commit: f5bf5ebb82e3af420dcd23d602b1be6cc86838e1
parent: 26bc5915727e0a0173c03cb49f5193dd612fb888
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Wed, 3 May 2017 02:04:16 +0200
Replace sprockets/browserify with Webpack (#2617)
* Replace browserify with webpack
* Add react-intl-translations-manager
* Do not minify in development, add offline-plugin for ServiceWorker background cache updates
* Adjust tests and dependencies
* Fix production deployments
* Fix tests
* More optimizations
* Improve travis cache for npm stuff
* Re-run travis
* Add back support for custom.scss as before
* Remove offline-plugin and babili
* Fix issue with Immutable.List().unshift(...values) not working as expected
* Make travis load schema instead of running all migrations in sequence
* Fix missing React import in WarningContainer. Optimize rendering performance by using ImmutablePureComponent instead of
React.PureComponent. ImmutablePureComponent uses Immutable.is() to compare props. Replace dynamic callback bindings in
<UI />
* Add react definitions to places that use JSX
* Add Procfile.dev for running rails, webpack and streaming API at the same time
Diffstat:
492 files changed, 21500 insertions(+), 18238 deletions(-)
diff --git a/.babelrc b/.babelrc
@@ -1,7 +1,25 @@
{
- "presets": ["es2015", "react"],
+ "presets": [
+ "es2015",
+ "react",
+ [
+ "env",
+ {
+ "loose": true,
+ "modules": false
+ }
+ ]
+ ],
"plugins": [
+ "transform-react-jsx-source",
+ "transform-react-jsx-self",
"transform-decorators-legacy",
- "transform-object-rest-spread"
+ "transform-object-rest-spread",
+ [
+ "react-intl",
+ {
+ "messagesDir": "./build/messages"
+ }
+ ]
]
}
diff --git a/.foreman b/.foreman
@@ -0,0 +1 @@
+procfile: Procfile.dev
diff --git a/.gitignore b/.gitignore
@@ -22,7 +22,7 @@ public/assets
.env
.env.production
node_modules/
-neo4j/
+build/
# Ignore Vagrant files
.vagrant/
@@ -43,3 +43,5 @@ redis
# Ignore vim files
*~
*.swp
+/public/packs
+/node_modules
diff --git a/.postcssrc.yml b/.postcssrc.yml
@@ -0,0 +1,4 @@
+plugins:
+ postcss-smart-import: {}
+ precss: {}
+ autoprefixer: {}
diff --git a/.travis.yml b/.travis.yml
@@ -1,9 +1,7 @@
language: ruby
cache:
bundler: true
- yarn: true
- directories:
- - node_modules
+ yarn: false
dist: trusty
sudo: false
@@ -42,7 +40,8 @@ install:
- yarn install
before_script:
- - bundle exec rails db:create db:migrate
+ - bundle exec rails db:create db:schema:load
+ - bundle exec rails assets:precompile
script:
- bundle exec rspec
diff --git a/Dockerfile b/Dockerfile
@@ -10,8 +10,6 @@ EXPOSE 3000 4000
WORKDIR /mastodon
-COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
-
RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories \
&& BUILD_DEPS=" \
postgresql-dev \
@@ -23,6 +21,7 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit
$BUILD_DEPS \
nodejs@edge \
nodejs-npm@edge \
+ git \
libpq \
libxml2 \
libxslt \
@@ -31,14 +30,14 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit
imagemagick@edge \
ca-certificates \
&& npm install -g npm@3 && npm install -g yarn \
- && bundle install --deployment --without test development \
- && yarn --ignore-optional \
- && yarn cache clean \
- && npm -g cache clean \
&& update-ca-certificates \
- && apk del $BUILD_DEPS \
&& rm -rf /tmp/* /var/cache/apk/*
+COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
+
+RUN bundle install --deployment --without test development \
+ && yarn --ignore-optional --pure-lockfile
+
COPY . /mastodon
-VOLUME /mastodon/public/system /mastodon/public/assets
+VOLUME /mastodon/public/system /mastodon/public/assets /mastodon/public/packs
diff --git a/Gemfile b/Gemfile
@@ -5,22 +5,19 @@ ruby '>= 2.3.0', '< 2.5.0'
gem 'pkg-config'
+gem 'puma'
gem 'rails', '~> 5.0.2'
-gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
-gem 'jquery-rails'
-gem 'puma'
gem 'hamlit-rails'
gem 'pg'
gem 'pghero'
gem 'dotenv-rails'
-gem 'font-awesome-rails'
gem 'best_in_place', '~> 3.0.1'
+gem 'aws-sdk', '>= 2.0'
gem 'paperclip', '~> 5.1'
gem 'paperclip-av-transcoder'
-gem 'aws-sdk', '>= 2.0'
gem 'addressable'
gem 'devise'
@@ -58,18 +55,18 @@ gem 'sprockets-rails', require: 'sprockets/railtie'
gem 'statsd-instrument'
gem 'twitter-text'
gem 'tzinfo-data'
+gem 'webpacker', '~>1.2'
gem 'whatlanguage'
+# For some reason the view specs start failing without this
gem 'react-rails'
-gem 'browserify-rails'
-gem 'autoprefixer-rails'
group :development, :test do
- gem 'rspec-rails'
- gem 'pry-rails'
- gem 'fuubar'
gem 'fabrication'
+ gem 'fuubar'
gem 'i18n-tasks', '~> 0.9.6'
+ gem 'pry-rails'
+ gem 'rspec-rails'
end
group :test do
@@ -83,24 +80,23 @@ group :test do
end
group :development do
- gem 'rubocop', '0.46.0', require: false
+ gem 'active_record_query_trace'
+ gem 'annotate'
gem 'better_errors'
gem 'binding_of_caller'
+ gem 'bullet'
gem 'letter_opener'
gem 'letter_opener_web'
- gem 'bullet'
- gem 'active_record_query_trace'
- gem 'annotate'
+ gem 'rubocop', '0.46.0', require: false
gem 'capistrano', '3.8.0'
gem 'capistrano-rails'
gem 'capistrano-rbenv'
gem 'capistrano-yarn'
- gem 'capistrano-faster-assets', '~> 1.0'
end
group :production do
+ gem 'lograge'
gem 'rails_12factor'
gem 'redis-rails'
- gem 'lograge'
end
diff --git a/Gemfile.lock b/Gemfile.lock
@@ -43,15 +43,13 @@ GEM
public_suffix (~> 2.0, >= 2.0.2)
airbrussh (1.2.0)
sshkit (>= 1.6.1, != 1.7.0)
- annotate (2.7.1)
- activerecord (>= 3.2, < 6.0)
- rake (>= 10.4, < 12.0)
+ annotate (2.6.5)
+ activerecord (>= 2.3.0)
+ rake (>= 0.8.7)
arel (7.1.4)
ast (2.3.0)
attr_encrypted (3.0.3)
encryptor (~> 3.0.0)
- autoprefixer-rails (6.7.7.2)
- execjs
av (0.9.0)
cocaine (~> 0.5.3)
aws-sdk (2.9.12)
@@ -76,10 +74,6 @@ GEM
rack (>= 0.9.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
- browserify-rails (4.1.0)
- addressable (>= 2.4.0)
- railties (>= 4.0.0, < 5.1)
- sprockets (>= 3.6.0)
builder (3.2.3)
bullet (5.5.1)
activesupport (>= 3.0.0)
@@ -92,8 +86,6 @@ GEM
capistrano-bundler (1.2.0)
capistrano (~> 3.1)
sshkit (~> 1.2)
- capistrano-faster-assets (1.0.2)
- capistrano (>= 3.1)
capistrano-rails (1.2.3)
capistrano (~> 3.1)
capistrano-bundler (~> 1.1)
@@ -161,8 +153,6 @@ GEM
faker (1.7.3)
i18n (~> 0.5)
fast_blank (1.0.0)
- font-awesome-rails (4.7.0.1)
- railties (>= 3.2, < 5.1)
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
@@ -210,10 +200,6 @@ GEM
rainbow (~> 2.2)
terminal-table (>= 1.5.1)
jmespath (1.3.1)
- jquery-rails (4.3.1)
- rails-dom-testing (>= 1, < 3)
- railties (>= 4.2.0)
- thor (>= 0.14, < 2.0)
json (2.1.0)
kaminari (1.0.1)
activesupport (>= 4.1.0)
@@ -257,6 +243,7 @@ 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.1.0)
@@ -348,8 +335,8 @@ GEM
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
- rake (11.3.0)
- react-rails (1.11.0)
+ rake (12.0.0)
+ react-rails (2.1.0)
babel-transpiler (>= 0.7.0)
connection_pool
execjs
@@ -410,13 +397,6 @@ GEM
crass (~> 1.0.2)
nokogiri (>= 1.4.4)
nokogumbo (~> 1.4.1)
- 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)
sidekiq (4.2.10)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
@@ -473,6 +453,10 @@ GEM
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
+ webpacker (1.2)
+ activesupport (>= 4.2)
+ multi_json (~> 1.2)
+ railties (>= 4.2)
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
@@ -487,15 +471,12 @@ DEPENDENCIES
active_record_query_trace
addressable
annotate
- autoprefixer-rails
aws-sdk (>= 2.0)
best_in_place (~> 3.0.1)
better_errors
binding_of_caller
- browserify-rails
bullet
capistrano (= 3.8.0)
- capistrano-faster-assets (~> 1.0)
capistrano-rails
capistrano-rbenv
capistrano-yarn
@@ -507,7 +488,6 @@ DEPENDENCIES
fabrication
faker
fast_blank
- font-awesome-rails
fuubar
goldfinger
hamlit-rails
@@ -517,7 +497,6 @@ DEPENDENCIES
http_accept_language
httplog
i18n-tasks (~> 0.9.6)
- jquery-rails
kaminari
letter_opener
letter_opener_web
@@ -554,7 +533,6 @@ DEPENDENCIES
rubocop (= 0.46.0)
ruby-oembed
sanitize
- sass-rails (~> 5.0)
sidekiq
sidekiq-unique-jobs
simple-navigation
@@ -566,6 +544,7 @@ DEPENDENCIES
tzinfo-data
uglifier (>= 1.3.0)
webmock
+ webpacker (~> 1.2)
whatlanguage
RUBY VERSION
diff --git a/Procfile.dev b/Procfile.dev
@@ -0,0 +1,3 @@
+web: bundle exec rails s -p 3000
+stream: yarn run start
+webpack: ./bin/webpack-dev-server
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
@@ -1,15 +0,0 @@
-// This is a manifest file that'll be compiled into application.js, which will include all the files
-// listed below.
-//
-// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
-//
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// compiled file.
-//
-// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
-// about supported directives.
-//
-//= require jquery2
-//= require jquery_ujs
-//= require components
diff --git a/app/assets/javascripts/application_public.js b/app/assets/javascripts/application_public.js
@@ -1,9 +0,0 @@
-//= require jquery2
-//= require jquery_ujs
-//= require extras
-//= require best_in_place
-//= require local_time
-
-$(function () {
- $(".best_in_place").best_in_place();
-});
diff --git a/app/assets/javascripts/components.js b/app/assets/javascripts/components.js
@@ -1,15 +0,0 @@
-//= require_self
-//= require react_ujs
-
-window.React = require('react');
-window.ReactDOM = require('react-dom');
-window.Perf = require('react-addons-perf');
-
-if (!window.Intl) {
- require('intl');
- require('intl/locale-data/jsonp/en.js');
-}
-
-//= require_tree ./components
-
-window.Mastodon = require('./components/containers/mastodon');
diff --git a/app/assets/javascripts/components/components/account.jsx b/app/assets/javascripts/components/components/account.jsx
@@ -1,91 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import Avatar from './avatar';
-import DisplayName from './display_name';
-import Permalink from './permalink';
-import IconButton from './icon_button';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- follow: { id: 'account.follow', defaultMessage: 'Follow' },
- unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
- requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
- unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
- unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }
-});
-
-class Account extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleFollow = this.handleFollow.bind(this);
- this.handleBlock = this.handleBlock.bind(this);
- this.handleMute = this.handleMute.bind(this);
- }
-
- handleFollow () {
- this.props.onFollow(this.props.account);
- }
-
- handleBlock () {
- this.props.onBlock(this.props.account);
- }
-
- handleMute () {
- this.props.onMute(this.props.account);
- }
-
- render () {
- const { account, me, intl } = this.props;
-
- if (!account) {
- return <div />;
- }
-
- let buttons;
-
- if (account.get('id') !== me && account.get('relationship', null) !== null) {
- const following = account.getIn(['relationship', 'following']);
- const requested = account.getIn(['relationship', 'requested']);
- const blocking = account.getIn(['relationship', 'blocking']);
- const muting = account.getIn(['relationship', 'muting']);
-
- if (requested) {
- buttons = <IconButton disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} />
- } else if (blocking) {
- buttons = <IconButton active={true} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
- } else if (muting) {
- buttons = <IconButton active={true} icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
- } else {
- buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
- }
- }
-
- return (
- <div className='account'>
- <div className='account__wrapper'>
- <Permalink key={account.get('id')} className='account__display-name' href={account.get('url')} to={`/accounts/${account.get('id')}`}>
- <div className='account__avatar-wrapper'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={36} /></div>
- <DisplayName account={account} />
- </Permalink>
-
- <div className='account__relationship'>
- {buttons}
- </div>
- </div>
- </div>
- );
- }
-
-}
-
-Account.propTypes = {
- account: ImmutablePropTypes.map.isRequired,
- me: PropTypes.number.isRequired,
- onFollow: PropTypes.func.isRequired,
- onBlock: PropTypes.func.isRequired,
- onMute: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-}
-
-export default injectIntl(Account);
diff --git a/app/assets/javascripts/components/components/attachment_list.jsx b/app/assets/javascripts/components/components/attachment_list.jsx
@@ -1,32 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
-
-class AttachmentList extends React.PureComponent {
-
- render () {
- const { media } = this.props;
-
- return (
- <div className='attachment-list'>
- <div className='attachment-list__icon'>
- <i className='fa fa-link' />
- </div>
-
- <ul className='attachment-list__list'>
- {media.map(attachment =>
- <li key={attachment.get('id')}>
- <a href={attachment.get('remote_url')} target='_blank' rel='noopener'>{filename(attachment.get('remote_url'))}</a>
- </li>
- )}
- </ul>
- </div>
- );
- }
-}
-
-AttachmentList.propTypes = {
- media: ImmutablePropTypes.list.isRequired
-};
-
-export default AttachmentList;
diff --git a/app/assets/javascripts/components/components/autosuggest_textarea.jsx b/app/assets/javascripts/components/components/autosuggest_textarea.jsx
@@ -1,211 +0,0 @@
-import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { isRtl } from '../rtl';
-
-const textAtCursorMatchesToken = (str, caretPosition) => {
- let word;
-
- let left = str.slice(0, caretPosition).search(/\S+$/);
- let right = str.slice(caretPosition).search(/\s/);
-
- if (right < 0) {
- word = str.slice(left);
- } else {
- word = str.slice(left, right + caretPosition);
- }
-
- if (!word || word.trim().length < 2 || word[0] !== '@') {
- return [null, null];
- }
-
- word = word.trim().toLowerCase().slice(1);
-
- if (word.length > 0) {
- return [left + 1, word];
- } else {
- return [null, null];
- }
-};
-
-class AutosuggestTextarea extends React.Component {
-
- constructor (props, context) {
- super(props, context);
- this.state = {
- suggestionsHidden: false,
- selectedSuggestion: 0,
- lastToken: null,
- tokenStart: 0
- };
- this.onChange = this.onChange.bind(this);
- this.onKeyDown = this.onKeyDown.bind(this);
- this.onBlur = this.onBlur.bind(this);
- this.onSuggestionClick = this.onSuggestionClick.bind(this);
- this.setTextarea = this.setTextarea.bind(this);
- this.onPaste = this.onPaste.bind(this);
- }
-
- onChange (e) {
- const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
-
- if (token !== null && this.state.lastToken !== token) {
- this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
- this.props.onSuggestionsFetchRequested(token);
- } else if (token === null) {
- this.setState({ lastToken: null });
- this.props.onSuggestionsClearRequested();
- }
-
- // auto-resize textarea
- e.target.style.height = `${e.target.scrollHeight}px`;
-
- this.props.onChange(e);
- }
-
- onKeyDown (e) {
- const { suggestions, disabled } = this.props;
- const { selectedSuggestion, suggestionsHidden } = this.state;
-
- if (disabled) {
- e.preventDefault();
- return;
- }
-
- switch(e.key) {
- case 'Escape':
- if (!suggestionsHidden) {
- e.preventDefault();
- this.setState({ suggestionsHidden: true });
- }
-
- break;
- case 'ArrowDown':
- if (suggestions.size > 0 && !suggestionsHidden) {
- e.preventDefault();
- this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
- }
-
- break;
- case 'ArrowUp':
- if (suggestions.size > 0 && !suggestionsHidden) {
- e.preventDefault();
- this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
- }
-
- break;
- case 'Enter':
- case 'Tab':
- // Select suggestion
- if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
- e.preventDefault();
- e.stopPropagation();
- this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
- }
-
- break;
- }
-
- if (e.defaultPrevented || !this.props.onKeyDown) {
- return;
- }
-
- this.props.onKeyDown(e);
- }
-
- onBlur () {
- // If we hide the suggestions immediately, then this will prevent the
- // onClick for the suggestions themselves from firing.
- // Setting a short window for that to take place before hiding the
- // suggestions ensures that can't happen.
- setTimeout(() => {
- this.setState({ suggestionsHidden: true });
- }, 100);
- }
-
- onSuggestionClick (suggestion, e) {
- e.preventDefault();
- this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
- this.textarea.focus();
- }
-
- componentWillReceiveProps (nextProps) {
- if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
- this.setState({ suggestionsHidden: false });
- }
- }
-
- setTextarea (c) {
- this.textarea = c;
- }
-
- onPaste (e) {
- if (e.clipboardData && e.clipboardData.files.length === 1) {
- this.props.onPaste(e.clipboardData.files)
- e.preventDefault();
- }
- }
-
- reset () {
- this.textarea.style.height = 'auto';
- }
-
- render () {
- const { value, suggestions, disabled, placeholder, onKeyUp } = this.props;
- const { suggestionsHidden, selectedSuggestion } = this.state;
- const style = { direction: 'ltr' };
-
- if (isRtl(value)) {
- style.direction = 'rtl';
- }
-
- return (
- <div className='autosuggest-textarea'>
- <textarea
- ref={this.setTextarea}
- className='autosuggest-textarea__textarea'
- disabled={disabled}
- placeholder={placeholder}
- autoFocus={true}
- value={value}
- onChange={this.onChange}
- onKeyDown={this.onKeyDown}
- onKeyUp={onKeyUp}
- onBlur={this.onBlur}
- onPaste={this.onPaste}
- style={style}
- />
-
- <div style={{ display: (suggestions.size > 0 && !suggestionsHidden) ? 'block' : 'none' }} className='autosuggest-textarea__suggestions'>
- {suggestions.map((suggestion, i) => (
- <div
- role='button'
- tabIndex='0'
- key={suggestion}
- className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`}
- onClick={this.onSuggestionClick.bind(this, suggestion)}>
- <AutosuggestAccountContainer id={suggestion} />
- </div>
- ))}
- </div>
- </div>
- );
- }
-
-};
-
-AutosuggestTextarea.propTypes = {
- value: PropTypes.string,
- suggestions: ImmutablePropTypes.list,
- disabled: PropTypes.bool,
- placeholder: PropTypes.string,
- onSuggestionSelected: PropTypes.func.isRequired,
- onSuggestionsClearRequested: PropTypes.func.isRequired,
- onSuggestionsFetchRequested: PropTypes.func.isRequired,
- onChange: PropTypes.func.isRequired,
- onKeyUp: PropTypes.func,
- onKeyDown: PropTypes.func,
- onPaste: PropTypes.func.isRequired,
-};
-
-export default AutosuggestTextarea;
diff --git a/app/assets/javascripts/components/components/avatar.jsx b/app/assets/javascripts/components/components/avatar.jsx
@@ -1,63 +0,0 @@
-import PropTypes from 'prop-types';
-
-class Avatar extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.state = {
- hovering: false
- };
- this.handleMouseEnter = this.handleMouseEnter.bind(this);
- this.handleMouseLeave = this.handleMouseLeave.bind(this);
- }
-
- handleMouseEnter () {
- this.setState({ hovering: true });
- }
-
- handleMouseLeave () {
- this.setState({ hovering: false });
- }
-
- render () {
- const { src, size, staticSrc, animate } = this.props;
- const { hovering } = this.state;
-
- 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 (
- <div
- className='account__avatar'
- onMouseEnter={this.handleMouseEnter}
- onMouseLeave={this.handleMouseLeave}
- style={style}
- />
- );
- }
-
-}
-
-Avatar.propTypes = {
- src: PropTypes.string.isRequired,
- staticSrc: PropTypes.string,
- size: PropTypes.number.isRequired,
- style: PropTypes.object,
- animate: PropTypes.bool
-};
-
-Avatar.defaultProps = {
- animate: false
-};
-
-export default Avatar;
diff --git a/app/assets/javascripts/components/components/button.jsx b/app/assets/javascripts/components/components/button.jsx
@@ -1,49 +0,0 @@
-import PropTypes from 'prop-types';
-
-class Button extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleClick (e) {
- if (!this.props.disabled) {
- this.props.onClick();
- }
- }
-
- render () {
- const style = {
- display: this.props.block ? 'block' : 'inline-block',
- width: this.props.block ? '100%' : 'auto',
- padding: `0 ${this.props.size / 2.25}px`,
- height: `${this.props.size}px`,
- lineHeight: `${this.props.size}px`
- };
-
- return (
- <button className={`button ${this.props.secondary ? 'button-secondary' : ''}`} disabled={this.props.disabled} onClick={this.handleClick} style={{ ...style, ...this.props.style }}>
- {this.props.text || this.props.children}
- </button>
- );
- }
-
-}
-
-Button.propTypes = {
- text: PropTypes.node,
- onClick: PropTypes.func,
- disabled: PropTypes.bool,
- block: PropTypes.bool,
- secondary: PropTypes.bool,
- size: PropTypes.number,
- style: PropTypes.object,
- children: PropTypes.node
-};
-
-Button.defaultProps = {
- size: 36
-};
-
-export default Button;
diff --git a/app/assets/javascripts/components/components/collapsable.jsx b/app/assets/javascripts/components/components/collapsable.jsx
@@ -1,20 +0,0 @@
-import { Motion, spring } from 'react-motion';
-import PropTypes from 'prop-types';
-
-const Collapsable = ({ fullHeight, isVisible, children }) => (
- <Motion defaultStyle={{ opacity: !isVisible ? 0 : 100, height: isVisible ? fullHeight : 0 }} style={{ opacity: spring(!isVisible ? 0 : 100), height: spring(!isVisible ? 0 : fullHeight) }}>
- {({ opacity, height }) =>
- <div style={{ height: `${height}px`, overflow: 'hidden', opacity: opacity / 100, display: Math.floor(opacity) === 0 ? 'none' : 'block' }}>
- {children}
- </div>
- }
- </Motion>
-);
-
-Collapsable.propTypes = {
- fullHeight: PropTypes.number.isRequired,
- isVisible: PropTypes.bool.isRequired,
- children: PropTypes.node.isRequired
-};
-
-export default Collapsable;
diff --git a/app/assets/javascripts/components/components/column_back_button.jsx b/app/assets/javascripts/components/components/column_back_button.jsx
@@ -1,31 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-import PropTypes from 'prop-types';
-
-class ColumnBackButton extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleClick () {
- if (window.history && window.history.length === 1) this.context.router.push("/");
- else this.context.router.goBack();
- }
-
- render () {
- return (
- <div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button'>
- <i className='fa fa-fw fa-chevron-left column-back-button__icon'/>
- <FormattedMessage id='column_back_button.label' defaultMessage='Back' />
- </div>
- );
- }
-
-};
-
-ColumnBackButton.contextTypes = {
- router: PropTypes.object
-};
-
-export default ColumnBackButton;
diff --git a/app/assets/javascripts/components/components/column_back_button_slim.jsx b/app/assets/javascripts/components/components/column_back_button_slim.jsx
@@ -1,31 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-import PropTypes from 'prop-types';
-
-class ColumnBackButtonSlim extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleClick () {
- this.context.router.push('/');
- }
-
- render () {
- return (
- <div className='column-back-button--slim'>
- <div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
- <i className='fa fa-fw fa-chevron-left column-back-button__icon' />
- <FormattedMessage id='column_back_button.label' defaultMessage='Back' />
- </div>
- </div>
- );
- }
-}
-
-ColumnBackButtonSlim.contextTypes = {
- router: PropTypes.object
-};
-
-export default ColumnBackButtonSlim;
diff --git a/app/assets/javascripts/components/components/column_collapsable.jsx b/app/assets/javascripts/components/components/column_collapsable.jsx
@@ -1,56 +0,0 @@
-import { Motion, spring } from 'react-motion';
-import PropTypes from 'prop-types';
-
-class ColumnCollapsable extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.state = {
- collapsed: true
- };
-
- this.handleToggleCollapsed = this.handleToggleCollapsed.bind(this);
- }
-
- handleToggleCollapsed () {
- const currentState = this.state.collapsed;
-
- this.setState({ collapsed: !currentState });
-
- if (!currentState && this.props.onCollapse) {
- this.props.onCollapse();
- }
- }
-
- render () {
- const { icon, title, fullHeight, children } = this.props;
- const { collapsed } = this.state;
- const collapsedClassName = collapsed ? 'collapsable-collapsed' : 'collapsable';
-
- return (
- <div className='column-collapsable'>
- <div role='button' tabIndex='0' title={`${title}`} className={`column-icon ${collapsedClassName}`} onClick={this.handleToggleCollapsed}>
- <i className={`fa fa-${icon}`} />
- </div>
-
- <Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : fullHeight, collapsed ? undefined : { stiffness: 150, damping: 9 }) }}>
- {({ opacity, height }) =>
- <div style={{ overflow: height === fullHeight ? 'auto' : 'hidden', height: `${height}px`, opacity: opacity / 100, maxHeight: '70vh' }}>
- {children}
- </div>
- }
- </Motion>
- </div>
- );
- }
-}
-
-ColumnCollapsable.propTypes = {
- icon: PropTypes.string.isRequired,
- title: PropTypes.string,
- fullHeight: PropTypes.number.isRequired,
- children: PropTypes.node,
- onCollapse: PropTypes.func
-};
-
-export default ColumnCollapsable;
diff --git a/app/assets/javascripts/components/components/display_name.jsx b/app/assets/javascripts/components/components/display_name.jsx
@@ -1,24 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import escapeTextContentForBrowser from 'escape-html';
-import emojify from '../emoji';
-
-class DisplayName extends React.PureComponent {
-
- render () {
- const displayName = this.props.account.get('display_name').length === 0 ? this.props.account.get('username') : this.props.account.get('display_name');
- const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
-
- return (
- <span className='display-name'>
- <strong className='display-name__html' dangerouslySetInnerHTML={displayNameHTML} /> <span className='display-name__account'>@{this.props.account.get('acct')}</span>
- </span>
- );
- }
-
-};
-
-DisplayName.propTypes = {
- account: ImmutablePropTypes.map.isRequired
-}
-
-export default DisplayName;
diff --git a/app/assets/javascripts/components/components/dropdown_menu.jsx b/app/assets/javascripts/components/components/dropdown_menu.jsx
@@ -1,78 +0,0 @@
-import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
-import PropTypes from 'prop-types';
-
-class DropdownMenu extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.state = {
- direction: 'left'
- };
- this.setRef = this.setRef.bind(this);
- this.renderItem = this.renderItem.bind(this);
- }
-
- setRef (c) {
- this.dropdown = c;
- }
-
- handleClick (i, e) {
- const { action } = this.props.items[i];
-
- if (typeof action === 'function') {
- e.preventDefault();
- action();
- this.dropdown.hide();
- }
- }
-
- renderItem (item, i) {
- if (item === null) {
- return <li key={ 'sep' + i } className='dropdown__sep' />;
- }
-
- const { text, action, href = '#' } = item;
-
- return (
- <li className='dropdown__content-list-item' key={ text + i }>
- <a href={href} target='_blank' rel='noopener' onClick={this.handleClick.bind(this, i)} className='dropdown__content-list-link'>
- {text}
- </a>
- </li>
- );
- }
-
- render () {
- const { icon, items, size, direction, ariaLabel } = this.props;
- const directionClass = (direction === "left") ? "dropdown__left" : "dropdown__right";
-
- return (
- <Dropdown ref={this.setRef}>
- <DropdownTrigger className='icon-button' style={{ fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }} aria-label={ariaLabel}>
- <i className={ `fa fa-fw fa-${icon} dropdown__icon` } aria-hidden={true} />
- </DropdownTrigger>
-
- <DropdownContent className={directionClass}>
- <ul className='dropdown__content-list'>
- {items.map(this.renderItem)}
- </ul>
- </DropdownContent>
- </Dropdown>
- );
- }
-
-}
-
-DropdownMenu.propTypes = {
- icon: PropTypes.string.isRequired,
- items: PropTypes.array.isRequired,
- size: PropTypes.number.isRequired,
- direction: PropTypes.string,
- ariaLabel: PropTypes.string
-};
-
-DropdownMenu.defaultProps = {
- ariaLabel: "Menu"
-};
-
-export default DropdownMenu;
diff --git a/app/assets/javascripts/components/components/extended_video_player.jsx b/app/assets/javascripts/components/components/extended_video_player.jsx
@@ -1,53 +0,0 @@
-import PropTypes from 'prop-types';
-
-class ExtendedVideoPlayer extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleLoadedData = this.handleLoadedData.bind(this);
- this.setRef = this.setRef.bind(this);
- }
-
- 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 (
- <div className='extended-video-player'>
- <video
- ref={this.setRef}
- src={this.props.src}
- autoPlay
- muted={this.props.muted}
- controls={this.props.controls}
- loop={!this.props.controls}
- />
- </div>
- );
- }
-
-}
-
-ExtendedVideoPlayer.propTypes = {
- src: PropTypes.string.isRequired,
- time: PropTypes.number,
- controls: PropTypes.bool.isRequired,
- muted: PropTypes.bool.isRequired
-};
-
-export default ExtendedVideoPlayer;
diff --git a/app/assets/javascripts/components/components/icon_button.jsx b/app/assets/javascripts/components/components/icon_button.jsx
@@ -1,95 +0,0 @@
-import { Motion, spring } from 'react-motion';
-import PropTypes from 'prop-types';
-
-class IconButton extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleClick (e) {
- e.preventDefault();
-
- if (!this.props.disabled) {
- this.props.onClick(e);
- }
- }
-
- render () {
- let style = {
- fontSize: `${this.props.size}px`,
- width: `${this.props.size * 1.28571429}px`,
- height: `${this.props.size * 1.28571429}px`,
- lineHeight: `${this.props.size}px`,
- ...this.props.style
- };
-
- if (this.props.active) {
- style = { ...style, ...this.props.activeStyle };
- }
-
- const classes = ['icon-button'];
-
- if (this.props.active) {
- classes.push('active');
- }
-
- if (this.props.disabled) {
- classes.push('disabled');
- }
-
- if (this.props.inverted) {
- classes.push('inverted');
- }
-
- if (this.props.overlay) {
- classes.push('overlayed');
- }
-
- if (this.props.className) {
- classes.push(this.props.className)
- }
-
- return (
- <Motion defaultStyle={{ rotate: this.props.active ? -360 : 0 }} style={{ rotate: this.props.animate ? spring(this.props.active ? -360 : 0, { stiffness: 120, damping: 7 }) : 0 }}>
- {({ rotate }) =>
- <button
- aria-label={this.props.title}
- title={this.props.title}
- className={classes.join(' ')}
- onClick={this.handleClick}
- style={style}>
- <i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
- </button>
- }
- </Motion>
- );
- }
-
-}
-
-IconButton.propTypes = {
- className: PropTypes.string,
- title: PropTypes.string.isRequired,
- icon: PropTypes.string.isRequired,
- onClick: PropTypes.func,
- size: PropTypes.number,
- active: PropTypes.bool,
- style: PropTypes.object,
- activeStyle: PropTypes.object,
- disabled: PropTypes.bool,
- inverted: PropTypes.bool,
- animate: PropTypes.bool,
- overlay: PropTypes.bool
-};
-
-IconButton.defaultProps = {
- size: 18,
- active: false,
- disabled: false,
- animate: false,
- overlay: false
-};
-
-export default IconButton;
diff --git a/app/assets/javascripts/components/components/load_more.jsx b/app/assets/javascripts/components/components/load_more.jsx
@@ -1,14 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-import PropTypes from 'prop-types';
-
-const LoadMore = ({ onClick }) => (
- <a href="#" className='load-more' role='button' onClick={onClick}>
- <FormattedMessage id='status.load_more' defaultMessage='Load more' />
- </a>
-);
-
-LoadMore.propTypes = {
- onClick: PropTypes.func
-};
-
-export default LoadMore;
diff --git a/app/assets/javascripts/components/components/loading_indicator.jsx b/app/assets/javascripts/components/components/loading_indicator.jsx
@@ -1,9 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-const LoadingIndicator = () => (
- <div className='loading-indicator'>
- <FormattedMessage id='loading_indicator.label' defaultMessage='Loading...' />
- </div>
-);
-
-export default LoadingIndicator;
diff --git a/app/assets/javascripts/components/components/media_gallery.jsx b/app/assets/javascripts/components/components/media_gallery.jsx
@@ -1,195 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import IconButton from './icon_button';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { isIOS } from '../is_mobile';
-
-const messages = defineMessages({
- toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }
-});
-
-class Item extends React.PureComponent {
- constructor (props, context) {
- super(props, context);
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleClick (e) {
- const { index, onClick } = this.props;
-
- if (e.button === 0) {
- e.preventDefault();
- onClick(index);
- }
-
- e.stopPropagation();
- }
-
- render () {
- const { attachment, index, size } = this.props;
-
- let width = 50;
- let height = 100;
- let top = 'auto';
- let left = 'auto';
- let bottom = 'auto';
- let right = 'auto';
-
- if (size === 1) {
- width = 100;
- }
-
- if (size === 4 || (size === 3 && index > 0)) {
- height = 50;
- }
-
- if (size === 2) {
- if (index === 0) {
- right = '2px';
- } else {
- left = '2px';
- }
- } else if (size === 3) {
- if (index === 0) {
- right = '2px';
- } else if (index > 0) {
- left = '2px';
- }
-
- if (index === 1) {
- bottom = '2px';
- } else if (index > 1) {
- top = '2px';
- }
- } else if (size === 4) {
- if (index === 0 || index === 2) {
- right = '2px';
- }
-
- if (index === 1 || index === 3) {
- left = '2px';
- }
-
- if (index < 2) {
- bottom = '2px';
- } else {
- top = '2px';
- }
- }
-
- let thumbnail = '';
-
- if (attachment.get('type') === 'image') {
- thumbnail = (
- <a
- className='media-gallery__item-thumbnail'
- href={attachment.get('remote_url') || attachment.get('url')}
- onClick={this.handleClick}
- target='_blank'
- style={{ backgroundImage: `url(${attachment.get('preview_url')})` }}
- />
- );
- } else if (attachment.get('type') === 'gifv') {
- const autoPlay = !isIOS() && this.props.autoPlayGif;
-
- thumbnail = (
- <div className={`media-gallery__gifv ${autoPlay ? 'autoplay' : ''}`}>
- <video
- className='media-gallery__item-gifv-thumbnail'
- role='application'
- src={attachment.get('url')}
- onClick={this.handleClick}
- autoPlay={autoPlay}
- loop={true}
- muted={true}
- />
-
- <span className='media-gallery__gifv__label'>GIF</span>
- </div>
- );
- }
-
- return (
- <div className='media-gallery__item' key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
- {thumbnail}
- </div>
- );
- }
-
-}
-
-Item.propTypes = {
- attachment: ImmutablePropTypes.map.isRequired,
- index: PropTypes.number.isRequired,
- size: PropTypes.number.isRequired,
- onClick: PropTypes.func.isRequired,
- autoPlayGif: PropTypes.bool.isRequired
-};
-
-class MediaGallery extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.state = {
- visible: !props.sensitive
- };
- this.handleOpen = this.handleOpen.bind(this);
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleOpen (e) {
- this.setState({ visible: !this.state.visible });
- }
-
- handleClick (index) {
- this.props.onOpenMedia(this.props.media, index);
- }
-
- render () {
- const { media, intl, sensitive } = this.props;
-
- let children;
-
- if (!this.state.visible) {
- let warning;
-
- if (sensitive) {
- warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
- } else {
- warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
- }
-
- children = (
- <div role='button' tabIndex='0' className='media-spoiler' onClick={this.handleOpen}>
- <span className='media-spoiler__warning'>{warning}</span>
- <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
- </div>
- );
- } else {
- const size = media.take(4).size;
- children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} autoPlayGif={this.props.autoPlayGif} index={i} size={size} />);
- }
-
- return (
- <div className='media-gallery' style={{ height: `${this.props.height}px` }}>
- <div className='spoiler-button' style={{ display: !this.state.visible ? 'none' : 'block' }}>
- <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
- </div>
-
- {children}
- </div>
- );
- }
-
-}
-
-MediaGallery.propTypes = {
- sensitive: PropTypes.bool,
- media: ImmutablePropTypes.list.isRequired,
- height: PropTypes.number.isRequired,
- onOpenMedia: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- autoPlayGif: PropTypes.bool.isRequired
-};
-
-export default injectIntl(MediaGallery);
diff --git a/app/assets/javascripts/components/components/missing_indicator.jsx b/app/assets/javascripts/components/components/missing_indicator.jsx
@@ -1,9 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-const MissingIndicator = () => (
- <div className='missing-indicator'>
- <FormattedMessage id='missing_indicator.label' defaultMessage='Not found' />
- </div>
-);
-
-export default MissingIndicator;
diff --git a/app/assets/javascripts/components/components/permalink.jsx b/app/assets/javascripts/components/components/permalink.jsx
@@ -1,36 +0,0 @@
-import PropTypes from 'prop-types';
-
-class Permalink extends React.Component {
-
- constructor (props, context) {
- super(props, context);
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleClick (e) {
- if (e.button === 0) {
- e.preventDefault();
- this.context.router.push(this.props.to);
- }
- }
-
- render () {
- const { href, children, className, ...other } = this.props;
-
- return <a href={href} onClick={this.handleClick} {...other} className={'permalink ' + className}>{children}</a>;
- }
-
-}
-
-Permalink.contextTypes = {
- router: PropTypes.object
-};
-
-Permalink.propTypes = {
- className: PropTypes.string,
- href: PropTypes.string.isRequired,
- to: PropTypes.string.isRequired,
- children: PropTypes.node
-};
-
-export default Permalink;
diff --git a/app/assets/javascripts/components/components/relative_timestamp.jsx b/app/assets/javascripts/components/components/relative_timestamp.jsx
@@ -1,19 +0,0 @@
-import { injectIntl, FormattedRelative } from 'react-intl';
-import PropTypes from 'prop-types';
-
-const RelativeTimestamp = ({ intl, timestamp }) => {
- const date = new Date(timestamp);
-
- return (
- <time dateTime={timestamp} title={intl.formatDate(date, { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' })}>
- <FormattedRelative value={date} />
- </time>
- );
-};
-
-RelativeTimestamp.propTypes = {
- intl: PropTypes.object.isRequired,
- timestamp: PropTypes.string.isRequired
-};
-
-export default injectIntl(RelativeTimestamp);
diff --git a/app/assets/javascripts/components/components/status.jsx b/app/assets/javascripts/components/components/status.jsx
@@ -1,121 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import Avatar from './avatar';
-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';
-import emojify from '../emoji';
-import escapeTextContentForBrowser from 'escape-html';
-
-class Status extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleClick = this.handleClick.bind(this);
- this.handleAccountClick = this.handleAccountClick.bind(this);
- }
-
- handleClick () {
- const { status } = this.props;
- this.context.router.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
- }
-
- handleAccountClick (id, e) {
- if (e.button === 0) {
- e.preventDefault();
- this.context.router.push(`/accounts/${id}`);
- }
- }
-
- render () {
- let media = '';
- const { status, ...other } = this.props;
-
- if (status === null) {
- return <div />;
- }
-
- if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
- let displayName = status.getIn(['account', 'display_name']);
-
- if (displayName.length === 0) {
- displayName = status.getIn(['account', 'username']);
- }
-
- const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
-
- return (
- <div className='status__wrapper'>
- <div className='status__prepend'>
- <div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
- <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={displayNameHTML} /></a> }} />
- </div>
-
- <Status {...other} wrapped={true} status={status.get('reblog')} />
- </div>
- );
- }
-
- if (status.get('media_attachments').size > 0 && !this.props.muted) {
- if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
-
- } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
- media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />;
- } else {
- media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
- }
- }
-
- return (
- <div className={this.props.muted ? 'status muted' : 'status'}>
- <div className='status__info'>
- <div className='status__info-time'>
- <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
- </div>
-
- <a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name'>
- <div className='status__avatar'>
- <Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} />
- </div>
-
- <DisplayName account={status.get('account')} />
- </a>
- </div>
-
- <StatusContent status={status} onClick={this.handleClick} />
-
- {media}
-
- <StatusActionBar {...this.props} />
- </div>
- );
- }
-
-}
-
-Status.contextTypes = {
- router: PropTypes.object
-};
-
-Status.propTypes = {
- status: ImmutablePropTypes.map,
- wrapped: PropTypes.bool,
- onReply: PropTypes.func,
- onFavourite: PropTypes.func,
- onReblog: PropTypes.func,
- onDelete: PropTypes.func,
- onOpenMedia: PropTypes.func,
- onOpenVideo: PropTypes.func,
- onBlock: PropTypes.func,
- me: PropTypes.number,
- boostModal: PropTypes.bool,
- autoPlayGif: PropTypes.bool,
- muted: PropTypes.bool
-};
-
-export default Status;
diff --git a/app/assets/javascripts/components/components/status_action_bar.jsx b/app/assets/javascripts/components/components/status_action_bar.jsx
@@ -1,137 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import IconButton from './icon_button';
-import DropdownMenu from './dropdown_menu';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- delete: { id: 'status.delete', defaultMessage: 'Delete' },
- mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
- mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
- block: { id: 'account.block', defaultMessage: 'Block @{name}' },
- reply: { id: 'status.reply', defaultMessage: 'Reply' },
- replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
- reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
- cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
- favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
- open: { id: 'status.open', defaultMessage: 'Expand this status' },
- report: { id: 'status.report', defaultMessage: 'Report @{name}' }
-});
-
-class StatusActionBar extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleReplyClick = this.handleReplyClick.bind(this);
- this.handleFavouriteClick = this.handleFavouriteClick.bind(this);
- this.handleReblogClick = this.handleReblogClick.bind(this);
- this.handleDeleteClick = this.handleDeleteClick.bind(this);
- this.handleMentionClick = this.handleMentionClick.bind(this);
- this.handleMuteClick = this.handleMuteClick.bind(this);
- this.handleBlockClick = this.handleBlockClick.bind(this);
- this.handleOpen = this.handleOpen.bind(this);
- this.handleReport = this.handleReport.bind(this);
- }
-
- handleReplyClick () {
- this.props.onReply(this.props.status, this.context.router);
- }
-
- handleFavouriteClick () {
- this.props.onFavourite(this.props.status);
- }
-
- handleReblogClick (e) {
- this.props.onReblog(this.props.status, e);
- }
-
- handleDeleteClick () {
- this.props.onDelete(this.props.status);
- }
-
- handleMentionClick () {
- this.props.onMention(this.props.status.get('account'), this.context.router);
- }
-
- handleMuteClick () {
- this.props.onMute(this.props.status.get('account'));
- }
-
- handleBlockClick () {
- this.props.onBlock(this.props.status.get('account'));
- }
-
- handleOpen () {
- this.context.router.push(`/statuses/${this.props.status.get('id')}`);
- }
-
- handleReport () {
- this.props.onReport(this.props.status);
- this.context.router.push('/report');
- }
-
- render () {
- const { status, me, intl } = this.props;
- const reblog_disabled = status.get('visibility') === 'private' || status.get('visibility') === 'direct';
- let menu = [];
-
- menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
- menu.push(null);
-
- if (status.getIn(['account', 'id']) === me) {
- menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
- } else {
- menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
- menu.push(null);
- menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
- menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
- menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
- }
-
- let reblogIcon = 'retweet';
- if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
- else if (status.get('visibility') === 'private') reblogIcon = 'lock';
- let reply_icon;
- let reply_title;
- if (status.get('in_reply_to_id', null) === null) {
- reply_icon = "reply";
- reply_title = intl.formatMessage(messages.reply);
- } else {
- reply_icon = "reply-all";
- reply_title = intl.formatMessage(messages.replyAll);
- }
-
- return (
- <div className='status__action-bar'>
- <div className='status__action-bar-button-wrapper'><IconButton title={reply_title} icon={reply_icon} onClick={this.handleReplyClick} /></div>
- <div className='status__action-bar-button-wrapper'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
- <div className='status__action-bar-button-wrapper'><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} className='star-icon' /></div>
-
- <div className='status__action-bar-dropdown'>
- <DropdownMenu items={menu} icon='ellipsis-h' size={18} direction="right" ariaLabel="More"/>
- </div>
- </div>
- );
- }
-
-}
-
-StatusActionBar.contextTypes = {
- router: PropTypes.object
-};
-
-StatusActionBar.propTypes = {
- status: ImmutablePropTypes.map.isRequired,
- onReply: PropTypes.func,
- onFavourite: PropTypes.func,
- onReblog: PropTypes.func,
- onDelete: PropTypes.func,
- onMention: PropTypes.func,
- onMute: PropTypes.func,
- onBlock: PropTypes.func,
- onReport: PropTypes.func,
- me: PropTypes.number.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(StatusActionBar);
diff --git a/app/assets/javascripts/components/components/status_content.jsx b/app/assets/javascripts/components/components/status_content.jsx
@@ -1,157 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import escapeTextContentForBrowser from 'escape-html';
-import PropTypes from 'prop-types';
-import emojify from '../emoji';
-import { isRtl } from '../rtl';
-import { FormattedMessage } from 'react-intl';
-import Permalink from './permalink';
-
-class StatusContent extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.state = {
- hidden: true
- };
- this.onMentionClick = this.onMentionClick.bind(this);
- this.onHashtagClick = this.onHashtagClick.bind(this);
- this.handleMouseDown = this.handleMouseDown.bind(this)
- this.handleMouseUp = this.handleMouseUp.bind(this);
- this.handleSpoilerClick = this.handleSpoilerClick.bind(this);
- };
-
- componentDidMount () {
- const node = ReactDOM.findDOMNode(this);
- const links = node.querySelectorAll('a');
-
- 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);
- link.setAttribute('title', mention.get('acct'));
- } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
- link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
- } else if (media) {
- link.innerHTML = '<i class="fa fa-fw fa-photo"></i>';
- } else {
- link.setAttribute('target', '_blank');
- link.setAttribute('rel', 'noopener');
- link.setAttribute('title', link.href);
- }
- }
- }
-
- onMentionClick (mention, e) {
- if (e.button === 0) {
- e.preventDefault();
- this.context.router.push(`/accounts/${mention.get('id')}`);
- }
- }
-
- onHashtagClick (hashtag, e) {
- hashtag = hashtag.replace(/^#/, '').toLowerCase();
-
- if (e.button === 0) {
- e.preventDefault();
- this.context.router.push(`/timelines/tag/${hashtag}`);
- }
- }
-
- handleMouseDown (e) {
- this.startXY = [e.clientX, e.clientY];
- }
-
- handleMouseUp (e) {
- const [ startX, startY ] = this.startXY;
- const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)];
-
- if (e.target.localName === 'a' || (e.target.parentNode && e.target.parentNode.localName === 'a')) {
- return;
- }
-
- if (deltaX + deltaY < 5 && e.button === 0) {
- this.props.onClick();
- }
-
- this.startXY = null;
- }
-
- handleSpoilerClick (e) {
- e.preventDefault();
- this.setState({ hidden: !this.state.hidden });
- }
-
- render () {
- const { status } = this.props;
- const { hidden } = this.state;
-
- const content = { __html: emojify(status.get('content')) };
- const spoilerContent = { __html: emojify(escapeTextContentForBrowser(status.get('spoiler_text', ''))) };
- const directionStyle = { direction: 'ltr' };
-
- if (isRtl(status.get('content'))) {
- directionStyle.direction = 'rtl';
- }
-
- if (status.get('spoiler_text').length > 0) {
- let mentionsPlaceholder = '';
-
- const mentionLinks = status.get('mentions').map(item => (
- <Permalink to={`/accounts/${item.get('id')}`} href={item.get('url')} key={item.get('id')} className='mention'>
- @<span>{item.get('username')}</span>
- </Permalink>
- )).reduce((aggregate, item) => [...aggregate, item, ' '], [])
-
- const toggleText = hidden ? <FormattedMessage id='status.show_more' defaultMessage='Show more' /> : <FormattedMessage id='status.show_less' defaultMessage='Show less' />;
-
- if (hidden) {
- mentionsPlaceholder = <div>{mentionLinks}</div>;
- }
-
- return (
- <div className='status__content' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
- <p style={{ marginBottom: hidden && status.get('mentions').size === 0 ? '0px' : '' }} >
- <span dangerouslySetInnerHTML={spoilerContent} /> <a tabIndex='0' className='status__content__spoiler-link' role='button' onClick={this.handleSpoilerClick}>{toggleText}</a>
- </p>
-
- {mentionsPlaceholder}
-
- <div style={{ display: hidden ? 'none' : 'block', ...directionStyle }} dangerouslySetInnerHTML={content} />
- </div>
- );
- } else if (this.props.onClick) {
- return (
- <div
- className='status__content'
- style={{ ...directionStyle }}
- onMouseDown={this.handleMouseDown}
- onMouseUp={this.handleMouseUp}
- dangerouslySetInnerHTML={content}
- />
- );
- } else {
- return (
- <div
- className='status__content status__content--no-action'
- style={{ ...directionStyle }}
- dangerouslySetInnerHTML={content}
- />
- );
- }
- }
-
-}
-
-StatusContent.contextTypes = {
- router: PropTypes.object
-};
-
-StatusContent.propTypes = {
- status: ImmutablePropTypes.map.isRequired,
- onClick: PropTypes.func
-};
-
-export default StatusContent;
diff --git a/app/assets/javascripts/components/components/status_list.jsx b/app/assets/javascripts/components/components/status_list.jsx
@@ -1,128 +0,0 @@
-import Status from './status';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { ScrollContainer } from 'react-router-scroll';
-import PropTypes from 'prop-types';
-import StatusContainer from '../containers/status_container';
-import LoadMore from './load_more';
-
-class StatusList extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleScroll = this.handleScroll.bind(this);
- this.setRef = this.setRef.bind(this);
- this.handleLoadMore = this.handleLoadMore.bind(this);
- }
-
- handleScroll (e) {
- const { scrollTop, scrollHeight, clientHeight } = e.target;
- const offset = scrollHeight - scrollTop - clientHeight;
- this._oldScrollPosition = scrollHeight - scrollTop;
-
- if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
- this.props.onScrollToBottom();
- } else if (scrollTop < 100 && this.props.onScrollToTop) {
- this.props.onScrollToTop();
- } else if (this.props.onScroll) {
- this.props.onScroll();
- }
- }
-
- componentDidMount () {
- this.attachScrollListener();
- }
-
- componentDidUpdate (prevProps) {
- if (this.node.scrollTop > 0 && (prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition)) {
- this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
- }
- }
-
- componentWillUnmount () {
- this.detachScrollListener();
- }
-
- attachScrollListener () {
- this.node.addEventListener('scroll', this.handleScroll);
- }
-
- detachScrollListener () {
- this.node.removeEventListener('scroll', this.handleScroll);
- }
-
- setRef (c) {
- this.node = c;
- }
-
- handleLoadMore (e) {
- e.preventDefault();
- this.props.onScrollToBottom();
- }
-
- render () {
- const { statusIds, onScrollToBottom, scrollKey, shouldUpdateScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props;
-
- let loadMore = '';
- let scrollableArea = '';
- let unread = '';
-
- if (!isLoading && statusIds.size > 0 && hasMore) {
- loadMore = <LoadMore onClick={this.handleLoadMore} />;
- }
-
- if (isUnread) {
- unread = <div className='status-list__unread-indicator' />;
- }
-
- if (isLoading || statusIds.size > 0 || !emptyMessage) {
- scrollableArea = (
- <div className='scrollable' ref={this.setRef}>
- {unread}
-
- <div className='status-list'>
- {prepend}
-
- {statusIds.map((statusId) => {
- return <StatusContainer key={statusId} id={statusId} />;
- })}
-
- {loadMore}
- </div>
- </div>
- );
- } else {
- scrollableArea = (
- <div className='empty-column-indicator' ref={this.setRef}>
- {emptyMessage}
- </div>
- );
- }
-
- return (
- <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
- {scrollableArea}
- </ScrollContainer>
- );
- }
-
-}
-
-StatusList.propTypes = {
- scrollKey: PropTypes.string.isRequired,
- statusIds: ImmutablePropTypes.list.isRequired,
- onScrollToBottom: PropTypes.func,
- onScrollToTop: PropTypes.func,
- onScroll: PropTypes.func,
- shouldUpdateScroll: PropTypes.func,
- isLoading: PropTypes.bool,
- isUnread: PropTypes.bool,
- hasMore: PropTypes.bool,
- prepend: PropTypes.node,
- emptyMessage: PropTypes.node
-};
-
-StatusList.defaultProps = {
- trackScroll: true
-};
-
-export default StatusList;
diff --git a/app/assets/javascripts/components/components/video_player.jsx b/app/assets/javascripts/components/components/video_player.jsx
@@ -1,198 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import IconButton from './icon_button';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { isIOS } from '../is_mobile';
-
-const messages = defineMessages({
- toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' },
- toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' },
- expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' },
- expand_video: { id: 'video_player.video_error', defaultMessage: 'Video could not be played' }
-});
-
-class VideoPlayer extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.state = {
- visible: !this.props.sensitive,
- preview: true,
- muted: true,
- hasAudio: true,
- videoError: false
- };
-
- this.handleClick = this.handleClick.bind(this);
- this.handleVideoClick = this.handleVideoClick.bind(this);
- this.handleOpen = this.handleOpen.bind(this);
- this.handleVisibility = this.handleVisibility.bind(this);
- this.handleExpand = this.handleExpand.bind(this);
- this.setRef = this.setRef.bind(this);
- this.handleLoadedData = this.handleLoadedData.bind(this);
- this.handleVideoError = this.handleVideoError.bind(this);
- }
-
- handleClick () {
- this.setState({ muted: !this.state.muted });
- }
-
- handleVideoClick (e) {
- e.stopPropagation();
-
- const node = ReactDOM.findDOMNode(this).querySelector('video');
-
- if (node.paused) {
- node.play();
- } else {
- node.pause();
- }
- }
-
- handleOpen () {
- this.setState({ preview: !this.state.preview });
- }
-
- handleVisibility () {
- this.setState({
- visible: !this.state.visible,
- preview: true
- });
- }
-
- handleExpand () {
- this.video.pause();
- this.props.onOpenVideo(this.props.media, this.video.currentTime);
- }
-
- setRef (c) {
- this.video = c;
- }
-
- handleLoadedData () {
- if (('WebkitAppearance' in document.documentElement.style && this.video.audioTracks.length === 0) || this.video.mozHasAudio === false) {
- this.setState({ hasAudio: false });
- }
- }
-
- handleVideoError () {
- this.setState({ videoError: true });
- }
-
- componentDidMount () {
- if (!this.video) {
- return;
- }
-
- this.video.addEventListener('loadeddata', this.handleLoadedData);
- this.video.addEventListener('error', this.handleVideoError);
- }
-
- componentDidUpdate () {
- if (!this.video) {
- return;
- }
-
- this.video.addEventListener('loadeddata', this.handleLoadedData);
- this.video.addEventListener('error', this.handleVideoError);
- }
-
- componentWillUnmount () {
- if (!this.video) {
- return;
- }
-
- this.video.removeEventListener('loadeddata', this.handleLoadedData);
- this.video.removeEventListener('error', this.handleVideoError);
- }
-
- render () {
- const { media, intl, width, height, sensitive, autoplay } = this.props;
-
- let spoilerButton = (
- <div className='status__video-player-spoiler' style={{ display: !this.state.visible ? 'none' : 'block' }} >
- <IconButton overlay title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} />
- </div>
- );
-
- let expandButton = (
- <div className='status__video-player-expand'>
- <IconButton overlay title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} />
- </div>
- );
-
- let muteButton = '';
-
- if (this.state.hasAudio) {
- muteButton = (
- <div className='status__video-player-mute'>
- <IconButton overlay title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} />
- </div>
- );
- }
-
- if (!this.state.visible) {
- if (sensitive) {
- return (
- <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
- {spoilerButton}
- <span className='media-spoiler__warning'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
- <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
- </div>
- );
- } else {
- return (
- <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
- {spoilerButton}
- <span className='media-spoiler__warning'><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
- <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
- </div>
- );
- }
- }
-
- if (this.state.preview && !autoplay) {
- return (
- <div role='button' tabIndex='0' className='media-spoiler-video' style={{ width: `${width}px`, height: `${height}px`, background: `url(${media.get('preview_url')}) no-repeat center` }} onClick={this.handleOpen}>
- {spoilerButton}
- <div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div>
- </div>
- );
- }
-
- if (this.state.videoError) {
- return (
- <div style={{ width: `${width}px`, height: `${height}px` }} className='video-error-cover' >
- <span className='media-spoiler__warning'><FormattedMessage id='video_player.video_error' defaultMessage='Video could not be played' /></span>
- </div>
- );
- }
-
- return (
- <div className='status__video-player' style={{ width: `${width}px`, height: `${height}px` }}>
- {spoilerButton}
- {muteButton}
- {expandButton}
- <video className='status__video-player-video' role='button' tabIndex='0' ref={this.setRef} src={media.get('url')} autoPlay={!isIOS()} loop={true} muted={this.state.muted} onClick={this.handleVideoClick} />
- </div>
- );
- }
-
-}
-
-VideoPlayer.propTypes = {
- media: ImmutablePropTypes.map.isRequired,
- width: PropTypes.number,
- height: PropTypes.number,
- sensitive: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- autoplay: PropTypes.bool,
- onOpenVideo: PropTypes.func.isRequired
-};
-
-VideoPlayer.defaultProps = {
- width: 239,
- height: 110
-};
-
-export default injectIntl(VideoPlayer);
diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx
@@ -1,320 +0,0 @@
-import { Provider } from 'react-redux';
-import PropTypes from 'prop-types';
-import configureStore from '../store/configureStore';
-import {
- refreshTimelineSuccess,
- updateTimeline,
- deleteFromTimelines,
- refreshTimeline,
- connectTimeline,
- disconnectTimeline
-} from '../actions/timelines';
-import { showOnboardingOnce } from '../actions/onboarding';
-import { updateNotifications, refreshNotifications } from '../actions/notifications';
-import createBrowserHistory from 'history/lib/createBrowserHistory';
-import {
- applyRouterMiddleware,
- useRouterHistory,
- Router,
- Route,
- IndexRedirect,
- IndexRoute
-} from 'react-router';
-import { useScroll } from 'react-router-scroll';
-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 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 { IntlProvider, addLocaleData } from 'react-intl';
-import ar from 'react-intl/locale-data/ar';
-import en from 'react-intl/locale-data/en';
-import de from 'react-intl/locale-data/de';
-import eo from 'react-intl/locale-data/eo';
-import es from 'react-intl/locale-data/es';
-import fa from 'react-intl/locale-data/fa';
-import fi from 'react-intl/locale-data/fi';
-import fr from 'react-intl/locale-data/fr';
-import he from 'react-intl/locale-data/he';
-import hu from 'react-intl/locale-data/hu';
-import it from 'react-intl/locale-data/it';
-import ja from 'react-intl/locale-data/ja';
-import pt from 'react-intl/locale-data/pt';
-import nl from 'react-intl/locale-data/nl';
-import no from 'react-intl/locale-data/no';
-import ru from 'react-intl/locale-data/ru';
-import uk from 'react-intl/locale-data/uk';
-import zh from 'react-intl/locale-data/zh';
-import bg from 'react-intl/locale-data/bg';
-import id from 'react-intl/locale-data/id';
-import { localeData as zh_hk } from '../locales/zh-hk';
-import { localeData as zh_cn } from '../locales/zh-cn';
-import pt_br from '../locales/pt-br';
-import getMessagesForLocale from '../locales';
-import { hydrateStore } from '../actions/store';
-import createStream from '../stream';
-
-const store = configureStore();
-const initialState = JSON.parse(document.getElementById("initial-state").textContent);
-store.dispatch(hydrateStore(initialState));
-
-const browserHistory = useRouterHistory(createBrowserHistory)({
- basename: '/web'
-});
-
-addLocaleData([
- ...en,
- ...ar,
- ...de,
- ...eo,
- ...es,
- ...fa,
- ...fi,
- ...fr,
- ...he,
- ...hu,
- ...it,
- ...ja,
- ...pt,
- ...pt_br,
- ...nl,
- ...no,
- ...ru,
- ...uk,
- ...zh,
- ...zh_hk,
- ...zh_cn,
- ...bg,
- ...id,
-]);
-
-const getTopWhenReplacing = (previous, { location }) => location && location.action === 'REPLACE' && [0, 0];
-
-const hiddenColumnContainerStyle = {
- position: 'absolute',
- left: '0',
- top: '0',
- visibility: 'hidden'
-};
-
-class Container extends React.PureComponent {
-
- constructor(props) {
- super(props);
-
- this.state = {
- renderedPersistents: [],
- unrenderedPersistents: [],
- };
- }
-
- componentWillMount () {
- this.unlistenHistory = null;
-
- this.setState(() => {
- return {
- mountImpersistent: false,
- renderedPersistents: [],
- unrenderedPersistents: [
- {pathname: '/timelines/home', component: HomeTimeline},
- {pathname: '/timelines/public', component: PublicTimeline},
- {pathname: '/timelines/public/local', component: CommunityTimeline},
-
- {pathname: '/notifications', component: Notifications},
- {pathname: '/favourites', component: FavouritedStatuses}
- ],
- };
- }, () => {
- if (this.unlistenHistory) {
- return;
- }
-
- this.unlistenHistory = browserHistory.listen(location => {
- const pathname = location.pathname.replace(/\/$/, '').toLowerCase();
-
- this.setState(oldState => {
- let persistentMatched = false;
-
- const newState = {
- renderedPersistents: oldState.renderedPersistents.map(persistent => {
- const givenMatched = persistent.pathname === pathname;
-
- if (givenMatched) {
- persistentMatched = true;
- }
-
- return {
- hidden: !givenMatched,
- pathname: persistent.pathname,
- component: persistent.component
- };
- }),
- };
-
- if (!persistentMatched) {
- newState.unrenderedPersistents = [];
-
- oldState.unrenderedPersistents.forEach(persistent => {
- if (persistent.pathname === pathname) {
- persistentMatched = true;
-
- newState.renderedPersistents.push({
- hidden: false,
- pathname: persistent.pathname,
- component: persistent.component
- });
- } else {
- newState.unrenderedPersistents.push(persistent);
- }
- });
- }
-
- newState.mountImpersistent = !persistentMatched;
-
- return newState;
- });
- });
- });
- }
-
- componentWillUnmount () {
- if (this.unlistenHistory) {
- this.unlistenHistory();
- }
-
- this.unlistenHistory = "done";
- }
-
- render () {
- // Hide some components rather than unmounting them to allow to show again
- // quickly and keep the view state such as the scrolled offset.
- const persistentsView = this.state.renderedPersistents.map((persistent) =>
- <div aria-hidden={persistent.hidden} key={persistent.pathname} className='mastodon-column-container' style={persistent.hidden ? hiddenColumnContainerStyle : null}>
- <persistent.component shouldUpdateScroll={persistent.hidden ? Function.prototype : getTopWhenReplacing} />
- </div>
- );
-
- return (
- <UI>
- {this.state.mountImpersistent && this.props.children}
- {persistentsView}
- </UI>
- );
- }
-}
-
-Container.propTypes = {
- children: PropTypes.node,
-};
-
-class Mastodon extends React.Component {
-
- componentDidMount() {
- const { locale } = this.props;
- const streamingAPIBaseURL = store.getState().getIn(['meta', 'streaming_api_base_url']);
- const accessToken = store.getState().getIn(['meta', 'access_token']);
-
- this.subscription = createStream(streamingAPIBaseURL, accessToken, 'user', {
-
- connected () {
- store.dispatch(connectTimeline('home'));
- },
-
- disconnected () {
- store.dispatch(disconnectTimeline('home'));
- },
-
- received (data) {
- switch(data.event) {
- case 'update':
- store.dispatch(updateTimeline('home', JSON.parse(data.payload)));
- break;
- case 'delete':
- store.dispatch(deleteFromTimelines(data.payload));
- break;
- case 'notification':
- store.dispatch(updateNotifications(JSON.parse(data.payload), getMessagesForLocale(locale), locale));
- break;
- }
- },
-
- reconnected () {
- store.dispatch(connectTimeline('home'));
- store.dispatch(refreshTimeline('home'));
- store.dispatch(refreshNotifications());
- }
-
- });
-
- // Desktop notifications
- if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
- Notification.requestPermission();
- }
-
- store.dispatch(showOnboardingOnce());
- }
-
- componentWillUnmount () {
- if (typeof this.subscription !== 'undefined') {
- this.subscription.close();
- this.subscription = null;
- }
- }
-
- render () {
- const { locale } = this.props;
-
- return (
- <IntlProvider locale={locale} messages={getMessagesForLocale(locale)}>
- <Provider store={store}>
- <Router history={browserHistory} render={applyRouterMiddleware(useScroll())}>
- <Route path='/' component={Container}>
- <IndexRedirect to="/getting-started" />
-
- <Route path='getting-started' component={GettingStarted} />
- <Route path='timelines/tag/:id' component={HashtagTimeline} />
-
- <Route path='statuses/new' component={Compose} />
- <Route path='statuses/:statusId' component={Status} />
- <Route path='statuses/:statusId/reblogs' component={Reblogs} />
- <Route path='statuses/:statusId/favourites' component={Favourites} />
-
- <Route path='accounts/:accountId' component={AccountTimeline} />
- <Route path='accounts/:accountId/followers' component={Followers} />
- <Route path='accounts/:accountId/following' component={Following} />
-
- <Route path='follow_requests' component={FollowRequests} />
- <Route path='blocks' component={Blocks} />
- <Route path='mutes' component={Mutes} />
- <Route path='report' component={Report} />
-
- <Route path='*' component={GenericNotFound} />
- </Route>
- </Router>
- </Provider>
- </IntlProvider>
- );
- }
-
-}
-
-Mastodon.propTypes = {
- locale: PropTypes.string.isRequired
-};
-
-export default Mastodon;
diff --git a/app/assets/javascripts/components/containers/status_container.jsx b/app/assets/javascripts/components/containers/status_container.jsx
@@ -1,117 +0,0 @@
-import { connect } from 'react-redux';
-import Status from '../components/status';
-import { makeGetStatus } from '../selectors';
-import {
- replyCompose,
- mentionCompose
-} from '../actions/compose';
-import {
- reblog,
- favourite,
- unreblog,
- unfavourite
-} from '../actions/interactions';
-import {
- blockAccount,
- muteAccount
-} from '../actions/accounts';
-import { 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({
- deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
- deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
- blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
- muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
-});
-
-const makeMapStateToProps = () => {
- const getStatus = makeGetStatus();
-
- const mapStateToProps = (state, props) => ({
- status: getStatus(state, props.id),
- me: state.getIn(['meta', 'me']),
- boostModal: state.getIn(['meta', 'boost_modal']),
- autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
- });
-
- return mapStateToProps;
-};
-
-const mapDispatchToProps = (dispatch, { intl }) => ({
-
- onReply (status, router) {
- dispatch(replyCompose(status, router));
- },
-
- onModalReblog (status) {
- dispatch(reblog(status));
- },
-
- onReblog (status, e) {
- if (status.get('reblogged')) {
- dispatch(unreblog(status));
- } else {
- if (e.shiftKey || !this.boostModal) {
- this.onModalReblog(status);
- } else {
- dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
- }
- }
- },
-
- onFavourite (status) {
- if (status.get('favourited')) {
- dispatch(unfavourite(status));
- } else {
- dispatch(favourite(status));
- }
- },
-
- onDelete (status) {
- dispatch(openModal('CONFIRM', {
- message: intl.formatMessage(messages.deleteMessage),
- confirm: intl.formatMessage(messages.deleteConfirm),
- onConfirm: () => dispatch(deleteStatus(status.get('id')))
- }));
- },
-
- onMention (account, router) {
- dispatch(mentionCompose(account, router));
- },
-
- onOpenMedia (media, index) {
- dispatch(openModal('MEDIA', { media, index }));
- },
-
- onOpenVideo (media, time) {
- dispatch(openModal('VIDEO', { media, time }));
- },
-
- onBlock (account) {
- dispatch(openModal('CONFIRM', {
- message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
- confirm: intl.formatMessage(messages.blockConfirm),
- onConfirm: () => dispatch(blockAccount(account.get('id')))
- }));
- },
-
- onReport (status) {
- dispatch(initReport(status.get('account'), status));
- },
-
- onMute (account) {
- dispatch(openModal('CONFIRM', {
- message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
- confirm: intl.formatMessage(messages.muteConfirm),
- onConfirm: () => dispatch(muteAccount(account.get('id')))
- }));
- },
-
-});
-
-export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
diff --git a/app/assets/javascripts/components/features/account/components/action_bar.jsx b/app/assets/javascripts/components/features/account/components/action_bar.jsx
@@ -1,92 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import DropdownMenu from '../../../components/dropdown_menu';
-import { Link } from 'react-router';
-import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
-
-const messages = defineMessages({
- mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
- edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
- unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
- unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
- unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
- block: { id: 'account.block', defaultMessage: 'Block @{name}' },
- mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
- follow: { id: 'account.follow', defaultMessage: 'Follow' },
- report: { id: 'account.report', defaultMessage: 'Report @{name}' },
- disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' }
-});
-
-class ActionBar extends React.PureComponent {
-
- render () {
- const { account, me, intl } = this.props;
-
- let menu = [];
- let extraInfo = '';
-
- menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
- menu.push(null);
-
- if (account.get('id') === me) {
- menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
- } else {
- if (account.getIn(['relationship', 'muting'])) {
- menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
- } else {
- menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute });
- }
-
- if (account.getIn(['relationship', 'blocking'])) {
- menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
- } else {
- menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock });
- }
-
- menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
- }
-
- if (account.get('acct') !== account.get('username')) {
- extraInfo = <abbr title={intl.formatMessage(messages.disclaimer)}>*</abbr>;
- }
-
- return (
- <div className='account__action-bar'>
- <div className='account__action-bar-dropdown'>
- <DropdownMenu items={menu} icon='bars' size={24} direction="right" />
- </div>
-
- <div className='account__action-bar-links'>
- <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}`}>
- <span><FormattedMessage id='account.posts' defaultMessage='Posts' /></span>
- <strong><FormattedNumber value={account.get('statuses_count')} /> {extraInfo}</strong>
- </Link>
-
- <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`}>
- <span><FormattedMessage id='account.follows' defaultMessage='Follows' /></span>
- <strong><FormattedNumber value={account.get('following_count')} /> {extraInfo}</strong>
- </Link>
-
- <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`}>
- <span><FormattedMessage id='account.followers' defaultMessage='Followers' /></span>
- <strong><FormattedNumber value={account.get('followers_count')} /> {extraInfo}</strong>
- </Link>
- </div>
- </div>
- );
- }
-
-}
-
-ActionBar.propTypes = {
- account: ImmutablePropTypes.map.isRequired,
- me: PropTypes.number.isRequired,
- onFollow: PropTypes.func,
- onBlock: PropTypes.func.isRequired,
- onMention: PropTypes.func.isRequired,
- onReport: PropTypes.func.isRequired,
- onMute: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(ActionBar);
diff --git a/app/assets/javascripts/components/features/account/components/header.jsx b/app/assets/javascripts/components/features/account/components/header.jsx
@@ -1,148 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import emojify from '../../../emoji';
-import escapeTextContentForBrowser from 'escape-html';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import IconButton from '../../../components/icon_button';
-import { Motion, spring } from 'react-motion';
-import { connect } from 'react-redux';
-
-const messages = defineMessages({
- unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
- follow: { id: 'account.follow', defaultMessage: 'Follow' },
- requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }
-});
-
-const makeMapStateToProps = () => {
- const mapStateToProps = (state, props) => ({
- autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
- });
-
- return mapStateToProps;
-};
-
-class Avatar extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
-
- this.state = {
- isHovered: false
- };
-
- this.handleMouseOver = this.handleMouseOver.bind(this);
- this.handleMouseOut = this.handleMouseOut.bind(this);
- }
-
- handleMouseOver () {
- if (this.state.isHovered) return;
- this.setState({ isHovered: true });
- }
-
- handleMouseOut () {
- if (!this.state.isHovered) return;
- this.setState({ isHovered: false });
- }
-
- render () {
- const { account, autoPlayGif } = this.props;
- const { isHovered } = this.state;
-
- return (
- <Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}>
- {({ radius }) =>
- <a
- href={account.get('url')}
- className='account__header__avatar'
- target='_blank'
- rel='noopener'
- style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }}
- onMouseOver={this.handleMouseOver}
- onMouseOut={this.handleMouseOut}
- onFocus={this.handleMouseOver}
- onBlur={this.handleMouseOut}
- />
- }
- </Motion>
- );
- }
-
-}
-
-Avatar.propTypes = {
- account: ImmutablePropTypes.map.isRequired,
- autoPlayGif: PropTypes.bool.isRequired
-};
-
-class Header extends React.Component {
-
- render () {
- const { account, me, intl } = this.props;
-
- if (!account) {
- return null;
- }
-
- let displayName = account.get('display_name');
- let info = '';
- let actionBtn = '';
- let lockedIcon = '';
-
- if (displayName.length === 0) {
- displayName = account.get('username');
- }
-
- if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
- info = <span className='account--follows-info' style={{ position: 'absolute', top: '10px', right: '10px', opacity: '0.7', display: 'inline-block', verticalAlign: 'top', background: 'rgba(0, 0, 0, 0.4)', textTransform: 'uppercase', fontSize: '11px', fontWeight: '500', padding: '4px', borderRadius: '4px' }}><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>
- }
-
- if (me !== account.get('id')) {
- if (account.getIn(['relationship', 'requested'])) {
- actionBtn = (
- <div style={{ position: 'absolute', top: '10px', left: '20px' }}>
- <IconButton size={26} disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} />
- </div>
- );
- } else if (!account.getIn(['relationship', 'blocking'])) {
- actionBtn = (
- <div style={{ position: 'absolute', top: '10px', left: '20px' }}>
- <IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
- </div>
- );
- }
- }
-
- if (account.get('locked')) {
- lockedIcon = <i className='fa fa-lock' />;
- }
-
- const content = { __html: emojify(account.get('note')) };
- const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
-
- return (
- <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
- <div style={{ padding: '20px 10px' }}>
- <Avatar account={account} autoPlayGif={this.props.autoPlayGif} />
-
- <span style={{ display: 'inline-block', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }} className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} />
- <span className='account__header__username' style={{ fontSize: '14px', fontWeight: '400', display: 'block', marginBottom: '10px' }}>@{account.get('acct')} {lockedIcon}</span>
- <div style={{ fontSize: '14px' }} className='account__header__content' dangerouslySetInnerHTML={content} />
-
- {info}
- {actionBtn}
- </div>
- </div>
- );
- }
-
-}
-
-Header.propTypes = {
- account: ImmutablePropTypes.map,
- me: PropTypes.number.isRequired,
- onFollow: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- autoPlayGif: PropTypes.bool.isRequired
-};
-
-export default connect(makeMapStateToProps)(injectIntl(Header));
diff --git a/app/assets/javascripts/components/features/account_timeline/components/header.jsx b/app/assets/javascripts/components/features/account_timeline/components/header.jsx
@@ -1,81 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import InnerHeader from '../../account/components/header';
-import ActionBar from '../../account/components/action_bar';
-import MissingIndicator from '../../../components/missing_indicator';
-
-class Header extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleFollow = this.handleFollow.bind(this);
- this.handleBlock = this.handleBlock.bind(this);
- this.handleMention = this.handleMention.bind(this);
- this.handleReport = this.handleReport.bind(this);
- this.handleMute = this.handleMute.bind(this);
- }
-
- handleFollow () {
- this.props.onFollow(this.props.account);
- }
-
- handleBlock () {
- this.props.onBlock(this.props.account);
- }
-
- handleMention () {
- this.props.onMention(this.props.account, this.context.router);
- }
-
- handleReport () {
- this.props.onReport(this.props.account);
- this.context.router.push('/report');
- }
-
- handleMute() {
- this.props.onMute(this.props.account);
- }
-
- render () {
- const { account, me } = this.props;
-
- if (account === null) {
- return <MissingIndicator />;
- }
-
- return (
- <div className='account-timeline__header'>
- <InnerHeader
- account={account}
- me={me}
- onFollow={this.handleFollow}
- />
-
- <ActionBar
- account={account}
- me={me}
- onBlock={this.handleBlock}
- onMention={this.handleMention}
- onReport={this.handleReport}
- onMute={this.handleMute}
- />
- </div>
- );
- }
-}
-
-Header.propTypes = {
- account: ImmutablePropTypes.map,
- me: PropTypes.number.isRequired,
- onFollow: PropTypes.func.isRequired,
- onBlock: PropTypes.func.isRequired,
- onMention: PropTypes.func.isRequired,
- onReport: PropTypes.func.isRequired,
- onMute: PropTypes.func.isRequired
-};
-
-Header.contextTypes = {
- router: PropTypes.object
-};
-
-export default Header;
diff --git a/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx b/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx
@@ -1,75 +0,0 @@
-import { connect } from 'react-redux';
-import { makeGetAccount } from '../../../selectors';
-import Header from '../components/header';
-import {
- followAccount,
- unfollowAccount,
- blockAccount,
- unblockAccount,
- muteAccount,
- unmuteAccount
-} from '../../../actions/accounts';
-import { mentionCompose } from '../../../actions/compose';
-import { initReport } from '../../../actions/reports';
-import { openModal } from '../../../actions/modal';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-
-const messages = defineMessages({
- blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
- muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' }
-});
-
-const makeMapStateToProps = () => {
- const getAccount = makeGetAccount();
-
- const mapStateToProps = (state, { accountId }) => ({
- account: getAccount(state, Number(accountId)),
- me: state.getIn(['meta', 'me'])
- });
-
- return mapStateToProps;
-};
-
-const mapDispatchToProps = (dispatch, { intl }) => ({
- onFollow (account) {
- if (account.getIn(['relationship', 'following'])) {
- dispatch(unfollowAccount(account.get('id')));
- } else {
- dispatch(followAccount(account.get('id')));
- }
- },
-
- onBlock (account) {
- if (account.getIn(['relationship', 'blocking'])) {
- dispatch(unblockAccount(account.get('id')));
- } else {
- dispatch(openModal('CONFIRM', {
- message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
- confirm: intl.formatMessage(messages.blockConfirm),
- onConfirm: () => dispatch(blockAccount(account.get('id')))
- }));
- }
- },
-
- onMention (account, router) {
- dispatch(mentionCompose(account, router));
- },
-
- onReport (account) {
- dispatch(initReport(account));
- },
-
- onMute (account) {
- if (account.getIn(['relationship', 'muting'])) {
- dispatch(unmuteAccount(account.get('id')));
- } else {
- dispatch(openModal('CONFIRM', {
- message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
- confirm: intl.formatMessage(messages.muteConfirm),
- onConfirm: () => dispatch(muteAccount(account.get('id')))
- }));
- }
- }
-});
-
-export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
diff --git a/app/assets/javascripts/components/features/account_timeline/index.jsx b/app/assets/javascripts/components/features/account_timeline/index.jsx
@@ -1,87 +0,0 @@
-import { connect } from 'react-redux';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import {
- fetchAccount,
- fetchAccountTimeline,
- expandAccountTimeline
-} from '../../actions/accounts';
-import StatusList from '../../components/status_list';
-import LoadingIndicator from '../../components/loading_indicator';
-import Column from '../ui/components/column';
-import HeaderContainer from './containers/header_container';
-import ColumnBackButton from '../../components/column_back_button';
-import Immutable from 'immutable';
-
-const mapStateToProps = (state, props) => ({
- statusIds: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'items'], Immutable.List()),
- isLoading: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'isLoading']),
- hasMore: !!state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'next']),
- me: state.getIn(['meta', 'me'])
-});
-
-class AccountTimeline extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleScrollToBottom = this.handleScrollToBottom.bind(this);
- }
-
- componentWillMount () {
- this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
- this.props.dispatch(fetchAccountTimeline(Number(this.props.params.accountId)));
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
- this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
- this.props.dispatch(fetchAccountTimeline(Number(nextProps.params.accountId)));
- }
- }
-
- handleScrollToBottom () {
- if (!this.props.isLoading && this.props.hasMore) {
- this.props.dispatch(expandAccountTimeline(Number(this.props.params.accountId)));
- }
- }
-
- render () {
- const { statusIds, isLoading, hasMore, me } = this.props;
-
- if (!statusIds && isLoading) {
- return (
- <Column>
- <LoadingIndicator />
- </Column>
- );
- }
-
- return (
- <Column>
- <ColumnBackButton />
-
- <StatusList
- prepend={<HeaderContainer accountId={this.props.params.accountId} />}
- scrollKey='account_timeline'
- statusIds={statusIds}
- isLoading={isLoading}
- hasMore={hasMore}
- me={me}
- onScrollToBottom={this.handleScrollToBottom}
- />
- </Column>
- );
- }
-
-}
-
-AccountTimeline.propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- statusIds: ImmutablePropTypes.list,
- isLoading: PropTypes.bool,
- hasMore: PropTypes.bool,
- me: PropTypes.number.isRequired
-};
-
-export default connect(mapStateToProps)(AccountTimeline);
diff --git a/app/assets/javascripts/components/features/blocks/index.jsx b/app/assets/javascripts/components/features/blocks/index.jsx
@@ -1,72 +0,0 @@
-import { connect } from 'react-redux';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll';
-import Column from '../ui/components/column';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import AccountContainer from '../../containers/account_container';
-import { fetchBlocks, expandBlocks } from '../../actions/blocks';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- heading: { id: 'column.blocks', defaultMessage: 'Blocked users' }
-});
-
-const mapStateToProps = state => ({
- accountIds: state.getIn(['user_lists', 'blocks', 'items'])
-});
-
-class Blocks extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleScroll = this.handleScroll.bind(this);
- }
-
- componentWillMount () {
- this.props.dispatch(fetchBlocks());
- }
-
- handleScroll (e) {
- const { scrollTop, scrollHeight, clientHeight } = e.target;
-
- if (scrollTop === scrollHeight - clientHeight) {
- this.props.dispatch(expandBlocks());
- }
- }
-
- render () {
- const { intl, accountIds } = this.props;
-
- if (!accountIds) {
- return (
- <Column>
- <LoadingIndicator />
- </Column>
- );
- }
-
- return (
- <Column icon='ban' heading={intl.formatMessage(messages.heading)}>
- <ColumnBackButtonSlim />
- <ScrollContainer scrollKey='blocks'>
- <div className='scrollable' onScroll={this.handleScroll}>
- {accountIds.map(id =>
- <AccountContainer key={id} id={id} />
- )}
- </div>
- </ScrollContainer>
- </Column>
- );
- }
-}
-
-Blocks.propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- accountIds: ImmutablePropTypes.list,
- intl: PropTypes.object.isRequired
-};
-
-export default connect(mapStateToProps)(injectIntl(Blocks));
diff --git a/app/assets/javascripts/components/features/community_timeline/index.jsx b/app/assets/javascripts/components/features/community_timeline/index.jsx
@@ -1,95 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import StatusListContainer from '../ui/containers/status_list_container';
-import Column from '../ui/components/column';
-import {
- refreshTimeline,
- updateTimeline,
- deleteFromTimelines,
- connectTimeline,
- disconnectTimeline
-} from '../../actions/timelines';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import createStream from '../../stream';
-
-const messages = defineMessages({
- title: { id: 'column.community', defaultMessage: 'Local timeline' }
-});
-
-const mapStateToProps = state => ({
- hasUnread: state.getIn(['timelines', 'community', 'unread']) > 0,
- streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
- accessToken: state.getIn(['meta', 'access_token'])
-});
-
-let subscription;
-
-class CommunityTimeline extends React.PureComponent {
-
- componentDidMount () {
- const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
-
- dispatch(refreshTimeline('community'));
-
- if (typeof subscription !== 'undefined') {
- return;
- }
-
- subscription = createStream(streamingAPIBaseURL, accessToken, 'public:local', {
-
- connected () {
- dispatch(connectTimeline('community'));
- },
-
- reconnected () {
- dispatch(connectTimeline('community'));
- },
-
- disconnected () {
- dispatch(disconnectTimeline('community'));
- },
-
- received (data) {
- switch(data.event) {
- case 'update':
- dispatch(updateTimeline('community', JSON.parse(data.payload)));
- break;
- case 'delete':
- dispatch(deleteFromTimelines(data.payload));
- break;
- }
- }
-
- });
- }
-
- componentWillUnmount () {
- // if (typeof subscription !== 'undefined') {
- // subscription.close();
- // subscription = null;
- // }
- }
-
- render () {
- const { intl, hasUnread } = this.props;
-
- return (
- <Column icon='users' active={hasUnread} heading={intl.formatMessage(messages.title)}>
- <ColumnBackButtonSlim />
- <StatusListContainer {...this.props} scrollKey='community_timeline' type='community' emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} />
- </Column>
- );
- }
-
-}
-
-CommunityTimeline.propTypes = {
- dispatch: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- streamingAPIBaseURL: PropTypes.string.isRequired,
- accessToken: PropTypes.string.isRequired,
- hasUnread: PropTypes.bool
-};
-
-export default connect(mapStateToProps)(injectIntl(CommunityTimeline));
diff --git a/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx b/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx
@@ -1,16 +0,0 @@
-import Avatar from '../../../components/avatar';
-import DisplayName from '../../../components/display_name';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-const AutosuggestAccount = ({ account }) => (
- <div className='autosuggest-account'>
- <div className='autosuggest-account-icon'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={18} /></div>
- <DisplayName account={account} />
- </div>
-);
-
-AutosuggestAccount.propTypes = {
- account: ImmutablePropTypes.map.isRequired
-};
-
-export default AutosuggestAccount;
diff --git a/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx b/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx
@@ -1,15 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-import DisplayName from '../../../components/display_name';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-const AutosuggestStatus = ({ status }) => (
- <div className='autosuggest-status'>
- <FormattedMessage id='search.status_by' defaultMessage='Status by {name}' values={{ name: <strong>@{status.getIn(['account', 'acct'])}</strong> }} />
- </div>
-);
-
-AutosuggestStatus.propTypes = {
- status: ImmutablePropTypes.map.isRequired
-};
-
-export default AutosuggestStatus;
diff --git a/app/assets/javascripts/components/features/compose/components/character_counter.jsx b/app/assets/javascripts/components/features/compose/components/character_counter.jsx
@@ -1,26 +0,0 @@
-import PropTypes from 'prop-types';
-import { length } from 'stringz';
-
-class CharacterCounter extends React.PureComponent {
-
- checkRemainingText (diff) {
- if (diff < 0) {
- return <span className='character-counter character-counter--over'>{diff}</span>;
- }
- return <span className='character-counter'>{diff}</span>;
- }
-
- render () {
- const diff = this.props.max - length(this.props.text);
-
- return this.checkRemainingText(diff);
- }
-
-}
-
-CharacterCounter.propTypes = {
- text: PropTypes.string.isRequired,
- max: PropTypes.number.isRequired
-}
-
-export default CharacterCounter;
diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx
@@ -1,209 +0,0 @@
-import CharacterCounter from './character_counter';
-import Button from '../../../components/button';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import ReplyIndicatorContainer from '../containers/reply_indicator_container';
-import AutosuggestTextarea from '../../../components/autosuggest_textarea';
-import { debounce } from 'react-decoration';
-import UploadButtonContainer from '../containers/upload_button_container';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import Toggle from 'react-toggle';
-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';
-
-const messages = defineMessages({
- placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
- spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Content warning' },
- publish: { id: 'compose_form.publish', defaultMessage: 'Toot' }
-});
-
-class ComposeForm extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleChange = this.handleChange.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this);
- this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this);
- this.onSuggestionSelected = this.onSuggestionSelected.bind(this);
- this.handleChangeSpoilerText = this.handleChangeSpoilerText.bind(this);
- this.setAutosuggestTextarea = this.setAutosuggestTextarea.bind(this);
- this.handleEmojiPick = this.handleEmojiPick.bind(this);
- }
-
- handleChange (e) {
- this.props.onChange(e.target.value);
- }
-
- handleKeyDown (e) {
- if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
- this.handleSubmit();
- }
- }
-
- handleSubmit () {
- this.autosuggestTextarea.reset();
- this.props.onSubmit();
- }
-
- onSuggestionsClearRequested () {
- this.props.onClearSuggestions();
- }
-
- @debounce(500)
- onSuggestionsFetchRequested (token) {
- this.props.onFetchSuggestions(token);
- }
-
- onSuggestionSelected (tokenStart, token, value) {
- this._restoreCaret = null;
- this.props.onSuggestionSelected(tokenStart, token, value);
- }
-
- handleChangeSpoilerText (e) {
- this.props.onChangeSpoilerText(e.target.value);
- }
-
- componentWillReceiveProps (nextProps) {
- // If this is the update where we've finished uploading,
- // save the last caret position so we can restore it below!
- if (!nextProps.is_uploading && this.props.is_uploading) {
- this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart;
- }
- }
-
- componentDidUpdate (prevProps) {
- // This statement does several things:
- // - If we're beginning a reply, and,
- // - Replying to zero or one users, places the cursor at the end of the textbox.
- // - Replying to more than one user, selects any usernames past the first;
- // this provides a convenient shortcut to drop everyone else from the conversation.
- // - If we've just finished uploading an image, and have a saved caret position,
- // restores the cursor to that position after the text changes!
- if (this.props.focusDate !== prevProps.focusDate || (prevProps.is_uploading && !this.props.is_uploading && typeof this._restoreCaret === 'number')) {
- let selectionEnd, selectionStart;
-
- if (this.props.preselectDate !== prevProps.preselectDate) {
- selectionEnd = this.props.text.length;
- selectionStart = this.props.text.search(/\s/) + 1;
- } else if (typeof this._restoreCaret === 'number') {
- selectionStart = this._restoreCaret;
- selectionEnd = this._restoreCaret;
- } else {
- selectionEnd = this.props.text.length;
- selectionStart = selectionEnd;
- }
-
- this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
- this.autosuggestTextarea.textarea.focus();
- }
- }
-
- setAutosuggestTextarea (c) {
- this.autosuggestTextarea = c;
- }
-
- handleEmojiPick (data) {
- const position = this.autosuggestTextarea.textarea.selectionStart;
- this._restoreCaret = position + data.shortname.length + 1;
- this.props.onPickEmoji(position, data);
- }
-
- render () {
- const { intl, onPaste } = this.props;
- const disabled = this.props.is_submitting;
- 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 = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
- } else {
- publishText = intl.formatMessage(messages.publish) + (this.props.privacy !== 'unlisted' ? '!' : '');
- }
-
- return (
- <div className='compose-form'>
- <Collapsable isVisible={this.props.spoiler} fullHeight={50}>
- <div className="spoiler-input">
- <input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type="text" className="spoiler-input__input" id='cw-spoiler-input'/>
- </div>
- </Collapsable>
-
- <WarningContainer />
-
- <ReplyIndicatorContainer />
-
- <div className='compose-form__autosuggest-wrapper'>
- <AutosuggestTextarea
- ref={this.setAutosuggestTextarea}
- placeholder={intl.formatMessage(messages.placeholder)}
- disabled={disabled}
- value={this.props.text}
- onChange={this.handleChange}
- suggestions={this.props.suggestions}
- onKeyDown={this.handleKeyDown}
- onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
- onSuggestionsClearRequested={this.onSuggestionsClearRequested}
- onSuggestionSelected={this.onSuggestionSelected}
- onPaste={onPaste}
- />
-
- <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
- </div>
-
- <div className='compose-form__modifiers'>
- <UploadFormContainer />
- </div>
-
- <div className='compose-form__buttons-wrapper'>
- <div className='compose-form__buttons'>
- <UploadButtonContainer />
- <PrivacyDropdownContainer />
- <SensitiveButtonContainer />
- <SpoilerButtonContainer />
- </div>
-
- <div className='compose-form__publish'>
- <div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
- <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length > 500 || (text.length !==0 && text.trim().length === 0)} block /></div>
- </div>
- </div>
- </div>
- );
- }
-
-}
-
-ComposeForm.propTypes = {
- intl: PropTypes.object.isRequired,
- text: PropTypes.string.isRequired,
- suggestion_token: PropTypes.string,
- suggestions: ImmutablePropTypes.list,
- spoiler: PropTypes.bool,
- privacy: PropTypes.string,
- spoiler_text: PropTypes.string,
- focusDate: PropTypes.instanceOf(Date),
- preselectDate: PropTypes.instanceOf(Date),
- is_submitting: PropTypes.bool,
- is_uploading: PropTypes.bool,
- me: PropTypes.number,
- onChange: PropTypes.func.isRequired,
- onSubmit: PropTypes.func.isRequired,
- onClearSuggestions: PropTypes.func.isRequired,
- onFetchSuggestions: PropTypes.func.isRequired,
- onSuggestionSelected: PropTypes.func.isRequired,
- onChangeSpoilerText: PropTypes.func.isRequired,
- onPaste: PropTypes.func.isRequired,
- onPickEmoji: PropTypes.func.isRequired
-};
-
-export default injectIntl(ComposeForm);
diff --git a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx
@@ -1,114 +0,0 @@
-import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
-import EmojiPicker from 'emojione-picker';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
- emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
- people: { id: 'emoji_button.people', defaultMessage: 'People' },
- nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' },
- food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' },
- activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' },
- travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' },
- objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' },
- symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' },
- flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' }
-});
-
-const settings = {
- imageType: 'png',
- sprites: false,
- imagePathPNG: '/emoji/'
-};
-
-const dropdownStyle = {
- position: 'absolute',
- right: '5px',
- top: '5px'
-};
-
-const dropdownTriggerStyle = {
- display: 'block',
- fontSize: '24px',
- lineHeight: '24px',
- marginLeft: '2px',
- width: '24px'
-}
-
-class EmojiPickerDropdown extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.setRef = this.setRef.bind(this);
- this.handleChange = this.handleChange.bind(this);
- }
-
- setRef (c) {
- this.dropdown = c;
- }
-
- handleChange (data) {
- this.dropdown.hide();
- this.props.onPickEmoji(data);
- }
-
- render () {
- const { intl } = this.props;
-
- const categories = {
- people: {
- title: intl.formatMessage(messages.people),
- emoji: 'smile',
- },
- nature: {
- title: intl.formatMessage(messages.nature),
- emoji: 'hamster',
- },
- food: {
- title: intl.formatMessage(messages.food),
- emoji: 'pizza',
- },
- activity: {
- title: intl.formatMessage(messages.activity),
- emoji: 'soccer',
- },
- travel: {
- title: intl.formatMessage(messages.travel),
- emoji: 'earth_americas',
- },
- objects: {
- title: intl.formatMessage(messages.objects),
- emoji: 'bulb',
- },
- symbols: {
- title: intl.formatMessage(messages.symbols),
- emoji: 'clock9',
- },
- flags: {
- title: intl.formatMessage(messages.flags),
- emoji: 'flag_gb',
- }
- }
-
- return (
- <Dropdown ref={this.setRef} style={dropdownStyle}>
- <DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)} style={dropdownTriggerStyle}>
- <img draggable="false" className="emojione" alt="🙂" src="/emoji/1f602.svg" />
- </DropdownTrigger>
-
- <DropdownContent className='dropdown__left'>
- <EmojiPicker emojione={settings} onChange={this.handleChange} searchPlaceholder={intl.formatMessage(messages.emoji_search)} categories={categories} search={true} />
- </DropdownContent>
- </Dropdown>
- );
- }
-
-}
-
-EmojiPickerDropdown.propTypes = {
- intl: PropTypes.object.isRequired,
- onPickEmoji: PropTypes.func.isRequired
-};
-
-export default injectIntl(EmojiPickerDropdown);
diff --git a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx b/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx
@@ -1,32 +0,0 @@
-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';
-
-class NavigationBar extends React.PureComponent {
-
- render () {
- return (
- <div className='navigation-bar'>
- <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}><Avatar src={this.props.account.get('avatar')} animate size={40} /></Permalink>
-
- <div className='navigation-bar__profile'>
- <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
- <strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
- </Permalink>
- <a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
- </div>
- </div>
- );
- }
-
-}
-
-NavigationBar.propTypes = {
- account: ImmutablePropTypes.map.isRequired
-};
-
-export default NavigationBar;
diff --git a/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx
@@ -1,104 +0,0 @@
-import PropTypes from 'prop-types';
-import { injectIntl, defineMessages } from 'react-intl';
-import IconButton from '../../../components/icon_button';
-
-const messages = defineMessages({
- public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
- public_long: { id: 'privacy.public.long', defaultMessage: 'Post to public timelines' },
- unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
- unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' },
- private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
- private_long: { id: 'privacy.private.long', defaultMessage: 'Post to followers only' },
- direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
- direct_long: { id: 'privacy.direct.long', defaultMessage: 'Post to mentioned users only' },
- change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' }
-});
-
-const iconStyle = {
- height: null,
- lineHeight: '27px'
-}
-
-class PrivacyDropdown extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.state = {
- open: false
- };
- this.handleToggle = this.handleToggle.bind(this);
- this.handleClick = this.handleClick.bind(this);
- this.onGlobalClick = this.onGlobalClick.bind(this);
- this.setRef = this.setRef.bind(this);
- }
-
- handleToggle () {
- this.setState({ open: !this.state.open });
- }
-
- handleClick (value, e) {
- e.preventDefault();
- this.setState({ open: false });
- this.props.onChange(value);
- }
-
- onGlobalClick (e) {
- if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
- this.setState({ open: false });
- }
- }
-
- componentDidMount () {
- window.addEventListener('click', this.onGlobalClick);
- window.addEventListener('touchstart', this.onGlobalClick);
- }
-
- componentWillUnmount () {
- window.removeEventListener('click', this.onGlobalClick);
- window.removeEventListener('touchstart', this.onGlobalClick);
- }
-
- setRef (c) {
- this.node = c;
- }
-
- render () {
- const { value, onChange, intl } = this.props;
- const { open } = this.state;
-
- const options = [
- { icon: 'globe', value: 'public', shortText: intl.formatMessage(messages.public_short), longText: intl.formatMessage(messages.public_long) },
- { icon: 'unlock-alt', value: 'unlisted', shortText: intl.formatMessage(messages.unlisted_short), longText: intl.formatMessage(messages.unlisted_long) },
- { icon: 'lock', value: 'private', shortText: intl.formatMessage(messages.private_short), longText: intl.formatMessage(messages.private_long) },
- { icon: 'envelope', value: 'direct', shortText: intl.formatMessage(messages.direct_short), longText: intl.formatMessage(messages.direct_long) }
- ];
-
- const valueOption = options.find(item => item.value === value);
-
- return (
- <div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}>
- <div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} active={open} inverted onClick={this.handleToggle} style={iconStyle}/></div>
- <div className='privacy-dropdown__dropdown'>
- {options.map(item =>
- <div role='button' tabIndex='0' key={item.value} onClick={this.handleClick.bind(this, item.value)} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}>
- <div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div>
- <div className='privacy-dropdown__option__content'>
- <strong>{item.shortText}</strong>
- {item.longText}
- </div>
- </div>
- )}
- </div>
- </div>
- );
- }
-
-}
-
-PrivacyDropdown.propTypes = {
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(PrivacyDropdown);
diff --git a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx b/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx
@@ -1,69 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import Avatar from '../../../components/avatar';
-import IconButton from '../../../components/icon_button';
-import DisplayName from '../../../components/display_name';
-import emojify from '../../../emoji';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }
-});
-
-class ReplyIndicator extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleClick = this.handleClick.bind(this);
- this.handleAccountClick = this.handleAccountClick.bind(this);
- }
-
- handleClick () {
- this.props.onCancel();
- }
-
- handleAccountClick (e) {
- if (e.button === 0) {
- e.preventDefault();
- this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
- }
- }
-
- render () {
- const { status, intl } = this.props;
-
- if (!status) {
- return null;
- }
-
- const content = { __html: emojify(status.get('content')) };
-
- return (
- <div className='reply-indicator'>
- <div className='reply-indicator__header'>
- <div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div>
-
- <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name'>
- <div className='reply-indicator__display-avatar'><Avatar size={24} src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} /></div>
- <DisplayName account={status.get('account')} />
- </a>
- </div>
-
- <div className='reply-indicator__content' dangerouslySetInnerHTML={content} />
- </div>
- );
- }
-
-}
-
-ReplyIndicator.contextTypes = {
- router: PropTypes.object
-};
-
-ReplyIndicator.propTypes = {
- status: ImmutablePropTypes.map,
- onCancel: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(ReplyIndicator);
diff --git a/app/assets/javascripts/components/features/compose/components/search.jsx b/app/assets/javascripts/components/features/compose/components/search.jsx
@@ -1,82 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-
-const messages = defineMessages({
- placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }
-});
-
-class Search extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleChange = this.handleChange.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
- this.handleFocus = this.handleFocus.bind(this);
- this.handleClear = this.handleClear.bind(this);
- }
-
- handleChange (e) {
- this.props.onChange(e.target.value);
- }
-
- handleClear (e) {
- e.preventDefault();
-
- if (this.props.value.length > 0 || this.props.submitted) {
- this.props.onClear();
- }
- }
-
- handleKeyDown (e) {
- if (e.key === 'Enter') {
- e.preventDefault();
- this.props.onSubmit();
- }
- }
-
- noop () {
-
- }
-
- handleFocus () {
- this.props.onShow();
- }
-
- render () {
- const { intl, value, submitted } = this.props;
- const hasValue = value.length > 0 || submitted;
-
- return (
- <div className='search'>
- <input
- className='search__input'
- type='text'
- placeholder={intl.formatMessage(messages.placeholder)}
- value={value}
- onChange={this.handleChange}
- onKeyUp={this.handleKeyDown}
- onFocus={this.handleFocus}
- />
-
- <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
- <i className={`fa fa-search ${hasValue ? '' : 'active'}`} />
- <i aria-label={intl.formatMessage(messages.placeholder)} className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} />
- </div>
- </div>
- );
- }
-
-}
-
-Search.propTypes = {
- value: PropTypes.string.isRequired,
- submitted: PropTypes.bool,
- onChange: PropTypes.func.isRequired,
- onSubmit: PropTypes.func.isRequired,
- onClear: PropTypes.func.isRequired,
- onShow: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(Search);
diff --git a/app/assets/javascripts/components/features/compose/components/search_results.jsx b/app/assets/javascripts/components/features/compose/components/search_results.jsx
@@ -1,65 +0,0 @@
-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';
-
-class SearchResults extends React.PureComponent {
-
- render () {
- const { results } = this.props;
-
- let accounts, statuses, hashtags;
- let count = 0;
-
- if (results.get('accounts') && results.get('accounts').size > 0) {
- count += results.get('accounts').size;
- accounts = (
- <div className='search-results__section'>
- {results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
- </div>
- );
- }
-
- if (results.get('statuses') && results.get('statuses').size > 0) {
- count += results.get('statuses').size;
- statuses = (
- <div className='search-results__section'>
- {results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
- </div>
- );
- }
-
- if (results.get('hashtags') && results.get('hashtags').size > 0) {
- count += results.get('hashtags').size;
- hashtags = (
- <div className='search-results__section'>
- {results.get('hashtags').map(hashtag =>
- <Link className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}>
- #{hashtag}
- </Link>
- )}
- </div>
- );
- }
-
- return (
- <div className='search-results'>
- <div className='search-results__header'>
- <FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} />
- </div>
-
- {accounts}
- {statuses}
- {hashtags}
- </div>
- );
- }
-
-}
-
-SearchResults.propTypes = {
- results: ImmutablePropTypes.map.isRequired
-};
-
-export default SearchResults;
diff --git a/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx b/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx
@@ -1,35 +0,0 @@
-import PropTypes from 'prop-types';
-
-class TextIconButton extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleClick (e) {
- e.preventDefault();
- this.props.onClick();
- }
-
- render () {
- const { label, title, active, ariaControls } = this.props;
-
- return (
- <button title={title} aria-label={title} className={`text-icon-button ${active ? 'active' : ''}`} aria-expanded={active} onClick={this.handleClick} aria-controls={ariaControls}>
- {label}
- </button>
- );
- }
-
-}
-
-TextIconButton.propTypes = {
- label: PropTypes.string.isRequired,
- title: PropTypes.string,
- active: PropTypes.bool,
- onClick: PropTypes.func.isRequired,
- ariaControls: PropTypes.string
-};
-
-export default TextIconButton;
diff --git a/app/assets/javascripts/components/features/compose/components/upload_button.jsx b/app/assets/javascripts/components/features/compose/components/upload_button.jsx
@@ -1,60 +0,0 @@
-import IconButton from '../../../components/icon_button';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- upload: { id: 'upload_button.label', defaultMessage: 'Add media' }
-});
-
-
-const iconStyle = {
- height: null,
- lineHeight: '27px'
-}
-
-class UploadButton extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleChange = this.handleChange.bind(this);
- this.handleClick = this.handleClick.bind(this);
- this.setRef = this.setRef.bind(this);
- }
-
- handleChange (e) {
- if (e.target.files.length > 0) {
- this.props.onSelectFile(e.target.files);
- }
- }
-
- handleClick () {
- this.fileElement.click();
- }
-
- setRef (c) {
- this.fileElement = c;
- }
-
- render () {
-
- const { intl, resetFileKey, disabled } = this.props;
-
- return (
- <div className='compose-form__upload-button'>
- <IconButton icon='camera' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle}/>
- <input key={resetFileKey} ref={this.setRef} type='file' multiple={false} onChange={this.handleChange} disabled={disabled} style={{ display: 'none' }} />
- </div>
- );
- }
-
-}
-
-UploadButton.propTypes = {
- disabled: PropTypes.bool,
- onSelectFile: PropTypes.func.isRequired,
- style: PropTypes.object,
- resetFileKey: PropTypes.number,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(UploadButton);
diff --git a/app/assets/javascripts/components/features/compose/components/upload_form.jsx b/app/assets/javascripts/components/features/compose/components/upload_form.jsx
@@ -1,45 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import IconButton from '../../../components/icon_button';
-import { defineMessages, injectIntl } from 'react-intl';
-import UploadProgressContainer from '../containers/upload_progress_container';
-import { Motion, spring } from 'react-motion';
-
-const messages = defineMessages({
- undo: { id: 'upload_form.undo', defaultMessage: 'Undo' }
-});
-
-class UploadForm extends React.PureComponent {
-
- render () {
- const { intl, media } = this.props;
-
- const uploads = media.map(attachment =>
- <div className='compose-form__upload' key={attachment.get('id')}>
- <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
- {({ scale }) =>
- <div className='compose-form__upload-thumbnail' style={{ transform: `translateZ(0) scale(${scale})`, backgroundImage: `url(${attachment.get('preview_url')})` }}>
- <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} />
- </div>
- }
- </Motion>
- </div>
- );
-
- return (
- <div className='compose-form__upload-wrapper'>
- <UploadProgressContainer />
- <div className='compose-form__uploads-wrapper'>{uploads}</div>
- </div>
- );
- }
-
-}
-
-UploadForm.propTypes = {
- media: ImmutablePropTypes.list.isRequired,
- onRemoveFile: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(UploadForm);
diff --git a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx b/app/assets/javascripts/components/features/compose/components/upload_progress.jsx
@@ -1,42 +0,0 @@
-import PropTypes from 'prop-types';
-import { Motion, spring } from 'react-motion';
-import { FormattedMessage } from 'react-intl';
-
-class UploadProgress extends React.PureComponent {
-
- render () {
- const { active, progress } = this.props;
-
- if (!active) {
- return null;
- }
-
- return (
- <div className='upload-progress'>
- <div className='upload-progress__icon'>
- <i className='fa fa-upload' />
- </div>
-
- <div className='upload-progress__message'>
- <FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' />
-
- <div className='upload-progress__backdrop'>
- <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
- {({ width }) =>
- <div className='upload-progress__tracker' style={{ width: `${width}%` }} />
- }
- </Motion>
- </div>
- </div>
- </div>
- );
- }
-
-}
-
-UploadProgress.propTypes = {
- active: PropTypes.bool,
- progress: PropTypes.number
-};
-
-export default UploadProgress;
diff --git a/app/assets/javascripts/components/features/compose/components/warning.jsx b/app/assets/javascripts/components/features/compose/components/warning.jsx
@@ -1,25 +0,0 @@
-import PropTypes from 'prop-types';
-
-class Warning extends React.PureComponent {
-
- constructor (props) {
- super(props);
- }
-
- render () {
- const { message } = this.props;
-
- return (
- <div className='compose-form__warning'>
- {message}
- </div>
- );
- }
-
-}
-
-Warning.propTypes = {
- message: PropTypes.node.isRequired
-};
-
-export default Warning;
diff --git a/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx
@@ -1,50 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import TextIconButton from '../components/text_icon_button';
-import { changeComposeSensitivity } from '../../../actions/compose';
-import { Motion, spring } from 'react-motion';
-import { injectIntl, defineMessages } from 'react-intl';
-
-const messages = defineMessages({
- title: { id: 'compose_form.sensitive', defaultMessage: 'Mark media as sensitive' }
-});
-
-const mapStateToProps = state => ({
- visible: state.getIn(['compose', 'media_attachments']).size > 0,
- active: state.getIn(['compose', 'sensitive'])
-});
-
-const mapDispatchToProps = dispatch => ({
-
- onClick () {
- dispatch(changeComposeSensitivity());
- }
-
-});
-
-class SensitiveButton extends React.PureComponent {
-
- render () {
- const { visible, active, onClick, intl } = this.props;
-
- return (
- <Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(visible ? 1 : 0.87, { stiffness: 200, damping: 3 }) }}>
- {({ scale }) =>
- <div style={{ display: visible ? 'block' : 'none', transform: `translateZ(0) scale(${scale})` }}>
- <TextIconButton onClick={onClick} label='NSFW' title={intl.formatMessage(messages.title)} active={active} />
- </div>
- }
- </Motion>
- );
- }
-
-}
-
-SensitiveButton.propTypes = {
- visible: PropTypes.bool,
- active: PropTypes.bool,
- onClick: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton));
diff --git a/app/assets/javascripts/components/features/compose/containers/warning_container.jsx b/app/assets/javascripts/components/features/compose/containers/warning_container.jsx
@@ -1,48 +0,0 @@
-import { connect } from 'react-redux';
-import Warning from '../components/warning';
-import { createSelector } from 'reselect';
-import PropTypes from 'prop-types';
-import { FormattedMessage } from 'react-intl';
-
-const getMentionedUsernames = createSelector(state => state.getIn(['compose', 'text']), text => text.match(/(?:^|[^\/\w])@([a-z0-9_]+@[a-z0-9\.\-]+)/ig));
-
-const getMentionedDomains = createSelector(getMentionedUsernames, mentionedUsernamesWithDomains => {
- return mentionedUsernamesWithDomains !== null ? [...new Set(mentionedUsernamesWithDomains.map(item => item.split('@')[2]))] : [];
-});
-
-const mapStateToProps = state => {
- const mentionedUsernames = getMentionedUsernames(state);
- const mentionedUsernamesWithDomains = getMentionedDomains(state);
-
- return {
- needsLeakWarning: (state.getIn(['compose', 'privacy']) === 'private' || state.getIn(['compose', 'privacy']) === 'direct') && mentionedUsernames !== null,
- mentionedDomains: mentionedUsernamesWithDomains,
- needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', state.getIn(['meta', 'me']), 'locked'])
- };
-};
-
-const WarningWrapper = ({ needsLeakWarning, needsLockWarning, mentionedDomains }) => {
- if (needsLockWarning) {
- return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <a href='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></a> }} />} />;
- } else if (needsLeakWarning) {
- return (
- <Warning
- message={<FormattedMessage
- id='compose_form.privacy_disclaimer'
- defaultMessage='Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.'
- values={{ domains: <strong>{mentionedDomains.join(', ')}</strong>, domainsCount: mentionedDomains.length }}
- />}
- />
- );
- }
-
- return null;
-};
-
-WarningWrapper.propTypes = {
- needsLeakWarning: PropTypes.bool,
- needsLockWarning: PropTypes.bool,
- mentionedDomains: PropTypes.array.isRequired,
-};
-
-export default connect(mapStateToProps)(WarningWrapper);
diff --git a/app/assets/javascripts/components/features/compose/index.jsx b/app/assets/javascripts/components/features/compose/index.jsx
@@ -1,85 +0,0 @@
-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';
-import { mountCompose, unmountCompose } from '../../actions/compose';
-import { Link } from 'react-router';
-import { injectIntl, defineMessages } from 'react-intl';
-import SearchContainer from './containers/search_container';
-import { Motion, spring } from 'react-motion';
-import SearchResultsContainer from './containers/search_results_container';
-
-const messages = defineMessages({
- start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
- public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
- community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
- preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
- logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }
-});
-
-const mapStateToProps = state => ({
- showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden'])
-});
-
-class Compose extends React.PureComponent {
-
- componentDidMount () {
- this.props.dispatch(mountCompose());
- }
-
- componentWillUnmount () {
- this.props.dispatch(unmountCompose());
- }
-
- render () {
- const { withHeader, showSearch, intl } = this.props;
-
- let header = '';
-
- if (withHeader) {
- header = (
- <div className='drawer__header'>
- <Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)}><i role="img" aria-label={intl.formatMessage(messages.start)} className='fa fa-fw fa-asterisk' /></Link>
- <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role="img" aria-label={intl.formatMessage(messages.community)} className='fa fa-fw fa-users' /></Link>
- <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role="img" aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link>
- <a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)}><i role="img" aria-label={intl.formatMessage(messages.preferences)} className='fa fa-fw fa-cog' /></a>
- <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)}><i role="img" aria-label={intl.formatMessage(messages.logout)} className='fa fa-fw fa-sign-out' /></a>
- </div>
- );
- }
-
- return (
- <div className='drawer'>
- {header}
-
- <SearchContainer />
-
- <div className='drawer__pager'>
- <div className='drawer__inner'>
- <NavigationContainer />
- <ComposeFormContainer />
- </div>
-
- <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
- {({ x }) =>
- <div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
- <SearchResultsContainer />
- </div>
- }
- </Motion>
- </div>
- </div>
- );
- }
-
-}
-
-Compose.propTypes = {
- dispatch: PropTypes.func.isRequired,
- withHeader: PropTypes.bool,
- showSearch: PropTypes.bool,
- intl: PropTypes.object.isRequired
-};
-
-export default connect(mapStateToProps)(injectIntl(Compose));
diff --git a/app/assets/javascripts/components/features/favourited_statuses/index.jsx b/app/assets/javascripts/components/features/favourited_statuses/index.jsx
@@ -1,66 +0,0 @@
-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';
-import StatusList from '../../components/status_list';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- heading: { id: 'column.favourites', defaultMessage: 'Favourites' }
-});
-
-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 React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleScrollToBottom = this.handleScrollToBottom.bind(this);
- }
-
- componentWillMount () {
- this.props.dispatch(fetchFavouritedStatuses());
- }
-
- handleScrollToBottom () {
- this.props.dispatch(expandFavouritedStatuses());
- }
-
- render () {
- const { statusIds, loaded, intl, me } = this.props;
-
- if (!loaded) {
- return (
- <Column>
- <LoadingIndicator />
- </Column>
- );
- }
-
- return (
- <Column icon='star' heading={intl.formatMessage(messages.heading)}>
- <ColumnBackButtonSlim />
- <StatusList {...this.props} onScrollToBottom={this.handleScrollToBottom} />
- </Column>
- );
- }
-
-}
-
-Favourites.propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- statusIds: ImmutablePropTypes.list.isRequired,
- loaded: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- me: PropTypes.number.isRequired
-};
-
-export default connect(mapStateToProps)(injectIntl(Favourites));
diff --git a/app/assets/javascripts/components/features/favourites/index.jsx b/app/assets/javascripts/components/features/favourites/index.jsx
@@ -1,59 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import { fetchFavourites } from '../../actions/interactions';
-import { ScrollContainer } from 'react-router-scroll';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import ColumnBackButton from '../../components/column_back_button';
-
-const mapStateToProps = (state, props) => ({
- accountIds: state.getIn(['user_lists', 'favourited_by', Number(props.params.statusId)])
-});
-
-class Favourites extends React.PureComponent {
-
- componentWillMount () {
- this.props.dispatch(fetchFavourites(Number(this.props.params.statusId)));
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
- this.props.dispatch(fetchFavourites(Number(nextProps.params.statusId)));
- }
- }
-
- render () {
- const { accountIds } = this.props;
-
- if (!accountIds) {
- return (
- <Column>
- <LoadingIndicator />
- </Column>
- );
- }
-
- return (
- <Column>
- <ColumnBackButton />
-
- <ScrollContainer scrollKey='favourites'>
- <div className='scrollable'>
- {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
- </div>
- </ScrollContainer>
- </Column>
- );
- }
-
-}
-
-Favourites.propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- accountIds: ImmutablePropTypes.list
-};
-
-export default connect(mapStateToProps)(Favourites);
diff --git a/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx b/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx
@@ -1,44 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import Permalink from '../../../components/permalink';
-import Avatar from '../../../components/avatar';
-import DisplayName from '../../../components/display_name';
-import emojify from '../../../emoji';
-import IconButton from '../../../components/icon_button';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' },
- reject: { id: 'follow_request.reject', defaultMessage: 'Reject' }
-});
-
-const AccountAuthorize = ({ intl, account, onAuthorize, onReject }) => {
- const content = { __html: emojify(account.get('note')) };
-
- return (
- <div className='account-authorize__wrapper'>
- <div className='account-authorize'>
- <Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name'>
- <div className='account-authorize__avatar'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={48} /></div>
- <DisplayName account={account} />
- </Permalink>
-
- <div className='account__header__content' dangerouslySetInnerHTML={content} />
- </div>
-
- <div className='account--panel'>
- <div className='account--panel__button'><IconButton title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} /></div>
- <div className='account--panel__button'><IconButton title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} /></div>
- </div>
- </div>
- )
-};
-
-AccountAuthorize.propTypes = {
- account: ImmutablePropTypes.map.isRequired,
- onAuthorize: PropTypes.func.isRequired,
- onReject: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(AccountAuthorize);
diff --git a/app/assets/javascripts/components/features/follow_requests/index.jsx b/app/assets/javascripts/components/features/follow_requests/index.jsx
@@ -1,72 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll';
-import Column from '../ui/components/column';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import AccountAuthorizeContainer from './containers/account_authorize_container';
-import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' }
-});
-
-const mapStateToProps = state => ({
- accountIds: state.getIn(['user_lists', 'follow_requests', 'items'])
-});
-
-class FollowRequests extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleScroll = this.handleScroll.bind(this);
- }
-
- componentWillMount () {
- this.props.dispatch(fetchFollowRequests());
- }
-
- handleScroll (e) {
- const { scrollTop, scrollHeight, clientHeight } = e.target;
-
- if (scrollTop === scrollHeight - clientHeight) {
- this.props.dispatch(expandFollowRequests());
- }
- }
-
- render () {
- const { intl, accountIds } = this.props;
-
- if (!accountIds) {
- return (
- <Column>
- <LoadingIndicator />
- </Column>
- );
- }
-
- return (
- <Column icon='users' heading={intl.formatMessage(messages.heading)}>
- <ColumnBackButtonSlim />
- <ScrollContainer scrollKey='follow_requests'>
- <div className='scrollable' onScroll={this.handleScroll}>
- {accountIds.map(id =>
- <AccountAuthorizeContainer key={id} id={id} />
- )}
- </div>
- </ScrollContainer>
- </Column>
- );
- }
-}
-
-FollowRequests.propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- accountIds: ImmutablePropTypes.list,
- intl: PropTypes.object.isRequired
-};
-
-export default connect(mapStateToProps)(injectIntl(FollowRequests));
diff --git a/app/assets/javascripts/components/features/followers/index.jsx b/app/assets/javascripts/components/features/followers/index.jsx
@@ -1,90 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import {
- fetchAccount,
- fetchFollowers,
- expandFollowers
-} from '../../actions/accounts';
-import { ScrollContainer } from 'react-router-scroll';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import HeaderContainer from '../account_timeline/containers/header_container';
-import LoadMore from '../../components/load_more';
-import ColumnBackButton from '../../components/column_back_button';
-
-const mapStateToProps = (state, props) => ({
- accountIds: state.getIn(['user_lists', 'followers', Number(props.params.accountId), 'items'])
-});
-
-class Followers extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleScroll = this.handleScroll.bind(this);
- this.handleLoadMore = this.handleLoadMore.bind(this);
- }
-
- componentWillMount () {
- this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
- this.props.dispatch(fetchFollowers(Number(this.props.params.accountId)));
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
- this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
- this.props.dispatch(fetchFollowers(Number(nextProps.params.accountId)));
- }
- }
-
- handleScroll (e) {
- const { scrollTop, scrollHeight, clientHeight } = e.target;
-
- if (scrollTop === scrollHeight - clientHeight) {
- this.props.dispatch(expandFollowers(Number(this.props.params.accountId)));
- }
- }
-
- handleLoadMore (e) {
- e.preventDefault();
- this.props.dispatch(expandFollowers(Number(this.props.params.accountId)));
- }
-
- render () {
- const { accountIds } = this.props;
-
- if (!accountIds) {
- return (
- <Column>
- <LoadingIndicator />
- </Column>
- );
- }
-
- return (
- <Column>
- <ColumnBackButton />
-
- <ScrollContainer scrollKey='followers'>
- <div className='scrollable' onScroll={this.handleScroll}>
- <div className='followers'>
- <HeaderContainer accountId={this.props.params.accountId} />
- {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
- <LoadMore onClick={this.handleLoadMore} />
- </div>
- </div>
- </ScrollContainer>
- </Column>
- );
- }
-
-}
-
-Followers.propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- accountIds: ImmutablePropTypes.list
-};
-
-export default connect(mapStateToProps)(Followers);
diff --git a/app/assets/javascripts/components/features/following/index.jsx b/app/assets/javascripts/components/features/following/index.jsx
@@ -1,90 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import {
- fetchAccount,
- fetchFollowing,
- expandFollowing
-} from '../../actions/accounts';
-import { ScrollContainer } from 'react-router-scroll';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import HeaderContainer from '../account_timeline/containers/header_container';
-import LoadMore from '../../components/load_more';
-import ColumnBackButton from '../../components/column_back_button';
-
-const mapStateToProps = (state, props) => ({
- accountIds: state.getIn(['user_lists', 'following', Number(props.params.accountId), 'items'])
-});
-
-class Following extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleScroll = this.handleScroll.bind(this);
- this.handleLoadMore = this.handleLoadMore.bind(this);
- }
-
- componentWillMount () {
- this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
- this.props.dispatch(fetchFollowing(Number(this.props.params.accountId)));
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
- this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
- this.props.dispatch(fetchFollowing(Number(nextProps.params.accountId)));
- }
- }
-
- handleScroll (e) {
- const { scrollTop, scrollHeight, clientHeight } = e.target;
-
- if (scrollTop === scrollHeight - clientHeight) {
- this.props.dispatch(expandFollowing(Number(this.props.params.accountId)));
- }
- }
-
- handleLoadMore (e) {
- e.preventDefault();
- this.props.dispatch(expandFollowing(Number(this.props.params.accountId)));
- }
-
- render () {
- const { accountIds } = this.props;
-
- if (!accountIds) {
- return (
- <Column>
- <LoadingIndicator />
- </Column>
- );
- }
-
- return (
- <Column>
- <ColumnBackButton />
-
- <ScrollContainer scrollKey='following'>
- <div className='scrollable' onScroll={this.handleScroll}>
- <div className='following'>
- <HeaderContainer accountId={this.props.params.accountId} />
- {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
- <LoadMore onClick={this.handleLoadMore} />
- </div>
- </div>
- </ScrollContainer>
- </Column>
- );
- }
-
-}
-
-Following.propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- accountIds: ImmutablePropTypes.list
-};
-
-export default connect(mapStateToProps)(Following);
diff --git a/app/assets/javascripts/components/features/generic_not_found/index.jsx b/app/assets/javascripts/components/features/generic_not_found/index.jsx
@@ -1,10 +0,0 @@
-import Column from '../ui/components/column';
-import MissingIndicator from '../../components/missing_indicator';
-
-const GenericNotFound = () => (
- <Column>
- <MissingIndicator />
- </Column>
-);
-
-export default GenericNotFound;
diff --git a/app/assets/javascripts/components/features/getting_started/index.jsx b/app/assets/javascripts/components/features/getting_started/index.jsx
@@ -1,66 +0,0 @@
-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';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-const messages = defineMessages({
- heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
- public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
- navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation'},
- settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings'},
- community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
- preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
- follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
- sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
- favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
- blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
- mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
- info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }
-});
-
-const mapStateToProps = state => ({
- me: state.getIn(['accounts', state.getIn(['meta', 'me'])])
-});
-
-const GettingStarted = ({ intl, me }) => {
- let followRequests = '';
-
- if (me.get('locked')) {
- followRequests = <ColumnLink icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />;
- }
-
- return (
- <Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile={true}>
- <div className='getting-started__wrapper'>
- <ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)}/>
- <ColumnLink icon='users' hideOnMobile={true} text={intl.formatMessage(messages.community_timeline)} to='/timelines/public/local' />
- <ColumnLink icon='globe' hideOnMobile={true} text={intl.formatMessage(messages.public_timeline)} to='/timelines/public' />
- <ColumnLink icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />
- {followRequests}
- <ColumnLink icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />
- <ColumnLink icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />
- <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)}/>
- <ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
- <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
- <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
- </div>
-
- <div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}>
- <div className='static-content getting-started'>
- <p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p>
- </div>
- </div>
- </Column>
- );
-};
-
-GettingStarted.propTypes = {
- intl: PropTypes.object.isRequired,
- me: ImmutablePropTypes.map.isRequired
-};
-
-export default connect(mapStateToProps)(injectIntl(GettingStarted));
diff --git a/app/assets/javascripts/components/features/hashtag_timeline/index.jsx b/app/assets/javascripts/components/features/hashtag_timeline/index.jsx
@@ -1,89 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import StatusListContainer from '../ui/containers/status_list_container';
-import Column from '../ui/components/column';
-import {
- refreshTimeline,
- updateTimeline,
- deleteFromTimelines
-} from '../../actions/timelines';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import { FormattedMessage } from 'react-intl';
-import createStream from '../../stream';
-
-const mapStateToProps = state => ({
- hasUnread: state.getIn(['timelines', 'tag', 'unread']) > 0,
- streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
- accessToken: state.getIn(['meta', 'access_token'])
-});
-
-class HashtagTimeline extends React.PureComponent {
-
- _subscribe (dispatch, id) {
- const { streamingAPIBaseURL, accessToken } = this.props;
-
- this.subscription = createStream(streamingAPIBaseURL, accessToken, `hashtag&tag=${id}`, {
-
- received (data) {
- switch(data.event) {
- case 'update':
- dispatch(updateTimeline('tag', JSON.parse(data.payload)));
- break;
- case 'delete':
- dispatch(deleteFromTimelines(data.payload));
- break;
- }
- }
-
- });
- }
-
- _unsubscribe () {
- if (typeof this.subscription !== 'undefined') {
- this.subscription.close();
- this.subscription = null;
- }
- }
-
- componentDidMount () {
- const { dispatch } = this.props;
- const { id } = this.props.params;
-
- dispatch(refreshTimeline('tag', id));
- this._subscribe(dispatch, id);
- }
-
- componentWillReceiveProps (nextProps) {
- if (nextProps.params.id !== this.props.params.id) {
- this.props.dispatch(refreshTimeline('tag', nextProps.params.id));
- this._unsubscribe();
- this._subscribe(this.props.dispatch, nextProps.params.id);
- }
- }
-
- componentWillUnmount () {
- this._unsubscribe();
- }
-
- render () {
- const { id, hasUnread } = this.props.params;
-
- return (
- <Column icon='hashtag' active={hasUnread} heading={id}>
- <ColumnBackButtonSlim />
- <StatusListContainer scrollKey='hashtag_timeline' type='tag' id={id} emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} />
- </Column>
- );
- }
-
-}
-
-HashtagTimeline.propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- streamingAPIBaseURL: PropTypes.string.isRequired,
- accessToken: PropTypes.string.isRequired,
- hasUnread: PropTypes.bool
-};
-
-export default connect(mapStateToProps)(HashtagTimeline);
diff --git a/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx b/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx
@@ -1,50 +0,0 @@
-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 './setting_text';
-
-const messages = defineMessages({
- filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
- settings: { id: 'home.settings', defaultMessage: 'Column settings' }
-});
-
-class ColumnSettings extends React.PureComponent {
-
- render () {
- const { settings, onChange, onSave, intl } = this.props;
-
- return (
- <ColumnCollapsable icon='sliders' title={intl.formatMessage(messages.settings)} fullHeight={209} onCollapse={onSave}>
- <div className='column-settings__outer'>
- <span className='column-settings__section'><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span>
-
- <div className='column-settings__row'>
- <SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_reblogs' defaultMessage='Show boosts' />} />
- </div>
-
- <div className='column-settings__row'>
- <SettingToggle settings={settings} settingKey={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_replies' defaultMessage='Show replies' />} />
- </div>
-
- <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
-
- <div className='column-settings__row'>
- <SettingText settings={settings} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
- </div>
- </div>
- </ColumnCollapsable>
- );
- }
-
-}
-
-ColumnSettings.propTypes = {
- settings: ImmutablePropTypes.map.isRequired,
- onChange: PropTypes.func.isRequired,
- onSave: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-}
-
-export default injectIntl(ColumnSettings);
diff --git a/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx b/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx
@@ -1,37 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-class SettingText extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange (e) {
- this.props.onChange(this.props.settingKey, e.target.value)
- }
-
- render () {
- const { settings, settingKey, label } = this.props;
-
- return (
- <input
- className='setting-text'
- value={settings.getIn(settingKey)}
- onChange={this.handleChange}
- placeholder={label}
- />
- );
- }
-
-}
-
-SettingText.propTypes = {
- settings: ImmutablePropTypes.map.isRequired,
- settingKey: PropTypes.array.isRequired,
- label: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired
-};
-
-export default SettingText;
diff --git a/app/assets/javascripts/components/features/home_timeline/index.jsx b/app/assets/javascripts/components/features/home_timeline/index.jsx
@@ -1,37 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import StatusListContainer from '../ui/containers/status_list_container';
-import Column from '../ui/components/column';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnSettingsContainer from './containers/column_settings_container';
-import { Link } from 'react-router';
-
-const messages = defineMessages({
- title: { id: 'column.home', defaultMessage: 'Home' }
-});
-
-const mapStateToProps = state => ({
- hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0
-});
-
-class HomeTimeline extends React.PureComponent {
-
- render () {
- const { intl, hasUnread } = this.props;
-
- return (
- <Column icon='home' active={hasUnread} heading={intl.formatMessage(messages.title)}>
- <ColumnSettingsContainer />
- <StatusListContainer {...this.props} scrollKey='home_timeline' type='home' emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage="You aren't following anyone yet. Visit {public} or use search to get started and meet other users." values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} />
- </Column>
- );
- }
-
-}
-
-HomeTimeline.propTypes = {
- intl: PropTypes.object.isRequired,
- hasUnread: PropTypes.bool
-};
-
-export default connect(mapStateToProps)(injectIntl(HomeTimeline));
diff --git a/app/assets/javascripts/components/features/mutes/index.jsx b/app/assets/javascripts/components/features/mutes/index.jsx
@@ -1,73 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll';
-import Column from '../ui/components/column';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import AccountContainer from '../../containers/account_container';
-import { fetchMutes, expandMutes } from '../../actions/mutes';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- heading: { id: 'column.mutes', defaultMessage: 'Muted users' }
-});
-
-const mapStateToProps = state => ({
- accountIds: state.getIn(['user_lists', 'mutes', 'items'])
-});
-
-class Mutes extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleScroll = this.handleScroll.bind(this);
- }
-
- componentWillMount () {
- this.props.dispatch(fetchMutes());
- }
-
- handleScroll (e) {
- const { scrollTop, scrollHeight, clientHeight } = e.target;
-
- if (scrollTop === scrollHeight - clientHeight) {
- this.props.dispatch(expandMutes());
- }
- }
-
- render () {
- const { intl, accountIds } = this.props;
-
- if (!accountIds) {
- return (
- <Column>
- <LoadingIndicator />
- </Column>
- );
- }
-
- return (
- <Column icon='volume-off' heading={intl.formatMessage(messages.heading)}>
- <ColumnBackButtonSlim />
- <ScrollContainer scrollKey='mutes'>
- <div className='scrollable mutes' onScroll={this.handleScroll}>
- {accountIds.map(id =>
- <AccountContainer key={id} id={id} />
- )}
- </div>
- </ScrollContainer>
- </Column>
- );
- }
-
-}
-
-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/assets/javascripts/components/features/notifications/components/clear_column_button.jsx b/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx
@@ -1,26 +0,0 @@
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- clear: { id: 'notifications.clear', defaultMessage: 'Clear notifications' }
-});
-
-class ClearColumnButton extends React.Component {
-
- render () {
- const { intl } = this.props;
-
- return (
- <div role='button' title={intl.formatMessage(messages.clear)} className='column-icon column-icon-clear' tabIndex='0' onClick={this.props.onClick}>
- <i className='fa fa-eraser' />
- </div>
- );
- }
-}
-
-ClearColumnButton.propTypes = {
- onClick: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(ClearColumnButton);
diff --git a/app/assets/javascripts/components/features/notifications/components/column_settings.jsx b/app/assets/javascripts/components/features/notifications/components/column_settings.jsx
@@ -1,70 +0,0 @@
-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 './setting_toggle';
-
-const messages = defineMessages({
- settings: { id: 'notifications.settings', defaultMessage: 'Column settings' }
-});
-
-class ColumnSettings extends React.PureComponent {
-
- render () {
- const { settings, intl, onChange, onSave } = this.props;
-
- const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
- const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
- const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
-
- return (
- <ColumnCollapsable icon='sliders' title={intl.formatMessage(messages.settings)} fullHeight={616} onCollapse={onSave}>
- <div className='column-settings__outer'>
- <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
-
- <div className='column-settings__row'>
- <SettingToggle settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
- <SettingToggle settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
- <SettingToggle settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
- </div>
-
- <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
-
- <div className='column-settings__row'>
- <SettingToggle settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
- <SettingToggle settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
- <SettingToggle settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
- </div>
-
- <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
-
- <div className='column-settings__row'>
- <SettingToggle settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
- <SettingToggle settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
- <SettingToggle settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
- </div>
-
- <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
-
- <div className='column-settings__row'>
- <SettingToggle settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
- <SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
- <SettingToggle settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
- </div>
- </div>
- </ColumnCollapsable>
- );
- }
-
-}
-
-ColumnSettings.propTypes = {
- settings: ImmutablePropTypes.map.isRequired,
- onChange: PropTypes.func.isRequired,
- onSave: PropTypes.func.isRequired,
- intl: PropTypes.shape({
- formatMessage: PropTypes.func.isRequired
- }).isRequired
-};
-
-export default injectIntl(ColumnSettings);
diff --git a/app/assets/javascripts/components/features/notifications/components/notification.jsx b/app/assets/javascripts/components/features/notifications/components/notification.jsx
@@ -1,88 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import StatusContainer from '../../../containers/status_container';
-import AccountContainer from '../../../containers/account_container';
-import { FormattedMessage } from 'react-intl';
-import Permalink from '../../../components/permalink';
-import emojify from '../../../emoji';
-import escapeTextContentForBrowser from 'escape-html';
-
-class Notification extends React.PureComponent {
-
- renderFollow (account, link) {
- return (
- <div className='notification notification-follow'>
- <div className='notification__message'>
- <div className='notification__favourite-icon-wrapper'>
- <i className='fa fa-fw fa-user-plus' />
- </div>
-
- <FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
- </div>
-
- <AccountContainer id={account.get('id')} withNote={false} />
- </div>
- );
- }
-
- renderMention (notification) {
- return <StatusContainer id={notification.get('status')} />;
- }
-
- renderFavourite (notification, link) {
- return (
- <div className='notification notification-favourite'>
- <div className='notification__message'>
- <div className='notification__favourite-icon-wrapper'>
- <i className='fa fa-fw fa-star star-icon'/>
- </div>
-
- <FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
- </div>
-
- <StatusContainer id={notification.get('status')} muted={true} />
- </div>
- );
- }
-
- renderReblog (notification, link) {
- return (
- <div className='notification notification-reblog'>
- <div className='notification__message'>
- <div className='notification__favourite-icon-wrapper'>
- <i className='fa fa-fw fa-retweet' />
- </div>
-
- <FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
- </div>
-
- <StatusContainer id={notification.get('status')} muted={true} />
- </div>
- );
- }
-
- render () { // eslint-disable-line consistent-return
- const { notification } = this.props;
- const account = notification.get('account');
- const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username');
- const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
- const link = <Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHTML} />;
-
- switch(notification.get('type')) {
- case 'follow':
- return this.renderFollow(account, link);
- case 'mention':
- return this.renderMention(notification);
- case 'favourite':
- return this.renderFavourite(notification, link);
- case 'reblog':
- return this.renderReblog(notification, link);
- }
- }
-
-}
-
-Notification.propTypes = {
- notification: ImmutablePropTypes.map.isRequired
-};
-
-export default Notification;
diff --git a/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx b/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx
@@ -1,20 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import Toggle from 'react-toggle';
-
-const SettingToggle = ({ settings, settingKey, label, onChange, htmlFor = '' }) => (
- <label htmlFor={htmlFor} className='setting-toggle__label'>
- <Toggle checked={settings.getIn(settingKey)} onChange={(e) => onChange(settingKey, e.target.checked)} />
- <span className='setting-toggle'>{label}</span>
- </label>
-);
-
-SettingToggle.propTypes = {
- settings: ImmutablePropTypes.map.isRequired,
- settingKey: PropTypes.array.isRequired,
- label: PropTypes.node.isRequired,
- onChange: PropTypes.func.isRequired,
- htmlFor: PropTypes.string
-};
-
-export default SettingToggle;
diff --git a/app/assets/javascripts/components/features/notifications/index.jsx b/app/assets/javascripts/components/features/notifications/index.jsx
@@ -1,142 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import Column from '../ui/components/column';
-import { expandNotifications, clearNotifications, scrollTopNotifications } from '../../actions/notifications';
-import NotificationContainer from './containers/notification_container';
-import { ScrollContainer } from 'react-router-scroll';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnSettingsContainer from './containers/column_settings_container';
-import { createSelector } from 'reselect';
-import Immutable from 'immutable';
-import LoadMore from '../../components/load_more';
-import ClearColumnButton from './components/clear_column_button';
-import { openModal } from '../../actions/modal';
-
-const messages = defineMessages({
- title: { id: 'column.notifications', defaultMessage: 'Notifications' },
- clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' },
- clearConfirm: { id: 'notifications.clear', defaultMessage: 'Clear notifications' }
-});
-
-const getNotifications = createSelector([
- state => Immutable.List(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
- state => state.getIn(['notifications', 'items'])
-], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type'))));
-
-const mapStateToProps = state => ({
- notifications: getNotifications(state),
- isLoading: state.getIn(['notifications', 'isLoading'], true),
- isUnread: state.getIn(['notifications', 'unread']) > 0
-});
-
-class Notifications extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleScroll = this.handleScroll.bind(this);
- this.handleLoadMore = this.handleLoadMore.bind(this);
- this.handleClear = this.handleClear.bind(this);
- this.setRef = this.setRef.bind(this);
- }
-
- handleScroll (e) {
- const { scrollTop, scrollHeight, clientHeight } = e.target;
- const offset = scrollHeight - scrollTop - clientHeight;
- this._oldScrollPosition = scrollHeight - scrollTop;
-
- if (250 > offset && !this.props.isLoading) {
- this.props.dispatch(expandNotifications());
- } else if (scrollTop < 100) {
- this.props.dispatch(scrollTopNotifications(true));
- } else {
- this.props.dispatch(scrollTopNotifications(false));
- }
- }
-
- componentDidUpdate (prevProps) {
- if (this.node.scrollTop > 0 && (prevProps.notifications.size < this.props.notifications.size && prevProps.notifications.first() !== this.props.notifications.first() && !!this._oldScrollPosition)) {
- this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
- }
- }
-
- handleLoadMore (e) {
- e.preventDefault();
- this.props.dispatch(expandNotifications());
- }
-
- handleClear () {
- const { dispatch, intl } = this.props;
-
- dispatch(openModal('CONFIRM', {
- message: intl.formatMessage(messages.clearMessage),
- confirm: intl.formatMessage(messages.clearConfirm),
- onConfirm: () => dispatch(clearNotifications())
- }));
- }
-
- setRef (c) {
- this.node = c;
- }
-
- render () {
- const { intl, notifications, shouldUpdateScroll, isLoading, isUnread } = this.props;
-
- let loadMore = '';
- let scrollableArea = '';
- let unread = '';
-
- if (!isLoading && notifications.size > 0) {
- loadMore = <LoadMore onClick={this.handleLoadMore} />;
- }
-
- if (isUnread) {
- unread = <div className='notifications__unread-indicator' />;
- }
-
- if (isLoading || notifications.size > 0) {
- scrollableArea = (
- <div className='scrollable' onScroll={this.handleScroll} ref={this.setRef}>
- {unread}
-
- <div>
- {notifications.map(item => <NotificationContainer key={item.get('id')} notification={item} accountId={item.get('account')} />)}
- {loadMore}
- </div>
- </div>
- );
- } else {
- scrollableArea = (
- <div className='empty-column-indicator' ref={this.setRef}>
- <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />
- </div>
- );
- }
-
- return (
- <Column icon='bell' active={isUnread} heading={intl.formatMessage(messages.title)}>
- <ColumnSettingsContainer />
- <ClearColumnButton onClick={this.handleClear} />
- <ScrollContainer scrollKey='notifications' shouldUpdateScroll={shouldUpdateScroll}>
- {scrollableArea}
- </ScrollContainer>
- </Column>
- );
- }
-
-}
-
-Notifications.propTypes = {
- notifications: ImmutablePropTypes.list.isRequired,
- dispatch: PropTypes.func.isRequired,
- shouldUpdateScroll: PropTypes.func,
- intl: PropTypes.object.isRequired,
- isLoading: PropTypes.bool,
- isUnread: PropTypes.bool
-};
-
-Notifications.defaultProps = {
- trackScroll: true
-};
-
-export default connect(mapStateToProps)(injectIntl(Notifications));
diff --git a/app/assets/javascripts/components/features/public_timeline/index.jsx b/app/assets/javascripts/components/features/public_timeline/index.jsx
@@ -1,95 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import StatusListContainer from '../ui/containers/status_list_container';
-import Column from '../ui/components/column';
-import {
- refreshTimeline,
- updateTimeline,
- deleteFromTimelines,
- connectTimeline,
- disconnectTimeline
-} from '../../actions/timelines';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import createStream from '../../stream';
-
-const messages = defineMessages({
- title: { id: 'column.public', defaultMessage: 'Federated timeline' }
-});
-
-const mapStateToProps = state => ({
- hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0,
- streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
- accessToken: state.getIn(['meta', 'access_token'])
-});
-
-let subscription;
-
-class PublicTimeline extends React.PureComponent {
-
- componentDidMount () {
- const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
-
- dispatch(refreshTimeline('public'));
-
- if (typeof subscription !== 'undefined') {
- return;
- }
-
- subscription = createStream(streamingAPIBaseURL, accessToken, 'public', {
-
- connected () {
- dispatch(connectTimeline('public'));
- },
-
- reconnected () {
- dispatch(connectTimeline('public'));
- },
-
- disconnected () {
- dispatch(disconnectTimeline('public'));
- },
-
- received (data) {
- switch(data.event) {
- case 'update':
- dispatch(updateTimeline('public', JSON.parse(data.payload)));
- break;
- case 'delete':
- dispatch(deleteFromTimelines(data.payload));
- break;
- }
- }
-
- });
- }
-
- componentWillUnmount () {
- // if (typeof subscription !== 'undefined') {
- // subscription.close();
- // subscription = null;
- // }
- }
-
- render () {
- const { intl, hasUnread } = this.props;
-
- return (
- <Column icon='globe' active={hasUnread} heading={intl.formatMessage(messages.title)}>
- <ColumnBackButtonSlim />
- <StatusListContainer {...this.props} type='public' scrollKey='public_timeline' emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />} />
- </Column>
- );
- }
-
-}
-
-PublicTimeline.propTypes = {
- dispatch: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- streamingAPIBaseURL: PropTypes.string.isRequired,
- accessToken: PropTypes.string.isRequired,
- hasUnread: PropTypes.bool
-};
-
-export default connect(mapStateToProps)(injectIntl(PublicTimeline));
diff --git a/app/assets/javascripts/components/features/reblogs/index.jsx b/app/assets/javascripts/components/features/reblogs/index.jsx
@@ -1,59 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import { fetchReblogs } from '../../actions/interactions';
-import { ScrollContainer } from 'react-router-scroll';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import ColumnBackButton from '../../components/column_back_button';
-
-const mapStateToProps = (state, props) => ({
- accountIds: state.getIn(['user_lists', 'reblogged_by', Number(props.params.statusId)])
-});
-
-class Reblogs extends React.PureComponent {
-
- componentWillMount () {
- this.props.dispatch(fetchReblogs(Number(this.props.params.statusId)));
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
- this.props.dispatch(fetchReblogs(Number(nextProps.params.statusId)));
- }
- }
-
- render () {
- const { accountIds } = this.props;
-
- if (!accountIds) {
- return (
- <Column>
- <LoadingIndicator />
- </Column>
- );
- }
-
- return (
- <Column>
- <ColumnBackButton />
-
- <ScrollContainer scrollKey='reblogs'>
- <div className='scrollable reblogs'>
- {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
- </div>
- </ScrollContainer>
- </Column>
- );
- }
-
-}
-
-Reblogs.propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- accountIds: ImmutablePropTypes.list
-};
-
-export default connect(mapStateToProps)(Reblogs);
diff --git a/app/assets/javascripts/components/features/report/components/status_check_box.jsx b/app/assets/javascripts/components/features/report/components/status_check_box.jsx
@@ -1,39 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import emojify from '../../../emoji';
-import Toggle from 'react-toggle';
-
-class StatusCheckBox extends React.PureComponent {
-
- render () {
- const { status, checked, onToggle, disabled } = this.props;
- const content = { __html: emojify(status.get('content')) };
-
- if (status.get('reblog')) {
- return null;
- }
-
- return (
- <div className='status-check-box'>
- <div
- className='status__content'
- dangerouslySetInnerHTML={content}
- />
-
- <div className='status-check-box-toggle'>
- <Toggle checked={checked} onChange={onToggle} disabled={disabled} />
- </div>
- </div>
- );
- }
-
-}
-
-StatusCheckBox.propTypes = {
- status: ImmutablePropTypes.map.isRequired,
- checked: PropTypes.bool,
- onToggle: PropTypes.func.isRequired,
- disabled: PropTypes.bool
-};
-
-export default StatusCheckBox;
diff --git a/app/assets/javascripts/components/features/report/index.jsx b/app/assets/javascripts/components/features/report/index.jsx
@@ -1,130 +0,0 @@
-import { connect } from 'react-redux';
-import { cancelReport, changeReportComment, submitReport } from '../../actions/reports';
-import { fetchAccountTimeline } from '../../actions/accounts';
-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 { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
-import StatusCheckBox from './containers/status_check_box_container';
-import Immutable from 'immutable';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-
-const messages = defineMessages({
- heading: { id: 'report.heading', defaultMessage: 'New report' },
- placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
- submit: { id: 'report.submit', defaultMessage: 'Submit' }
-});
-
-const makeMapStateToProps = () => {
- const getAccount = makeGetAccount();
-
- const mapStateToProps = state => {
- const accountId = state.getIn(['reports', 'new', 'account_id']);
-
- return {
- isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
- account: getAccount(state, accountId),
- comment: state.getIn(['reports', 'new', 'comment']),
- statusIds: Immutable.OrderedSet(state.getIn(['timelines', 'accounts_timelines', accountId, 'items'])).union(state.getIn(['reports', 'new', 'status_ids']))
- };
- };
-
- return mapStateToProps;
-};
-
-class Report extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleCommentChange = this.handleCommentChange.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- }
-
- componentWillMount () {
- if (!this.props.account) {
- this.context.router.replace('/');
- }
- }
-
- componentDidMount () {
- if (!this.props.account) {
- return;
- }
-
- this.props.dispatch(fetchAccountTimeline(this.props.account.get('id')));
- }
-
- componentWillReceiveProps (nextProps) {
- if (this.props.account !== nextProps.account && nextProps.account) {
- this.props.dispatch(fetchAccountTimeline(nextProps.account.get('id')));
- }
- }
-
- handleCommentChange (e) {
- this.props.dispatch(changeReportComment(e.target.value));
- }
-
- handleSubmit () {
- this.props.dispatch(submitReport());
- this.context.router.replace('/');
- }
-
- render () {
- const { account, comment, intl, statusIds, isSubmitting } = this.props;
-
- if (!account) {
- return null;
- }
-
- return (
- <Column heading={intl.formatMessage(messages.heading)} icon='flag'>
- <ColumnBackButtonSlim />
-
- <div className='report scrollable'>
- <div className='report__target'>
- <FormattedMessage id='report.target' defaultMessage='Reporting' />
- <strong>{account.get('acct')}</strong>
- </div>
-
- <div className='scrollable report__statuses'>
- <div>
- {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
- </div>
- </div>
-
- <div className='report__textarea-wrapper'>
- <textarea
- className='report__textarea'
- placeholder={intl.formatMessage(messages.placeholder)}
- value={comment}
- onChange={this.handleCommentChange}
- disabled={isSubmitting}
- />
-
- <div className='report__submit'>
- <div className='report__submit-button'><Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} /></div>
- </div>
- </div>
- </div>
- </Column>
- );
- }
-
-}
-
-Report.contextTypes = {
- router: PropTypes.object
-};
-
-Report.propTypes = {
- isSubmitting: PropTypes.bool,
- account: ImmutablePropTypes.map,
- statusIds: ImmutablePropTypes.orderedSet.isRequired,
- comment: PropTypes.string.isRequired,
- dispatch: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default connect(makeMapStateToProps)(injectIntl(Report));
diff --git a/app/assets/javascripts/components/features/status/components/action_bar.jsx b/app/assets/javascripts/components/features/status/components/action_bar.jsx
@@ -1,101 +0,0 @@
-import PropTypes from 'prop-types';
-import IconButton from '../../../components/icon_button';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import DropdownMenu from '../../../components/dropdown_menu';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
- delete: { id: 'status.delete', defaultMessage: 'Delete' },
- mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
- reply: { id: 'status.reply', defaultMessage: 'Reply' },
- reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
- cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
- favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
- report: { id: 'status.report', defaultMessage: 'Report @{name}' }
-});
-
-class ActionBar extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleReplyClick = this.handleReplyClick.bind(this);
- this.handleReblogClick = this.handleReblogClick.bind(this);
- this.handleFavouriteClick = this.handleFavouriteClick.bind(this);
- this.handleDeleteClick = this.handleDeleteClick.bind(this);
- this.handleMentionClick = this.handleMentionClick.bind(this);
- this.handleReport = this.handleReport.bind(this);
- }
-
- handleReplyClick () {
- this.props.onReply(this.props.status);
- }
-
- handleReblogClick (e) {
- this.props.onReblog(this.props.status, e);
- }
-
- handleFavouriteClick () {
- this.props.onFavourite(this.props.status);
- }
-
- handleDeleteClick () {
- this.props.onDelete(this.props.status);
- }
-
- handleMentionClick () {
- this.props.onMention(this.props.status.get('account'), this.context.router);
- }
-
- handleReport () {
- this.props.onReport(this.props.status);
- this.context.router.push('/report');
- }
-
- render () {
- const { status, me, intl } = this.props;
-
- let menu = [];
-
- if (me === status.getIn(['account', 'id'])) {
- menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
- } else {
- menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
- menu.push(null);
- menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
- }
-
- let reblogIcon = 'retweet';
- if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
- else if (status.get('visibility') === 'private') reblogIcon = 'lock';
-
- let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private');
-
- return (
- <div className='detailed-status__action-bar'>
- <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_id', null) === null ? 'reply' : 'reply-all'} onClick={this.handleReplyClick} /></div>
- <div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
- <div className='detailed-status__button'><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
- <div className='detailed-status__button'><DropdownMenu size={18} icon='ellipsis-h' items={menu} direction="left" ariaLabel="More" /></div>
- </div>
- );
- }
-
-}
-
-ActionBar.contextTypes = {
- router: PropTypes.object
-};
-
-ActionBar.propTypes = {
- status: ImmutablePropTypes.map.isRequired,
- onReply: PropTypes.func.isRequired,
- onReblog: PropTypes.func.isRequired,
- onFavourite: PropTypes.func.isRequired,
- onDelete: PropTypes.func.isRequired,
- onMention: PropTypes.func.isRequired,
- onReport: PropTypes.func,
- me: PropTypes.number.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(ActionBar);
diff --git a/app/assets/javascripts/components/features/status/components/card.jsx b/app/assets/javascripts/components/features/status/components/card.jsx
@@ -1,95 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-const hostStyle = {
- display: 'block',
- marginTop: '5px',
- fontSize: '13px'
-};
-
-const getHostname = url => {
- const parser = document.createElement('a');
- parser.href = url;
- return parser.hostname;
-};
-
-class Card extends React.PureComponent {
-
- renderLink () {
- const { card } = this.props;
-
- let image = '';
- let provider = card.get('provider_name');
-
- if (card.get('image')) {
- image = (
- <div className='status-card__image'>
- <img src={card.get('image')} alt={card.get('title')} className='status-card__image-image' />
- </div>
- );
- }
-
- if (provider.length < 1) {
- provider = getHostname(card.get('url'))
- }
-
- return (
- <a href={card.get('url')} className='status-card' target='_blank' rel='noopener'>
- {image}
-
- <div className='status-card__content'>
- <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>
- <p className='status-card__description'>{(card.get('description') || '').substring(0, 50)}</p>
- <span className='status-card__host' style={hostStyle}>{provider}</span>
- </div>
- </a>
- );
- }
-
- renderPhoto () {
- const { card } = this.props;
-
- return (
- <a href={card.get('url')} className='status-card-photo' target='_blank' rel='noopener'>
- <img src={card.get('url')} alt={card.get('title')} width={card.get('width')} height={card.get('height')} />
- </a>
- );
- }
-
- renderVideo () {
- const { card } = this.props;
- const content = { __html: card.get('html') };
-
- return (
- <div
- className='status-card-video'
- dangerouslySetInnerHTML={content}
- />
- );
- }
-
- render () {
- const { card } = this.props;
-
- if (card === null) {
- return null;
- }
-
- switch(card.get('type')) {
- case 'link':
- return this.renderLink();
- case 'photo':
- return this.renderPhoto();
- case 'video':
- return this.renderVideo();
- case 'rich':
- default:
- return null;
- }
- }
-}
-
-Card.propTypes = {
- card: ImmutablePropTypes.map
-};
-
-export default Card;
diff --git a/app/assets/javascripts/components/features/status/components/detailed_status.jsx b/app/assets/javascripts/components/features/status/components/detailed_status.jsx
@@ -1,94 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import Avatar from '../../../components/avatar';
-import DisplayName from '../../../components/display_name';
-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';
-import { FormattedDate, FormattedNumber } from 'react-intl';
-import CardContainer from '../containers/card_container';
-
-class DetailedStatus extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleAccountClick = this.handleAccountClick.bind(this);
- }
-
- handleAccountClick (e) {
- if (e.button === 0) {
- e.preventDefault();
- this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
- }
-
- e.stopPropagation();
- }
-
- render () {
- const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
-
- let media = '';
- let applicationLink = '';
-
- if (status.get('media_attachments').size > 0) {
- if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
- media = <AttachmentList media={status.get('media_attachments')} />;
- } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
- media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} onOpenVideo={this.props.onOpenVideo} autoplay />;
- } else {
- media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
- }
- } else if (status.get('spoiler_text').length === 0) {
- media = <CardContainer statusId={status.get('id')} />;
- }
-
- if (status.get('application')) {
- applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>;
- }
-
- return (
- <div className='detailed-status'>
- <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
- <div className='detailed-status__display-avatar'><Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} /></div>
- <DisplayName account={status.get('account')} />
- </a>
-
- <StatusContent status={status} />
-
- {media}
-
- <div className='detailed-status__meta'>
- <a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
- <FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
- </a>{applicationLink} · <Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
- <i className='fa fa-retweet' />
- <span className='detailed-status__reblogs'>
- <FormattedNumber value={status.get('reblogs_count')} />
- </span>
- </Link> · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
- <i className='fa fa-star' />
- <span className='detailed-status__favorites'>
- <FormattedNumber value={status.get('favourites_count')} />
- </span>
- </Link>
- </div>
- </div>
- );
- }
-
-}
-
-DetailedStatus.contextTypes = {
- router: PropTypes.object
-};
-
-DetailedStatus.propTypes = {
- status: ImmutablePropTypes.map.isRequired,
- onOpenMedia: PropTypes.func.isRequired,
- onOpenVideo: PropTypes.func.isRequired,
- autoPlayGif: PropTypes.bool,
-};
-
-export default DetailedStatus;
diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx
@@ -1,197 +0,0 @@
-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';
-import Column from '../ui/components/column';
-import {
- favourite,
- unfavourite,
- reblog,
- unreblog
-} from '../../actions/interactions';
-import {
- replyCompose,
- mentionCompose
-} from '../../actions/compose';
-import { deleteStatus } from '../../actions/statuses';
-import { initReport } from '../../actions/reports';
-import {
- makeGetStatus,
- getStatusAncestors,
- getStatusDescendants
-} 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';
-
-const messages = defineMessages({
- deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
- deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }
-});
-
-const makeMapStateToProps = () => {
- const getStatus = makeGetStatus();
-
- const mapStateToProps = (state, props) => ({
- status: getStatus(state, Number(props.params.statusId)),
- ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
- descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
- me: state.getIn(['meta', 'me']),
- boostModal: state.getIn(['meta', 'boost_modal']),
- autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
- });
-
- return mapStateToProps;
-};
-
-class Status extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleFavouriteClick = this.handleFavouriteClick.bind(this);
- this.handleReplyClick = this.handleReplyClick.bind(this);
- this.handleModalReblog = this.handleModalReblog.bind(this);
- this.handleReblogClick = this.handleReblogClick.bind(this);
- this.handleDeleteClick = this.handleDeleteClick.bind(this);
- this.handleMentionClick = this.handleMentionClick.bind(this);
- this.handleOpenMedia = this.handleOpenMedia.bind(this);
- this.handleOpenVideo = this.handleOpenVideo.bind(this);
- this.handleReport = this.handleReport.bind(this);
- }
-
- componentWillMount () {
- this.props.dispatch(fetchStatus(Number(this.props.params.statusId)));
- }
-
- componentWillReceiveProps (nextProps) {
- if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
- this.props.dispatch(fetchStatus(Number(nextProps.params.statusId)));
- }
- }
-
- handleFavouriteClick (status) {
- if (status.get('favourited')) {
- this.props.dispatch(unfavourite(status));
- } else {
- this.props.dispatch(favourite(status));
- }
- }
-
- handleReplyClick (status) {
- this.props.dispatch(replyCompose(status, this.context.router));
- }
-
- handleModalReblog (status) {
- this.props.dispatch(reblog(status));
- }
-
- handleReblogClick (status, e) {
- if (status.get('reblogged')) {
- this.props.dispatch(unreblog(status));
- } else {
- if (e.shiftKey || !this.props.boostModal) {
- this.handleModalReblog(status);
- } else {
- this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
- }
- }
- }
-
- handleDeleteClick (status) {
- const { dispatch, intl } = this.props;
-
- dispatch(openModal('CONFIRM', {
- message: intl.formatMessage(messages.deleteMessage),
- confirm: intl.formatMessage(messages.deleteConfirm),
- onConfirm: () => dispatch(deleteStatus(status.get('id')))
- }));
- }
-
- handleMentionClick (account, router) {
- this.props.dispatch(mentionCompose(account, router));
- }
-
- handleOpenMedia (media, index) {
- this.props.dispatch(openModal('MEDIA', { media, index }));
- }
-
- handleOpenVideo (media, time) {
- this.props.dispatch(openModal('VIDEO', { media, time }));
- }
-
- handleReport (status) {
- this.props.dispatch(initReport(status.get('account'), status));
- }
-
- renderChildren (list) {
- return list.map(id => <StatusContainer key={id} id={id} />);
- }
-
- render () {
- let ancestors, descendants;
- const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props;
-
- if (status === null) {
- return (
- <Column>
- <ColumnBackButton />
- <MissingIndicator />
- </Column>
- );
- }
-
- const account = status.get('account');
-
- if (ancestorsIds && ancestorsIds.size > 0) {
- ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
- }
-
- if (descendantsIds && descendantsIds.size > 0) {
- descendants = <div>{this.renderChildren(descendantsIds)}</div>;
- }
-
- return (
- <Column>
- <ColumnBackButton />
-
- <ScrollContainer scrollKey='thread'>
- <div className='scrollable detailed-status__wrapper'>
- {ancestors}
-
- <DetailedStatus status={status} autoPlayGif={autoPlayGif} me={me} onOpenVideo={this.handleOpenVideo} onOpenMedia={this.handleOpenMedia} />
- <ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} onReport={this.handleReport} />
-
- {descendants}
- </div>
- </ScrollContainer>
- </Column>
- );
- }
-
-}
-
-Status.contextTypes = {
- router: PropTypes.object
-};
-
-Status.propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- status: ImmutablePropTypes.map,
- ancestorsIds: ImmutablePropTypes.list,
- descendantsIds: ImmutablePropTypes.list,
- me: PropTypes.number,
- boostModal: PropTypes.bool,
- autoPlayGif: PropTypes.bool,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(connect(makeMapStateToProps)(Status));
diff --git a/app/assets/javascripts/components/features/ui/components/boost_modal.jsx b/app/assets/javascripts/components/features/ui/components/boost_modal.jsx
@@ -1,82 +0,0 @@
-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';
-import RelativeTimestamp from '../../../components/relative_timestamp';
-import DisplayName from '../../../components/display_name';
-
-const messages = defineMessages({
- reblog: { id: 'status.reblog', defaultMessage: 'Boost' }
-});
-
-class BoostModal extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleReblog = this.handleReblog.bind(this);
- this.handleAccountClick = this.handleAccountClick.bind(this);
- }
-
- handleReblog() {
- this.props.onReblog(this.props.status);
- this.props.onClose();
- }
-
- handleAccountClick (e) {
- if (e.button === 0) {
- e.preventDefault();
- this.props.onClose();
- this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
- }
- }
-
- render () {
- const { status, intl, onClose } = this.props;
-
- return (
- <div className='modal-root__modal boost-modal'>
- <div className='boost-modal__container'>
- <div className='status light'>
- <div className='boost-modal__status-header'>
- <div className='boost-modal__status-time'>
- <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
- </div>
-
- <a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'>
- <div className='status__avatar'>
- <Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} />
- </div>
-
- <DisplayName account={status.get('account')} />
- </a>
- </div>
-
- <StatusContent status={status} />
- </div>
- </div>
-
- <div className='boost-modal__action-bar'>
- <div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <i className='fa fa-retweet' /></span> }} /></div>
- <Button text={intl.formatMessage(messages.reblog)} onClick={this.handleReblog} />
- </div>
- </div>
- );
- }
-
-}
-
-BoostModal.contextTypes = {
- router: PropTypes.object
-};
-
-BoostModal.propTypes = {
- status: ImmutablePropTypes.map.isRequired,
- onReblog: PropTypes.func.isRequired,
- onClose: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(BoostModal);
diff --git a/app/assets/javascripts/components/features/ui/components/column.jsx b/app/assets/javascripts/components/features/ui/components/column.jsx
@@ -1,82 +0,0 @@
-import ColumnHeader from './column_header';
-import PropTypes from 'prop-types';
-
-const easingOutQuint = (x, t, b, c, d) => c*((t=t/d-1)*t*t*t*t + 1) + b;
-
-const scrollTop = (node) => {
- const startTime = Date.now();
- const offset = node.scrollTop;
- const targetY = -offset;
- const duration = 1000;
- let interrupt = false;
-
- const step = () => {
- const elapsed = Date.now() - startTime;
- const percentage = elapsed / duration;
-
- if (percentage > 1 || interrupt) {
- return;
- }
-
- node.scrollTop = easingOutQuint(0, elapsed, offset, targetY, duration);
- requestAnimationFrame(step);
- };
-
- step();
-
- return () => {
- interrupt = true;
- };
-};
-
-class Column extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleHeaderClick = this.handleHeaderClick.bind(this);
- this.handleWheel = this.handleWheel.bind(this);
- }
-
- handleHeaderClick () {
- const scrollable = ReactDOM.findDOMNode(this).querySelector('.scrollable');
- if (!scrollable) {
- return;
- }
- this._interruptScrollAnimation = scrollTop(scrollable);
- }
-
- handleWheel () {
- if (typeof this._interruptScrollAnimation !== 'undefined') {
- this._interruptScrollAnimation();
- }
- }
-
- render () {
- const { heading, icon, children, active, hideHeadingOnMobile } = this.props;
-
- let columnHeaderId = null
- let header = '';
-
- if (heading) {
- columnHeaderId = heading.replace(/ /g, '-')
- header = <ColumnHeader icon={icon} active={active} type={heading} onClick={this.handleHeaderClick} hideOnMobile={hideHeadingOnMobile} columnHeaderId={columnHeaderId}/>;
- }
- return (
- <div role='region' aria-labelledby={columnHeaderId} className='column' onWheel={this.handleWheel}>
- {header}
- {children}
- </div>
- );
- }
-
-}
-
-Column.propTypes = {
- heading: PropTypes.string,
- icon: PropTypes.string,
- children: PropTypes.node,
- active: PropTypes.bool,
- hideHeadingOnMobile: PropTypes.bool
-};
-
-export default Column;
diff --git a/app/assets/javascripts/components/features/ui/components/column_header.jsx b/app/assets/javascripts/components/features/ui/components/column_header.jsx
@@ -1,42 +0,0 @@
-import PropTypes from 'prop-types'
-
-class ColumnHeader extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleClick () {
- this.props.onClick();
- }
-
- render () {
- const { type, active, hideOnMobile, columnHeaderId } = this.props;
-
- let icon = '';
-
- if (this.props.icon) {
- icon = <i className={`fa fa-fw fa-${this.props.icon} column-header__icon`} />;
- }
-
- return (
- <div role='button heading' tabIndex='0' className={`column-header ${active ? 'active' : ''} ${hideOnMobile ? 'hidden-on-mobile' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}>
- {icon}
- {type}
- </div>
- );
- }
-
-}
-
-ColumnHeader.propTypes = {
- icon: PropTypes.string,
- type: PropTypes.string,
- active: PropTypes.bool,
- onClick: PropTypes.func,
- hideOnMobile: PropTypes.bool,
- columnHeaderId: PropTypes.string
-};
-
-export default ColumnHeader;
diff --git a/app/assets/javascripts/components/features/ui/components/column_link.jsx b/app/assets/javascripts/components/features/ui/components/column_link.jsx
@@ -1,31 +0,0 @@
-import PropTypes from 'prop-types';
-import { Link } from 'react-router';
-
-const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => {
- if (href) {
- return (
- <a href={href} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
- <i className={`fa fa-fw fa-${icon} column-link__icon`} />
- {text}
- </a>
- );
- } else {
- return (
- <Link to={to} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`}>
- <i className={`fa fa-fw fa-${icon} column-link__icon`} />
- {text}
- </Link>
- );
- }
-};
-
-ColumnLink.propTypes = {
- icon: PropTypes.string.isRequired,
- text: PropTypes.string.isRequired,
- to: PropTypes.string,
- href: PropTypes.string,
- method: PropTypes.string,
- hideOnMobile: PropTypes.bool
-};
-
-export default ColumnLink;
diff --git a/app/assets/javascripts/components/features/ui/components/column_subheading.jsx b/app/assets/javascripts/components/features/ui/components/column_subheading.jsx
@@ -1,15 +0,0 @@
-import PropTypes from 'prop-types';
-
-const ColumnSubheading = ({ text }) => {
- return (
- <div className='column-subheading'>
- {text}
- </div>
- );
- };
-
-ColumnSubheading.propTypes = {
- text: PropTypes.string.isRequired,
-};
-
-export default ColumnSubheading;
diff --git a/app/assets/javascripts/components/features/ui/components/columns_area.jsx b/app/assets/javascripts/components/features/ui/components/columns_area.jsx
@@ -1,19 +0,0 @@
-import PropTypes from 'prop-types';
-
-class ColumnsArea extends React.PureComponent {
-
- render () {
- return (
- <div className='columns-area'>
- {this.props.children}
- </div>
- );
- }
-
-}
-
-ColumnsArea.propTypes = {
- children: PropTypes.node
-};
-
-export default ColumnsArea;
diff --git a/app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx b/app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx
@@ -1,50 +0,0 @@
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import Button from '../../../components/button';
-
-class ConfirmationModal extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleClick = this.handleClick.bind(this);
- this.handleCancel = this.handleCancel.bind(this);
- }
-
- handleClick () {
- this.props.onClose();
- this.props.onConfirm();
- }
-
- handleCancel (e) {
- e.preventDefault();
- this.props.onClose();
- }
-
- render () {
- const { intl, message, confirm, onConfirm, onClose } = this.props;
-
- return (
- <div className='modal-root__modal confirmation-modal'>
- <div className='confirmation-modal__container'>
- {message}
- </div>
-
- <div className='confirmation-modal__action-bar'>
- <div><a href='#' onClick={this.handleCancel}><FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' /></a></div>
- <Button text={confirm} onClick={this.handleClick} />
- </div>
- </div>
- );
- }
-
-}
-
-ConfirmationModal.propTypes = {
- message: PropTypes.node.isRequired,
- confirm: PropTypes.string.isRequired,
- onClose: PropTypes.func.isRequired,
- onConfirm: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(ConfirmationModal);
diff --git a/app/assets/javascripts/components/features/ui/components/media_modal.jsx b/app/assets/javascripts/components/features/ui/components/media_modal.jsx
@@ -1,101 +0,0 @@
-import LoadingIndicator from '../../../components/loading_indicator';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import ExtendedVideoPlayer from '../../../components/extended_video_player';
-import ImageLoader from 'react-imageloader';
-import { defineMessages, injectIntl } from 'react-intl';
-import IconButton from '../../../components/icon_button';
-
-const messages = defineMessages({
- close: { id: 'lightbox.close', defaultMessage: 'Close' }
-});
-
-class MediaModal extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.state = {
- index: null
- };
- this.handleNextClick = this.handleNextClick.bind(this);
- this.handlePrevClick = this.handlePrevClick.bind(this);
- this.handleKeyUp = this.handleKeyUp.bind(this);
- }
-
- handleNextClick () {
- this.setState({ index: (this.getIndex() + 1) % this.props.media.size});
- }
-
- handlePrevClick () {
- this.setState({ index: (this.getIndex() - 1) % this.props.media.size});
- }
-
- handleKeyUp (e) {
- switch(e.key) {
- case 'ArrowLeft':
- this.handlePrevClick();
- break;
- case 'ArrowRight':
- this.handleNextClick();
- break;
- }
- }
-
- componentDidMount () {
- window.addEventListener('keyup', this.handleKeyUp, false);
- }
-
- componentWillUnmount () {
- window.removeEventListener('keyup', this.handleKeyUp);
- }
-
- getIndex () {
- return this.state.index !== null ? this.state.index : this.props.index;
- }
-
- render () {
- const { media, intl, onClose } = this.props;
-
- const index = this.getIndex();
- const attachment = media.get(index);
- const url = attachment.get('url');
-
- let leftNav, rightNav, content;
-
- leftNav = rightNav = content = '';
-
- if (media.size > 1) {
- leftNav = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>;
- rightNav = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--right' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>;
- }
-
- if (attachment.get('type') === 'image') {
- content = <ImageLoader src={url} imgProps={{ style: { display: 'block' } }} />;
- } else if (attachment.get('type') === 'gifv') {
- content = <ExtendedVideoPlayer src={url} muted={true} controls={false} />;
- }
-
- return (
- <div className='modal-root__modal media-modal'>
- {leftNav}
-
- <div className='media-modal__content'>
- <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
- {content}
- </div>
-
- {rightNav}
- </div>
- );
- }
-
-}
-
-MediaModal.propTypes = {
- media: ImmutablePropTypes.list.isRequired,
- index: PropTypes.number.isRequired,
- onClose: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(MediaModal);
diff --git a/app/assets/javascripts/components/features/ui/components/modal_root.jsx b/app/assets/javascripts/components/features/ui/components/modal_root.jsx
@@ -1,92 +0,0 @@
-import PropTypes from 'prop-types';
-import MediaModal from './media_modal';
-import OnboardingModal from './onboarding_modal';
-import VideoModal from './video_modal';
-import BoostModal from './boost_modal';
-import ConfirmationModal from './confirmation_modal';
-import { TransitionMotion, spring } from 'react-motion';
-
-const MODAL_COMPONENTS = {
- 'MEDIA': MediaModal,
- 'ONBOARDING': OnboardingModal,
- 'VIDEO': VideoModal,
- 'BOOST': BoostModal,
- 'CONFIRM': ConfirmationModal
-};
-
-class ModalRoot extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.handleKeyUp = this.handleKeyUp.bind(this);
- }
-
- handleKeyUp (e) {
- if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27)
- && !!this.props.type) {
- this.props.onClose();
- }
- }
-
- componentDidMount () {
- window.addEventListener('keyup', this.handleKeyUp, false);
- }
-
- componentWillUnmount () {
- window.removeEventListener('keyup', this.handleKeyUp);
- }
-
- willEnter () {
- return { opacity: 0, scale: 0.98 };
- }
-
- willLeave () {
- return { opacity: spring(0), scale: spring(0.98) };
- }
-
- render () {
- const { type, props, onClose } = this.props;
- const items = [];
-
- if (!!type) {
- items.push({
- key: type,
- data: { type, props },
- style: { opacity: spring(1), scale: spring(1, { stiffness: 120, damping: 14 }) }
- });
- }
-
- return (
- <TransitionMotion
- styles={items}
- willEnter={this.willEnter}
- willLeave={this.willLeave}>
- {interpolatedStyles =>
- <div className='modal-root'>
- {interpolatedStyles.map(({ key, data: { type, props }, style }) => {
- const SpecificComponent = MODAL_COMPONENTS[type];
-
- return (
- <div key={key}>
- <div role='presentation' className='modal-root__overlay' style={{ opacity: style.opacity }} onClick={onClose} />
- <div className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}>
- <SpecificComponent {...props} onClose={onClose} />
- </div>
- </div>
- );
- })}
- </div>
- }
- </TransitionMotion>
- );
- }
-
-}
-
-ModalRoot.propTypes = {
- type: PropTypes.string,
- props: PropTypes.object,
- onClose: PropTypes.func.isRequired
-};
-
-export default ModalRoot;
diff --git a/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx b/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx
@@ -1,263 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import Permalink from '../../../components/permalink';
-import { TransitionMotion, spring } from 'react-motion';
-import ComposeForm from '../../compose/components/compose_form';
-import Search from '../../compose/components/search';
-import NavigationBar from '../../compose/components/navigation_bar';
-import ColumnHeader from './column_header';
-import Immutable from 'immutable';
-
-const messages = defineMessages({
- home_title: { id: 'column.home', defaultMessage: 'Home' },
- notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' },
- local_title: { id: 'column.community', defaultMessage: 'Local timeline' },
- federated_title: { id: 'column.public', defaultMessage: 'Federated timeline' }
-});
-
-const PageOne = ({ acct, domain }) => (
- <div className='onboarding-modal__page onboarding-modal__page-one'>
- <div style={{ flex: '0 0 auto' }}>
- <div className='onboarding-modal__page-one__elephant-friend' />
- </div>
-
- <div>
- <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to Mastodon!' /></h1>
- <p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' /></p>
- <p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>{acct}@{domain}</strong> }}/></p>
- </div>
- </div>
-);
-
-PageOne.propTypes = {
- acct: PropTypes.string.isRequired,
- domain: PropTypes.string.isRequired
-};
-
-const PageTwo = ({ me }) => (
- <div className='onboarding-modal__page onboarding-modal__page-two'>
- <div className='figure non-interactive'>
- <div className='pseudo-drawer'>
- <NavigationBar account={me} />
- </div>
- <ComposeForm
- text='Awoo! #introductions'
- suggestions={Immutable.List()}
- mentionedDomains={[]}
- spoiler={false}
- onChange={() => {}}
- onSubmit={() => {}}
- onPaste={() => {}}
- onPickEmoji={() => {}}
- onChangeSpoilerText={() => {}}
- onClearSuggestions={() => {}}
- onFetchSuggestions={() => {}}
- onSuggestionSelected={() => {}}
- />
- </div>
-
- <p><FormattedMessage id='onboarding.page_two.compose' defaultMessage='Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.' /></p>
- </div>
-);
-
-PageTwo.propTypes = {
- me: ImmutablePropTypes.map.isRequired,
-};
-
-const PageThree = ({ me, domain }) => (
- <div className='onboarding-modal__page onboarding-modal__page-three'>
- <div className='figure non-interactive'>
- <Search
- value=''
- onChange={() => {}}
- onSubmit={() => {}}
- onClear={() => {}}
- onShow={() => {}}
- />
-
- <div className='pseudo-drawer'>
- <NavigationBar account={me} />
- </div>
- </div>
-
- <p><FormattedMessage id='onboarding.page_three.search' defaultMessage='Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.' values={{ illustration: <Permalink to='/timelines/tag/illustration' href='/tags/illustration'>#illustration</Permalink>, introductions: <Permalink to='/timelines/tag/introductions' href='/tags/introductions'>#introductions</Permalink> }}/></p>
- <p><FormattedMessage id='onboarding.page_three.profile' defaultMessage='Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.' /></p>
- </div>
-);
-
-PageThree.propTypes = {
- me: ImmutablePropTypes.map.isRequired,
- domain: PropTypes.string.isRequired
-};
-
-const PageFour = ({ domain, intl }) => (
- <div className='onboarding-modal__page onboarding-modal__page-four'>
- <div className='onboarding-modal__page-four__columns'>
- <div className='row'>
- <div>
- <div className='figure non-interactive'><ColumnHeader icon='home' type={intl.formatMessage(messages.home_title)} /></div>
- <p><FormattedMessage id='onboarding.page_four.home' defaultMessage='The home timeline shows posts from people you follow.'/></p>
- </div>
-
- <div>
- <div className='figure non-interactive'><ColumnHeader icon='bell' type={intl.formatMessage(messages.notifications_title)} /></div>
- <p><FormattedMessage id='onboarding.page_four.notifications' defaultMessage='The notifications column shows when someone interacts with you.' /></p>
- </div>
- </div>
-
- <div className='row'>
- <div>
- <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='users' type={intl.formatMessage(messages.local_title)} /></div>
- </div>
-
- <div>
- <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='globe' type={intl.formatMessage(messages.federated_title)} /></div>
- </div>
- </div>
-
- <p><FormattedMessage id='onboarding.page_five.public_timelines' defaultMessage='The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.' values={{ domain }} /></p>
- </div>
- </div>
-);
-
-PageFour.propTypes = {
- domain: PropTypes.string.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-const PageSix = ({ admin, domain }) => {
- let adminSection = '';
-
- if (admin) {
- adminSection = (
- <p>
- <FormattedMessage id='onboarding.page_six.admin' defaultMessage="Your instance's admin is {admin}." values={{ admin: <Permalink href={admin.get('url')} to={`/accounts/${admin.get('id')}`}>@{admin.get('acct')}</Permalink> }} />
- <br />
- <FormattedMessage id='onboarding.page_six.read_guidelines' defaultMessage="Please read {domain}'s {guidelines}!" values={{domain, guidelines: <a href='/about/more' target='_blank'><FormattedMessage id='onboarding.page_six.guidelines' defaultMessage='community guidelines' /></a> }}/>
- </p>
- );
- }
-
- return (
- <div className='onboarding-modal__page onboarding-modal__page-six'>
- <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1>
- {adminSection}
- <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
- <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
- <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p>
- </div>
- );
-};
-
-PageSix.propTypes = {
- admin: ImmutablePropTypes.map,
- domain: PropTypes.string.isRequired
-};
-
-const mapStateToProps = state => ({
- me: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
- admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]),
- domain: state.getIn(['meta', 'domain'])
-});
-
-class OnboardingModal extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.state = {
- currentIndex: 0
- };
- this.handleSkip = this.handleSkip.bind(this);
- this.handleDot = this.handleDot.bind(this);
- this.handleNext = this.handleNext.bind(this);
- }
-
- handleSkip (e) {
- e.preventDefault();
- this.props.onClose();
- }
-
- handleDot (i, e) {
- e.preventDefault();
- this.setState({ currentIndex: i });
- }
-
- handleNext (maxNum, e) {
- e.preventDefault();
-
- if (this.state.currentIndex < maxNum - 1) {
- this.setState({ currentIndex: this.state.currentIndex + 1 });
- } else {
- this.props.onClose();
- }
- }
-
- render () {
- const { me, admin, domain, intl } = this.props;
-
- const pages = [
- <PageOne acct={me.get('acct')} domain={domain} />,
- <PageTwo me={me} />,
- <PageThree me={me} domain={domain} />,
- <PageFour domain={domain} intl={intl} />,
- <PageSix admin={admin} domain={domain} />
- ];
-
- const { currentIndex } = this.state;
- const hasMore = currentIndex < pages.length - 1;
-
- let nextOrDoneBtn;
-
- if(hasMore) {
- nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__next'><FormattedMessage id='onboarding.next' defaultMessage='Next' /></a>;
- } else {
- nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__done'><FormattedMessage id='onboarding.done' defaultMessage='Done' /></a>;
- }
-
- const styles = pages.map((page, i) => ({
- key: `page-${i}`,
- style: { opacity: spring(i === currentIndex ? 1 : 0) }
- }));
-
- return (
- <div className='modal-root__modal onboarding-modal'>
- <TransitionMotion styles={styles}>
- {interpolatedStyles =>
- <div className='onboarding-modal__pager'>
- {pages.map((page, i) =>
- <div key={`page-${i}`} style={{ opacity: interpolatedStyles[i].style.opacity, pointerEvents: i === currentIndex ? 'auto' : 'none' }}>{page}</div>
- )}
- </div>
- }
- </TransitionMotion>
-
- <div className='onboarding-modal__paginator'>
- <div>
- <a href='#' className='onboarding-modal__skip' onClick={this.handleSkip}><FormattedMessage id='onboarding.skip' defaultMessage='Skip' /></a>
- </div>
-
- <div className='onboarding-modal__dots'>
- {pages.map((_, i) => <div key={i} onClick={this.handleDot.bind(null, i)} className={`onboarding-modal__dot ${i === currentIndex ? 'active' : ''}`} />)}
- </div>
-
- <div>
- {nextOrDoneBtn}
- </div>
- </div>
- </div>
- );
- }
-
-}
-
-OnboardingModal.propTypes = {
- onClose: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- me: ImmutablePropTypes.map.isRequired,
- domain: PropTypes.string.isRequired,
- admin: ImmutablePropTypes.map
-}
-
-export default connect(mapStateToProps)(injectIntl(OnboardingModal));
diff --git a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx
@@ -1,23 +0,0 @@
-import { Link } from 'react-router';
-import { FormattedMessage } from 'react-intl';
-
-class TabsBar extends React.Component {
-
- render () {
- return (
- <div className='tabs-bar'>
- <Link className='tabs-bar__link primary' activeClassName='active' to='/statuses/new'><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></Link>
- <Link className='tabs-bar__link primary' activeClassName='active' to='/timelines/home'><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></Link>
- <Link className='tabs-bar__link primary' activeClassName='active' to='/notifications'><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></Link>
-
- <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public/local'><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></Link>
- <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public'><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></Link>
-
- <Link className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started'><i className='fa fa-fw fa-asterisk' /></Link>
- </div>
- );
- }
-
-}
-
-export default TabsBar;
diff --git a/app/assets/javascripts/components/features/ui/components/upload_area.jsx b/app/assets/javascripts/components/features/ui/components/upload_area.jsx
@@ -1,59 +0,0 @@
-import PropTypes from 'prop-types';
-import { Motion, spring } from 'react-motion';
-import { FormattedMessage } from 'react-intl';
-
-class UploadArea extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
-
- this.handleKeyUp = this.handleKeyUp.bind(this);
- }
-
- handleKeyUp (e) {
- e.preventDefault();
- e.stopPropagation();
-
- const keyCode = e.keyCode
- if (this.props.active) {
- switch(keyCode) {
- case 27:
- this.props.onClose();
- break;
- }
- }
- }
-
- componentDidMount () {
- window.addEventListener('keyup', this.handleKeyUp, false);
- }
-
- componentWillUnmount () {
- window.removeEventListener('keyup', this.handleKeyUp);
- }
-
- render () {
- const { active } = this.props;
-
- return (
- <Motion defaultStyle={{ backgroundOpacity: 0, backgroundScale: 0.95 }} style={{ backgroundOpacity: spring(active ? 1 : 0, { stiffness: 150, damping: 15 }), backgroundScale: spring(active ? 1 : 0.95, { stiffness: 200, damping: 3 }) }}>
- {({ backgroundOpacity, backgroundScale }) =>
- <div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}>
- <div className='upload-area__drop'>
- <div className='upload-area__background' style={{ transform: `translateZ(0) scale(${backgroundScale})` }} />
- <div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div>
- </div>
- </div>
- }
- </Motion>
- );
- }
-
-}
-
-UploadArea.propTypes = {
- active: PropTypes.bool,
- onClose: PropTypes.func
-};
-
-export default UploadArea;
diff --git a/app/assets/javascripts/components/features/ui/components/video_modal.jsx b/app/assets/javascripts/components/features/ui/components/video_modal.jsx
@@ -1,38 +0,0 @@
-import LoadingIndicator from '../../../components/loading_indicator';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import ExtendedVideoPlayer from '../../../components/extended_video_player';
-import { defineMessages, injectIntl } from 'react-intl';
-import IconButton from '../../../components/icon_button';
-
-const messages = defineMessages({
- close: { id: 'lightbox.close', defaultMessage: 'Close' }
-});
-
-class VideoModal extends React.PureComponent {
-
- render () {
- const { media, intl, time, onClose } = this.props;
-
- const url = media.get('url');
-
- return (
- <div className='modal-root__modal media-modal'>
- <div>
- <div className='media-modal__close'><IconButton title={intl.formatMessage(messages.close)} icon='times' overlay onClick={onClose} /></div>
- <ExtendedVideoPlayer src={url} muted={false} controls={true} time={time} />
- </div>
- </div>
- );
- }
-
-}
-
-VideoModal.propTypes = {
- media: ImmutablePropTypes.map.isRequired,
- time: PropTypes.number,
- onClose: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(VideoModal);
diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx
@@ -1,166 +0,0 @@
-import ColumnsArea from './components/columns_area';
-import NotificationsContainer from './containers/notifications_container';
-import PropTypes from 'prop-types';
-import LoadingBarContainer from './containers/loading_bar_container';
-import HomeTimeline from '../home_timeline';
-import Compose from '../compose';
-import TabsBar from './components/tabs_bar';
-import ModalContainer from './containers/modal_container';
-import Notifications from '../notifications';
-import { connect } from 'react-redux';
-import { isMobile } from '../../is_mobile';
-import { debounce } from 'react-decoration';
-import { uploadCompose } from '../../actions/compose';
-import { refreshTimeline } from '../../actions/timelines';
-import { refreshNotifications } from '../../actions/notifications';
-import UploadArea from './components/upload_area';
-
-class UI extends React.PureComponent {
-
- constructor (props, context) {
- super(props, context);
- this.state = {
- width: window.innerWidth,
- draggingOver: false
- };
- this.handleResize = this.handleResize.bind(this);
- this.handleDragEnter = this.handleDragEnter.bind(this);
- this.handleDragOver = this.handleDragOver.bind(this);
- this.handleDrop = this.handleDrop.bind(this);
- this.handleDragLeave = this.handleDragLeave.bind(this);
- this.handleDragEnd = this.handleDragLeave.bind(this)
- this.closeUploadModal = this.closeUploadModal.bind(this)
- this.setRef = this.setRef.bind(this);
- }
-
- @debounce(500)
- handleResize () {
- this.setState({ width: window.innerWidth });
- }
-
- handleDragEnter (e) {
- e.preventDefault();
-
- if (!this.dragTargets) {
- this.dragTargets = [];
- }
-
- if (this.dragTargets.indexOf(e.target) === -1) {
- this.dragTargets.push(e.target);
- }
-
- if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
- this.setState({ draggingOver: true });
- }
- }
-
- handleDragOver (e) {
- e.preventDefault();
- e.stopPropagation();
-
- try {
- e.dataTransfer.dropEffect = 'copy';
- } catch (err) {
-
- }
-
- return false;
- }
-
- handleDrop (e) {
- e.preventDefault();
-
- this.setState({ draggingOver: false });
-
- if (e.dataTransfer && e.dataTransfer.files.length === 1) {
- this.props.dispatch(uploadCompose(e.dataTransfer.files));
- }
- }
-
- handleDragLeave (e) {
- e.preventDefault();
- e.stopPropagation();
-
- this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));
-
- if (this.dragTargets.length > 0) {
- return;
- }
-
- this.setState({ draggingOver: false });
- }
-
- closeUploadModal() {
- this.setState({ draggingOver: false });
- }
-
- componentWillMount () {
- window.addEventListener('resize', this.handleResize, { passive: true });
- document.addEventListener('dragenter', this.handleDragEnter, false);
- document.addEventListener('dragover', this.handleDragOver, false);
- document.addEventListener('drop', this.handleDrop, false);
- document.addEventListener('dragleave', this.handleDragLeave, false);
- document.addEventListener('dragend', this.handleDragEnd, false);
-
- this.props.dispatch(refreshTimeline('home'));
- this.props.dispatch(refreshNotifications());
- }
-
- componentWillUnmount () {
- window.removeEventListener('resize', this.handleResize);
- document.removeEventListener('dragenter', this.handleDragEnter);
- document.removeEventListener('dragover', this.handleDragOver);
- document.removeEventListener('drop', this.handleDrop);
- document.removeEventListener('dragleave', this.handleDragLeave);
- document.removeEventListener('dragend', this.handleDragEnd);
- }
-
- setRef (c) {
- this.node = c;
- }
-
- render () {
- const { width, draggingOver } = this.state;
- const { children } = this.props;
-
- let mountedColumns;
-
- if (isMobile(width)) {
- mountedColumns = (
- <ColumnsArea>
- {children}
- </ColumnsArea>
- );
- } else {
- mountedColumns = (
- <ColumnsArea>
- <Compose withHeader={true} />
- <HomeTimeline shouldUpdateScroll={() => false} />
- <Notifications shouldUpdateScroll={() => false} />
- <div style={{display: 'flex', flex: '1 1 auto', position: 'relative'}}>{children}</div>
- </ColumnsArea>
- );
- }
-
- return (
- <div className='ui' ref={this.setRef}>
- <TabsBar />
-
- {mountedColumns}
-
- <NotificationsContainer />
- <LoadingBarContainer className="loading-bar" />
- <ModalContainer />
- <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
- </div>
- );
- }
-
-}
-
-UI.propTypes = {
- dispatch: PropTypes.func.isRequired,
- children: PropTypes.node
-};
-
-export default connect()(UI);
diff --git a/app/assets/javascripts/components/locales/ar.jsx b/app/assets/javascripts/components/locales/ar.jsx
@@ -1,131 +0,0 @@
-/**
- * ملاحظة للمساهمين و المساهمات :
- * لجعل مهمة المساهمين الآخرين أسهل، رجاءا تذكر :
- * 1. إضافة سلسلة جديدة هنا؛ و
- * 2. لإزالة السلاسل القديمة التي لم تعد هناك حاجة إليها. و
- * 3. لفرز السلاسل تبعا للأبجدية
- * شكر!
- */
-const ar = {
- "account.block": "حظر @{name}",
- "account.disclaimer": "هذا المستخدم من مثيل خادم آخر. قد يكون هذا الرقم أكبر.",
- "account.edit_profile": "تعديل الملف الشخصي",
- "account.follow": "إتبع",
- "account.followers": "المتابعون",
- "account.follows": "يتبع",
- "account.follows_you": "يتابعك",
- "account.mention": "أُذكُر @{name}",
- "account.mute": "أكتم @{name}",
- "account.posts": "المشاركات",
- "account.report": "أبلغ عن @{name}",
- "account.requested": "في انتظار الموافقة",
- "account.unblock": "إلغاء الحظر عن @{name}",
- "account.unfollow": "إلغاء المتابعة",
- "account.unmute": "إلغاء الكتم عن @{name}",
- "boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة",
- "column.blocks": "الحسابات المحجوبة",
- "column.community": "الخيط العام المحلي",
- "column.favourites": "المفضلة",
- "column.follow_requests": "طلبات المتابعة",
- "column.home": "الرئيسية",
- "column.mutes": "الحسابات المكتومة",
- "column.notifications": "الإشعارات",
- "column.public": "الخيط العام الموحد",
- "column_back_button.label": "العودة",
- "column_subheading.navigation": "التصفح",
- "column_subheading.settings": "الإعدادات",
- "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.",
- "compose_form.lock_disclaimer.lock": "مقفل",
- "compose_form.placeholder": "فيمَ تفكّر؟",
- "compose_form.publish": "بوّق !",
- "compose_form.sensitive": "ضع علامة على الوسيط باعتباره حسّاس",
- "compose_form.spoiler": "أخفِ النص واعرض تحذيرا",
- "compose_form.spoiler_placeholder": "تنبيه عن المحتوى",
- "confirmation_modal.cancel": "إلغاء",
- "confirmations.block.confirm": "حجب",
- "confirmations.block.message": "هل أنت متأكد أنك تريد حجب {name} ؟",
- "confirmations.delete.confirm": "حذف",
- "confirmations.delete.message": "هل أنت متأكد أنك تريد حذف هذا المنشور ؟",
- "confirmations.mute.confirm": "أكتم",
- "confirmations.mute.message": "هل أنت متأكد أنك تريد كتم {name} ؟",
- "emoji_button.activity": "الأنشطة",
- "emoji_button.flags": "الأعلام",
- "emoji_button.food": "الطعام والشراب",
- "emoji_button.label": "أدرج إيموجي",
- "emoji_button.nature": "الطبيعة",
- "emoji_button.objects": "أشياء",
- "emoji_button.people": "الناس",
- "emoji_button.search": "ابحث...",
- "emoji_button.symbols": "رموز",
- "emoji_button.travel": "أماكن و أسفار",
- "empty_column.community": "الخط الزمني المحلي فارغ. اكتب شيئا ما للعامة كبداية.",
- "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
- "empty_column.home.public_timeline": "الخيط العام",
- "empty_column.home": "إنك لا تتبع بعد أي شخص إلى حد الآن. زر {public} أو استخدام حقل البحث لكي تبدأ على التعرف على مستخدمين آخرين.",
- "empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
- "empty_column.public": "لا يوجد شيء هنا ! قم بتحرير شيء ما بشكل عام، أو اتبع مستخدمين آخرين في الخوادم المثيلة الأخرى لملء خيط المحادثات العام.",
- "follow_request.authorize": "ترخيص",
- "follow_request.reject": "رفض",
- "getting_started.apps": "عدة تطبيقات مختلفة متوفرة",
- "getting_started.heading": "إستعدّ للبدء",
- "getting_started.about_addressing": "يمكنك متابعة الأشخاص إذا كنت تعرف اسم المستخدم الخاص بهم والنطاق الذي هم عليه عن طريق إدخال عنوان شبيه بالبريد الإلكتروني في الحقل المخصص للبحث.",
- "getting_started.about_shortcuts": "إذا كان المستخدم المستهدف في نفس النطاق الذي تستخدمه، فإسم المستخدم وحده يكفي. وتنطبق نفس القاعدة على ذكر الأشخاص في المنشورات و التبويقات.",
- "getting_started.open_source_notice": "ماستدون برنامج مفتوح المصدر. يمكنك المساهمة، أو الإبلاغ عن تقارير الأخطاء، على GitHub {github}. {apps}.",
- "home.column_settings.advanced": "متقدمة",
- "home.column_settings.basic": "أساسية",
- "home.column_settings.filter_regex": "تصفية حسب التعبيرات العادية",
- "home.column_settings.show_reblogs": "عرض الترقيات",
- "home.column_settings.show_replies": "عرض الردود",
- "home.settings": "إعدادات العمود",
- "lightbox.close": "إغلاق",
- "loading_indicator.label": "تحميل ...",
- "media_gallery.toggle_visible": "Toggle visibility",
- "missing_indicator.label": "تعذر العثور عليه",
- "navigation_bar.blocks": "الحسابات المحجوبة",
- "navigation_bar.community_timeline": "الخيط العام المحلي",
- "navigation_bar.edit_profile": "تعديل الملف الشخصي",
- "navigation_bar.preferences": "التفضيلات",
- "navigation_bar.community_timeline": "الخيط العام المحلي",
- "navigation_bar.public_timeline": "الخيط العام الموحد",
- "navigation_bar.logout": "خروج",
- "reply_indicator.cancel": "إلغاء",
- "search.placeholder": "ابحث",
- "search.account": "حساب",
- "search.hashtag": "وسم",
- "status.mention": "أذكُر @{name}",
- "status.delete": "إحذف",
- "status.reply": "ردّ",
- "status.reblog": "رَقِّي",
- "status.favourite": "أضف إلى المفضلة",
- "status.reblogged_by": "{name} رقى",
- "status.sensitive_warning": "محتوى حساس",
- "status.sensitive_toggle": "اضغط للعرض",
- "status.show_more": "أظهر المزيد",
- "status.show_less": "إعرض أقلّ",
- "status.open": "وسع هذه المشاركة",
- "status.report": "إبلِغ عن @{name}",
- "tabs_bar.compose": "تحرير",
- "tabs_bar.home": "الرئيسية",
- "tabs_bar.mentions": "الإشارات",
- "tabs_bar.public": "الخيط العام الموحد",
- "tabs_bar.notifications": "الإخطارات",
- "upload_button.label": "إضافة وسائط",
- "upload_form.undo": "إلغاء",
- "upload_progress.label": "يرفع...",
- "notification.follow": "{name} يتبعك",
- "notification.favourite": "{name} أعجب بمنشورك",
- "notification.reblog": "{name} قام بترقية تبويقك",
- "notification.mention": "{name} ذكرك",
- "notifications.column_settings.alert": "إشعارات سطح المكتب",
- "notifications.column_settings.show": "إعرِضها في عمود",
- "notifications.column_settings.follow": "متابعُون جُدُد :",
- "notifications.column_settings.favourite": "المُفَضَّلة :",
- "notifications.column_settings.mention": "الإشارات :",
- "notifications.column_settings.reblog": "الترقيّات:",
- "video_player.toggle_sound": "تبديل الصوت",
- "video_player.toggle_visible": "إظهار / إخفاء الفيديو",
- "video_player.expand": "وسّع الفيديو",
- "video_player.video_error": "تعذر تشغيل الفيديو",
-};
-
-export default ar;
diff --git a/app/assets/javascripts/components/locales/bg.jsx b/app/assets/javascripts/components/locales/bg.jsx
@@ -1,68 +0,0 @@
-const bg = {
- "column_back_button.label": "Назад",
- "lightbox.close": "Затвори",
- "loading_indicator.label": "Зареждане...",
- "status.mention": "Споменаване",
- "status.delete": "Изтриване",
- "status.reply": "Отговор",
- "status.reblog": "Споделяне",
- "status.favourite": "Предпочитани",
- "status.reblogged_by": "{name} сподели",
- "status.sensitive_warning": "Деликатно съдържание",
- "status.sensitive_toggle": "Покажи",
- "video_player.toggle_sound": "Звук",
- "account.mention": "Споменаване",
- "account.edit_profile": "Редактирай профила си",
- "account.unblock": "Не блокирай",
- "account.unfollow": "Не следвай",
- "account.block": "Блокирай",
- "account.follow": "Последвай",
- "account.posts": "Публикации",
- "account.follows": "Следвам",
- "account.followers": "Последователи",
- "account.follows_you": "Твой последовател",
- "account.requested": "В очакване на одобрение",
- "getting_started.heading": "Първи стъпки",
- "getting_started.about_addressing": "Можеш да последваш потребител, ако знаеш потребителското му име и домейна, на който се намира, като в полето за търсене ги въведеш по този начин: име@домейн",
- "getting_started.about_shortcuts": "Ако с търсения потребител се намирате на един и същ домейн, достатъчно е да въведеш само името. Същото важи и за споменаване на хора в публикации.",
- "getting_started.about_developer": "Можеш да потърсиш разработчика на този проект като: Gargron@mastodon.social",
- "getting_started.open_source_notice": "Mastodon е софтуер с отворен код. Можеш да помогнеш или да докладваш за проблеми в Github: {github}.",
- "column.home": "Начало",
- "column.mentions": "Споменавания",
- "column.public": "Публичен канал",
- "column.notifications": "Известия",
- "tabs_bar.compose": "Съставяне",
- "tabs_bar.home": "Начало",
- "tabs_bar.mentions": "Споменавания",
- "tabs_bar.public": "Публичен канал",
- "tabs_bar.notifications": "Известия",
- "compose_form.placeholder": "Какво си мислиш?",
- "compose_form.publish": "Раздумай",
- "compose_form.sensitive": "Отбележи съдържанието като деликатно",
- "compose_form.spoiler": "Скрий текста зад предупреждение",
- "compose_form.private": "Отбележи като поверително",
- "compose_form.privacy_disclaimer": "Поверителни публикации ще бъдат изпратени до споменатите потребители на {domains}. Доверяваш ли се на {domainsCount, plural, one {that server} other {those servers}}, че няма да издаде твоята публикация?",
- "compose_form.unlisted": "Не показвай в публичния канал",
- "navigation_bar.edit_profile": "Редактирай профил",
- "navigation_bar.preferences": "Предпочитания",
- "navigation_bar.public_timeline": "Публичен канал",
- "navigation_bar.logout": "Излизане",
- "reply_indicator.cancel": "Отказ",
- "search.placeholder": "Търсене",
- "search.account": "Акаунт",
- "search.hashtag": "Хаштаг",
- "upload_button.label": "Добави медия",
- "upload_form.undo": "Отмяна",
- "notification.follow": "{name} те последва",
- "notification.favourite": "{name} хареса твоята публикация",
- "notification.reblog": "{name} сподели твоята публикация",
- "notification.mention": "{name} те спомена",
- "notifications.column_settings.alert": "Десктоп известия",
- "notifications.column_settings.show": "Покажи в колона",
- "notifications.column_settings.follow": "Нови последователи:",
- "notifications.column_settings.favourite": "Предпочитани:",
- "notifications.column_settings.mention": "Споменавания:",
- "notifications.column_settings.reblog": "Споделяния:",
-};
-
-export default bg;
diff --git a/app/assets/javascripts/components/locales/de.jsx b/app/assets/javascripts/components/locales/de.jsx
@@ -1,126 +0,0 @@
-const de = {
- "account.block": "@{name} blocken",
- "account.disclaimer": "Dieser Benutzer ist von einer anderen Instanz. Diese Zahl könnte größer sein.",
- "account.edit_profile": "Profil bearbeiten",
- "account.follow": "Folgen",
- "account.followers": "Folgende",
- "account.follows": "Folgt",
- "account.follows_you": "Folgt dir",
- "account.mention": "@{name} erwähnen",
- "account.mute": "@{name} stummschalten",
- "account.posts": "Beiträge",
- "account.report": "@{name} melden",
- "account.requested": "Warte auf Erlaubnis",
- "account.unblock": "@{name} entblocken",
- "account.unfollow": "Entfolgen",
- "account.unmute": "@{name} nicht mehr stummschalten",
- "boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen",
- "column_back_button.label": "Zurück",
- "column.blocks": "Blockierte Benutzer",
- "column.community": "Lokale Zeitleiste",
- "column.favourites": "Favoriten",
- "column.follow_requests": "Folgeanfragen",
- "column.home": "Startseite",
- "column.mutes": "Stummgeschaltete Benutzer",
- "column.notifications": "Mitteilungen",
- "column.public": "Gesamtes bekanntes Netz",
- "compose_form.placeholder": "Worüber möchtest du schreiben?",
- "compose_form.privacy_disclaimer": "Dein privater Status wird an die genannten Benutzer auf den Domains {domains} zugestellt. Vertraust du {domainsCount, plural, one {diesem Server} other {diesen Servern}}? Private Beiträge funktionieren nur auf Mastodon-Instanzen. Wenn {domains} {domainsCount, plural, one {keine Mastodon-Instanz ist} other {keine Mastodon-Instanzen sind}}, wird es dort kein Anzeichen geben, dass dein Beitrag privat ist und er könnte geteilt oder anderweitig für unerwünschte Empfänger sichtbar gemacht werden.",
- "compose_form.publish": "Tröt",
- "compose_form.sensitive": "Medien als heikel markieren",
- "compose_form.spoiler_placeholder": "Inhaltswarnung",
- "compose_form.spoiler": "Text hinter Warnung verbergen",
- "emoji_button.label": "Emoji einfügen",
- "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe etwas öffentlich, um den Ball ins Rollen zu bringen!",
- "empty_column.hashtag": "Es gibt noch nichts unter diesem Hashtag.",
- "empty_column.home.public_timeline": "die öffentliche Zeitleiste",
- "empty_column.home": "Du folgst noch niemandem. Besuche {public} oder benutze die Suche, um zu starten oder andere Benutzer anzutreffen.",
- "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um die Konversation zu starten.",
- "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Benutzern von anderen Instanzen, um es aufzufüllen.",
- "follow_request.authorize": "Erlauben",
- "follow_request.reject": "Ablehnen",
- "getting_started.apps": "Es sind verschiedene Apps verfügbar",
- "getting_started.heading": "Erste Schritte",
- "getting_started.open_source_notice": "Mastodon ist quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.",
- "home.column_settings.advanced": "Fortgeschritten",
- "home.column_settings.basic": "Einfach",
- "home.column_settings.filter_regex": "Filter durch reguläre Ausdrücke",
- "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen",
- "home.column_settings.show_replies": "Antworten anzeigen",
- "home.settings": "Spalteneinstellungen",
- "lightbox.close": "Schließen",
- "loading_indicator.label": "Lade…",
- "media_gallery.toggle_visible": "Sichtbarkeit einstellen",
- "missing_indicator.label": "Nicht gefunden",
- "navigation_bar.blocks": "Blockierte Benutzer",
- "navigation_bar.community_timeline": "Lokale Zeitleiste",
- "navigation_bar.edit_profile": "Profil bearbeiten",
- "navigation_bar.favourites": "Favoriten",
- "navigation_bar.follow_requests": "Folgeanfragen",
- "navigation_bar.info": "Erweiterte Informationen",
- "navigation_bar.logout": "Abmelden",
- "navigation_bar.mutes": "Stummgeschaltete Benutzer",
- "navigation_bar.preferences": "Einstellungen",
- "navigation_bar.public_timeline": "Föderierte Zeitleiste",
- "notification.favourite": "{name} favorisierte deinen Status",
- "notification.follow": "{name} folgt dir",
- "notification.mention": "{name} erwähnte dich",
- "notification.reblog": "{name} teilte deinen Status",
- "notifications.clear_confirmation": "Bist du sicher, dass du alle Mitteilungen beseitigen willst?",
- "notifications.clear": "Mitteilungen beseitigen",
- "notifications.column_settings.alert": "Desktop-Benachrichtigungen",
- "notifications.column_settings.favourite": "Favorisierungen:",
- "notifications.column_settings.follow": "Neue Folgende:",
- "notifications.column_settings.mention": "Erwähnungen:",
- "notifications.column_settings.reblog": "Geteilte Beiträge:",
- "notifications.column_settings.show": "In der Spalte anzeigen",
- "notifications.column_settings.sound": "Ton abspielen",
- "notifications.settings": "Spalteneinstellungen",
- "privacy.change": "Privatsphäre des Status anpassen",
- "privacy.direct.long": "Beitrag nur an erwähnte Benutzer",
- "privacy.direct.short": "Direkt",
- "privacy.private.long": "Beitrag nur an Folgende",
- "privacy.private.short": "Privat",
- "privacy.public.long": "Beitrag an öffentliche Zeitleisten",
- "privacy.public.short": "Öffentlich",
- "privacy.unlisted.long": "Nicht in öffentlichen Zeitleisten anzeigen",
- "privacy.unlisted.short": "Nicht gelistet",
- "reply_indicator.cancel": "Abbrechen",
- "report.heading": "Neue Meldung",
- "report.placeholder": "Zusätzliche Kommentare",
- "report.submit": "Absenden",
- "report.target": "Melden",
- "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
- "search.placeholder": "Suche",
- "search.status_by": "Status von {name}",
- "status.delete": "Löschen",
- "status.favourite": "Favorisieren",
- "status.load_more": "Weitere laden",
- "status.media_hidden": "Medien versteckt",
- "status.mention": "Erwähnen",
- "status.open": "Öffnen",
- "status.reblog": "Teilen",
- "status.reblogged_by": "{name} teilte",
- "status.reply": "Antworten",
- "status.replyAll": "Auf Thread antworten",
- "status.report": "@{name} melden",
- "status.sensitive_toggle": "Klicke, um sie zu sehen",
- "status.sensitive_warning": "Heikle Inhalte",
- "status.show_less": "Weniger anzeigen",
- "status.show_more": "Mehr anzeigen",
- "tabs_bar.compose": "Schreiben",
- "tabs_bar.federated_timeline": "Föderation",
- "tabs_bar.home": "Home",
- "tabs_bar.local_timeline": "Lokal",
- "tabs_bar.notifications": "Mitteilungen",
- "upload_area.title": "Hereinziehen zum Hochladen",
- "upload_button.label": "Mediendatei hinzufügen",
- "upload_form.undo": "Entfernen",
- "upload_progress.label": "Lade hoch…",
- "video_player.toggle_sound": "Ton umschalten",
- "video_player.toggle_visible": "Sichtbarkeit umschalten",
- "video_player.expand": "Videoanzeige vergrößern",
- "video_player.video_error": "Video konnte nicht abgespielt werden",
-};
-
-export default de;
diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx
@@ -1,177 +0,0 @@
-/**
- * Note for Contributors:
- * This file (en.jsx) serve as a template for other languages.
- * To make other contributors' life easier, please REMEMBER:
- * 1. to add your new string here; and
- * 2. to remove old strings that are no longer needed; and
- * 3. to sort the strings by the key.
- * 4. To rename the `en` const name and export default name to match your locale.
- * Thanks!
- */
-const en = {
- "account.block": "Block @{name}",
- "account.disclaimer": "This user is from another instance. This number may be larger.",
- "account.edit_profile": "Edit profile",
- "account.follow": "Follow",
- "account.followers": "Followers",
- "account.follows": "Follows",
- "account.follows_you": "Follows you",
- "account.mention": "Mention @{name}",
- "account.mute": "Mute @{name}",
- "account.posts": "Posts",
- "account.report": "Report @{name}",
- "account.requested": "Awaiting approval",
- "account.unblock": "Unblock @{name}",
- "account.unfollow": "Unfollow",
- "account.unmute": "Unmute @{name}",
- "boost_modal.combo": "You can press {combo} to skip this next time",
- "column.blocks": "Blocked users",
- "column.community": "Local timeline",
- "column.favourites": "Favourites",
- "column.follow_requests": "Follow requests",
- "column.home": "Home",
- "column.mutes": "Muted users",
- "column.notifications": "Notifications",
- "column.public": "Federated timeline",
- "column_back_button.label": "Back",
- "column_subheading.navigation": "Navigation",
- "column_subheading.settings": "Settings",
- "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
- "compose_form.lock_disclaimer.lock": "locked",
- "compose_form.placeholder": "What is on your mind?",
- "compose_form.privacy_disclaimer": "Your post will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is not a public post, and it may be boosted or otherwise made visible to unintended recipients.",
- "compose_form.publish": "Toot",
- "compose_form.sensitive": "Mark media as sensitive",
- "compose_form.spoiler": "Hide text behind warning",
- "compose_form.spoiler_placeholder": "Content warning",
- "confirmation_modal.cancel": "Cancel",
- "confirmations.block.confirm": "Block",
- "confirmations.block.message": "Are you sure you want to block {name}?",
- "confirmations.delete.confirm": "Delete",
- "confirmations.delete.message": "Are you sure you want to delete this status?",
- "confirmations.mute.confirm": "Mute",
- "confirmations.mute.message": "Are you sure you want to mute {name}?",
- "emoji_button.activity": "Activity",
- "emoji_button.flags": "Flags",
- "emoji_button.food": "Food & Drink",
- "emoji_button.label": "Insert emoji",
- "emoji_button.nature": "Nature",
- "emoji_button.objects": "Objects",
- "emoji_button.people": "People",
- "emoji_button.search": "Search...",
- "emoji_button.symbols": "Symbols",
- "emoji_button.travel": "Travel & Places",
- "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
- "empty_column.hashtag": "There is nothing in this hashtag yet.",
- "empty_column.home.public_timeline": "the public timeline",
- "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
- "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
- "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
- "follow_request.authorize": "Authorize",
- "follow_request.reject": "Reject",
- "getting_started.apps": "Various apps are available",
- "getting_started.heading": "Getting started",
- "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
- "home.column_settings.advanced": "Advanced",
- "home.column_settings.basic": "Basic",
- "home.column_settings.filter_regex": "Filter out by regular expressions",
- "home.column_settings.show_reblogs": "Show boosts",
- "home.column_settings.show_replies": "Show replies",
- "home.settings": "Column settings",
- "lightbox.close": "Close",
- "loading_indicator.label": "Loading...",
- "media_gallery.toggle_visible": "Toggle visibility",
- "missing_indicator.label": "Not found",
- "navigation_bar.blocks": "Blocked users",
- "navigation_bar.community_timeline": "Local timeline",
- "navigation_bar.edit_profile": "Edit profile",
- "navigation_bar.favourites": "Favourites",
- "navigation_bar.follow_requests": "Follow requests",
- "navigation_bar.info": "Extended information",
- "navigation_bar.logout": "Logout",
- "navigation_bar.mutes": "Muted users",
- "navigation_bar.preferences": "Preferences",
- "navigation_bar.public_timeline": "Federated timeline",
- "notification.favourite": "{name} favourited your status",
- "notification.follow": "{name} followed you",
- "notification.mention": "{name} mentioned you",
- "notification.reblog": "{name} boosted your status",
- "notifications.clear": "Clear notifications",
- "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
- "notifications.column_settings.alert": "Desktop notifications",
- "notifications.column_settings.favourite": "Favourites:",
- "notifications.column_settings.follow": "New followers:",
- "notifications.column_settings.mention": "Mentions:",
- "notifications.column_settings.reblog": "Boosts:",
- "notifications.column_settings.show": "Show in column",
- "notifications.column_settings.sound": "Play sound",
- "notifications.settings": "Column settings",
- "onboarding.done": "Done",
- "onboarding.next": "Next",
- "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
- "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
- "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
- "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
- "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
- "onboarding.page_one.welcome": "Welcome to Mastodon!",
- "onboarding.page_six.admin": "Your instance's admin is {admin}.",
- "onboarding.page_six.almost_done": "Almost done...",
- "onboarding.page_six.appetoot": "Bon Appetoot!",
- "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
- "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
- "onboarding.page_six.guidelines": "community guidelines",
- "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
- "onboarding.page_six.various_app": "mobile apps",
- "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
- "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
- "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
- "onboarding.skip": "Skip",
- "privacy.change": "Adjust status privacy",
- "privacy.direct.long": "Post to mentioned users only",
- "privacy.direct.short": "Direct",
- "privacy.private.long": "Post to followers only",
- "privacy.private.short": "Followers-only",
- "privacy.public.long": "Post to public timelines",
- "privacy.public.short": "Public",
- "privacy.unlisted.long": "Do not post to public timelines",
- "privacy.unlisted.short": "Unlisted",
- "reply_indicator.cancel": "Cancel",
- "report.heading": "New report",
- "report.placeholder": "Additional comments",
- "report.submit": "Submit",
- "report.target": "Reporting",
- "search.placeholder": "Search",
- "search.status_by": "Status by {name}",
- "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
- "status.cannot_reblog": "This post cannot be boosted",
- "status.delete": "Delete",
- "status.favourite": "Favourite",
- "status.load_more": "Load more",
- "status.media_hidden": "Media hidden",
- "status.mention": "Mention @{name}",
- "status.open": "Expand this status",
- "status.reblog": "Boost",
- "status.reblogged_by": "{name} boosted",
- "status.reply": "Reply",
- "status.replyAll": "Reply to thread",
- "status.report": "Report @{name}",
- "status.sensitive_toggle": "Click to view",
- "status.sensitive_warning": "Sensitive content",
- "status.show_less": "Show less",
- "status.show_more": "Show more",
- "tabs_bar.compose": "Compose",
- "tabs_bar.federated_timeline": "Federated",
- "tabs_bar.home": "Home",
- "tabs_bar.local_timeline": "Local",
- "tabs_bar.notifications": "Notifications",
- "upload_area.title": "Drag & drop to upload",
- "upload_button.label": "Add media",
- "upload_form.undo": "Undo",
- "upload_progress.label": "Uploading...",
- "video_player.expand": "Expand video",
- "video_player.toggle_sound": "Toggle sound",
- "video_player.toggle_visible": "Toggle visibility",
- "video_player.video_error": "Video could not be played",
-};
-
-export default en;
diff --git a/app/assets/javascripts/components/locales/eo.jsx b/app/assets/javascripts/components/locales/eo.jsx
@@ -1,68 +0,0 @@
-const eo = {
- "column_back_button.label": "Reveni",
- "lightbox.close": "Fermi",
- "loading_indicator.label": "Ŝarĝanta...",
- "status.mention": "Mencii @{name}",
- "status.delete": "Forigi",
- "status.reply": "Respondi",
- "status.reblog": "Diskonigi",
- "status.favourite": "Favori",
- "status.reblogged_by": "{name} diskonigita",
- "status.sensitive_warning": "Tikla enhavo",
- "status.sensitive_toggle": "Alklaki por vidi",
- "video_player.toggle_sound": "Aktivigi sonojn",
- "account.mention": "Mencii @{name}",
- "account.edit_profile": "Redakti la profilon",
- "account.unblock": "Malbloki @{name}",
- "account.unfollow": "Malsekvi",
- "account.block": "Bloki @{name}",
- "account.follow": "Sekvi",
- "account.posts": "Mesaĝoj",
- "account.follows": "Sekvatoj",
- "account.followers": "Sekvantoj",
- "account.follows_you": "Sekvas vin",
- "account.requested": "Atendas aprobon",
- "getting_started.heading": "Por komenci",
- "getting_started.about_addressing": "Vi povas sekvi homojn se vi konas la uzantnomon kaj domajnon tajpinte retpoŝtecan adreson en la serĉilon.",
- "getting_started.about_shortcuts": "Se la celita uzanto troviĝas en la sama domajno de vi, uzi nur la uzantnomon sufiĉos. La sama regulo validas por mencii aliajn uzantojn en mesaĝo.",
- "getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en github je {github}. {apps}.",
- "column.home": "Hejmo",
- "column.community": "Loka tempolinio",
- "column.public": "Fratara tempolinio",
- "column.notifications": "Sciigoj",
- "tabs_bar.compose": "Ekskribi",
- "tabs_bar.home": "Hejmo",
- "tabs_bar.mentions": "Sciigoj",
- "tabs_bar.public": "Fratara tempolinio",
- "tabs_bar.notifications": "Sciigoj",
- "compose_form.placeholder": "Pri kio vi pensas?",
- "compose_form.publish": "Hup",
- "compose_form.sensitive": "Marki ke la enhavo estas tikla",
- "compose_form.spoiler": "Kaŝi la tekston malantaŭ averto",
- "compose_form.private": "Marki ke la enhavo estas privata",
- "compose_form.privacy_disclaimer": "Via privata mesaĝo estos sendita nur al menciitaj uzantoj en {domains}. Ĉu vi fidas {domainsCount, plural, one {tiun servilon} other {tiujn servilojn}}? Mesaĝa privateco funkcias nur en aperaĵoj de Mastodon. Se {domains} {domainsCount, plural, one {ne estas aperaĵo de Mastodon} other {ne estas aperaĵoj de Mastodon}}, estos neniu indiko ke via mesaĝo estas privata, kaj ĝi povus esti diskonigita aŭ videbligita al necelitaj ricevantoj.",
- "compose_form.unlisted": "Ne afiŝi en publikaj tempolinioj",
- "navigation_bar.edit_profile": "Redakti la profilon",
- "navigation_bar.preferences": "Preferoj",
- "navigation_bar.community_timeline": "Loka tempolinio",
- "navigation_bar.public_timeline": "Fratara tempolinio",
- "navigation_bar.logout": "Elsaluti",
- "reply_indicator.cancel": "Rezigni",
- "search.placeholder": "Serĉi",
- "search.account": "Konto",
- "search.hashtag": "Kradvorto",
- "upload_button.label": "Aldoni enhavaĵon",
- "upload_form.undo": "Malfari",
- "notification.follow": "{name} sekvis vin",
- "notification.favourite": "{name} favoris vian mesaĝon",
- "notification.reblog": "{name} diskonigis vian mesaĝon",
- "notification.mention": "{name} menciis vin",
- "notifications.column_settings.alert": "Retumilaj atentigoj",
- "notifications.column_settings.show": "Montri en kolono",
- "notifications.column_settings.follow": "Novaj sekvantoj:",
- "notifications.column_settings.favourite": "Favoroj:",
- "notifications.column_settings.mention": "Mencioj:",
- "notifications.column_settings.reblog": "Diskonigoj:",
-};
-
-export default eo;
diff --git a/app/assets/javascripts/components/locales/es.jsx b/app/assets/javascripts/components/locales/es.jsx
@@ -1,93 +0,0 @@
-const es = {
- "column_back_button.label": "Atrás",
- "lightbox.close": "Cerrar",
- "loading_indicator.label": "Cargando...",
- "status.mention": "Mencionar",
- "status.delete": "Borrar",
- "status.reply": "Responder",
- "status.reblog": "Retoot",
- "status.favourite": "Favorito",
- "status.reblogged_by": "Retooteado por {name}",
- "status.sensitive_warning": "Contenido sensible",
- "status.sensitive_toggle": "Click para ver",
- "status.show_more": "Mostrar más",
- "status.show_less": "Mostrar menos",
- "status.open": "Expandir estado",
- "status.report": "Reportar",
- "video_player.toggle_sound": "Act/Desac. sonido",
- "account.mention": "Mencionar",
- "account.edit_profile": "Editar perfil",
- "account.unblock": "Desbloquear",
- "account.unfollow": "Dejar de seguir",
- "account.mute": "Silenciar",
- "account.block": "Bloquear",
- "account.follow": "Seguir",
- "account.posts": "Publicaciones",
- "account.follows": "Seguir",
- "account.followers": "Seguidores",
- "account.follows_you": "Te sigue",
- "account.requested": "Esperando aprobación",
- "getting_started.heading": "Primeros pasos",
- "getting_started.about_addressing": "Puedes seguir a gente si conoces su nombre de usuario y el dominio en el que están registrados, introduciendo algo similar a una dirección de correo electrónico en el formulario en la parte superior de la barra lateral.",
- "getting_started.about_shortcuts": "Si el usuario que buscas está en el mismo dominio que tú, simplemente funcionará introduciendo el nombre de usuario. La misma regla se aplica para mencionar a usuarios.",
- "getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}. {apps}.",
- "column.home": "Inicio",
- "column.community": "Historia local",
- "column.public": "Historia federada",
- "column.notifications": "Notificaciones",
- "column.blocks": "Usuarios bloqueados",
- "column.favourites": "Favoritos",
- "column.follow_requests": "Solicitudes para seguirte",
- "column.mutes": "Usuarios silenciados",
- "tabs_bar.compose": "Redactar",
- "tabs_bar.home": "Inicio",
- "tabs_bar.mentions": "Menciones",
- "tabs_bar.public": "Público",
- "tabs_bar.notifications": "Notificaciones",
- "compose_form.placeholder": "¿En qué estás pensando?",
- "compose_form.publish": "Tootear",
- "compose_form.sensitive": "Marcar contenido como sensible",
- "compose_form.spoiler": "Ocultar texto tras advertencia",
- "compose_form.spoiler_placeholder": "Advertencia de contenido",
- "composer_form.private": "Marcar como privado",
- "composer_form.privacy_disclaimer": "Tu estado se mostrará a los usuarios mencionados en {domains}. Tu estado podrá ser visto en otras instancias, quizás no quieras que tu estado sea visto por otros usuarios.",
- "compose_form.unlisted": "No mostrar en la historia federada",
- "navigation_bar.edit_profile": "Editar perfil",
- "navigation_bar.preferences": "Preferencias",
- "navigation_bar.community_timeline": "Historia local",
- "navigation_bar.public_timeline": "Historia federada",
- "navigation_bar.favourites": "Favoritos",
- "navigation_bar.blocks": "Usuarios bloqueados",
- "navigation_bar.info": "Información adicional",
- "navigation_bar.logout": "Cerrar sesión",
- "navigation_bar.follow_requests": "Solicitudes para seguirte",
- "navigation_bar.mutes": "Usuarios silenciados",
- "reply_indicator.cancel": "Cancelar",
- "search.placeholder": "Buscar",
- "search.account": "Cuenta",
- "search.hashtag": "Etiqueta",
- "upload_button.label": "Subir multimedia",
- "upload_form.undo": "Deshacer",
- "notification.follow": "{name} te empezó a seguir",
- "notification.favourite": "{name} marcó tu estado como favorito",
- "notification.reblog": "{name} ha retooteado tu estado",
- "notification.mention": "{name} te ha mencionado",
- "notifications.column_settings.alert": "Notificaciones de escritorio",
- "notifications.column_settings.show": "Mostrar en columna",
- "notifications.column_settings.follow": "Nuevos seguidores:",
- "notifications.column_settings.favourite": "Favoritos:",
- "notifications.column_settings.mention": "Menciones:",
- "notifications.column_settings.reblog": "Retoots:",
- "emoji_button.label": "Insertar emoji",
- "privacy.public.short": "Público",
- "privacy.public.long": "Mostrar en la historia federada",
- "privacy.unlisted.short": "Sin federar",
- "privacy.unlisted.long": "No mostrar en la historia federada",
- "privacy.private.short": "Privado",
- "privacy.private.long": "Sólo mostrar a seguidores",
- "privacy.direct.short": "Directo",
- "privacy.direct.long": "Sólo mostrar a los usuarios mencionados",
- "privacy.change": "Ajustar privacidad"
-};
-
-export default es;
diff --git a/app/assets/javascripts/components/locales/fa.jsx b/app/assets/javascripts/components/locales/fa.jsx
@@ -1,136 +0,0 @@
-const fa = {
- "account.block": "@{name} را مسدود کن",
- "account.disclaimer": "این کاربر عضو سرور متفاوتی است. شاید عدد واقعی بیشتر از این باشد.",
- "account.edit_profile": "ویرایش نمایه",
- "account.follow": "پی بگیرید",
- "account.followers": "پیگیران",
- "account.follows_you": "پیگیر شماست",
- "account.follows": "پی میگیرد",
- "account.mention": "نامبردن از @{name}",
- "account.mute": "بیصدا کردن @{name}",
- "account.posts": "نوشتهها",
- "account.report": "گزارش @{name}",
- "account.requested": "در انتظار پذیرش",
- "account.unblock": "رفع انسداد @{name}",
- "account.unfollow": "پایان پیگیری",
- "account.unmute": "باصدا کردن @{name}",
- "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
- "column_back_button.label": "بازگشت",
- "column.blocks": "کاربران مسدودشده",
- "column.community": "نوشتههای محلی",
- "column.favourites": "پسندیدهها",
- "column.follow_requests": "درخواستهای پیگیری",
- "column.home": "خانه",
- "column.mutes": "کاربران بیصداشده",
- "column.notifications": "اعلانها",
- "column.public": "نوشتههای همهجا",
- "compose_form.placeholder": "تازه چه خبر؟",
- "compose_form.privacy_disclaimer": "نوشتهٔ خصوصی شما به کاربران نامبردهشده در {domains} فرستاده میشود. آیا به {domainsCount, plural, one {آن سرور} other {آن سرورها}} اعتماد دارید؟ تنظیمات حریم خصوصی نوشتهها تنها در سرورهای ماستدون کار میکند. اگر {domains} {domainsCount, plural, one {یک سرور ماستدون نباشد} other {سرورهای ماستدون نباشند}}، اشارهای به خصوصیبودن نوشتهٔ شما نخواهد شد و شاید نوشتهٔ شما همرسان شود یا برای کاربرانی که نمیخواهید نمایش یابد.",
- "compose_form.publish": "بوق",
- "compose_form.sensitive": "تصاویر حساس هستند",
- "compose_form.spoiler_placeholder": "هشدار محتوا",
- "compose_form.spoiler": "نوشته را پشت هشدار پنهان کنید",
- "emoji_button.label": "افزودن شکلک",
- "emoji_button.search": "جستجو...",
- "emoji_button.people": "مردم",
- "emoji_button.nature": "طبیعت",
- "emoji_button.food": "غذا و نوشیدنی",
- "emoji_button.activity": "فعالیت",
- "emoji_button.travel": "سفر و مکان",
- "emoji_button.objects": "اشیا",
- "emoji_button.symbols": "نمادها",
- "emoji_button.flags": "پرچمها",
- "empty_column.community": "فهرست نوشتههای محلی خالی است. چیزی بنویسید تا چرخش بچرخد!",
- "empty_column.hashtag": "هنوز هیچ چیزی با این هشتگ نیست.",
- "empty_column.home.public_timeline": "فهرست نوشتههای همهجا",
- "empty_column.home": "شما هنوز پیگیر کسی نیستید. {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.",
- "empty_column.notifications": "هنوز هیچ اعلانی ندارید. به نوشتههای دیگران واکنش نشان دهید تا گفتگو آغاز شود.",
- "empty_column.public": "اینجا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران دیگر را پی بگیرید تا اینجا پر شود",
- "follow_request.authorize": "اجازه دهید",
- "follow_request.reject": "اجازه ندهید",
- "getting_started.apps": "اپهای گوناگونی در دسترساند",
- "getting_started.heading": "آغاز کنید",
- "getting_started.open_source_notice": "ماستدون یک نرمافزار آزاد است. میتوانید در ساخت آن مشارکت کنید یا مشکلاتش را در {github} گزارش دهید. {apps}.",
- "home.column_settings.advanced": "پیشرفته",
- "home.column_settings.basic": "اصلی",
- "home.column_settings.filter_regex": "با عبارتهای باقاعده فیلتر کنید",
- "home.column_settings.show_reblogs": "نمایش بازبوقها",
- "home.column_settings.show_replies": "نمایش پاسخها",
- "home.settings": "تنظیمات ستون",
- "lightbox.close": "بستن",
- "loading_indicator.label": "بارگیری...",
- "media_gallery.toggle_visible": "تغییر پیدایی",
- "missing_indicator.label": "پیدا نشد",
- "navigation_bar.blocks": "کاربران مسدودشده",
- "navigation_bar.community_timeline": "نوشتههای محلی",
- "navigation_bar.edit_profile": "ویرایش نمایه",
- "navigation_bar.favourites": "پسندیدهها",
- "navigation_bar.follow_requests": "درخواستهای پیگیری",
- "navigation_bar.info": "اطلاعات تکمیلی",
- "navigation_bar.logout": "خروج",
- "navigation_bar.mutes": "کاربران بیصداشده",
- "navigation_bar.preferences": "ترجیحات",
- "navigation_bar.public_timeline": "نوشتههای همهجا",
- "notification.favourite": "{name} نوشتهٔ شما را پسندید",
- "notification.follow": "{name} پیگیر شما شد",
- "notification.mention": "{name} از شما نام برد",
- "notification.reblog": "{name} نوشتهٔ شما را بازبوقید",
- "notifications.clear_confirmation": "واقعاً میخواهید همهٔ اعلانهایتان را برای همیشه پاک کنید؟",
- "notifications.clear": "پاککردن اعلانها",
- "notifications.column_settings.alert": "اعلان در کامپیوتر",
- "notifications.column_settings.favourite": "پسندیدهها:",
- "notifications.column_settings.follow": "پیگیران تازه:",
- "notifications.column_settings.mention": "نامبردنها:",
- "notifications.column_settings.reblog": "بازبوقها:",
- "notifications.column_settings.show": "در ستون نشان بده",
- "notifications.column_settings.sound": "صدا را پخش کن",
- "notifications.settings": "تنظیمات ستون",
- "privacy.change": "تنظیم حریم خصوصی نوشتهها",
- "privacy.direct.long": "تنها به کاربران نامبردهشده نشان بده",
- "privacy.direct.short": "مستقیم",
- "privacy.private.long": "تنها به پیگیران نشان بده",
- "privacy.private.short": "خصوصی",
- "privacy.public.long": "در فهرست نوشتههای عمومی نشان بده",
- "privacy.public.short": "عمومی",
- "privacy.unlisted.long": "در فهرست نوشتههای همهجا نشان نده",
- "privacy.unlisted.short": "فهرستنشده",
- "reply_indicator.cancel": "لغو",
- "report.heading": "گزارش تازه",
- "report.placeholder": "توضیح اضافه",
- "report.submit": "بفرست",
- "report.target": "گزارشدادن",
- "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
- "search.placeholder": "جستجو",
- "search.status_by": "نوشتهٔ {name}",
- "status.delete": "پاککردن",
- "status.favourite": "پسندیدن",
- "status.load_more": "بیشتر نشان بده",
- "status.media_hidden": "تصویر پنهان شده",
- "status.mention": "از @{name} نام ببرید",
- "status.open": "این نوشته را باز کن",
- "status.reblog": "بوق",
- "status.cannot_reblog": "این نوشته را نمیشود بازبوقید",
- "status.reblogged_by": "{name} بازبوقید",
- "status.reply": "پاسخ",
- "status.replyAll": "به نوشته پاسخ دهید",
- "status.report": "@{name} را گزارش دهید",
- "status.sensitive_toggle": "برای دیدن کلیک کنید",
- "status.sensitive_warning": "محتوای حساس",
- "status.show_less": "نهفتن",
- "status.show_more": "نمایش",
- "tabs_bar.compose": "بنویسید",
- "tabs_bar.federated_timeline": "همگانی",
- "tabs_bar.home": "خانه",
- "tabs_bar.local_timeline": "محلی",
- "tabs_bar.notifications": "اعلانها",
- "upload_area.title": "برای بارگذاری به اینجا بکشید",
- "upload_button.label": "افزودن تصویر",
- "upload_form.undo": "واگردانی",
- "upload_progress.label": "بارگذاری...",
- "video_player.toggle_sound": "تغییر صداداری",
- "video_player.toggle_visible": "تغییر پیدایی",
- "video_player.expand": "بازکردن ویدیو",
- "video_player.video_error": "ویدیو نمیتواند پخش شود",
-};
-
-export default fa;
diff --git a/app/assets/javascripts/components/locales/fi.jsx b/app/assets/javascripts/components/locales/fi.jsx
@@ -1,68 +0,0 @@
-const fi = {
- "column_back_button.label": "Takaisin",
- "lightbox.close": "Sulje",
- "loading_indicator.label": "Ladataan...",
- "status.mention": "Mainitse @{name}",
- "status.delete": "Poista",
- "status.reply": "Vastaa",
- "status.reblog": "Buustaa",
- "status.favourite": "Tykkää",
- "status.reblogged_by": "{name} buustasi",
- "status.sensitive_warning": "Arkaluontoista sisältöä",
- "status.sensitive_toggle": "Klikkaa nähdäksesi",
- "video_player.toggle_sound": "Äänet päälle/pois",
- "account.mention": "Mainitse @{name}",
- "account.edit_profile": "Muokkaa",
- "account.unblock": "Salli @{name}",
- "account.unfollow": "Lopeta seuraaminen",
- "account.block": "Estä @{name}",
- "account.follow": "Seuraa",
- "account.posts": "Postit",
- "account.follows": "Seuraa",
- "account.followers": "Seuraajia",
- "account.follows_you": "Seuraa sinua",
- "account.requested": "Odottaa hyväksyntää",
- "getting_started.heading": "Aloitus",
- "getting_started.about_addressing": "Voit seurata ihmisiä jos tiedät heidän käyttäjänimensä ja domainin missä he ovat syöttämällä e-mail-esque osoitteen Etsi kenttään.",
- "getting_started.about_shortcuts": "Jos etsimäsi henkilö on samassa domainissa kuin sinä, pelkkä käyttäjänimi kelpaa. Sama pätee kun mainitset ihmisiä statuksessasi",
- "getting_started.open_source_notice": "Mastodon Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}. {apps}.",
- "column.home": "Koti",
- "column.community": "Paikallinen aikajana",
- "column.public": "Yleinen aikajana",
- "column.notifications": "Ilmoitukset",
- "tabs_bar.compose": "Luo",
- "tabs_bar.home": "Koti",
- "tabs_bar.mentions": "Maininnat",
- "tabs_bar.public": "Yleinen aikajana",
- "tabs_bar.notifications": "Ilmoitukset",
- "compose_form.placeholder": "Mitä sinulla on mielessä?",
- "compose_form.publish": "Toot",
- "compose_form.sensitive": "Merkitse media herkäksi",
- "compose_form.spoiler": "Piiloita teksti varoituksen taakse",
- "compose_form.private": "Merkitse yksityiseksi",
- "compose_form.privacy_disclaimer": "Sinun yksityinen status toimitetaan mainitsemallesi käyttäjille domaineissa {domains}. Luotatko {domainsCount, plural, one {tähän palvelimeen} other {näihin palvelimiin}}? Postauksen yksityisyys toimii van Mastodon palvelimilla. Jos {domains} {domainsCount, plural, one {ei ole Mastodon palvelin} other {eivät ole Mastodon palvelin}}, viestiin ei tule Yksityinen-merkintää, ja sitä voidaan boostata tai muuten tehdä näkyväksi muille vastaanottajille.",
- "compose_form.unlisted": "Älä näytä yleisillä aikajanoilla",
- "navigation_bar.edit_profile": "Muokkaa profiilia",
- "navigation_bar.preferences": "Ominaisuudet",
- "navigation_bar.community_timeline": "Paikallinen aikajana",
- "navigation_bar.public_timeline": "Yleinen aikajana",
- "navigation_bar.logout": "Kirjaudu ulos",
- "reply_indicator.cancel": "Peruuta",
- "search.placeholder": "Hae",
- "search.account": "Tili",
- "search.hashtag": "Hashtag",
- "upload_button.label": "Lisää mediaa",
- "upload_form.undo": "Peru",
- "notification.follow": "{name} seurasi sinua",
- "notification.favourite": "{name} tykkäsi statuksestasi",
- "notification.reblog": "{name} buustasi statustasi",
- "notification.mention": "{name} mainitsi sinut",
- "notifications.column_settings.alert": "Työpöytä ilmoitukset",
- "notifications.column_settings.show": "Näytä sarakkeessa",
- "notifications.column_settings.follow": "Uusia seuraajia:",
- "notifications.column_settings.favourite": "Tykkäyksiä:",
- "notifications.column_settings.mention": "Mainintoja:",
- "notifications.column_settings.reblog": "Buusteja:",
-};
-
-export default fi;
diff --git a/app/assets/javascripts/components/locales/fr.jsx b/app/assets/javascripts/components/locales/fr.jsx
@@ -1,155 +0,0 @@
-/**
- * Note aux contributeurs⋅trices:
- * Pour rendre plus simple la vie des autres personnes
- * apportant leur contribution, merci de penser aux choses suivantes :
- * 1. Ajoutez les nouvelles chaînes traduites par ordre alphabétique
- * 2. Pensez à supprimer les chaînes inutilisées
- * Merci !
- */
-const fr = {
- "account.block": "Bloquer",
- "account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.",
- "account.edit_profile": "Modifier le profil",
- "account.followers": "Abonné⋅e⋅s",
- "account.follows": "Abonnements",
- "account.follow": "Suivre",
- "account.follows_you": "Vous suit",
- "account.mention": "Mentionner",
- "account.mute": "Masquer",
- "account.posts": "Statuts",
- "account.report": "Signaler",
- "account.requested": "Invitation envoyée",
- "account.unblock": "Débloquer",
- "account.unfollow": "Ne plus suivre",
- "account.unmute": "Ne plus masquer",
- "column_back_button.label": "Retour",
- "column.blocks": "Comptes bloqués",
- "column.community": "Fil public local",
- "column.favourites": "Favoris",
- "column.follow_requests": "Demandes de suivi",
- "column.home": "Accueil",
- "column.notifications": "Notifications",
- "column.public": "Fil public global",
- "compose_form.placeholder": "Qu’avez-vous en tête ?",
- "compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodon. Si {domains} {domainsCount, plural, one {n’est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n’y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d’une autre manière à d’autres personnes imprévues.",
- "compose_form.private": "Rendre privé",
- "compose_form.publish": "Pouet",
- "compose_form.sensitive": "Marquer le média comme délicat",
- "compose_form.spoiler": "Masquer le texte derrière un avertissement",
- "compose_form.spoiler_placeholder": "Avertissement",
- "compose_form.unlisted": "Ne pas afficher dans les fils publics",
- "emoji_button.label": "Insérer un emoji",
- "empty_column.community": "Le fil public local est vide. Écrivez-donc quelque chose pour le remplir !",
- "empty_column.hashtag": "Il n’y a encore aucun contenu relatif à ce hashtag",
- "empty_column.home.public_timeline": "le fil public",
- "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateurs⋅trices.",
- "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateurs⋅trices pour débuter la conversation.",
- "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateurs⋅trices d’autres instances pour remplir le fil public.",
- "follow_request.authorize": "Autoriser",
- "follow_request.reject": "Rejeter",
- "getting_started.about_addressing": "Vous pouvez suivre les statuts de quelqu’un en entrant dans le champ de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière d’une adresse courriel.",
- "getting_started.about_developer": "Pour suivre le développeur de ce projet, c’est Gargron@mastodon.social",
- "getting_started.about_shortcuts": "Si cette personne utilise la même instance que vous, l’identifiant suffit. C’est le même principe pour mentionner quelqu’un dans vos statuts.",
- "getting_started.heading": "Pour commencer",
- "getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {github} sur GitHub.",
- "home.column_settings.advanced": "Avancé",
- "home.column_settings.basic": "Basique",
- "home.column_settings.filter_regex": "Filtrer avec une expression rationnelle",
- "home.column_settings.show_reblogs": "Afficher les partages",
- "home.column_settings.show_replies": "Afficher les réponses",
- "home.settings": "Paramètres de la colonne",
- "lightbox.close": "Fermer",
- "loading_indicator.label": "Chargement…",
- "media_gallery.toggle_visible": "Modifier la visibilité",
- "missing_indicator.label": "Non trouvé",
- "navigation_bar.blocks": "Comptes bloqués",
- "navigation_bar.community_timeline": "Fil public local",
- "navigation_bar.edit_profile": "Modifier le profil",
- "navigation_bar.favourites": "Favoris",
- "navigation_bar.follow_requests": "Demandes de suivi",
- "navigation_bar.info": "Plus d’informations",
- "navigation_bar.logout": "Déconnexion",
- "navigation_bar.mutes": "Comptes silencés",
- "navigation_bar.preferences": "Préférences",
- "navigation_bar.public_timeline": "Fil public global",
- "notification.favourite": "{name} a ajouté à ses favoris :",
- "notification.follow": "{name} vous suit.",
- "notification.mention": "{name} vous a mentionné⋅e :",
- "notification.reblog": "{name} a partagé votre statut :",
- "notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?",
- "notifications.clear": "Nettoyer",
- "notifications.column_settings.alert": "Notifications locales",
- "notifications.column_settings.favourite": "Favoris :",
- "notifications.column_settings.follow": "Nouveaux abonné⋅e⋅s :",
- "notifications.column_settings.mention": "Mentions :",
- "notifications.column_settings.reblog": "Partages :",
- "notifications.column_settings.show": "Afficher dans la colonne",
- "notifications.column_settings.sound": "Émettre un son",
- "notifications.settings": "Paramètres de la colonne",
- "onboarding.next": "Suivant",
- "onboarding.page_five.public_timelines": "Le fil public global affiche les posts de tou⋅te⋅s les utilisateurs⋅trices suivi⋅es par les membres de {domain}. Le fil public local est identique mais se limite aux utilisateurs⋅trices de {domain}.",
- "onboarding.page_four.home": "L’Accueil affiche les posts de tou⋅te⋅s les utilisateurs⋅trices que vous suivez",
- "onboarding.page_four.notifications": "Les Notifications vous informent lorsque quelqu’un interagit avec vous",
- "onboarding.page_one.federation": "Mastodon est un réseau social qui appartient à tou⋅te⋅s.",
- "onboarding.page_one.handle": "Vous êtes sur {domain}, une des nombreuses instances indépendantes de Mastodon. Votre nom d’utilisateur⋅trice complet est {handle}",
- "onboarding.page_one.welcome": "Bienvenue sur Mastodon !",
- "onboarding.page_six.admin": "L’administrateur⋅trice de votre instance est {admin}",
- "onboarding.page_six.almost_done": "Nous y sommes presque…",
- "onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres. Et maintenant… Bon Appetoot!",
- "onboarding.page_six.github": "Mastodon est un logiciel libre, gratuit et open-source. Vous pouvez rapporter des bogues, suggérer des fonctionnalités, ou contribuer à son développement sur {github}.",
- "onboarding.page_six.guidelines": "règles de la communauté",
- "onboarding.page_six.read_guidelines": "S’il vous plaît, n’oubliez pas de lire les {guidelines} !",
- "onboarding.page_six.various_app": "applications mobiles",
- "onboarding.page_three.profile": "Modifiez votre profil pour changer votre avatar, votre description ainsi que votre nom. Vous y trouverez également d’autres préférences.",
- "onboarding.page_three.search": "Utilisez la barre de recherche pour trouver des utilisateurs⋅trices et regarder des hashtags tels que {illustration} et {introductions}. Pour trouver quelqu’un qui n’est pas sur cette instance, utilisez son nom d’utilisateur⋅trice complet.",
- "onboarding.page_two.compose": "Écrivez depuis la colonne de composition. Vous pouvez ajouter des images, changer les réglages de confidentialité, et ajouter des avertissements de contenu (Content Warning) grâce aux icônes en dessous.",
- "onboarding.skip": "Passer",
- "privacy.change": "Ajuster la confidentialité du message",
- "privacy.direct.long": "N’afficher que pour les personnes mentionnées",
- "privacy.direct.short": "Direct",
- "privacy.private.long": "N’afficher que pour vos abonné⋅e⋅s",
- "privacy.private.short": "Privé",
- "privacy.public.long": "Afficher dans les fils publics",
- "privacy.public.short": "Public",
- "privacy.unlisted.long": "Ne pas afficher dans les fils publics",
- "privacy.unlisted.short": "Non-listé",
- "reply_indicator.cancel": "Annuler",
- "report.heading": "Nouveau signalement",
- "report.placeholder": "Commentaires additionnels",
- "report.submit": "Envoyer",
- "report.target": "Signalement",
- "search.account": "Compte",
- "search.hashtag": "Mot-clé",
- "search.placeholder": "Rechercher",
- "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
- "search.status_by": "Statuts de {name}",
- "status.delete": "Effacer",
- "status.favourite": "Ajouter aux favoris",
- "status.load_more": "Charger plus",
- "status.media_hidden": "Média caché",
- "status.mention": "Mentionner",
- "status.open": "Déplier ce statut",
- "status.reblogged_by": "{name} a partagé :",
- "status.reblog": "Partager",
- "status.reply": "Répondre",
- "status.report": "Signaler @{name}",
- "status.sensitive_toggle": "Cliquer pour dévoiler",
- "status.sensitive_warning": "Contenu délicat",
- "status.show_less": "Replier",
- "status.show_more": "Déplier",
- "tabs_bar.compose": "Composer",
- "tabs_bar.federated_timeline": "Fil public global",
- "tabs_bar.home": "Accueil",
- "tabs_bar.local_timeline": "Fil public local",
- "tabs_bar.mentions": "Mentions",
- "tabs_bar.notifications": "Notifications",
- "tabs_bar.public": "Fil public global",
- "upload_area.title": "Glissez et déposez pour envoyer",
- "upload_button.label": "Joindre un média",
- "upload_form.undo": "Annuler",
- "upload_progress.label": "Envoi en cours…",
- "video_player.toggle_sound": "Mettre/Couper le son",
- "video_player.toggle_visible": "Afficher/Cacher la vidéo",
-};
-
-export default fr;
diff --git a/app/assets/javascripts/components/locales/he.jsx b/app/assets/javascripts/components/locales/he.jsx
@@ -1,177 +0,0 @@
-/**
- * הערה לתורמים:
- * קובץ זה (he.jsx)מבוסס על en.jsx ויש לעדכנו מפעם לפעם כשיוצאות גרסאות חדשות.
- * אנא הקלו על התורמים העתידיים:
- * 1. הוסיפו לכאן מחרוזות חדשות
- * 2. הסירו מחרוזות ישנות שכבר לא בשימוש בגרסא האנגלית
- * 3. מיינו את השורות לפי סדר ABC כמו בקובץ המקורי.
- * 4. ובבקשה כבדו את סגנון התרגום שהנחלנו כאן, או תאמו איתנו אם ישנם שינויים יסודיים
- * תודה!
- */
-const he = {
- "account.block": "חסימת @{name}",
- "account.disclaimer": "‏משתמש זה מגיע מקהילה אחרת. המספר הזה עשוי להיות גדול יותר.",
- "account.edit_profile": "עריכת פרופיל",
- "account.follow": "מעקב",
- "account.followers": "עוקבים",
- "account.follows_you": "במעקב אחריך",
- "account.follows": "נעקבים",
- "account.mention": "אזכור של @{name}",
- "account.mute": "להשתיק את @{name}",
- "account.posts": "הודעות",
- "account.report": "לדווח על @{name}",
- "account.requested": "בהמתנה לאישור",
- "account.unblock": "הסרת חסימה מעל @{name}",
- "account.unfollow": "הפסקת מעקב",
- "account.unmute": "הפסקת השתקת @{name}",
- "boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
- "column.blocks": "חסימות",
- "column.community": "פיד מקומי",
- "column.favourites": "חיבובים",
- "column.follow_requests": "בקשות מעקב",
- "column.home": "בבית",
- "column.mutes": "השתקות",
- "column.notifications": "התראות",
- "column.public": "בפרהסיה",
- "column_back_button.label": "אחורה",
- "column_subheading.navigation": "ניווט",
- "column_subheading.settings": "אפשרויות",
- "compose_form.lock_disclaimer": "חשבונך אינו {locked}. כל אחד יוכל לעקוב אחריך כדי לקרוא את הודעותיך המיועדות לעוקבים בלבד.",
- "compose_form.lock_disclaimer.lock": "נעול",
- "compose_form.placeholder": "‏מה עובר לך בראש?",
- "compose_form.privacy_disclaimer": "‏הודעתך הפרטית תשלח למשתמשים על {domains}. האם ניתן לסמוך על {domainsCount, plural, one {שרת זה} other {שרתים אלו}}? פרטיות ההודעה קיימת רק על שרתי מסטודון. אם {domains} {domainsCount, plural, one {הוא לא שרת מסטודון} other {הם לא שרתי מסטודון}}, לא יהיה שום סימן שההודעה פרטית, והוא עשוי להיות מקודם או להחשף למשתמשים שלא ברשימת היעד.",
- "compose_form.publish": "‏לחצרץ",
- "compose_form.sensitive": "סימון תוכן כרגיש",
- "compose_form.spoiler": "הסתרה מאחורי אזהרת תוכן",
- "compose_form.spoiler_placeholder": "אזהרת תוכן",
- "confirmation_modal.cancel": "ביטול",
- "confirmations.block.confirm": "לחסום",
- "confirmations.block.message": "לחסום את {name}?",
- "confirmations.delete.confirm": "למחוק",
- "confirmations.delete.message": "למחוק את ההודעה?",
- "confirmations.mute.confirm": "להשתיק",
- "confirmations.mute.message": "להשתיק את {name}?",
- "emoji_button.activity": "פעילות",
- "emoji_button.flags": "דגלים",
- "emoji_button.food": "אוכל ושתיה",
- "emoji_button.label": "הוספת אמוג'י",
- "emoji_button.nature": "טבע",
- "emoji_button.objects": "חפצים",
- "emoji_button.people": "אנשים",
- "emoji_button.search": "‏חיפוש...",
- "emoji_button.symbols": "סמלים",
- "emoji_button.travel": "טיולים ואתרים",
- "empty_column.community": "‏טור הסביבה ריק. יש לפרסם משהו כדי שדברים יתרחילו להתגלגל!",
- "empty_column.hashtag": "‏אין כלום בהאשתג הזה עדיין.",
- "empty_column.home.public_timeline": "בפרהסיה",
- "empty_column.home": "‏אף אחד לא במעקב עדיין. אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר חצוצרנים אחרים.",
- "empty_column.notifications": "‏אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב!",
- "empty_column.public": "‏אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות.",
- "follow_request.authorize": "קבלה",
- "follow_request.reject": "דחיה",
- "getting_started.apps": "קיים מבחר יישומונים לניידים",
- "getting_started.heading": "על ההתחלה",
- "getting_started.open_source_notice": "מסטודון היא תוכנה חופשית (בקוד פתוח). ניתן לתרום או לדווח על בעיות בגיטהאב: {github}. {apps}.",
- "home.column_settings.advanced": "למתקדמים",
- "home.column_settings.basic": "למתחילים",
- "home.column_settings.filter_regex": "‏סינון באמצעות ביטויים רגולריים (regular expressions)",
- "home.column_settings.show_reblogs": "הצגת הדהודים",
- "home.column_settings.show_replies": "הצגת תגובות",
- "home.settings": "הגדרות טור",
- "lightbox.close": "סגירה",
- "loading_indicator.label": "טוען...",
- "media_gallery.toggle_visible": "נראה\\בלתי נראה",
- "missing_indicator.label": "לא נמצא",
- "navigation_bar.blocks": "חסימות",
- "navigation_bar.community_timeline": "פיד מקומי",
- "navigation_bar.edit_profile": "עריכת פרופיל",
- "navigation_bar.favourites": "חיבובים",
- "navigation_bar.follow_requests": "בקשות מעקב",
- "navigation_bar.info": "מידע נוסף",
- "navigation_bar.logout": "יציאה",
- "navigation_bar.mutes": "השתקות",
- "navigation_bar.preferences": "העדפות",
- "navigation_bar.public_timeline": "בפרהסיה",
- "notification.favourite": "חצרוצך חובב על ידי {name}",
- "notification.follow": "{name} במעקב אחרייך",
- "notification.mention": 'אוזכרת ע"י {name}',
- "notification.reblog": "חצרוצך הודהד על ידי {name}",
- "notifications.clear": "הסרת התראות",
- "notifications.clear_confirmation": "‏להסיר את כל ההתראות? בטוח?",
- "notifications.column_settings.alert": "התראות לשולחן העבודה",
- "notifications.column_settings.favourite": "מחובבים:",
- "notifications.column_settings.follow": "עוקבים חדשים:",
- "notifications.column_settings.mention": "‏פניות:",
- "notifications.column_settings.reblog": "‏הדהודים:",
- "notifications.column_settings.show": "הצגה בטור",
- "notifications.column_settings.sound": "שמע מופעל",
- "notifications.settings": "הגדרות טור",
- "onboarding.done": "יציאה",
- "onboarding.next": "הלאה",
- "onboarding.page_five.public_timelines": "ציר הזמן המקומי מראה הודעות פומביות מכל באי קהילת {domain}. ציר הזמן העולמי מראה הודעות פומביות מאת כי מי שבאי קהילת {domain} עוקבים אחריו. אלו צירי הזמן הפומביים, דרך נהדרת לגלות אנשים חדשים.",
- "onboarding.page_four.home": "ציר זמן הבית מראה הודעות מהנעקבים שלך.",
- "onboarding.page_four.notifications": "טור ההתראות מראה כשמישהו מתייחס להודעות שלך.",
- "onboarding.page_one.federation": "מסטודון היא רשת של שרתים עצמאיים מצורפים ביחד לכדי רשת חברתית אחת גדולה. אנחנו מכנים את השרתים האלו: קהילות",
- "onboarding.page_one.handle": "אתם בקהילה {domain}, ולכן מזהה המשתמש המלא שלכם הוא {handle}",
- "onboarding.page_one.welcome": "ברוכים הבאים למסטודון!",
- "onboarding.page_six.admin": "הקהילה מנוהלת בידי {admin}.",
- "onboarding.page_six.almost_done": "כמעט סיימנו...",
- "onboarding.page_six.appetoot": "בתותאבון!",
- "onboarding.page_six.apps_available": "קיימים {apps} זמינים עבור אנדרואיד, אייפון ופלטפורמות נוספות.",
- "onboarding.page_six.github": "מסטודון הוא תוכנה חופשית. ניתן לדווח על באגים, לבקש יכולות, או לתרום לקוד באתר {github}.",
- "onboarding.page_six.guidelines": "חוקי הקהילה",
- "onboarding.page_six.read_guidelines": "‏נא לקרוא את {guidelines} של {domain}!",
- "onboarding.page_six.various_app": "יישומונים ניידים",
- "onboarding.page_three.profile": "ץתחת 'עריכת פרופיל' ניתן להחליף את תמונת הפרופיל שלך, תיאור קצר, והשם המוצג. שם גם ניתן למצוא אפשרויות והעדפות נוספות.",
- "onboarding.page_three.search": "בחלונית החיפוש ניתן לחפש אנשים והאשתגים, כמו למשל {illustration} או {introductions}. כדי למצוא מישהו שלא על האינסטנס המקומי, יש להשתמש בכינוי המשתמש המלא.",
- "onboarding.page_two.compose": "הודעות כותבים מטור הכתיבה. ניתן לנעלות תמונות, לשנות הגדרות פרטיות, ולהוסיף אזהרות תוכן בעזרת האייקונים שמתחת.",
- "onboarding.skip": "לדלג",
- "privacy.change": "שינוי פרטיות ההודעה",
- "privacy.direct.long": "הצג רק למי שהודעה זו פונה אליו",
- "privacy.direct.short": "הודעה ישירה",
- "privacy.private.long": "הצג לעוקבים מקומיים בלבד",
- "privacy.private.short": "לעוקבים בלבד",
- "privacy.public.long": "פרסם בפומבי",
- "privacy.public.short": "פומבי",
- "privacy.unlisted.long": "לא יופיע בפידים הציבוריים המשותפים",
- "privacy.unlisted.short": "לא לפיד הכללי",
- "reply_indicator.cancel": "ביטול",
- "report.heading": "דווח חדש",
- "report.placeholder": "הערות נוספות",
- "report.submit": "שליחה",
- "report.target": "דיווח",
- "search.placeholder": "חיפוש",
- "search.status_by": "הודעה מאת {name}",
- "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}",
- "status.cannot_reblog": "לא ניתן להדהד הודעה זו",
- "status.delete": "מחיקה",
- "status.favourite": "חיבוב",
- "status.load_more": "עוד",
- "status.media_hidden": "מדיה מוסתרת",
- "status.mention": "פניה אל @{name}",
- "status.open": "הרחבת הודעה",
- "status.reblog": "הדהוד",
- "status.reblogged_by": "הודהד על ידי {name}",
- "status.reply": "תגובה",
- "status.replyAll": "תגובה לכולם",
- "status.report": "דיווח על @{name}",
- "status.sensitive_warning": "תוכן רגיש",
- "status.sensitive_toggle": "לחצו כדי לראות",
- "status.show_less": "הראה פחות",
- "status.show_more": "הראה יותר",
- "tabs_bar.compose": "חיבור",
- "tabs_bar.federated_timeline": "בפדרציה",
- "tabs_bar.home": "בבית",
- "tabs_bar.local_timeline": "פיד מקומי",
- "tabs_bar.notifications": "התראות",
- "upload_area.title": "ניתן להעלות על ידי Drag & drop",
- "upload_button.label": "הוספת מדיה",
- "upload_form.undo": "ביטול",
- "upload_progress.label": "עולה...",
- "video_player.expand": "הרחבת וידאו",
- "video_player.toggle_sound": "הפעלת\\ביטול שמע",
- "video_player.toggle_visible": "הפעלת\\ביטול תצוגה",
- "video_player.video_error": "לא ניתן לנגן וידאו",
-};
-
-export default he;
diff --git a/app/assets/javascripts/components/locales/hr.jsx b/app/assets/javascripts/components/locales/hr.jsx
@@ -1,121 +0,0 @@
-const hr = {
- "account.block": "Blokiraj @{name}",
- "account.disclaimer": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.",
- "account.edit_profile": "Uredi profil",
- "account.follow": "Slijedi",
- "account.followers": "Sljedbenici",
- "account.follows_you": "te slijedi",
- "account.follows": "Slijedi",
- "account.mention": "Spomeni @{name}",
- "account.mute": "Utišaj @{name}",
- "account.posts": "Postovi",
- "account.report": "Prijavi @{name}",
- "account.requested": "Čeka pristanak",
- "account.unblock": "Deblokiraj @{name}",
- "account.unfollow": "Prestani slijediti",
- "account.unmute": "Poništi utišavanje @{name}",
- "boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put",
- "column_back_button.label": "Natrag",
- "column.blocks": "Blokirani korisnici",
- "column.community": "Lokalni timeline",
- "column.favourites": "Favoriti",
- "column.follow_requests": "Zahtjevi za slijeđenje",
- "column.home": "Dom",
- "column.notifications": "Notifikacije",
- "column.public": "Federalni timeline",
- "compose_form.placeholder": "Što ti je na umu?",
- "compose_form.privacy_disclaimer": "Tvoj privatni status će biti dostavljen spomenutim korisnicima na {domains}. Vjeruješ li {domainsCount, plural, one {that server} drugim {those servers}}? Privatnost postova radi samo na Mastodon instancama. Ako {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, neće biti indikacije da je tvoj post privatan, i mogao bit biti podignut ili biti učinjen vidljivim na drugi način neželjenim primateljima.",
- "compose_form.publish": "Toot",
- "compose_form.sensitive": "Označi media sadržaj kao osjetljiv",
- "compose_form.spoiler_placeholder": "Upozorenje o sadržaju",
- "compose_form.spoiler": "Sakrij text iza upozorenja",
- "emoji_button.label": "Umetni smajlije",
- "empty_column.community": "Lokalni timeline je prazan. Napiši nešto javno kako bi pokrenuo stvari!",
- "empty_column.hashtag": "Još ne postoji ništa s ovim hashtagom.",
- "empty_column.home.public_timeline": "javni timeline",
- "empty_column.home": "Još ne slijediš nikoga. Posjeti {public} ili koristi tražilicu kako bi počeo i upoznao druge korisnike.",
- "empty_column.notifications": "Još nemaš notifikacija. Komuniciraj sa drugima kako bi započeo razgovor.",
- "empty_column.public": "Ovdje nema ništa! Napiši nešto javno, ili ručno slijedi korisnike sa drugih instanci kako bi popunio",
- "follow_request.authorize": "Authoriziraj",
- "follow_request.reject": "Odbij",
- "getting_started.apps": "Dostupne su razne aplikacije",
- "getting_started.heading": "Počnimo",
- "getting_started.open_source_notice": "Mastodon je softver otvorenog koda. Možeš pridonijeti ili prijaviti probleme na GitHubu {github}. {apps}.",
- "home.column_settings.advanced": "Napredno",
- "home.column_settings.basic": "Osnovno",
- "home.column_settings.filter_regex": "Filtriraj s regularnim izrazima",
- "home.column_settings.show_reblogs": "Pokaži boosts",
- "home.column_settings.show_replies": "Pokaži odgovore",
- "home.settings": "Postavke Stupca",
- "lightbox.close": "Zatvori",
- "loading_indicator.label": "Učitavam...",
- "media_gallery.toggle_visible": "Preklopi vidljivost",
- "missing_indicator.label": "Nije nađen",
- "navigation_bar.blocks": "Blokirani korisnici",
- "navigation_bar.community_timeline": "Lokalni timeline",
- "navigation_bar.edit_profile": "Uredi profil",
- "navigation_bar.favourites": "Favoriti",
- "navigation_bar.follow_requests": "Zahtjevi za sljeđenje",
- "navigation_bar.info": "Proširena informacija",
- "navigation_bar.logout": "Odjavi se",
- "navigation_bar.preferences": "Postavke",
- "navigation_bar.public_timeline": "Federalni timeline",
- "notification.favourite": "{name} je lajkao tvoj status",
- "notification.follow": "{name} te sada slijedi",
- "notification.reblog": "{name} je podigao tvoj status",
- "notifications.clear_confirmation": "Želiš li zaista obrisati sve svoje notifikacije?",
- "notifications.clear": "Očisti notifikacije",
- "notifications.column_settings.alert": "Desktop notifikacije",
- "notifications.column_settings.favourite": "Favoriti:",
- "notifications.column_settings.follow": "Novi sljedbenici:",
- "notifications.column_settings.mention": "Spominjanja:",
- "notifications.column_settings.reblog": "Boosts:",
- "notifications.column_settings.show": "Prikaži u stupcu",
- "notifications.column_settings.sound": "Sviraj zvuk",
- "notifications.settings": "Postavke rubrike",
- "privacy.change": "Podesi status privatnosti",
- "privacy.direct.long": "Prikaži samo spomenutim korisnicima",
- "privacy.direct.short": "Direktno",
- "privacy.private.long": "Prikaži samo sljedbenicima",
- "privacy.private.short": "Privatno",
- "privacy.public.long": "Postaj na javne timeline",
- "privacy.public.short": "Javno",
- "privacy.unlisted.long": "Ne prikazuj u javnim timelineovima",
- "privacy.unlisted.short": "Unlisted",
- "reply_indicator.cancel": "Otkaži",
- "report.heading": "Nova prijava",
- "report.placeholder": "Dodatni komentari",
- "report.submit": "Pošalji",
- "report.target": "Prijavljivanje",
- "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
- "search.placeholder": "Traži",
- "search.status_by": "Status od {name}",
- "status.delete": "Obriši",
- "status.favourite": "Označi omiljenim",
- "status.load_more": "Učitaj više",
- "status.media_hidden": "Sakriven media sadržaj",
- "status.mention": "Spomeni @{name}",
- "status.open": "Proširi ovaj status",
- "status.reblog": "Podigni",
- "status.reblogged_by": "{name} je podigao",
- "status.reply": "Odgovori",
- "status.report": "Prijavi @{name}",
- "status.sensitive_toggle": "Klikni da bi vidio",
- "status.sensitive_warning": "Osjetljiv sadržaj",
- "status.show_less": "Pokaži manje",
- "status.show_more": "Pokaži više",
- "tabs_bar.compose": "Sastavi",
- "tabs_bar.federated_timeline": "Federalni",
- "tabs_bar.home": "Dom",
- "tabs_bar.local_timeline": "Lokalno",
- "tabs_bar.notifications": "Notifikacije",
- "upload_area.title": "Povuci & spusti kako bi uploadao",
- "upload_button.label": "Dodaj media",
- "upload_form.undo": "Poništi",
- "upload_progress.label": "Uploadam...",
- "video_player.toggle_sound": "Toggle zvuk",
- "video_player.toggle_visible": "Preklopi vidljivost",
- "video_player.expand": "Proširi video",
-};
-
-export default hr;
diff --git a/app/assets/javascripts/components/locales/hu.jsx b/app/assets/javascripts/components/locales/hu.jsx
@@ -1,57 +0,0 @@
-const hu = {
- "column_back_button.label": "Vissza",
- "lightbox.close": "Bezárás",
- "loading_indicator.label": "Betöltés...",
- "status.mention": "Említés",
- "status.delete": "Törlés",
- "status.reply": "Válasz",
- "status.reblog": "Reblog",
- "status.favourite": "Kedvenc",
- "status.reblogged_by": "{name} reblogolta",
- "status.sensitive_warning": "Érzékeny tartalom",
- "status.sensitive_toggle": "Katt a megtekintéshez",
- "video_player.toggle_sound": "Hang kapcsolása",
- "account.mention": "Említés",
- "account.edit_profile": "Profil szerkesztése",
- "account.unblock": "Blokkolás levétele",
- "account.unfollow": "Követés abbahagyása",
- "account.block": "Blokkolás",
- "account.follow": "Követés",
- "account.posts": "Posts",
- "account.follows": "Követve",
- "account.followers": "Követők",
- "account.follows_you": "Követnek téged",
- "getting_started.heading": "Első lépések",
- "getting_started.about_addressing": "Követhetsz embereket felhasználónevük és a doménjük ismeretében, amennyiben megadod ezt az e-mail-szerű címet az oldalsáv tetején lévő rubrikában.",
- "getting_started.about_shortcuts": "Ha a célzott személy azonos doménen tartózkodik, a felhasználónév elegendő. Ugyanez érvényes mikor személyeket említesz az állapotokban.",
- "getting_started.about_developer": "A projekt fejlesztője követhető, mint Gargron@mastodon.social",
- "column.home": "Kezdőlap",
- "column.mentions": "Említések",
- "column.public": "Nyilvános",
- "column.notifications": "Értesítések",
- "tabs_bar.compose": "Összeállítás",
- "tabs_bar.home": "Kezdőlap",
- "tabs_bar.mentions": "Említések",
- "tabs_bar.public": "Nyilvános",
- "tabs_bar.notifications": "Notifications",
- "compose_form.placeholder": "Mire gondolsz?",
- "compose_form.publish": "Tülk!",
- "compose_form.sensitive": "Tartalom érzékenynek jelölése",
- "compose_form.unlisted": "Listázatlan mód",
- "navigation_bar.edit_profile": "Profil szerkesztése",
- "navigation_bar.preferences": "Beállítások",
- "navigation_bar.public_timeline": "Nyilvános időfolyam",
- "navigation_bar.logout": "Kijelentkezés",
- "reply_indicator.cancel": "Mégsem",
- "search.placeholder": "Keresés",
- "search.account": "Fiók",
- "search.hashtag": "Hashtag",
- "upload_button.label": "Média hozzáadása",
- "upload_form.undo": "Mégsem",
- "notification.follow": "{name} követ téged",
- "notification.favourite": "{name} kedvencnek jelölte az állapotod",
- "notification.reblog": "{name} reblogolta az állapotod",
- "notification.mention": "{name} megemlített"
-};
-
-export default hu;
diff --git a/app/assets/javascripts/components/locales/id.jsx b/app/assets/javascripts/components/locales/id.jsx
@@ -1,167 +0,0 @@
-const id = {
- "account.block": "Blokir @{name}",
- "account.disclaimer": "Pengguna ini berasal dari server lain. Angka berikut mungkin lebih besar.",
- "account.edit_profile": "Ubah profil",
- "account.follow": "Ikuti",
- "account.followers": "Pengikut",
- "account.follows_you": "Mengikuti anda",
- "account.follows": "Mengikuti",
- "account.mention": "Balasan @{name}",
- "account.mute": "Bisukan @{name}",
- "account.posts": "Postingan",
- "account.report": "Laporkan @{name}",
- "account.requested": "Menunggu persetujuan",
- "account.unblock": "Hapus blokir @{name}",
- "account.unfollow": "Berhenti mengikuti",
- "account.unmute": "Berhenti membisukan @{name}",
- "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini",
- "column.blocks": "Pengguna diblokir",
- "column.community": "Linimasa Lokal",
- "column.favourites": "Favorit",
- "column.follow_requests": "Permintaan mengikuti",
- "column.home": "Beranda",
- "column.mutes": "Pengguna dibisukan",
- "column.notifications": "Notifikasi",
- "column.public": "Linimasa gabunggan",
- "column_back_button.label": "Kembali",
- "column_subheading.navigation": "Navigasi",
- "column_subheading.settings": "Pengaturan",
- "compose_form.lock_disclaimer": "Akun anda tidak {locked}. Semua orang dapat mengikuti anda untuk melihat postingan khusus untuk pengikut anda.",
- "compose_form.lock_disclaimer.lock": "dikunci",
- "compose_form.placeholder": "Apa yang ada di pikiran anda?",
- "compose_form.privacy_disclaimer": "Status pribadi anda akan dikirim ke pengguna yang disebut dalam {domains}. Apa anda mempercayai {domainsCount, plural, one {server tersebut} other {server tersebut}}? Privasi postingan hanya bekerja dalam server Mastodon. Jika {domains} {domainsCount, plural, one {bukan server Mastodon} other {bukan server Mastodon}}, akan ada indikasi bahwa postingan anda adalah postingan pribadi, dan dapat di-boost atau dapat dilihat oleh orang lain.",
- "compose_form.publish": "Toot",
- "compose_form.sensitive": "Tandai media sensitif",
- "compose_form.spoiler": "Sembunyikan teks dibalik peringatan",
- "compose_form.spoiler_placeholder": "Peringatan konten",
- "confirmation_modal.cancel": "Batal",
- "confirmations.block.confirm": "Blokir",
- "confirmations.block.message": "Apa anda yakin ingin memblokir {name}?",
- "confirmations.delete.confirm": "Hapus",
- "confirmations.delete.message": "Apa anda yakin akan menghapus status ini?",
- "confirmations.mute.confirm": "Bisukan",
- "confirmations.mute.message": "Apa anda yakin ingin membisukan {name}?",
- "emoji_button.activity": "Aktivitas",
- "emoji_button.flags": "Bendera",
- "emoji_button.food": "Makanan & Minuman",
- "emoji_button.label": "Tambahkan emoji",
- "emoji_button.nature": "Alam",
- "emoji_button.objects": "Benda-benda",
- "emoji_button.people": "Orang",
- "emoji_button.search": "Cari...",
- "emoji_button.symbols": "Simbol",
- "emoji_button.travel": "Tempat Wisata",
- "empty_column.community": "Linimasa lokal masih kosong. Tulis sesuatu secara publik dan buat roda berputar!",
- "empty_column.hashtag": "Tidak ada apapun dalam hashtag ini.",
- "empty_column.home.public_timeline": "linimasa publik",
- "empty_column.home": "Anda sedang tidak mengikuti siapapun. Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.",
- "empty_column.notifications": "Anda tidak memiliki notifikasi apapun. Berinteraksi dengan orang lain untuk memulai percakapan.",
- "empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisinya secara manual",
- "follow_request.authorize": "Izinkan",
- "follow_request.reject": "Tolak",
- "getting_started.apps": "Tersedia dalam berbagai aplikasi",
- "getting_started.heading": "Mulai",
- "getting_started.open_source_notice": "Mastodon adalah perangkat lunak yang bersifat open source. Anda dapat berkontribusi atau melaporkan permasalahan/bug di Github {github}. {apps}.",
- "home.column_settings.advanced": "Tingkat Lanjut",
- "home.column_settings.basic": "Dasar",
- "home.column_settings.filter_regex": "Penyaringan dengan Regular Expression",
- "home.column_settings.show_reblogs": "Tampilkan Boost",
- "home.column_settings.show_replies": "Tampilkan balasan",
- "home.settings": "Pengaturan kolom",
- "lightbox.close": "Tutup",
- "loading_indicator.label": "Tunggu sebentar...",
- "media_gallery.toggle_visible": "Tampil/Sembunyikan",
- "missing_indicator.label": "Tidak ditemukan",
- "navigation_bar.blocks": "Pengguna diblokir",
- "navigation_bar.community_timeline": "Linimasa lokal",
- "navigation_bar.edit_profile": "Ubah profil",
- "navigation_bar.favourites": "Favorit",
- "navigation_bar.follow_requests": "Permintaan mengikuti",
- "navigation_bar.info": "Informasi selengkapnya",
- "navigation_bar.logout": "Keluar",
- "navigation_bar.mutes": "Pengguna dibisukan",
- "navigation_bar.preferences": "Pengaturan",
- "navigation_bar.public_timeline": "Linimasa gabungan",
- "notification.favourite": "{name} menyukai status anda",
- "notification.follow": "{name} mengikuti anda",
- "notification.reblog": "{name} mem-boost status anda",
- "notifications.clear": "Hapus notifikasi",
- "notifications.clear_confirmation": "Apa anda yakin hendak menghapus semua notifikasi anda?",
- "notifications.column_settings.alert": "Notifikasi desktop",
- "notifications.column_settings.favourite": "Favorit:",
- "notifications.column_settings.follow": "Pengikut baru:",
- "notifications.column_settings.mention": "Balasan:",
- "notifications.column_settings.reblog": "Boost:",
- "notifications.column_settings.show": "Tampilkan dalam kolom",
- "notifications.column_settings.sound": "Mainkan suara",
- "notifications.settings": "Pengaturan kolom",
- "onboarding.done": "Selesei",
- "onboarding.next": "Selanjutnya",
- "onboarding.page_five.public_timelines": "Linimasa lokal menampilkan semua postingan publik dari semua orang di {domain}. Linimasa gabungan menampilkan postingan publik dari semua orang yang diikuti oleh {domain}. Ini semua adalah Linimasa Publik, cara terbaik untuk bertemu orang lain.",
- "onboarding.page_four.home": "Linimasa beranda menampilkan postingan dari orang-orang yang anda ikuti.",
- "onboarding.page_four.notifications": "Kolom notifikasi menampilkan ketika seseorang berinteraksi dengan anda.",
- "onboarding.page_one.federation": "Mastodon adalah jaringan dari beberapa server independen yang bergabung untuk membuat jejaring sosial yang besar.",
- "onboarding.page_one.handle": "Ada berada dalam {domain}, jadi nama user lengkap anda adalah {handle}",
- "onboarding.page_one.welcome": "Selamat datang di Mastodon!",
- "onboarding.page_six.admin": "Admin serveer anda adalah {admin}.",
- "onboarding.page_six.almost_done": "Hampir selesei...",
- "onboarding.page_six.appetoot": "Bon Appetoot!",
- "onboarding.page_six.apps_available": "Ada beberapa apl yang tersedia untuk iOS, Android, dan platform lainnya.",
- "onboarding.page_six.github": "Mastodon adalah software open-source. Anda bisa melaporkan bug, meminta fitur, atau berkontribusi dengan kode di {github}.",
- "onboarding.page_six.guidelines": "pedoman komunitas",
- "onboarding.page_six.read_guidelines": "Silakan baca {guidelines} {domain}!",
- "onboarding.page_six.various_app": "apl handphone",
- "onboarding.page_three.profile": "Ubah profil anda untuk mengganti avatar, bio, dan nama pengguna anda. Disitu, anda juga bisa mengatur opsi lainnya.",
- "onboarding.page_three.search": "Gunakan kolom pencarian untuk mencari orang atau melihat hashtag, seperti {illustration} dan {introductions}. Untuk mencari pengguna yang tidak berada dalam server ini, gunakan nama pengguna mereka selengkapnya.",
- "onboarding.page_two.compose": "Tulis postingan melalui kolom posting. Anda dapat mengunggah gambar, mengganti pengaturan privasi, dan menambahkan peringatan konten dengan ikon-ikon dibawah ini.",
- "onboarding.skip": "Lewati",
- "privacy.change": "Tentukan privasi status",
- "privacy.direct.long": "Kirim hanya ke pengguna yang disebut",
- "privacy.direct.short": "Langsung",
- "privacy.private.long": "Kirim hanya ke pengikut",
- "privacy.private.short": "Pribadi",
- "privacy.public.long": "Kirim ke linimasa publik",
- "privacy.public.short": "Publik",
- "privacy.unlisted.long": "Tidak ditampilkan di linimasa publik",
- "privacy.unlisted.short": "Tak Terdaftar",
- "reply_indicator.cancel": "Batal",
- "report.heading": "Laporan baru",
- "report.placeholder": "Komentar tambahan",
- "report.submit": "Kirim",
- "report.target": "Melaporkan",
- "search.status_by": "Status yang dibuat oleh {name}",
- "search_results.total": "{count} {count, plural, one {hasil} other {hasil}}",
- "status.cannot_reblog": "Postingan ini tidak dapat di-boost",
- "search.placeholder": "Pencarian",
- "search.status_by": "Status oleh {name}",
- "status.delete": "Hapus",
- "status.favourite": "Difavoritkan",
- "status.load_more": "Tampilkan semua",
- "status.media_hidden": "Media disembunyikan",
- "status.mention": "Balasan @{name}",
- "status.open": "Tampilkan status ini",
- "status.reblog": "Boost",
- "status.reblogged_by": "di-boost {name}",
- "status.reply": "Balas",
- "status.replyAll": "Balas ke semua",
- "status.report": "Laporkan @{name}",
- "status.sensitive_toggle": "Klik untuk menampilkan",
- "status.sensitive_warning": "Konten sensitif",
- "status.show_less": "Tampilkan lebih sedikit",
- "status.show_more": "Tampilkan semua",
- "tabs_bar.compose": "Tulis",
- "tabs_bar.federated_timeline": "Gabungan",
- "tabs_bar.home": "Beranda",
- "tabs_bar.local_timeline": "Lokal",
- "tabs_bar.notifications": "Notifikasi",
- "upload_area.title": "Seret & lepaskan untuk mengunggah",
- "upload_button.label": "Tambahkan media",
- "upload_form.undo": "Undo",
- "upload_progress.label": "Mengunggah...",
- "video_player.toggle_sound": "Suara",
- "video_player.toggle_visible": "Tampilan",
- "video_player.expand": "Tampilkan video",
- "video_player.video_error": "Video tidak dapat diputar",
-};
-
-export default id;
diff --git a/app/assets/javascripts/components/locales/index.jsx b/app/assets/javascripts/components/locales/index.jsx
@@ -1,57 +0,0 @@
-import ar from './ar';
-import en from './en';
-import de from './de';
-import es from './es';
-import fa from './fa';
-import he from './he';
-import hr from './hr';
-import hu from './hu';
-import io from './io';
-import it from './it';
-import fr from './fr';
-import nl from './nl';
-import no from './no';
-import oc from './oc';
-import pt from './pt';
-import pt_br from './pt-br';
-import uk from './uk';
-import fi from './fi';
-import eo from './eo';
-import ru from './ru';
-import ja from './ja';
-import zh_hk from './zh-hk';
-import zh_cn from './zh-cn';
-import bg from './bg';
-import id from './id';
-
-const locales = {
- ar,
- en,
- de,
- es,
- fa,
- he,
- hr,
- hu,
- io,
- it,
- fr,
- nl,
- no,
- oc,
- pt,
- 'pt-BR': pt_br,
- uk,
- fi,
- eo,
- ru,
- ja,
- 'zh-HK': zh_hk,
- 'zh-CN': zh_cn,
- bg,
- id,
-};
-
-export default function getMessagesForLocale (locale) {
- return locales[locale];
-};
diff --git a/app/assets/javascripts/components/locales/io.jsx b/app/assets/javascripts/components/locales/io.jsx
@@ -1,126 +0,0 @@
-const io = {
- "account.block": "Blokusar @{name}",
- "account.disclaimer": "Ca uzero esas de altra instaluro. Ca nombro forsan esas plu granda.",
- "account.edit_profile": "Modifikar profilo",
- "account.follow": "Sequar",
- "account.followers": "Sequanti",
- "account.follows_you": "Sequas tu",
- "account.follows": "Sequas",
- "account.mention": "Mencionar @{name}",
- "account.mute": "Celar @{name}",
- "account.posts": "Mesaji",
- "account.report": "Denuncar @{name}",
- "account.requested": "Vartante aprobo",
- "account.unblock": "Desblokusar @{name}",
- "account.unfollow": "Ne plus sequar",
- "account.unmute": "Ne plus celar @{name}",
- "boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo",
- "column_back_button.label": "Retro",
- "column.blocks": "Blokusita uzeri",
- "column.community": "Lokala tempolineo",
- "column.favourites": "Favorati",
- "column.follow_requests": "Demandi di sequado",
- "column.home": "Hemo",
- "column.mutes": "Celita uzeri",
- "column.notifications": "Savigi",
- "column.public": "Federata tempolineo",
- "compose_form.placeholder": "Quo esas en tua spirito?",
- "compose_form.privacy_disclaimer": "Tua privata mesajo livresos a mencionata uzeri en {domains}. Ka tu fidas {domainsCount, plural, one {ta servero} other {ta serveri}}? Privateso di mesaji funcionas nur en instaluri di Mastodon. Se {domains} {domainsCount, plural, one {ne esas instaluro di Mastodon} other {ne esas instaluri di Mastodon}}, esos nula indiko, ke tua mesajo esas privata, ed ol povos repetesar od altre divenar videbla da nedezirinda recevanti.",
- "compose_form.publish": "Siflar",
- "compose_form.sensitive": "Markizar kontenajo kom trubliva",
- "compose_form.spoiler_placeholder": "Averto di kontenajo",
- "compose_form.spoiler": "Celar texto dop averto",
- "emoji_button.label": "Insertar emoji",
- "empty_column.community": "La lokala tempolineo esas vakua. Skribez ulo publike por iniciar la agiveso!",
- "empty_column.hashtag": "Esas ankore nulo en ta gretovorto.",
- "empty_column.home.public_timeline": "la publika tempolineo",
- "empty_column.home": "Tu sequas ankore nulu. Vizitez {public} od uzez la serchilo por komencar e renkontrar altra uzeri.",
- "empty_column.notifications": "Tu havas ankore nula savigo. Komunikez kun altri por debutar la konverso.",
- "empty_column.public": "Esas nulo hike! Skribez ulo publike, o manuale sequez uzeri de altra instaluri por plenigar ol.",
- "follow_request.authorize": "Yurizar",
- "follow_request.reject": "Refuzar",
- "getting_started.apps": "Apliki diversa esas disponebla",
- "getting_started.heading": "Debuto",
- "getting_started.open_source_notice": "Mastodon esas programaro kun apertita kodexo. Tu povas kontributar o signalar problemi en GitHub ye {github}. {apps}.",
- "home.column_settings.advanced": "Komplexa",
- "home.column_settings.basic": "Simpla",
- "home.column_settings.filter_regex": "Ekfiltrar per reguloza expresuri",
- "home.column_settings.show_reblogs": "Montrar repeti",
- "home.column_settings.show_replies": "Montrar respondi",
- "home.settings": "Aranji di la kolumno",
- "lightbox.close": "Klozar",
- "loading_indicator.label": "Kargante...",
- "media_gallery.toggle_visible": "Chanjar videbleso",
- "missing_indicator.label": "Ne trovita",
- "navigation_bar.blocks": "Blokusita uzeri",
- "navigation_bar.community_timeline": "Lokala tempolineo",
- "navigation_bar.edit_profile": "Modifikar profilo",
- "navigation_bar.favourites": "Favorati",
- "navigation_bar.follow_requests": "Demandi di sequado",
- "navigation_bar.info": "Detaloza informi",
- "navigation_bar.logout": "Ekirar",
- "navigation_bar.mutes": "Celita uzeri",
- "navigation_bar.preferences": "Preferi",
- "navigation_bar.public_timeline": "Federata tempolineo",
- "notification.favourite": "{name} favorizis tua mesajo",
- "notification.follow": "{name} sequeskis tu",
- "notification.mention": "{name} mencionis tu",
- "notification.reblog": "{name} repetis tua mesajo",
- "notifications.clear_confirmation": "Ka tu esas certa, ke tu volas efacar omna tua savigi?",
- "notifications.clear": "Efacar savigi",
- "notifications.column_settings.alert": "Surtabla savigi",
- "notifications.column_settings.favourite": "Favorati:",
- "notifications.column_settings.follow": "Nova sequanti:",
- "notifications.column_settings.mention": "Mencioni:",
- "notifications.column_settings.reblog": "Repeti:",
- "notifications.column_settings.show": "Montrar en kolumno",
- "notifications.column_settings.sound": "Plear sono",
- "notifications.settings": "Aranji di kolumno",
- "privacy.change": "Aranjar privateso di mesaji",
- "privacy.direct.long": "Sendar nur a mencionata uzeri",
- "privacy.direct.short": "Direte",
- "privacy.private.long": "Sendar nur a sequanti",
- "privacy.private.short": "Private",
- "privacy.public.long": "Sendar a publika tempolinei",
- "privacy.public.short": "Publike",
- "privacy.unlisted.long": "Ne montrar en publika tempolinei",
- "privacy.unlisted.short": "Ne enlistigota",
- "reply_indicator.cancel": "Nihiligar",
- "report.heading": "Nova denunco",
- "report.placeholder": "Plusa komenti",
- "report.submit": "Sendar",
- "report.target": "Denuncante",
- "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}",
- "search.placeholder": "Serchez",
- "search.status_by": "Mesajo da {name}",
- "status.delete": "Efacar",
- "status.favourite": "Favorizar",
- "status.load_more": "Kargar pluse",
- "status.media_hidden": "Kontenajo celita",
- "status.mention": "Mencionar @{name}",
- "status.open": "Detaligar ca mesajo",
- "status.reblog": "Repetar",
- "status.reblogged_by": "{name} repetita",
- "status.reply": "Respondar",
- "status.replyAll": "Respondar a filo",
- "status.report": "Denuncar @{name}",
- "status.sensitive_toggle": "Kliktar por vidar",
- "status.sensitive_warning": "Trubliva kontenajo",
- "status.show_less": "Montrar mine",
- "status.show_more": "Montrar plue",
- "tabs_bar.compose": "Kompozar",
- "tabs_bar.federated_timeline": "Federata",
- "tabs_bar.home": "Hemo",
- "tabs_bar.local_timeline": "Lokala",
- "tabs_bar.notifications": "Savigi",
- "upload_area.title": "Tranar faligar por kargar",
- "upload_button.label": "Adjuntar kontenajo",
- "upload_form.undo": "Desfacar",
- "upload_progress.label": "Kargante...",
- "video_player.toggle_sound": "Acendar sono",
- "video_player.toggle_visible": "Chanjar videbleso",
- "video_player.expand": "Extensar video",
- "video_player.video_error": "Video ne povus pleesar",
-};
-
-export default io;
diff --git a/app/assets/javascripts/components/locales/it.jsx b/app/assets/javascripts/components/locales/it.jsx
@@ -1,125 +0,0 @@
-const it = {
- "account.block": "Blocca @{name}",
- "account.disclaimer": "Questo utente si trova su un altro server. Questo numero potrebbe essere maggiore.",
- "account.edit_profile": "Modifica profilo",
- "account.follow": "Segui",
- "account.followers": "Seguaci",
- "account.follows_you": "Ti segue",
- "account.follows": "Segue",
- "account.mention": "Menziona @{name}",
- "account.mute": "Silenzia @{name}",
- "account.posts": "Posts",
- "account.report": "Segnala @{name}",
- "account.requested": "In attesa di approvazione",
- "account.unblock": "Sblocca @{name}",
- "account.unfollow": "Non seguire",
- "account.unmute": "Non silenziare @{name}",
- "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta",
- "column_back_button.label": "Indietro",
- "column.blocks": "Utenti bloccati",
- "column.community": "Timeline locale",
- "column.favourites": "Apprezzati",
- "column.follow_requests": "Richieste di amicizia",
- "column.home": "Home",
- "column.mutes": "Utenti silenziati",
- "column.notifications": "Notifiche",
- "column.public": "Timeline federata",
- "compose_form.placeholder": "A cosa stai pensando?",
- "compose_form.privacy_disclaimer": "Il tuo status privato verrà condiviso con gli utenti menzionati su {domains}. Ti fidi di {domainsCount, plural, one {quel server} other {quei server}}? Le impostazioni sulla privacy valgono solo su server Mastodon. Se {domains} {domainsCount, plural, one {non è un server Mastodon} other {non sono server Mastodon}}, non ci saranno indicazioni sulla privacy del tuo status, e potrebbe essere condiviso o reso visibile a destinatari indesiderati.",
- "compose_form.publish": "Toot",
- "compose_form.sensitive": "Segnala file come sensibile",
- "compose_form.spoiler_placeholder": "Content warning",
- "compose_form.spoiler": "Nascondi testo con avvertimento",
- "emoji_button.label": "Inserisci emoji",
- "empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!",
- "empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.",
- "empty_column.home.public_timeline": "la timeline pubblica",
- "empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.",
- "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.",
- "empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio.",
- "follow_request.authorize": "Autorizza",
- "follow_request.reject": "Rifiuta",
- "getting_started.apps": "Sono disponibili diverse app",
- "getting_started.heading": "Come iniziare",
- "getting_started.open_source_notice": "Mastodon è un software open source. Puoi contribuire o segnalare errori su GitHub all'indirizzo {github}. {apps}.",
- "home.column_settings.advanced": "Avanzato",
- "home.column_settings.basic": "Semplice",
- "home.column_settings.filter_regex": "Filtra con espressioni regolari",
- "home.column_settings.show_reblogs": "Mostra post condivisi",
- "home.column_settings.show_replies": "Mostra risposte",
- "home.settings": "Impostazioni colonna",
- "lightbox.close": "Chiudi",
- "loading_indicator.label": "Carico...",
- "media_gallery.toggle_visible": "Imposta visibilità",
- "missing_indicator.label": "Non trovato",
- "navigation_bar.blocks": "Utenti bloccati",
- "navigation_bar.community_timeline": "Timeline locale",
- "navigation_bar.edit_profile": "Modifica profilo",
- "navigation_bar.favourites": "Apprezzati",
- "navigation_bar.follow_requests": "Richieste di amicizia",
- "navigation_bar.info": "Informazioni estese",
- "navigation_bar.logout": "Logout",
- "navigation_bar.mutes": "Utenti silenziati",
- "navigation_bar.preferences": "Impostazioni",
- "navigation_bar.public_timeline": "Timeline federata",
- "notification.favourite": "{name} ha apprezzato il tuo post",
- "notification.follow": "{name} ha iniziato a seguirti",
- "notification.mention": "{name} ti ha menzionato",
- "notification.reblog": "{name} ha condiviso il tuo post",
- "notifications.clear_confirmation": "Vuoi davvero cancellare tutte le notifiche?",
- "notifications.clear": "Cancella notifiche",
- "notifications.column_settings.alert": "Notifiche desktop",
- "notifications.column_settings.favourite": "Apprezzati:",
- "notifications.column_settings.follow": "Nuovi seguaci:",
- "notifications.column_settings.mention": "Menzioni:",
- "notifications.column_settings.reblog": "Post condivisi:",
- "notifications.column_settings.show": "Mostra in colonna",
- "notifications.column_settings.sound": "Riproduci suono",
- "notifications.settings": "Impostazioni colonna",
- "privacy.change": "Modifica privacy post",
- "privacy.direct.long": "Invia solo a utenti menzionati",
- "privacy.direct.short": "Diretto",
- "privacy.private.long": "Invia solo ai seguaci",
- "privacy.private.short": "Privato",
- "privacy.public.long": "Invia alla timeline pubblica",
- "privacy.public.short": "Pubblico",
- "privacy.unlisted.long": "Non mostrare sulla timeline pubblica",
- "privacy.unlisted.short": "Non elencato",
- "reply_indicator.cancel": "Annulla",
- "report.heading": "Nuova segnalazione",
- "report.placeholder": "Commenti aggiuntivi",
- "report.submit": "Invia",
- "report.target": "Invio la segnalazione",
- "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}",
- "search.placeholder": "Cerca",
- "search.status_by": "Status per {name}",
- "status.delete": "Elimina",
- "status.favourite": "Apprezzato",
- "status.load_more": "Mostra di più",
- "status.media_hidden": "Allegato nascosto",
- "status.mention": "Nomina @{name}",
- "status.open": "Espandi questo post",
- "status.reblog": "Condividi",
- "status.reblogged_by": "{name} ha condiviso",
- "status.reply": "Rispondi",
- "status.report": "Segnala @{name}",
- "status.sensitive_toggle": "Clicca per vedere",
- "status.sensitive_warning": "Materiale sensibile",
- "status.show_less": "Mostra meno",
- "status.show_more": "Mostra di più",
- "tabs_bar.compose": "Scrivi",
- "tabs_bar.federated_timeline": "Federazione",
- "tabs_bar.home": "Home",
- "tabs_bar.local_timeline": "Locale",
- "tabs_bar.notifications": "Notifiche",
- "upload_area.title": "Trascina per caricare",
- "upload_button.label": "Aggiungi file multimediale",
- "upload_form.undo": "Annulla",
- "upload_progress.label": "Sto caricando...",
- "video_player.toggle_sound": "Attiva suono",
- "video_player.toggle_visible": "Attiva visibilità",
- "video_player.expand": "Espandi video",
- "video_player.video_error": "Il video non può essere riprodotto",
-};
-
-export default it;-
\ No newline at end of file
diff --git a/app/assets/javascripts/components/locales/ja.jsx b/app/assets/javascripts/components/locales/ja.jsx
@@ -1,167 +0,0 @@
-const ja = {
- "account.block": "ブロック",
- "account.disclaimer": "このユーザーは他のインスタンスに所属しているため、数字が正確で無い場合があります。",
- "account.edit_profile": "プロフィールを編集",
- "account.follow": "フォロー",
- "account.followers": "フォロワー",
- "account.follows": "フォロー",
- "account.follows_you": "フォローされています",
- "account.mention": "返信",
- "account.mute": "ミュート",
- "account.posts": "投稿",
- "account.report": "通報",
- "account.requested": "承認待ち",
- "account.unblock": "ブロック解除",
- "account.unfollow": "フォロー解除",
- "account.unmute": "ミュート解除",
- "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。",
- "column.blocks": "ブロックしたユーザー",
- "column.community": "ローカルタイムライン",
- "column.favourites": "お気に入り",
- "column.follow_requests": "フォローリクエスト",
- "column.home": "ホーム",
- "column.mutes": "ミュートしたユーザー",
- "column.notifications": "通知",
- "column.public": "連合タイムライン",
- "column_back_button.label": "戻る",
- "column_subheading.navigation": "ナビゲーション",
- "column_subheading.settings": "設定",
- "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。",
- "compose_form.lock_disclaimer.lock": "非公開",
- "compose_form.placeholder": "今なにしてる?",
- "compose_form.privacy_disclaimer": "あなたの非公開トゥートは返信先ユーザーが所属する {domains} に送信されます。{domainsCount, plural, one {このサーバー} other {これらのサーバー}}は信頼できますか?投稿のプライバシー保護はMastodonサーバー内でのみ有効です。 {domains} {domainsCount, plural, one {がMastodonインスタンス} other {がMastodonインスタンス}}でない場合、あなたの投稿がプライベートなものとして扱われず、ブーストされたり予期しないユーザーに見られる可能性があります。",
- "compose_form.publish": "トゥート",
- "compose_form.sensitive": "メディアを閲覧注意としてマークする",
- "compose_form.spoiler": "テキストを隠す",
- "compose_form.spoiler_placeholder": "警告",
- "confirmation_modal.cancel": "キャンセル",
- "confirmations.block.confirm": "ブロック",
- "confirmations.block.message": "本当に {name} をブロックしますか?",
- "confirmations.delete.confirm": "削除",
- "confirmations.delete.message": "本当に削除しますか?",
- "confirmations.mute.confirm": "ミュート",
- "confirmations.mute.message": "本当に {name} をミュートしますか?",
- "emoji_button.label": "絵文字を追加",
- "emoji_button.search": "検索...",
- "emoji_button.people": "人々",
- "emoji_button.nature": "自然",
- "emoji_button.food": "食べ物",
- "emoji_button.activity": "活動",
- "emoji_button.travel": "旅行と場所",
- "emoji_button.objects": "物",
- "emoji_button.symbols": "記号",
- "emoji_button.flags": "国旗",
- "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!",
- "empty_column.hashtag": "このハッシュタグはまだ使われていません。",
- "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
- "empty_column.home.public_timeline": "連合タイムライン",
- "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
- "empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
- "follow_request.authorize": "許可",
- "follow_request.reject": "拒否",
- "getting_started.apps": "さまざまなアプリで利用できます。",
- "getting_started.heading": "スタート",
- "getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub({github})から開発に参加したり、問題を報告したりできます。 {apps}",
- "home.column_settings.advanced": "上級者向け",
- "home.column_settings.basic": "シンプル",
- "home.column_settings.filter_regex": "正規表現でフィルター",
- "home.column_settings.show_reblogs": "ブースト表示",
- "home.column_settings.show_replies": "返信表示",
- "home.settings": "カラム設定",
- "lightbox.close": "閉じる",
- "loading_indicator.label": "読み込み中...",
- "media_gallery.toggle_visible": "表示切り替え",
- "missing_indicator.label": "見つかりません",
- "navigation_bar.blocks": "ブロックしたユーザー",
- "navigation_bar.community_timeline": "ローカルタイムライン",
- "navigation_bar.edit_profile": "プロフィールを編集",
- "navigation_bar.favourites": "お気に入り",
- "navigation_bar.follow_requests": "フォローリクエスト",
- "navigation_bar.info": "サーバー情報",
- "navigation_bar.logout": "ログアウト",
- "navigation_bar.mutes": "ミュートしたユーザー",
- "navigation_bar.preferences": "ユーザー設定",
- "navigation_bar.public_timeline": "連合タイムライン",
- "notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました",
- "notification.follow": "{name} さんにフォローされました",
- "notification.mention": "{name} さんがあなたに返信しました",
- "notification.reblog": "{name} さんがあなたのトゥートをブーストしました",
- "notifications.clear": "通知を消去",
- "notifications.clear_confirmation": "本当に通知を消去しますか?",
- "notifications.column_settings.alert": "デスクトップ通知",
- "notifications.column_settings.favourite": "お気に入り",
- "notifications.column_settings.follow": "新しいフォロワー",
- "notifications.column_settings.mention": "返信",
- "notifications.column_settings.reblog": "ブースト",
- "notifications.column_settings.show": "カラムに表示",
- "notifications.column_settings.sound": "通知音を再生",
- "notifications.settings": "カラム設定",
- "onboarding.done": "完了",
- "onboarding.next": "次へ",
- "onboarding.page_one.welcome": "Mastodonへようこそ!",
- "onboarding.page_one.federation": "Mastodonは誰でも参加できるSNSです。",
- "onboarding.page_one.handle": "あなたは今数あるMastodonインスタンスの1つである{domain}にいます。あなたのフルハンドルは{handle}です。",
- "onboarding.page_two.compose": "フォームから投稿できます。イメージや、公開範囲の設定や、表示時の警告の設定は下部のアイコンから行なえます。",
- "onboarding.page_three.search": "検索バーで、{illustration}や{introductions}のように特定のハッシュタグの投稿を見たり、ユーザーを探したりできます。",
- "onboarding.page_three.profile": "「プロフィールを編集」から、あなたの自己紹介や表示名を変更できます。またそこでは他の設定ができます。",
- "onboarding.page_four.home": "「ホーム」タイムラインではあなたがフォローしている人の投稿を表示します。",
- "onboarding.page_four.notifications": "「通知」ではあなたへの他の人からの関わりを表示します。",
- "onboarding.page_five.public_timelines": "連合タイムラインでは{domain}の人がフォローしているMastodon全体での公開投稿を表示します。同じくローカルタイムラインでは{domain}のみの公開投稿を表示します。",
- "onboarding.page_six.almost_done": "以上です。",
- "onboarding.page_six.admin": "あなたのインスタンスの管理者は{admin}です。",
- "onboarding.page_six.read_guidelines": "{guidelines}を読むことを忘れないようにしてください。",
- "onboarding.page_six.guidelines": "コミュニティガイドライン",
- "onboarding.page_six.github": "MastodonはOSSです。バグ報告や機能要望あるいは貢献を{github}から行なえます。",
- "onboarding.page_six.apps_available": "iOS、Androidあるいは他のプラットフォームで使える{apps}があります。",
- "onboarding.page_six.various_app": "様々なモバイルアプリ",
- "onboarding.page_six.appetoot": "Bon Appetoot!",
- "onboarding.skip": "スキップ",
- "privacy.change": "投稿のプライバシーを変更",
- "privacy.direct.long": "メンションしたユーザーだけに公開",
- "privacy.direct.short": "ダイレクト",
- "privacy.private.long": "フォロワーだけに公開",
- "privacy.private.short": "非公開",
- "privacy.public.long": "公開TLに投稿する",
- "privacy.public.short": "公開",
- "privacy.unlisted.long": "公開TLで表示しない",
- "privacy.unlisted.short": "未収載",
- "reply_indicator.cancel": "キャンセル",
- "report.heading": "新規通報",
- "report.placeholder": "コメント",
- "report.submit": "通報する",
- "report.target": "問題のユーザー",
- "search.placeholder": "検索",
- "search.status_by": "{name}からの投稿",
- "search_results.total": "{count, number} 件の結果",
- "status.cannot_reblog": "この投稿はブーストできません",
- "status.delete": "削除",
- "status.favourite": "お気に入り",
- "status.load_more": "もっと見る",
- "status.media_hidden": "非表示のメデイア",
- "status.mention": "返信",
- "status.open": "詳細を表示",
- "status.reblog": "ブースト",
- "status.reblogged_by": "{name} さんにブーストされました",
- "status.reply": "返信",
- "status.replyAll": "全員に返信",
- "status.report": "通報",
- "status.sensitive_toggle": "クリックして表示",
- "status.sensitive_warning": "閲覧注意",
- "status.show_less": "隠す",
- "status.show_more": "もっと見る",
- "tabs_bar.compose": "投稿",
- "tabs_bar.federated_timeline": "連合",
- "tabs_bar.home": "ホーム",
- "tabs_bar.local_timeline": "ローカル",
- "tabs_bar.notifications": "通知",
- "upload_area.title": "ドラッグ&ドロップでアップロード",
- "upload_button.label": "メディアを追加",
- "upload_form.undo": "やり直す",
- "upload_progress.label": "アップロード中…",
- "video_player.expand": "動画の詳細",
- "video_player.toggle_sound": "音の切り替え",
- "video_player.toggle_visible": "表示切り替え",
- "video_player.video_error": "動画の再生に失敗しました",
-};
-
-export default ja;
diff --git a/app/assets/javascripts/components/locales/nl.jsx b/app/assets/javascripts/components/locales/nl.jsx
@@ -1,130 +0,0 @@
-const nl = {
- "account.block": "Blokkeer @{name}",
- "account.edit_profile": "Profiel bewerken",
- "account.followers": "Volgers",
- "account.follows": "Volgt",
- "account.follows_you": "Volgt jou",
- "account.follow": "Volgen",
- "account.mention": "Vermeld @{name}",
- "account.mute": "Negeer @{name}",
- "account.posts": "Berichten",
- "account.report": "Rapporteer @{name}",
- "account.requested": "Wacht op goedkeuring",
- "account.unblock": "Deblokkeer @{name}",
- "account.unfollow": "Ontvolgen",
- "account.unmute": "Negeer @{name} niet meer",
- "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
- "column_back_button.label": "terug",
- "column.blocks": "Geblokkeerde gebruikers",
- "column.community": "Lokale tijdlijn",
- "column.favourites": "Favorieten",
- "column.home": "Jouw tijdlijn",
- "column.mutes": "Genegeerde gebruikers",
- "column.notifications": "Meldingen",
- "column.public": "Globale tijdlijn",
- "column_subheading.navigation": "Navigatie",
- "column_subheading.settings": "Instellingen",
- "compose_form.placeholder": "Wat wil je kwijt?",
- "compose_form.privacy_disclaimer": "Jouw privétoot wordt afgeleverd aan de vermelde gebruikers op {domains}. Vertrouw jij {domainsCount, plural, one {die server} other {die servers}}? Het privé plaatsen van toots werkt alleen op Mastodon-servers. Wanneer {domains} {domainsCount, plural, one {geen Mastodon-server is} other {geen Mastodon-servers zijn}}, dan wordt er niet aangegeven dat de toot privé is, waardoor het kan worden geboost of op een andere manier zichtbaar wordt gemaakt voor mensen waarvoor het niet was bedoeld.",
- "compose_form.private": "Als privé markeren",
- "compose_form.publish": "Toot",
- "compose_form.sensitive": "Media als gevoelig markeren",
- "compose_form.spoiler_placeholder": "Waarschuwingstekst",
- "compose_form.spoiler": "Tekst achter waarschuwing verbergen",
- "compose_form.unlisted": "Niet op openbare tijdlijnen tonen",
- "emoji_button.activity": "Activiteiten",
- "emoji_button.flags": "Vlaggen",
- "emoji_button.food": "Eten en drinken",
- "emoji_button.label": "Emoji toevoegen",
- "emoji_button.nature": "Natuur",
- "emoji_button.objects": "Voorwerpen",
- "emoji_button.people": "Mensen",
- "emoji_button.search": "Zoeken...",
- "emoji_button.symbols": "Symbolen",
- "emoji_button.travel": "Reizen en plekken",
- "getting_started.about_addressing": "Je kunt mensen volgen als je hun gebruikersnaam en het domein van hun server kent. Voer hiervoor het e-mailachtige adres in het zoekveld in.",
- "getting_started.about_shortcuts": "Als de gezochte gebruiker op hetzelfde domein zit als jijzelf, is invoeren van de gebruikersnaam genoeg. Dat geldt ook als je mensen in toots wilt vermelden.",
- "getting_started.apps": "Er zijn meerdere apps beschikbaar",
- "getting_started.heading": "Beginnen",
- "getting_started.open_source_notice": "Mastodon is open-sourcesoftware. Je kunt bijdragen of problemen melden op GitHub via {github}. {apps}.",
- "lightbox.close": "Sluiten",
- "loading_indicator.label": "Laden…",
- "navigation_bar.blocks": "Geblokkeerde gebruikers",
- "navigation_bar.community_timeline": "Lokale tijdlijn",
- "navigation_bar.edit_profile": "Profiel bewerken",
- "navigation_bar.favourites": "Favorieten",
- "navigation_bar.follow_requests": "Volgverzoeken",
- "navigation_bar.info": "Uitgebreide informatie",
- "navigation_bar.logout": "Afmelden",
- "navigation_bar.mutes": "Genegeerde gebruikers",
- "navigation_bar.preferences": "Instellingen",
- "navigation_bar.public_timeline": "Globale tijdlijn",
- "notification.favourite": "{name} markeerde jouw toot als favoriet",
- "notification.follow": "{name} volgt jou nu",
- "notification.mention": "{name} vermeldde jou",
- "notification.reblog": "{name} boostte jouw toot",
- "notifications.clear_confirmation": "Weet je zeker dat je al jouw meldingen wilt verwijderen?",
- "notifications.clear": "Meldingen verwijderen",
- "notifications.column_settings.alert": "Desktopmeldingen",
- "notifications.column_settings.favourite": "Favorieten:",
- "notifications.column_settings.follow": "Nieuwe volgers:",
- "notifications.column_settings.mention": "Vermeldingen:",
- "notifications.column_settings.reblog": "Boosts:",
- "notifications.column_settings.show": "In kolom tonen",
- "notifications.column_settings.sound": "Geluid afspelen",
- "notifications.settings": "Kolom-instellingen",
- "onboarding.next": "Volgende",
- "onboarding.page_five.public_timelines": "De lokale tijdlijn toont openbare toots van iedereen op {domain}. De globale tijdlijn toont openbare toots van iedereen die door gebruikers van {domain} worden gevolgd, dus ook mensen van andere Mastodon-servers. Dit zijn de openbare tijdlijnen en vormen een uitstekende manier om nieuwe mensen te ontdekken.",
- "onboarding.page_four.home": "Jouw tijdlijn laat toots zien van mensen die jij volgt.",
- "onboarding.page_four.notifications": "De kolom met meldingen toont alle interacties die je met andere Mastodon-gebruikers hebt.",
- "onboarding.page_one.federation": "Mastodon is een netwerk van onafhankelijke servers die samen een groot sociaal netwerk vormen.",
- "onboarding.page_one.handle": "Je bevindt je nu op {domain}, dus is jouw volledige Mastodon-adres {handle}",
- "onboarding.page_one.welcome": "Welkom op Mastodon!",
- "onboarding.page_six.admin": "De beheerder van jouw Mastodon-server is {admin}.",
- "onboarding.page_six.almost_done": "Bijna klaar...",
- "onboarding.page_six.appetoot": "Veel succes!",
- "onboarding.page_six.apps_available": "Er zijn {apps} beschikbaar voor iOS, Android en andere platformen.",
- "onboarding.page_six.github": "Mastodon kost niets, en is open-source- en vrije software. Je kan bugs melden, nieuwe mogelijkheden aanvragen en als ontwikkelaar meewerken op {github}.",
- "onboarding.page_six.guidelines": "communityrichtlijnen",
- "onboarding.page_six.read_guidelines": "Vergeet niet de {guidelines} van {domain} te lezen!",
- "onboarding.page_six.various_app": "mobiele apps",
- "onboarding.page_three.profile": "Bewerk jouw profiel om jouw avatar, bio en weergavenaam te veranderen. Daar vind je ook andere instellingen.",
- "onboarding.page_three.search": "Gebruik de zoekbalk linksboven om andere mensen op Mastodon te vinden en om te zoeken op hashtags, zoals {illustration} en {introductions}. Om iemand te vinden die niet op deze Mastodon-server zit, moet je het volledige Mastodon-adres van deze persoon invoeren.",
- "onboarding.page_two.compose": "Schrijf berichten (wij noemen dit toots) in het tekstvak in de linkerkolom. Je kan met de pictogrammen daaronder afbeeldingen uploaden, privacy-instellingen veranderen en je tekst een waarschuwing meegeven.",
- "onboarding.skip": "Overslaan",
- "privacy.change": "Privacy toot aanpassen",
- "privacy.direct.long": "Toot alleen naar vermelde gebruikers",
- "privacy.direct.short": "Direct",
- "privacy.private.long": "Alleen aan volgers tonen",
- "privacy.private.short": "Alleen volgers",
- "privacy.public.long": "Op openbare tijdlijnen tonen",
- "privacy.public.short": "Openbaar",
- "privacy.unlisted.long": "Niet op openbare tijdlijnen tonen",
- "privacy.unlisted.short": "Minder openbaar",
- "reply_indicator.cancel": "Annuleren",
- "search.account": "Account",
- "search.hashtag": "Hashtag",
- "search.placeholder": "Zoeken",
- "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
- "status.delete": "Verwijderen",
- "status.favourite": "Favoriet",
- "status.mention": "@{name} vermelden",
- "status.reblog": "Boost",
- "status.reblogged_by": "{name} boostte",
- "status.reply": "Reageren",
- "status.sensitive_toggle": "Klik om te zien",
- "status.sensitive_warning": "Gevoelige inhoud",
- "status.show_less": "Minder tonen",
- "status.show_more": "Meer tonen",
- "tabs_bar.compose": "Schrijven",
- "tabs_bar.home": "Jouw tijdlijn",
- "tabs_bar.mentions": "Vermeldingen",
- "tabs_bar.notifications": "Meldingen",
- "tabs_bar.public": "Globale tijdlijn",
- "upload_button.label": "Media toevoegen",
- "upload_form.undo": "Ongedaan maken",
- "video_player.toggle_sound": "Geluid in-/uitschakelen",
-
-};
-
-export default nl;
diff --git a/app/assets/javascripts/components/locales/no.jsx b/app/assets/javascripts/components/locales/no.jsx
@@ -1,130 +0,0 @@
-const no = {
- "account.block": "Blokkér @{name}",
- "account.disclaimer": "Denne brukeren er fra en annen instans. Dette tallet kan være høyere.",
- "account.edit_profile": "Rediger profil",
- "account.follow": "Følg",
- "account.followers": "Følgere",
- "account.follows_you": "Følger deg",
- "account.follows": "Følger",
- "account.mention": "Nevn @{name}",
- "account.mute": "Demp @{name}",
- "account.posts": "Innlegg",
- "account.report": "Rapportér @{name}",
- "account.requested": "Venter på godkjennelse",
- "account.unblock": "Avblokker @{name}",
- "account.unfollow": "Avfølg",
- "account.unmute": "Avdemp @{name}",
- "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
- "column_back_button.label": "Tilbake",
- "column.blocks": "Blokkerte brukere",
- "column.community": "Lokal tidslinje",
- "column.favourites": "Likt",
- "column.follow_requests": "Følgeforespørsler",
- "column.home": "Hjem",
- "column.notifications": "Varslinger",
- "column.public": "Felles tidslinje",
- "compose_form.placeholder": "Hva har du på hjertet?",
- "compose_form.privacy_disclaimer": "Din private status vil leveres til nevnte brukere på {domains}. Stoler du på {domainsCount, plural, one {den serveren} other {de serverne}}? Synlighet fungerer kun på Mastodon-instanser. Hvis {domains} {domainsCount, plural, one {ike er en Mastodon-instans} other {ikke er Mastodon-instanser}}, vil det ikke indikeres at posten din er privat, og den kan kanskje bli fremhevd eller på annen måte bli synlig for uventede mottakere.",
- "compose_form.publish": "Tut",
- "compose_form.sensitive": "Merk media som følsomt",
- "compose_form.spoiler_placeholder": "Innholdsadvarsel",
- "compose_form.spoiler": "Skjul tekst bak advarsel",
- "emoji_button.label": "Sett inn emoji",
- "empty_column.community": "Den lokale tidslinjen er tom. Skriv noe offentlig for å få snøballen til å rulle!",
- "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.",
- "empty_column.home.public_timeline": "en offentlig tidslinje",
- "empty_column.home": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.",
- "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.",
- "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp",
- "follow_request.authorize": "Autorisér",
- "follow_request.reject": "Avvis",
- "getting_started.apps": "Diverse apper er tilgjengelige",
- "getting_started.heading": "Kom i gang",
- "getting_started.open_source_notice": "Mastodon er fri programvare. Du kan bidra eller rapportere problemer på GitHub på {github}. {apps}.",
- "home.column_settings.advanced": "Advansert",
- "home.column_settings.basic": "Enkel",
- "home.column_settings.filter_regex": "Filtrér med regulære uttrykk",
- "home.column_settings.show_reblogs": "Vis fremhevinger",
- "home.column_settings.show_replies": "Vis svar",
- "home.settings": "Kolonneinnstillinger",
- "lightbox.close": "Lukk",
- "loading_indicator.label": "Laster...",
- "media_gallery.toggle_visible": "Veksle synlighet",
- "missing_indicator.label": "Ikke funnet",
- "navigation_bar.blocks": "Blokkerte brukere",
- "navigation_bar.community_timeline": "Lokal tidslinje",
- "navigation_bar.edit_profile": "Rediger profil",
- "navigation_bar.favourites": "Likt",
- "navigation_bar.follow_requests": "Følgeforespørsler",
- "navigation_bar.info": "Utvidet informasjon",
- "navigation_bar.logout": "Logg ut",
- "navigation_bar.preferences": "Preferanser",
- "navigation_bar.public_timeline": "Felles tidslinje",
- "notification.favourite": "{name} likte din status",
- "notification.follow": "{name} fulgte deg",
- "notification.reblog": "{name} fremhevde din status",
- "notifications.clear_confirmation": "Er du sikker på at du vil fjerne alle dine varsler?",
- "notifications.clear": "Fjern varsler",
- "notifications.column_settings.alert": "Skrivebordsvarslinger",
- "notifications.column_settings.favourite": "Likt:",
- "notifications.column_settings.follow": "Nye følgere:",
- "notifications.column_settings.mention": "Nevninger:",
- "notifications.column_settings.reblog": "Fremhevinger:",
- "notifications.column_settings.show": "Vis i kolonne",
- "notifications.column_settings.sound": "Spill lyd",
- "notifications.settings": "Kolonneinstillinger",
- "privacy.change": "Justér synlighet",
- "privacy.direct.long": "Post kun til nevnte brukere",
- "privacy.direct.short": "Direkte",
- "privacy.private.long": "Post kun til følgere",
- "privacy.private.short": "Privat",
- "privacy.public.long": "Post kun til offentlige tidslinjer",
- "privacy.public.short": "Offentlig",
- "privacy.unlisted.long": "Ikke vis i offentlige tidslinjer",
- "privacy.unlisted.short": "Uoppført",
- "reply_indicator.cancel": "Avbryt",
- "report.heading": "Ny rapport",
- "report.placeholder": "Tilleggskommentarer",
- "report.submit": "Send inn",
- "report.target": "Rapporterer",
- "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
- "search.placeholder": "Søk",
- "search.status_by": "Status fra {name}",
- "status.delete": "Slett",
- "status.favourite": "Lik",
- "status.load_more": "Last mer",
- "status.media_hidden": "Media skjult",
- "status.mention": "Nevn @{name}",
- "status.open": "Utvid denne statusen",
- "status.reblog": "Fremhev",
- "status.reblogged_by": "Fremhevd av {name}",
- "status.reply": "Svar",
- "status.report": "Rapporter @{name}",
- "status.sensitive_toggle": "Klikk for å vise",
- "status.sensitive_warning": "Følsomt innhold",
- "status.show_less": "Vis mindre",
- "status.show_more": "Vis mer",
- "tabs_bar.compose": "Komponer",
- "tabs_bar.federated_timeline": "Felles",
- "tabs_bar.home": "Hjem",
- "tabs_bar.local_timeline": "Lokal",
- "tabs_bar.notifications": "Varslinger",
- "upload_area.title": "Dra og slipp for å laste opp",
- "upload_button.label": "Legg til media",
- "upload_form.undo": "Angre",
- "upload_progress.label": "Laster opp...",
- "video_player.toggle_sound": "Veksle lyd",
- "video_player.toggle_visible": "Veksle synlighet",
- "video_player.expand": "Utvid video",
- "getting_started.about_addressing": "Du kan følge noen hvis du vet brukernavnet deres og domenet de er på ved å skrive en e-postadresse inn i søkeskjemaet.",
- "getting_started.about_shortcuts": "Hvis målbrukeren er på samme domene som deg, vil kun brukernavnet også fungere. Den samme regelen gjelder når man nevner noen i statuser.",
- "tabs_bar.mentions": "Nevninger",
- "tabs_bar.public": "Felles tidslinje",
- "compose_form.private": "Merk som privat",
- "compose_form.unlisted": "Ikke vis på offentlige tidslinjer",
- "search.account": "Konto",
- "search.hashtag": "Hashtag",
- "notification.mention": "{name} nevnte deg"
-};
-
-export default no;
diff --git a/app/assets/javascripts/components/locales/oc.jsx b/app/assets/javascripts/components/locales/oc.jsx
@@ -1,128 +0,0 @@
-const oc = {
- "column_back_button.label": "Tornar",
- "lightbox.close": "Tampar",
- "loading_indicator.label": "Cargament…",
- "status.mention": "Mencionar",
- "status.delete": "Escafar",
- "status.reply": "Respondre",
- "status.reblog": "Partejar",
- "status.favourite": "Apondre als favorits",
- "status.reblogged_by": "{name} a partejat :",
- "status.sensitive_warning": "Contengut embarrassant",
- "status.sensitive_toggle": "Clicar per mostrar",
- "status.show_more": "Desplegar",
- "status.show_less": "Tornar plegar",
- "status.open": "Desplegar aqueste estatut",
- "status.report": "Senhalar @{name}",
- "status.load_more": "Cargar mai",
- "status.media_hidden": "Mèdia rescondut",
- "video_player.toggle_sound": "Activar/Desactivar lo son",
- "video_player.toggle_visible": "Mostrar/Rescondre la vidèo",
- "account.mention": "Mencionar",
- "account.edit_profile": "Modificar lo perfil",
- "account.unblock": "Desblocar",
- "account.unfollow": "Quitar de sègre",
- "account.block": "Blocar",
- "account.mute": "Rescondre",
- "account.unmute": "Quitar de rescondre",
- "account.follow": "Sègre",
- "account.posts": "Estatuts",
- "account.follows": "Abonaments",
- "account.followers": "Abonats",
- "account.follows_you": "Vos sèc",
- "account.requested": "Invitacion mandada",
- "account.report": "Senhalar",
- "account.disclaimer": "Aqueste compte es sus una autra instància. Los nombres pòdon èsser mai grandes.",
- "getting_started.heading": "Per començar",
- "getting_started.about_addressing": "Podètz sègre los estatuts de qualqu’un en picant son identificant e lo domeni de l’instància separat amb un @ coma una adreça de corrièl dins lo camp de recèrca.",
- "getting_started.about_shortcuts": "S’aquesta persona emplega la meteissa instància que vos l’identifican basta. Atal foncionan tanben las mencions dins vòstres estatuts.",
- "getting_started.about_developer": "Per sègre lo desvolopaire d’aqueste projècte : Gargron@mastodon.social",
- "getting_started.open_source_notice": "Mastodon es un logicial liure. Podètz contribuir e mandar vòstres comentaris e rapòrt de bug via{github} sus GitHub.",
- "column.home": "Acuèlh",
- "column.community": "Fil public local",
- "column.public": "Fil public global",
- "column.notifications": "Notificacions",
- "column.blocks": "Personas blocadas",
- "column.favourites": "Favorits",
- "column.follow_requests": "Demandas d’abonament",
- "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
- "empty_column.public": "I a pas res aquí ! Escribètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo fil public.",
- "empty_column.home": "Pel moment segètz pas segun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
- "empty_column.home.public_timeline": "lo fil public",
- "empty_column.community": "Lo fil public local es void. Escribètz quicòm per lo garnir !",
- "empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag",
- "tabs_bar.compose": "Compausar",
- "tabs_bar.home": "Acuèlh",
- "tabs_bar.mentions": "Mencions",
- "tabs_bar.public": "Fil public global",
- "tabs_bar.notifications": "Notifications",
- "tabs_bar.local_timeline": "Fil public local",
- "tabs_bar.federated_timeline": "Fil public global",
- "compose_form.placeholder": "A de qué pensatz ?",
- "compose_form.publish": "Tut",
- "compose_form.sensitive": "Marcar lo mèdia coma embarrassant",
- "compose_form.spoiler": "Rescondre lo tèxte darrièr un avertiment",
- "compose_form.spoiler_placeholder": "Avertiment",
- "compose_form.private": "Far venir privat",
- "compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz d’aqueste{domainsCount, plural, one { servidor} other {s servidors}} per divulgar pas vòstre estatut ? Los estatuts privats foncionan pas que sus las instàncias a Mastodons. Se {domains} {domainsCount, plural, one {es pas una instància a Mastodon} other {son pas d'instàncias a Mastodon}}, i aurà pas d’indicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists",
- "compose_form.unlisted": "Mostrar pas dins los fils publics",
- "emoji_button.label": "Inserir un emoji",
- "navigation_bar.edit_profile": "Modificar lo perfil",
- "navigation_bar.preferences": "Preferéncias",
- "navigation_bar.community_timeline": "Fil public local",
- "navigation_bar.public_timeline": "Fil public global",
- "navigation_bar.blocks": "Personas blocadas",
- "navigation_bar.favourites": "Favorits",
- "navigation_bar.info": "Mai informacions",
- "navigation_bar.logout": "Desconnexion",
- "navigation_bar.follow_requests": "Demandas d'abonament",
- "reply_indicator.cancel": "Anullar",
- "search.placeholder": "Recercar",
- "search.account": "Compte",
- "search.hashtag": "Mot-clau",
- "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
- "search.status_by": "Estatuts de {name}",
- "upload_button.label": "Apondre un mèdia",
- "upload_form.undo": "Anullar",
- "upload_progress.label": "Mandadís…",
- "upload_area.title": "Lisatz e depausatz per mandar",
- "notification.follow": "{name} vos sèc.",
- "notification.favourite": "{name} a apondut a sos favorits :",
- "notification.reblog": "{name} a partejat vòstre estatut :",
- "notification.mention": "{name} vos a mencionat :",
- "notifications.column_settings.alert": "Notificacions localas",
- "notifications.column_settings.show": "Mostrar dins la colomna",
- "notifications.column_settings.sound": "Emetre un son",
- "notifications.column_settings.follow": "Nòus abonats :",
- "notifications.column_settings.favourite": "Favorits :",
- "notifications.column_settings.mention": "Mencions :",
- "notifications.column_settings.reblog": "Partatges :",
- "notifications.clear": "Levar",
- "notifications.clear_confirmation": "Volètz vertadièrament levar totas vòstras las notificacions ?",
- "notifications.settings": "Paramètres de la colomna",
- "privacy.public.short": "Public",
- "privacy.public.long": "Mostrar dins los fils publics",
- "privacy.unlisted.short": "Pas-listat",
- "privacy.unlisted.long": "Mostrar pas dins los fils publics",
- "privacy.private.short": "Privat",
- "privacy.private.long": "Mostrar pas qu'a vòstres abonats",
- "privacy.direct.short": "Dirècte",
- "privacy.direct.long": "Mostrar pas qu'a las personas mencionadas",
- "privacy.change": "Ajustar la confidencialitat del messatge",
- "media_gallery.toggle_visible": "Modificar la visibilitat",
- "missing_indicator.label": "Pas trobat",
- "follow_request.authorize": "Autorizar",
- "follow_request.reject": "Regetar",
- "home.settings": "Paramètres de la colomna",
- "home.column_settings.basic": "Basic",
- "home.column_settings.show_reblogs": "Mostrar los partatges",
- "home.column_settings.show_replies": "Mostrar las responsas",
- "home.column_settings.advanced": "Avançat",
- "home.column_settings.filter_regex": "Filtrar amb una expression racionala",
- "report.heading": "Nòu senhalament",
- "report.placeholder": "Comentaris addicionals",
- "report.submit": "Mandat",
- "report.target": "Senhalament"
-};
-
-export default oc;
diff --git a/app/assets/javascripts/components/locales/pt-br.jsx b/app/assets/javascripts/components/locales/pt-br.jsx
@@ -1,125 +0,0 @@
-const pt_br = {
- "account.block": "Bloquear @{name}",
- "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
- "account.edit_profile": "Editar perfil",
- "account.follow": "Seguir",
- "account.followers": "Seguidores",
- "account.follows_you": "É teu seguidor",
- "account.follows": "Segue",
- "account.mention": "Mencionar @{name}",
- "account.mute": "Silenciar @{name}",
- "account.posts": "Posts",
- "account.report": "Denunciar @{name}",
- "account.requested": "A aguardar aprovação",
- "account.unblock": "Não bloquear @{name}",
- "account.unfollow": "Deixar de seguir",
- "account.unmute": "Não silenciar @{name}",
- "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
- "column_back_button.label": "Voltar",
- "column.blocks": "Utilizadores Bloqueados",
- "column.community": "Local",
- "column.favourites": "Favoritos",
- "column.follow_requests": "Seguidores Pendentes",
- "column.home": "Home",
- "column.mutes": "Utilizadores silenciados",
- "column.notifications": "Notificações",
- "column.public": "Global",
- "compose_form.placeholder": "Em que estás a pensar?",
- "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
- "compose_form.publish": "Publicar",
- "compose_form.sensitive": "Marcar media como conteúdo sensível",
- "compose_form.spoiler_placeholder": "Aviso de conteúdo",
- "compose_form.spoiler": "Esconder texto com aviso",
- "emoji_button.label": "Inserir Emoji",
- "empty_column.community": "Ainda não existem conteúdo local para mostrar!",
- "empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag",
- "empty_column.home.public_timeline": "global",
- "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
- "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
- "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
- "follow_request.authorize": "Autorizar",
- "follow_request.reject": "Rejeitar",
- "getting_started.apps": "Existem várias aplicações disponíveis",
- "getting_started.heading": "Primeiros passos",
- "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
- "home.column_settings.advanced": "Avançado",
- "home.column_settings.basic": "Básico",
- "home.column_settings.filter_regex": "Filtrar com uma expressão regular",
- "home.column_settings.show_reblogs": "Mostrar as partilhas",
- "home.column_settings.show_replies": "Mostrar as respostas",
- "home.settings": "Parâmetros da listagem Home",
- "lightbox.close": "Fechar",
- "loading_indicator.label": "Carregando...",
- "media_gallery.toggle_visible": "Esconder/Mostrar",
- "missing_indicator.label": "Não encontrado",
- "navigation_bar.blocks": "Utilizadores bloqueados",
- "navigation_bar.community_timeline": "Local",
- "navigation_bar.edit_profile": "Editar perfil",
- "navigation_bar.favourites": "Favoritos",
- "navigation_bar.follow_requests": "Seguidores pendentes",
- "navigation_bar.info": "Mais informações",
- "navigation_bar.logout": "Sair",
- "navigation_bar.mutes": "Utilizadores silenciados",
- "navigation_bar.preferences": "Preferências",
- "navigation_bar.public_timeline": "Global",
- "notification.favourite": "{name} adicionou o teu post aos favoritos",
- "notification.follow": "{name} seguiu-te",
- "notification.mention": "{name} mencionou-te",
- "notification.reblog": "{name} partilhou o teu post",
- "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
- "notifications.clear": "Limpar notificações",
- "notifications.column_settings.alert": "Notificações no computador",
- "notifications.column_settings.favourite": "Favoritos:",
- "notifications.column_settings.follow": "Novos seguidores:",
- "notifications.column_settings.mention": "Menções:",
- "notifications.column_settings.reblog": "Partilhas:",
- "notifications.column_settings.show": "Mostrar nas colunas",
- "notifications.column_settings.sound": "Reproduzir som",
- "notifications.settings": "Parâmetros da listagem de Notificações",
- "privacy.change": "Ajustar a privacidade da mensagem",
- "privacy.direct.long": "Apenas para utilizadores mencionados",
- "privacy.direct.short": "Directo",
- "privacy.private.long": "Apenas para os seguidores",
- "privacy.private.short": "Privado",
- "privacy.public.long": "Publicar em todos os feeds",
- "privacy.public.short": "Público",
- "privacy.unlisted.long": "Não publicar nos feeds públicos",
- "privacy.unlisted.short": "Não listar",
- "reply_indicator.cancel": "Cancelar",
- "report.heading": "Nova denúncia",
- "report.placeholder": "Comentários adicionais",
- "report.submit": "Enviar",
- "report.target": "Denunciar",
- "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
- "search.placeholder": "Pesquisar",
- "search.status_by": "Post de {name}",
- "status.delete": "Eliminar",
- "status.favourite": "Adicionar aos favoritos",
- "status.load_more": "Carregar mais",
- "status.media_hidden": "Media escondida",
- "status.mention": "Mencionar @{name}",
- "status.open": "Expandir",
- "status.reblog": "Partilhar",
- "status.reblogged_by": "{name} partilhou",
- "status.reply": "Responder",
- "status.report": "Denúnciar @{name}",
- "status.sensitive_toggle": "Clique para ver",
- "status.sensitive_warning": "Conteúdo sensível",
- "status.show_less": "Mostrar menos",
- "status.show_more": "Mostrar mais",
- "tabs_bar.compose": "Criar",
- "tabs_bar.federated_timeline": "Global",
- "tabs_bar.home": "Home",
- "tabs_bar.local_timeline": "Local",
- "tabs_bar.notifications": "Notificações",
- "upload_area.title": "Arraste e solte para enviar",
- "upload_button.label": "Adicionar media",
- "upload_form.undo": "Anular",
- "upload_progress.label": "A gravar...",
- "video_player.toggle_sound": "Ligar/Desligar som",
- "video_player.toggle_visible": "Ligar/Desligar vídeo",
- "video_player.expand": "Expandir vídeo",
- "video_player.video_error": "Não é possível ver o vídeo",
-};
-
-export default pt_br;
diff --git a/app/assets/javascripts/components/locales/pt.jsx b/app/assets/javascripts/components/locales/pt.jsx
@@ -1,125 +0,0 @@
-const pt = {
- "account.block": "Bloquear @{name}",
- "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
- "account.edit_profile": "Editar perfil",
- "account.follow": "Seguir",
- "account.followers": "Seguidores",
- "account.follows_you": "É teu seguidor",
- "account.follows": "Segue",
- "account.mention": "Mencionar @{name}",
- "account.mute": "Silenciar @{name}",
- "account.posts": "Posts",
- "account.report": "Denunciar @{name}",
- "account.requested": "A aguardar aprovação",
- "account.unblock": "Não bloquear @{name}",
- "account.unfollow": "Deixar de seguir",
- "account.unmute": "Não silenciar @{name}",
- "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
- "column_back_button.label": "Voltar",
- "column.blocks": "Utilizadores Bloqueados",
- "column.community": "Local",
- "column.favourites": "Favoritos",
- "column.follow_requests": "Seguidores Pendentes",
- "column.home": "Home",
- "column.mutes": "Utilizadores silenciados",
- "column.notifications": "Notificações",
- "column.public": "Global",
- "compose_form.placeholder": "Em que estás a pensar?",
- "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
- "compose_form.publish": "Publicar",
- "compose_form.sensitive": "Marcar media como conteúdo sensível",
- "compose_form.spoiler_placeholder": "Aviso de conteúdo",
- "compose_form.spoiler": "Esconder texto com aviso",
- "emoji_button.label": "Inserir Emoji",
- "empty_column.community": "Ainda não existem conteúdo local para mostrar!",
- "empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag",
- "empty_column.home.public_timeline": "global",
- "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
- "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
- "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
- "follow_request.authorize": "Autorizar",
- "follow_request.reject": "Rejeitar",
- "getting_started.apps": "Existem várias aplicações disponíveis",
- "getting_started.heading": "Primeiros passos",
- "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
- "home.column_settings.advanced": "Avançado",
- "home.column_settings.basic": "Básico",
- "home.column_settings.filter_regex": "Filtrar com uma expressão regular",
- "home.column_settings.show_reblogs": "Mostrar as partilhas",
- "home.column_settings.show_replies": "Mostrar as respostas",
- "home.settings": "Parâmetros da listagem Home",
- "lightbox.close": "Fechar",
- "loading_indicator.label": "Carregando...",
- "media_gallery.toggle_visible": "Esconder/Mostrar",
- "missing_indicator.label": "Não encontrado",
- "navigation_bar.blocks": "Utilizadores bloqueados",
- "navigation_bar.community_timeline": "Local",
- "navigation_bar.edit_profile": "Editar perfil",
- "navigation_bar.favourites": "Favoritos",
- "navigation_bar.follow_requests": "Seguidores pendentes",
- "navigation_bar.info": "Mais informações",
- "navigation_bar.logout": "Sair",
- "navigation_bar.mutes": "Utilizadores silenciados",
- "navigation_bar.preferences": "Preferências",
- "navigation_bar.public_timeline": "Global",
- "notification.favourite": "{name} adicionou o teu post aos favoritos",
- "notification.follow": "{name} seguiu-te",
- "notification.mention": "{name} mencionou-te",
- "notification.reblog": "{name} partilhou o teu post",
- "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
- "notifications.clear": "Limpar notificações",
- "notifications.column_settings.alert": "Notificações no computador",
- "notifications.column_settings.favourite": "Favoritos:",
- "notifications.column_settings.follow": "Novos seguidores:",
- "notifications.column_settings.mention": "Menções:",
- "notifications.column_settings.reblog": "Partilhas:",
- "notifications.column_settings.show": "Mostrar nas colunas",
- "notifications.column_settings.sound": "Reproduzir som",
- "notifications.settings": "Parâmetros da listagem de Notificações",
- "privacy.change": "Ajustar a privacidade da mensagem",
- "privacy.direct.long": "Apenas para utilizadores mencionados",
- "privacy.direct.short": "Directo",
- "privacy.private.long": "Apenas para os seguidores",
- "privacy.private.short": "Privado",
- "privacy.public.long": "Publicar em todos os feeds",
- "privacy.public.short": "Público",
- "privacy.unlisted.long": "Não publicar nos feeds públicos",
- "privacy.unlisted.short": "Não listar",
- "reply_indicator.cancel": "Cancelar",
- "report.heading": "Nova denúncia",
- "report.placeholder": "Comentários adicionais",
- "report.submit": "Enviar",
- "report.target": "Denunciar",
- "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
- "search.placeholder": "Pesquisar",
- "search.status_by": "Post de {name}",
- "status.delete": "Eliminar",
- "status.favourite": "Adicionar aos favoritos",
- "status.load_more": "Carregar mais",
- "status.media_hidden": "Media escondida",
- "status.mention": "Mencionar @{name}",
- "status.open": "Expandir",
- "status.reblog": "Partilhar",
- "status.reblogged_by": "{name} partilhou",
- "status.reply": "Responder",
- "status.report": "Denúnciar @{name}",
- "status.sensitive_toggle": "Clique para ver",
- "status.sensitive_warning": "Conteúdo sensível",
- "status.show_less": "Mostrar menos",
- "status.show_more": "Mostrar mais",
- "tabs_bar.compose": "Criar",
- "tabs_bar.federated_timeline": "Global",
- "tabs_bar.home": "Home",
- "tabs_bar.local_timeline": "Local",
- "tabs_bar.notifications": "Notificações",
- "upload_area.title": "Arraste e solte para enviar",
- "upload_button.label": "Adicionar media",
- "upload_form.undo": "Anular",
- "upload_progress.label": "A gravar...",
- "video_player.toggle_sound": "Ligar/Desligar som",
- "video_player.toggle_visible": "Ligar/Desligar vídeo",
- "video_player.expand": "Expandir vídeo",
- "video_player.video_error": "Não é possível ver o vídeo",
-};
-
-export default pt;
diff --git a/app/assets/javascripts/components/locales/ru.jsx b/app/assets/javascripts/components/locales/ru.jsx
@@ -1,138 +0,0 @@
-const ru = {
- "account.block": "Блокировать",
- "account.disclaimer": "Это пользователь с другого узла. Число может быть больше.",
- "account.edit_profile": "Изменить профиль",
- "account.follow": "Подписаться",
- "account.followers": "Подписаны",
- "account.follows": "Подписки",
- "account.follows_you": "Подписан(а) на Вас",
- "account.mention": "Упомянуть",
- "account.mute": "Заглушить",
- "account.posts": "Посты",
- "account.report": "Пожаловаться",
- "account.requested": "Ожидает подтверждения",
- "account.unblock": "Разблокировать",
- "account.unfollow": "Отписаться",
- "account.unmute": "Снять глушение",
- "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз",
- "column.blocks": "Список блокировки",
- "column.community": "Локальная лента",
- "column.favourites": "Понравившееся",
- "column.follow_requests": "Запросы на подписку",
- "column.home": "Главная",
- "column.mutes": "Список глушения",
- "column.notifications": "Уведомления",
- "column.public": "Глобальная лента",
- "column_back_button.label": "Назад",
- "column_subheading.navigation": "Навигация",
- "column_subheading.settings": "Настройки",
- "compose_form.placeholder": "О чем Вы думаете?",
- "compose_form.privacy_disclaimer": "Ваш приватный статус будет доставлен упомянутым пользователям на доменах {domains}. Доверяете ли вы {domainsCount, plural, one {этому серверу} other {этим серверам}}? Приватность постов работает только на узлах Mastodon. Если {domains} {domainsCount, plural, one {не является узлом Mastodon} other {не являются узлами Mastodon}}, приватность поста не будет указана, и он может оказаться продвинут или иным образом показан не обозначенным Вами пользователям.",
- "compose_form.publish": "Трубить",
- "compose_form.sensitive": "Отметить как чувствительный контент",
- "compose_form.spoiler": "Скрыть текст за предупреждением",
- "compose_form.spoiler_placeholder": "Предупреждение о скрытом тексте",
- "emoji_button.activity": "Занятия",
- "emoji_button.flags": "Флаги",
- "emoji_button.food": "Еда и напитки",
- "emoji_button.label": "Вставить эмодзи",
- "emoji_button.nature": "Природа",
- "emoji_button.objects": "Предметы",
- "emoji_button.people": "Люди",
- "emoji_button.search": "Найти...",
- "emoji_button.symbols": "Символы",
- "emoji_button.travel": "Путешествия",
- "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
- "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.",
- "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.",
- "empty_column.home.public_timeline": "публичные ленты",
- "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.",
- "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.",
- "follow_request.authorize": "Авторизовать",
- "follow_request.reject": "Отказать",
- "getting_started.apps": "Доступны различные приложения.",
- "getting_started.heading": "Добро пожаловать",
- "getting_started.open_source_notice": "Mastodon - программа с открытым исходным кодом. Вы можете помочь проекту или сообщить о проблемах на GitHub по адресу {github}. {apps}.",
- "home.column_settings.advanced": "Дополнительные",
- "home.column_settings.basic": "Основные",
- "home.column_settings.filter_regex": "Отфильтровать регулярным выражением",
- "home.column_settings.show_reblogs": "Показывать продвижения",
- "home.column_settings.show_replies": "Показывать ответы",
- "home.settings": "Настройки колонки",
- "lightbox.close": "Закрыть",
- "loading_indicator.label": "Загрузка...",
- "media_gallery.toggle_visible": "Показать/скрыть",
- "missing_indicator.label": "Не найдено",
- "navigation_bar.blocks": "Список блокировки",
- "navigation_bar.community_timeline": "Локальная лента",
- "navigation_bar.edit_profile": "Изменить профиль",
- "navigation_bar.favourites": "Понравившееся",
- "navigation_bar.follow_requests": "Запросы на подписку",
- "navigation_bar.info": "Об узле",
- "navigation_bar.logout": "Выйти",
- "navigation_bar.mutes": "Список глушения",
- "navigation_bar.preferences": "Опции",
- "navigation_bar.public_timeline": "Глобальная лента",
- "notification.favourite": "{name} понравился Ваш статус",
- "notification.follow": "{name} подписался(-лась) на Вас",
- "notification.mention": "{name} упомянул(а) Вас",
- "notification.reblog": "{name} продвинул(а) Ваш статус",
- "notifications.clear": "Очистить уведомления",
- "notifications.clear_confirmation": "Вы уверены, что хотите очистить все уведомления?",
- "notifications.column_settings.alert": "Десктопные уведомления",
- "notifications.column_settings.favourite": "Нравится:",
- "notifications.column_settings.follow": "Новые подписчики:",
- "notifications.column_settings.mention": "Упоминания:",
- "notifications.column_settings.reblog": "Продвижения:",
- "notifications.column_settings.show": "Показывать в колонке",
- "notifications.column_settings.sound": "Проигрывать звук",
- "notifications.settings": "Настройки колонки",
- "privacy.change": "Изменить видимость статуса",
- "privacy.direct.long": "Показать только упомянутым",
- "privacy.direct.short": "Направленный",
- "privacy.private.long": "Показать только подписчикам",
- "privacy.private.short": "Приватный",
- "privacy.public.long": "Показать в публичных лентах",
- "privacy.public.short": "Публичный",
- "privacy.unlisted.long": "Не показывать в лентах",
- "privacy.unlisted.short": "Скрытый",
- "reply_indicator.cancel": "Отмена",
- "report.heading": "Новая жалоба",
- "report.placeholder": "Комментарий",
- "report.submit": "Отправить",
- "report.target": "Жалуемся на",
- "search.placeholder": "Поиск",
- "search.status_by": "Статус от {name}",
- "search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}",
- "status.cannot_reblog": "Этот статус не может быть продвинут",
- "status.delete": "Удалить",
- "status.favourite": "Нравится",
- "status.load_more": "Показать еще",
- "status.media_hidden": "Медиаконтент скрыт",
- "status.mention": "Упомянуть @{name}",
- "status.open": "Развернуть статус",
- "status.reblog": "Продвинуть",
- "status.reblogged_by": "{name} продвинул(а)",
- "status.reply": "Ответить",
- "status.replyAll": "Ответить на тред",
- "status.report": "Пожаловаться",
- "status.sensitive_toggle": "Нажмите для просмотра",
- "status.sensitive_warning": "Чувствительный контент",
- "status.show_less": "Свернуть",
- "status.show_more": "Развернуть",
- "tabs_bar.compose": "Написать",
- "tabs_bar.federated_timeline": "Глобальная",
- "tabs_bar.home": "Главная",
- "tabs_bar.local_timeline": "Локальная",
- "tabs_bar.notifications": "Уведомления",
- "upload_area.title": "Перетащите сюда, чтобы загрузить",
- "upload_button.label": "Добавить медиаконтент",
- "upload_form.undo": "Отменить",
- "upload_progress.label": "Загрузка...",
- "video_player.expand": "Развернуть видео",
- "video_player.toggle_sound": "Вкл./выкл. звук",
- "video_player.toggle_visible": "Показать/скрыть",
- "video_player.video_error": "Видео не может быть проиграно",
-};
-
-export default ru;
diff --git a/app/assets/javascripts/components/locales/uk.jsx b/app/assets/javascripts/components/locales/uk.jsx
@@ -1,57 +0,0 @@
-const uk = {
- "column_back_button.label": "Назад",
- "lightbox.close": "Закрити",
- "loading_indicator.label": "Завантаження...",
- "status.mention": "Згадати",
- "status.delete": "Видалити",
- "status.reply": "Відповісти",
- "status.reblog": "Передмухнути",
- "status.favourite": "Подобається",
- "status.reblogged_by": "{name} передмухнув(-ла)",
- "status.sensitive_warning": "Непристойний зміст",
- "status.sensitive_toggle": "Натисніть, щоб подивитися",
- "video_player.toggle_sound": "Увімкнути/вимкнути звук",
- "account.mention": "Згадати",
- "account.edit_profile": "Налаштування профілю",
- "account.unblock": "Розблокувати",
- "account.unfollow": "Відписатися",
- "account.block": "Заблокувати",
- "account.follow": "Підписатися",
- "account.posts": "Пости",
- "account.follows": "Підписки",
- "account.followers": "Підписники",
- "account.follows_you": "Підписаний",
- "getting_started.heading": "Ласкаво просимо",
- "getting_started.about_addressing": "Ви можете підписуватись на людей, якщо ви знаєте їх ім'я користувача чи домен, шляхом введення email-подібної адреси у верхньому рядку бокової панелі.",
- "getting_started.about_shortcuts": "Якщо користувач, якого ви шукаєте, знаходиться на тому ж домені, що й ви, можна просто ввести ім'я користувача. Це правило стосується й згадування людей у статусах.",
- "getting_started.about_developer": "Розробник проекту знаходиться за адресою Gargron@mastodon.social",
- "column.home": "Головна",
- "column.mentions": "Згадування",
- "column.public": "Стіна",
- "column.notifications": "Сповіщення",
- "tabs_bar.compose": "Написати",
- "tabs_bar.home": "Головна",
- "tabs_bar.mentions": "Згадування",
- "tabs_bar.public": "Стіна",
- "tabs_bar.notifications": "Сповіщення",
- "compose_form.placeholder": "Що у Вас на думці?",
- "compose_form.publish": "Дмухнути",
- "compose_form.sensitive": "Непристойний зміст",
- "compose_form.unlisted": "Таємний режим",
- "navigation_bar.edit_profile": "Редагувати профіль",
- "navigation_bar.preferences": "Налаштування",
- "navigation_bar.public_timeline": "Публічна стіна",
- "navigation_bar.logout": "Вийти",
- "reply_indicator.cancel": "Відмінити",
- "search.placeholder": "Пошук",
- "search.account": "Аккаунт",
- "search.hashtag": "Хештеґ",
- "upload_button.label": "Додати медіа",
- "upload_form.undo": "Відмінити",
- "notification.follow": "{name} підписався(-лась) на Вас",
- "notification.favourite": "{name} сподобався ваш допис",
- "notification.reblog": "{name} передмухнув(-ла) Ваш статус",
- "notification.mention": "{name} згадав(-ла) Вас"
-};
-
-export default uk;
diff --git a/app/assets/javascripts/components/locales/zh-cn.jsx b/app/assets/javascripts/components/locales/zh-cn.jsx
@@ -1,157 +0,0 @@
-import zh from 'react-intl/locale-data/zh';
-
-const localeData = zh.reduce(function (acc, localeData) {
- if (localeData.locale === "zh-Hans-CN") {
- // rename the locale "zh-Hans-CN" as "zh-CN"
- // (match the code usually used in Accepted-Language header)
- acc.push(Object.assign({},
- localeData,
- {
- "locale": "zh-CN",
- "parentLocale": "zh-Hans-CN",
- }
- ));
- }
- return acc;
-}, []);
-
-export { localeData as localeData };
-
-const zh_cn = {
- "account.block": "屏蔽 @{name}",
- "account.disclaimer": "由于这个账户处于另一个服务站,实际数字会比这个更多。",
- "account.edit_profile": "修改个人资料",
- "account.follow": "关注",
- "account.followers": "关注者",
- "account.follows_you": "关注你",
- "account.follows": "正关注",
- "account.mention": "提及 @{name}",
- "account.mute": "将 @{name} 静音",
- "account.posts": "嘟文",
- "account.report": "举报 @{name}",
- "account.requested": "等候审批",
- "account.unblock": "解除对 @{name} 的屏蔽",
- "account.unfollow": "取消关注",
- "account.unmute": "取消 @{name} 的静音",
- "boost_modal.combo": "如你想在下次路过时显示,请按{combo},",
- "column_back_button.label": "返回",
- "column.blocks": "屏蔽用户",
- "column.community": "本站时间轴",
- // intentional departure from existing "推文" translation for posts:
- // "推文" refers to "推特", the official translation for Twitter.
- // Currently using a semi-phonetic translation "嘟", which refers
- // to train horn sounds, for "toot".
- "column.favourites": "赞过的嘟文",
- "column.follow_requests": "关注请求",
- "column.home": "主页",
- "column.notifications": "通知",
- "column.public": "跨站公共时间轴",
- "compose_form.placeholder": "在想啥?",
- "compose_form.privacy_disclaimer": "你的私人嘟文,将被发送至你所提及的 {domains} 用户。你是否信任{domainsCount, plural, one {这个网站} other {这些网站}}?请留意,嘟文隐私设置只适用于各 Mastodon 服务站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服务站} other {之中有些不是 Mastodon 服务站}},对方将无法收到这篇嘟文的隐私设置,然后可能被转嘟给不能预知的用户阅读。",
- "compose_form.private": "标示为“只有关注你的人能看”",
- // Going "toot-toot" here below.
- "compose_form.publish": "嘟嘟",
- "compose_form.sensitive": "将媒体文件标示为“敏感内容”",
- "compose_form.spoiler_placeholder": "敏感内容的警告消息",
- "compose_form.spoiler": "将部分文本藏于警告消息之后",
- "compose_form.unlisted": "请勿在公共时间轴显示",
- "emoji_button.label": "加入表情符号",
- "empty_column.community": "本站时间轴暂时未有内容,快贴文来抢头香啊!",
- "empty_column.hashtag": "这个标签暂时未有内容。",
- "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
- "empty_column.home.public_timeline": "公共时间轴",
- "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
- "empty_column.notifications": "你没有任何通知纪录,快向其他用户搭讪吧。",
- "empty_column.public": "跨站公共时间轴暂时没有内容!快写一些公共的嘟文,或者关注另一些服务站的用户吧!你和本站、友站的交流,将决定这里出现的内容。",
- "follow_request.authorize": "批准",
- "follow_request.reject": "拒绝",
- "getting_started.about_addressing": "只要你知道一位用户的用户名称和域名,你可以用“@用户名称@域名”的格式在搜索栏寻找该用户。",
- "getting_started.about_shortcuts": "只要该用户是在你现在的服务站开立,你就可以直接输入用户名搜索。在嘟文中提及别的用户也是如此。",
- "getting_started.apps": "手机或桌面应用程序",
- "getting_started.heading": "开始使用",
- "getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。你亦可通过{apps}阅读 Mastodon 上的消息。",
- "home.column_settings.advanced": "高端",
- "home.column_settings.basic": "基本",
- "home.column_settings.filter_regex": "使用正则表达式 (regex) 过滤",
- "home.column_settings.show_reblogs": "显示被转的嘟文",
- "home.column_settings.show_replies": "显示回应嘟文",
- "home.settings": "字段设置",
- "lightbox.close": "关闭",
- "loading_indicator.label": "加载中……",
- "media_gallery.toggle_visible": "打开或关上",
- "missing_indicator.label": "找不到内容",
- "navigation_bar.blocks": "被屏蔽的用户",
- "navigation_bar.community_timeline": "本站时间轴",
- "navigation_bar.edit_profile": "修改个人资料",
- "navigation_bar.favourites": "赞的内容",
- "navigation_bar.follow_requests": "关注请求",
- "navigation_bar.info": "关于本服务站",
- "navigation_bar.logout": "注销",
- // intentional departure from https://github.com/tootsuite/mastodon/blob/f864fee1/config/locales/zh-CN.yml#L126:
- // clashes for settings/preferences
- "navigation_bar.preferences": "首选项",
- "navigation_bar.public_timeline": "跨站公共时间轴",
- "notification.favourite": "{name} 赞你的嘟文",
- "notification.follow": "{name} 开始关注你",
- "notification.mention": "{name} 提及你",
- "notification.reblog": "{name} 转嘟你的嘟文",
- "notifications.clear_confirmation": "你确定要清空通知纪录吗?",
- "notifications.clear": "清空通知纪录",
- "notifications.column_settings.alert": "显示桌面通知",
- "notifications.column_settings.favourite": "赞你的嘟文:",
- "notifications.column_settings.follow": "关注你:",
- "notifications.column_settings.mention": "提及你:",
- "notifications.column_settings.reblog": "转你的嘟文:",
- "notifications.column_settings.show": "在通知栏显示",
- "notifications.column_settings.sound": "播放音效",
- "notifications.settings": "字段设置",
- "privacy.change": "调整隐私设置",
- "privacy.direct.long": "只有提及的用户能看到",
- "privacy.direct.short": "私人消息",
- "privacy.private.long": "只有关注你用户能看到",
- "privacy.private.short": "关注者",
- "privacy.public.long": "在公共时间轴显示",
- "privacy.public.short": "公共",
- "privacy.unlisted.long": "公开,但不在公共时间轴显示",
- "privacy.unlisted.short": "公开",
- "reply_indicator.cancel": "取消",
- "report.heading": "举报",
- "report.placeholder": "额外消息",
- "report.submit": "提交",
- "report.target": "Reporting",
- "search_results.total": "{count, number} 项结果",
- "search.account": "用户",
- "search.hashtag": "标签",
- "search.placeholder": "搜索",
- "search.status_by": "按{name}搜索嘟文",
- "status.delete": "删除",
- "status.favourite": "赞",
- "status.load_more": "加载更多",
- "status.media_hidden": "隐藏媒体内容",
- "status.mention": "提及 @{name}",
- "status.open": "展开嘟文",
- "status.reblog": "转嘟",
- "status.reblogged_by": "{name} 转嘟",
- "status.reply": "回应",
- "status.report": "举报 @{name}",
- "status.sensitive_toggle": "点击显示",
- "status.sensitive_warning": "敏感内容",
- "status.show_less": "减少显示",
- "status.show_more": "显示更多",
- "tabs_bar.compose": "撰写",
- "tabs_bar.federated_timeline": "跨站",
- "tabs_bar.home": "主页",
- "tabs_bar.local_timeline": "本站",
- "tabs_bar.mentions": "提及",
- "tabs_bar.notifications": "通知",
- "tabs_bar.public": "跨站公共时间轴",
- "upload_area.title": "将文件拖放至此上传",
- "upload_button.label": "上传媒体文件",
- "upload_form.undo": "还原",
- "upload_progress.label": "上传中……",
- "video_player.expand": "展开影片",
- "video_player.toggle_sound": "开关音效",
- "video_player.toggle_visible": "打开或关上",
-};
-
-export default zh_cn;
diff --git a/app/assets/javascripts/components/locales/zh-hk.jsx b/app/assets/javascripts/components/locales/zh-hk.jsx
@@ -1,150 +0,0 @@
-import zh from 'react-intl/locale-data/zh';
-
-const localeData = zh.reduce(function (acc, localeData) {
- if (localeData.locale === "zh-Hant-HK") {
- // rename the locale "zh-Hant-HK" as "zh-HK"
- // (match the code usually used in Accepted-Language header)
- acc.push(Object.assign({},
- localeData,
- {
- "locale": "zh-HK",
- "parentLocale": "zh-Hant-HK",
- }
- ));
- }
- return acc;
-}, []);
-
-export { localeData as localeData };
-
-const zh_hk = {
- "account.block": "封鎖 @{name}",
- "account.disclaimer": "由於這個用戶在另一個服務站,實際數字會比這個更多。",
- "account.edit_profile": "修改個人資料",
- "account.follow": "關注",
- "account.followers": "關注的人",
- "account.follows_you": "關注你",
- "account.follows": "正在關注",
- "account.mention": "提及 @{name}",
- "account.mute": "將 @{name} 靜音",
- "account.posts": "文章",
- "account.report": "舉報 @{name}",
- "account.requested": "等候審批",
- "account.unblock": "解除對 @{name} 的封鎖",
- "account.unfollow": "取消關注",
- "account.unmute": "取消 @{name} 的靜音",
- "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},",
- "column_back_button.label": "返回",
- "column.blocks": "封鎖用戶",
- "column.community": "本站時間軸",
- "column.favourites": "喜歡的文章",
- "column.follow_requests": "關注請求",
- "column.home": "主頁",
- "column.notifications": "通知",
- "column.public": "跨站公共時間軸",
- "compose_form.placeholder": "你在想甚麼?",
- "compose_form.privacy_disclaimer": "你的私人文章,將被遞送至你所提及的 {domains} 用戶。你是否信任{domainsCount, plural, one {這個網站} other {這些網站}}?請留意,文章私隱設定只適用於各 Mastodon 服務站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服務站} other {之中有些不是 Mastodon 服務站}},對方將無法收到這篇文章的私隱設定,然後可能被轉推給不能預知的用戶閱讀。",
- "compose_form.private": "標示為「只有關注你的人能看」",
- "compose_form.publish": "發文",
- "compose_form.sensitive": "將媒體檔案標示為「敏感內容」",
- "compose_form.spoiler_placeholder": "敏感警告訊息",
- "compose_form.spoiler": "將部份文字藏於警告訊息之後",
- "compose_form.unlisted": "請勿在公共時間軸顯示",
- "emoji_button.label": "加入表情符號",
- "empty_column.community": "本站時間軸暫時未有內容,快貼文來搶頭香啊!",
- "empty_column.hashtag": "這個標籤暫時未有內容。",
- "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
- "empty_column.home.public_timeline": "公共時間軸",
- "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
- "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。",
- "empty_column.public": "跨站公共時間軸暫時沒有內容!快寫一些公共的文章,或者關注另一些服務站的用戶吧!你和本站、友站的交流,將決定這裏出現的內容。",
- "follow_request.authorize": "批准",
- "follow_request.reject": "拒絕",
- "getting_started.about_addressing": "只要你知道一位用戶的用戶名稱和域名,你可以用「@用戶名稱@域名」的格式在搜尋欄尋找該用戶。",
- "getting_started.about_shortcuts": "只要該用戶是在你現在的服務站開立,你可以直接輸入用戶𠱷搜尋。同樣的規則適用於在文章提及別的用戶。",
- "getting_started.apps": "手機或桌面應用程式",
- "getting_started.heading": "開始使用",
- "getting_started.open_source_notice": "Mastodon 是一個開放源碼的軟件。你可以在官方 GitHub ({github}) 貢獻或者回報問題。你亦可透過{apps}閱讀 Mastodon 上的消息。",
- "home.column_settings.advanced": "進階",
- "home.column_settings.basic": "基本",
- "home.column_settings.filter_regex": "使用正規表達式 (regular expression) 過濾",
- "home.column_settings.show_reblogs": "顯示被轉推的文章",
- "home.column_settings.show_replies": "顯示回應文章",
- "home.settings": "欄位設定",
- "lightbox.close": "Close",
- "loading_indicator.label": "載入中...",
- "media_gallery.toggle_visible": "打開或關上",
- "missing_indicator.label": "找不到內容",
- "navigation_bar.blocks": "被封鎖的用戶",
- "navigation_bar.community_timeline": "本站時間軸",
- "navigation_bar.edit_profile": "修改個人資料",
- "navigation_bar.favourites": "喜歡的內容",
- "navigation_bar.follow_requests": "關注請求",
- "navigation_bar.info": "關於本服務站",
- "navigation_bar.logout": "登出",
- "navigation_bar.preferences": "偏好設定",
- "navigation_bar.public_timeline": "跨站公共時間軸",
- "notification.favourite": "{name} 喜歡你的文章",
- "notification.follow": "{name} 開始關注你",
- "notification.mention": "{name} 提及你",
- "notification.reblog": "{name} 轉推你的文章",
- "notifications.clear_confirmation": "你確定要清空通知紀錄嗎?",
- "notifications.clear": "清空通知紀錄",
- "notifications.column_settings.alert": "顯示桌面通知",
- "notifications.column_settings.favourite": "喜歡你的文章:",
- "notifications.column_settings.follow": "關注你:",
- "notifications.column_settings.mention": "提及你:",
- "notifications.column_settings.reblog": "轉推你的文章:",
- "notifications.column_settings.show": "在通知欄顯示",
- "notifications.column_settings.sound": "播放音效",
- "notifications.settings": "欄位設定",
- "privacy.change": "調整私隱設定",
- "privacy.direct.long": "只有提及的用戶能看到",
- "privacy.direct.short": "私人訊息",
- "privacy.private.long": "只有關注你用戶能看到",
- "privacy.private.short": "關注者",
- "privacy.public.long": "在公共時間軸顯示",
- "privacy.public.short": "公共",
- "privacy.unlisted.long": "公開,但不在公共時間軸顯示",
- "privacy.unlisted.short": "公開",
- "reply_indicator.cancel": "取消",
- "report.heading": "舉報",
- "report.placeholder": "額外訊息",
- "report.submit": "提交",
- "report.target": "Reporting",
- "search_results.total": "{count, number} 項結果",
- "search.account": "用戶",
- "search.hashtag": "標籤",
- "search.placeholder": "搜尋",
- "search.status_by": "按{name}搜尋文章",
- "status.delete": "刪除",
- "status.favourite": "喜歡",
- "status.load_more": "載入更多",
- "status.media_hidden": "隱藏媒體內容",
- "status.mention": "提及 @{name}",
- "status.open": "展開文章",
- "status.reblog": "轉推",
- "status.reblogged_by": "{name} 轉推",
- "status.reply": "回應",
- "status.report": "舉報 @{name}",
- "status.sensitive_toggle": "點擊顯示",
- "status.sensitive_warning": "敏感內容",
- "status.show_less": "減少顯示",
- "status.show_more": "顯示更多",
- "tabs_bar.compose": "撰寫",
- "tabs_bar.federated_timeline": "跨站",
- "tabs_bar.home": "主頁",
- "tabs_bar.local_timeline": "本站",
- "tabs_bar.mentions": "提及",
- "tabs_bar.notifications": "通知",
- "tabs_bar.public": "跨站公共時間軸",
- "upload_area.title": "將檔案拖放至此上載",
- "upload_button.label": "上載媒體檔案",
- "upload_form.undo": "還原",
- "upload_progress.label": "上載中……",
- "video_player.expand": "展開影片",
- "video_player.toggle_sound": "開關音效",
- "video_player.toggle_visible": "打開或關上",
-};
-
-export default zh_hk;
diff --git a/app/assets/javascripts/components/middleware/errors.jsx b/app/assets/javascripts/components/middleware/errors.jsx
@@ -1,33 +0,0 @@
-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) {
- const { data, status, statusText } = action.error.response;
-
- let message = statusText;
- let title = `${status}`;
-
- if (data.error) {
- message = data.error;
- }
-
- dispatch(showAlert(title, message));
- } else {
- console.error(action.error); // eslint-disable-line no-console
- dispatch(showAlert('Oops!', 'An unexpected error occurred.'));
- }
- }
- }
-
- return next(action);
- };
-};
diff --git a/app/assets/javascripts/components/middleware/loading_bar.jsx b/app/assets/javascripts/components/middleware/loading_bar.jsx
@@ -1,25 +0,0 @@
-import { showLoading, hideLoading } from 'react-redux-loading-bar';
-
-const defaultTypeSuffixes = ['PENDING', 'FULFILLED', 'REJECTED'];
-
-export default function loadingBarMiddleware(config = {}) {
- const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypeSuffixes;
-
- return ({ dispatch }) => next => (action) => {
- if (action.type && !action.skipLoading) {
- const [PENDING, FULFILLED, REJECTED] = promiseTypeSuffixes;
-
- const isPending = new RegExp(`${PENDING}$`, 'g');
- const isFulfilled = new RegExp(`${FULFILLED}$`, 'g');
- const isRejected = new RegExp(`${REJECTED}$`, 'g');
-
- if (action.type.match(isPending)) {
- dispatch(showLoading());
- } else if (action.type.match(isFulfilled) || action.type.match(isRejected)) {
- dispatch(hideLoading());
- }
- }
-
- return next(action);
- };
-};
diff --git a/app/assets/javascripts/components/middleware/sounds.jsx b/app/assets/javascripts/components/middleware/sounds.jsx
@@ -1,22 +0,0 @@
-const play = audio => {
- if (!audio.paused) {
- audio.pause();
- audio.fastSeek(0);
- }
-
- audio.play();
-};
-
-export default function soundsMiddleware() {
- const soundCache = {
- boop: new Audio(['/sounds/boop.mp3'])
- };
-
- return ({ dispatch }) => next => (action) => {
- if (action.meta && action.meta.sound && soundCache[action.meta.sound]) {
- play(soundCache[action.meta.sound]);
- }
-
- return next(action);
- };
-};
diff --git a/app/assets/javascripts/components/reducers/accounts.jsx b/app/assets/javascripts/components/reducers/accounts.jsx
@@ -1,131 +0,0 @@
-import {
- ACCOUNT_FETCH_SUCCESS,
- FOLLOWERS_FETCH_SUCCESS,
- FOLLOWERS_EXPAND_SUCCESS,
- FOLLOWING_FETCH_SUCCESS,
- FOLLOWING_EXPAND_SUCCESS,
- ACCOUNT_TIMELINE_FETCH_SUCCESS,
- ACCOUNT_TIMELINE_EXPAND_SUCCESS,
- FOLLOW_REQUESTS_FETCH_SUCCESS,
- FOLLOW_REQUESTS_EXPAND_SUCCESS,
- ACCOUNT_FOLLOW_SUCCESS,
- ACCOUNT_UNFOLLOW_SUCCESS
-} from '../actions/accounts';
-import {
- BLOCKS_FETCH_SUCCESS,
- BLOCKS_EXPAND_SUCCESS
-} from '../actions/blocks';
-import {
- MUTES_FETCH_SUCCESS,
- MUTES_EXPAND_SUCCESS
-} from '../actions/mutes';
-import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose';
-import {
- REBLOG_SUCCESS,
- UNREBLOG_SUCCESS,
- FAVOURITE_SUCCESS,
- UNFAVOURITE_SUCCESS,
- REBLOGS_FETCH_SUCCESS,
- FAVOURITES_FETCH_SUCCESS
-} from '../actions/interactions';
-import {
- TIMELINE_REFRESH_SUCCESS,
- TIMELINE_UPDATE,
- TIMELINE_EXPAND_SUCCESS
-} from '../actions/timelines';
-import {
- STATUS_FETCH_SUCCESS,
- CONTEXT_FETCH_SUCCESS
-} from '../actions/statuses';
-import { SEARCH_FETCH_SUCCESS } from '../actions/search';
-import {
- NOTIFICATIONS_UPDATE,
- NOTIFICATIONS_REFRESH_SUCCESS,
- NOTIFICATIONS_EXPAND_SUCCESS
-} from '../actions/notifications';
-import {
- FAVOURITED_STATUSES_FETCH_SUCCESS,
- FAVOURITED_STATUSES_EXPAND_SUCCESS
-} from '../actions/favourites';
-import { STORE_HYDRATE } from '../actions/store';
-import Immutable from 'immutable';
-
-const normalizeAccount = (state, account) => state.set(account.id, Immutable.fromJS(account));
-
-const normalizeAccounts = (state, accounts) => {
- accounts.forEach(account => {
- state = normalizeAccount(state, account);
- });
-
- return state;
-};
-
-const normalizeAccountFromStatus = (state, status) => {
- state = normalizeAccount(state, status.account);
-
- if (status.reblog && status.reblog.account) {
- state = normalizeAccount(state, status.reblog.account);
- }
-
- return state;
-};
-
-const normalizeAccountsFromStatuses = (state, statuses) => {
- statuses.forEach(status => {
- state = normalizeAccountFromStatus(state, status);
- });
-
- return state;
-};
-
-const initialState = Immutable.Map();
-
-export default function accounts(state = initialState, action) {
- switch(action.type) {
- case STORE_HYDRATE:
- return state.merge(action.state.get('accounts'));
- case ACCOUNT_FETCH_SUCCESS:
- case NOTIFICATIONS_UPDATE:
- return normalizeAccount(state, action.account);
- case FOLLOWERS_FETCH_SUCCESS:
- case FOLLOWERS_EXPAND_SUCCESS:
- case FOLLOWING_FETCH_SUCCESS:
- case FOLLOWING_EXPAND_SUCCESS:
- case REBLOGS_FETCH_SUCCESS:
- case FAVOURITES_FETCH_SUCCESS:
- case COMPOSE_SUGGESTIONS_READY:
- case FOLLOW_REQUESTS_FETCH_SUCCESS:
- case FOLLOW_REQUESTS_EXPAND_SUCCESS:
- case BLOCKS_FETCH_SUCCESS:
- case BLOCKS_EXPAND_SUCCESS:
- case MUTES_FETCH_SUCCESS:
- case MUTES_EXPAND_SUCCESS:
- return normalizeAccounts(state, action.accounts);
- case NOTIFICATIONS_REFRESH_SUCCESS:
- case NOTIFICATIONS_EXPAND_SUCCESS:
- case SEARCH_FETCH_SUCCESS:
- return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses);
- case TIMELINE_REFRESH_SUCCESS:
- case TIMELINE_EXPAND_SUCCESS:
- case ACCOUNT_TIMELINE_FETCH_SUCCESS:
- case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
- case CONTEXT_FETCH_SUCCESS:
- case FAVOURITED_STATUSES_FETCH_SUCCESS:
- case FAVOURITED_STATUSES_EXPAND_SUCCESS:
- return normalizeAccountsFromStatuses(state, action.statuses);
- case REBLOG_SUCCESS:
- case FAVOURITE_SUCCESS:
- case UNREBLOG_SUCCESS:
- case UNFAVOURITE_SUCCESS:
- return normalizeAccountFromStatus(state, action.response);
- case TIMELINE_UPDATE:
- case STATUS_FETCH_SUCCESS:
- return normalizeAccountFromStatus(state, action.status);
- case ACCOUNT_FOLLOW_SUCCESS:
- return state.updateIn([action.relationship.id, 'followers_count'], num => num + 1);
- case ACCOUNT_UNFOLLOW_SUCCESS:
- return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1));
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/alerts.jsx b/app/assets/javascripts/components/reducers/alerts.jsx
@@ -1,25 +0,0 @@
-import {
- ALERT_SHOW,
- ALERT_DISMISS,
- ALERT_CLEAR
-} from '../actions/alerts';
-import Immutable from 'immutable';
-
-const initialState = Immutable.List([]);
-
-export default function alerts(state = initialState, action) {
- switch(action.type) {
- case ALERT_SHOW:
- return state.push(Immutable.Map({
- key: state.size > 0 ? state.last().get('key') + 1 : 0,
- title: action.title,
- message: action.message
- }));
- case ALERT_DISMISS:
- return state.filterNot(item => item.get('key') === action.alert.key);
- case ALERT_CLEAR:
- return state.clear();
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/cards.jsx b/app/assets/javascripts/components/reducers/cards.jsx
@@ -1,14 +0,0 @@
-import { STATUS_CARD_FETCH_SUCCESS } from '../actions/cards';
-
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map();
-
-export default function cards(state = initialState, action) {
- switch(action.type) {
- case STATUS_CARD_FETCH_SUCCESS:
- return state.set(action.id, Immutable.fromJS(action.card));
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx
@@ -1,232 +0,0 @@
-import {
- COMPOSE_MOUNT,
- COMPOSE_UNMOUNT,
- COMPOSE_CHANGE,
- COMPOSE_REPLY,
- COMPOSE_REPLY_CANCEL,
- COMPOSE_MENTION,
- COMPOSE_SUBMIT_REQUEST,
- COMPOSE_SUBMIT_SUCCESS,
- COMPOSE_SUBMIT_FAIL,
- COMPOSE_UPLOAD_REQUEST,
- COMPOSE_UPLOAD_SUCCESS,
- COMPOSE_UPLOAD_FAIL,
- COMPOSE_UPLOAD_UNDO,
- COMPOSE_UPLOAD_PROGRESS,
- COMPOSE_SUGGESTIONS_CLEAR,
- COMPOSE_SUGGESTIONS_READY,
- COMPOSE_SUGGESTION_SELECT,
- COMPOSE_SENSITIVITY_CHANGE,
- 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';
-import { STORE_HYDRATE } from '../actions/store';
-import Immutable from 'immutable';
-import uuid from '../uuid';
-
-const initialState = Immutable.Map({
- mounted: false,
- sensitive: false,
- spoiler: false,
- spoiler_text: '',
- privacy: null,
- text: '',
- focusDate: null,
- preselectDate: null,
- in_reply_to: null,
- is_submitting: false,
- is_uploading: false,
- progress: 0,
- media_attachments: Immutable.List(),
- suggestion_token: null,
- suggestions: Immutable.List(),
- me: null,
- default_privacy: 'public',
- resetFileKey: Math.floor((Math.random() * 0x10000)),
- idempotencyKey: null
-});
-
-function statusToTextMentions(state, status) {
- let set = Immutable.OrderedSet([]);
- let me = state.get('me');
-
- if (status.getIn(['account', 'id']) !== me) {
- set = set.add(`@${status.getIn(['account', 'acct'])} `);
- }
-
- return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
-};
-
-function clearAll(state) {
- return state.withMutations(map => {
- map.set('text', '');
- map.set('spoiler', false);
- map.set('spoiler_text', '');
- map.set('is_submitting', false);
- map.set('in_reply_to', null);
- map.set('privacy', state.get('default_privacy'));
- map.set('sensitive', false);
- map.update('media_attachments', list => list.clear());
- map.set('idempotencyKey', uuid());
- });
-};
-
-function appendMedia(state, media) {
- return state.withMutations(map => {
- map.update('media_attachments', list => list.push(media));
- map.set('is_uploading', false);
- map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
- map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
- map.set('focusDate', new Date());
- map.set('idempotencyKey', uuid());
- });
-};
-
-function removeMedia(state, mediaId) {
- const media = state.get('media_attachments').find(item => item.get('id') === mediaId);
- const prevSize = state.get('media_attachments').size;
-
- return state.withMutations(map => {
- map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
- map.update('text', text => text.replace(media.get('text_url'), '').trim());
- map.set('idempotencyKey', uuid());
-
- if (prevSize === 1) {
- map.set('sensitive', false);
- }
- });
-};
-
-const insertSuggestion = (state, position, token, completion) => {
- return state.withMutations(map => {
- map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`);
- map.set('suggestion_token', null);
- map.update('suggestions', Immutable.List(), list => list.clear());
- map.set('focusDate', new Date());
- map.set('idempotencyKey', uuid());
- });
-};
-
-const insertEmoji = (state, position, emojiData) => {
- const emoji = emojiData.shortname;
-
- return state.withMutations(map => {
- map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`);
- map.set('focusDate', new Date());
- map.set('idempotencyKey', uuid());
- });
-};
-
-const privacyPreference = (a, b) => {
- if (a === 'direct' || b === 'direct') {
- return 'direct';
- } else if (a === 'private' || b === 'private') {
- return 'private';
- } else if (a === 'unlisted' || b === 'unlisted') {
- return 'unlisted';
- } else {
- return 'public';
- }
-};
-
-export default function compose(state = initialState, action) {
- switch(action.type) {
- case STORE_HYDRATE:
- return clearAll(state.merge(action.state.get('compose')));
- case COMPOSE_MOUNT:
- return state.set('mounted', true);
- case COMPOSE_UNMOUNT:
- return state.set('mounted', false);
- case COMPOSE_SENSITIVITY_CHANGE:
- return state
- .set('sensitive', !state.get('sensitive'))
- .set('idempotencyKey', uuid());
- case COMPOSE_SPOILERNESS_CHANGE:
- return state.withMutations(map => {
- map.set('spoiler_text', '');
- map.set('spoiler', !state.get('spoiler'));
- map.set('idempotencyKey', uuid());
- });
- case COMPOSE_SPOILER_TEXT_CHANGE:
- return state
- .set('spoiler_text', action.text)
- .set('idempotencyKey', uuid());
- case COMPOSE_VISIBILITY_CHANGE:
- return state
- .set('privacy', action.value)
- .set('idempotencyKey', uuid());
- case COMPOSE_CHANGE:
- return state
- .set('text', action.text)
- .set('idempotencyKey', uuid());
- case COMPOSE_REPLY:
- return state.withMutations(map => {
- map.set('in_reply_to', action.status.get('id'));
- map.set('text', statusToTextMentions(state, action.status));
- map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
- map.set('focusDate', new Date());
- map.set('preselectDate', new Date());
- map.set('idempotencyKey', uuid());
-
- if (action.status.get('spoiler_text').length > 0) {
- map.set('spoiler', true);
- map.set('spoiler_text', action.status.get('spoiler_text'));
- } else {
- map.set('spoiler', false);
- map.set('spoiler_text', '');
- }
- });
- case COMPOSE_REPLY_CANCEL:
- return state.withMutations(map => {
- map.set('in_reply_to', null);
- map.set('text', '');
- map.set('spoiler', false);
- map.set('spoiler_text', '');
- map.set('privacy', state.get('default_privacy'));
- map.set('idempotencyKey', uuid());
- });
- case COMPOSE_SUBMIT_REQUEST:
- return state.set('is_submitting', true);
- case COMPOSE_SUBMIT_SUCCESS:
- return clearAll(state);
- case COMPOSE_SUBMIT_FAIL:
- return state.set('is_submitting', false);
- case COMPOSE_UPLOAD_REQUEST:
- return state.withMutations(map => {
- map.set('is_uploading', true);
- });
- case COMPOSE_UPLOAD_SUCCESS:
- return appendMedia(state, Immutable.fromJS(action.media));
- case COMPOSE_UPLOAD_FAIL:
- return state.set('is_uploading', false);
- case COMPOSE_UPLOAD_UNDO:
- return removeMedia(state, action.media_id);
- case COMPOSE_UPLOAD_PROGRESS:
- return state.set('progress', Math.round((action.loaded / action.total) * 100));
- case COMPOSE_MENTION:
- return state
- .update('text', text => `${text}@${action.account.get('acct')} `)
- .set('focusDate', new Date())
- .set('idempotencyKey', uuid());
- case COMPOSE_SUGGESTIONS_CLEAR:
- return state.update('suggestions', Immutable.List(), list => list.clear()).set('suggestion_token', null);
- case COMPOSE_SUGGESTIONS_READY:
- return state.set('suggestions', Immutable.List(action.accounts.map(item => item.id))).set('suggestion_token', action.token);
- case COMPOSE_SUGGESTION_SELECT:
- return insertSuggestion(state, action.position, action.token, action.completion);
- case TIMELINE_DELETE:
- if (action.id === state.get('in_reply_to')) {
- return state.set('in_reply_to', null);
- } else {
- return state;
- }
- case COMPOSE_EMOJI_INSERT:
- return insertEmoji(state, action.position, action.emoji);
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/index.jsx b/app/assets/javascripts/components/reducers/index.jsx
@@ -1,36 +0,0 @@
-import { combineReducers } from 'redux-immutable';
-import timelines from './timelines';
-import meta from './meta';
-import compose from './compose';
-import alerts from './alerts';
-import { loadingBarReducer } from 'react-redux-loading-bar';
-import modal from './modal';
-import user_lists from './user_lists';
-import accounts from './accounts';
-import statuses from './statuses';
-import relationships from './relationships';
-import search from './search';
-import notifications from './notifications';
-import settings from './settings';
-import status_lists from './status_lists';
-import cards from './cards';
-import reports from './reports';
-
-export default combineReducers({
- timelines,
- meta,
- compose,
- alerts,
- loadingBar: loadingBarReducer,
- modal,
- user_lists,
- status_lists,
- accounts,
- statuses,
- relationships,
- search,
- notifications,
- settings,
- cards,
- reports
-});
diff --git a/app/assets/javascripts/components/reducers/meta.jsx b/app/assets/javascripts/components/reducers/meta.jsx
@@ -1,17 +0,0 @@
-import { STORE_HYDRATE } from '../actions/store';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
- streaming_api_base_url: null,
- access_token: null,
- me: null
-});
-
-export default function meta(state = initialState, action) {
- switch(action.type) {
- case STORE_HYDRATE:
- return state.merge(action.state.get('meta'));
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/modal.jsx b/app/assets/javascripts/components/reducers/modal.jsx
@@ -1,18 +0,0 @@
-import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal';
-import Immutable from 'immutable';
-
-const initialState = {
- modalType: null,
- modalProps: {}
-};
-
-export default function modal(state = initialState, action) {
- switch(action.type) {
- case MODAL_OPEN:
- return { modalType: action.modalType, modalProps: action.modalProps };
- case MODAL_CLOSE:
- return initialState;
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/notifications.jsx b/app/assets/javascripts/components/reducers/notifications.jsx
@@ -1,104 +0,0 @@
-import {
- NOTIFICATIONS_UPDATE,
- NOTIFICATIONS_REFRESH_SUCCESS,
- NOTIFICATIONS_EXPAND_SUCCESS,
- NOTIFICATIONS_REFRESH_REQUEST,
- NOTIFICATIONS_EXPAND_REQUEST,
- NOTIFICATIONS_REFRESH_FAIL,
- NOTIFICATIONS_EXPAND_FAIL,
- NOTIFICATIONS_CLEAR,
- NOTIFICATIONS_SCROLL_TOP
-} from '../actions/notifications';
-import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
- items: Immutable.List(),
- next: null,
- top: true,
- unread: 0,
- loaded: false,
- isLoading: true
-});
-
-const notificationToMap = notification => Immutable.Map({
- id: notification.id,
- type: notification.type,
- account: notification.account.id,
- status: notification.status ? notification.status.id : null
-});
-
-const normalizeNotification = (state, notification) => {
- if (!state.get('top')) {
- state = state.update('unread', unread => unread + 1);
- }
-
- return state.update('items', list => list.unshift(notificationToMap(notification)));
-};
-
-const normalizeNotifications = (state, notifications, next) => {
- let items = Immutable.List();
- const loaded = state.get('loaded');
-
- notifications.forEach((n, i) => {
- items = items.set(i, notificationToMap(n));
- });
-
- if (state.get('next') === null) {
- state = state.set('next', next);
- }
-
- return state
- .update('items', list => loaded ? list.unshift(...items) : list.push(...items))
- .set('loaded', true)
- .set('isLoading', false);
-};
-
-const appendNormalizedNotifications = (state, notifications, next) => {
- let items = Immutable.List();
-
- notifications.forEach((n, i) => {
- items = items.set(i, notificationToMap(n));
- });
-
- return state
- .update('items', list => list.push(...items))
- .set('next', next)
- .set('isLoading', false);
-};
-
-const filterNotifications = (state, relationship) => {
- return state.update('items', list => list.filterNot(item => item.get('account') === relationship.id));
-};
-
-const updateTop = (state, top) => {
- if (top) {
- state = state.set('unread', 0);
- }
-
- return state.set('top', top);
-};
-
-export default function notifications(state = initialState, action) {
- switch(action.type) {
- case NOTIFICATIONS_REFRESH_REQUEST:
- case NOTIFICATIONS_EXPAND_REQUEST:
- case NOTIFICATIONS_REFRESH_FAIL:
- case NOTIFICATIONS_EXPAND_FAIL:
- return state.set('isLoading', true);
- case NOTIFICATIONS_SCROLL_TOP:
- return updateTop(state, action.top);
- case NOTIFICATIONS_UPDATE:
- return normalizeNotification(state, action.notification);
- case NOTIFICATIONS_REFRESH_SUCCESS:
- return normalizeNotifications(state, action.notifications, action.next);
- case NOTIFICATIONS_EXPAND_SUCCESS:
- return appendNormalizedNotifications(state, action.notifications, action.next);
- case ACCOUNT_BLOCK_SUCCESS:
- return filterNotifications(state, action.relationship);
- case NOTIFICATIONS_CLEAR:
- return state.set('items', Immutable.List()).set('next', null);
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/relationships.jsx b/app/assets/javascripts/components/reducers/relationships.jsx
@@ -1,38 +0,0 @@
-import {
- ACCOUNT_FOLLOW_SUCCESS,
- ACCOUNT_UNFOLLOW_SUCCESS,
- ACCOUNT_BLOCK_SUCCESS,
- ACCOUNT_UNBLOCK_SUCCESS,
- ACCOUNT_MUTE_SUCCESS,
- ACCOUNT_UNMUTE_SUCCESS,
- RELATIONSHIPS_FETCH_SUCCESS
-} from '../actions/accounts';
-import Immutable from 'immutable';
-
-const normalizeRelationship = (state, relationship) => state.set(relationship.id, Immutable.fromJS(relationship));
-
-const normalizeRelationships = (state, relationships) => {
- relationships.forEach(relationship => {
- state = normalizeRelationship(state, relationship);
- });
-
- return state;
-};
-
-const initialState = Immutable.Map();
-
-export default function relationships(state = initialState, action) {
- switch(action.type) {
- case ACCOUNT_FOLLOW_SUCCESS:
- case ACCOUNT_UNFOLLOW_SUCCESS:
- case ACCOUNT_BLOCK_SUCCESS:
- case ACCOUNT_UNBLOCK_SUCCESS:
- case ACCOUNT_MUTE_SUCCESS:
- case ACCOUNT_UNMUTE_SUCCESS:
- return normalizeRelationship(state, action.relationship);
- case RELATIONSHIPS_FETCH_SUCCESS:
- return normalizeRelationships(state, action.relationships);
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/reports.jsx b/app/assets/javascripts/components/reducers/reports.jsx
@@ -1,60 +0,0 @@
-import {
- REPORT_INIT,
- REPORT_SUBMIT_REQUEST,
- REPORT_SUBMIT_SUCCESS,
- REPORT_SUBMIT_FAIL,
- REPORT_CANCEL,
- REPORT_STATUS_TOGGLE,
- REPORT_COMMENT_CHANGE
-} from '../actions/reports';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
- new: Immutable.Map({
- isSubmitting: false,
- account_id: null,
- status_ids: Immutable.Set(),
- comment: ''
- })
-});
-
-export default function reports(state = initialState, action) {
- switch(action.type) {
- case REPORT_INIT:
- return state.withMutations(map => {
- map.setIn(['new', 'isSubmitting'], false);
- map.setIn(['new', 'account_id'], action.account.get('id'));
-
- if (state.getIn(['new', 'account_id']) !== action.account.get('id')) {
- map.setIn(['new', 'status_ids'], action.status ? Immutable.Set([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : Immutable.Set());
- map.setIn(['new', 'comment'], '');
- } else {
- map.updateIn(['new', 'status_ids'], Immutable.Set(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id'))));
- }
- });
- case REPORT_STATUS_TOGGLE:
- return state.updateIn(['new', 'status_ids'], Immutable.Set(), set => {
- if (action.checked) {
- return set.add(action.statusId);
- }
-
- return set.remove(action.statusId);
- });
- case REPORT_COMMENT_CHANGE:
- return state.setIn(['new', 'comment'], action.comment);
- case REPORT_SUBMIT_REQUEST:
- return state.setIn(['new', 'isSubmitting'], true);
- case REPORT_SUBMIT_FAIL:
- return state.setIn(['new', 'isSubmitting'], false);
- case REPORT_CANCEL:
- case REPORT_SUBMIT_SUCCESS:
- return state.withMutations(map => {
- map.setIn(['new', 'account_id'], null);
- map.setIn(['new', 'status_ids'], Immutable.Set());
- map.setIn(['new', 'comment'], '');
- map.setIn(['new', 'isSubmitting'], false);
- });
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/search.jsx b/app/assets/javascripts/components/reducers/search.jsx
@@ -1,96 +0,0 @@
-import {
- SEARCH_CHANGE,
- SEARCH_CLEAR,
- SEARCH_FETCH_SUCCESS,
- SEARCH_SHOW
-} from '../actions/search';
-import { COMPOSE_MENTION, COMPOSE_REPLY } from '../actions/compose';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
- value: '',
- submitted: false,
- hidden: false,
- 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:
- return state.set('value', action.value);
- case SEARCH_CLEAR:
- return state.withMutations(map => {
- map.set('value', '');
- map.set('results', Immutable.Map());
- map.set('submitted', false);
- map.set('hidden', false);
- });
- case SEARCH_SHOW:
- return state.set('hidden', false);
- case COMPOSE_REPLY:
- case COMPOSE_MENTION:
- return state.set('hidden', true);
- case SEARCH_FETCH_SUCCESS:
- return state.set('results', Immutable.Map({
- accounts: Immutable.List(action.results.accounts.map(item => item.id)),
- statuses: Immutable.List(action.results.statuses.map(item => item.id)),
- hashtags: Immutable.List(action.results.hashtags)
- })).set('submitted', true);
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/settings.jsx b/app/assets/javascripts/components/reducers/settings.jsx
@@ -1,48 +0,0 @@
-import { SETTING_CHANGE } from '../actions/settings';
-import { STORE_HYDRATE } from '../actions/store';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
- onboarded: false,
-
- home: Immutable.Map({
- shows: Immutable.Map({
- reblog: true,
- reply: true
- })
- }),
-
- notifications: Immutable.Map({
- alerts: Immutable.Map({
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- }),
-
- shows: Immutable.Map({
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- }),
-
- sounds: Immutable.Map({
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- })
- })
-});
-
-export default function settings(state = initialState, action) {
- switch(action.type) {
- case STORE_HYDRATE:
- return state.mergeDeep(action.state.get('settings'));
- case SETTING_CHANGE:
- return state.setIn(action.key, action.value);
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/status_lists.jsx b/app/assets/javascripts/components/reducers/status_lists.jsx
@@ -1,39 +0,0 @@
-import {
- FAVOURITED_STATUSES_FETCH_SUCCESS,
- FAVOURITED_STATUSES_EXPAND_SUCCESS
-} from '../actions/favourites';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
- favourites: Immutable.Map({
- next: null,
- loaded: false,
- items: Immutable.List()
- })
-});
-
-const normalizeList = (state, listType, statuses, next) => {
- return state.update(listType, listMap => listMap.withMutations(map => {
- map.set('next', next);
- map.set('loaded', true);
- map.set('items', Immutable.List(statuses.map(item => item.id)));
- }));
-};
-
-const appendToList = (state, listType, statuses, next) => {
- return state.update(listType, listMap => listMap.withMutations(map => {
- map.set('next', next);
- map.set('items', map.get('items').push(...statuses.map(item => item.id)));
- }));
-};
-
-export default function statusLists(state = initialState, action) {
- switch(action.type) {
- case FAVOURITED_STATUSES_FETCH_SUCCESS:
- return normalizeList(state, 'favourites', action.statuses, action.next);
- case FAVOURITED_STATUSES_EXPAND_SUCCESS:
- return appendToList(state, 'favourites', action.statuses, action.next);
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/statuses.jsx b/app/assets/javascripts/components/reducers/statuses.jsx
@@ -1,124 +0,0 @@
-import {
- REBLOG_REQUEST,
- REBLOG_SUCCESS,
- REBLOG_FAIL,
- UNREBLOG_SUCCESS,
- FAVOURITE_REQUEST,
- FAVOURITE_SUCCESS,
- FAVOURITE_FAIL,
- UNFAVOURITE_SUCCESS
-} from '../actions/interactions';
-import {
- STATUS_FETCH_SUCCESS,
- CONTEXT_FETCH_SUCCESS
-} from '../actions/statuses';
-import {
- TIMELINE_REFRESH_SUCCESS,
- TIMELINE_UPDATE,
- TIMELINE_DELETE,
- TIMELINE_EXPAND_SUCCESS
-} from '../actions/timelines';
-import {
- ACCOUNT_TIMELINE_FETCH_SUCCESS,
- ACCOUNT_TIMELINE_EXPAND_SUCCESS,
- ACCOUNT_BLOCK_SUCCESS
-} from '../actions/accounts';
-import {
- NOTIFICATIONS_UPDATE,
- NOTIFICATIONS_REFRESH_SUCCESS,
- NOTIFICATIONS_EXPAND_SUCCESS
-} from '../actions/notifications';
-import {
- FAVOURITED_STATUSES_FETCH_SUCCESS,
- FAVOURITED_STATUSES_EXPAND_SUCCESS
-} from '../actions/favourites';
-import { SEARCH_FETCH_SUCCESS } from '../actions/search';
-import Immutable from 'immutable';
-
-const normalizeStatus = (state, status) => {
- if (!status) {
- return state;
- }
-
- const normalStatus = { ...status };
- normalStatus.account = status.account.id;
-
- if (status.reblog && status.reblog.id) {
- state = normalizeStatus(state, status.reblog);
- normalStatus.reblog = status.reblog.id;
- }
-
- const linebreakComplemented = status.content.replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
- normalStatus.unescaped_content = new DOMParser().parseFromString(linebreakComplemented, 'text/html').documentElement.textContent;
-
- return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(normalStatus)));
-};
-
-const normalizeStatuses = (state, statuses) => {
- statuses.forEach(status => {
- state = normalizeStatus(state, status);
- });
-
- return state;
-};
-
-const deleteStatus = (state, id, references) => {
- references.forEach(ref => {
- state = deleteStatus(state, ref[0], []);
- });
-
- return state.delete(id);
-};
-
-const filterStatuses = (state, relationship) => {
- state.forEach(status => {
- if (status.get('account') !== relationship.id) {
- return;
- }
-
- state = deleteStatus(state, status.get('id'), state.filter(item => item.get('reblog') === status.get('id')));
- });
-
- return state;
-};
-
-const initialState = Immutable.Map();
-
-export default function statuses(state = initialState, action) {
- switch(action.type) {
- case TIMELINE_UPDATE:
- case STATUS_FETCH_SUCCESS:
- case NOTIFICATIONS_UPDATE:
- return normalizeStatus(state, action.status);
- case REBLOG_SUCCESS:
- case UNREBLOG_SUCCESS:
- case FAVOURITE_SUCCESS:
- case UNFAVOURITE_SUCCESS:
- return normalizeStatus(state, action.response);
- case FAVOURITE_REQUEST:
- return state.setIn([action.status.get('id'), 'favourited'], true);
- case FAVOURITE_FAIL:
- return state.setIn([action.status.get('id'), 'favourited'], false);
- case REBLOG_REQUEST:
- return state.setIn([action.status.get('id'), 'reblogged'], true);
- case REBLOG_FAIL:
- return state.setIn([action.status.get('id'), 'reblogged'], false);
- case TIMELINE_REFRESH_SUCCESS:
- case TIMELINE_EXPAND_SUCCESS:
- case ACCOUNT_TIMELINE_FETCH_SUCCESS:
- case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
- case CONTEXT_FETCH_SUCCESS:
- case NOTIFICATIONS_REFRESH_SUCCESS:
- case NOTIFICATIONS_EXPAND_SUCCESS:
- case FAVOURITED_STATUSES_FETCH_SUCCESS:
- case FAVOURITED_STATUSES_EXPAND_SUCCESS:
- case SEARCH_FETCH_SUCCESS:
- return normalizeStatuses(state, action.statuses);
- case TIMELINE_DELETE:
- return deleteStatus(state, action.id, action.references);
- case ACCOUNT_BLOCK_SUCCESS:
- return filterStatuses(state, action.relationship);
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx
@@ -1,317 +0,0 @@
-import {
- TIMELINE_REFRESH_REQUEST,
- TIMELINE_REFRESH_SUCCESS,
- TIMELINE_REFRESH_FAIL,
- TIMELINE_UPDATE,
- TIMELINE_DELETE,
- TIMELINE_EXPAND_SUCCESS,
- TIMELINE_EXPAND_REQUEST,
- TIMELINE_EXPAND_FAIL,
- TIMELINE_SCROLL_TOP,
- TIMELINE_CONNECT,
- TIMELINE_DISCONNECT
-} from '../actions/timelines';
-import {
- REBLOG_SUCCESS,
- UNREBLOG_SUCCESS,
- FAVOURITE_SUCCESS,
- UNFAVOURITE_SUCCESS
-} from '../actions/interactions';
-import {
- ACCOUNT_TIMELINE_FETCH_REQUEST,
- ACCOUNT_TIMELINE_FETCH_SUCCESS,
- ACCOUNT_TIMELINE_FETCH_FAIL,
- ACCOUNT_TIMELINE_EXPAND_REQUEST,
- ACCOUNT_TIMELINE_EXPAND_SUCCESS,
- ACCOUNT_TIMELINE_EXPAND_FAIL,
- ACCOUNT_BLOCK_SUCCESS,
- ACCOUNT_MUTE_SUCCESS
-} from '../actions/accounts';
-import {
- CONTEXT_FETCH_SUCCESS
-} from '../actions/statuses';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
- home: Immutable.Map({
- path: () => '/api/v1/timelines/home',
- next: null,
- isLoading: false,
- online: false,
- loaded: false,
- top: true,
- unread: 0,
- items: Immutable.List()
- }),
-
- public: Immutable.Map({
- path: () => '/api/v1/timelines/public',
- next: null,
- isLoading: false,
- online: false,
- loaded: false,
- top: true,
- unread: 0,
- items: Immutable.List()
- }),
-
- community: Immutable.Map({
- path: () => '/api/v1/timelines/public',
- next: null,
- params: { local: true },
- isLoading: false,
- online: false,
- loaded: false,
- top: true,
- unread: 0,
- items: Immutable.List()
- }),
-
- tag: Immutable.Map({
- path: (id) => `/api/v1/timelines/tag/${id}`,
- next: null,
- isLoading: false,
- id: null,
- loaded: false,
- top: true,
- unread: 0,
- items: Immutable.List()
- }),
-
- accounts_timelines: Immutable.Map(),
- ancestors: Immutable.Map(),
- descendants: Immutable.Map()
-});
-
-const normalizeStatus = (state, status) => {
- const replyToId = status.get('in_reply_to_id');
- const id = status.get('id');
-
- if (replyToId) {
- if (!state.getIn(['descendants', replyToId], Immutable.List()).includes(id)) {
- state = state.updateIn(['descendants', replyToId], Immutable.List(), set => set.push(id));
- }
-
- if (!state.getIn(['ancestors', id], Immutable.List()).includes(replyToId)) {
- state = state.updateIn(['ancestors', id], Immutable.List(), set => set.push(replyToId));
- }
- }
-
- return state;
-};
-
-const normalizeTimeline = (state, timeline, statuses, next) => {
- let ids = Immutable.List();
- const loaded = state.getIn([timeline, 'loaded']);
-
- statuses.forEach((status, i) => {
- state = normalizeStatus(state, status);
- ids = ids.set(i, status.get('id'));
- });
-
- state = state.setIn([timeline, 'loaded'], true);
- state = state.setIn([timeline, 'isLoading'], false);
-
- if (state.getIn([timeline, 'next']) === null) {
- state = state.setIn([timeline, 'next'], next);
- }
-
- return state.updateIn([timeline, 'items'], Immutable.List(), list => (loaded ? list.unshift(...ids) : ids));
-};
-
-const appendNormalizedTimeline = (state, timeline, statuses, next) => {
- let moreIds = Immutable.List();
-
- statuses.forEach((status, i) => {
- state = normalizeStatus(state, status);
- moreIds = moreIds.set(i, status.get('id'));
- });
-
- state = state.setIn([timeline, 'isLoading'], false);
- state = state.setIn([timeline, 'next'], next);
-
- return state.updateIn([timeline, 'items'], Immutable.List(), list => list.push(...moreIds));
-};
-
-const normalizeAccountTimeline = (state, accountId, statuses, replace = false) => {
- let ids = Immutable.List();
-
- statuses.forEach((status, i) => {
- state = normalizeStatus(state, status);
- ids = ids.set(i, status.get('id'));
- });
-
- return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map
- .set('isLoading', false)
- .set('loaded', true)
- .set('next', true)
- .update('items', Immutable.List(), list => (replace ? ids : list.unshift(...ids))));
-};
-
-const appendNormalizedAccountTimeline = (state, accountId, statuses, next) => {
- let moreIds = Immutable.List([]);
-
- statuses.forEach((status, i) => {
- state = normalizeStatus(state, status);
- moreIds = moreIds.set(i, status.get('id'));
- });
-
- return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map
- .set('isLoading', false)
- .set('next', next)
- .update('items', list => list.push(...moreIds)));
-};
-
-const updateTimeline = (state, timeline, status, references) => {
- const top = state.getIn([timeline, 'top']);
-
- state = normalizeStatus(state, status);
-
- if (!top) {
- state = state.updateIn([timeline, 'unread'], unread => unread + 1);
- }
-
- state = state.updateIn([timeline, 'items'], Immutable.List(), list => {
- if (top && list.size > 40) {
- list = list.take(20);
- }
-
- if (list.includes(status.get('id'))) {
- return list;
- }
-
- const reblogOfId = status.getIn(['reblog', 'id'], null);
-
- if (reblogOfId !== null) {
- list = list.filterNot(itemId => references.includes(itemId));
- }
-
- return list.unshift(status.get('id'));
- });
-
- return state;
-};
-
-const deleteStatus = (state, id, accountId, references, reblogOf) => {
- if (reblogOf) {
- // If we are deleting a reblog, just replace reblog with its original
- return state.updateIn(['home', 'items'], list => list.map(item => item === id ? reblogOf : item));
- }
-
- // Remove references from timelines
- ['home', 'public', 'community', 'tag'].forEach(function (timeline) {
- state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id));
- });
-
- // Remove references from account timelines
- state = state.updateIn(['accounts_timelines', accountId, 'items'], Immutable.List([]), list => list.filterNot(item => item === id));
-
- // Remove references from context
- state.getIn(['descendants', id], Immutable.List()).forEach(descendantId => {
- state = state.updateIn(['ancestors', descendantId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
- });
-
- state.getIn(['ancestors', id], Immutable.List()).forEach(ancestorId => {
- state = state.updateIn(['descendants', ancestorId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
- });
-
- state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]);
-
- // Remove reblogs of deleted status
- references.forEach(ref => {
- state = deleteStatus(state, ref[0], ref[1], []);
- });
-
- return state;
-};
-
-const filterTimelines = (state, relationship, statuses) => {
- let references;
-
- statuses.forEach(status => {
- if (status.get('account') !== relationship.id) {
- return;
- }
-
- references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
- state = deleteStatus(state, status.get('id'), status.get('account'), references);
- });
-
- return state;
-};
-
-const normalizeContext = (state, id, ancestors, descendants) => {
- const ancestorsIds = ancestors.map(ancestor => ancestor.get('id'));
- const descendantsIds = descendants.map(descendant => descendant.get('id'));
-
- return state.withMutations(map => {
- map.setIn(['ancestors', id], ancestorsIds);
- map.setIn(['descendants', id], descendantsIds);
- });
-};
-
-const resetTimeline = (state, timeline, id) => {
- if (timeline === 'tag' && typeof id !== 'undefined' && state.getIn([timeline, 'id']) !== id) {
- state = state.update(timeline, map => map
- .set('id', id)
- .set('isLoading', true)
- .set('loaded', false)
- .set('next', null)
- .set('top', true)
- .update('items', list => list.clear()));
- } else {
- state = state.setIn([timeline, 'isLoading'], true);
- }
-
- return state;
-};
-
-const updateTop = (state, timeline, top) => {
- if (top) {
- state = state.setIn([timeline, 'unread'], 0);
- }
-
- return state.setIn([timeline, 'top'], top);
-};
-
-export default function timelines(state = initialState, action) {
- switch(action.type) {
- case TIMELINE_REFRESH_REQUEST:
- case TIMELINE_EXPAND_REQUEST:
- return resetTimeline(state, action.timeline, action.id);
- case TIMELINE_REFRESH_FAIL:
- case TIMELINE_EXPAND_FAIL:
- return state.setIn([action.timeline, 'isLoading'], false);
- case TIMELINE_REFRESH_SUCCESS:
- return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
- case TIMELINE_EXPAND_SUCCESS:
- return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
- case TIMELINE_UPDATE:
- return updateTimeline(state, action.timeline, Immutable.fromJS(action.status), action.references);
- case TIMELINE_DELETE:
- return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
- case CONTEXT_FETCH_SUCCESS:
- return normalizeContext(state, action.id, Immutable.fromJS(action.ancestors), Immutable.fromJS(action.descendants));
- case ACCOUNT_TIMELINE_FETCH_REQUEST:
- case ACCOUNT_TIMELINE_EXPAND_REQUEST:
- return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', true));
- case ACCOUNT_TIMELINE_FETCH_FAIL:
- case ACCOUNT_TIMELINE_EXPAND_FAIL:
- return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', false));
- case ACCOUNT_TIMELINE_FETCH_SUCCESS:
- return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace);
- case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
- return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.next);
- case ACCOUNT_BLOCK_SUCCESS:
- case ACCOUNT_MUTE_SUCCESS:
- return filterTimelines(state, action.relationship, action.statuses);
- case TIMELINE_SCROLL_TOP:
- return updateTop(state, action.timeline, action.top);
- case TIMELINE_CONNECT:
- return state.setIn([action.timeline, 'online'], true);
- case TIMELINE_DISCONNECT:
- return state.setIn([action.timeline, 'online'], false);
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/reducers/user_lists.jsx b/app/assets/javascripts/components/reducers/user_lists.jsx
@@ -1,80 +0,0 @@
-import {
- FOLLOWERS_FETCH_SUCCESS,
- FOLLOWERS_EXPAND_SUCCESS,
- FOLLOWING_FETCH_SUCCESS,
- FOLLOWING_EXPAND_SUCCESS,
- FOLLOW_REQUESTS_FETCH_SUCCESS,
- FOLLOW_REQUESTS_EXPAND_SUCCESS,
- FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
- FOLLOW_REQUEST_REJECT_SUCCESS
-} from '../actions/accounts';
-import {
- REBLOGS_FETCH_SUCCESS,
- FAVOURITES_FETCH_SUCCESS
-} from '../actions/interactions';
-import {
- BLOCKS_FETCH_SUCCESS,
- BLOCKS_EXPAND_SUCCESS
-} from '../actions/blocks';
-import {
- MUTES_FETCH_SUCCESS,
- MUTES_EXPAND_SUCCESS
-} from '../actions/mutes';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
- followers: Immutable.Map(),
- following: Immutable.Map(),
- reblogged_by: Immutable.Map(),
- favourited_by: Immutable.Map(),
- follow_requests: Immutable.Map(),
- blocks: Immutable.Map(),
- mutes: Immutable.Map()
-});
-
-const normalizeList = (state, type, id, accounts, next) => {
- return state.setIn([type, id], Immutable.Map({
- next,
- items: Immutable.List(accounts.map(item => item.id))
- }));
-};
-
-const appendToList = (state, type, id, accounts, next) => {
- return state.updateIn([type, id], map => {
- return map.set('next', next).update('items', list => list.push(...accounts.map(item => item.id)));
- });
-};
-
-export default function userLists(state = initialState, action) {
- switch(action.type) {
- case FOLLOWERS_FETCH_SUCCESS:
- return normalizeList(state, 'followers', action.id, action.accounts, action.next);
- case FOLLOWERS_EXPAND_SUCCESS:
- return appendToList(state, 'followers', action.id, action.accounts, action.next);
- case FOLLOWING_FETCH_SUCCESS:
- return normalizeList(state, 'following', action.id, action.accounts, action.next);
- case FOLLOWING_EXPAND_SUCCESS:
- return appendToList(state, 'following', action.id, action.accounts, action.next);
- case REBLOGS_FETCH_SUCCESS:
- return state.setIn(['reblogged_by', action.id], Immutable.List(action.accounts.map(item => item.id)));
- case FAVOURITES_FETCH_SUCCESS:
- return state.setIn(['favourited_by', action.id], Immutable.List(action.accounts.map(item => item.id)));
- case FOLLOW_REQUESTS_FETCH_SUCCESS:
- return state.setIn(['follow_requests', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next);
- case FOLLOW_REQUESTS_EXPAND_SUCCESS:
- return state.updateIn(['follow_requests', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next);
- case FOLLOW_REQUEST_AUTHORIZE_SUCCESS:
- case FOLLOW_REQUEST_REJECT_SUCCESS:
- return state.updateIn(['follow_requests', 'items'], list => list.filterNot(item => item === action.id));
- case BLOCKS_FETCH_SUCCESS:
- return state.setIn(['blocks', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next);
- case BLOCKS_EXPAND_SUCCESS:
- return state.updateIn(['blocks', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next);
- case MUTES_FETCH_SUCCESS:
- return state.setIn(['mutes', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
- case MUTES_EXPAND_SUCCESS:
- return state.updateIn(['mutes', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
- default:
- return state;
- }
-};
diff --git a/app/assets/javascripts/components/rtl.jsx b/app/assets/javascripts/components/rtl.jsx
@@ -1,27 +0,0 @@
-// U+0590 to U+05FF - Hebrew
-// U+0600 to U+06FF - Arabic
-// U+0700 to U+074F - Syriac
-// U+0750 to U+077F - Arabic Supplement
-// U+0780 to U+07BF - Thaana
-// U+07C0 to U+07FF - N'Ko
-// U+0800 to U+083F - Samaritan
-// U+08A0 to U+08FF - Arabic Extended-A
-// U+FB1D to U+FB4F - Hebrew presentation forms
-// U+FB50 to U+FDFF - Arabic presentation forms A
-// U+FE70 to U+FEFF - Arabic presentation forms B
-
-const rtlChars = /[\u0590-\u083F]|[\u08A0-\u08FF]|[\uFB1D-\uFDFF]|[\uFE70-\uFEFF]/mg;
-
-export function isRtl(text) {
- if (text.length === 0) {
- return false;
- }
-
- const matches = text.match(rtlChars);
-
- if (!matches) {
- return false;
- }
-
- return matches.length / text.trim().length > 0.3;
-};
diff --git a/app/assets/javascripts/components/selectors/index.jsx b/app/assets/javascripts/components/selectors/index.jsx
@@ -1,72 +0,0 @@
-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 getAccountRelationship = (state, id) => state.getIn(['relationships', id], null);
-
-export const makeGetAccount = () => {
- return createSelector([getAccountBase, getAccountRelationship], (base, relationship) => {
- if (base === null) {
- return null;
- }
-
- return base.set('relationship', relationship);
- });
-};
-
-export const makeGetStatus = () => {
- return createSelector(
- [
- (state, id) => state.getIn(['statuses', id]),
- (state, id) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]),
- (state, id) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
- (state, id) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
- ],
-
- (statusBase, statusReblog, accountBase, accountReblog) => {
- if (!statusBase) {
- return null;
- }
-
- if (statusReblog) {
- statusReblog = statusReblog.set('account', accountReblog);
- } else {
- statusReblog = null;
- }
-
- return statusBase.withMutations(map => {
- map.set('reblog', statusReblog);
- map.set('account', accountBase);
- });
- }
- );
-};
-
-const getAlertsBase = state => state.get('alerts');
-
-export const getAlerts = createSelector([getAlertsBase], (base) => {
- let arr = [];
-
- base.forEach(item => {
- arr.push({
- message: item.get('message'),
- title: item.get('title'),
- key: item.get('key'),
- dismissAfter: 5000
- });
- });
-
- return arr;
-});
-
-export const makeGetNotification = () => {
- return createSelector([
- (_, base) => base,
- (state, _, accountId) => state.getIn(['accounts', accountId])
- ], (base, account) => {
- return base.set('account', account);
- });
-};
diff --git a/app/assets/javascripts/components/store/configureStore.jsx b/app/assets/javascripts/components/store/configureStore.jsx
@@ -1,16 +0,0 @@
-import { createStore, applyMiddleware, compose } from 'redux';
-import thunk from 'redux-thunk';
-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(
- thunk,
- loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
- errorsMiddleware(),
- soundsMiddleware()
- ), window.devToolsExtension ? window.devToolsExtension() : f => f));
-};
diff --git a/app/assets/javascripts/components/stream.jsx b/app/assets/javascripts/components/stream.jsx
@@ -1,22 +0,0 @@
-import WebSocketClient from 'websocket.js';
-
-const createWebSocketURL = (url) => {
- const a = document.createElement('a');
-
- a.href = url;
- a.href = a.href;
- a.protocol = a.protocol.replace('http', 'ws');
-
- return a.href;
-};
-
-export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) {
- const ws = new WebSocketClient(`${createWebSocketURL(streamingAPIBaseURL)}/api/v1/streaming/?access_token=${accessToken}&stream=${stream}`);
-
- ws.onopen = connected;
- ws.onmessage = e => received(JSON.parse(e.data));
- ws.onclose = disconnected;
- ws.onreconnect = reconnected;
-
- return ws;
-};
diff --git a/app/assets/javascripts/components/uuid.jsx b/app/assets/javascripts/components/uuid.jsx
@@ -1,3 +0,0 @@
-export default function uuid(a) {
- return a ? (a^Math.random() * 16 >> a / 4).toString(16) : ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, uuid);
-};
diff --git a/app/assets/javascripts/extras.jsx b/app/assets/javascripts/extras.jsx
@@ -1,49 +0,0 @@
-import emojify from './components/emoji';
-import { length } from 'stringz';
-
-$(() => {
- $.each($('.emojify'), (_, content) => {
- const $content = $(content);
- $content.html(emojify($content.html()));
- });
-
- $('.video-player video').on('click', e => {
- if (e.target.paused) {
- e.target.play();
- } else {
- e.target.pause();
- }
- });
-
- $('.media-spoiler').on('click', e => {
- $(e.target).hide();
- });
-
- $('.webapp-btn').on('click', e => {
- if (e.button === 0) {
- e.preventDefault();
- window.location.href = $(e.target).attr('href');
- }
- });
-
- $('.status__content__spoiler-link').on('click', e => {
- e.preventDefault();
- const contentEl = $(e.target).parent().parent().find('div');
-
- if (contentEl.is(':visible')) {
- contentEl.hide();
- $(e.target).parent().attr('style', 'margin-bottom: 0');
- } else {
- contentEl.show();
- $(e.target).parent().attr('style', null);
- }
- });
-
- // used on /settings/profile
- $('.account_display_name').on('input', e => {
- $('.name-counter').text(30 - length($(e.target).val()));
- });
- $('.account_note').on('input', e => {
- $('.note-counter').text(160 - length($(e.target).val()));
- });
-});
diff --git a/app/assets/images/.keep b/app/assets/stylesheets/.gitkeep
diff --git a/app/assets/stylesheets/about.scss b/app/assets/stylesheets/about.scss
@@ -1,374 +0,0 @@
-.about-body {
- .wrapper {
- max-width: 600px;
- margin: 0 auto;
- color: $color3;
- padding-top: 50px;
- padding-bottom: 50px;
-
- &.thicc {
- max-width: 700px;
- }
- }
-
- h1 {
- font: 46px/52px 'Roboto', sans-serif;
- font-weight: 600;
- margin-bottom: 20px;
- color: $color4;
- padding: 20px 0;
-
- img {
- margin-bottom: -5px;
- margin-right: 5px;
- width: 46px;
- height: 46px;
- }
- }
-
- h2 {
- font-family: 'Montserrat', sans-serif;
- font-size: 24px;
- line-height: 28px;
- font-weight: 400;
- margin-bottom: 20px;
- color: $color5;
- }
-
- h3 {
- font-family: 'Montserrat', sans-serif;
- font-size: 20px;
- line-height: 28px;
- font-weight: 400;
- margin-bottom: 20px;
- color: $color2;
- }
-
- ul, ol {
- list-style: inherit;
- margin-left: 20px;
-
- &[type='a'] {
- list-style-type: lower-alpha;
- }
-
- &[type='i'] {
- list-style-type: lower-roman;
- }
- }
-
- li > ol, li > ul {
- margin-top: 20px;
- }
-
- p, li {
- font: 16px/28px 'Montserrat', sans-serif;
- font-weight: 400;
- margin-bottom: 12px;
-
- a {
- color: $color4;
- text-decoration: underline;
- }
- }
-
- em {
- display: inline-block;
- padding: 7px 7px 5px 7px;
- margin: 0 2px;
- background: $color3;
- color: $color1;
- font: 16px/16px 'Montserrat', sans-serif;
- font-weight: 300;
- }
-
- .screenshot {
- box-shadow: 0 0 15px rgba($color8, 0.4);
- margin-bottom: 26px;
-
- img {
- max-width: 100%;
- height: auto;
- display: block;
- }
- }
-
- .actions {
- overflow: hidden;
- margin-bottom: 20px;
-
- .info {
- float: right;
- text-align: right;
- line-height: 36px;
-
- a {
- color: $color3;
- text-decoration: underline;
- }
- }
- }
-
- @media screen and (max-width: 625px) {
- .wrapper {
- padding: 20px;
- }
-
- .features-list {
- display: block;
- }
- }
-}
-
-.information-board {
- margin: 20px 0;
- display: flex;
- justify-content: space-between;
- border-top: 1px solid lighten($color1, 10%);
- border-bottom: 1px solid lighten($color1, 10%);
- padding-right: 14px;
-
- .section {
- flex: 1 0 0;
- padding: 14px;
- text-align: right;
- font: 16px/28px 'Montserrat', sans-serif;
-
- span, strong {
- display: block;
- }
-
- span {
- font-size: 16px;
-
- &:last-child {
- color: $color2;
- font-size: 14px;
- }
- }
-
- strong {
- font-weight: 500;
- font-size: 32px;
- line-height: 48px;
- color: $color5;
- }
- }
-
- @media screen and (max-width: 500px) {
- flex-direction: column;
-
- .section {
- text-align: left;
- }
- }
-}
-
-.owner {
- text-align: center;
-
- .avatar {
- width: 80px;
- height: 80px;
- margin: 0 auto;
- margin-bottom: 15px;
-
- img {
- display: block;
- width: 80px;
- height: 80px;
- border-radius: 48px;
- }
- }
-
- .name {
- font-size: 14px;
-
- a {
- display: block;
- color: $color5;
- text-decoration: none;
-
- &:hover {
- .display_name {
- text-decoration: underline;
- }
- }
- }
-
- .username {
- display: block;
- color: $color3;
- }
- }
-}
-
-.contact-email {
- text-align: center;
- margin: 40px 0;
-
- strong {
- display: block;
- color: $color5;
- word-break: break-word;
- }
-}
-
-.sidebar-layout {
- display: flex;
-
- .main {
- flex: 1 1 auto;
- padding: 14px 0;
-
- .panel {
- padding-right: 14px;
- }
- }
-
- .sidebar {
- border-left: 1px solid lighten($color1, 10%);
- width: 180px;
- flex: 0 0 auto;
- }
-
- .panel {
- .panel-header {
- background: lighten($color1, 10%);
- padding: 7px 14px;
- text-transform: uppercase;
- font-size: 12px;
- font-weight: 500;
- }
-
- .panel-body {
- padding: 14px;
- }
-
- .panel-list {
- ul {
- list-style: none;
- margin: 0;
-
- li {
- margin: 0;
- font-family: inherit;
- font-size: 13px;
- line-height: 18px;
-
- a {
- display: block;
- padding: 7px 14px;
- color: rgba($color5, 0.7);
- text-decoration: none;
- transition: all 200ms linear;
-
- i.fa {
- margin-right: 5px;
- }
-
- &:hover {
- color: $color5;
- background-color: darken($color1, 5%);
- transition: all 100ms linear;
- }
-
- &.selected {
- color: $color5;
- background-color: $color4;
-
- &:hover {
- background-color: lighten($color4, 5%);
- }
- }
- }
- }
- }
- }
- }
-
- @media screen and (max-width: 625px) {
- flex-direction: column;
-
- .sidebar {
- border: 1px solid lighten($color1, 10%);
- width: auto;
- }
- }
-}
-
-.features-list {
- display: flex;
- margin-bottom: 20px;
-
- .features-list__column {
- flex: 1 1 0;
-
- ul {
- list-style: none;
- }
-
- li {
- margin: 0;
- }
- }
-}
-
-.screenshot-with-signup {
- display: flex;
- margin-bottom: 20px;
-
- .mascot {
- flex: 1 1 auto;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
-
- img {
- display: block;
- margin: 0 auto;
- max-width: 100%;
- height: auto;
- }
- }
-
- .simple_form, .closed-registrations-message {
- width: 300px;
- flex: 0 0 auto;
- background: rgba(darken($color1, 7%), 0.5);
- padding: 14px;
- border-radius: 4px;
- box-shadow: 0 0 15px rgba($color8, 0.4);
-
- .actions {
- margin-bottom: 0;
- }
-
- .info {
- text-align: center;
-
- a {
- color: $color2;
- }
- }
- }
-
- @media screen and (max-width: 625px) {
- .mascot {
- display: none;
- }
-
- .simple_form, .closed-registrations-message {
- flex: auto;
- }
- }
-}
-
-.closed-registrations-message {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- text-align: center;
-}
diff --git a/app/assets/stylesheets/accounts.scss b/app/assets/stylesheets/accounts.scss
@@ -1,391 +0,0 @@
-.card {
- background: $color1;
- background-size: cover;
- padding: 60px 0;
- padding-bottom: 0;
- border-radius: 4px 4px 0 0;
- box-shadow: 0 0 15px rgba($color8, 0.2);
- overflow: hidden;
- position: relative;
-
- @media screen and (max-width: 700px) {
- border-radius: 0;
- box-shadow: none;
- }
-
- &:after {
- background: linear-gradient(rgba($color8, 0.5), rgba($color8, 0.8));
- display: block;
- content: "";
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- z-index: 1;
- }
-
- .name {
- display: block;
- font-size: 20px;
- line-height: 18px * 1.5;
- color: $color5;
- font-weight: 500;
- text-align: center;
- position: relative;
- z-index: 2;
- text-shadow: 0 0 2px $color8;
-
- small {
- display: block;
- font-size: 14px;
- color: $color4;
- font-weight: 400;
- }
- }
-
- .avatar {
- width: 120px;
- margin: 0 auto;
- margin-bottom: 15px;
- position: relative;
- z-index: 2;
-
- img {
- width: 120px;
- height: 120px;
- display: block;
- border-radius: 120px;
- }
- }
-
- .controls {
- position: absolute;
- top: 10px;
- right: 10px;
- z-index: 2;
- }
-
- .details {
- display: flex;
- margin-top: 30px;
- position: relative;
- z-index: 2;
- flex-direction: row;
- }
-
- .details-counters {
- display: flex;
- flex-direction: row;
- order: 0;
- }
-
- .counter {
- width: 80px;
- color: $color3;
- padding: 5px 10px 0px;
- margin-bottom: 10px;
- border-right: 1px solid $color3;
- cursor: default;
- position: relative;
-
- a {
- display: block;
- }
-
- &:after {
- display: block;
- content: "";
- position: absolute;
- bottom: -10px;
- left: 0;
- width: 100%;
- border-bottom: 4px solid $color3;
- opacity: 0.5;
- transition: all 0.8s ease;
- }
-
- &.active {
- &:after {
- border-bottom: 4px solid $color4;
- opacity: 1;
- }
- }
-
- &:hover {
- &:after {
- opacity: 1;
- transition-duration: 0.2s;
- }
- }
-
- a {
- text-decoration: none;
- color: inherit;
- }
-
- .counter-label {
- font-size: 12px;
- text-transform: uppercase;
- display: block;
- margin-bottom: 5px;
- text-shadow: 0 0 2px $color8;
- }
-
- .counter-number {
- font-weight: 500;
- font-size: 18px;
- color: $color5;
- }
- }
-
- .bio {
- flex: 1;
- font-size: 14px;
- line-height: 18px;
- padding: 5px 10px;
- color: $color2;
- order: 1;
- }
-
- @media screen and (max-width: 480px) {
- .details {
- display: block;
- }
-
- .bio {
- text-align: center;
- margin-bottom: 20px;
- }
-
- .counter {
- flex: 1 1 auto;
- }
-
- .counter:last-child {
- border-right: none;
- }
- }
-}
-
-.pagination {
- padding: 30px 0;
- text-align: center;
- overflow: hidden;
-
- a, .current, .next, .prev, .page, .gap {
- font-size: 14px;
- color: $color5;
- font-weight: 500;
- display: inline-block;
- padding: 6px 10px;
- text-decoration: none;
- }
-
- .current {
- background: $color5;
- border-radius: 100px;
- color: $color1;
- cursor: default;
- margin: 0 10px;
- }
-
- .gap {
- cursor: default;
- }
-
- .prev, .next {
- text-transform: uppercase;
- color: $color2;
- }
-
- .prev {
- float: left;
- padding-left: 0;
-
- .fa {
- display: inline-block;
- margin-right: 5px;
- }
- }
-
- .next {
- float: right;
- padding-right: 0;
-
- .fa {
- display: inline-block;
- margin-left: 5px;
- }
- }
-
- .disabled {
- cursor: default;
- color: lighten($color1, 10%);
- }
-
- @media screen and (max-width: 360px) {
- padding: 30px 20px;
-
- a, .current, .next, .prev, .gap {
- display: none;
- }
-
- .next, .prev {
- display: inline-block;
- }
- }
-}
-
-.accounts-grid {
- box-shadow: 0 0 15px rgba($color8, 0.2);
- background: $color5;
- border-radius: 0 0 4px 4px;
- padding: 20px 10px;
- padding-bottom: 10px;
- overflow: hidden;
- display: flex;
- flex-wrap: wrap;
-
- @media screen and (max-width: 700px) {
- border-radius: 0;
- box-shadow: none;
- }
-
- .account-grid-card {
- box-sizing: border-box;
- width: 335px;
- border: 1px solid $color2;
- border-radius: 4px;
- color: $color1;
- margin-bottom: 10px;
-
- &:nth-child(odd) {
- margin-right: 10px;
- }
-
- .account-grid-card__header {
- overflow: hidden;
- padding: 10px;
- border-bottom: 1px solid $color2;
- }
-
- .avatar {
- width: 60px;
- height: 60px;
- float: left;
- margin-right: 15px;
-
- img {
- display: block;
- width: 60px;
- height: 60px;
- border-radius: 60px;
- }
- }
-
- .name {
- padding-top: 10px;
-
- a {
- display: block;
- color: $color1;
- text-decoration: none;
-
- &:hover {
- .display_name {
- text-decoration: underline;
- }
- }
- }
- }
-
- .display_name {
- font-size: 14px;
- display: block;
- }
-
- .username {
- color: $color4;
- }
-
- .note {
- padding: 10px;
- padding-top: 15px;
- color: $color3;
- word-wrap: break-word;
- }
- }
-}
-
-.nothing-here {
- color: $color3;
- font-size: 14px;
- font-weight: 500;
- text-align: center;
- padding: 15px 0;
- padding-bottom: 25px;
- cursor: default;
-}
-
-.account-card {
- padding: 14px 10px;
- background: $color5;
- border-radius: 4px;
- text-align: left;
- box-shadow: 0 0 15px rgba($color8, 0.2);
-
- .detailed-status__display-name {
- display: block;
- overflow: hidden;
- margin-bottom: 15px;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- & > div {
- float: left;
- margin-right: 10px;
- width: 48px;
- height: 48px;
- }
-
- .avatar {
- display: block;
- border-radius: 4px;
- }
-
- .display-name {
- display: block;
- max-width: 100%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- cursor: default;
-
- strong {
- font-weight: 500;
- color: $color1;
- }
-
- span {
- font-size: 14px;
- color: $color3;
- }
- }
-
- &:hover {
- .display-name {
- strong {
- text-decoration: none;
- }
- }
- }
- }
-
- .account__header__content {
- font-size: 14px;
- color: $color1;
- }
-}
diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss
@@ -1,245 +0,0 @@
-.admin-wrapper {
- display: flex;
- justify-content: center;
- height: 100%;
-
- .sidebar-wrapper {
- flex: 1;
- height: 100%;
- background: $color1;
- display: flex;
- justify-content: flex-end;
- }
-
- .sidebar {
- width: 240px;
- height: 100%;
- padding: 0;
- overflow-y: auto;
-
- .logo {
- display: block;
- margin: 40px auto;
- width: 100px;
- height: 100px;
- }
-
- ul {
- list-style: none;
- border-radius: 4px 0 0 4px;
- overflow: hidden;
- margin-bottom: 20px;
-
- a {
- display: block;
- padding: 15px 25px;
- color: rgba($color5, 0.7);
- text-decoration: none;
- transition: all 200ms linear;
- border-radius: 4px 0 0 4px;
-
- i.fa {
- margin-right: 5px;
- }
-
- &:hover {
- color: $color5;
- background-color: darken($color1, 5%);
- transition: all 100ms linear;
- }
-
- &.selected {
- background: darken($color1, 2%);
- border-radius: 4px 0 0 0;
- }
- }
-
- ul {
- background: darken($color1, 4%);
- border-radius: 0 0 0 4px;
- margin: 0;
-
- a {
- border: 0;
-
- &.selected {
- color: $color5;
- background-color: $color4;
- border-bottom: 0;
- border-radius: 0;
-
- &:hover {
- background-color: lighten($color4, 5%);
- }
- }
- }
- }
- }
- }
-
- .content-wrapper {
- flex: 2;
- overflow: auto;
- }
-
- .content {
- max-width: 700px;
- padding: 20px 15px;
- padding-top: 60px;
- padding-left: 25px;
-
- h2 {
- color: $color2;
- font-size: 24px;
- line-height: 28px;
- font-weight: 400;
- margin-bottom: 40px;
- }
-
- & > p {
- font-size: 14px;
- line-height: 18px;
- color: $color2;
- margin-bottom: 20px;
-
- strong {
- color: $color5;
- font-weight: 500;
- }
- }
-
- hr {
- margin: 20px 0;
- border: 0;
- background: transparent;
- border-bottom: 1px solid $color1;
- }
- }
-
- .simple_form {
- max-width: 400px;
- .label_input {
- label.select {
- width: 50%;
- }
- select {
- width: 50%;
- float: right;
- }
- }
- }
-
- @media screen and (max-width: 600px) {
- display: block;
- overflow-y: auto;
- -webkit-overflow-scrolling: touch;
-
- .sidebar-wrapper, .content-wrapper {
- flex: 0 0 auto;
- height: auto;
- overflow: initial;
- }
-
- .sidebar {
- width: 100%;
- padding: 10px 0;
- height: auto;
-
- .logo {
- margin: 20px auto;
- }
- }
-
- .content {
- padding-top: 20px;
- }
- }
-}
-
-.filters {
- display: flex;
- margin-bottom: 20px;
-
- .filter-subset {
- flex: 0 0 auto;
- margin-right: 40px;
-
- ul {
- margin-top: 5px;
- list-style: none;
-
- li {
- display: inline-block;
- margin-right: 5px;
- }
- }
-
- strong {
- font-weight: 500;
- text-transform: uppercase;
- font-size: 12px;
- }
-
- a {
- display: inline-block;
- color: rgba($color5, 0.7);
- text-decoration: none;
- text-transform: uppercase;
- font-size: 12px;
- font-weight: 500;
- border-bottom: 2px solid $color1;
-
- &:hover {
- color: $color5;
- border-bottom: 2px solid lighten($color1, 5%);
- }
-
- &.selected {
- color: $color4;
- border-bottom: 2px solid $color4;
- }
- }
- }
-}
-
-.report-accounts {
- display: flex;
- margin-bottom: 20px;
-}
-
-.report-accounts__item {
- flex: 1 1 0;
- display: flex;
- flex-direction: column;
-
- & > strong {
- display: block;
- margin-bottom: 10px;
- font-weight: 500;
- font-size: 14px;
- line-height: 18px;
- color: $color2;
- }
-
- &:first-child {
- margin-right: 10px;
- }
-
- .account-card {
- flex: 1 1 auto;
- }
-}
-
-.report-status {
- display: flex;
- margin-bottom: 10px;
-
- .activity-stream {
- flex: 2 0 0;
- margin-right: 20px;
- }
-}
-
-.report-status__actions {
- flex: 0 0 auto;
-}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
@@ -1,21 +0,0 @@
-@import 'variables';
-@import 'fonts/roboto';
-@import 'fonts/roboto-mono';
-@import 'fonts/montserrat';
-@import 'font-awesome';
-
-@import 'reset';
-@import 'basics';
-@import 'containers';
-@import 'lists';
-@import 'footer';
-@import 'compact_header';
-@import 'landing_strip';
-@import 'forms';
-@import 'accounts';
-@import 'stream_entries';
-@import 'components';
-@import 'about';
-@import 'tables';
-@import 'admin';
-@import 'rtl';
diff --git a/app/assets/stylesheets/basics.scss b/app/assets/stylesheets/basics.scss
@@ -1,58 +0,0 @@
-body {
- font-family: 'Roboto', sans-serif;
- background: $color1 image-url('background-photo.jpg');
- background-size: cover;
- background-attachment: fixed;
- font-size: 13px;
- line-height: 18px;
- font-weight: 400;
- color: $color5;
- padding-bottom: 140px;
- text-rendering: optimizelegibility;
- font-feature-settings: "kern";
- text-size-adjust: none;
-
- &.app-body {
- position: fixed;
- width: 100%;
- height: 100%;
- padding: 0;
- background: $color1;
- }
-
- &.embed {
- background: transparent;
- margin: 0;
-
- .container {
- position: absolute;
- width: 100%;
- height: 100%;
- overflow: hidden;
- }
- }
-
- &.admin {
- background: darken($color1, 4%);
- position: fixed;
- width: 100%;
- height: 100%;
- padding: 0;
- }
-
- @media screen and (max-width: 360px) {
- padding-bottom: 0;
- }
-}
-
-button:focus {
- outline: none;
-}
-
-.app-holder {
- display: flex;
- width: 100%;
- height: 100%;
- align-items: center;
- justify-content: center;
-}
diff --git a/app/assets/stylesheets/boost.scss b/app/assets/stylesheets/boost.scss
@@ -1,11 +0,0 @@
-@function url-friendly-colour($colour) {
- @return '%23' + str-slice('#{$colour}', 2, -1)
-}
-
-button.icon-button i.fa-retweet {
- background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='209'><path d='M4.97 3.16c-.1.03-.17.1-.22.18L.8 8.24c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77L5.5 3.35c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.02-2.4.02H7.1l2.32 2.85.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour(lighten($color1, 26%))}' stroke-width='0'/><path d='M7.78 19.66c-.24.02-.44.25-.44.5v2.46h-.06c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v4.47c0 4.26-.56 3.62 3.65 3.62H8.5l-1.3-1.06c-.1-.08-.18-.2-.2-.3-.02-.17.06-.35.2-.45l1.33-1.1H7.28c-.44 0-.72-.3-.72-.7v-4.48c0-.44.28-.72.72-.72h.06v2.5c0 .38.54.63.82.38l4.9-3.93c.25-.18.25-.6 0-.78l-4.9-3.92c-.1-.1-.24-.14-.38-.12zm9.34 2.93c-.54-.02-1.3.02-2.4.02h-1.25l1.3 1.07c.1.07.18.2.2.33.02.16-.06.3-.2.4l-1.33 1.1h1.28c.42 0 .72.28.72.72v4.47c0 .42-.3.72-.72.72h-.1v-2.47c0-.3-.3-.53-.6-.47-.07 0-.14.05-.2.1l-4.9 3.93c-.26.18-.26.6 0 .78l4.9 3.92c.27.25.82 0 .8-.38v-2.5h.1c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.15.4-3.62-1.25-3.66zM10.34 38.66c-.24.02-.44.25-.43.5v2.47H7.3c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.47c0 3.66-.23 3.7 2.34 3.66l-1.34-1.1c-.1-.08-.18-.2-.2-.3 0-.17.07-.35.2-.45l1.96-1.6c-.03-.06-.04-.13-.04-.2v-4.48c0-.44.28-.72.72-.72H9.9v2.5c0 .36.5.6.8.38l4.93-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.08-.23-.13-.36-.12zm5.63 2.93l1.34 1.1c.1.07.18.2.2.33.02.16-.03.3-.16.4l-1.96 1.6c.02.07.06.13.06.22v4.47c0 .42-.3.72-.72.72h-2.66v-2.47c0-.3-.3-.53-.6-.47-.06.02-.12.05-.18.1l-4.94 3.93c-.24.18-.24.6 0 .78l4.94 3.92c.28.22.78-.02.78-.38v-2.5h2.66c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.66.34-3.7-2.4-3.66zM13.06 57.66c-.23.03-.4.26-.4.5v2.47H7.28c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.87l2.93-2.37v-2.5c0-.44.28-.72.72-.72h5.38v2.5c0 .36.5.6.78.38l4.94-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.1-.24-.14-.38-.12zm5.3 6.15l-2.92 2.4v2.52c0 .42-.3.72-.72.72h-5.4v-2.47c0-.3-.32-.53-.6-.47-.07.02-.13.05-.2.1L3.6 70.52c-.25.18-.25.6 0 .78l4.93 3.92c.28.22.78-.02.78-.38v-2.5h5.42c4.27 0 3.65.67 3.65-3.62v-4.47-.44zM19.25 78.8c-.1.03-.2.1-.28.17l-.9.9c-.44-.3-1.36-.25-3.35-.25H7.28c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v.7l2.93.3v-1c0-.44.28-.72.72-.72h7.44c.2 0 .37.08.5.2l-1.8 1.8c-.25.26-.08.76.27.8l6.27.7c.28.03.56-.25.53-.53l-.7-6.25c0-.27-.3-.48-.55-.44zm-17.2 6.1c-.2.07-.36.3-.33.54l.7 6.25c.02.36.58.55.83.27l.8-.8c.02 0 .04-.02.04 0 .46.24 1.37.17 3.18.17h7.44c4.27 0 3.65.67 3.65-3.62v-.75l-2.93-.3v1.05c0 .42-.3.72-.72.72H7.28c-.15 0-.3-.03-.4-.1L8.8 86.4c.3-.24.1-.8-.27-.84l-6.28-.65h-.2zM4.88 98.6c-1.33 0-1.34.48-1.3 2.3l1.14-1.37c.08-.1.22-.17.34-.2.16 0 .34.08.44.2l1.66 2.03c.04 0 .07-.03.12-.03h7.44c.34 0 .57.2.65.5h-2.43c-.34.05-.53.52-.3.78l3.92 4.95c.18.24.6.24.78 0l3.94-4.94c.22-.27-.02-.76-.37-.77H18.4c.02-3.9.6-3.4-3.66-3.4H7.28c-1.08 0-1.86-.04-2.4-.04zm.15 2.46c-.1.03-.2.1-.28.2l-3.94 4.9c-.2.28.03.77.4.78H3.6c-.02 3.94-.45 3.4 3.66 3.4h7.44c3.65 0 3.74.3 3.7-2.25l-1.1 1.34c-.1.1-.2.17-.32.2-.16 0-.34-.08-.44-.2l-1.65-2.03c-.06.02-.1.04-.18.04H7.28c-.35 0-.57-.2-.66-.5h2.44c.37 0 .63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.23-.47-.2zM4.88 117.6c-1.16 0-1.3.3-1.3 1.56l1.14-1.38c.08-.1.22-.14.34-.16.16 0 .34.04.44.16l2.22 2.75h7c.42 0 .72.28.72.72v.53h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-.53c0-4.2.72-3.63-3.66-3.63H7.28c-1.08 0-1.86-.03-2.4-.03zm.1 1.74c-.1.03-.17.1-.23.16L.8 124.44c-.2.28.03.77.4.78H3.6v.5c0 4.26-.55 3.62 3.66 3.62h7.44c1.03 0 1.74.02 2.28 0-.16.02-.34-.03-.44-.15l-2.22-2.76H7.28c-.44 0-.72-.3-.72-.72v-.5h2.5c.37.02.63-.5.4-.78L5.5 119.5c-.12-.15-.34-.22-.53-.16zm12.02 10c1.2-.02 1.4-.25 1.4-1.53l-1.1 1.36c-.07.1-.17.17-.3.18zM5.94 136.6l2.37 2.93h6.42c.42 0 .72.28.72.72v1.25h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.25c0-4.2.72-3.63-3.66-3.63H7.28c-.6 0-.92-.02-1.34-.03zm-1.72.06c-.4.08-.54.3-.6.75l.6-.74zm.84.93c-.12 0-.24.08-.3.18l-3.95 4.9c-.24.3 0 .83.4.82H3.6v1.22c0 4.26-.55 3.62 3.66 3.62h7.44c.63 0 .97.02 1.4.03l-2.37-2.93H7.28c-.44 0-.72-.3-.72-.72v-1.22h2.5c.4.04.67-.53.4-.8l-3.96-4.92c-.1-.13-.27-.2-.44-.2zm13.28 10.03l-.56.7c.36-.07.5-.3.56-.7zM17.13 155.6c-.55-.02-1.32.03-2.4.03h-8.2l2.38 2.9h5.82c.42 0 .72.28.72.72v1.97H12.9c-.32.06-.48.52-.28.78l3.94 4.94c.2.23.6.22.78-.03l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.97c0-3.15.4-3.62-1.25-3.66zm-12.1.28c-.1.02-.2.1-.28.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v1.96c0 4.26-.55 3.62 3.66 3.62h8.24l-2.36-2.9H7.28c-.44 0-.72-.3-.72-.72v-1.97h2.5c.37.02.63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.22-.47-.2zM5.13 174.5c-.15 0-.3.07-.38.2L.8 179.6c-.24.27 0 .82.4.8H3.6v2.32c0 4.26-.55 3.62 3.66 3.62h7.94l-2.35-2.9h-5.6c-.43 0-.7-.3-.7-.72v-2.3h2.5c.38.03.66-.54.4-.83l-3.97-4.9c-.1-.13-.23-.2-.38-.2zm12 .1c-.55-.02-1.32.03-2.4.03H6.83l2.35 2.9h5.52c.42 0 .72.28.72.72v2.34h-2.6c-.3.1-.43.53-.2.78l3.92 4.9c.18.24.6.24.78 0l3.94-4.9c.22-.3-.02-.78-.37-.8H18.4v-2.33c0-3.15.4-3.62-1.25-3.66zM4.97 193.16c-.1.03-.17.1-.22.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77l-3.96-4.9c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.03-2.4.03H7.1l2.32 2.84.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour($color4)}' stroke-width='0'/></svg>");
-
- &:hover {
- background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='209'><path d='M4.97 3.16c-.1.03-.17.1-.22.18L.8 8.24c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77L5.5 3.35c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.02-2.4.02H7.1l2.32 2.85.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour(lighten($color1, 33%))}' stroke-width='0'/><path d='M7.78 19.66c-.24.02-.44.25-.44.5v2.46h-.06c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v4.47c0 4.26-.56 3.62 3.65 3.62H8.5l-1.3-1.06c-.1-.08-.18-.2-.2-.3-.02-.17.06-.35.2-.45l1.33-1.1H7.28c-.44 0-.72-.3-.72-.7v-4.48c0-.44.28-.72.72-.72h.06v2.5c0 .38.54.63.82.38l4.9-3.93c.25-.18.25-.6 0-.78l-4.9-3.92c-.1-.1-.24-.14-.38-.12zm9.34 2.93c-.54-.02-1.3.02-2.4.02h-1.25l1.3 1.07c.1.07.18.2.2.33.02.16-.06.3-.2.4l-1.33 1.1h1.28c.42 0 .72.28.72.72v4.47c0 .42-.3.72-.72.72h-.1v-2.47c0-.3-.3-.53-.6-.47-.07 0-.14.05-.2.1l-4.9 3.93c-.26.18-.26.6 0 .78l4.9 3.92c.27.25.82 0 .8-.38v-2.5h.1c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.15.4-3.62-1.25-3.66zM10.34 38.66c-.24.02-.44.25-.43.5v2.47H7.3c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.47c0 3.66-.23 3.7 2.34 3.66l-1.34-1.1c-.1-.08-.18-.2-.2-.3 0-.17.07-.35.2-.45l1.96-1.6c-.03-.06-.04-.13-.04-.2v-4.48c0-.44.28-.72.72-.72H9.9v2.5c0 .36.5.6.8.38l4.93-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.08-.23-.13-.36-.12zm5.63 2.93l1.34 1.1c.1.07.18.2.2.33.02.16-.03.3-.16.4l-1.96 1.6c.02.07.06.13.06.22v4.47c0 .42-.3.72-.72.72h-2.66v-2.47c0-.3-.3-.53-.6-.47-.06.02-.12.05-.18.1l-4.94 3.93c-.24.18-.24.6 0 .78l4.94 3.92c.28.22.78-.02.78-.38v-2.5h2.66c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.66.34-3.7-2.4-3.66zM13.06 57.66c-.23.03-.4.26-.4.5v2.47H7.28c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.87l2.93-2.37v-2.5c0-.44.28-.72.72-.72h5.38v2.5c0 .36.5.6.78.38l4.94-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.1-.24-.14-.38-.12zm5.3 6.15l-2.92 2.4v2.52c0 .42-.3.72-.72.72h-5.4v-2.47c0-.3-.32-.53-.6-.47-.07.02-.13.05-.2.1L3.6 70.52c-.25.18-.25.6 0 .78l4.93 3.92c.28.22.78-.02.78-.38v-2.5h5.42c4.27 0 3.65.67 3.65-3.62v-4.47-.44zM19.25 78.8c-.1.03-.2.1-.28.17l-.9.9c-.44-.3-1.36-.25-3.35-.25H7.28c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v.7l2.93.3v-1c0-.44.28-.72.72-.72h7.44c.2 0 .37.08.5.2l-1.8 1.8c-.25.26-.08.76.27.8l6.27.7c.28.03.56-.25.53-.53l-.7-6.25c0-.27-.3-.48-.55-.44zm-17.2 6.1c-.2.07-.36.3-.33.54l.7 6.25c.02.36.58.55.83.27l.8-.8c.02 0 .04-.02.04 0 .46.24 1.37.17 3.18.17h7.44c4.27 0 3.65.67 3.65-3.62v-.75l-2.93-.3v1.05c0 .42-.3.72-.72.72H7.28c-.15 0-.3-.03-.4-.1L8.8 86.4c.3-.24.1-.8-.27-.84l-6.28-.65h-.2zM4.88 98.6c-1.33 0-1.34.48-1.3 2.3l1.14-1.37c.08-.1.22-.17.34-.2.16 0 .34.08.44.2l1.66 2.03c.04 0 .07-.03.12-.03h7.44c.34 0 .57.2.65.5h-2.43c-.34.05-.53.52-.3.78l3.92 4.95c.18.24.6.24.78 0l3.94-4.94c.22-.27-.02-.76-.37-.77H18.4c.02-3.9.6-3.4-3.66-3.4H7.28c-1.08 0-1.86-.04-2.4-.04zm.15 2.46c-.1.03-.2.1-.28.2l-3.94 4.9c-.2.28.03.77.4.78H3.6c-.02 3.94-.45 3.4 3.66 3.4h7.44c3.65 0 3.74.3 3.7-2.25l-1.1 1.34c-.1.1-.2.17-.32.2-.16 0-.34-.08-.44-.2l-1.65-2.03c-.06.02-.1.04-.18.04H7.28c-.35 0-.57-.2-.66-.5h2.44c.37 0 .63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.23-.47-.2zM4.88 117.6c-1.16 0-1.3.3-1.3 1.56l1.14-1.38c.08-.1.22-.14.34-.16.16 0 .34.04.44.16l2.22 2.75h7c.42 0 .72.28.72.72v.53h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-.53c0-4.2.72-3.63-3.66-3.63H7.28c-1.08 0-1.86-.03-2.4-.03zm.1 1.74c-.1.03-.17.1-.23.16L.8 124.44c-.2.28.03.77.4.78H3.6v.5c0 4.26-.55 3.62 3.66 3.62h7.44c1.03 0 1.74.02 2.28 0-.16.02-.34-.03-.44-.15l-2.22-2.76H7.28c-.44 0-.72-.3-.72-.72v-.5h2.5c.37.02.63-.5.4-.78L5.5 119.5c-.12-.15-.34-.22-.53-.16zm12.02 10c1.2-.02 1.4-.25 1.4-1.53l-1.1 1.36c-.07.1-.17.17-.3.18zM5.94 136.6l2.37 2.93h6.42c.42 0 .72.28.72.72v1.25h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.25c0-4.2.72-3.63-3.66-3.63H7.28c-.6 0-.92-.02-1.34-.03zm-1.72.06c-.4.08-.54.3-.6.75l.6-.74zm.84.93c-.12 0-.24.08-.3.18l-3.95 4.9c-.24.3 0 .83.4.82H3.6v1.22c0 4.26-.55 3.62 3.66 3.62h7.44c.63 0 .97.02 1.4.03l-2.37-2.93H7.28c-.44 0-.72-.3-.72-.72v-1.22h2.5c.4.04.67-.53.4-.8l-3.96-4.92c-.1-.13-.27-.2-.44-.2zm13.28 10.03l-.56.7c.36-.07.5-.3.56-.7zM17.13 155.6c-.55-.02-1.32.03-2.4.03h-8.2l2.38 2.9h5.82c.42 0 .72.28.72.72v1.97H12.9c-.32.06-.48.52-.28.78l3.94 4.94c.2.23.6.22.78-.03l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.97c0-3.15.4-3.62-1.25-3.66zm-12.1.28c-.1.02-.2.1-.28.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v1.96c0 4.26-.55 3.62 3.66 3.62h8.24l-2.36-2.9H7.28c-.44 0-.72-.3-.72-.72v-1.97h2.5c.37.02.63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.22-.47-.2zM5.13 174.5c-.15 0-.3.07-.38.2L.8 179.6c-.24.27 0 .82.4.8H3.6v2.32c0 4.26-.55 3.62 3.66 3.62h7.94l-2.35-2.9h-5.6c-.43 0-.7-.3-.7-.72v-2.3h2.5c.38.03.66-.54.4-.83l-3.97-4.9c-.1-.13-.23-.2-.38-.2zm12 .1c-.55-.02-1.32.03-2.4.03H6.83l2.35 2.9h5.52c.42 0 .72.28.72.72v2.34h-2.6c-.3.1-.43.53-.2.78l3.92 4.9c.18.24.6.24.78 0l3.94-4.9c.22-.3-.02-.78-.37-.8H18.4v-2.33c0-3.15.4-3.62-1.25-3.66zM4.97 193.16c-.1.03-.17.1-.22.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77l-3.96-4.9c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.03-2.4.03H7.1l2.32 2.84.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour($color4)}' stroke-width='0'/></svg>");
- }
-}
diff --git a/app/assets/stylesheets/compact_header.scss b/app/assets/stylesheets/compact_header.scss
@@ -1,28 +0,0 @@
-.compact-header {
- h1 {
- font-size: 24px;
- line-height: 28px;
- color: $color3;
- overflow: hidden;
- font-weight: 500;
- margin-bottom: 20px;
-
- a {
- color: inherit;
- text-decoration: none;
- }
-
- small {
- font-weight: 400;
- color: $color2;
- }
-
- img {
- display: inline-block;
- margin-bottom: -5px;
- margin-right: 15px;
- width: 36px;
- height: 36px;
- }
- }
-}
diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss
@@ -1,3180 +0,0 @@
-@import 'variables';
-
-.app-body {
- -webkit-overflow-scrolling: touch;
- -ms-overflow-style: -ms-autohiding-scrollbar;
-}
-
-.button {
- background-color: darken($color4, 3%);
- border: 10px none;
- border-radius: 4px;
- box-sizing: border-box;
- color: $color5;
- cursor: pointer;
- display: inline-block;
- font-family: inherit;
- font-size: 14px;
- font-weight: 500;
- height: 36px;
- letter-spacing: 0;
- line-height: 36px;
- overflow: hidden;
- padding: 0 16px;
- position: relative;
- text-align: center;
- text-transform: uppercase;
- text-decoration: none;
- text-overflow: ellipsis;
- transition: all 100ms ease-in;
- white-space: nowrap;
-
- &:active,
- &:focus,
- &:hover {
- background-color: lighten($color4, 7%);
- transition: all 200ms ease-out;
- }
-
- &:disabled {
- background-color: $color3;
- cursor: default;
- }
-
- &.button-secondary {
- //
- }
-}
-
-.column-collapsable {
- position: relative;
-}
-
-.column-icon {
- background: lighten($color1, 4%);
- color: $color3;
- cursor: pointer;
- font-size: 16px;
- padding: 15px;
- position: absolute;
- right: 0;
- top: -48px;
- z-index: 3;
-
- &:hover {
- color: lighten($color3, 7%);
- }
-}
-
-.column-icon-clear {
- font-size: 16px;
- padding: 15px;
- position: absolute;
- right: 48px;
- top: 0;
- cursor: pointer;
- z-index: 2;
-}
-
-@media screen and (min-width: 1025px) {
- .column-icon-clear {
- top: 10px;
- }
-}
-
-.icon-button {
- display: inline-block;
- padding: 0;
- color: lighten($color1, 26%);
- border: none;
- background: transparent;
- cursor: pointer;
- transition: color 100ms ease-in;
-
- &:hover, &:active, &:focus {
- color: lighten($color1, 33%);
- transition: color 200ms ease-out;
- }
-
- &.disabled {
- color: lighten($color1, 13%);
- cursor: default;
- }
-
- &.active {
- color: $color4;
- }
-
- &::-moz-focus-inner {
- border: 0;
- }
-
- &::-moz-focus-inner, &:focus, &:active {
- outline: 0 !important;
- }
-
- &.inverted {
- color: lighten($color1, 33%);
-
- &:hover, &:active, &:focus {
- color: lighten($color1, 26%);
- }
-
- &.active {
- color: $color4;
- }
-
- &.disabled {
- color: $color3;
- }
- }
-
- &.overlayed {
- box-sizing: content-box;
- background: rgba($color8, 0.6);
- color: rgba($color5, 0.7);
- border-radius: 4px;
- padding: 2px;
-
- &:hover {
- background: rgba($color8, 0.9);
- }
- }
-}
-
-.text-icon-button {
- color: lighten($color1, 33%);
- border: none;
- background: transparent;
- cursor: pointer;
- font-weight: 600;
- font-size: 11px;
- padding: 0 3px;
- line-height: 27px;
- outline: 0;
- transition: color 100ms ease-in;
-
- &:hover, &:active, &:focus {
- color: lighten($color1, 26%);
- transition: color 200ms ease-out;
- }
-
- &.disabled {
- color: lighten($color1, 13%);
- cursor: default;
- }
-
- &.active {
- color: $color4;
- }
-
- &::-moz-focus-inner {
- border: 0;
- }
-
- &::-moz-focus-inner, &:focus, &:active {
- outline: 0 !important;
- }
-}
-
-.dropdown--active .icon-button {
- color: $color4;
-}
-
-.dropdown--active:after {
- content: "";
- display: block;
- position: absolute;
- width: 0;
- height: 0;
- border-style: solid;
- border-width: 0 4.5px 7.8px 4.5px;
- border-color: transparent transparent $color2 transparent;
- bottom: 8px;
- right: 104px;
-}
-
-.invisible {
- font-size: 0;
- line-height: 0;
- display: inline-block;
- width: 0;
-}
-
-.ellipsis {
- &:after {
- content: "…";
- }
-}
-
-.lightbox .icon-button {
- color: $color1;
-}
-
-.compose-form {
- padding: 10px;
-}
-
-.compose-form__warning {
- color: darken($color3, 33%);
- margin-bottom: 15px;
- background: $color3;
- box-shadow: 0 2px 6px rgba($color8, 0.3);
- padding: 8px 10px;
- border-radius: 4px;
- font-size: 13px;
- font-weight: 400;
-
- strong {
- color: darken($color3, 33%);
- font-weight: 500;
- }
-
- a {
- color: darken($color3, 33%);
- font-weight: 500;
- text-decoration: underline;
-
- &:hover, &:active, &:focus {
- text-decoration: none;
- }
- }
-}
-
-.compose-form__modifiers {
- color: $color1;
- font-family: inherit;
- font-size: 14px;
- background: $color5;
- border-radius: 0 0 4px 0;
-}
-
-.compose-form__buttons-wrapper {
- display: flex;
- justify-content: space-between;
-}
-
-.compose-form__buttons {
- padding: 10px;
- background: darken($color5, 8%);
- box-shadow: inset 0 5px 5px rgba($color8, 0.05);
- border-radius: 0 0 4px 4px;
- display: flex;
-
- .icon-button {
- box-sizing: content-box;
- padding: 0 3px;
- }
-}
-
-.compose-form__upload-button-icon {
- line-height: 27px;
-}
-
-.compose-form__upload-wrapper {
- overflow: hidden;
-}
-
-.compose-form__uploads-wrapper {
- display: flex;
- padding: 5px;
-}
-
-.compose-form__upload {
- flex: 1 1 0;
- margin: 5px;
-}
-
-.compose-form__upload-thumbnail {
- border-radius: 4px;
- background-position: center;
- background-size: cover;
- background-repeat: no-repeat;
- height: 100px;
- width: 100%;
-}
-
-.compose-form__upload-cancel {
- background-size: cover;
- border-radius: 4px;
- height: 100px;
- width: 100px;
-}
-
-.compose-form__label {
- display: block;
- line-height: 24px;
- vertical-align: middle;
-
- &.with-border {
- border-top: 1px solid $color1;
- padding-top: 10px;
- }
-
- .compose-form__label__text {
- display: inline-block;
- vertical-align: middle;
- margin-bottom: 14px;
- margin-left: 8px;
- color: $color3;
- }
-}
-
-.compose-form__textarea, .follow-form__input {
- background: $color5;
-
- &:disabled {
- background: $color2;
- }
-}
-
-.compose-form__autosuggest-wrapper {
- position: relative;
-
- .dropdown--active:after {
- border-color: transparent transparent $color5 transparent;
- bottom: -1px;
- right: 8px;
- }
-}
-
-.compose-form__publish {
- display: flex;
- min-width: 0;
-}
-
-.compose-form__publish-button-wrapper {
- overflow: hidden;
- padding-top: 10px;
-}
-
-.emojione {
- display: inline-block;
- font-size: inherit;
- vertical-align: middle;
- margin: -.2ex .15em .2ex;
- width: 16px;
- height: 16px;
-
- img {
- width: auto;
- }
-}
-
-.reply-indicator {
- border-radius: 4px 4px 0 0;
- position: relative;
- bottom: -2px;
- background: $color3;
- padding: 10px;
-}
-
-.reply-indicator__header {
- margin-bottom: 5px;
- overflow: hidden;
-}
-
-.reply-indicator__cancel {
- float: right;
- line-height: 24px;
-}
-
-.reply-indicator__display-name {
- color: $color1;
- display: block;
- max-width: 100%;
- line-height: 24px;
- overflow: hidden;
- padding-right: 25px;
- text-decoration: none;
-}
-
-.reply-indicator__display-avatar {
- float: left;
- margin-right: 5px;
-}
-
-.status__content {
- cursor: pointer;
-}
-
-.status__content--no-action {
- cursor: default;
-}
-
-.status__content,
-.reply-indicator__content {
- font-size: 15px;
- line-height: 20px;
- word-wrap: break-word;
- font-weight: 400;
- overflow: hidden;
- white-space: pre-wrap;
-
- .emojione {
- width: 18px;
- height: 18px;
- }
-
- p {
- margin-bottom: 20px;
-
- &:last-child {
- margin-bottom: 0;
- }
- }
-
- a {
- color: $color2;
- text-decoration: none;
-
- &:hover {
- text-decoration: underline;
-
- .fa {
- color: lighten($color1, 40%);
- }
- }
-
- &.mention {
- &:hover {
- text-decoration: none;
-
- span {
- text-decoration: underline;
- }
- }
- }
-
- .fa {
- color: lighten($color1, 30%);
- }
- }
-
- .status__content__spoiler-link {
- background: lighten($color1, 30%);
-
- &:hover {
- background: lighten($color1, 33%);
- text-decoration: none;
- }
- }
-}
-
-a.status__content__spoiler-link {
- display: inline-block;
- border-radius: 2px;
- color: lighten($color1, 8%);
- font-weight: 500;
- font-size: 11px;
- padding: 0px 6px;
- text-transform: uppercase;
- line-height: inherit;
-}
-
-.status__prepend-icon-wrapper {
- left: -26px;
- position: absolute;
-}
-
-.status {
- padding: 8px 10px;
- padding-left: 68px;
- position: relative;
- min-height: 48px;
- border-bottom: 1px solid lighten($color1, 8%);
- cursor: default;
-
- &.light {
- .status__relative-time {
- color: $color3;
- }
-
- .status__display-name {
- color: $color1;
- }
-
- .display-name {
- strong {
- color: $color1;
- }
-
- span {
- color: $color3;
- }
- }
-
- .status__content {
- color: $color1;
-
- a {
- color: $color4;
- }
-
- a.status__content__spoiler-link {
- color: $color5;
- background: $color3;
-
- &:hover {
- background: lighten($color3, 8%);
- }
- }
- }
- }
-}
-
-.status__relative-time {
- color: lighten($color1, 26%);
-}
-
-.status__display-name {
- color: lighten($color1, 26%);
-}
-
-.status__info .status__display-name {
- display: block;
- max-width: 100%;
- padding-right: 25px;
-}
-
-.status__info {
- font-size: 15px;
-}
-
-.status__info-time {
- float: right;
- font-size: 14px;
-}
-
-.status-check-box {
- border-bottom: 1px solid lighten($color1, 8%);
- display: flex;
-
- .status__content {
- background: lighten($color1, 4%);
- flex: 1 1 auto;
- padding: 10px;
- }
-}
-
-.status-check-box-toggle {
- align-items: center;
- display: flex;
- flex: 0 0 auto;
- justify-content: center;
- padding: 10px;
-}
-
-.status__prepend {
- margin-left: 68px;
- color: lighten($color1, 26%);
- padding: 8px 0;
- padding-bottom: 2px;
- font-size: 14px;
- position: relative;
-
- .status__display-name strong {
- color: lighten($color1, 26%);
- }
-}
-
-.status__action-bar {
- align-items: center;
- display: flex;
- margin-top: 10px;
-}
-
-.status__action-bar-button-wrapper {
- float: left;
- margin-right: 18px;
-}
-
-.status__action-bar-dropdown {
- float: left;
- height: 18px;
- width: 18px;
-}
-
-.detailed-status {
- background: lighten($color1, 4%);
- padding: 14px 10px;
-
- .status__content {
- font-size: 19px;
- line-height: 24px;
-
- .emojione {
- width: 22px;
- height: 22px;
- }
- }
-}
-
-.detailed-status__meta {
- margin-top: 15px;
- color: lighten($color1, 26%);
- font-size: 14px;
- line-height: 18px;
-}
-
-.detailed-status__action-bar {
- background: lighten($color1, 4%);
- border-top: 1px solid lighten($color1, 8%);
- border-bottom: 1px solid lighten($color1, 8%);
- display: flex;
- flex-direction: row;
- padding: 10px 0;
-}
-
-.detailed-status__link {
- color: inherit;
- text-decoration: none;
-}
-
-.detailed-status__favorites,
-.detailed-status__reblogs {
- display: inline-block;
- font-weight: 500;
- font-size: 12px;
- margin-left: 6px;
-}
-
-.reply-indicator__content {
- color: $color1;
- font-size: 14px;
-
- a {
- color: lighten($color1, 20%);
- }
-}
-
-.account {
- padding: 10px;
- border-bottom: 1px solid lighten($color1, 8%);
-
- .account__display-name {
- flex: 1 1 auto;
- display: block;
- color: $color3;
- overflow: hidden;
- text-decoration: none;
- font-size: 14px;
- }
-}
-
-.account__wrapper {
- display: flex;
-}
-
-.account__avatar-wrapper {
- float: left;
- margin-left: 12px;
- margin-right: 12px;
-}
-
-.account__avatar {
- border-radius: 4px;
- background: transparent no-repeat;
- background-position: 50%;
- background-clip: padding-box;
- position: relative;
-}
-
-.account__relationship {
- height: 18px;
- padding: 10px;
-}
-
-.account__header {
- flex: 0 0 auto;
- background: lighten($color1, 4%);
- text-align: center;
- background-size: cover;
- background-position: center;
- position: relative;
-
- & > div {
- background: rgba(lighten($color1, 4%), 0.9);
- }
-
- .account__header__content {
- color: $color2;
- }
-
- .account__header__display-name {
- color: $color5;
- }
-
- .account__header__username {
- color: $color4;
- }
-}
-
-.account__header__content {
- color: $color3;
- font-size: 14px;
- font-weight: 400;
- overflow: hidden;
- word-break: normal;
- word-wrap: break-word;
-
- p {
- margin-bottom: 20px;
-
- &:last-child {
- margin-bottom: 0;
- }
- }
-
- a {
- color: inherit;
- text-decoration: underline;
-
- &:hover {
- text-decoration: none;
- }
- }
-}
-
-.account__header__display-name {
- .emojione {
- width: 25px;
- height: 25px;
- }
-}
-
-.account__action-bar {
- border-top: 1px solid lighten($color1, 8%);
- border-bottom: 1px solid lighten($color1, 8%);
- line-height: 36px;
- overflow: hidden;
- flex: 0 0 auto;
- display: flex;
-}
-
-.account__action-bar-dropdown {
- flex: 1 1 auto;
- padding: 10px;
-
- .dropdown--active {
- .dropdown__content.dropdown__right {
- left: 6px;
- right: initial;
- }
-
- &:after {
- bottom: initial;
- margin-left: 11px;
- margin-top: -7px;
- right: initial;
- }
- }
-}
-
-.account__action-bar-links {
- display: flex;
- flex: 1 1 auto;
- line-height: 18px;
-}
-
-.account__action-bar__tab {
- text-decoration: none;
- overflow: hidden;
- width: 80px;
- border-left: 1px solid lighten($color1, 8%);
- padding: 10px 5px;
-
- & > span {
- display: block;
- text-transform: uppercase;
- font-size: 11px;
- color: $color3;
- }
-
- strong {
- display: block;
- font-size: 15px;
- font-weight: 500;
- color: $color5;
- }
-
- abbr {
- color: lighten($color1, 26%);
- }
-}
-
-.account__header__avatar {
- background-size: 90px 90px;
- display: block;
- height: 90px;
- margin: 0 auto 10px;
- overflow: hidden;
- width: 90px;
-}
-
-.account-authorize {
- padding: 14px 10px;
-
- .detailed-status__display-name {
- display: block;
- margin-bottom: 15px;
- overflow: hidden;
- }
-}
-
-.account-authorize__avatar {
- float: left;
- margin-right: 10px;
-}
-
-.status__display-name,
-.status__relative-time,
-.detailed-status__display-name,
-.detailed-status__datetime,
-.detailed-status__application,
-.account__display-name {
- text-decoration: none;
-}
-
-.status__display-name,
-.account__display-name {
- strong {
- color: $color5;
- }
-
- &.muted {
- .emojione {
- opacity: 0.5;
- }
- }
-}
-
-.status__display-name,
-.reply-indicator__display-name,
-.detailed-status__display-name,
-.account__display-name {
- &:hover strong {
- text-decoration: underline;
- }
-}
-
-.account__display-name strong {
- display: block;
-}
-
-.detailed-status__application,
-.detailed-status__datetime {
- color: inherit;
-}
-
-.detailed-status__display-name {
- color: $color2;
- display: block;
- line-height: 24px;
- margin-bottom: 15px;
- overflow: hidden;
-
- strong,
- span {
- display: block;
- }
-
- strong {
- font-size: 16px;
- color: $color5;
- }
-}
-
-.detailed-status__display-avatar {
- float: left;
- margin-right: 10px;
-}
-
-.status__avatar {
- height: 48px;
- left: 10px;
- position: absolute;
- top: 10px;
- width: 48px;
-}
-
-.muted {
- .status__content p,
- .status__content a {
- color: lighten($color1, 26%);
- }
-
- .status__display-name strong {
- color: lighten($color1, 26%);
- }
-
- .status__avatar {
- opacity: 0.5;
- }
-
- a.status__content__spoiler-link {
- background: lighten($color1, 26%);
- color: lighten($color1, 4%);
-
- &:hover {
- background: lighten($color1, 29%);
- text-decoration: none;
- }
- }
-}
-
-.notification__message {
- margin-left: 68px;
- padding: 8px 0;
- padding-bottom: 0;
- cursor: default;
- color: $color3;
- font-size: 15px;
- position: relative;
-
- .fa {
- color: $color4;
- }
-}
-
-.notification__favourite-icon-wrapper {
- left: -26px;
- position: absolute;
-
- .star-icon {
- color: #ca8f04;
- }
-}
-
-.star-icon.active {
- color: #ca8f04;
-}
-
-.notification__display-name {
- color: inherit;
- font-weight: 500;
- text-decoration: none;
-
- &:hover {
- color: $color5;
- text-decoration: underline;
- }
-}
-
-.display-name {
- display: block;
- max-width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.display-name__html {
- font-weight: 500;
-}
-
-.display-name__account {
- font-size: 14px;
-}
-
-.status__relative-time,
-.detailed-status__datetime {
- &:hover {
- text-decoration: underline;
- }
-}
-
-.transparent-background, .imageloader {
- background: image-url('void.png');
-}
-
-.imageloader {
- display: block;
-}
-
-.navigation-bar {
- padding: 10px;
- display: flex;
- flex-shrink: 0;
- cursor: default;
- color: $color3;
-
- strong {
- color: $color5;
- }
-
- .permalink {
- text-decoration: none;
- }
-}
-
-.navigation-bar__profile {
- flex: 1 1 auto;
- margin-left: 8px;
-}
-
-.navigation-bar__profile-account {
- display: block;
- font-weight: 500;
-}
-
-.navigation-bar__profile-edit {
- color: inherit;
- text-decoration: none;
-}
-
-.dropdown {
- display: inline-block;
-}
-
-.dropdown__content {
- display: none;
- position: absolute;
-}
-
-.dropdown__sep {
- border-bottom: 1px solid darken($color2, 8%);
- margin: 5px 7px 6px;
- padding-top: 1px;
-}
-
-.dropdown--active .dropdown__content {
- display: block;
- line-height: 18px;
- max-width: 311px;
- right: 0;
- text-align: left;
- z-index: 9999;
-
- & > ul {
- list-style: none;
- background: $color2;
- padding: 4px 0;
- border-radius: 4px;
- box-shadow: 0 0 15px rgba($color8, 0.4);
- min-width: 140px;
- position: relative;
- }
-
- &.dropdown__right {
- right: 0;
- }
-
- &.dropdown__left {
- & > ul {
- left: -98px;
- }
- }
-
- & > ul > li > a {
- font-size: 13px;
- line-height: 18px;
- display: block;
- padding: 4px 14px;
- box-sizing: border-box;
- text-decoration: none;
- background: $color2;
- color: $color1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-
- &:focus {
- outline: 0;
- }
-
- &:hover {
- background: $color4;
- color: $color2;
- }
- }
-}
-
-.dropdown__icon {
- vertical-align: middle;
-}
-
-.static-content {
- padding: 10px;
- padding-top: 20px;
- color: lighten($color1, 26%);
-
- h1 {
- font-size: 16px;
- font-weight: 500;
- margin-bottom: 40px;
- text-align: center;
- }
-
- p {
- font-size: 13px;
- margin-bottom: 20px;
- }
-}
-
-.columns-area {
- display: flex;
- flex: 1 1 auto;
- flex-direction: row;
- justify-content: flex-start;
- overflow-x: auto;
- position: relative;
-}
-
-@media screen and (min-width: 360px) {
- .columns-area {
- padding: 10px;
- }
-}
-
-.column {
- width: 330px;
- position: relative;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
-
- > .scrollable {
- background: $color1;
- }
-}
-
-.ui {
- flex: 0 0 auto;
- display: flex;
- flex-direction: column;
- width: 100%;
- height: 100%;
- background: darken($color1, 7%);
-}
-
-.drawer {
- width: 300px;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- overflow-y: hidden;
-}
-
-.drawer__tab {
- display: block;
- flex: 1 1 auto;
- padding: 15px;
- padding-bottom: 13px;
- color: $color3;
- text-decoration: none;
- text-align: center;
- font-size: 16px;
- border-bottom: 2px solid transparent;
-}
-
-.column, .drawer {
- flex: 1 1 100%;
- overflow: hidden;
-}
-
-@media screen and (min-width: 360px) {
- .tabs-bar {
- margin: 10px;
- margin-bottom: 0;
- }
-
- .search {
- margin-bottom: 10px;
- }
-}
-
-@media screen and (max-width: 1024px) {
- .column, .drawer {
- width: 100%;
- padding: 0;
- }
-
- .columns-area {
- flex-direction: column;
- }
-
- .search__input, .autosuggest-textarea__textarea {
- font-size: 16px;
- }
-}
-
-@media screen and (min-width: 1025px) {
- .columns-area {
- padding: 0;
- }
-
- .column, .drawer {
- flex: 0 0 auto;
- padding: 10px;
- padding-left: 5px;
- padding-right: 5px;
-
- &:first-child {
- padding-left: 10px;
- }
-
- &:last-child {
- padding-right: 10px;
- }
- }
-
- .columns-area > div {
- .column, .drawer {
- padding-left: 5px;
- padding-right: 5px;
- }
- }
-}
-
-@media screen and (min-width: 1397px) { /* Width of 4 columns with margins */
- .columns-area {
- margin-left: auto;
- margin-right: auto;
- }
-}
-
-@media screen and (min-width: 1900px) {
- .column, .drawer {
- width: 400px;
- border-radius: 4px;
- height: 96vh;
- margin-top: 2vh;
- }
-}
-
-.drawer__pager {
- box-sizing: border-box;
- padding: 0;
- flex-grow: 1;
- position: relative;
- overflow: hidden;
- display: flex;
-}
-
-.drawer__inner {
- position: absolute;
- top: 0;
- left: 0;
- background: lighten($color1, 13%);
- box-sizing: border-box;
- padding: 0;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- overflow-y: auto;
- width: 100%;
- height: 100%;
-
- &.darker {
- background: $color1;
- }
-}
-
-.pseudo-drawer {
- background: lighten($color1, 13%);
- font-size: 13px;
- text-align: left;
-}
-
-.drawer__header {
- flex: 0 0 auto;
- font-size: 16px;
- background: lighten($color1, 8%);
- margin-bottom: 10px;
- display: flex;
- flex-direction: row;
-
- a {
- transition: background 100ms ease-in;
-
- &:hover {
- background: lighten($color1, 3%);
- transition: background 200ms ease-out;
- }
- }
-}
-
-.tabs-bar {
- display: flex;
- background: lighten($color1, 8%);
- flex: 0 0 auto;
- overflow-y: auto;
-}
-
-.tabs-bar__link {
- display: block;
- flex: 1 1 auto;
- padding: 15px 10px;
- color: $color5;
- text-decoration: none;
- text-align: center;
- font-size: 14px;
- font-weight: 500;
- border-bottom: 2px solid lighten($color1, 8%);
- transition: all 200ms linear;
-
- .fa {
- font-weight: 400;
- font-size: 16px;
- }
-
- &.active {
- border-bottom: 2px solid $color4;
- color: $color4;
- }
-
- &:hover, &:focus, &:active {
- background: lighten($color1, 14%);
- transition: all 100ms linear;
- }
-
- span {
- margin-left: 5px;
- display: none;
- }
-}
-
-@media screen and (min-width: 600px) {
- .tabs-bar__link {
- span {
- display: inline;
- }
- }
-}
-
-@media screen and (min-width: 1025px) {
- .tabs-bar {
- display: none;
- }
-}
-
-.react-autosuggest__container {
- position: relative;
-}
-
-.react-autosuggest__suggestions-container {
- position: absolute;
- top: 100%;
- width: 100%;
- z-index: 99;
- box-shadow: 0 0 15px rgba($color8, 0.4);
-}
-
-.react-autosuggest__section-title {
- background: $color3;
- padding: 4px 10px;
- font-weight: 500;
- cursor: default;
- color: $color1;
- text-transform: uppercase;
- font-size: 11px;
-}
-
-.react-autosuggest__suggestions-list {
- background: $color2;
- color: $color1;
- font-size: 14px;
-}
-
-.react-autosuggest__suggestion {
- padding: 10px;
- cursor: pointer;
-}
-
-.react-autosuggest__suggestion--focused {
- background: $color4;
- color: $color5;
-}
-
-.scrollable {
- overflow-y: scroll;
- overflow-x: hidden;
- flex: 1 1 auto;
- backface-visibility: hidden;
- -webkit-overflow-scrolling: touch;
-
- &.optionally-scrollable {
- overflow-y: auto;
- }
-}
-
-.column-back-button {
- background: lighten($color1, 4%);
- color: $color4;
- cursor: pointer;
- flex: 0 0 auto;
- font-size: 16px;
- padding: 15px;
- z-index: 3;
-
- &:hover {
- text-decoration: underline;
- }
-}
-
-.column-back-button__icon {
- display: inline-block;
- margin-right: 5px;
-}
-
-.column-back-button--slim {
- position: relative;
-}
-
-.column-back-button--slim-button {
- cursor: pointer;
- flex: 0 0 auto;
- font-size: 16px;
- padding: 15px;
- position: absolute;
- right: 0;
- top: -48px;
-}
-
-.react-toggle {
- display: inline-block;
- position: relative;
- cursor: pointer;
- background-color: transparent;
- border: 0;
- padding: 0;
- user-select: none;
- -webkit-tap-highlight-color: rgba($color8, 0);
- -webkit-tap-highlight-color: transparent;
-}
-
-.react-toggle-screenreader-only {
- border: 0;
- clip: rect(0 0 0 0);
- height: 1px;
- margin: -1px;
- overflow: hidden;
- padding: 0;
- position: absolute;
- width: 1px;
-}
-
-.react-toggle--disabled {
- cursor: not-allowed;
- opacity: 0.5;
- transition: opacity 0.25s;
-}
-
-.react-toggle-track {
- width: 50px;
- height: 24px;
- padding: 0;
- border-radius: 30px;
- background-color: $color1;
- transition: all 0.2s ease;
-}
-
-.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
- background-color: darken($color1, 10%);
-}
-
-.react-toggle--checked .react-toggle-track {
- background-color: $color4;
-}
-
-.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
- background-color: lighten($color4, 10%);
-}
-
-.react-toggle-track-check {
- position: absolute;
- width: 14px;
- height: 10px;
- top: 0px;
- bottom: 0px;
- margin-top: auto;
- margin-bottom: auto;
- line-height: 0;
- left: 8px;
- opacity: 0;
- transition: opacity 0.25s ease;
-}
-
-.react-toggle--checked .react-toggle-track-check {
- opacity: 1;
- transition: opacity 0.25s ease;
-}
-
-.react-toggle-track-x {
- position: absolute;
- width: 10px;
- height: 10px;
- top: 0px;
- bottom: 0px;
- margin-top: auto;
- margin-bottom: auto;
- line-height: 0;
- right: 10px;
- opacity: 1;
- transition: opacity 0.25s ease;
-}
-
-.react-toggle--checked .react-toggle-track-x {
- opacity: 0;
-}
-
-.react-toggle-thumb {
- transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
- position: absolute;
- top: 1px;
- left: 1px;
- width: 22px;
- height: 22px;
- border: 1px solid $color1;
- border-radius: 50%;
- background-color: darken($color5, 2%);
- box-sizing: border-box;
- transition: all 0.25s ease;
-}
-
-.react-toggle--checked .react-toggle-thumb {
- left: 27px;
- border-color: $color4;
-}
-
-.column-link {
- background: lighten($color1, 8%);
- color: $color5;
- display: block;
- font-size: 16px;
- padding: 15px;
- text-decoration: none;
-
- &:hover {
- background: lighten($color1, 11%);
- }
-
- &.hidden-on-mobile {
- @media screen and (max-width: 1024px) {
- display: none;
- }
- }
-}
-
-.column-link__icon {
- display: inline-block;
- margin-right: 5px;
-}
-
-.column-subheading {
- background: $color1;
- color: lighten($color1, 26%);
- padding: 8px 20px;
- font-size: 12px;
- font-weight: 500;
- text-transform: uppercase;
- cursor: default;
-}
-
-.autosuggest-textarea,
-.spoiler-input {
- position: relative;
-}
-
-.autosuggest-textarea__textarea,
-.spoiler-input__input {
- display: block;
- box-sizing: border-box;
- width: 100%;
- margin: 0;
- color: $color1;
- padding: 10px;
- font-family: inherit;
- font-size: 14px;
- resize: vertical;
- border: 0;
- outline: 0;
-
- &:focus {
- outline: 0;
- }
-
- @media screen and (max-width: 600px) {
- font-size: 16px;
- }
-}
-
-.spoiler-input__input {
- border-radius: 4px;
-}
-
-.autosuggest-textarea__textarea {
- min-height: 100px;
- background: $color5;
- border-radius: 4px 4px 0 0;
- padding-bottom: 0;
- padding-right: 10px + 22px;
- resize: none;
-
- @media screen and (max-width: 600px) {
- height: 100px !important; // prevent auto-resize textarea
- resize: vertical;
- }
-}
-
-.autosuggest-textarea__suggestions {
- position: absolute;
- top: 100%;
- width: 100%;
- z-index: 99;
- box-shadow: 0 0 15px rgba($color8, 0.4);
- background: $color2;
- color: $color1;
- font-size: 14px;
-}
-
-.autosuggest-textarea__suggestions__item {
- padding: 10px;
- cursor: pointer;
-
- &:hover {
- background: darken($color2, 10%);
- }
-
- &.selected {
- background: $color4;
- color: $color5;
- }
-}
-
-.autosuggest-account {
- overflow: hidden;
-}
-
-.autosuggest-account-icon {
- float: left;
- margin-right: 5px;
-}
-
-.autosuggest-status {
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
-
- strong {
- font-weight: 500;
- }
-}
-
-.character-counter__wrapper {
- line-height: 36px;
- margin-right: 16px;
- padding-top: 10px;
-}
-
-.character-counter {
- cursor: default;
- font-size: 16px;
-}
-
-.character-counter--over {
- color: #ff5050;
-}
-
-.getting-started__wrapper {
- position: relative;
-}
-
-.getting-started {
- box-sizing: border-box;
- padding-bottom: 235px;
- background: image-url('mastodon-getting-started.png') no-repeat 0 100%/contain local;
- flex: 1 0 auto;
-
- p {
- color: $color2;
- }
-
- a {
- color: lighten($color1, 26%);
- }
-}
-
-.setting-text {
- color: $color3;
- background: transparent;
- border: none;
- border-bottom: 2px solid $color3;
- box-sizing: border-box;
- display: block;
- font-family: inherit;
- margin-bottom: 10px;
- padding: 7px 0px;
- width: 100%;
-
- &:focus, &:active {
- color: $color5;
- border-bottom-color: $color4;
- }
-
- @media screen and (max-width: 600px) {
- font-size: 16px;
- }
-}
-
-@import 'boost';
-
-button.icon-button i.fa-retweet {
- background-position: 0 0;
- height: 19px;
- transition: background-position 0.9s steps(10);
- transition-duration: 0s;
- vertical-align: middle;
- width: 22px;
-
- &::before {
- display: none !important;
- }
-}
-
-button.icon-button.active i.fa-retweet {
- transition-duration: 0.9s;
- background-position: 0 100%;
-}
-
-.status-card {
- display: flex;
- cursor: pointer;
- font-size: 14px;
- border: 1px solid lighten($color1, 8%);
- border-radius: 4px;
- color: lighten($color1, 26%);
- margin-top: 14px;
- text-decoration: none;
- overflow: hidden;
-
- &:hover {
- background: lighten($color1, 8%);
- }
-}
-
-.status-card-video, .status-card-rich, .status-card-photo {
- margin-top: 14px;
- overflow: hidden;
-
- iframe {
- width: 100%;
- height: auto;
- }
-}
-
-.status-card-photo {
- display: block;
- text-decoration: none;
-
- img {
- display: block;
- width: 100%;
- height: auto;
- margin: 0;
- }
-}
-
-.status-card__title {
- display: block;
- font-weight: 500;
- margin-bottom: 5px;
- color: $color3;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.status-card__content {
- flex: 1 1 auto;
- overflow: hidden;
- padding: 14px 14px 14px 8px;
-}
-
-.status-card__description {
- color: $color3;
-}
-
-.status-card__image {
- flex: 0 0 100px;
- background: lighten($color1, 8%);
-}
-
-.status-card__image-image {
- border-radius: 4px 0px 0px 4px;
- display: block;
- height: auto;
- margin: 0;
- width: 100%;
-}
-
-.load-more {
- display: block;
- color: lighten($color1, 26%);
- text-align: center;
- padding: 15px;
- text-decoration: none;
-
- &:hover {
- background: lighten($color1, 2%);
- }
-}
-
-.missing-indicator {
- text-align: center;
- font-size: 16px;
- font-weight: 500;
- color: lighten($color1, 16%);
- padding-top: 210px;
- background: image-url('mastodon-not-found.png') no-repeat center -50px;
- cursor: default;
-}
-
-.column-header {
- padding: 15px;
- font-size: 16px;
- background: lighten($color1, 4%);
- flex: 0 0 auto;
- cursor: pointer;
- position: relative;
- z-index: 2;
- outline: 0;
-
- &.active {
- box-shadow: 0 1px 0 rgba($color4, 0.3);
- }
-
- &.active .fa {
- color: $color4;
- text-shadow: 0 0 10px rgba($color4, 0.4);
- }
-
- &.hidden-on-mobile {
- @media screen and (max-width: 1024px) {
- display: none;
- }
- }
-
- &:focus, &:active {
- outline: 0;
- }
-}
-
-.column-header__icon {
- display: inline-block;
- margin-right: 5px;
-}
-
-.loading-indicator {
- color: $color2;
- font-size: 16px;
- font-weight: 500;
- padding-top: 120px;
- text-align: center;
-}
-
-.collapsable-collapsed {
- color: $color3;
- background: lighten($color1, 4%);
-}
-
-.collapsable {
- color: $color5;
- background: lighten($color1, 8%);
-
- &:hover {
- color: $color5;
- background: lighten($color1, 8%);
- }
-}
-
-.video-error-cover {
- align-items: center;
- background: $color8;
- color: $color5;
- cursor: pointer;
- display: flex;
- flex-direction: column;
- height: 100%;
- justify-content: center;
- margin-top: 8px;
- position: relative;
- text-align: center;
- z-index: 100;
-}
-
-.media-spoiler {
- align-items: center;
- background: $color8;
- color: $color5;
- cursor: pointer;
- display: flex;
- flex-direction: column;
- height: 100%;
- justify-content: center;
- position: relative;
- text-align: center;
- z-index: 100;
-}
-
-.media-spoiler__warning {
- display: block;
- font-size: 14px;
-}
-
-.media-spoiler__trigger {
- display: block;
- font-size: 11px;
- font-weight: 500;
-}
-
-.spoiler-button {
- left: 4px;
- position: absolute;
- text-shadow: 0px 1px 1px #000, 1px 0px 1px #000;
- top: 4px;
- z-index: 100;
-}
-
-.modal-container--preloader {
- background: lighten($color1, 8%);
-}
-
-.account--panel {
- background: lighten($color1, 4%);
- border-top: 1px solid lighten($color1, 8%);
- border-bottom: 1px solid lighten($color1, 8%);
- display: flex;
- flex-direction: row;
- padding: 10px 0px;
-}
-
-.account--panel__button,
-.detailed-status__button {
- flex: 1 1 auto;
- text-align: center;
-}
-
-.column-settings__outer {
- background: lighten($color1, 8%);
- padding: 15px;
-}
-
-.column-settings__section {
- color: $color3;
- cursor: default;
- display: block;
- font-weight: 500;
- margin-bottom: 10px;
-}
-
-.modal-container__nav {
- align-items: center;
- background: rgba(0, 0, 0, 0.5);
- box-sizing: border-box;
- color: $color5;
- cursor: pointer;
- display: flex;
- font-size: 24px;
- height: 100%;
- padding: 30px 15px;
- position: absolute;
- top: 0;
-}
-
-.modal-container__nav--left {
- left: -61px;
-}
-
-.modal-container__nav--right {
- right: -61px;
-}
-
-.account--follows-info {
- color: $color5;
-}
-
-.setting-toggle__label {
- display: block;
- line-height: 24px;
- vertical-align: middle;
-}
-
-.setting-toggle {
- color: $color3;
- display: inline-block;
- margin-bottom: 14px;
- margin-left: 8px;
- vertical-align: middle;
-}
-
-.report.scrollable {
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- max-height: 100%;
-}
-
-.report__target {
- border-bottom: 1px solid lighten($color1, 4%);
- color: $color2;
- flex: 0 0 auto;
- padding: 10px;
-
- strong {
- display: block;
- color: $color5;
- font-weight: 500;
- }
-}
-
-.report__statuses {
- flex: 1 1 auto;
-}
-
-.report__textarea-wrapper {
- flex: 0 0 100px;
- padding: 10px;
-}
-
-.report__textarea {
- background: transparent;
- box-sizing: border-box;
- border: 0;
- border-bottom: 2px solid $color3;
- border-radius: 2px 2px 0 0;
- color: $color5;
- display: block;
- font-family: inherit;
- font-size: 14px;
- margin-bottom: 10px;
- outline: 0;
- padding: 7px 4px;
- resize: vertical;
- width: 100%;
-
- &:active, &:focus {
- border-bottom-color: $color4;
- background: rgba($color8, 0.1);
- }
-}
-
-.report__submit {
- margin-top: 10px;
- overflow: hidden;
-}
-
-.report__submit-button {
- float: right;
-}
-
-.empty-column-indicator {
- color: lighten($color1, 20%);
- background: $color1;
- text-align: center;
- padding: 20px;
- font-size: 15px;
- font-weight: 400;
- cursor: default;
- display: flex;
- flex: 1 1 auto;
- align-items: center;
-
- a {
- color: $color4;
- text-decoration: none;
-
- &:hover {
- text-decoration: underline;
- }
- }
-}
-
-.status-list__unread-indicator, .notifications__unread-indicator {
- position: absolute;
- top: 35px;
- left: 0;
- right: 0;
- margin: 0 auto;
- width: 60%;
- pointer-events: none;
- height: 28px;
- z-index: 1;
- background: radial-gradient(ellipse, rgba($color4, 0.23) 0%, rgba($color4, 0) 60%);
-}
-
-.emoji-dialog {
- width: 245px;
- height: 270px;
- background: $color5;
- box-sizing: border-box;
- border-radius: 4px;
- overflow: hidden;
- position: relative;
- box-shadow: 0 0 8px rgba($color8, 0.2);
-
- .emojione {
- margin: 0;
- width: 100%;
- height: auto;
- }
-
- .emoji-dialog-header {
- padding: 0 10px;
-
- ul {
- padding: 0;
- margin: 0;
- list-style: none;
- }
-
- li {
- display: inline-block;
- box-sizing: border-box;
- padding: 10px 5px;
- cursor: pointer;
- border-bottom: 2px solid transparent;
-
- .emoji {
- width: 18px;
- height: 18px;
- }
-
- img, svg {
- width: 18px;
- height: 18px;
- filter: grayscale(100%);
- }
-
- &:hover {
- img, svg {
- filter: grayscale(0);
- }
- }
-
- &.active {
- border-bottom-color: $color4;
-
- img, svg {
- filter: grayscale(0);
- }
- }
- }
- }
-
- .emoji-row {
- box-sizing: border-box;
- overflow-y: hidden;
- padding-left: 10px;
-
- .emoji {
- display: inline-block;
- padding: 2.5px;
- border-radius: 4px;
- }
- }
-
- .emoji-category-header {
- box-sizing: border-box;
- overflow-y: hidden;
- padding: 10px 8px 10px 16px;
- display: table;
-
- > * {
- display: table-cell;
- vertical-align: middle;
- }
- }
-
- .emoji-category-title {
- font-size: 12px;
- text-transform: uppercase;
- font-weight: 500;
- color: darken($color2, 18%);
- cursor: default;
- }
-
- .emoji-category-heading-decoration {
- text-align: right;
- }
-
- .modifiers {
- list-style: none;
- padding: 0;
- margin: 0;
- vertical-align: middle;
- white-space: nowrap;
- margin-top: 4px;
-
- li {
- display: inline-block;
- padding: 0 2px;
-
- &:last-of-type {
- padding-right: 0;
- }
- }
-
- .modifier {
- display: inline-block;
- border-radius: 10px;
- width: 15px;
- height: 15px;
- position: relative;
- cursor: pointer;
-
- &.active:after {
- content: "";
- display: block;
- position: absolute;
- width: 7px;
- height: 7px;
- border-radius: 10px;
- border: 2px solid $color5;
- top: 2px;
- left: 2px;
- }
- }
- }
-
- .emoji-search-wrapper {
- padding: 10px;
- border-bottom: 1px solid lighten($color2, 4%);
- }
-
- .emoji-search {
- font-size: 14px;
- font-weight: 400;
- padding: 7px 9px;
- font-family: inherit;
- display: block;
- width: 100%;
- background: rgba($color2, 0.3);
- color: darken($color2, 18%);
- border: 1px solid $color2;
- border-radius: 4px;
- }
-
- .emoji-categories-wrapper {
- position: absolute;
- top: 42px;
- bottom: 0;
- left: 0;
- right: 0;
- }
-
- .emoji-search-wrapper + .emoji-categories-wrapper {
- top: 93px;
- }
-
- .emoji-row .emoji {
- img, svg {
- transition: transform 60ms ease-in-out;
- }
-
- &:hover {
- background: lighten($color2, 3%);
-
- img, svg {
- transform: translateZ(0) scale(1.2);
- }
- }
- }
-
- .emoji {
- width: 22px;
- height: 22px;
- cursor: pointer;
-
- &:focus {
- outline: 0;
- }
- }
-}
-
-.upload-area {
- align-items: center;
- background: rgba($color8, 0.8);
- display: flex;
- height: 100%;
- justify-content: center;
- left: 0;
- opacity: 0;
- position: absolute;
- top: 0;
- visibility: hidden;
- width: 100%;
- z-index: 2000;
-
- * {
- pointer-events: none;
- }
-}
-
-.upload-area__drop {
- width: 320px;
- height: 160px;
- display: flex;
- box-sizing: border-box;
- position: relative;
- padding: 8px;
-}
-
-.upload-area__background {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: -1;
- border-radius: 4px;
- background: $color1;
- box-shadow: 0 0 5px rgba($color8, 0.2);
-}
-
-.upload-area__content {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- color: $color2;
- font-size: 18px;
- font-weight: 500;
- border: 2px dashed lighten($color1, 26%);
- border-radius: 4px;
-}
-
-.upload-progress {
- padding: 10px;
- color: lighten($color1, 26%);
- overflow: hidden;
- display: flex;
-
- .fa {
- font-size: 34px;
- margin-right: 10px;
- }
-
- span {
- font-size: 12px;
- text-transform: uppercase;
- font-weight: 500;
- display: block;
- }
-}
-
-.upload-progess__message {
- flex: 1 1 auto;
-}
-
-.upload-progress__backdrop {
- width: 100%;
- height: 6px;
- border-radius: 6px;
- background: lighten($color1, 26%);
- position: relative;
- margin-top: 5px;
-}
-
-.upload-progress__tracker {
- position: absolute;
- left: 0;
- top: 0;
- height: 6px;
- background: $color4;
- border-radius: 6px;
-}
-
-.emoji-button {
- outline: 0;
-
- &:active, &:focus {
- outline: 0 !important;
- }
-
- img {
- filter: grayscale(100%);
- opacity: 0.8;
- display: block;
- margin: 0;
- width: 22px;
- height: 22px;
- margin-top: 2px;
- }
-
- &:hover, &:active, &:focus {
- img {
- opacity: 1;
- filter: none;
- }
- }
-}
-
-.dropdown--active .emoji-button img {
- opacity: 1;
- filter: none;
-}
-
-.privacy-dropdown {
- position: relative;
-}
-
-.privacy-dropdown__dropdown {
- display: none;
- position: absolute;
- left: 0;
- top: 27px;
- width: 230px;
- background: $color5;
- border-radius: 0 4px 4px 4px;
- z-index: 2;
- overflow: hidden;
-}
-
-.privacy-dropdown__option {
- color: $color1;
- padding: 10px;
- cursor: pointer;
- display: flex;
-
- &:hover, &.active {
- background: $color4;
- color: $color5;
-
- .privacy-dropdown__option__content {
- color: $color5;
-
- strong {
- color: $color5;
- }
- }
- }
-
- &.active:hover {
- background: lighten($color4, 4%);
- }
-}
-
-.privacy-dropdown__option__icon {
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 10px;
-}
-
-.privacy-dropdown__option__content {
- flex: 1 1 auto;
- color: darken($color3, 24%);
-
- strong {
- font-weight: 500;
- display: block;
- color: $color1;
- }
-}
-
-.privacy-dropdown.active {
- .privacy-dropdown__value {
- background: $color5;
- border-radius: 4px 4px 0 0;
- box-shadow: 0 -4px 4px rgba($color8, 0.1);
- }
-
- .privacy-dropdown__dropdown {
- display: block;
- box-shadow: 2px 4px 6px rgba($color8, 0.1);
- }
-}
-
-.search {
- position: relative;
-}
-
-.search__input {
- padding-right: 30px;
- color: $color2;
- outline: 0;
- box-sizing: border-box;
- display: block;
- width: 100%;
- border: none;
- padding: 10px;
- padding-right: 30px;
- font-family: inherit;
- background: $color1;
- color: $color3;
- font-size: 14px;
- margin: 0;
-
- &::-moz-focus-inner {
- border: 0;
- }
-
- &::-moz-focus-inner, &:focus, &:active {
- outline: 0 !important;
- }
-
- &:focus {
- background: lighten($color1, 4%);
- }
-
- @media screen and (max-width: 600px) {
- font-size: 16px;
- }
-}
-
-.search__icon {
- .fa {
- position: absolute;
- top: 10px;
- right: 10px;
- z-index: 2;
- display: inline-block;
- opacity: 0;
- transition: all 100ms linear;
- font-size: 18px;
- width: 18px;
- height: 18px;
- color: $color2;
- cursor: default;
- pointer-events: none;
-
- &.active {
- pointer-events: auto;
- opacity: 0.3;
- }
- }
-
- .fa-search {
- transform: translateZ(0) rotate(90deg);
-
- &.active {
- pointer-events: none;
- transform: translateZ(0) rotate(0deg);
- }
- }
-
- .fa-times-circle {
- top: 11px;
- transform: translateZ(0) rotate(0deg);
- cursor: pointer;
-
- &.active {
- transform: translateZ(0) rotate(90deg);
- }
-
- &:hover {
- color: $color5;
- }
- }
-}
-
-.search-results__header {
- color: lighten($color1, 26%);
- background: lighten($color1, 2%);
- border-bottom: 1px solid darken($color1, 4%);
- padding: 15px 10px;
- font-size: 14px;
- font-weight: 500;
-}
-
-.search-results__hashtag {
- display: block;
- padding: 10px;
- color: $color2;
- text-decoration: none;
-
- &:hover, &:active, &:focus {
- color: lighten($color2, 4%);
- text-decoration: underline;
- }
-}
-
-.modal-root__overlay {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 9999;
- opacity: 0;
- background: rgba($color8, 0.7);
- transform: translateZ(0px);
-}
-
-.modal-root__container {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- align-content: space-around;
- z-index: 9999;
- opacity: 0;
- pointer-events: none;
- user-select: none;
-}
-
-.modal-root__modal {
- pointer-events: auto;
- display: flex;
- z-index: 9999;
-}
-
-.media-modal {
- max-width: 80vw;
- max-height: 80vh;
- position: relative;
-
- img, video {
- max-width: 80vw;
- max-height: 80vh;
- }
-}
-
-.media-modal__close {
- position: absolute;
- right: 4px;
- top: 4px;
- z-index: 100;
-}
-
-.onboarding-modal {
- background: $color2;
- color: $color1;
- border-radius: 8px;
- overflow: hidden;
- display: flex;
- flex-direction: column;
-}
-
-.onboarding-modal__pager {
- height: 80vh;
- width: 80vw;
- max-width: 520px;
- max-height: 420px;
- position: relative;
-
- & > div {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- box-sizing: border-box;
- padding: 25px;
- display: none;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- display: flex;
- opacity: 0;
- user-select: text;
- }
-}
-
-@media screen and (max-width: 550px) {
- .onboarding-modal {
- width: 100%;
- height: 100%;
- border-radius: 0;
- }
-
- .onboarding-modal__pager {
- width: 100%;
- height: auto;
- max-width: none;
- max-height: none;
- flex: 1 1 auto;
- }
-}
-
-.onboarding-modal__paginator {
- flex: 0 0 auto;
- background: darken($color2, 8%);
- display: flex;
- padding: 25px;
-
- & > div {
- min-width: 33px;
- }
-
- a {
- color: darken($color2, 34%);
- text-decoration: none;
- font-size: 14px;
- font-weight: 500;
-
- &:hover, &:focus, &:active {
- color: darken($color2, 38%);
- }
-
- &.onboarding-modal__done, &.onboarding-modal__next {
- color: $color4;
- }
- }
-}
-
-.onboarding-modal__dots {
- flex: 1 1 auto;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.onboarding-modal__dot {
- width: 14px;
- height: 14px;
- border-radius: 14px;
- background: darken($color2, 16%);
- margin: 0 3px;
- cursor: pointer;
-
- &:hover {
- background: darken($color2, 18%);
- }
-
- &.active {
- cursor: default;
- background: darken($color2, 24%);
- }
-}
-
-.onboarding-modal__page {
- cursor: default;
- line-height: 21px;
-
- h1 {
- font-size: 18px;
- font-weight: 500;
- color: $color1;
- margin-bottom: 20px;
- }
-
- a {
- color: $color4;
-
- &:hover, &:focus, &:active {
- color: lighten($color4, 4%);
- }
- }
-
- p {
- font-size: 16px;
- color: lighten($color1, 8%);
- margin-top: 10px;
- margin-bottom: 10px;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- strong {
- font-weight: 500;
- background: $color1;
- color: $color2;
- border-radius: 4px;
- font-size: 14px;
- padding: 3px 6px;
- }
- }
-}
-
-.onboarding-modal__page-one {
- display: flex;
-}
-
-.onboarding-modal__page-one__elephant-friend {
- background: image-url('elephant-friend.png') no-repeat center center/contain;
- width: 147px;
- height: 160px;
- margin-right: 10px;
-}
-
-.onboarding-modal__page-two,
-.onboarding-modal__page-three,
-.onboarding-modal__page-four,
-.onboarding-modal__page-five {
- p {
- text-align: left;
- }
-
- .figure {
- background: darken($color1, 8%);
- color: $color2;
- margin-bottom: 20px;
- border-radius: 4px;
- padding: 10px;
- text-align: center;
- font-size: 14px;
- box-shadow: 1px 2px 6px rgba($color8, 0.3);
-
- .onboarding-modal__image {
- border-radius: 4px;
- margin-bottom: 10px;
- }
-
- &.non-interactive {
- pointer-events: none;
- text-align: left;
- }
- }
-}
-
-.onboarding-modal__page-four__columns {
- .row {
- display: flex;
- margin-bottom: 20px;
-
- & > div {
- flex: 1 1 0;
- margin: 0 10px;
-
- &:first-child {
- margin-left: 0;
- }
-
- &:last-child {
- margin-right: 0;
- }
-
- p {
- text-align: center;
- }
- }
-
- &:last-child {
- margin-bottom: 0;
- }
- }
-
- .column-header {
- color: $color5;
- }
-}
-
-.onboarding-modal__image {
- border-radius: 8px;
- width: 70vw;
- max-width: 450px;
- max-height: auto;
- display: block;
- margin: auto;
- margin-bottom: 20px;
-}
-
-.onboard-sliders {
- display: inline-block;
- max-width: 30px;
- max-height: auto;
- margin-left: 10px;
-}
-
-.boost-modal, .confirmation-modal {
- background: lighten($color2, 8%);
- color: $color1;
- border-radius: 8px;
- overflow: hidden;
- max-width: 90vw;
- width: 480px;
- position: relative;
- flex-direction: column;
-
- .status__display-name {
- display: block;
- max-width: 100%;
- padding-right: 25px;
- }
-
- .status__avatar {
- height: 28px;
- left: 10px;
- position: absolute;
- top: 10px;
- width: 48px;
- }
-}
-
-.boost-modal__container {
- overflow-x: scroll;
- padding: 10px;
-
- .status {
- user-select: text;
- border-bottom: 0;
- }
-}
-
-.boost-modal__action-bar, .confirmation-modal__action-bar {
- display: flex;
- background: $color2;
- padding: 10px;
- line-height: 36px;
-
- & > div {
- flex: 1 1 auto;
- text-align: right;
- color: lighten($color1, 33%);
- padding-right: 10px;
- }
-
- .button {
- flex: 0 0 auto;
- }
-}
-
-.boost-modal__status-header {
- font-size: 15px;
-}
-
-.boost-modal__status-time {
- float: right;
- font-size: 14px;
-}
-
-.confirmation-modal {
- max-width: 380px;
-}
-
-.confirmation-modal__action-bar {
- & > div {
- text-align: left;
- padding: 0 16px;
- }
-
- a {
- color: darken($color2, 34%);
- text-decoration: none;
- font-size: 14px;
- font-weight: 500;
-
- &:hover, &:focus, &:active {
- color: darken($color2, 38%);
- }
- }
-}
-
-.confirmation-modal__container {
- padding: 30px;
- font-size: 16px;
- text-align: center;
-
- strong {
- font-weight: 500;
- }
-}
-
-.loading-bar {
- background-color: $color4;
- height: 3px;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-.media-gallery__gifv__label {
- display: block;
- position: absolute;
- color: $color5;
- background: rgba($color8, 0.5);
- bottom: 6px;
- left: 6px;
- padding: 2px 6px;
- border-radius: 2px;
- font-size: 11px;
- font-weight: 600;
- z-index: 1;
- pointer-events: none;
- opacity: 0.9;
- transition: opacity 0.1s ease;
-}
-
-.media-gallery__gifv {
- &.autoplay {
- .media-gallery__gifv__label {
- display: none;
- }
- }
-
- &:hover {
- .media-gallery__gifv__label {
- opacity: 1;
- }
- }
-}
-
-.attachment-list {
- display: flex;
- font-size: 14px;
- border: 1px solid lighten($color1, 8%);
- border-radius: 4px;
- margin-top: 14px;
- overflow: hidden;
-}
-
-.attachment-list__icon {
- flex: 0 0 auto;
- color: lighten($color1, 26%);
- padding: 8px 18px;
- cursor: default;
- border-right: 1px solid lighten($color1, 8%);
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: 26px;
-
- .fa {
- display: block;
- }
-}
-
-.attachment-list__list {
- list-style: none;
- padding: 4px 0;
- padding-left: 8px;
- display: flex;
- flex-direction: column;
- justify-content: center;
-
- li {
- display: block;
- padding: 4px 0;
- }
-
- a {
- text-decoration: none;
- color: lighten($color1, 26%);
- font-weight: 500;
-
- &:hover {
- text-decoration: underline;
- }
- }
-}
-
-/* Media Gallery */
-.media-gallery {
- box-sizing: border-box;
- margin-top: 8px;
- overflow: hidden;
- position: relative;
- width: 100%;
-}
-
-.media-gallery__item {
- border: none;
- box-sizing: border-box;
- display: block;
- float: left;
- position: relative;
-}
-
-.media-gallery__item-thumbnail {
- background-position: center;
- background-repeat: no-repeat;
- background-size: cover;
- cursor: zoom-in;
- display: block;
- height: 100%;
- text-decoration: none;
- width: 100%;
-}
-
-.media-gallery__gifv {
- height: 100%;
- overflow: hidden;
- position: relative;
- width: 100%;
-}
-
-.media-gallery__item-gifv-thumbnail {
- cursor: zoom-in;
- height: 100%;
- object-fit: cover;
- position: relative;
- top: 50%;
- transform: translateY(-50%);
- width: 100%;
- z-index: 1;
-}
-
-.media-gallery__item-thumbnail-label {
- clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
- clip: rect(1px, 1px, 1px, 1px);
- overflow: hidden;
- position: absolute;
-}
-/* End Media Gallery */
-
-/* Status Video Player */
-.status__video-player {
- background: #000;
- box-sizing: border-box;
- cursor: default; /* May not be needed */
- margin-top: 8px;
- overflow: hidden;
- position: relative;
-}
-
-.status__video-player-video {
- height: 100%;
- object-fit: cover;
- position: relative;
- top: 50%;
- transform: translateY(-35%);
- width: 100%;
- z-index: 1;
-}
-
-.status__video-player-expand,
-.status__video-player-mute {
- color: #fff;
- opacity: 0.8;
- position: absolute;
- right: 4px;
- text-shadow: 0px 1px 1px #000, 1px 0px 1px #000;
-}
-
-.status__video-player-spoiler {
- color: #fff;
- left: 4px;
- position: absolute;
- text-shadow: 0px 1px 1px #000, 1px 0px 1px #000;
- top: 4px;
- z-index: 100;
-}
-
-.status__video-player-expand {
- bottom: 4px;
- z-index: 100;
-}
-
-.status__video-player-mute {
- top: 4px;
- z-index: 5;
-}
-
-.media-spoiler-video {
- background-size: cover;
- cursor: pointer;
- margin-top: 8px;
- position: relative;
-}
-
-.media-spoiler-video-play-icon {
- border-radius: 100px;
- color: rgba(255, 255, 255, 0.8);
- font-size: 36px;
- left: 50%;
- padding: 5px;
- position: absolute;
- top: 50%;
- transform: translate(-50%, -50%);
-}
-/* End Video Player */
diff --git a/app/assets/stylesheets/containers.scss b/app/assets/stylesheets/containers.scss
@@ -1,71 +0,0 @@
-.container {
- width: 700px;
- margin: 0 auto;
- margin-top: 40px;
-
- @media screen and (max-width: 700px) {
- width: 100%;
- margin: 0;
- }
-}
-
-.mastodon-column-container {
- display: flex;
- height: 100%;
- width: 100%;
-
- // 707568 - height 100% doesn't work on child of a flex item - chromium - Monorail
- // https://bugs.chromium.org/p/chromium/issues/detail?id=707568
- flex: 1 1 auto;
-}
-
-.logo-container {
- max-width: 400px;
- margin: 100px auto;
- margin-bottom: 0;
- cursor: default;
-
- @media screen and (max-width: 360px) {
- margin: 30px auto;
- }
-
- h1 {
- display: block;
- text-align: center;
- color: $color5;
- font-size: 48px;
- font-weight: 500;
-
- img {
- display: block;
- margin: 20px auto;
- width: 180px;
- height: 180px;
- }
-
- a {
- color: inherit;
- text-decoration: none;
- outline: 0;
-
- img {
- opacity: 0.8;
- transition: opacity 0.8s ease;
- }
-
- &:hover {
- img {
- opacity: 1;
- transition-duration: 0.2s;
- }
- }
- }
-
- small {
- display: block;
- font-size: 12px;
- font-weight: 400;
- font-family: 'Roboto Mono', monospace;
- }
- }
-}
diff --git a/app/assets/stylesheets/fonts/montserrat.scss b/app/assets/stylesheets/fonts/montserrat.scss
@@ -1,11 +0,0 @@
-@font-face {
- font-family: 'Montserrat';
- src: local('Montserrat');
- src: font-url('montserrat/Montserrat-Regular.eot');
- src: font-url('montserrat/Montserrat-Regular.eot?#iefix') format('embedded-opentype'),
- font-url('montserrat/Montserrat-Regular.woff2') format('woff2'),
- font-url('montserrat/Montserrat-Regular.woff') format('woff'),
- font-url('montserrat/Montserrat-Regular.ttf') format('truetype');
- font-weight: 400;
- font-style: normal;
-}
diff --git a/app/assets/stylesheets/fonts/roboto-mono.scss b/app/assets/stylesheets/fonts/roboto-mono.scss
@@ -1,12 +0,0 @@
-@font-face {
- font-family: 'Roboto Mono';
- src: local('Roboto Mono');
- src: font-url('roboto-mono/robotomono-regular-webfont.eot');
- src: font-url('roboto-mono/robotomono-regular-webfont.eot?#iefix') format('embedded-opentype'),
- font-url('roboto-mono/robotomono-regular-webfont.woff2') format('woff2'),
- font-url('roboto-mono/robotomono-regular-webfont.woff') format('woff'),
- font-url('roboto-mono/robotomono-regular-webfont.ttf') format('truetype'),
- font-url('roboto-mono/robotomono-regular-webfont.svg#roboto_monoregular') format('svg');
- font-weight: 400;
- font-style: normal;
-}
diff --git a/app/assets/stylesheets/fonts/roboto.scss b/app/assets/stylesheets/fonts/roboto.scss
@@ -1,52 +0,0 @@
-@font-face {
- font-family: 'Roboto';
- src: local('Roboto');
- src: font-url('roboto/roboto-italic-webfont.eot');
- src: font-url('roboto/roboto-italic-webfont.eot?#iefix') format('embedded-opentype'),
- font-url('roboto/roboto-italic-webfont.woff2') format('woff2'),
- font-url('roboto/roboto-italic-webfont.woff') format('woff'),
- font-url('roboto/roboto-italic-webfont.ttf') format('truetype'),
- font-url('roboto/roboto-italic-webfont.svg#roboto-italic-webfont') format('svg');
- font-weight: normal;
- font-style: italic;
-}
-
-@font-face {
- font-family: 'Roboto';
- src: local('Roboto');
- src: font-url('roboto/roboto-bold-webfont.eot');
- src: local('Roboto bold'), local('roboto-bold'),
- font-url('roboto/roboto-bold-webfont.eot?#iefix') format('embedded-opentype'),
- font-url('roboto/roboto-bold-webfont.woff2') format('woff2'),
- font-url('roboto/roboto-bold-webfont.woff') format('woff'),
- font-url('roboto/roboto-bold-webfont.ttf') format('truetype'),
- font-url('roboto/roboto-bold-webfont.svg#roboto-bold-webfont') format('svg');
- font-weight: bold;
- font-style: normal;
-}
-
-@font-face {
- font-family: 'Roboto';
- src: local('Roboto');
- src: font-url('roboto/roboto-medium-webfont.eot');
- src: font-url('roboto/roboto-medium-webfont.eot?#iefix') format('embedded-opentype'),
- font-url('roboto/roboto-medium-webfont.woff2') format('woff2'),
- font-url('roboto/roboto-medium-webfont.woff') format('woff'),
- font-url('roboto/roboto-medium-webfont.ttf') format('truetype'),
- font-url('roboto/roboto-medium-webfont.svg#roboto-medium-webfont') format('svg');
- font-weight: 500;
- font-style: normal;
-}
-
-@font-face {
- font-family: 'Roboto';
- src: local('Roboto');
- src: font-url('roboto/roboto-regular-webfont.eot');
- src: font-url('roboto/roboto-regular-webfont.eot?#iefix') format('embedded-opentype'),
- font-url('roboto/roboto-regular-webfont.woff2') format('woff2'),
- font-url('roboto/roboto-regular-webfont.woff') format('woff'),
- font-url('roboto/roboto-regular-webfont.ttf') format('truetype'),
- font-url('roboto/roboto-regular-webfont.svg#roboto-regular-webfont') format('svg');
- font-weight: normal;
- font-style: normal;
-}
diff --git a/app/assets/stylesheets/footer.scss b/app/assets/stylesheets/footer.scss
@@ -1,29 +0,0 @@
-.footer {
- text-align: center;
- margin-top: 30px;
- font-size: 12px;
- color: darken($color2, 25%);
-
- .domain {
- font-weight: 500;
-
- a {
- color: inherit;
- text-decoration: none;
- }
- }
-
- .powered-by, .single-user-login {
- font-weight: 400;
-
- a {
- color: inherit;
- text-decoration: underline;
- font-weight: 500;
-
- &:hover {
- text-decoration: none;
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/forms.scss b/app/assets/stylesheets/forms.scss
@@ -1,335 +0,0 @@
-code {
- font-family: 'Roboto Mono', monospace;
- font-weight: 400;
-}
-
-.form-container {
- max-width: 400px;
- padding: 20px;
- margin: 0 auto;
-}
-
-.simple_form {
- .input {
- margin-bottom: 15px;
- }
-
- span.hint {
- display: block;
- color: $color3;
- font-size: 12px;
- margin-top: 4px;
- }
-
- p.hint {
- margin-bottom: 15px;
- }
-
- strong {
- font-weight: 500;
- }
-
- .label_input {
- display: flex;
-
- label {
- flex: 0 0 auto;
- width: 100px;
- }
-
- input {
- flex: 1 1 auto;
- }
- }
-
- .input.file, .input.select, .input.radio_buttons {
- padding: 15px 0;
- margin-bottom: 0;
-
- label {
- font-family: inherit;
- font-size: 16px;
- color: $color5;
- display: block;
- padding-top: 5px;
- }
- }
-
- .fields-group {
- margin-bottom: 25px;
- }
-
- .input.radio_buttons .radio label {
- margin-bottom: 5px;
- font-family: inherit;
- font-size: 14px;
- color: white;
- display: block;
- width: auto;
- }
-
- .input.boolean {
- margin-bottom: 5px;
-
- label {
- font-family: inherit;
- font-size: 14px;
- color: white;
- display: block;
- width: auto;
- }
-
- label.checkbox {
- position: relative;
- padding-left: 25px;
- flex: 1 1 auto;
- }
-
- input[type=checkbox] {
- position: absolute;
- left: 0;
- top: 1px;
- margin: 0;
- }
-
- .hint {
- padding-left: 25px;
- margin-left: 0;
- }
- }
-
- input[type=text], input[type=number], input[type=email], input[type=password], textarea {
- background: transparent;
- box-sizing: border-box;
- border: 0;
- border-bottom: 2px solid $color3;
- border-radius: 2px 2px 0 0;
- padding: 7px 4px;
- font-size: 16px;
- color: $color5;
- display: block;
- width: 100%;
- outline: 0;
- font-family: inherit;
- resize: vertical;
-
- &:invalid {
- box-shadow: none;
- }
-
- &:focus:invalid {
- border-bottom-color: $color6;
- }
-
- &:required:valid {
- border-bottom-color: $color7;
- }
-
- &:active, &:focus {
- border-bottom-color: $color4;
- background: rgba($color8, 0.1);
- }
- }
-
- .input.field_with_errors {
- label {
- color: $color6;
- }
-
- input[type=text], input[type=email], input[type=password] {
- border-bottom-color: $color6;
- }
-
- .error {
- display: block;
- font-weight: 500;
- color: $color6;
- margin-top: 4px;
- }
- }
-
- .actions {
- margin-top: 30px;
- }
-
- button, .block-button {
- display: block;
- width: 100%;
- border: 0;
- border-radius: 4px;
- background: $color4;
- color: $color5;
- font-size: 18px;
- padding: 10px;
- text-transform: uppercase;
- text-decoration: none;
- text-align: center;
- box-sizing: border-box;
- cursor: pointer;
- font-weight: 500;
- outline: 0;
- margin-bottom: 10px;
-
- &:hover {
- background-color: lighten($color4, 5%);
- }
-
- &:active, &:focus {
- position: relative;
- top: 1px;
- background-color: darken($color4, 5%);
- }
-
- &.negative {
- background: $color6;
-
- &:hover {
- background-color: lighten($color6, 5%);
- }
-
- &:active, &:focus {
- background-color: darken($color6, 5%);
- }
- }
- }
-
- select {
- font-size: 16px;
- }
-}
-
-.flash-message {
- background: $color1;
- color: $color3;
- border-radius: 4px;
- padding: 15px 10px;
- margin-bottom: 30px;
- box-shadow: 0 0 5px rgba($color8, 0.2);
- text-align: center;
-
- strong {
- font-weight: 500;
- }
-}
-
-.form-footer {
- margin-top: 30px;
- text-align: center;
-
- a {
- color: $color5;
- text-decoration: none;
-
- &:hover {
- text-decoration: underline;
- }
- }
-}
-
-.oauth-prompt, .follow-prompt {
- margin-bottom: 30px;
- text-align: center;
- color: $color3;
-
- h2 {
- font-size: 16px;
- margin-bottom: 30px;
- }
-
- strong {
- color: $color2;
- font-weight: 500;
- }
-}
-
-.qr-wrapper {
- display: flex;
-}
-
-.qr-code {
- flex: 0 0 auto;
- background: #fff;
- padding: 4px;
- margin-bottom: 20px;
- box-shadow: 0 0 15px rgba($color8, 0.2);
- display: inline-block;
-
- svg {
- display: block;
- margin: 0;
- }
-}
-
-.qr-alternative {
- margin-left: 10px;
- color: $color3;
-
- samp {
- display: block;
- font-size: 14px;
- }
-}
-
-.table-form {
- p {
- max-width: 400px;
- margin-bottom: 15px;
-
- strong {
- font-weight: 500;
- }
- }
-
- .warning {
- max-width: 400px;
- box-sizing: border-box;
- background: rgba($color6, 0.5);
- color: $color5;
- text-shadow: 1px 1px 0 rgba($color8, 0.3);
- box-shadow: 0 2px 6px rgba($color8, 0.4);
- border-radius: 4px;
- padding: 10px;
- margin-bottom: 15px;
-
- a {
- color: $color5;
- text-decoration: underline;
-
- &:hover, &:focus, &:active {
- text-decoration: none;
- }
- }
-
- strong {
- font-weight: 600;
- display: block;
- margin-bottom: 5px;
-
- .fa {
- font-weight: 400;
- }
- }
- }
-}
-
-.action-pagination {
- display: flex;
- align-items: center;
-
- .actions, .pagination {
- flex: 1 1 auto;
- }
-
- .actions {
- padding: 30px 0;
- padding-right: 20px;
- flex: 0 0 auto;
- }
-}
-
-.user_allowed_languages {
- li {
- float: left;
- width: 50%;
- }
-}
diff --git a/app/assets/stylesheets/landing_strip.scss b/app/assets/stylesheets/landing_strip.scss
@@ -1,17 +0,0 @@
-.landing-strip {
- background: rgba(darken($color1, 7%), 0.8);
- color: $color3;
- font-weight: 400;
- padding: 14px;
- border-radius: 4px;
- margin-bottom: 20px;
-
- strong, a {
- font-weight: 500;
- }
-
- a {
- color: inherit;
- text-decoration: underline;
- }
-}
diff --git a/app/assets/stylesheets/lists.scss b/app/assets/stylesheets/lists.scss
@@ -1,20 +0,0 @@
-.no-list {
- list-style: none;
-
- li {
- display: inline-block;
- margin: 0 5px;
- }
-}
-
-.recovery-codes {
- list-style: none;
- margin: 0 auto;
- text-align: center;
-
- li {
- font-size: 125%;
- line-height: 1.5;
- letter-spacing: 1px;
- }
-}
diff --git a/app/assets/stylesheets/reset.scss b/app/assets/stylesheets/reset.scss
@@ -1,91 +0,0 @@
-/* http://meyerweb.com/eric/tools/css/reset/
- v2.0 | 20110126
- License: none (public domain)
-*/
-
-html, body, div, span, applet, object, iframe,
-h1, h2, h3, h4, h5, h6, p, blockquote, pre,
-a, abbr, acronym, address, big, cite, code,
-del, dfn, em, img, ins, kbd, q, s, samp,
-small, strike, strong, sub, sup, tt, var,
-b, u, i, center,
-dl, dt, dd, ol, ul, li,
-fieldset, form, label, legend,
-table, caption, tbody, tfoot, thead, tr, th, td,
-article, aside, canvas, details, embed,
-figure, figcaption, footer, header, hgroup,
-menu, nav, output, ruby, section, summary,
-time, mark, audio, video {
- margin: 0;
- padding: 0;
- border: 0;
- font-size: 100%;
- font: inherit;
- vertical-align: baseline;
-}
-
-/* HTML5 display-role reset for older browsers */
-article, aside, details, figcaption, figure,
-footer, header, hgroup, menu, nav, section {
- display: block;
-}
-
-body {
- line-height: 1;
-}
-
-ol, ul {
- list-style: none;
-}
-
-blockquote, q {
- quotes: none;
-}
-
-blockquote:before, blockquote:after,
-q:before, q:after {
- content: '';
- content: none;
-}
-
-table {
- border-collapse: collapse;
- border-spacing: 0;
-}
-
-::-webkit-scrollbar {
- width: 8px;
- height: 8px;
-}
-
-::-webkit-scrollbar-thumb {
- background: lighten($color1, 4%);
- border: 0px none $color5;
- border-radius: 50px;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background: lighten($color1, 6%);
-}
-
-::-webkit-scrollbar-thumb:active {
- background: lighten($color1, 4%);
-}
-
-::-webkit-scrollbar-track {
- border: 0px none $color5;
- border-radius: 0;
- background: rgba($color8, 0.1);
-}
-
-::-webkit-scrollbar-track:hover {
- background: $color1;
-}
-
-::-webkit-scrollbar-track:active {
- background: $color1;
-}
-
-::-webkit-scrollbar-corner {
- background: transparent;
-}
diff --git a/app/assets/stylesheets/rtl.scss b/app/assets/stylesheets/rtl.scss
@@ -1,136 +0,0 @@
-body.rtl {
- direction: rtl;
-
- .column-link__icon, .column-header__icon {
- margin-right: 0;
- margin-left: 5px;
- }
-
- .character-counter__wrapper {
- margin-right: 0;
- margin-left: 16px;
- }
-
- .navigation-bar__profile {
- margin-left: 0;
- margin-right: 8px;
- }
-
- .search__input {
- padding-right: 10px;
- padding-left: 30px;
- }
-
- .search__icon .fa {
- right: auto;
- left: 10px;
- }
-
- .column-icon-clear {
- right: auto;
- left: 48px;
- }
-
- .column-icon {
- right: auto;
- left: 5px;
- }
-
- .setting-toggle {
- margin-left: 0;
- margin-right: 8px;
- }
-
- .status__avatar {
- left: auto;
- right: 10px;
- }
-
- .status {
- padding-left: 10px;
- padding-right: 68px;
- }
-
- .status__info .status__display-name {
- padding-left: 25px;
- padding-right: 0;
- }
-
- .column-back-button--slim-button {
- right: auto;
- left: 0;
- }
-
- .status__info-time {
- float: left;
- }
-
- .status__action-bar-button-wrapper {
- float: right;
- margin-right: 0;
- margin-left: 18px;
- }
-
- .status__action-bar-dropdown {
- float: right;
- }
-
- .privacy-dropdown__dropdown {
- left: auto;
- right: 0;
- }
-
- .dropdown--active .dropdown__content {
- text-align: right;
- }
-
- .dropdown--active .dropdown__content::before {
- left: auto;
- right: 8px;
- }
-
- .dropdown--active .dropdown__content > ul {
- left: auto;
- right: -10px;
- }
-
- .privacy-dropdown__option__icon {
- margin-left: 10px;
- margin-right: 0;
- }
-
- .detailed-status__display-avatar {
- margin-right: 0;
- margin-left: 10px;
- float: right;
- }
-
- .detailed-status__favorites, .detailed-status__reblogs {
- margin-left: 0;
- margin-right: 6px;
- }
-
- @media screen and (min-width: 1025px) {
- .column, .drawer {
- padding-left: 5px;
- padding-right: 5px;
-
- &:first-child {
- padding-left: 5px;
- padding-right: 10px;
- }
-
- &:last-child {
- padding-right: 0px;
- padding-left: 10px;
- }
- }
-
- .columns-area > div {
- .column, .drawer {
- padding-left: 5px;
- padding-right: 5px;
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/stream_entries.scss b/app/assets/stylesheets/stream_entries.scss
@@ -1,372 +0,0 @@
-.activity-stream {
- clear: both;
- box-shadow: 0 0 15px rgba($color8, 0.2);
-
- .entry {
- background: $color5;
-
- .detailed-status.light, .status.light {
- border-bottom: 1px solid $color2;
- }
-
- &:last-child {
- &, .detailed-status.light, .status.light {
- border-bottom: 0;
- border-radius: 0 0 4px 4px;
- }
- }
-
- &:first-child {
- &, .detailed-status.light, .status.light {
- border-radius: 4px 4px 0 0;
- }
-
- &:last-child {
- &, .detailed-status.light, .status.light {
- border-radius: 4px;
- }
- }
- }
- }
-
- .status.light {
- padding: 14px 14px 14px (48px + 14px*2);
- position: relative;
- min-height: 48px;
- cursor: default;
-
- .status__header {
- font-size: 15px;
-
- .status__meta {
- float: right;
- font-size: 14px;
-
- .status__relative-time {
- color: $color4;
- }
- }
- }
-
- .status__display-name {
- display: block;
- max-width: 100%;
- padding-right: 25px;
- color: $color1;
- }
-
- .status__avatar {
- position: absolute;
- left: 14px;
- top: 14px;
- width: 48px;
- height: 48px;
-
- & > div {
- width: 48px;
- height: 48px;
- }
-
- img {
- display: block;
- border-radius: 4px;
- }
- }
-
- .display-name {
- display: block;
- max-width: 100%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
-
- strong {
- font-weight: 500;
- color: $color1;
- }
-
- span {
- font-size: 14px;
- color: $color4;
- }
- }
-
- .status__content {
- color: $color1;
-
- a {
- color: $color4;
- }
-
- a.status__content__spoiler-link {
- color: $color5;
- background: $color3;
-
- &:hover {
- background: lighten($color3, 8%);
- }
- }
- }
-
- .status__attachments {
- margin-top: 8px;
- overflow: hidden;
- width: 100%;
- box-sizing: border-box;
- position: relative;
-
- .status__attachments__inner {
- display: flex;
- height: 214px;
- }
- }
- }
-
- .detailed-status.light {
- padding: 14px;
- background: $color5;
- cursor: default;
-
- .detailed-status__display-name {
- display: block;
- overflow: hidden;
- margin-bottom: 15px;
-
- & > div {
- float: left;
- margin-right: 10px;
- }
-
- .display-name {
- display: block;
- max-width: 100%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
-
- strong {
- font-weight: 500;
- color: $color1;
- }
-
- span {
- font-size: 14px;
- color: $color3;
- }
- }
- }
-
- .avatar {
- width: 48px;
- height: 48px;
-
- img {
- display: block;
- border-radius: 4px;
- }
- }
-
- .status__content {
- color: $color1;
-
- a {
- color: $color4;
- }
-
- a.status__content__spoiler-link {
- color: $color5;
- background: $color3;
-
- &:hover {
- background: lighten($color3, 8%);
- }
- }
- }
-
- .detailed-status__meta {
- margin-top: 15px;
- color: $color3;
- font-size: 14px;
- line-height: 18px;
-
- a {
- color: inherit;
- }
-
- span > span {
- font-weight: 500;
- font-size: 12px;
- margin-left: 6px;
- display: inline-block;
- }
- }
-
- .detailed-status__attachments {
- margin-top: 8px;
- overflow: hidden;
- width: 100%;
- box-sizing: border-box;
- position: relative;
-
- .status__attachments__inner {
- display: flex;
- height: 360px;
- }
- }
-
- .video-player {
- margin-top: 8px;
- height: 300px;
- overflow: hidden;
- position: relative;
-
- video {
- position: relative;
- z-index: 1;
- width: 100%;
- height: 100%;
- object-fit: cover;
- top: 50%;
- transform: translateY(-50%);
- }
- }
- }
-
- .media-item, .video-item {
- box-sizing: border-box;
- position: relative;
- left: auto;
- top: auto;
- right: auto;
- bottom: auto;
- float: left;
- border: medium none;
- display: block;
- flex: 1 1 auto;
- height: 100%;
- margin-right: 2px;
-
- &:last-child {
- margin-right: 0;
- }
-
- a {
- display: block;
- width: 100%;
- height: 100%;
- background: no-repeat scroll center center / cover;
- text-decoration: none;
- cursor: zoom-in;
- }
-
- video {
- position: relative;
- z-index: 1;
- width: 100%;
- height: 100%;
- object-fit: cover;
- top: 50%;
- transform: translateY(-50%);
- }
- }
-
- .video-item {
- a {
- cursor: pointer;
- }
-
- .video-item__play {
- position: absolute;
- top: 50%;
- left: 50%;
- font-size: 36px;
- transform: translate(-50%, -50%);
- padding: 5px;
- border-radius: 100px;
- color: rgba($color5, 0.8);
- z-index: 1;
- }
- }
-
- .media-spoiler {
- background: $color3;
- width: 100%;
- height: 100%;
- cursor: pointer;
- position: absolute;
- top: 0;
- left: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- text-align: center;
- transition: all 100ms linear;
- z-index: 2;
-
- &:hover {
- background: darken($color3, 5%);
- }
-
- span {
- display: block;
-
- &:first-child {
- font-size: 14px;
- }
-
- &:last-child {
- font-size: 11px;
- font-weight: 500;
- }
- }
- }
-
- .pre-header {
- padding: 14px 0px;
- padding-left: (48px + 14px*2);
- padding-bottom: 0;
- margin-bottom: -4px;
- color: $color3;
- font-size: 14px;
- position: relative;
-
- .pre-header__icon {
- position: absolute;
- left: (48px + 14px*2 - 30px);
- }
-
- .status__display-name.muted strong {
- color: $color3;
- }
- }
-
- .open-in-web-link {
- text-decoration: none;
-
- &:hover {
- text-decoration: underline;
- }
- }
-}
-
-.embed {
- .activity-stream {
- border-radius: 4px;
- box-shadow: none;
-
- .entry {
- &:last-child {
- border-radius: 0 0 4px 4px;
- }
-
- &:first-child {
- border-radius: 4px 4px 0 0;
-
- &:last-child {
- border-radius: 4px;
- }
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/tables.scss b/app/assets/stylesheets/tables.scss
@@ -1,65 +0,0 @@
-.table {
- width: 100%;
- max-width: 100%;
- border-spacing: 0;
- border-collapse: collapse;
- margin-bottom: 20px;
-
- th, td {
- padding: 8px;
- line-height: 18px;
- vertical-align: top;
- border-top: 1px solid $color1;
- text-align: left;
- }
-
- & > thead > tr > th {
- vertical-align: bottom;
- border-bottom: 2px solid $color1;
- border-top: 0;
- font-weight: 500;
- }
-
- & > tbody > tr > th {
- font-weight: 500;
- }
-
- & > tbody > tr:nth-child(odd) > td, & > tbody > tr:nth-child(odd) > th {
- background: $color1;
- }
-
- a {
- color: $color4;
- text-decoration: underline;
-
- &:hover {
- text-decoration: none;
- }
- }
-
- strong {
- font-weight: 500;
- }
-}
-
-samp {
- font-family: 'Roboto Mono', monospace;
-}
-
-a.table-action-link {
- text-decoration: none;
- display: inline-block;
- margin-right: 5px;
- padding: 0 10px;
- color: rgba($color5, 0.7);
- font-weight: 500;
-
- &:hover {
- color: $color5;
- }
-
- i.fa {
- font-weight: 400;
- margin-right: 5px;
- }
-}
diff --git a/app/assets/stylesheets/variables.scss b/app/assets/stylesheets/variables.scss
@@ -1,8 +0,0 @@
-$color1: #282c37 !default; // darkest
-$color2: #d9e1e8 !default; // lightest
-$color3: #9baec8 !default; // lighter
-$color4: #2b90d9 !default; // vibrant
-$color5: #ffffff !default; // white
-$color6: #df405a !default; // error red
-$color7: #79bd9a !default; // succ green
-$color8: #000000 !default; // black
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
@@ -10,7 +10,7 @@ module ApplicationHelper
end
def add_rtl_body_class(other_classes)
- other_classes = "#{other_classes} rtl" if [:ar, :fa].include?(I18n.locale)
+ other_classes = "#{other_classes} rtl" if [:ar, :fa, :he].include?(I18n.locale)
other_classes
end
@@ -22,4 +22,8 @@ module ApplicationHelper
def title
Rails.env.production? ? site_title : "#{site_title} (Dev)"
end
+
+ def fa_icon(icon)
+ content_tag(:i, nil, class: 'fa ' + icon.split(' ').map { |cl| "fa-#{cl}" }.join(' '))
+ end
end
diff --git a/app/assets/fonts/montserrat/Montserrat-Regular.eot b/app/javascript/fonts/montserrat/Montserrat-Regular.eot
Binary files differ.
diff --git a/app/assets/fonts/montserrat/Montserrat-Regular.ttf b/app/javascript/fonts/montserrat/Montserrat-Regular.ttf
Binary files differ.
diff --git a/app/assets/fonts/montserrat/Montserrat-Regular.woff b/app/javascript/fonts/montserrat/Montserrat-Regular.woff
Binary files differ.
diff --git a/app/assets/fonts/montserrat/Montserrat-Regular.woff2 b/app/javascript/fonts/montserrat/Montserrat-Regular.woff2
Binary files differ.
diff --git a/app/assets/fonts/roboto-mono/robotomono-regular-webfont.eot b/app/javascript/fonts/roboto-mono/robotomono-regular-webfont.eot
Binary files differ.
diff --git a/app/assets/fonts/roboto-mono/robotomono-regular-webfont.svg b/app/javascript/fonts/roboto-mono/robotomono-regular-webfont.svg
diff --git a/app/assets/fonts/roboto-mono/robotomono-regular-webfont.ttf b/app/javascript/fonts/roboto-mono/robotomono-regular-webfont.ttf
Binary files differ.
diff --git a/app/assets/fonts/roboto-mono/robotomono-regular-webfont.woff b/app/javascript/fonts/roboto-mono/robotomono-regular-webfont.woff
Binary files differ.
diff --git a/app/assets/fonts/roboto-mono/robotomono-regular-webfont.woff2 b/app/javascript/fonts/roboto-mono/robotomono-regular-webfont.woff2
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-bold-webfont.eot b/app/javascript/fonts/roboto/roboto-bold-webfont.eot
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-bold-webfont.svg b/app/javascript/fonts/roboto/roboto-bold-webfont.svg
diff --git a/app/assets/fonts/roboto/roboto-bold-webfont.ttf b/app/javascript/fonts/roboto/roboto-bold-webfont.ttf
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-bold-webfont.woff b/app/javascript/fonts/roboto/roboto-bold-webfont.woff
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-bold-webfont.woff2 b/app/javascript/fonts/roboto/roboto-bold-webfont.woff2
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-italic-webfont.eot b/app/javascript/fonts/roboto/roboto-italic-webfont.eot
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-italic-webfont.svg b/app/javascript/fonts/roboto/roboto-italic-webfont.svg
diff --git a/app/assets/fonts/roboto/roboto-italic-webfont.ttf b/app/javascript/fonts/roboto/roboto-italic-webfont.ttf
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-italic-webfont.woff b/app/javascript/fonts/roboto/roboto-italic-webfont.woff
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-italic-webfont.woff2 b/app/javascript/fonts/roboto/roboto-italic-webfont.woff2
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-medium-webfont.eot b/app/javascript/fonts/roboto/roboto-medium-webfont.eot
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-medium-webfont.svg b/app/javascript/fonts/roboto/roboto-medium-webfont.svg
diff --git a/app/assets/fonts/roboto/roboto-medium-webfont.ttf b/app/javascript/fonts/roboto/roboto-medium-webfont.ttf
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-medium-webfont.woff b/app/javascript/fonts/roboto/roboto-medium-webfont.woff
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-medium-webfont.woff2 b/app/javascript/fonts/roboto/roboto-medium-webfont.woff2
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-regular-webfont.eot b/app/javascript/fonts/roboto/roboto-regular-webfont.eot
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-regular-webfont.svg b/app/javascript/fonts/roboto/roboto-regular-webfont.svg
diff --git a/app/assets/fonts/roboto/roboto-regular-webfont.ttf b/app/javascript/fonts/roboto/roboto-regular-webfont.ttf
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-regular-webfont.woff b/app/javascript/fonts/roboto/roboto-regular-webfont.woff
Binary files differ.
diff --git a/app/assets/fonts/roboto/roboto-regular-webfont.woff2 b/app/javascript/fonts/roboto/roboto-regular-webfont.woff2
Binary files differ.
diff --git a/app/assets/javascripts/components/.gitkeep b/app/javascript/images/.keep
diff --git a/app/assets/images/background-photo.jpg b/app/javascript/images/background-photo.jpg
Binary files differ.
diff --git a/app/assets/images/boost_sprite.png b/app/javascript/images/boost_sprite.png
Binary files differ.
diff --git a/app/assets/images/elephant-friend.png b/app/javascript/images/elephant-friend.png
Binary files differ.
diff --git a/app/assets/images/fluffy-elephant-friend.png b/app/javascript/images/fluffy-elephant-friend.png
Binary files differ.
diff --git a/app/assets/images/logo.png b/app/javascript/images/logo.png
Binary files differ.
diff --git a/app/assets/images/logo.svg b/app/javascript/images/logo.svg
diff --git a/app/assets/images/mastodon-getting-started.png b/app/javascript/images/mastodon-getting-started.png
Binary files differ.
diff --git a/app/assets/images/mastodon-not-found.png b/app/javascript/images/mastodon-not-found.png
Binary files differ.
diff --git a/app/assets/images/mastodon.jpg b/app/javascript/images/mastodon.jpg
Binary files differ.
diff --git a/app/assets/images/mastodon_small.jpg b/app/javascript/images/mastodon_small.jpg
Binary files differ.
diff --git a/app/assets/images/screenshot.png b/app/javascript/images/screenshot.png
Binary files differ.
diff --git a/app/assets/images/void.png b/app/javascript/images/void.png
Binary files differ.
diff --git a/app/assets/images/.keep b/app/javascript/mastodon/.gitkeep
diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/javascript/mastodon/actions/accounts.js
diff --git a/app/assets/javascripts/components/actions/alerts.jsx b/app/javascript/mastodon/actions/alerts.js
diff --git a/app/assets/javascripts/components/actions/blocks.jsx b/app/javascript/mastodon/actions/blocks.js
diff --git a/app/assets/javascripts/components/actions/cards.jsx b/app/javascript/mastodon/actions/cards.js
diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/javascript/mastodon/actions/compose.js
diff --git a/app/assets/javascripts/components/actions/favourites.jsx b/app/javascript/mastodon/actions/favourites.js
diff --git a/app/assets/javascripts/components/actions/interactions.jsx b/app/javascript/mastodon/actions/interactions.js
diff --git a/app/assets/javascripts/components/actions/modal.jsx b/app/javascript/mastodon/actions/modal.js
diff --git a/app/assets/javascripts/components/actions/mutes.jsx b/app/javascript/mastodon/actions/mutes.js
diff --git a/app/assets/javascripts/components/actions/notifications.jsx b/app/javascript/mastodon/actions/notifications.js
diff --git a/app/assets/javascripts/components/actions/onboarding.jsx b/app/javascript/mastodon/actions/onboarding.js
diff --git a/app/assets/javascripts/components/actions/reports.jsx b/app/javascript/mastodon/actions/reports.js
diff --git a/app/assets/javascripts/components/actions/search.jsx b/app/javascript/mastodon/actions/search.js
diff --git a/app/assets/javascripts/components/actions/settings.jsx b/app/javascript/mastodon/actions/settings.js
diff --git a/app/assets/javascripts/components/actions/statuses.jsx b/app/javascript/mastodon/actions/statuses.js
diff --git a/app/assets/javascripts/components/actions/store.jsx b/app/javascript/mastodon/actions/store.js
diff --git a/app/assets/javascripts/components/actions/timelines.jsx b/app/javascript/mastodon/actions/timelines.js
diff --git a/app/assets/javascripts/components/api.jsx b/app/javascript/mastodon/api.js
diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js
@@ -0,0 +1,93 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import Avatar from './avatar';
+import DisplayName from './display_name';
+import Permalink from './permalink';
+import IconButton from './icon_button';
+import { defineMessages, injectIntl } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ follow: { id: 'account.follow', defaultMessage: 'Follow' },
+ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
+ requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
+ unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
+ unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }
+});
+
+class Account extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleFollow = this.handleFollow.bind(this);
+ this.handleBlock = this.handleBlock.bind(this);
+ this.handleMute = this.handleMute.bind(this);
+ }
+
+ handleFollow () {
+ this.props.onFollow(this.props.account);
+ }
+
+ handleBlock () {
+ this.props.onBlock(this.props.account);
+ }
+
+ handleMute () {
+ this.props.onMute(this.props.account);
+ }
+
+ render () {
+ const { account, me, intl } = this.props;
+
+ if (!account) {
+ return <div />;
+ }
+
+ let buttons;
+
+ if (account.get('id') !== me && account.get('relationship', null) !== null) {
+ const following = account.getIn(['relationship', 'following']);
+ const requested = account.getIn(['relationship', 'requested']);
+ const blocking = account.getIn(['relationship', 'blocking']);
+ const muting = account.getIn(['relationship', 'muting']);
+
+ if (requested) {
+ buttons = <IconButton disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} />
+ } else if (blocking) {
+ buttons = <IconButton active={true} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
+ } else if (muting) {
+ buttons = <IconButton active={true} icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
+ } else {
+ buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
+ }
+ }
+
+ return (
+ <div className='account'>
+ <div className='account__wrapper'>
+ <Permalink key={account.get('id')} className='account__display-name' href={account.get('url')} to={`/accounts/${account.get('id')}`}>
+ <div className='account__avatar-wrapper'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={36} /></div>
+ <DisplayName account={account} />
+ </Permalink>
+
+ <div className='account__relationship'>
+ {buttons}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+Account.propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ me: PropTypes.number.isRequired,
+ onFollow: PropTypes.func.isRequired,
+ onBlock: PropTypes.func.isRequired,
+ onMute: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+}
+
+export default injectIntl(Account);
diff --git a/app/javascript/mastodon/components/attachment_list.js b/app/javascript/mastodon/components/attachment_list.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+
+const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
+
+class AttachmentList extends React.PureComponent {
+
+ render () {
+ const { media } = this.props;
+
+ return (
+ <div className='attachment-list'>
+ <div className='attachment-list__icon'>
+ <i className='fa fa-link' />
+ </div>
+
+ <ul className='attachment-list__list'>
+ {media.map(attachment =>
+ <li key={attachment.get('id')}>
+ <a href={attachment.get('remote_url')} target='_blank' rel='noopener'>{filename(attachment.get('remote_url'))}</a>
+ </li>
+ )}
+ </ul>
+ </div>
+ );
+ }
+}
+
+AttachmentList.propTypes = {
+ media: ImmutablePropTypes.list.isRequired
+};
+
+export default AttachmentList;
diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js
@@ -0,0 +1,213 @@
+import React from 'react';
+import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { isRtl } from '../rtl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const textAtCursorMatchesToken = (str, caretPosition) => {
+ let word;
+
+ let left = str.slice(0, caretPosition).search(/\S+$/);
+ let right = str.slice(caretPosition).search(/\s/);
+
+ if (right < 0) {
+ word = str.slice(left);
+ } else {
+ word = str.slice(left, right + caretPosition);
+ }
+
+ if (!word || word.trim().length < 2 || word[0] !== '@') {
+ return [null, null];
+ }
+
+ word = word.trim().toLowerCase().slice(1);
+
+ if (word.length > 0) {
+ return [left + 1, word];
+ } else {
+ return [null, null];
+ }
+};
+
+class AutosuggestTextarea extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.state = {
+ suggestionsHidden: false,
+ selectedSuggestion: 0,
+ lastToken: null,
+ tokenStart: 0
+ };
+ this.onChange = this.onChange.bind(this);
+ this.onKeyDown = this.onKeyDown.bind(this);
+ this.onBlur = this.onBlur.bind(this);
+ this.onSuggestionClick = this.onSuggestionClick.bind(this);
+ this.setTextarea = this.setTextarea.bind(this);
+ this.onPaste = this.onPaste.bind(this);
+ }
+
+ onChange (e) {
+ const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
+
+ if (token !== null && this.state.lastToken !== token) {
+ this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
+ this.props.onSuggestionsFetchRequested(token);
+ } else if (token === null) {
+ this.setState({ lastToken: null });
+ this.props.onSuggestionsClearRequested();
+ }
+
+ // auto-resize textarea
+ e.target.style.height = `${e.target.scrollHeight}px`;
+
+ this.props.onChange(e);
+ }
+
+ onKeyDown (e) {
+ const { suggestions, disabled } = this.props;
+ const { selectedSuggestion, suggestionsHidden } = this.state;
+
+ if (disabled) {
+ e.preventDefault();
+ return;
+ }
+
+ switch(e.key) {
+ case 'Escape':
+ if (!suggestionsHidden) {
+ e.preventDefault();
+ this.setState({ suggestionsHidden: true });
+ }
+
+ break;
+ case 'ArrowDown':
+ if (suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
+ }
+
+ break;
+ case 'ArrowUp':
+ if (suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
+ }
+
+ break;
+ case 'Enter':
+ case 'Tab':
+ // Select suggestion
+ if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ e.stopPropagation();
+ this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
+ }
+
+ break;
+ }
+
+ if (e.defaultPrevented || !this.props.onKeyDown) {
+ return;
+ }
+
+ this.props.onKeyDown(e);
+ }
+
+ onBlur () {
+ // If we hide the suggestions immediately, then this will prevent the
+ // onClick for the suggestions themselves from firing.
+ // Setting a short window for that to take place before hiding the
+ // suggestions ensures that can't happen.
+ setTimeout(() => {
+ this.setState({ suggestionsHidden: true });
+ }, 100);
+ }
+
+ onSuggestionClick (suggestion, e) {
+ e.preventDefault();
+ this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
+ this.textarea.focus();
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
+ this.setState({ suggestionsHidden: false });
+ }
+ }
+
+ setTextarea (c) {
+ this.textarea = c;
+ }
+
+ onPaste (e) {
+ if (e.clipboardData && e.clipboardData.files.length === 1) {
+ this.props.onPaste(e.clipboardData.files)
+ e.preventDefault();
+ }
+ }
+
+ reset () {
+ this.textarea.style.height = 'auto';
+ }
+
+ render () {
+ const { value, suggestions, disabled, placeholder, onKeyUp } = this.props;
+ const { suggestionsHidden, selectedSuggestion } = this.state;
+ const style = { direction: 'ltr' };
+
+ if (isRtl(value)) {
+ style.direction = 'rtl';
+ }
+
+ return (
+ <div className='autosuggest-textarea'>
+ <textarea
+ ref={this.setTextarea}
+ className='autosuggest-textarea__textarea'
+ disabled={disabled}
+ placeholder={placeholder}
+ autoFocus={true}
+ value={value}
+ onChange={this.onChange}
+ onKeyDown={this.onKeyDown}
+ onKeyUp={onKeyUp}
+ onBlur={this.onBlur}
+ onPaste={this.onPaste}
+ style={style}
+ />
+
+ <div style={{ display: (suggestions.size > 0 && !suggestionsHidden) ? 'block' : 'none' }} className='autosuggest-textarea__suggestions'>
+ {suggestions.map((suggestion, i) => (
+ <div
+ role='button'
+ tabIndex='0'
+ key={suggestion}
+ className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`}
+ onClick={this.onSuggestionClick.bind(this, suggestion)}>
+ <AutosuggestAccountContainer id={suggestion} />
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+ }
+
+};
+
+AutosuggestTextarea.propTypes = {
+ value: PropTypes.string,
+ suggestions: ImmutablePropTypes.list,
+ disabled: PropTypes.bool,
+ placeholder: PropTypes.string,
+ onSuggestionSelected: PropTypes.func.isRequired,
+ onSuggestionsClearRequested: PropTypes.func.isRequired,
+ onSuggestionsFetchRequested: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onKeyUp: PropTypes.func,
+ onKeyDown: PropTypes.func,
+ onPaste: PropTypes.func.isRequired,
+};
+
+export default AutosuggestTextarea;
diff --git a/app/javascript/mastodon/components/avatar.js b/app/javascript/mastodon/components/avatar.js
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+class Avatar extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+
+ this.state = {
+ hovering: false
+ };
+
+ this.handleMouseEnter = this.handleMouseEnter.bind(this);
+ this.handleMouseLeave = this.handleMouseLeave.bind(this);
+ }
+
+ handleMouseEnter () {
+ if (this.props.animate) return;
+ this.setState({ hovering: true });
+ }
+
+ handleMouseLeave () {
+ if (this.props.animate) return;
+ this.setState({ hovering: false });
+ }
+
+ render () {
+ const { src, size, staticSrc, animate } = this.props;
+ const { hovering } = this.state;
+
+ 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 (
+ <div
+ className='account__avatar'
+ onMouseEnter={this.handleMouseEnter}
+ onMouseLeave={this.handleMouseLeave}
+ style={style}
+ />
+ );
+ }
+
+}
+
+Avatar.propTypes = {
+ src: PropTypes.string.isRequired,
+ staticSrc: PropTypes.string,
+ size: PropTypes.number.isRequired,
+ style: PropTypes.object,
+ animate: PropTypes.bool
+};
+
+Avatar.defaultProps = {
+ animate: false
+};
+
+export default Avatar;
diff --git a/app/javascript/mastodon/components/button.js b/app/javascript/mastodon/components/button.js
@@ -0,0 +1,50 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+class Button extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick (e) {
+ if (!this.props.disabled) {
+ this.props.onClick();
+ }
+ }
+
+ render () {
+ const style = {
+ display: this.props.block ? 'block' : 'inline-block',
+ width: this.props.block ? '100%' : 'auto',
+ padding: `0 ${this.props.size / 2.25}px`,
+ height: `${this.props.size}px`,
+ lineHeight: `${this.props.size}px`
+ };
+
+ return (
+ <button className={`button ${this.props.secondary ? 'button-secondary' : ''}`} disabled={this.props.disabled} onClick={this.handleClick} style={{ ...style, ...this.props.style }}>
+ {this.props.text || this.props.children}
+ </button>
+ );
+ }
+
+}
+
+Button.propTypes = {
+ text: PropTypes.node,
+ onClick: PropTypes.func,
+ disabled: PropTypes.bool,
+ block: PropTypes.bool,
+ secondary: PropTypes.bool,
+ size: PropTypes.number,
+ style: PropTypes.object,
+ children: PropTypes.node
+};
+
+Button.defaultProps = {
+ size: 36
+};
+
+export default Button;
diff --git a/app/javascript/mastodon/components/collapsable.js b/app/javascript/mastodon/components/collapsable.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import { Motion, spring } from 'react-motion';
+import PropTypes from 'prop-types';
+
+const Collapsable = ({ fullHeight, isVisible, children }) => (
+ <Motion defaultStyle={{ opacity: !isVisible ? 0 : 100, height: isVisible ? fullHeight : 0 }} style={{ opacity: spring(!isVisible ? 0 : 100), height: spring(!isVisible ? 0 : fullHeight) }}>
+ {({ opacity, height }) =>
+ <div style={{ height: `${height}px`, overflow: 'hidden', opacity: opacity / 100, display: Math.floor(opacity) === 0 ? 'none' : 'block' }}>
+ {children}
+ </div>
+ }
+ </Motion>
+);
+
+Collapsable.propTypes = {
+ fullHeight: PropTypes.number.isRequired,
+ isVisible: PropTypes.bool.isRequired,
+ children: PropTypes.node.isRequired
+};
+
+export default Collapsable;
diff --git a/app/javascript/mastodon/components/column_back_button.js b/app/javascript/mastodon/components/column_back_button.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import PropTypes from 'prop-types';
+
+class ColumnBackButton extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick () {
+ if (window.history && window.history.length === 1) this.context.router.push("/");
+ else this.context.router.goBack();
+ }
+
+ render () {
+ return (
+ <div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button'>
+ <i className='fa fa-fw fa-chevron-left column-back-button__icon'/>
+ <FormattedMessage id='column_back_button.label' defaultMessage='Back' />
+ </div>
+ );
+ }
+
+};
+
+ColumnBackButton.contextTypes = {
+ router: PropTypes.object
+};
+
+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
@@ -0,0 +1,32 @@
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import PropTypes from 'prop-types';
+
+class ColumnBackButtonSlim extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick () {
+ this.context.router.push('/');
+ }
+
+ render () {
+ return (
+ <div className='column-back-button--slim'>
+ <div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
+ <i className='fa fa-fw fa-chevron-left column-back-button__icon' />
+ <FormattedMessage id='column_back_button.label' defaultMessage='Back' />
+ </div>
+ </div>
+ );
+ }
+}
+
+ColumnBackButtonSlim.contextTypes = {
+ router: PropTypes.object
+};
+
+export default ColumnBackButtonSlim;
diff --git a/app/javascript/mastodon/components/column_collapsable.js b/app/javascript/mastodon/components/column_collapsable.js
@@ -0,0 +1,57 @@
+import React from 'react';
+import { Motion, spring } from 'react-motion';
+import PropTypes from 'prop-types';
+
+class ColumnCollapsable extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.state = {
+ collapsed: true
+ };
+
+ this.handleToggleCollapsed = this.handleToggleCollapsed.bind(this);
+ }
+
+ handleToggleCollapsed () {
+ const currentState = this.state.collapsed;
+
+ this.setState({ collapsed: !currentState });
+
+ if (!currentState && this.props.onCollapse) {
+ this.props.onCollapse();
+ }
+ }
+
+ render () {
+ const { icon, title, fullHeight, children } = this.props;
+ const { collapsed } = this.state;
+ const collapsedClassName = collapsed ? 'collapsable-collapsed' : 'collapsable';
+
+ return (
+ <div className='column-collapsable'>
+ <div role='button' tabIndex='0' title={`${title}`} className={`column-icon ${collapsedClassName}`} onClick={this.handleToggleCollapsed}>
+ <i className={`fa fa-${icon}`} />
+ </div>
+
+ <Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : fullHeight, collapsed ? undefined : { stiffness: 150, damping: 9 }) }}>
+ {({ opacity, height }) =>
+ <div style={{ overflow: height === fullHeight ? 'auto' : 'hidden', height: `${height}px`, opacity: opacity / 100, maxHeight: '70vh' }}>
+ {children}
+ </div>
+ }
+ </Motion>
+ </div>
+ );
+ }
+}
+
+ColumnCollapsable.propTypes = {
+ icon: PropTypes.string.isRequired,
+ title: PropTypes.string,
+ fullHeight: PropTypes.number.isRequired,
+ children: PropTypes.node,
+ onCollapse: PropTypes.func
+};
+
+export default ColumnCollapsable;
diff --git a/app/javascript/mastodon/components/display_name.js b/app/javascript/mastodon/components/display_name.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import escapeTextContentForBrowser from 'escape-html';
+import emojify from '../emoji';
+
+class DisplayName extends React.PureComponent {
+
+ render () {
+ const displayName = this.props.account.get('display_name').length === 0 ? this.props.account.get('username') : this.props.account.get('display_name');
+ const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
+
+ return (
+ <span className='display-name'>
+ <strong className='display-name__html' dangerouslySetInnerHTML={displayNameHTML} /> <span className='display-name__account'>@{this.props.account.get('acct')}</span>
+ </span>
+ );
+ }
+
+};
+
+DisplayName.propTypes = {
+ account: ImmutablePropTypes.map.isRequired
+}
+
+export default DisplayName;
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
@@ -0,0 +1,79 @@
+import React from 'react';
+import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
+import PropTypes from 'prop-types';
+
+class DropdownMenu extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.state = {
+ direction: 'left'
+ };
+ this.setRef = this.setRef.bind(this);
+ this.renderItem = this.renderItem.bind(this);
+ }
+
+ setRef (c) {
+ this.dropdown = c;
+ }
+
+ handleClick (i, e) {
+ const { action } = this.props.items[i];
+
+ if (typeof action === 'function') {
+ e.preventDefault();
+ action();
+ this.dropdown.hide();
+ }
+ }
+
+ renderItem (item, i) {
+ if (item === null) {
+ return <li key={ 'sep' + i } className='dropdown__sep' />;
+ }
+
+ const { text, action, href = '#' } = item;
+
+ return (
+ <li className='dropdown__content-list-item' key={ text + i }>
+ <a href={href} target='_blank' rel='noopener' onClick={this.handleClick.bind(this, i)} className='dropdown__content-list-link'>
+ {text}
+ </a>
+ </li>
+ );
+ }
+
+ render () {
+ const { icon, items, size, direction, ariaLabel } = this.props;
+ const directionClass = (direction === "left") ? "dropdown__left" : "dropdown__right";
+
+ return (
+ <Dropdown ref={this.setRef}>
+ <DropdownTrigger className='icon-button' style={{ fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }} aria-label={ariaLabel}>
+ <i className={ `fa fa-fw fa-${icon} dropdown__icon` } aria-hidden={true} />
+ </DropdownTrigger>
+
+ <DropdownContent className={directionClass}>
+ <ul className='dropdown__content-list'>
+ {items.map(this.renderItem)}
+ </ul>
+ </DropdownContent>
+ </Dropdown>
+ );
+ }
+
+}
+
+DropdownMenu.propTypes = {
+ icon: PropTypes.string.isRequired,
+ items: PropTypes.array.isRequired,
+ size: PropTypes.number.isRequired,
+ direction: PropTypes.string,
+ ariaLabel: PropTypes.string
+};
+
+DropdownMenu.defaultProps = {
+ ariaLabel: "Menu"
+};
+
+export default DropdownMenu;
diff --git a/app/javascript/mastodon/components/extended_video_player.js b/app/javascript/mastodon/components/extended_video_player.js
@@ -0,0 +1,54 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+class ExtendedVideoPlayer extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleLoadedData = this.handleLoadedData.bind(this);
+ this.setRef = this.setRef.bind(this);
+ }
+
+ 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 (
+ <div className='extended-video-player'>
+ <video
+ ref={this.setRef}
+ src={this.props.src}
+ autoPlay
+ muted={this.props.muted}
+ controls={this.props.controls}
+ loop={!this.props.controls}
+ />
+ </div>
+ );
+ }
+
+}
+
+ExtendedVideoPlayer.propTypes = {
+ src: PropTypes.string.isRequired,
+ time: PropTypes.number,
+ controls: PropTypes.bool.isRequired,
+ muted: PropTypes.bool.isRequired
+};
+
+export default ExtendedVideoPlayer;
diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js
@@ -0,0 +1,96 @@
+import React from 'react';
+import { Motion, spring } from 'react-motion';
+import PropTypes from 'prop-types';
+
+class IconButton extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick (e) {
+ e.preventDefault();
+
+ if (!this.props.disabled) {
+ this.props.onClick(e);
+ }
+ }
+
+ render () {
+ let style = {
+ fontSize: `${this.props.size}px`,
+ width: `${this.props.size * 1.28571429}px`,
+ height: `${this.props.size * 1.28571429}px`,
+ lineHeight: `${this.props.size}px`,
+ ...this.props.style
+ };
+
+ if (this.props.active) {
+ style = { ...style, ...this.props.activeStyle };
+ }
+
+ const classes = ['icon-button'];
+
+ if (this.props.active) {
+ classes.push('active');
+ }
+
+ if (this.props.disabled) {
+ classes.push('disabled');
+ }
+
+ if (this.props.inverted) {
+ classes.push('inverted');
+ }
+
+ if (this.props.overlay) {
+ classes.push('overlayed');
+ }
+
+ if (this.props.className) {
+ classes.push(this.props.className)
+ }
+
+ return (
+ <Motion defaultStyle={{ rotate: this.props.active ? -360 : 0 }} style={{ rotate: this.props.animate ? spring(this.props.active ? -360 : 0, { stiffness: 120, damping: 7 }) : 0 }}>
+ {({ rotate }) =>
+ <button
+ aria-label={this.props.title}
+ title={this.props.title}
+ className={classes.join(' ')}
+ onClick={this.handleClick}
+ style={style}>
+ <i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
+ </button>
+ }
+ </Motion>
+ );
+ }
+
+}
+
+IconButton.propTypes = {
+ className: PropTypes.string,
+ title: PropTypes.string.isRequired,
+ icon: PropTypes.string.isRequired,
+ onClick: PropTypes.func,
+ size: PropTypes.number,
+ active: PropTypes.bool,
+ style: PropTypes.object,
+ activeStyle: PropTypes.object,
+ disabled: PropTypes.bool,
+ inverted: PropTypes.bool,
+ animate: PropTypes.bool,
+ overlay: PropTypes.bool
+};
+
+IconButton.defaultProps = {
+ size: 18,
+ active: false,
+ disabled: false,
+ animate: false,
+ overlay: false
+};
+
+export default IconButton;
diff --git a/app/javascript/mastodon/components/load_more.js b/app/javascript/mastodon/components/load_more.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import PropTypes from 'prop-types';
+
+const LoadMore = ({ onClick }) => (
+ <a href="#" className='load-more' role='button' onClick={onClick}>
+ <FormattedMessage id='status.load_more' defaultMessage='Load more' />
+ </a>
+);
+
+LoadMore.propTypes = {
+ onClick: PropTypes.func
+};
+
+export default LoadMore;
diff --git a/app/javascript/mastodon/components/loading_indicator.js b/app/javascript/mastodon/components/loading_indicator.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+
+const LoadingIndicator = () => (
+ <div className='loading-indicator'>
+ <FormattedMessage id='loading_indicator.label' defaultMessage='Loading...' />
+ </div>
+);
+
+export default LoadingIndicator;
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
@@ -0,0 +1,196 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import IconButton from './icon_button';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { isIOS } from '../is_mobile';
+
+const messages = defineMessages({
+ toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }
+});
+
+class Item extends React.PureComponent {
+ constructor (props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick (e) {
+ const { index, onClick } = this.props;
+
+ if (e.button === 0) {
+ e.preventDefault();
+ onClick(index);
+ }
+
+ e.stopPropagation();
+ }
+
+ render () {
+ const { attachment, index, size } = this.props;
+
+ let width = 50;
+ let height = 100;
+ let top = 'auto';
+ let left = 'auto';
+ let bottom = 'auto';
+ let right = 'auto';
+
+ if (size === 1) {
+ width = 100;
+ }
+
+ if (size === 4 || (size === 3 && index > 0)) {
+ height = 50;
+ }
+
+ if (size === 2) {
+ if (index === 0) {
+ right = '2px';
+ } else {
+ left = '2px';
+ }
+ } else if (size === 3) {
+ if (index === 0) {
+ right = '2px';
+ } else if (index > 0) {
+ left = '2px';
+ }
+
+ if (index === 1) {
+ bottom = '2px';
+ } else if (index > 1) {
+ top = '2px';
+ }
+ } else if (size === 4) {
+ if (index === 0 || index === 2) {
+ right = '2px';
+ }
+
+ if (index === 1 || index === 3) {
+ left = '2px';
+ }
+
+ if (index < 2) {
+ bottom = '2px';
+ } else {
+ top = '2px';
+ }
+ }
+
+ let thumbnail = '';
+
+ if (attachment.get('type') === 'image') {
+ thumbnail = (
+ <a
+ className='media-gallery__item-thumbnail'
+ href={attachment.get('remote_url') || attachment.get('url')}
+ onClick={this.handleClick}
+ target='_blank'
+ style={{ backgroundImage: `url(${attachment.get('preview_url')})` }}
+ />
+ );
+ } else if (attachment.get('type') === 'gifv') {
+ const autoPlay = !isIOS() && this.props.autoPlayGif;
+
+ thumbnail = (
+ <div className={`media-gallery__gifv ${autoPlay ? 'autoplay' : ''}`}>
+ <video
+ className='media-gallery__item-gifv-thumbnail'
+ role='application'
+ src={attachment.get('url')}
+ onClick={this.handleClick}
+ autoPlay={autoPlay}
+ loop={true}
+ muted={true}
+ />
+
+ <span className='media-gallery__gifv__label'>GIF</span>
+ </div>
+ );
+ }
+
+ return (
+ <div className='media-gallery__item' key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
+ {thumbnail}
+ </div>
+ );
+ }
+
+}
+
+Item.propTypes = {
+ attachment: ImmutablePropTypes.map.isRequired,
+ index: PropTypes.number.isRequired,
+ size: PropTypes.number.isRequired,
+ onClick: PropTypes.func.isRequired,
+ autoPlayGif: PropTypes.bool.isRequired
+};
+
+class MediaGallery extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.state = {
+ visible: !props.sensitive
+ };
+ this.handleOpen = this.handleOpen.bind(this);
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleOpen (e) {
+ this.setState({ visible: !this.state.visible });
+ }
+
+ handleClick (index) {
+ this.props.onOpenMedia(this.props.media, index);
+ }
+
+ render () {
+ const { media, intl, sensitive } = this.props;
+
+ let children;
+
+ if (!this.state.visible) {
+ let warning;
+
+ if (sensitive) {
+ warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
+ } else {
+ warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
+ }
+
+ children = (
+ <div role='button' tabIndex='0' className='media-spoiler' onClick={this.handleOpen}>
+ <span className='media-spoiler__warning'>{warning}</span>
+ <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
+ </div>
+ );
+ } else {
+ const size = media.take(4).size;
+ children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} autoPlayGif={this.props.autoPlayGif} index={i} size={size} />);
+ }
+
+ return (
+ <div className='media-gallery' style={{ height: `${this.props.height}px` }}>
+ <div className='spoiler-button' style={{ display: !this.state.visible ? 'none' : 'block' }}>
+ <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
+ </div>
+
+ {children}
+ </div>
+ );
+ }
+
+}
+
+MediaGallery.propTypes = {
+ sensitive: PropTypes.bool,
+ media: ImmutablePropTypes.list.isRequired,
+ height: PropTypes.number.isRequired,
+ onOpenMedia: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ autoPlayGif: PropTypes.bool.isRequired
+};
+
+export default injectIntl(MediaGallery);
diff --git a/app/javascript/mastodon/components/missing_indicator.js b/app/javascript/mastodon/components/missing_indicator.js
@@ -0,0 +1,12 @@
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+
+const MissingIndicator = () => (
+ <div className='missing-indicator'>
+ <div>
+ <FormattedMessage id='missing_indicator.label' defaultMessage='Not found' />
+ </div>
+ </div>
+);
+
+export default MissingIndicator;
diff --git a/app/javascript/mastodon/components/permalink.js b/app/javascript/mastodon/components/permalink.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+class Permalink extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick (e) {
+ if (e.button === 0) {
+ e.preventDefault();
+ this.context.router.push(this.props.to);
+ }
+ }
+
+ render () {
+ const { href, children, className, ...other } = this.props;
+
+ return (
+ <a href={href} onClick={this.handleClick} {...other} className={'permalink ' + className}>
+ {children}
+ </a>
+ );
+ }
+
+}
+
+Permalink.contextTypes = {
+ router: PropTypes.object
+};
+
+Permalink.propTypes = {
+ className: PropTypes.string,
+ href: PropTypes.string.isRequired,
+ to: PropTypes.string.isRequired,
+ children: PropTypes.node
+};
+
+export default Permalink;
diff --git a/app/javascript/mastodon/components/relative_timestamp.js b/app/javascript/mastodon/components/relative_timestamp.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import { injectIntl, FormattedRelative } from 'react-intl';
+import PropTypes from 'prop-types';
+
+const RelativeTimestamp = ({ intl, timestamp }) => {
+ const date = new Date(timestamp);
+
+ return (
+ <time dateTime={timestamp} title={intl.formatDate(date, { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' })}>
+ <FormattedRelative value={date} />
+ </time>
+ );
+};
+
+RelativeTimestamp.propTypes = {
+ intl: PropTypes.object.isRequired,
+ timestamp: PropTypes.string.isRequired
+};
+
+export default injectIntl(RelativeTimestamp);
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
@@ -0,0 +1,123 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import Avatar from './avatar';
+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';
+import emojify from '../emoji';
+import escapeTextContentForBrowser from 'escape-html';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+class Status extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ this.handleAccountClick = this.handleAccountClick.bind(this);
+ }
+
+ handleClick () {
+ const { status } = this.props;
+ this.context.router.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
+ }
+
+ handleAccountClick (id, e) {
+ if (e.button === 0) {
+ e.preventDefault();
+ this.context.router.push(`/accounts/${id}`);
+ }
+ }
+
+ render () {
+ let media = '';
+ const { status, ...other } = this.props;
+
+ if (status === null) {
+ return <div />;
+ }
+
+ if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
+ let displayName = status.getIn(['account', 'display_name']);
+
+ if (displayName.length === 0) {
+ displayName = status.getIn(['account', 'username']);
+ }
+
+ const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
+
+ return (
+ <div className='status__wrapper'>
+ <div className='status__prepend'>
+ <div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
+ <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={displayNameHTML} /></a> }} />
+ </div>
+
+ <Status {...other} wrapped={true} status={status.get('reblog')} />
+ </div>
+ );
+ }
+
+ if (status.get('media_attachments').size > 0 && !this.props.muted) {
+ if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
+
+ } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
+ media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />;
+ } else {
+ media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
+ }
+ }
+
+ return (
+ <div className={this.props.muted ? 'status muted' : 'status'}>
+ <div className='status__info'>
+ <div className='status__info-time'>
+ <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
+ </div>
+
+ <a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name'>
+ <div className='status__avatar'>
+ <Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} />
+ </div>
+
+ <DisplayName account={status.get('account')} />
+ </a>
+ </div>
+
+ <StatusContent status={status} onClick={this.handleClick} />
+
+ {media}
+
+ <StatusActionBar {...this.props} />
+ </div>
+ );
+ }
+
+}
+
+Status.contextTypes = {
+ router: PropTypes.object
+};
+
+Status.propTypes = {
+ status: ImmutablePropTypes.map,
+ wrapped: PropTypes.bool,
+ onReply: PropTypes.func,
+ onFavourite: PropTypes.func,
+ onReblog: PropTypes.func,
+ onDelete: PropTypes.func,
+ onOpenMedia: PropTypes.func,
+ onOpenVideo: PropTypes.func,
+ onBlock: PropTypes.func,
+ me: PropTypes.number,
+ boostModal: PropTypes.bool,
+ autoPlayGif: PropTypes.bool,
+ muted: PropTypes.bool
+};
+
+export default Status;
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
@@ -0,0 +1,138 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import IconButton from './icon_button';
+import DropdownMenu from './dropdown_menu';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+ delete: { id: 'status.delete', defaultMessage: 'Delete' },
+ mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
+ mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
+ block: { id: 'account.block', defaultMessage: 'Block @{name}' },
+ reply: { id: 'status.reply', defaultMessage: 'Reply' },
+ replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
+ reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
+ cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
+ favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
+ open: { id: 'status.open', defaultMessage: 'Expand this status' },
+ report: { id: 'status.report', defaultMessage: 'Report @{name}' }
+});
+
+class StatusActionBar extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleReplyClick = this.handleReplyClick.bind(this);
+ this.handleFavouriteClick = this.handleFavouriteClick.bind(this);
+ this.handleReblogClick = this.handleReblogClick.bind(this);
+ this.handleDeleteClick = this.handleDeleteClick.bind(this);
+ this.handleMentionClick = this.handleMentionClick.bind(this);
+ this.handleMuteClick = this.handleMuteClick.bind(this);
+ this.handleBlockClick = this.handleBlockClick.bind(this);
+ this.handleOpen = this.handleOpen.bind(this);
+ this.handleReport = this.handleReport.bind(this);
+ }
+
+ handleReplyClick () {
+ this.props.onReply(this.props.status, this.context.router);
+ }
+
+ handleFavouriteClick () {
+ this.props.onFavourite(this.props.status);
+ }
+
+ handleReblogClick (e) {
+ this.props.onReblog(this.props.status, e);
+ }
+
+ handleDeleteClick () {
+ this.props.onDelete(this.props.status);
+ }
+
+ handleMentionClick () {
+ this.props.onMention(this.props.status.get('account'), this.context.router);
+ }
+
+ handleMuteClick () {
+ this.props.onMute(this.props.status.get('account'));
+ }
+
+ handleBlockClick () {
+ this.props.onBlock(this.props.status.get('account'));
+ }
+
+ handleOpen () {
+ this.context.router.push(`/statuses/${this.props.status.get('id')}`);
+ }
+
+ handleReport () {
+ this.props.onReport(this.props.status);
+ this.context.router.push('/report');
+ }
+
+ render () {
+ const { status, me, intl } = this.props;
+ const reblog_disabled = status.get('visibility') === 'private' || status.get('visibility') === 'direct';
+ let menu = [];
+
+ menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
+ menu.push(null);
+
+ if (status.getIn(['account', 'id']) === me) {
+ menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
+ } else {
+ menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
+ menu.push(null);
+ menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
+ menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
+ menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
+ }
+
+ let reblogIcon = 'retweet';
+ if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
+ else if (status.get('visibility') === 'private') reblogIcon = 'lock';
+ let reply_icon;
+ let reply_title;
+ if (status.get('in_reply_to_id', null) === null) {
+ reply_icon = "reply";
+ reply_title = intl.formatMessage(messages.reply);
+ } else {
+ reply_icon = "reply-all";
+ reply_title = intl.formatMessage(messages.replyAll);
+ }
+
+ return (
+ <div className='status__action-bar'>
+ <div className='status__action-bar-button-wrapper'><IconButton title={reply_title} icon={reply_icon} onClick={this.handleReplyClick} /></div>
+ <div className='status__action-bar-button-wrapper'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
+ <div className='status__action-bar-button-wrapper'><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} className='star-icon' /></div>
+
+ <div className='status__action-bar-dropdown'>
+ <DropdownMenu items={menu} icon='ellipsis-h' size={18} direction="right" ariaLabel="More"/>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+StatusActionBar.contextTypes = {
+ router: PropTypes.object
+};
+
+StatusActionBar.propTypes = {
+ status: ImmutablePropTypes.map.isRequired,
+ onReply: PropTypes.func,
+ onFavourite: PropTypes.func,
+ onReblog: PropTypes.func,
+ onDelete: PropTypes.func,
+ onMention: PropTypes.func,
+ onMute: PropTypes.func,
+ onBlock: PropTypes.func,
+ onReport: PropTypes.func,
+ me: PropTypes.number.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+export default injectIntl(StatusActionBar);
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
@@ -0,0 +1,165 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import escapeTextContentForBrowser from 'escape-html';
+import PropTypes from 'prop-types';
+import emojify from '../emoji';
+import { isRtl } from '../rtl';
+import { FormattedMessage } from 'react-intl';
+import Permalink from './permalink';
+
+class StatusContent extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.state = {
+ hidden: true
+ };
+ this.onMentionClick = this.onMentionClick.bind(this);
+ this.onHashtagClick = this.onHashtagClick.bind(this);
+ this.handleMouseDown = this.handleMouseDown.bind(this)
+ this.handleMouseUp = this.handleMouseUp.bind(this);
+ this.handleSpoilerClick = this.handleSpoilerClick.bind(this);
+ this.setRef = this.setRef.bind(this);
+ };
+
+ componentDidMount () {
+ const node = this.node;
+ const links = node.querySelectorAll('a');
+
+ 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);
+ link.setAttribute('title', mention.get('acct'));
+ } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
+ link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
+ } else if (media) {
+ link.innerHTML = '<i class="fa fa-fw fa-photo"></i>';
+ } else {
+ link.setAttribute('target', '_blank');
+ link.setAttribute('rel', 'noopener');
+ link.setAttribute('title', link.href);
+ }
+ }
+ }
+
+ onMentionClick (mention, e) {
+ if (e.button === 0) {
+ e.preventDefault();
+ this.context.router.push(`/accounts/${mention.get('id')}`);
+ }
+ }
+
+ onHashtagClick (hashtag, e) {
+ hashtag = hashtag.replace(/^#/, '').toLowerCase();
+
+ if (e.button === 0) {
+ e.preventDefault();
+ this.context.router.push(`/timelines/tag/${hashtag}`);
+ }
+ }
+
+ handleMouseDown (e) {
+ this.startXY = [e.clientX, e.clientY];
+ }
+
+ handleMouseUp (e) {
+ const [ startX, startY ] = this.startXY;
+ const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)];
+
+ if (e.target.localName === 'a' || (e.target.parentNode && e.target.parentNode.localName === 'a')) {
+ return;
+ }
+
+ if (deltaX + deltaY < 5 && e.button === 0) {
+ this.props.onClick();
+ }
+
+ this.startXY = null;
+ }
+
+ handleSpoilerClick (e) {
+ e.preventDefault();
+ this.setState({ hidden: !this.state.hidden });
+ }
+
+ setRef (c) {
+ this.node = c;
+ }
+
+ render () {
+ const { status } = this.props;
+ const { hidden } = this.state;
+
+ const content = { __html: emojify(status.get('content')) };
+ const spoilerContent = { __html: emojify(escapeTextContentForBrowser(status.get('spoiler_text', ''))) };
+ const directionStyle = { direction: 'ltr' };
+
+ if (isRtl(status.get('content'))) {
+ directionStyle.direction = 'rtl';
+ }
+
+ if (status.get('spoiler_text').length > 0) {
+ let mentionsPlaceholder = '';
+
+ const mentionLinks = status.get('mentions').map(item => (
+ <Permalink to={`/accounts/${item.get('id')}`} href={item.get('url')} key={item.get('id')} className='mention'>
+ @<span>{item.get('username')}</span>
+ </Permalink>
+ )).reduce((aggregate, item) => [...aggregate, item, ' '], [])
+
+ const toggleText = hidden ? <FormattedMessage id='status.show_more' defaultMessage='Show more' /> : <FormattedMessage id='status.show_less' defaultMessage='Show less' />;
+
+ if (hidden) {
+ mentionsPlaceholder = <div>{mentionLinks}</div>;
+ }
+
+ return (
+ <div className='status__content' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
+ <p style={{ marginBottom: hidden && status.get('mentions').size === 0 ? '0px' : '' }} >
+ <span dangerouslySetInnerHTML={spoilerContent} /> <a tabIndex='0' className='status__content__spoiler-link' role='button' onClick={this.handleSpoilerClick}>{toggleText}</a>
+ </p>
+
+ {mentionsPlaceholder}
+
+ <div ref={this.setRef} style={{ display: hidden ? 'none' : 'block', ...directionStyle }} dangerouslySetInnerHTML={content} />
+ </div>
+ );
+ } else if (this.props.onClick) {
+ return (
+ <div
+ ref={this.setRef}
+ className='status__content'
+ style={{ ...directionStyle }}
+ onMouseDown={this.handleMouseDown}
+ onMouseUp={this.handleMouseUp}
+ dangerouslySetInnerHTML={content}
+ />
+ );
+ } else {
+ return (
+ <div
+ ref={this.setRef}
+ className='status__content status__content--no-action'
+ style={{ ...directionStyle }}
+ dangerouslySetInnerHTML={content}
+ />
+ );
+ }
+ }
+
+}
+
+StatusContent.contextTypes = {
+ router: PropTypes.object
+};
+
+StatusContent.propTypes = {
+ status: ImmutablePropTypes.map.isRequired,
+ onClick: PropTypes.func
+};
+
+export default StatusContent;
diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js
@@ -0,0 +1,130 @@
+import React from 'react';
+import Status from './status';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { ScrollContainer } from 'react-router-scroll';
+import PropTypes from 'prop-types';
+import StatusContainer from '../containers/status_container';
+import LoadMore from './load_more';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+class StatusList extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleScroll = this.handleScroll.bind(this);
+ this.setRef = this.setRef.bind(this);
+ this.handleLoadMore = this.handleLoadMore.bind(this);
+ }
+
+ handleScroll (e) {
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
+ const offset = scrollHeight - scrollTop - clientHeight;
+ this._oldScrollPosition = scrollHeight - scrollTop;
+
+ if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
+ this.props.onScrollToBottom();
+ } else if (scrollTop < 100 && this.props.onScrollToTop) {
+ this.props.onScrollToTop();
+ } else if (this.props.onScroll) {
+ this.props.onScroll();
+ }
+ }
+
+ componentDidMount () {
+ this.attachScrollListener();
+ }
+
+ componentDidUpdate (prevProps) {
+ if (this.node.scrollTop > 0 && (prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition)) {
+ this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
+ }
+ }
+
+ componentWillUnmount () {
+ this.detachScrollListener();
+ }
+
+ attachScrollListener () {
+ this.node.addEventListener('scroll', this.handleScroll);
+ }
+
+ detachScrollListener () {
+ this.node.removeEventListener('scroll', this.handleScroll);
+ }
+
+ setRef (c) {
+ this.node = c;
+ }
+
+ handleLoadMore (e) {
+ e.preventDefault();
+ this.props.onScrollToBottom();
+ }
+
+ render () {
+ const { statusIds, onScrollToBottom, scrollKey, shouldUpdateScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props;
+
+ let loadMore = '';
+ let scrollableArea = '';
+ let unread = '';
+
+ if (!isLoading && statusIds.size > 0 && hasMore) {
+ loadMore = <LoadMore onClick={this.handleLoadMore} />;
+ }
+
+ if (isUnread) {
+ unread = <div className='status-list__unread-indicator' />;
+ }
+
+ if (isLoading || statusIds.size > 0 || !emptyMessage) {
+ scrollableArea = (
+ <div className='scrollable' ref={this.setRef}>
+ {unread}
+
+ <div className='status-list'>
+ {prepend}
+
+ {statusIds.map((statusId) => {
+ return <StatusContainer key={statusId} id={statusId} />;
+ })}
+
+ {loadMore}
+ </div>
+ </div>
+ );
+ } else {
+ scrollableArea = (
+ <div className='empty-column-indicator' ref={this.setRef}>
+ {emptyMessage}
+ </div>
+ );
+ }
+
+ return (
+ <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
+ {scrollableArea}
+ </ScrollContainer>
+ );
+ }
+
+}
+
+StatusList.propTypes = {
+ scrollKey: PropTypes.string.isRequired,
+ statusIds: ImmutablePropTypes.list.isRequired,
+ onScrollToBottom: PropTypes.func,
+ onScrollToTop: PropTypes.func,
+ onScroll: PropTypes.func,
+ shouldUpdateScroll: PropTypes.func,
+ isLoading: PropTypes.bool,
+ isUnread: PropTypes.bool,
+ hasMore: PropTypes.bool,
+ prepend: PropTypes.node,
+ emptyMessage: PropTypes.node
+};
+
+StatusList.defaultProps = {
+ trackScroll: true
+};
+
+export default StatusList;
diff --git a/app/javascript/mastodon/components/video_player.js b/app/javascript/mastodon/components/video_player.js
@@ -0,0 +1,210 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import IconButton from './icon_button';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { isIOS } from '../is_mobile';
+
+const messages = defineMessages({
+ toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' },
+ toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' },
+ expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' },
+ expand_video: { id: 'video_player.video_error', defaultMessage: 'Video could not be played' }
+});
+
+class VideoPlayer extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.state = {
+ visible: !this.props.sensitive,
+ preview: true,
+ muted: true,
+ hasAudio: true,
+ videoError: false
+ };
+
+ this.handleClick = this.handleClick.bind(this);
+ this.handleVideoClick = this.handleVideoClick.bind(this);
+ this.handleOpen = this.handleOpen.bind(this);
+ this.handleVisibility = this.handleVisibility.bind(this);
+ this.handleExpand = this.handleExpand.bind(this);
+ this.setRef = this.setRef.bind(this);
+ this.handleLoadedData = this.handleLoadedData.bind(this);
+ this.handleVideoError = this.handleVideoError.bind(this);
+ }
+
+ handleClick () {
+ this.setState({ muted: !this.state.muted });
+ }
+
+ handleVideoClick (e) {
+ e.stopPropagation();
+
+ const node = this.video;
+
+ if (node.paused) {
+ node.play();
+ } else {
+ node.pause();
+ }
+ }
+
+ handleOpen () {
+ this.setState({ preview: !this.state.preview });
+ }
+
+ handleVisibility () {
+ this.setState({
+ visible: !this.state.visible,
+ preview: true
+ });
+ }
+
+ handleExpand () {
+ this.video.pause();
+ this.props.onOpenVideo(this.props.media, this.video.currentTime);
+ }
+
+ setRef (c) {
+ this.video = c;
+ }
+
+ handleLoadedData () {
+ if (('WebkitAppearance' in document.documentElement.style && this.video.audioTracks.length === 0) || this.video.mozHasAudio === false) {
+ this.setState({ hasAudio: false });
+ }
+ }
+
+ handleVideoError () {
+ this.setState({ videoError: true });
+ }
+
+ componentDidMount () {
+ if (!this.video) {
+ return;
+ }
+
+ this.video.addEventListener('loadeddata', this.handleLoadedData);
+ this.video.addEventListener('error', this.handleVideoError);
+ }
+
+ componentDidUpdate () {
+ if (!this.video) {
+ return;
+ }
+
+ this.video.addEventListener('loadeddata', this.handleLoadedData);
+ this.video.addEventListener('error', this.handleVideoError);
+ }
+
+ componentWillUnmount () {
+ if (!this.video) {
+ return;
+ }
+
+ this.video.removeEventListener('loadeddata', this.handleLoadedData);
+ this.video.removeEventListener('error', this.handleVideoError);
+ }
+
+ render () {
+ const { media, intl, width, height, sensitive, autoplay } = this.props;
+
+ let spoilerButton = (
+ <div className='status__video-player-spoiler' style={{ display: !this.state.visible ? 'none' : 'block' }} >
+ <IconButton overlay title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} />
+ </div>
+ );
+
+ let expandButton = (
+ <div className='status__video-player-expand'>
+ <IconButton overlay title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} />
+ </div>
+ );
+
+ let muteButton = '';
+
+ if (this.state.hasAudio) {
+ muteButton = (
+ <div className='status__video-player-mute'>
+ <IconButton overlay title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} />
+ </div>
+ );
+ }
+
+ if (!this.state.visible) {
+ if (sensitive) {
+ return (
+ <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
+ {spoilerButton}
+ <span className='media-spoiler__warning'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
+ <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
+ </div>
+ );
+ } else {
+ return (
+ <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
+ {spoilerButton}
+ <span className='media-spoiler__warning'><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
+ <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
+ </div>
+ );
+ }
+ }
+
+ if (this.state.preview && !autoplay) {
+ return (
+ <div role='button' tabIndex='0' className='media-spoiler-video' style={{ width: `${width}px`, height: `${height}px`, background: `url(${media.get('preview_url')}) no-repeat center` }} onClick={this.handleOpen}>
+ {spoilerButton}
+ <div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div>
+ </div>
+ );
+ }
+
+ if (this.state.videoError) {
+ return (
+ <div style={{ width: `${width}px`, height: `${height}px` }} className='video-error-cover' >
+ <span className='media-spoiler__warning'><FormattedMessage id='video_player.video_error' defaultMessage='Video could not be played' /></span>
+ </div>
+ );
+ }
+
+ return (
+ <div className='status__video-player' style={{ width: `${width}px`, height: `${height}px` }}>
+ {spoilerButton}
+ {muteButton}
+ {expandButton}
+
+ <video
+ className='status__video-player-video'
+ role='button'
+ tabIndex='0'
+ ref={this.setRef}
+ src={media.get('url')}
+ autoPlay={!isIOS()}
+ loop={true}
+ muted={this.state.muted}
+ onClick={this.handleVideoClick}
+ />
+ </div>
+ );
+ }
+
+}
+
+VideoPlayer.propTypes = {
+ media: ImmutablePropTypes.map.isRequired,
+ width: PropTypes.number,
+ height: PropTypes.number,
+ sensitive: PropTypes.bool,
+ intl: PropTypes.object.isRequired,
+ autoplay: PropTypes.bool,
+ onOpenVideo: PropTypes.func.isRequired
+};
+
+VideoPlayer.defaultProps = {
+ width: 239,
+ height: 110
+};
+
+export default injectIntl(VideoPlayer);
diff --git a/app/assets/javascripts/components/containers/account_container.jsx b/app/javascript/mastodon/containers/account_container.js
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
@@ -0,0 +1,314 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+import PropTypes from 'prop-types';
+import configureStore from '../store/configureStore';
+import {
+ refreshTimelineSuccess,
+ updateTimeline,
+ deleteFromTimelines,
+ refreshTimeline,
+ connectTimeline,
+ disconnectTimeline
+} from '../actions/timelines';
+import { showOnboardingOnce } from '../actions/onboarding';
+import { updateNotifications, refreshNotifications } from '../actions/notifications';
+import createBrowserHistory from 'history/lib/createBrowserHistory';
+import {
+ applyRouterMiddleware,
+ useRouterHistory,
+ Router,
+ Route,
+ IndexRedirect,
+ IndexRoute
+} from 'react-router';
+import { useScroll } from 'react-router-scroll';
+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 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 { IntlProvider, addLocaleData } from 'react-intl';
+import ar from 'react-intl/locale-data/ar';
+import en from 'react-intl/locale-data/en';
+import de from 'react-intl/locale-data/de';
+import eo from 'react-intl/locale-data/eo';
+import es from 'react-intl/locale-data/es';
+import fa from 'react-intl/locale-data/fa';
+import fi from 'react-intl/locale-data/fi';
+import fr from 'react-intl/locale-data/fr';
+import he from 'react-intl/locale-data/he';
+import hu from 'react-intl/locale-data/hu';
+import it from 'react-intl/locale-data/it';
+import ja from 'react-intl/locale-data/ja';
+import pt from 'react-intl/locale-data/pt';
+import nl from 'react-intl/locale-data/nl';
+import no from 'react-intl/locale-data/no';
+import ru from 'react-intl/locale-data/ru';
+import uk from 'react-intl/locale-data/uk';
+import zh from 'react-intl/locale-data/zh';
+import bg from 'react-intl/locale-data/bg';
+import id from 'react-intl/locale-data/id';
+import getMessagesForLocale from '../locales';
+import { hydrateStore } from '../actions/store';
+import createStream from '../stream';
+
+const store = configureStore();
+const initialState = JSON.parse(document.getElementById("initial-state").textContent);
+store.dispatch(hydrateStore(initialState));
+
+const browserHistory = useRouterHistory(createBrowserHistory)({
+ basename: '/web'
+});
+
+addLocaleData([
+ ...en,
+ ...ar,
+ ...de,
+ ...eo,
+ ...es,
+ ...fa,
+ ...fi,
+ ...fr,
+ ...he,
+ ...hu,
+ ...it,
+ ...ja,
+ ...pt,
+ ...nl,
+ ...no,
+ ...ru,
+ ...uk,
+ ...zh,
+ ...bg,
+ ...id,
+]);
+
+const getTopWhenReplacing = (previous, { location }) => location && location.action === 'REPLACE' && [0, 0];
+
+const hiddenColumnContainerStyle = {
+ position: 'absolute',
+ left: '0',
+ top: '0',
+ visibility: 'hidden'
+};
+
+class Container extends React.PureComponent {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ renderedPersistents: [],
+ unrenderedPersistents: [],
+ };
+ }
+
+ componentWillMount () {
+ this.unlistenHistory = null;
+
+ this.setState(() => {
+ return {
+ mountImpersistent: false,
+ renderedPersistents: [],
+ unrenderedPersistents: [
+ {pathname: '/timelines/home', component: HomeTimeline},
+ {pathname: '/timelines/public', component: PublicTimeline},
+ {pathname: '/timelines/public/local', component: CommunityTimeline},
+
+ {pathname: '/notifications', component: Notifications},
+ {pathname: '/favourites', component: FavouritedStatuses}
+ ],
+ };
+ }, () => {
+ if (this.unlistenHistory) {
+ return;
+ }
+
+ this.unlistenHistory = browserHistory.listen(location => {
+ const pathname = location.pathname.replace(/\/$/, '').toLowerCase();
+
+ this.setState(oldState => {
+ let persistentMatched = false;
+
+ const newState = {
+ renderedPersistents: oldState.renderedPersistents.map(persistent => {
+ const givenMatched = persistent.pathname === pathname;
+
+ if (givenMatched) {
+ persistentMatched = true;
+ }
+
+ return {
+ hidden: !givenMatched,
+ pathname: persistent.pathname,
+ component: persistent.component
+ };
+ }),
+ };
+
+ if (!persistentMatched) {
+ newState.unrenderedPersistents = [];
+
+ oldState.unrenderedPersistents.forEach(persistent => {
+ if (persistent.pathname === pathname) {
+ persistentMatched = true;
+
+ newState.renderedPersistents.push({
+ hidden: false,
+ pathname: persistent.pathname,
+ component: persistent.component
+ });
+ } else {
+ newState.unrenderedPersistents.push(persistent);
+ }
+ });
+ }
+
+ newState.mountImpersistent = !persistentMatched;
+
+ return newState;
+ });
+ });
+ });
+ }
+
+ componentWillUnmount () {
+ if (this.unlistenHistory) {
+ this.unlistenHistory();
+ }
+
+ this.unlistenHistory = "done";
+ }
+
+ render () {
+ // Hide some components rather than unmounting them to allow to show again
+ // quickly and keep the view state such as the scrolled offset.
+ const persistentsView = this.state.renderedPersistents.map((persistent) =>
+ <div aria-hidden={persistent.hidden} key={persistent.pathname} className='mastodon-column-container' style={persistent.hidden ? hiddenColumnContainerStyle : null}>
+ <persistent.component shouldUpdateScroll={persistent.hidden ? Function.prototype : getTopWhenReplacing} />
+ </div>
+ );
+
+ return (
+ <UI>
+ {this.state.mountImpersistent && this.props.children}
+ {persistentsView}
+ </UI>
+ );
+ }
+}
+
+Container.propTypes = {
+ children: PropTypes.node,
+};
+
+class Mastodon extends React.Component {
+
+ componentDidMount() {
+ const { locale } = this.props;
+ const streamingAPIBaseURL = store.getState().getIn(['meta', 'streaming_api_base_url']);
+ const accessToken = store.getState().getIn(['meta', 'access_token']);
+
+ this.subscription = createStream(streamingAPIBaseURL, accessToken, 'user', {
+
+ connected () {
+ store.dispatch(connectTimeline('home'));
+ },
+
+ disconnected () {
+ store.dispatch(disconnectTimeline('home'));
+ },
+
+ received (data) {
+ switch(data.event) {
+ case 'update':
+ store.dispatch(updateTimeline('home', JSON.parse(data.payload)));
+ break;
+ case 'delete':
+ store.dispatch(deleteFromTimelines(data.payload));
+ break;
+ case 'notification':
+ store.dispatch(updateNotifications(JSON.parse(data.payload), getMessagesForLocale(locale), locale));
+ break;
+ }
+ },
+
+ reconnected () {
+ store.dispatch(connectTimeline('home'));
+ store.dispatch(refreshTimeline('home'));
+ store.dispatch(refreshNotifications());
+ }
+
+ });
+
+ // Desktop notifications
+ if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
+ Notification.requestPermission();
+ }
+
+ store.dispatch(showOnboardingOnce());
+ }
+
+ componentWillUnmount () {
+ if (typeof this.subscription !== 'undefined') {
+ this.subscription.close();
+ this.subscription = null;
+ }
+ }
+
+ render () {
+ const { locale } = this.props;
+
+ return (
+ <IntlProvider locale={locale} messages={getMessagesForLocale(locale)}>
+ <Provider store={store}>
+ <Router history={browserHistory} render={applyRouterMiddleware(useScroll())}>
+ <Route path='/' component={Container}>
+ <IndexRedirect to='/getting-started' />
+ <Route path='getting-started' component={GettingStarted} />
+ <Route path='timelines/tag/:id' component={HashtagTimeline} />
+
+ <Route path='statuses/new' component={Compose} />
+ <Route path='statuses/:statusId' component={Status} />
+ <Route path='statuses/:statusId/reblogs' component={Reblogs} />
+ <Route path='statuses/:statusId/favourites' component={Favourites} />
+
+ <Route path='accounts/:accountId' component={AccountTimeline} />
+ <Route path='accounts/:accountId/followers' component={Followers} />
+ <Route path='accounts/:accountId/following' component={Following} />
+
+ <Route path='follow_requests' component={FollowRequests} />
+ <Route path='blocks' component={Blocks} />
+ <Route path='mutes' component={Mutes} />
+ <Route path='report' component={Report} />
+
+ <Route path='*' component={GenericNotFound} />
+ </Route>
+ </Router>
+ </Provider>
+ </IntlProvider>
+ );
+ }
+
+}
+
+Mastodon.propTypes = {
+ locale: PropTypes.string.isRequired
+};
+
+export default Mastodon;
diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js
@@ -0,0 +1,118 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import Status from '../components/status';
+import { makeGetStatus } from '../selectors';
+import {
+ replyCompose,
+ mentionCompose
+} from '../actions/compose';
+import {
+ reblog,
+ favourite,
+ unreblog,
+ unfavourite
+} from '../actions/interactions';
+import {
+ blockAccount,
+ muteAccount
+} from '../actions/accounts';
+import { 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({
+ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
+ deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
+ blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
+ muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
+});
+
+const makeMapStateToProps = () => {
+ const getStatus = makeGetStatus();
+
+ const mapStateToProps = (state, props) => ({
+ status: getStatus(state, props.id),
+ me: state.getIn(['meta', 'me']),
+ boostModal: state.getIn(['meta', 'boost_modal']),
+ autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
+ });
+
+ return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch, { intl }) => ({
+
+ onReply (status, router) {
+ dispatch(replyCompose(status, router));
+ },
+
+ onModalReblog (status) {
+ dispatch(reblog(status));
+ },
+
+ onReblog (status, e) {
+ if (status.get('reblogged')) {
+ dispatch(unreblog(status));
+ } else {
+ if (e.shiftKey || !this.boostModal) {
+ this.onModalReblog(status);
+ } else {
+ dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
+ }
+ }
+ },
+
+ onFavourite (status) {
+ if (status.get('favourited')) {
+ dispatch(unfavourite(status));
+ } else {
+ dispatch(favourite(status));
+ }
+ },
+
+ onDelete (status) {
+ dispatch(openModal('CONFIRM', {
+ message: intl.formatMessage(messages.deleteMessage),
+ confirm: intl.formatMessage(messages.deleteConfirm),
+ onConfirm: () => dispatch(deleteStatus(status.get('id')))
+ }));
+ },
+
+ onMention (account, router) {
+ dispatch(mentionCompose(account, router));
+ },
+
+ onOpenMedia (media, index) {
+ dispatch(openModal('MEDIA', { media, index }));
+ },
+
+ onOpenVideo (media, time) {
+ dispatch(openModal('VIDEO', { media, time }));
+ },
+
+ onBlock (account) {
+ dispatch(openModal('CONFIRM', {
+ message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
+ confirm: intl.formatMessage(messages.blockConfirm),
+ onConfirm: () => dispatch(blockAccount(account.get('id')))
+ }));
+ },
+
+ onReport (status) {
+ dispatch(initReport(status.get('account'), status));
+ },
+
+ onMute (account) {
+ dispatch(openModal('CONFIRM', {
+ message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
+ confirm: intl.formatMessage(messages.muteConfirm),
+ onConfirm: () => dispatch(muteAccount(account.get('id')))
+ }));
+ },
+
+});
+
+export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
diff --git a/app/assets/javascripts/components/emoji.jsx b/app/javascript/mastodon/emoji.js
diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js
@@ -0,0 +1,93 @@
+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';
+import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
+
+const messages = defineMessages({
+ mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
+ edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
+ unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
+ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
+ unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
+ block: { id: 'account.block', defaultMessage: 'Block @{name}' },
+ mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
+ follow: { id: 'account.follow', defaultMessage: 'Follow' },
+ report: { id: 'account.report', defaultMessage: 'Report @{name}' },
+ disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' }
+});
+
+class ActionBar extends React.PureComponent {
+
+ render () {
+ const { account, me, intl } = this.props;
+
+ let menu = [];
+ let extraInfo = '';
+
+ menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
+ menu.push(null);
+
+ if (account.get('id') === me) {
+ menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
+ } else {
+ if (account.getIn(['relationship', 'muting'])) {
+ menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
+ } else {
+ menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute });
+ }
+
+ if (account.getIn(['relationship', 'blocking'])) {
+ menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
+ } else {
+ menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock });
+ }
+
+ menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
+ }
+
+ if (account.get('acct') !== account.get('username')) {
+ extraInfo = <abbr title={intl.formatMessage(messages.disclaimer)}>*</abbr>;
+ }
+
+ return (
+ <div className='account__action-bar'>
+ <div className='account__action-bar-dropdown'>
+ <DropdownMenu items={menu} icon='bars' size={24} direction="right" />
+ </div>
+
+ <div className='account__action-bar-links'>
+ <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}`}>
+ <span><FormattedMessage id='account.posts' defaultMessage='Posts' /></span>
+ <strong><FormattedNumber value={account.get('statuses_count')} /> {extraInfo}</strong>
+ </Link>
+
+ <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`}>
+ <span><FormattedMessage id='account.follows' defaultMessage='Follows' /></span>
+ <strong><FormattedNumber value={account.get('following_count')} /> {extraInfo}</strong>
+ </Link>
+
+ <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`}>
+ <span><FormattedMessage id='account.followers' defaultMessage='Followers' /></span>
+ <strong><FormattedNumber value={account.get('followers_count')} /> {extraInfo}</strong>
+ </Link>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+ActionBar.propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ me: PropTypes.number.isRequired,
+ onFollow: PropTypes.func,
+ onBlock: PropTypes.func.isRequired,
+ onMention: PropTypes.func.isRequired,
+ onReport: PropTypes.func.isRequired,
+ onMute: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+export default injectIntl(ActionBar);
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
@@ -0,0 +1,150 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import emojify from '../../../emoji';
+import escapeTextContentForBrowser from 'escape-html';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import IconButton from '../../../components/icon_button';
+import { Motion, spring } from 'react-motion';
+import { connect } from 'react-redux';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
+ follow: { id: 'account.follow', defaultMessage: 'Follow' },
+ requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }
+});
+
+const makeMapStateToProps = () => {
+ const mapStateToProps = (state, props) => ({
+ autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
+ });
+
+ return mapStateToProps;
+};
+
+class Avatar extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+
+ this.state = {
+ isHovered: false
+ };
+
+ this.handleMouseOver = this.handleMouseOver.bind(this);
+ this.handleMouseOut = this.handleMouseOut.bind(this);
+ }
+
+ handleMouseOver () {
+ if (this.state.isHovered) return;
+ this.setState({ isHovered: true });
+ }
+
+ handleMouseOut () {
+ if (!this.state.isHovered) return;
+ this.setState({ isHovered: false });
+ }
+
+ render () {
+ const { account, autoPlayGif } = this.props;
+ const { isHovered } = this.state;
+
+ return (
+ <Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}>
+ {({ radius }) =>
+ <a
+ href={account.get('url')}
+ className='account__header__avatar'
+ target='_blank'
+ rel='noopener'
+ style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }}
+ onMouseOver={this.handleMouseOver}
+ onMouseOut={this.handleMouseOut}
+ onFocus={this.handleMouseOver}
+ onBlur={this.handleMouseOut}
+ />
+ }
+ </Motion>
+ );
+ }
+
+}
+
+Avatar.propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ autoPlayGif: PropTypes.bool.isRequired
+};
+
+class Header extends ImmutablePureComponent {
+
+ render () {
+ const { account, me, intl } = this.props;
+
+ if (!account) {
+ return null;
+ }
+
+ let displayName = account.get('display_name');
+ let info = '';
+ let actionBtn = '';
+ let lockedIcon = '';
+
+ if (displayName.length === 0) {
+ displayName = account.get('username');
+ }
+
+ if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
+ info = <span className='account--follows-info' style={{ position: 'absolute', top: '10px', right: '10px', opacity: '0.7', display: 'inline-block', verticalAlign: 'top', background: 'rgba(0, 0, 0, 0.4)', textTransform: 'uppercase', fontSize: '11px', fontWeight: '500', padding: '4px', borderRadius: '4px' }}><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>
+ }
+
+ if (me !== account.get('id')) {
+ if (account.getIn(['relationship', 'requested'])) {
+ actionBtn = (
+ <div style={{ position: 'absolute', top: '10px', left: '20px' }}>
+ <IconButton size={26} disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} />
+ </div>
+ );
+ } else if (!account.getIn(['relationship', 'blocking'])) {
+ actionBtn = (
+ <div style={{ position: 'absolute', top: '10px', left: '20px' }}>
+ <IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
+ </div>
+ );
+ }
+ }
+
+ if (account.get('locked')) {
+ lockedIcon = <i className='fa fa-lock' />;
+ }
+
+ const content = { __html: emojify(account.get('note')) };
+ const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
+
+ return (
+ <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
+ <div style={{ padding: '20px 10px' }}>
+ <Avatar account={account} autoPlayGif={this.props.autoPlayGif} />
+
+ <span style={{ display: 'inline-block', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }} className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} />
+ <span className='account__header__username' style={{ fontSize: '14px', fontWeight: '400', display: 'block', marginBottom: '10px' }}>@{account.get('acct')} {lockedIcon}</span>
+ <div style={{ fontSize: '14px' }} className='account__header__content' dangerouslySetInnerHTML={content} />
+
+ {info}
+ {actionBtn}
+ </div>
+ </div>
+ );
+ }
+
+}
+
+Header.propTypes = {
+ account: ImmutablePropTypes.map,
+ me: PropTypes.number.isRequired,
+ onFollow: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ autoPlayGif: PropTypes.bool.isRequired
+};
+
+export default connect(makeMapStateToProps)(injectIntl(Header));
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -0,0 +1,83 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import InnerHeader from '../../account/components/header';
+import ActionBar from '../../account/components/action_bar';
+import MissingIndicator from '../../../components/missing_indicator';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+class Header extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleFollow = this.handleFollow.bind(this);
+ this.handleBlock = this.handleBlock.bind(this);
+ this.handleMention = this.handleMention.bind(this);
+ this.handleReport = this.handleReport.bind(this);
+ this.handleMute = this.handleMute.bind(this);
+ }
+
+ handleFollow () {
+ this.props.onFollow(this.props.account);
+ }
+
+ handleBlock () {
+ this.props.onBlock(this.props.account);
+ }
+
+ handleMention () {
+ this.props.onMention(this.props.account, this.context.router);
+ }
+
+ handleReport () {
+ this.props.onReport(this.props.account);
+ this.context.router.push('/report');
+ }
+
+ handleMute() {
+ this.props.onMute(this.props.account);
+ }
+
+ render () {
+ const { account, me } = this.props;
+
+ if (account === null) {
+ return <MissingIndicator />;
+ }
+
+ return (
+ <div className='account-timeline__header'>
+ <InnerHeader
+ account={account}
+ me={me}
+ onFollow={this.handleFollow}
+ />
+
+ <ActionBar
+ account={account}
+ me={me}
+ onBlock={this.handleBlock}
+ onMention={this.handleMention}
+ onReport={this.handleReport}
+ onMute={this.handleMute}
+ />
+ </div>
+ );
+ }
+}
+
+Header.propTypes = {
+ account: ImmutablePropTypes.map,
+ me: PropTypes.number.isRequired,
+ onFollow: PropTypes.func.isRequired,
+ onBlock: PropTypes.func.isRequired,
+ onMention: PropTypes.func.isRequired,
+ onReport: PropTypes.func.isRequired,
+ onMute: PropTypes.func.isRequired
+};
+
+Header.contextTypes = {
+ router: PropTypes.object
+};
+
+export default Header;
diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
@@ -0,0 +1,76 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { makeGetAccount } from '../../../selectors';
+import Header from '../components/header';
+import {
+ followAccount,
+ unfollowAccount,
+ blockAccount,
+ unblockAccount,
+ muteAccount,
+ unmuteAccount
+} from '../../../actions/accounts';
+import { mentionCompose } from '../../../actions/compose';
+import { initReport } from '../../../actions/reports';
+import { openModal } from '../../../actions/modal';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+const messages = defineMessages({
+ blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
+ muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' }
+});
+
+const makeMapStateToProps = () => {
+ const getAccount = makeGetAccount();
+
+ const mapStateToProps = (state, { accountId }) => ({
+ account: getAccount(state, Number(accountId)),
+ me: state.getIn(['meta', 'me'])
+ });
+
+ return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch, { intl }) => ({
+ onFollow (account) {
+ if (account.getIn(['relationship', 'following'])) {
+ dispatch(unfollowAccount(account.get('id')));
+ } else {
+ dispatch(followAccount(account.get('id')));
+ }
+ },
+
+ onBlock (account) {
+ if (account.getIn(['relationship', 'blocking'])) {
+ dispatch(unblockAccount(account.get('id')));
+ } else {
+ dispatch(openModal('CONFIRM', {
+ message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
+ confirm: intl.formatMessage(messages.blockConfirm),
+ onConfirm: () => dispatch(blockAccount(account.get('id')))
+ }));
+ }
+ },
+
+ onMention (account, router) {
+ dispatch(mentionCompose(account, router));
+ },
+
+ onReport (account) {
+ dispatch(initReport(account));
+ },
+
+ onMute (account) {
+ if (account.getIn(['relationship', 'muting'])) {
+ dispatch(unmuteAccount(account.get('id')));
+ } else {
+ dispatch(openModal('CONFIRM', {
+ message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
+ confirm: intl.formatMessage(messages.muteConfirm),
+ onConfirm: () => dispatch(muteAccount(account.get('id')))
+ }));
+ }
+ }
+});
+
+export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
@@ -0,0 +1,89 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import {
+ fetchAccount,
+ fetchAccountTimeline,
+ expandAccountTimeline
+} from '../../actions/accounts';
+import StatusList from '../../components/status_list';
+import LoadingIndicator from '../../components/loading_indicator';
+import Column from '../ui/components/column';
+import HeaderContainer from './containers/header_container';
+import ColumnBackButton from '../../components/column_back_button';
+import Immutable from 'immutable';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const mapStateToProps = (state, props) => ({
+ statusIds: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'items'], Immutable.List()),
+ isLoading: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'isLoading']),
+ hasMore: !!state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'next']),
+ me: state.getIn(['meta', 'me'])
+});
+
+class AccountTimeline extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleScrollToBottom = this.handleScrollToBottom.bind(this);
+ }
+
+ componentWillMount () {
+ this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
+ this.props.dispatch(fetchAccountTimeline(Number(this.props.params.accountId)));
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
+ this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
+ this.props.dispatch(fetchAccountTimeline(Number(nextProps.params.accountId)));
+ }
+ }
+
+ handleScrollToBottom () {
+ if (!this.props.isLoading && this.props.hasMore) {
+ this.props.dispatch(expandAccountTimeline(Number(this.props.params.accountId)));
+ }
+ }
+
+ render () {
+ const { statusIds, isLoading, hasMore, me } = this.props;
+
+ if (!statusIds && isLoading) {
+ return (
+ <Column>
+ <LoadingIndicator />
+ </Column>
+ );
+ }
+
+ return (
+ <Column>
+ <ColumnBackButton />
+
+ <StatusList
+ prepend={<HeaderContainer accountId={this.props.params.accountId} />}
+ scrollKey='account_timeline'
+ statusIds={statusIds}
+ isLoading={isLoading}
+ hasMore={hasMore}
+ me={me}
+ onScrollToBottom={this.handleScrollToBottom}
+ />
+ </Column>
+ );
+ }
+
+}
+
+AccountTimeline.propTypes = {
+ params: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ statusIds: ImmutablePropTypes.list,
+ isLoading: PropTypes.bool,
+ hasMore: PropTypes.bool,
+ me: PropTypes.number.isRequired
+};
+
+export default connect(mapStateToProps)(AccountTimeline);
diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.js
@@ -0,0 +1,74 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import LoadingIndicator from '../../components/loading_indicator';
+import { ScrollContainer } from 'react-router-scroll';
+import Column from '../ui/components/column';
+import ColumnBackButtonSlim from '../../components/column_back_button_slim';
+import AccountContainer from '../../containers/account_container';
+import { fetchBlocks, expandBlocks } from '../../actions/blocks';
+import { defineMessages, injectIntl } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ heading: { id: 'column.blocks', defaultMessage: 'Blocked users' }
+});
+
+const mapStateToProps = state => ({
+ accountIds: state.getIn(['user_lists', 'blocks', 'items'])
+});
+
+class Blocks extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleScroll = this.handleScroll.bind(this);
+ }
+
+ componentWillMount () {
+ this.props.dispatch(fetchBlocks());
+ }
+
+ handleScroll (e) {
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
+
+ if (scrollTop === scrollHeight - clientHeight) {
+ this.props.dispatch(expandBlocks());
+ }
+ }
+
+ render () {
+ const { intl, accountIds } = this.props;
+
+ if (!accountIds) {
+ return (
+ <Column>
+ <LoadingIndicator />
+ </Column>
+ );
+ }
+
+ return (
+ <Column icon='ban' heading={intl.formatMessage(messages.heading)}>
+ <ColumnBackButtonSlim />
+ <ScrollContainer scrollKey='blocks'>
+ <div className='scrollable' onScroll={this.handleScroll}>
+ {accountIds.map(id =>
+ <AccountContainer key={id} id={id} />
+ )}
+ </div>
+ </ScrollContainer>
+ </Column>
+ );
+ }
+}
+
+Blocks.propTypes = {
+ params: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ accountIds: ImmutablePropTypes.list,
+ intl: PropTypes.object.isRequired
+};
+
+export default connect(mapStateToProps)(injectIntl(Blocks));
diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js
@@ -0,0 +1,96 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import StatusListContainer from '../ui/containers/status_list_container';
+import Column from '../ui/components/column';
+import {
+ refreshTimeline,
+ updateTimeline,
+ deleteFromTimelines,
+ connectTimeline,
+ disconnectTimeline
+} from '../../actions/timelines';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import ColumnBackButtonSlim from '../../components/column_back_button_slim';
+import createStream from '../../stream';
+
+const messages = defineMessages({
+ title: { id: 'column.community', defaultMessage: 'Local timeline' }
+});
+
+const mapStateToProps = state => ({
+ hasUnread: state.getIn(['timelines', 'community', 'unread']) > 0,
+ streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
+ accessToken: state.getIn(['meta', 'access_token'])
+});
+
+let subscription;
+
+class CommunityTimeline extends React.PureComponent {
+
+ componentDidMount () {
+ const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
+
+ dispatch(refreshTimeline('community'));
+
+ if (typeof subscription !== 'undefined') {
+ return;
+ }
+
+ subscription = createStream(streamingAPIBaseURL, accessToken, 'public:local', {
+
+ connected () {
+ dispatch(connectTimeline('community'));
+ },
+
+ reconnected () {
+ dispatch(connectTimeline('community'));
+ },
+
+ disconnected () {
+ dispatch(disconnectTimeline('community'));
+ },
+
+ received (data) {
+ switch(data.event) {
+ case 'update':
+ dispatch(updateTimeline('community', JSON.parse(data.payload)));
+ break;
+ case 'delete':
+ dispatch(deleteFromTimelines(data.payload));
+ break;
+ }
+ }
+
+ });
+ }
+
+ componentWillUnmount () {
+ // if (typeof subscription !== 'undefined') {
+ // subscription.close();
+ // subscription = null;
+ // }
+ }
+
+ render () {
+ const { intl, hasUnread } = this.props;
+
+ return (
+ <Column icon='users' active={hasUnread} heading={intl.formatMessage(messages.title)}>
+ <ColumnBackButtonSlim />
+ <StatusListContainer {...this.props} scrollKey='community_timeline' type='community' emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} />
+ </Column>
+ );
+ }
+
+}
+
+CommunityTimeline.propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ streamingAPIBaseURL: PropTypes.string.isRequired,
+ accessToken: PropTypes.string.isRequired,
+ hasUnread: PropTypes.bool
+};
+
+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
@@ -0,0 +1,26 @@
+import React from 'react';
+import Avatar from '../../../components/avatar';
+import DisplayName from '../../../components/display_name';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+class AutosuggestAccount extends ImmutablePureComponent {
+
+ render () {
+ const { account } = this.props;
+
+ return (
+ <div className='autosuggest-account'>
+ <div className='autosuggest-account-icon'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={18} /></div>
+ <DisplayName account={account} />
+ </div>
+ );
+ }
+
+}
+
+AutosuggestAccount.propTypes = {
+ account: ImmutablePropTypes.map.isRequired
+};
+
+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
@@ -0,0 +1,27 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { length } from 'stringz';
+
+class CharacterCounter extends React.PureComponent {
+
+ checkRemainingText (diff) {
+ if (diff < 0) {
+ return <span className='character-counter character-counter--over'>{diff}</span>;
+ }
+ return <span className='character-counter'>{diff}</span>;
+ }
+
+ render () {
+ const diff = this.props.max - length(this.props.text);
+
+ return this.checkRemainingText(diff);
+ }
+
+}
+
+CharacterCounter.propTypes = {
+ text: PropTypes.string.isRequired,
+ max: PropTypes.number.isRequired
+}
+
+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
@@ -0,0 +1,211 @@
+import React from 'react';
+import CharacterCounter from './character_counter';
+import Button from '../../../components/button';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import ReplyIndicatorContainer from '../containers/reply_indicator_container';
+import AutosuggestTextarea from '../../../components/autosuggest_textarea';
+import { debounce } from 'react-decoration';
+import UploadButtonContainer from '../containers/upload_button_container';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import Toggle from 'react-toggle';
+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';
+
+const messages = defineMessages({
+ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
+ spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Content warning' },
+ publish: { id: 'compose_form.publish', defaultMessage: 'Toot' }
+});
+
+class ComposeForm extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleKeyDown = this.handleKeyDown.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this);
+ this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this);
+ this.onSuggestionSelected = this.onSuggestionSelected.bind(this);
+ this.handleChangeSpoilerText = this.handleChangeSpoilerText.bind(this);
+ this.setAutosuggestTextarea = this.setAutosuggestTextarea.bind(this);
+ this.handleEmojiPick = this.handleEmojiPick.bind(this);
+ }
+
+ handleChange (e) {
+ this.props.onChange(e.target.value);
+ }
+
+ handleKeyDown (e) {
+ if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
+ this.handleSubmit();
+ }
+ }
+
+ handleSubmit () {
+ this.autosuggestTextarea.reset();
+ this.props.onSubmit();
+ }
+
+ onSuggestionsClearRequested () {
+ this.props.onClearSuggestions();
+ }
+
+ @debounce(500)
+ onSuggestionsFetchRequested (token) {
+ this.props.onFetchSuggestions(token);
+ }
+
+ onSuggestionSelected (tokenStart, token, value) {
+ this._restoreCaret = null;
+ this.props.onSuggestionSelected(tokenStart, token, value);
+ }
+
+ handleChangeSpoilerText (e) {
+ this.props.onChangeSpoilerText(e.target.value);
+ }
+
+ componentWillReceiveProps (nextProps) {
+ // If this is the update where we've finished uploading,
+ // save the last caret position so we can restore it below!
+ if (!nextProps.is_uploading && this.props.is_uploading) {
+ this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart;
+ }
+ }
+
+ componentDidUpdate (prevProps) {
+ // This statement does several things:
+ // - If we're beginning a reply, and,
+ // - Replying to zero or one users, places the cursor at the end of the textbox.
+ // - Replying to more than one user, selects any usernames past the first;
+ // this provides a convenient shortcut to drop everyone else from the conversation.
+ // - If we've just finished uploading an image, and have a saved caret position,
+ // restores the cursor to that position after the text changes!
+ if (this.props.focusDate !== prevProps.focusDate || (prevProps.is_uploading && !this.props.is_uploading && typeof this._restoreCaret === 'number')) {
+ let selectionEnd, selectionStart;
+
+ if (this.props.preselectDate !== prevProps.preselectDate) {
+ selectionEnd = this.props.text.length;
+ selectionStart = this.props.text.search(/\s/) + 1;
+ } else if (typeof this._restoreCaret === 'number') {
+ selectionStart = this._restoreCaret;
+ selectionEnd = this._restoreCaret;
+ } else {
+ selectionEnd = this.props.text.length;
+ selectionStart = selectionEnd;
+ }
+
+ this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
+ this.autosuggestTextarea.textarea.focus();
+ }
+ }
+
+ setAutosuggestTextarea (c) {
+ this.autosuggestTextarea = c;
+ }
+
+ handleEmojiPick (data) {
+ const position = this.autosuggestTextarea.textarea.selectionStart;
+ this._restoreCaret = position + data.shortname.length + 1;
+ this.props.onPickEmoji(position, data);
+ }
+
+ render () {
+ const { intl, onPaste } = this.props;
+ const disabled = this.props.is_submitting;
+ 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 = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
+ } else {
+ publishText = intl.formatMessage(messages.publish) + (this.props.privacy !== 'unlisted' ? '!' : '');
+ }
+
+ return (
+ <div className='compose-form'>
+ <Collapsable isVisible={this.props.spoiler} fullHeight={50}>
+ <div className="spoiler-input">
+ <input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type="text" className="spoiler-input__input" id='cw-spoiler-input'/>
+ </div>
+ </Collapsable>
+
+ <WarningContainer />
+
+ <ReplyIndicatorContainer />
+
+ <div className='compose-form__autosuggest-wrapper'>
+ <AutosuggestTextarea
+ ref={this.setAutosuggestTextarea}
+ placeholder={intl.formatMessage(messages.placeholder)}
+ disabled={disabled}
+ value={this.props.text}
+ onChange={this.handleChange}
+ suggestions={this.props.suggestions}
+ onKeyDown={this.handleKeyDown}
+ onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
+ onSuggestionsClearRequested={this.onSuggestionsClearRequested}
+ onSuggestionSelected={this.onSuggestionSelected}
+ onPaste={onPaste}
+ />
+
+ <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
+ </div>
+
+ <div className='compose-form__modifiers'>
+ <UploadFormContainer />
+ </div>
+
+ <div className='compose-form__buttons-wrapper'>
+ <div className='compose-form__buttons'>
+ <UploadButtonContainer />
+ <PrivacyDropdownContainer />
+ <SensitiveButtonContainer />
+ <SpoilerButtonContainer />
+ </div>
+
+ <div className='compose-form__publish'>
+ <div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
+ <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length > 500 || (text.length !==0 && text.trim().length === 0)} block /></div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+ComposeForm.propTypes = {
+ intl: PropTypes.object.isRequired,
+ text: PropTypes.string.isRequired,
+ suggestion_token: PropTypes.string,
+ suggestions: ImmutablePropTypes.list,
+ spoiler: PropTypes.bool,
+ privacy: PropTypes.string,
+ spoiler_text: PropTypes.string,
+ focusDate: PropTypes.instanceOf(Date),
+ preselectDate: PropTypes.instanceOf(Date),
+ is_submitting: PropTypes.bool,
+ is_uploading: PropTypes.bool,
+ me: PropTypes.number,
+ onChange: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ onClearSuggestions: PropTypes.func.isRequired,
+ onFetchSuggestions: PropTypes.func.isRequired,
+ onSuggestionSelected: PropTypes.func.isRequired,
+ onChangeSpoilerText: PropTypes.func.isRequired,
+ onPaste: PropTypes.func.isRequired,
+ onPickEmoji: PropTypes.func.isRequired
+};
+
+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
@@ -0,0 +1,115 @@
+import React from 'react';
+import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
+import EmojiPicker from 'emojione-picker';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
+ emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
+ people: { id: 'emoji_button.people', defaultMessage: 'People' },
+ nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' },
+ food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' },
+ activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' },
+ travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' },
+ objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' },
+ symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' },
+ flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' }
+});
+
+const settings = {
+ imageType: 'png',
+ sprites: false,
+ imagePathPNG: '/emoji/'
+};
+
+const dropdownStyle = {
+ position: 'absolute',
+ right: '5px',
+ top: '5px'
+};
+
+const dropdownTriggerStyle = {
+ display: 'block',
+ fontSize: '24px',
+ lineHeight: '24px',
+ marginLeft: '2px',
+ width: '24px'
+}
+
+class EmojiPickerDropdown extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.setRef = this.setRef.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ }
+
+ setRef (c) {
+ this.dropdown = c;
+ }
+
+ handleChange (data) {
+ this.dropdown.hide();
+ this.props.onPickEmoji(data);
+ }
+
+ render () {
+ const { intl } = this.props;
+
+ const categories = {
+ people: {
+ title: intl.formatMessage(messages.people),
+ emoji: 'smile',
+ },
+ nature: {
+ title: intl.formatMessage(messages.nature),
+ emoji: 'hamster',
+ },
+ food: {
+ title: intl.formatMessage(messages.food),
+ emoji: 'pizza',
+ },
+ activity: {
+ title: intl.formatMessage(messages.activity),
+ emoji: 'soccer',
+ },
+ travel: {
+ title: intl.formatMessage(messages.travel),
+ emoji: 'earth_americas',
+ },
+ objects: {
+ title: intl.formatMessage(messages.objects),
+ emoji: 'bulb',
+ },
+ symbols: {
+ title: intl.formatMessage(messages.symbols),
+ emoji: 'clock9',
+ },
+ flags: {
+ title: intl.formatMessage(messages.flags),
+ emoji: 'flag_gb',
+ }
+ }
+
+ return (
+ <Dropdown ref={this.setRef} style={dropdownStyle}>
+ <DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)} style={dropdownTriggerStyle}>
+ <img draggable="false" className="emojione" alt="🙂" src="/emoji/1f602.svg" />
+ </DropdownTrigger>
+
+ <DropdownContent className='dropdown__left'>
+ <EmojiPicker emojione={settings} onChange={this.handleChange} searchPlaceholder={intl.formatMessage(messages.emoji_search)} categories={categories} search={true} />
+ </DropdownContent>
+ </Dropdown>
+ );
+ }
+
+}
+
+EmojiPickerDropdown.propTypes = {
+ intl: PropTypes.object.isRequired,
+ onPickEmoji: PropTypes.func.isRequired
+};
+
+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
@@ -0,0 +1,37 @@
+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';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+class NavigationBar extends ImmutablePureComponent {
+
+ render () {
+ return (
+ <div className='navigation-bar'>
+ <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
+ <Avatar src={this.props.account.get('avatar')} animate size={40} />
+ </Permalink>
+
+ <div className='navigation-bar__profile'>
+ <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
+ <strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
+ </Permalink>
+
+ <a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+NavigationBar.propTypes = {
+ account: ImmutablePropTypes.map.isRequired
+};
+
+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
@@ -0,0 +1,105 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { injectIntl, defineMessages } from 'react-intl';
+import IconButton from '../../../components/icon_button';
+
+const messages = defineMessages({
+ public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
+ public_long: { id: 'privacy.public.long', defaultMessage: 'Post to public timelines' },
+ unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
+ unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' },
+ private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
+ private_long: { id: 'privacy.private.long', defaultMessage: 'Post to followers only' },
+ direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
+ direct_long: { id: 'privacy.direct.long', defaultMessage: 'Post to mentioned users only' },
+ change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' }
+});
+
+const iconStyle = {
+ height: null,
+ lineHeight: '27px'
+}
+
+class PrivacyDropdown extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.state = {
+ open: false
+ };
+ this.handleToggle = this.handleToggle.bind(this);
+ this.handleClick = this.handleClick.bind(this);
+ this.onGlobalClick = this.onGlobalClick.bind(this);
+ this.setRef = this.setRef.bind(this);
+ }
+
+ handleToggle () {
+ this.setState({ open: !this.state.open });
+ }
+
+ handleClick (value, e) {
+ e.preventDefault();
+ this.setState({ open: false });
+ this.props.onChange(value);
+ }
+
+ onGlobalClick (e) {
+ if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
+ this.setState({ open: false });
+ }
+ }
+
+ componentDidMount () {
+ window.addEventListener('click', this.onGlobalClick);
+ window.addEventListener('touchstart', this.onGlobalClick);
+ }
+
+ componentWillUnmount () {
+ window.removeEventListener('click', this.onGlobalClick);
+ window.removeEventListener('touchstart', this.onGlobalClick);
+ }
+
+ setRef (c) {
+ this.node = c;
+ }
+
+ render () {
+ const { value, onChange, intl } = this.props;
+ const { open } = this.state;
+
+ const options = [
+ { icon: 'globe', value: 'public', shortText: intl.formatMessage(messages.public_short), longText: intl.formatMessage(messages.public_long) },
+ { icon: 'unlock-alt', value: 'unlisted', shortText: intl.formatMessage(messages.unlisted_short), longText: intl.formatMessage(messages.unlisted_long) },
+ { icon: 'lock', value: 'private', shortText: intl.formatMessage(messages.private_short), longText: intl.formatMessage(messages.private_long) },
+ { icon: 'envelope', value: 'direct', shortText: intl.formatMessage(messages.direct_short), longText: intl.formatMessage(messages.direct_long) }
+ ];
+
+ const valueOption = options.find(item => item.value === value);
+
+ return (
+ <div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}>
+ <div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} active={open} inverted onClick={this.handleToggle} style={iconStyle}/></div>
+ <div className='privacy-dropdown__dropdown'>
+ {options.map(item =>
+ <div role='button' tabIndex='0' key={item.value} onClick={this.handleClick.bind(this, item.value)} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}>
+ <div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div>
+ <div className='privacy-dropdown__option__content'>
+ <strong>{item.shortText}</strong>
+ {item.longText}
+ </div>
+ </div>
+ )}
+ </div>
+ </div>
+ );
+ }
+
+}
+
+PrivacyDropdown.propTypes = {
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+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
@@ -0,0 +1,71 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import Avatar from '../../../components/avatar';
+import IconButton from '../../../components/icon_button';
+import DisplayName from '../../../components/display_name';
+import emojify from '../../../emoji';
+import { defineMessages, injectIntl } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }
+});
+
+class ReplyIndicator extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ this.handleAccountClick = this.handleAccountClick.bind(this);
+ }
+
+ handleClick () {
+ this.props.onCancel();
+ }
+
+ handleAccountClick (e) {
+ if (e.button === 0) {
+ e.preventDefault();
+ this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+ }
+ }
+
+ render () {
+ const { status, intl } = this.props;
+
+ if (!status) {
+ return null;
+ }
+
+ const content = { __html: emojify(status.get('content')) };
+
+ return (
+ <div className='reply-indicator'>
+ <div className='reply-indicator__header'>
+ <div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div>
+
+ <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name'>
+ <div className='reply-indicator__display-avatar'><Avatar size={24} src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} /></div>
+ <DisplayName account={status.get('account')} />
+ </a>
+ </div>
+
+ <div className='reply-indicator__content' dangerouslySetInnerHTML={content} />
+ </div>
+ );
+ }
+
+}
+
+ReplyIndicator.contextTypes = {
+ router: PropTypes.object
+};
+
+ReplyIndicator.propTypes = {
+ status: ImmutablePropTypes.map,
+ onCancel: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+export default injectIntl(ReplyIndicator);
diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js
@@ -0,0 +1,82 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+const messages = defineMessages({
+ placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }
+});
+
+class Search extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleKeyDown = this.handleKeyDown.bind(this);
+ this.handleFocus = this.handleFocus.bind(this);
+ this.handleClear = this.handleClear.bind(this);
+ }
+
+ handleChange (e) {
+ this.props.onChange(e.target.value);
+ }
+
+ handleClear (e) {
+ e.preventDefault();
+
+ if (this.props.value.length > 0 || this.props.submitted) {
+ this.props.onClear();
+ }
+ }
+
+ handleKeyDown (e) {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ this.props.onSubmit();
+ }
+ }
+
+ noop () {
+
+ }
+
+ handleFocus () {
+ this.props.onShow();
+ }
+
+ render () {
+ const { intl, value, submitted } = this.props;
+ const hasValue = value.length > 0 || submitted;
+
+ return (
+ <div className='search'>
+ <input
+ className='search__input'
+ type='text'
+ placeholder={intl.formatMessage(messages.placeholder)}
+ value={value}
+ onChange={this.handleChange}
+ onKeyUp={this.handleKeyDown}
+ onFocus={this.handleFocus}
+ />
+
+ <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
+ <i className={`fa fa-search ${hasValue ? '' : 'active'}`} />
+ <i aria-label={intl.formatMessage(messages.placeholder)} className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} />
+ </div>
+ </div>
+ );
+ }
+
+}
+
+Search.propTypes = {
+ value: PropTypes.string.isRequired,
+ submitted: PropTypes.bool,
+ onChange: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ onClear: PropTypes.func.isRequired,
+ onShow: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+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
@@ -0,0 +1,67 @@
+import React from 'react';
+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';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+class SearchResults extends ImmutablePureComponent {
+
+ render () {
+ const { results } = this.props;
+
+ let accounts, statuses, hashtags;
+ let count = 0;
+
+ if (results.get('accounts') && results.get('accounts').size > 0) {
+ count += results.get('accounts').size;
+ accounts = (
+ <div className='search-results__section'>
+ {results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
+ </div>
+ );
+ }
+
+ if (results.get('statuses') && results.get('statuses').size > 0) {
+ count += results.get('statuses').size;
+ statuses = (
+ <div className='search-results__section'>
+ {results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
+ </div>
+ );
+ }
+
+ if (results.get('hashtags') && results.get('hashtags').size > 0) {
+ count += results.get('hashtags').size;
+ hashtags = (
+ <div className='search-results__section'>
+ {results.get('hashtags').map(hashtag =>
+ <Link className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}>
+ #{hashtag}
+ </Link>
+ )}
+ </div>
+ );
+ }
+
+ return (
+ <div className='search-results'>
+ <div className='search-results__header'>
+ <FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} />
+ </div>
+
+ {accounts}
+ {statuses}
+ {hashtags}
+ </div>
+ );
+ }
+
+}
+
+SearchResults.propTypes = {
+ results: ImmutablePropTypes.map.isRequired
+};
+
+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
@@ -0,0 +1,36 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+class TextIconButton extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick (e) {
+ e.preventDefault();
+ this.props.onClick();
+ }
+
+ render () {
+ const { label, title, active, ariaControls } = this.props;
+
+ return (
+ <button title={title} aria-label={title} className={`text-icon-button ${active ? 'active' : ''}`} aria-expanded={active} onClick={this.handleClick} aria-controls={ariaControls}>
+ {label}
+ </button>
+ );
+ }
+
+}
+
+TextIconButton.propTypes = {
+ label: PropTypes.string.isRequired,
+ title: PropTypes.string,
+ active: PropTypes.bool,
+ onClick: PropTypes.func.isRequired,
+ ariaControls: PropTypes.string
+};
+
+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
@@ -0,0 +1,61 @@
+import React from 'react';
+import IconButton from '../../../components/icon_button';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+ upload: { id: 'upload_button.label', defaultMessage: 'Add media' }
+});
+
+
+const iconStyle = {
+ height: null,
+ lineHeight: '27px'
+}
+
+class UploadButton extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleClick = this.handleClick.bind(this);
+ this.setRef = this.setRef.bind(this);
+ }
+
+ handleChange (e) {
+ if (e.target.files.length > 0) {
+ this.props.onSelectFile(e.target.files);
+ }
+ }
+
+ handleClick () {
+ this.fileElement.click();
+ }
+
+ setRef (c) {
+ this.fileElement = c;
+ }
+
+ render () {
+
+ const { intl, resetFileKey, disabled } = this.props;
+
+ return (
+ <div className='compose-form__upload-button'>
+ <IconButton icon='camera' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle}/>
+ <input key={resetFileKey} ref={this.setRef} type='file' multiple={false} onChange={this.handleChange} disabled={disabled} style={{ display: 'none' }} />
+ </div>
+ );
+ }
+
+}
+
+UploadButton.propTypes = {
+ disabled: PropTypes.bool,
+ onSelectFile: PropTypes.func.isRequired,
+ style: PropTypes.object,
+ resetFileKey: PropTypes.number,
+ intl: PropTypes.object.isRequired
+};
+
+export default injectIntl(UploadButton);
diff --git a/app/javascript/mastodon/features/compose/components/upload_form.js b/app/javascript/mastodon/features/compose/components/upload_form.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import IconButton from '../../../components/icon_button';
+import { defineMessages, injectIntl } from 'react-intl';
+import UploadProgressContainer from '../containers/upload_progress_container';
+import { Motion, spring } from 'react-motion';
+
+const messages = defineMessages({
+ undo: { id: 'upload_form.undo', defaultMessage: 'Undo' }
+});
+
+class UploadForm extends React.PureComponent {
+
+ render () {
+ const { intl, media } = this.props;
+
+ const uploads = media.map(attachment =>
+ <div className='compose-form__upload' key={attachment.get('id')}>
+ <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
+ {({ scale }) =>
+ <div className='compose-form__upload-thumbnail' style={{ transform: `translateZ(0) scale(${scale})`, backgroundImage: `url(${attachment.get('preview_url')})` }}>
+ <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} />
+ </div>
+ }
+ </Motion>
+ </div>
+ );
+
+ return (
+ <div className='compose-form__upload-wrapper'>
+ <UploadProgressContainer />
+ <div className='compose-form__uploads-wrapper'>{uploads}</div>
+ </div>
+ );
+ }
+
+}
+
+UploadForm.propTypes = {
+ media: ImmutablePropTypes.list.isRequired,
+ onRemoveFile: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+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
@@ -0,0 +1,43 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Motion, spring } from 'react-motion';
+import { FormattedMessage } from 'react-intl';
+
+class UploadProgress extends React.PureComponent {
+
+ render () {
+ const { active, progress } = this.props;
+
+ if (!active) {
+ return null;
+ }
+
+ return (
+ <div className='upload-progress'>
+ <div className='upload-progress__icon'>
+ <i className='fa fa-upload' />
+ </div>
+
+ <div className='upload-progress__message'>
+ <FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' />
+
+ <div className='upload-progress__backdrop'>
+ <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
+ {({ width }) =>
+ <div className='upload-progress__tracker' style={{ width: `${width}%` }} />
+ }
+ </Motion>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+UploadProgress.propTypes = {
+ active: PropTypes.bool,
+ progress: PropTypes.number
+};
+
+export default UploadProgress;
diff --git a/app/javascript/mastodon/features/compose/components/warning.js b/app/javascript/mastodon/features/compose/components/warning.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+class Warning extends React.PureComponent {
+
+ constructor (props) {
+ super(props);
+ }
+
+ render () {
+ const { message } = this.props;
+
+ return (
+ <div className='compose-form__warning'>
+ {message}
+ </div>
+ );
+ }
+
+}
+
+Warning.propTypes = {
+ message: PropTypes.node.isRequired
+};
+
+export default Warning;
diff --git a/app/assets/javascripts/components/features/compose/containers/autosuggest_account_container.jsx b/app/javascript/mastodon/features/compose/containers/autosuggest_account_container.js
diff --git a/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx b/app/javascript/mastodon/features/compose/containers/autosuggest_status_container.js
diff --git a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
diff --git a/app/assets/javascripts/components/features/compose/containers/navigation_container.jsx b/app/javascript/mastodon/features/compose/containers/navigation_container.js
diff --git a/app/assets/javascripts/components/features/compose/containers/privacy_dropdown_container.jsx b/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js
diff --git a/app/assets/javascripts/components/features/compose/containers/reply_indicator_container.jsx b/app/javascript/mastodon/features/compose/containers/reply_indicator_container.js
diff --git a/app/assets/javascripts/components/features/compose/containers/search_container.jsx b/app/javascript/mastodon/features/compose/containers/search_container.js
diff --git a/app/assets/javascripts/components/features/compose/containers/search_results_container.jsx b/app/javascript/mastodon/features/compose/containers/search_results_container.js
diff --git a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import TextIconButton from '../components/text_icon_button';
+import { changeComposeSensitivity } from '../../../actions/compose';
+import { Motion, spring } from 'react-motion';
+import { injectIntl, defineMessages } from 'react-intl';
+
+const messages = defineMessages({
+ title: { id: 'compose_form.sensitive', defaultMessage: 'Mark media as sensitive' }
+});
+
+const mapStateToProps = state => ({
+ visible: state.getIn(['compose', 'media_attachments']).size > 0,
+ active: state.getIn(['compose', 'sensitive'])
+});
+
+const mapDispatchToProps = dispatch => ({
+
+ onClick () {
+ dispatch(changeComposeSensitivity());
+ }
+
+});
+
+class SensitiveButton extends React.PureComponent {
+
+ render () {
+ const { visible, active, onClick, intl } = this.props;
+
+ return (
+ <Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(visible ? 1 : 0.87, { stiffness: 200, damping: 3 }) }}>
+ {({ scale }) =>
+ <div style={{ display: visible ? 'block' : 'none', transform: `translateZ(0) scale(${scale})` }}>
+ <TextIconButton onClick={onClick} label='NSFW' title={intl.formatMessage(messages.title)} active={active} />
+ </div>
+ }
+ </Motion>
+ );
+ }
+
+}
+
+SensitiveButton.propTypes = {
+ visible: PropTypes.bool,
+ active: PropTypes.bool,
+ onClick: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton));
diff --git a/app/assets/javascripts/components/features/compose/containers/spoiler_button_container.jsx b/app/javascript/mastodon/features/compose/containers/spoiler_button_container.js
diff --git a/app/assets/javascripts/components/features/compose/containers/upload_button_container.jsx b/app/javascript/mastodon/features/compose/containers/upload_button_container.js
diff --git a/app/assets/javascripts/components/features/compose/containers/upload_form_container.jsx b/app/javascript/mastodon/features/compose/containers/upload_form_container.js
diff --git a/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx b/app/javascript/mastodon/features/compose/containers/upload_progress_container.js
diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.js b/app/javascript/mastodon/features/compose/containers/warning_container.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import Warning from '../components/warning';
+import { createSelector } from 'reselect';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+
+const getMentionedUsernames = createSelector(state => state.getIn(['compose', 'text']), text => text.match(/(?:^|[^\/\w])@([a-z0-9_]+@[a-z0-9\.\-]+)/ig));
+
+const getMentionedDomains = createSelector(getMentionedUsernames, mentionedUsernamesWithDomains => {
+ return mentionedUsernamesWithDomains !== null ? [...new Set(mentionedUsernamesWithDomains.map(item => item.split('@')[2]))] : [];
+});
+
+const mapStateToProps = state => {
+ const mentionedUsernames = getMentionedUsernames(state);
+ const mentionedUsernamesWithDomains = getMentionedDomains(state);
+
+ return {
+ needsLeakWarning: (state.getIn(['compose', 'privacy']) === 'private' || state.getIn(['compose', 'privacy']) === 'direct') && mentionedUsernames !== null,
+ mentionedDomains: mentionedUsernamesWithDomains,
+ needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', state.getIn(['meta', 'me']), 'locked'])
+ };
+};
+
+const WarningWrapper = ({ needsLeakWarning, needsLockWarning, mentionedDomains }) => {
+ if (needsLockWarning) {
+ return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <a href='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></a> }} />} />;
+ } else if (needsLeakWarning) {
+ return (
+ <Warning
+ message={<FormattedMessage
+ id='compose_form.privacy_disclaimer'
+ defaultMessage='Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.'
+ values={{ domains: <strong>{mentionedDomains.join(', ')}</strong>, domainsCount: mentionedDomains.length }}
+ />}
+ />
+ );
+ }
+
+ return null;
+};
+
+WarningWrapper.propTypes = {
+ needsLeakWarning: PropTypes.bool,
+ needsLockWarning: PropTypes.bool,
+ mentionedDomains: PropTypes.array.isRequired,
+};
+
+export default connect(mapStateToProps)(WarningWrapper);
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
@@ -0,0 +1,86 @@
+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';
+import { mountCompose, unmountCompose } from '../../actions/compose';
+import { Link } from 'react-router';
+import { injectIntl, defineMessages } from 'react-intl';
+import SearchContainer from './containers/search_container';
+import { Motion, spring } from 'react-motion';
+import SearchResultsContainer from './containers/search_results_container';
+
+const messages = defineMessages({
+ start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
+ public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
+ community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
+ preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+ logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }
+});
+
+const mapStateToProps = state => ({
+ showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden'])
+});
+
+class Compose extends React.PureComponent {
+
+ componentDidMount () {
+ this.props.dispatch(mountCompose());
+ }
+
+ componentWillUnmount () {
+ this.props.dispatch(unmountCompose());
+ }
+
+ render () {
+ const { withHeader, showSearch, intl } = this.props;
+
+ let header = '';
+
+ if (withHeader) {
+ header = (
+ <div className='drawer__header'>
+ <Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)}><i role="img" aria-label={intl.formatMessage(messages.start)} className='fa fa-fw fa-asterisk' /></Link>
+ <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role="img" aria-label={intl.formatMessage(messages.community)} className='fa fa-fw fa-users' /></Link>
+ <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role="img" aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link>
+ <a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)}><i role="img" aria-label={intl.formatMessage(messages.preferences)} className='fa fa-fw fa-cog' /></a>
+ <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)}><i role="img" aria-label={intl.formatMessage(messages.logout)} className='fa fa-fw fa-sign-out' /></a>
+ </div>
+ );
+ }
+
+ return (
+ <div className='drawer'>
+ {header}
+
+ <SearchContainer />
+
+ <div className='drawer__pager'>
+ <div className='drawer__inner'>
+ <NavigationContainer />
+ <ComposeFormContainer />
+ </div>
+
+ <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
+ {({ x }) =>
+ <div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
+ <SearchResultsContainer />
+ </div>
+ }
+ </Motion>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+Compose.propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ withHeader: PropTypes.bool,
+ showSearch: PropTypes.bool,
+ intl: PropTypes.object.isRequired
+};
+
+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
@@ -0,0 +1,67 @@
+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';
+import StatusList from '../../components/status_list';
+import ColumnBackButtonSlim from '../../components/column_back_button_slim';
+import { defineMessages, injectIntl } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ heading: { id: 'column.favourites', defaultMessage: 'Favourites' }
+});
+
+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 {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleScrollToBottom = this.handleScrollToBottom.bind(this);
+ }
+
+ componentWillMount () {
+ this.props.dispatch(fetchFavouritedStatuses());
+ }
+
+ handleScrollToBottom () {
+ this.props.dispatch(expandFavouritedStatuses());
+ }
+
+ render () {
+ const { statusIds, loaded, intl, me } = this.props;
+
+ if (!loaded) {
+ return (
+ <Column>
+ <LoadingIndicator />
+ </Column>
+ );
+ }
+
+ return (
+ <Column icon='star' heading={intl.formatMessage(messages.heading)}>
+ <ColumnBackButtonSlim />
+ <StatusList {...this.props} scrollKey='favourited_statuses' onScrollToBottom={this.handleScrollToBottom} />
+ </Column>
+ );
+ }
+
+}
+
+Favourites.propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ statusIds: ImmutablePropTypes.list.isRequired,
+ loaded: PropTypes.bool,
+ intl: PropTypes.object.isRequired,
+ me: PropTypes.number.isRequired
+};
+
+export default connect(mapStateToProps)(injectIntl(Favourites));
diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js
@@ -0,0 +1,61 @@
+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 { fetchFavourites } from '../../actions/interactions';
+import { ScrollContainer } from 'react-router-scroll';
+import AccountContainer from '../../containers/account_container';
+import Column from '../ui/components/column';
+import ColumnBackButton from '../../components/column_back_button';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const mapStateToProps = (state, props) => ({
+ accountIds: state.getIn(['user_lists', 'favourited_by', Number(props.params.statusId)])
+});
+
+class Favourites extends ImmutablePureComponent {
+
+ componentWillMount () {
+ this.props.dispatch(fetchFavourites(Number(this.props.params.statusId)));
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
+ this.props.dispatch(fetchFavourites(Number(nextProps.params.statusId)));
+ }
+ }
+
+ render () {
+ const { accountIds } = this.props;
+
+ if (!accountIds) {
+ return (
+ <Column>
+ <LoadingIndicator />
+ </Column>
+ );
+ }
+
+ return (
+ <Column>
+ <ColumnBackButton />
+
+ <ScrollContainer scrollKey='favourites'>
+ <div className='scrollable'>
+ {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
+ </div>
+ </ScrollContainer>
+ </Column>
+ );
+ }
+
+}
+
+Favourites.propTypes = {
+ params: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ accountIds: ImmutablePropTypes.list
+};
+
+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
@@ -0,0 +1,51 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Permalink from '../../../components/permalink';
+import Avatar from '../../../components/avatar';
+import DisplayName from '../../../components/display_name';
+import emojify from '../../../emoji';
+import IconButton from '../../../components/icon_button';
+import { defineMessages, injectIntl } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' },
+ reject: { id: 'follow_request.reject', defaultMessage: 'Reject' }
+});
+
+class AccountAuthorize extends ImmutablePureComponent {
+
+ render () {
+ const { intl, account, onAuthorize, onReject } = this.props;
+ const content = { __html: emojify(account.get('note')) };
+
+ return (
+ <div className='account-authorize__wrapper'>
+ <div className='account-authorize'>
+ <Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name'>
+ <div className='account-authorize__avatar'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={48} /></div>
+ <DisplayName account={account} />
+ </Permalink>
+
+ <div className='account__header__content' dangerouslySetInnerHTML={content} />
+ </div>
+
+ <div className='account--panel'>
+ <div className='account--panel__button'><IconButton title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} /></div>
+ <div className='account--panel__button'><IconButton title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} /></div>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+AccountAuthorize.propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ onAuthorize: PropTypes.func.isRequired,
+ onReject: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+export default injectIntl(AccountAuthorize);
diff --git a/app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx b/app/javascript/mastodon/features/follow_requests/containers/account_authorize_container.js
diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js
@@ -0,0 +1,74 @@
+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 { ScrollContainer } from 'react-router-scroll';
+import Column from '../ui/components/column';
+import ColumnBackButtonSlim from '../../components/column_back_button_slim';
+import AccountAuthorizeContainer from './containers/account_authorize_container';
+import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
+import { defineMessages, injectIntl } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' }
+});
+
+const mapStateToProps = state => ({
+ accountIds: state.getIn(['user_lists', 'follow_requests', 'items'])
+});
+
+class FollowRequests extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleScroll = this.handleScroll.bind(this);
+ }
+
+ componentWillMount () {
+ this.props.dispatch(fetchFollowRequests());
+ }
+
+ handleScroll (e) {
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
+
+ if (scrollTop === scrollHeight - clientHeight) {
+ this.props.dispatch(expandFollowRequests());
+ }
+ }
+
+ render () {
+ const { intl, accountIds } = this.props;
+
+ if (!accountIds) {
+ return (
+ <Column>
+ <LoadingIndicator />
+ </Column>
+ );
+ }
+
+ return (
+ <Column icon='users' heading={intl.formatMessage(messages.heading)}>
+ <ColumnBackButtonSlim />
+ <ScrollContainer scrollKey='follow_requests'>
+ <div className='scrollable' onScroll={this.handleScroll}>
+ {accountIds.map(id =>
+ <AccountAuthorizeContainer key={id} id={id} />
+ )}
+ </div>
+ </ScrollContainer>
+ </Column>
+ );
+ }
+}
+
+FollowRequests.propTypes = {
+ params: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ accountIds: ImmutablePropTypes.list,
+ intl: PropTypes.object.isRequired
+};
+
+export default connect(mapStateToProps)(injectIntl(FollowRequests));
diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js
@@ -0,0 +1,92 @@
+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 {
+ fetchAccount,
+ fetchFollowers,
+ expandFollowers
+} from '../../actions/accounts';
+import { ScrollContainer } from 'react-router-scroll';
+import AccountContainer from '../../containers/account_container';
+import Column from '../ui/components/column';
+import HeaderContainer from '../account_timeline/containers/header_container';
+import LoadMore from '../../components/load_more';
+import ColumnBackButton from '../../components/column_back_button';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const mapStateToProps = (state, props) => ({
+ accountIds: state.getIn(['user_lists', 'followers', Number(props.params.accountId), 'items'])
+});
+
+class Followers extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleScroll = this.handleScroll.bind(this);
+ this.handleLoadMore = this.handleLoadMore.bind(this);
+ }
+
+ componentWillMount () {
+ this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
+ this.props.dispatch(fetchFollowers(Number(this.props.params.accountId)));
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
+ this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
+ this.props.dispatch(fetchFollowers(Number(nextProps.params.accountId)));
+ }
+ }
+
+ handleScroll (e) {
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
+
+ if (scrollTop === scrollHeight - clientHeight) {
+ this.props.dispatch(expandFollowers(Number(this.props.params.accountId)));
+ }
+ }
+
+ handleLoadMore (e) {
+ e.preventDefault();
+ this.props.dispatch(expandFollowers(Number(this.props.params.accountId)));
+ }
+
+ render () {
+ const { accountIds } = this.props;
+
+ if (!accountIds) {
+ return (
+ <Column>
+ <LoadingIndicator />
+ </Column>
+ );
+ }
+
+ return (
+ <Column>
+ <ColumnBackButton />
+
+ <ScrollContainer scrollKey='followers'>
+ <div className='scrollable' onScroll={this.handleScroll}>
+ <div className='followers'>
+ <HeaderContainer accountId={this.props.params.accountId} />
+ {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
+ <LoadMore onClick={this.handleLoadMore} />
+ </div>
+ </div>
+ </ScrollContainer>
+ </Column>
+ );
+ }
+
+}
+
+Followers.propTypes = {
+ params: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ accountIds: ImmutablePropTypes.list
+};
+
+export default connect(mapStateToProps)(Followers);
diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js
@@ -0,0 +1,92 @@
+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 {
+ fetchAccount,
+ fetchFollowing,
+ expandFollowing
+} from '../../actions/accounts';
+import { ScrollContainer } from 'react-router-scroll';
+import AccountContainer from '../../containers/account_container';
+import Column from '../ui/components/column';
+import HeaderContainer from '../account_timeline/containers/header_container';
+import LoadMore from '../../components/load_more';
+import ColumnBackButton from '../../components/column_back_button';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const mapStateToProps = (state, props) => ({
+ accountIds: state.getIn(['user_lists', 'following', Number(props.params.accountId), 'items'])
+});
+
+class Following extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleScroll = this.handleScroll.bind(this);
+ this.handleLoadMore = this.handleLoadMore.bind(this);
+ }
+
+ componentWillMount () {
+ this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
+ this.props.dispatch(fetchFollowing(Number(this.props.params.accountId)));
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
+ this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
+ this.props.dispatch(fetchFollowing(Number(nextProps.params.accountId)));
+ }
+ }
+
+ handleScroll (e) {
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
+
+ if (scrollTop === scrollHeight - clientHeight) {
+ this.props.dispatch(expandFollowing(Number(this.props.params.accountId)));
+ }
+ }
+
+ handleLoadMore (e) {
+ e.preventDefault();
+ this.props.dispatch(expandFollowing(Number(this.props.params.accountId)));
+ }
+
+ render () {
+ const { accountIds } = this.props;
+
+ if (!accountIds) {
+ return (
+ <Column>
+ <LoadingIndicator />
+ </Column>
+ );
+ }
+
+ return (
+ <Column>
+ <ColumnBackButton />
+
+ <ScrollContainer scrollKey='following'>
+ <div className='scrollable' onScroll={this.handleScroll}>
+ <div className='following'>
+ <HeaderContainer accountId={this.props.params.accountId} />
+ {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
+ <LoadMore onClick={this.handleLoadMore} />
+ </div>
+ </div>
+ </ScrollContainer>
+ </Column>
+ );
+ }
+
+}
+
+Following.propTypes = {
+ params: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ accountIds: ImmutablePropTypes.list
+};
+
+export default connect(mapStateToProps)(Following);
diff --git a/app/javascript/mastodon/features/generic_not_found/index.js b/app/javascript/mastodon/features/generic_not_found/index.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import Column from '../ui/components/column';
+import MissingIndicator from '../../components/missing_indicator';
+
+const GenericNotFound = () => (
+ <Column>
+ <MissingIndicator />
+ </Column>
+);
+
+export default GenericNotFound;
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
@@ -0,0 +1,73 @@
+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';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
+ public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
+ navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation'},
+ settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings'},
+ community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
+ preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+ follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
+ sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
+ favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
+ blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
+ mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
+ info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }
+});
+
+const mapStateToProps = state => ({
+ me: state.getIn(['accounts', state.getIn(['meta', 'me'])])
+});
+
+class GettingStarted extends ImmutablePureComponent {
+
+ render () {
+ const { intl, me } = this.props;
+
+ let followRequests = '';
+
+ if (me.get('locked')) {
+ followRequests = <ColumnLink icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />;
+ }
+
+ return (
+ <Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile={true}>
+ <div className='getting-started__wrapper'>
+ <ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)}/>
+ <ColumnLink icon='users' hideOnMobile={true} text={intl.formatMessage(messages.community_timeline)} to='/timelines/public/local' />
+ <ColumnLink icon='globe' hideOnMobile={true} text={intl.formatMessage(messages.public_timeline)} to='/timelines/public' />
+ <ColumnLink icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />
+ {followRequests}
+ <ColumnLink icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />
+ <ColumnLink icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />
+ <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)}/>
+ <ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
+ <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
+ <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
+ </div>
+
+ <div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}>
+ <div className='static-content getting-started'>
+ <p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p>
+ </div>
+ </div>
+ </Column>
+ );
+ }
+}
+
+GettingStarted.propTypes = {
+ intl: PropTypes.object.isRequired,
+ me: ImmutablePropTypes.map.isRequired
+};
+
+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
@@ -0,0 +1,90 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import StatusListContainer from '../ui/containers/status_list_container';
+import Column from '../ui/components/column';
+import {
+ refreshTimeline,
+ updateTimeline,
+ deleteFromTimelines
+} from '../../actions/timelines';
+import ColumnBackButtonSlim from '../../components/column_back_button_slim';
+import { FormattedMessage } from 'react-intl';
+import createStream from '../../stream';
+
+const mapStateToProps = state => ({
+ hasUnread: state.getIn(['timelines', 'tag', 'unread']) > 0,
+ streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
+ accessToken: state.getIn(['meta', 'access_token'])
+});
+
+class HashtagTimeline extends React.PureComponent {
+
+ _subscribe (dispatch, id) {
+ const { streamingAPIBaseURL, accessToken } = this.props;
+
+ this.subscription = createStream(streamingAPIBaseURL, accessToken, `hashtag&tag=${id}`, {
+
+ received (data) {
+ switch(data.event) {
+ case 'update':
+ dispatch(updateTimeline('tag', JSON.parse(data.payload)));
+ break;
+ case 'delete':
+ dispatch(deleteFromTimelines(data.payload));
+ break;
+ }
+ }
+
+ });
+ }
+
+ _unsubscribe () {
+ if (typeof this.subscription !== 'undefined') {
+ this.subscription.close();
+ this.subscription = null;
+ }
+ }
+
+ componentDidMount () {
+ const { dispatch } = this.props;
+ const { id } = this.props.params;
+
+ dispatch(refreshTimeline('tag', id));
+ this._subscribe(dispatch, id);
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.params.id !== this.props.params.id) {
+ this.props.dispatch(refreshTimeline('tag', nextProps.params.id));
+ this._unsubscribe();
+ this._subscribe(this.props.dispatch, nextProps.params.id);
+ }
+ }
+
+ componentWillUnmount () {
+ this._unsubscribe();
+ }
+
+ render () {
+ const { id, hasUnread } = this.props.params;
+
+ return (
+ <Column icon='hashtag' active={hasUnread} heading={id}>
+ <ColumnBackButtonSlim />
+ <StatusListContainer scrollKey='hashtag_timeline' type='tag' id={id} emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} />
+ </Column>
+ );
+ }
+
+}
+
+HashtagTimeline.propTypes = {
+ params: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ streamingAPIBaseURL: PropTypes.string.isRequired,
+ accessToken: PropTypes.string.isRequired,
+ hasUnread: PropTypes.bool
+};
+
+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
@@ -0,0 +1,51 @@
+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 './setting_text';
+
+const messages = defineMessages({
+ filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
+ settings: { id: 'home.settings', defaultMessage: 'Column settings' }
+});
+
+class ColumnSettings extends React.PureComponent {
+
+ render () {
+ const { settings, onChange, onSave, intl } = this.props;
+
+ return (
+ <ColumnCollapsable icon='sliders' title={intl.formatMessage(messages.settings)} fullHeight={209} onCollapse={onSave}>
+ <div className='column-settings__outer'>
+ <span className='column-settings__section'><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span>
+
+ <div className='column-settings__row'>
+ <SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_reblogs' defaultMessage='Show boosts' />} />
+ </div>
+
+ <div className='column-settings__row'>
+ <SettingToggle settings={settings} settingKey={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_replies' defaultMessage='Show replies' />} />
+ </div>
+
+ <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
+
+ <div className='column-settings__row'>
+ <SettingText settings={settings} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
+ </div>
+ </div>
+ </ColumnCollapsable>
+ );
+ }
+
+}
+
+ColumnSettings.propTypes = {
+ settings: ImmutablePropTypes.map.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onSave: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+}
+
+export default injectIntl(ColumnSettings);
diff --git a/app/javascript/mastodon/features/home_timeline/components/setting_text.js b/app/javascript/mastodon/features/home_timeline/components/setting_text.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+
+class SettingText extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleChange = this.handleChange.bind(this);
+ }
+
+ handleChange (e) {
+ this.props.onChange(this.props.settingKey, e.target.value)
+ }
+
+ render () {
+ const { settings, settingKey, label } = this.props;
+
+ return (
+ <input
+ className='setting-text'
+ value={settings.getIn(settingKey)}
+ onChange={this.handleChange}
+ placeholder={label}
+ />
+ );
+ }
+
+}
+
+SettingText.propTypes = {
+ settings: ImmutablePropTypes.map.isRequired,
+ settingKey: PropTypes.array.isRequired,
+ label: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired
+};
+
+export default SettingText;
diff --git a/app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx b/app/javascript/mastodon/features/home_timeline/containers/column_settings_container.js
diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import StatusListContainer from '../ui/containers/status_list_container';
+import Column from '../ui/components/column';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import ColumnSettingsContainer from './containers/column_settings_container';
+import { Link } from 'react-router';
+
+const messages = defineMessages({
+ title: { id: 'column.home', defaultMessage: 'Home' }
+});
+
+const mapStateToProps = state => ({
+ hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0
+});
+
+class HomeTimeline extends React.PureComponent {
+
+ render () {
+ const { intl, hasUnread } = this.props;
+
+ return (
+ <Column icon='home' active={hasUnread} heading={intl.formatMessage(messages.title)}>
+ <ColumnSettingsContainer />
+ <StatusListContainer {...this.props} scrollKey='home_timeline' type='home' emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage="You aren't following anyone yet. Visit {public} or use search to get started and meet other users." values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} />
+ </Column>
+ );
+ }
+
+}
+
+HomeTimeline.propTypes = {
+ intl: PropTypes.object.isRequired,
+ hasUnread: PropTypes.bool
+};
+
+export default connect(mapStateToProps)(injectIntl(HomeTimeline));
diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.js
@@ -0,0 +1,75 @@
+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 { ScrollContainer } from 'react-router-scroll';
+import Column from '../ui/components/column';
+import ColumnBackButtonSlim from '../../components/column_back_button_slim';
+import AccountContainer from '../../containers/account_container';
+import { fetchMutes, expandMutes } from '../../actions/mutes';
+import { defineMessages, injectIntl } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ heading: { id: 'column.mutes', defaultMessage: 'Muted users' }
+});
+
+const mapStateToProps = state => ({
+ accountIds: state.getIn(['user_lists', 'mutes', 'items'])
+});
+
+class Mutes extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleScroll = this.handleScroll.bind(this);
+ }
+
+ componentWillMount () {
+ this.props.dispatch(fetchMutes());
+ }
+
+ handleScroll (e) {
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
+
+ if (scrollTop === scrollHeight - clientHeight) {
+ this.props.dispatch(expandMutes());
+ }
+ }
+
+ render () {
+ const { intl, accountIds } = this.props;
+
+ if (!accountIds) {
+ return (
+ <Column>
+ <LoadingIndicator />
+ </Column>
+ );
+ }
+
+ return (
+ <Column icon='volume-off' heading={intl.formatMessage(messages.heading)}>
+ <ColumnBackButtonSlim />
+ <ScrollContainer scrollKey='mutes'>
+ <div className='scrollable mutes' onScroll={this.handleScroll}>
+ {accountIds.map(id =>
+ <AccountContainer key={id} id={id} />
+ )}
+ </div>
+ </ScrollContainer>
+ </Column>
+ );
+ }
+
+}
+
+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
@@ -0,0 +1,27 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+ clear: { id: 'notifications.clear', defaultMessage: 'Clear notifications' }
+});
+
+class ClearColumnButton extends React.Component {
+
+ render () {
+ const { intl } = this.props;
+
+ return (
+ <div role='button' title={intl.formatMessage(messages.clear)} className='column-icon column-icon-clear' tabIndex='0' onClick={this.props.onClick}>
+ <i className='fa fa-eraser' />
+ </div>
+ );
+ }
+}
+
+ClearColumnButton.propTypes = {
+ onClick: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+export default injectIntl(ClearColumnButton);
diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js
@@ -0,0 +1,71 @@
+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 './setting_toggle';
+
+const messages = defineMessages({
+ settings: { id: 'notifications.settings', defaultMessage: 'Column settings' }
+});
+
+class ColumnSettings extends React.PureComponent {
+
+ render () {
+ const { settings, intl, onChange, onSave } = this.props;
+
+ const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
+ const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
+ const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
+
+ return (
+ <ColumnCollapsable icon='sliders' title={intl.formatMessage(messages.settings)} fullHeight={616} onCollapse={onSave}>
+ <div className='column-settings__outer'>
+ <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
+
+ <div className='column-settings__row'>
+ <SettingToggle settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
+ <SettingToggle settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
+ <SettingToggle settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
+ </div>
+
+ <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
+
+ <div className='column-settings__row'>
+ <SettingToggle settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
+ <SettingToggle settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
+ <SettingToggle settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
+ </div>
+
+ <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
+
+ <div className='column-settings__row'>
+ <SettingToggle settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
+ <SettingToggle settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
+ <SettingToggle settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
+ </div>
+
+ <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
+
+ <div className='column-settings__row'>
+ <SettingToggle settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
+ <SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
+ <SettingToggle settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
+ </div>
+ </div>
+ </ColumnCollapsable>
+ );
+ }
+
+}
+
+ColumnSettings.propTypes = {
+ settings: ImmutablePropTypes.map.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onSave: PropTypes.func.isRequired,
+ intl: PropTypes.shape({
+ formatMessage: PropTypes.func.isRequired
+ }).isRequired
+};
+
+export default injectIntl(ColumnSettings);
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -0,0 +1,90 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import StatusContainer from '../../../containers/status_container';
+import AccountContainer from '../../../containers/account_container';
+import { FormattedMessage } from 'react-intl';
+import Permalink from '../../../components/permalink';
+import emojify from '../../../emoji';
+import escapeTextContentForBrowser from 'escape-html';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+class Notification extends ImmutablePureComponent {
+
+ renderFollow (account, link) {
+ return (
+ <div className='notification notification-follow'>
+ <div className='notification__message'>
+ <div className='notification__favourite-icon-wrapper'>
+ <i className='fa fa-fw fa-user-plus' />
+ </div>
+
+ <FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
+ </div>
+
+ <AccountContainer id={account.get('id')} withNote={false} />
+ </div>
+ );
+ }
+
+ renderMention (notification) {
+ return <StatusContainer id={notification.get('status')} />;
+ }
+
+ renderFavourite (notification, link) {
+ return (
+ <div className='notification notification-favourite'>
+ <div className='notification__message'>
+ <div className='notification__favourite-icon-wrapper'>
+ <i className='fa fa-fw fa-star star-icon'/>
+ </div>
+
+ <FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
+ </div>
+
+ <StatusContainer id={notification.get('status')} muted={true} />
+ </div>
+ );
+ }
+
+ renderReblog (notification, link) {
+ return (
+ <div className='notification notification-reblog'>
+ <div className='notification__message'>
+ <div className='notification__favourite-icon-wrapper'>
+ <i className='fa fa-fw fa-retweet' />
+ </div>
+
+ <FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
+ </div>
+
+ <StatusContainer id={notification.get('status')} muted={true} />
+ </div>
+ );
+ }
+
+ render () { // eslint-disable-line consistent-return
+ const { notification } = this.props;
+ const account = notification.get('account');
+ const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username');
+ const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
+ const link = <Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHTML} />;
+
+ switch(notification.get('type')) {
+ case 'follow':
+ return this.renderFollow(account, link);
+ case 'mention':
+ return this.renderMention(notification);
+ case 'favourite':
+ return this.renderFavourite(notification, link);
+ case 'reblog':
+ return this.renderReblog(notification, link);
+ }
+ }
+
+}
+
+Notification.propTypes = {
+ notification: ImmutablePropTypes.map.isRequired
+};
+
+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
@@ -0,0 +1,21 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Toggle from 'react-toggle';
+
+const SettingToggle = ({ settings, settingKey, label, onChange, htmlFor = '' }) => (
+ <label htmlFor={htmlFor} className='setting-toggle__label'>
+ <Toggle checked={settings.getIn(settingKey)} onChange={(e) => onChange(settingKey, e.target.checked)} />
+ <span className='setting-toggle'>{label}</span>
+ </label>
+);
+
+SettingToggle.propTypes = {
+ settings: ImmutablePropTypes.map.isRequired,
+ settingKey: PropTypes.array.isRequired,
+ label: PropTypes.node.isRequired,
+ onChange: PropTypes.func.isRequired,
+ htmlFor: PropTypes.string
+};
+
+export default SettingToggle;
diff --git a/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
diff --git a/app/assets/javascripts/components/features/notifications/containers/notification_container.jsx b/app/javascript/mastodon/features/notifications/containers/notification_container.js
diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js
@@ -0,0 +1,143 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Column from '../ui/components/column';
+import { expandNotifications, clearNotifications, scrollTopNotifications } from '../../actions/notifications';
+import NotificationContainer from './containers/notification_container';
+import { ScrollContainer } from 'react-router-scroll';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import ColumnSettingsContainer from './containers/column_settings_container';
+import { createSelector } from 'reselect';
+import Immutable from 'immutable';
+import LoadMore from '../../components/load_more';
+import ClearColumnButton from './components/clear_column_button';
+import { openModal } from '../../actions/modal';
+
+const messages = defineMessages({
+ title: { id: 'column.notifications', defaultMessage: 'Notifications' },
+ clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' },
+ clearConfirm: { id: 'notifications.clear', defaultMessage: 'Clear notifications' }
+});
+
+const getNotifications = createSelector([
+ state => Immutable.List(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
+ state => state.getIn(['notifications', 'items'])
+], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type'))));
+
+const mapStateToProps = state => ({
+ notifications: getNotifications(state),
+ isLoading: state.getIn(['notifications', 'isLoading'], true),
+ isUnread: state.getIn(['notifications', 'unread']) > 0
+});
+
+class Notifications extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleScroll = this.handleScroll.bind(this);
+ this.handleLoadMore = this.handleLoadMore.bind(this);
+ this.handleClear = this.handleClear.bind(this);
+ this.setRef = this.setRef.bind(this);
+ }
+
+ handleScroll (e) {
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
+ const offset = scrollHeight - scrollTop - clientHeight;
+ this._oldScrollPosition = scrollHeight - scrollTop;
+
+ if (250 > offset && !this.props.isLoading) {
+ this.props.dispatch(expandNotifications());
+ } else if (scrollTop < 100) {
+ this.props.dispatch(scrollTopNotifications(true));
+ } else {
+ this.props.dispatch(scrollTopNotifications(false));
+ }
+ }
+
+ componentDidUpdate (prevProps) {
+ if (this.node.scrollTop > 0 && (prevProps.notifications.size < this.props.notifications.size && prevProps.notifications.first() !== this.props.notifications.first() && !!this._oldScrollPosition)) {
+ this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
+ }
+ }
+
+ handleLoadMore (e) {
+ e.preventDefault();
+ this.props.dispatch(expandNotifications());
+ }
+
+ handleClear () {
+ const { dispatch, intl } = this.props;
+
+ dispatch(openModal('CONFIRM', {
+ message: intl.formatMessage(messages.clearMessage),
+ confirm: intl.formatMessage(messages.clearConfirm),
+ onConfirm: () => dispatch(clearNotifications())
+ }));
+ }
+
+ setRef (c) {
+ this.node = c;
+ }
+
+ render () {
+ const { intl, notifications, shouldUpdateScroll, isLoading, isUnread } = this.props;
+
+ let loadMore = '';
+ let scrollableArea = '';
+ let unread = '';
+
+ if (!isLoading && notifications.size > 0) {
+ loadMore = <LoadMore onClick={this.handleLoadMore} />;
+ }
+
+ if (isUnread) {
+ unread = <div className='notifications__unread-indicator' />;
+ }
+
+ if (isLoading || notifications.size > 0) {
+ scrollableArea = (
+ <div className='scrollable' onScroll={this.handleScroll} ref={this.setRef}>
+ {unread}
+
+ <div>
+ {notifications.map(item => <NotificationContainer key={item.get('id')} notification={item} accountId={item.get('account')} />)}
+ {loadMore}
+ </div>
+ </div>
+ );
+ } else {
+ scrollableArea = (
+ <div className='empty-column-indicator' ref={this.setRef}>
+ <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />
+ </div>
+ );
+ }
+
+ return (
+ <Column icon='bell' active={isUnread} heading={intl.formatMessage(messages.title)}>
+ <ColumnSettingsContainer />
+ <ClearColumnButton onClick={this.handleClear} />
+ <ScrollContainer scrollKey='notifications' shouldUpdateScroll={shouldUpdateScroll}>
+ {scrollableArea}
+ </ScrollContainer>
+ </Column>
+ );
+ }
+
+}
+
+Notifications.propTypes = {
+ notifications: ImmutablePropTypes.list.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ shouldUpdateScroll: PropTypes.func,
+ intl: PropTypes.object.isRequired,
+ isLoading: PropTypes.bool,
+ isUnread: PropTypes.bool
+};
+
+Notifications.defaultProps = {
+ trackScroll: true
+};
+
+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
@@ -0,0 +1,96 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import StatusListContainer from '../ui/containers/status_list_container';
+import Column from '../ui/components/column';
+import {
+ refreshTimeline,
+ updateTimeline,
+ deleteFromTimelines,
+ connectTimeline,
+ disconnectTimeline
+} from '../../actions/timelines';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import ColumnBackButtonSlim from '../../components/column_back_button_slim';
+import createStream from '../../stream';
+
+const messages = defineMessages({
+ title: { id: 'column.public', defaultMessage: 'Federated timeline' }
+});
+
+const mapStateToProps = state => ({
+ hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0,
+ streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
+ accessToken: state.getIn(['meta', 'access_token'])
+});
+
+let subscription;
+
+class PublicTimeline extends React.PureComponent {
+
+ componentDidMount () {
+ const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
+
+ dispatch(refreshTimeline('public'));
+
+ if (typeof subscription !== 'undefined') {
+ return;
+ }
+
+ subscription = createStream(streamingAPIBaseURL, accessToken, 'public', {
+
+ connected () {
+ dispatch(connectTimeline('public'));
+ },
+
+ reconnected () {
+ dispatch(connectTimeline('public'));
+ },
+
+ disconnected () {
+ dispatch(disconnectTimeline('public'));
+ },
+
+ received (data) {
+ switch(data.event) {
+ case 'update':
+ dispatch(updateTimeline('public', JSON.parse(data.payload)));
+ break;
+ case 'delete':
+ dispatch(deleteFromTimelines(data.payload));
+ break;
+ }
+ }
+
+ });
+ }
+
+ componentWillUnmount () {
+ // if (typeof subscription !== 'undefined') {
+ // subscription.close();
+ // subscription = null;
+ // }
+ }
+
+ render () {
+ const { intl, hasUnread } = this.props;
+
+ return (
+ <Column icon='globe' active={hasUnread} heading={intl.formatMessage(messages.title)}>
+ <ColumnBackButtonSlim />
+ <StatusListContainer {...this.props} type='public' scrollKey='public_timeline' emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />} />
+ </Column>
+ );
+ }
+
+}
+
+PublicTimeline.propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ streamingAPIBaseURL: PropTypes.string.isRequired,
+ accessToken: PropTypes.string.isRequired,
+ hasUnread: PropTypes.bool
+};
+
+export default connect(mapStateToProps)(injectIntl(PublicTimeline));
diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js
@@ -0,0 +1,61 @@
+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 { fetchReblogs } from '../../actions/interactions';
+import { ScrollContainer } from 'react-router-scroll';
+import AccountContainer from '../../containers/account_container';
+import Column from '../ui/components/column';
+import ColumnBackButton from '../../components/column_back_button';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const mapStateToProps = (state, props) => ({
+ accountIds: state.getIn(['user_lists', 'reblogged_by', Number(props.params.statusId)])
+});
+
+class Reblogs extends ImmutablePureComponent {
+
+ componentWillMount () {
+ this.props.dispatch(fetchReblogs(Number(this.props.params.statusId)));
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
+ this.props.dispatch(fetchReblogs(Number(nextProps.params.statusId)));
+ }
+ }
+
+ render () {
+ const { accountIds } = this.props;
+
+ if (!accountIds) {
+ return (
+ <Column>
+ <LoadingIndicator />
+ </Column>
+ );
+ }
+
+ return (
+ <Column>
+ <ColumnBackButton />
+
+ <ScrollContainer scrollKey='reblogs'>
+ <div className='scrollable reblogs'>
+ {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
+ </div>
+ </ScrollContainer>
+ </Column>
+ );
+ }
+
+}
+
+Reblogs.propTypes = {
+ params: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ accountIds: ImmutablePropTypes.list
+};
+
+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
@@ -0,0 +1,40 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import emojify from '../../../emoji';
+import Toggle from 'react-toggle';
+
+class StatusCheckBox extends React.PureComponent {
+
+ render () {
+ const { status, checked, onToggle, disabled } = this.props;
+ const content = { __html: emojify(status.get('content')) };
+
+ if (status.get('reblog')) {
+ return null;
+ }
+
+ return (
+ <div className='status-check-box'>
+ <div
+ className='status__content'
+ dangerouslySetInnerHTML={content}
+ />
+
+ <div className='status-check-box-toggle'>
+ <Toggle checked={checked} onChange={onToggle} disabled={disabled} />
+ </div>
+ </div>
+ );
+ }
+
+}
+
+StatusCheckBox.propTypes = {
+ status: ImmutablePropTypes.map.isRequired,
+ checked: PropTypes.bool,
+ onToggle: PropTypes.func.isRequired,
+ disabled: PropTypes.bool
+};
+
+export default StatusCheckBox;
diff --git a/app/assets/javascripts/components/features/report/containers/status_check_box_container.jsx b/app/javascript/mastodon/features/report/containers/status_check_box_container.js
diff --git a/app/javascript/mastodon/features/report/index.js b/app/javascript/mastodon/features/report/index.js
@@ -0,0 +1,131 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { cancelReport, changeReportComment, submitReport } from '../../actions/reports';
+import { fetchAccountTimeline } from '../../actions/accounts';
+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 { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
+import StatusCheckBox from './containers/status_check_box_container';
+import Immutable from 'immutable';
+import ColumnBackButtonSlim from '../../components/column_back_button_slim';
+
+const messages = defineMessages({
+ heading: { id: 'report.heading', defaultMessage: 'New report' },
+ placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
+ submit: { id: 'report.submit', defaultMessage: 'Submit' }
+});
+
+const makeMapStateToProps = () => {
+ const getAccount = makeGetAccount();
+
+ const mapStateToProps = state => {
+ const accountId = state.getIn(['reports', 'new', 'account_id']);
+
+ return {
+ isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
+ account: getAccount(state, accountId),
+ comment: state.getIn(['reports', 'new', 'comment']),
+ statusIds: Immutable.OrderedSet(state.getIn(['timelines', 'accounts_timelines', accountId, 'items'])).union(state.getIn(['reports', 'new', 'status_ids']))
+ };
+ };
+
+ return mapStateToProps;
+};
+
+class Report extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleCommentChange = this.handleCommentChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ }
+
+ componentWillMount () {
+ if (!this.props.account) {
+ this.context.router.replace('/');
+ }
+ }
+
+ componentDidMount () {
+ if (!this.props.account) {
+ return;
+ }
+
+ this.props.dispatch(fetchAccountTimeline(this.props.account.get('id')));
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (this.props.account !== nextProps.account && nextProps.account) {
+ this.props.dispatch(fetchAccountTimeline(nextProps.account.get('id')));
+ }
+ }
+
+ handleCommentChange (e) {
+ this.props.dispatch(changeReportComment(e.target.value));
+ }
+
+ handleSubmit () {
+ this.props.dispatch(submitReport());
+ this.context.router.replace('/');
+ }
+
+ render () {
+ const { account, comment, intl, statusIds, isSubmitting } = this.props;
+
+ if (!account) {
+ return null;
+ }
+
+ return (
+ <Column heading={intl.formatMessage(messages.heading)} icon='flag'>
+ <ColumnBackButtonSlim />
+
+ <div className='report scrollable'>
+ <div className='report__target'>
+ <FormattedMessage id='report.target' defaultMessage='Reporting' />
+ <strong>{account.get('acct')}</strong>
+ </div>
+
+ <div className='scrollable report__statuses'>
+ <div>
+ {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
+ </div>
+ </div>
+
+ <div className='report__textarea-wrapper'>
+ <textarea
+ className='report__textarea'
+ placeholder={intl.formatMessage(messages.placeholder)}
+ value={comment}
+ onChange={this.handleCommentChange}
+ disabled={isSubmitting}
+ />
+
+ <div className='report__submit'>
+ <div className='report__submit-button'><Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} /></div>
+ </div>
+ </div>
+ </div>
+ </Column>
+ );
+ }
+
+}
+
+Report.contextTypes = {
+ router: PropTypes.object
+};
+
+Report.propTypes = {
+ isSubmitting: PropTypes.bool,
+ account: ImmutablePropTypes.map,
+ statusIds: ImmutablePropTypes.orderedSet.isRequired,
+ comment: PropTypes.string.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+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
@@ -0,0 +1,102 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import IconButton from '../../../components/icon_button';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import DropdownMenu from '../../../components/dropdown_menu';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+ delete: { id: 'status.delete', defaultMessage: 'Delete' },
+ mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
+ reply: { id: 'status.reply', defaultMessage: 'Reply' },
+ reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
+ cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
+ favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
+ report: { id: 'status.report', defaultMessage: 'Report @{name}' }
+});
+
+class ActionBar extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleReplyClick = this.handleReplyClick.bind(this);
+ this.handleReblogClick = this.handleReblogClick.bind(this);
+ this.handleFavouriteClick = this.handleFavouriteClick.bind(this);
+ this.handleDeleteClick = this.handleDeleteClick.bind(this);
+ this.handleMentionClick = this.handleMentionClick.bind(this);
+ this.handleReport = this.handleReport.bind(this);
+ }
+
+ handleReplyClick () {
+ this.props.onReply(this.props.status);
+ }
+
+ handleReblogClick (e) {
+ this.props.onReblog(this.props.status, e);
+ }
+
+ handleFavouriteClick () {
+ this.props.onFavourite(this.props.status);
+ }
+
+ handleDeleteClick () {
+ this.props.onDelete(this.props.status);
+ }
+
+ handleMentionClick () {
+ this.props.onMention(this.props.status.get('account'), this.context.router);
+ }
+
+ handleReport () {
+ this.props.onReport(this.props.status);
+ this.context.router.push('/report');
+ }
+
+ render () {
+ const { status, me, intl } = this.props;
+
+ let menu = [];
+
+ if (me === status.getIn(['account', 'id'])) {
+ menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
+ } else {
+ menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
+ menu.push(null);
+ menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
+ }
+
+ let reblogIcon = 'retweet';
+ if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
+ else if (status.get('visibility') === 'private') reblogIcon = 'lock';
+
+ let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private');
+
+ return (
+ <div className='detailed-status__action-bar'>
+ <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_id', null) === null ? 'reply' : 'reply-all'} onClick={this.handleReplyClick} /></div>
+ <div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
+ <div className='detailed-status__button'><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
+ <div className='detailed-status__button'><DropdownMenu size={18} icon='ellipsis-h' items={menu} direction="left" ariaLabel="More" /></div>
+ </div>
+ );
+ }
+
+}
+
+ActionBar.contextTypes = {
+ router: PropTypes.object
+};
+
+ActionBar.propTypes = {
+ status: ImmutablePropTypes.map.isRequired,
+ onReply: PropTypes.func.isRequired,
+ onReblog: PropTypes.func.isRequired,
+ onFavourite: PropTypes.func.isRequired,
+ onDelete: PropTypes.func.isRequired,
+ onMention: PropTypes.func.isRequired,
+ onReport: PropTypes.func,
+ me: PropTypes.number.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+export default injectIntl(ActionBar);
diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js
@@ -0,0 +1,96 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+
+const hostStyle = {
+ display: 'block',
+ marginTop: '5px',
+ fontSize: '13px'
+};
+
+const getHostname = url => {
+ const parser = document.createElement('a');
+ parser.href = url;
+ return parser.hostname;
+};
+
+class Card extends React.PureComponent {
+
+ renderLink () {
+ const { card } = this.props;
+
+ let image = '';
+ let provider = card.get('provider_name');
+
+ if (card.get('image')) {
+ image = (
+ <div className='status-card__image'>
+ <img src={card.get('image')} alt={card.get('title')} className='status-card__image-image' />
+ </div>
+ );
+ }
+
+ if (provider.length < 1) {
+ provider = getHostname(card.get('url'))
+ }
+
+ return (
+ <a href={card.get('url')} className='status-card' target='_blank' rel='noopener'>
+ {image}
+
+ <div className='status-card__content'>
+ <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>
+ <p className='status-card__description'>{(card.get('description') || '').substring(0, 50)}</p>
+ <span className='status-card__host' style={hostStyle}>{provider}</span>
+ </div>
+ </a>
+ );
+ }
+
+ renderPhoto () {
+ const { card } = this.props;
+
+ return (
+ <a href={card.get('url')} className='status-card-photo' target='_blank' rel='noopener'>
+ <img src={card.get('url')} alt={card.get('title')} width={card.get('width')} height={card.get('height')} />
+ </a>
+ );
+ }
+
+ renderVideo () {
+ const { card } = this.props;
+ const content = { __html: card.get('html') };
+
+ return (
+ <div
+ className='status-card-video'
+ dangerouslySetInnerHTML={content}
+ />
+ );
+ }
+
+ render () {
+ const { card } = this.props;
+
+ if (card === null) {
+ return null;
+ }
+
+ switch(card.get('type')) {
+ case 'link':
+ return this.renderLink();
+ case 'photo':
+ return this.renderPhoto();
+ case 'video':
+ return this.renderVideo();
+ case 'rich':
+ default:
+ return null;
+ }
+ }
+}
+
+Card.propTypes = {
+ card: ImmutablePropTypes.map
+};
+
+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
@@ -0,0 +1,96 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Avatar from '../../../components/avatar';
+import DisplayName from '../../../components/display_name';
+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';
+import { FormattedDate, FormattedNumber } from 'react-intl';
+import CardContainer from '../containers/card_container';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+class DetailedStatus extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleAccountClick = this.handleAccountClick.bind(this);
+ }
+
+ handleAccountClick (e) {
+ if (e.button === 0) {
+ e.preventDefault();
+ this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+ }
+
+ e.stopPropagation();
+ }
+
+ render () {
+ const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
+
+ let media = '';
+ let applicationLink = '';
+
+ if (status.get('media_attachments').size > 0) {
+ if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
+ media = <AttachmentList media={status.get('media_attachments')} />;
+ } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
+ media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} onOpenVideo={this.props.onOpenVideo} autoplay />;
+ } else {
+ media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
+ }
+ } else if (status.get('spoiler_text').length === 0) {
+ media = <CardContainer statusId={status.get('id')} />;
+ }
+
+ if (status.get('application')) {
+ applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>;
+ }
+
+ return (
+ <div className='detailed-status'>
+ <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
+ <div className='detailed-status__display-avatar'><Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} /></div>
+ <DisplayName account={status.get('account')} />
+ </a>
+
+ <StatusContent status={status} />
+
+ {media}
+
+ <div className='detailed-status__meta'>
+ <a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
+ <FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
+ </a>{applicationLink} · <Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
+ <i className='fa fa-retweet' />
+ <span className='detailed-status__reblogs'>
+ <FormattedNumber value={status.get('reblogs_count')} />
+ </span>
+ </Link> · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
+ <i className='fa fa-star' />
+ <span className='detailed-status__favorites'>
+ <FormattedNumber value={status.get('favourites_count')} />
+ </span>
+ </Link>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+DetailedStatus.contextTypes = {
+ router: PropTypes.object
+};
+
+DetailedStatus.propTypes = {
+ status: ImmutablePropTypes.map.isRequired,
+ onOpenMedia: PropTypes.func.isRequired,
+ onOpenVideo: PropTypes.func.isRequired,
+ autoPlayGif: PropTypes.bool,
+};
+
+export default DetailedStatus;
diff --git a/app/assets/javascripts/components/features/status/containers/card_container.jsx b/app/javascript/mastodon/features/status/containers/card_container.js
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
@@ -0,0 +1,199 @@
+import React from 'react';
+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';
+import Column from '../ui/components/column';
+import {
+ favourite,
+ unfavourite,
+ reblog,
+ unreblog
+} from '../../actions/interactions';
+import {
+ replyCompose,
+ mentionCompose
+} from '../../actions/compose';
+import { deleteStatus } from '../../actions/statuses';
+import { initReport } from '../../actions/reports';
+import {
+ makeGetStatus,
+ getStatusAncestors,
+ getStatusDescendants
+} 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 ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
+ deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }
+});
+
+const makeMapStateToProps = () => {
+ const getStatus = makeGetStatus();
+
+ const mapStateToProps = (state, props) => ({
+ status: getStatus(state, Number(props.params.statusId)),
+ ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
+ descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
+ me: state.getIn(['meta', 'me']),
+ boostModal: state.getIn(['meta', 'boost_modal']),
+ autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
+ });
+
+ return mapStateToProps;
+};
+
+class Status extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleFavouriteClick = this.handleFavouriteClick.bind(this);
+ this.handleReplyClick = this.handleReplyClick.bind(this);
+ this.handleModalReblog = this.handleModalReblog.bind(this);
+ this.handleReblogClick = this.handleReblogClick.bind(this);
+ this.handleDeleteClick = this.handleDeleteClick.bind(this);
+ this.handleMentionClick = this.handleMentionClick.bind(this);
+ this.handleOpenMedia = this.handleOpenMedia.bind(this);
+ this.handleOpenVideo = this.handleOpenVideo.bind(this);
+ this.handleReport = this.handleReport.bind(this);
+ }
+
+ componentWillMount () {
+ this.props.dispatch(fetchStatus(Number(this.props.params.statusId)));
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
+ this.props.dispatch(fetchStatus(Number(nextProps.params.statusId)));
+ }
+ }
+
+ handleFavouriteClick (status) {
+ if (status.get('favourited')) {
+ this.props.dispatch(unfavourite(status));
+ } else {
+ this.props.dispatch(favourite(status));
+ }
+ }
+
+ handleReplyClick (status) {
+ this.props.dispatch(replyCompose(status, this.context.router));
+ }
+
+ handleModalReblog (status) {
+ this.props.dispatch(reblog(status));
+ }
+
+ handleReblogClick (status, e) {
+ if (status.get('reblogged')) {
+ this.props.dispatch(unreblog(status));
+ } else {
+ if (e.shiftKey || !this.props.boostModal) {
+ this.handleModalReblog(status);
+ } else {
+ this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
+ }
+ }
+ }
+
+ handleDeleteClick (status) {
+ const { dispatch, intl } = this.props;
+
+ dispatch(openModal('CONFIRM', {
+ message: intl.formatMessage(messages.deleteMessage),
+ confirm: intl.formatMessage(messages.deleteConfirm),
+ onConfirm: () => dispatch(deleteStatus(status.get('id')))
+ }));
+ }
+
+ handleMentionClick (account, router) {
+ this.props.dispatch(mentionCompose(account, router));
+ }
+
+ handleOpenMedia (media, index) {
+ this.props.dispatch(openModal('MEDIA', { media, index }));
+ }
+
+ handleOpenVideo (media, time) {
+ this.props.dispatch(openModal('VIDEO', { media, time }));
+ }
+
+ handleReport (status) {
+ this.props.dispatch(initReport(status.get('account'), status));
+ }
+
+ renderChildren (list) {
+ return list.map(id => <StatusContainer key={id} id={id} />);
+ }
+
+ render () {
+ let ancestors, descendants;
+ const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props;
+
+ if (status === null) {
+ return (
+ <Column>
+ <ColumnBackButton />
+ <MissingIndicator />
+ </Column>
+ );
+ }
+
+ const account = status.get('account');
+
+ if (ancestorsIds && ancestorsIds.size > 0) {
+ ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
+ }
+
+ if (descendantsIds && descendantsIds.size > 0) {
+ descendants = <div>{this.renderChildren(descendantsIds)}</div>;
+ }
+
+ return (
+ <Column>
+ <ColumnBackButton />
+
+ <ScrollContainer scrollKey='thread'>
+ <div className='scrollable detailed-status__wrapper'>
+ {ancestors}
+
+ <DetailedStatus status={status} autoPlayGif={autoPlayGif} me={me} onOpenVideo={this.handleOpenVideo} onOpenMedia={this.handleOpenMedia} />
+ <ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} onReport={this.handleReport} />
+
+ {descendants}
+ </div>
+ </ScrollContainer>
+ </Column>
+ );
+ }
+
+}
+
+Status.contextTypes = {
+ router: PropTypes.object
+};
+
+Status.propTypes = {
+ params: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ status: ImmutablePropTypes.map,
+ ancestorsIds: ImmutablePropTypes.list,
+ descendantsIds: ImmutablePropTypes.list,
+ me: PropTypes.number,
+ boostModal: PropTypes.bool,
+ autoPlayGif: PropTypes.bool,
+ intl: PropTypes.object.isRequired
+};
+
+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
@@ -0,0 +1,84 @@
+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';
+import RelativeTimestamp from '../../../components/relative_timestamp';
+import DisplayName from '../../../components/display_name';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ reblog: { id: 'status.reblog', defaultMessage: 'Boost' }
+});
+
+class BoostModal extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleReblog = this.handleReblog.bind(this);
+ this.handleAccountClick = this.handleAccountClick.bind(this);
+ }
+
+ handleReblog() {
+ this.props.onReblog(this.props.status);
+ this.props.onClose();
+ }
+
+ handleAccountClick (e) {
+ if (e.button === 0) {
+ e.preventDefault();
+ this.props.onClose();
+ this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+ }
+ }
+
+ render () {
+ const { status, intl, onClose } = this.props;
+
+ return (
+ <div className='modal-root__modal boost-modal'>
+ <div className='boost-modal__container'>
+ <div className='status light'>
+ <div className='boost-modal__status-header'>
+ <div className='boost-modal__status-time'>
+ <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
+ </div>
+
+ <a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'>
+ <div className='status__avatar'>
+ <Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} />
+ </div>
+
+ <DisplayName account={status.get('account')} />
+ </a>
+ </div>
+
+ <StatusContent status={status} />
+ </div>
+ </div>
+
+ <div className='boost-modal__action-bar'>
+ <div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <i className='fa fa-retweet' /></span> }} /></div>
+ <Button text={intl.formatMessage(messages.reblog)} onClick={this.handleReblog} />
+ </div>
+ </div>
+ );
+ }
+
+}
+
+BoostModal.contextTypes = {
+ router: PropTypes.object
+};
+
+BoostModal.propTypes = {
+ status: ImmutablePropTypes.map.isRequired,
+ onReblog: PropTypes.func.isRequired,
+ onClose: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+export default injectIntl(BoostModal);
diff --git a/app/javascript/mastodon/features/ui/components/column.js b/app/javascript/mastodon/features/ui/components/column.js
@@ -0,0 +1,93 @@
+import React from 'react';
+import ColumnHeader from './column_header';
+import PropTypes from 'prop-types';
+
+const easingOutQuint = (x, t, b, c, d) => c*((t=t/d-1)*t*t*t*t + 1) + b;
+
+const scrollTop = (node) => {
+ const startTime = Date.now();
+ const offset = node.scrollTop;
+ const targetY = -offset;
+ const duration = 1000;
+ let interrupt = false;
+
+ const step = () => {
+ const elapsed = Date.now() - startTime;
+ const percentage = elapsed / duration;
+
+ if (percentage > 1 || interrupt) {
+ return;
+ }
+
+ node.scrollTop = easingOutQuint(0, elapsed, offset, targetY, duration);
+ requestAnimationFrame(step);
+ };
+
+ step();
+
+ return () => {
+ interrupt = true;
+ };
+};
+
+class Column extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleHeaderClick = this.handleHeaderClick.bind(this);
+ this.handleWheel = this.handleWheel.bind(this);
+ this.setRef = this.setRef.bind(this);
+ }
+
+ handleHeaderClick () {
+ const scrollable = this.node.querySelector('.scrollable');
+ if (!scrollable) {
+ return;
+ }
+ this._interruptScrollAnimation = scrollTop(scrollable);
+ }
+
+ handleWheel () {
+ if (typeof this._interruptScrollAnimation !== 'undefined') {
+ this._interruptScrollAnimation();
+ }
+ }
+
+ setRef (c) {
+ this.node = c;
+ }
+
+ render () {
+ const { heading, icon, children, active, hideHeadingOnMobile } = this.props;
+
+ let columnHeaderId = null
+ let header = '';
+
+ if (heading) {
+ columnHeaderId = heading.replace(/ /g, '-')
+ header = <ColumnHeader icon={icon} active={active} type={heading} onClick={this.handleHeaderClick} hideOnMobile={hideHeadingOnMobile} columnHeaderId={columnHeaderId}/>;
+ }
+ return (
+ <div
+ ref={this.setRef}
+ role='region'
+ aria-labelledby={columnHeaderId}
+ className='column'
+ onWheel={this.handleWheel}>
+ {header}
+ {children}
+ </div>
+ );
+ }
+
+}
+
+Column.propTypes = {
+ heading: PropTypes.string,
+ icon: PropTypes.string,
+ children: PropTypes.node,
+ active: PropTypes.bool,
+ hideHeadingOnMobile: PropTypes.bool
+};
+
+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
@@ -0,0 +1,43 @@
+import React from 'react';
+import PropTypes from 'prop-types'
+
+class ColumnHeader extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick () {
+ this.props.onClick();
+ }
+
+ render () {
+ const { type, active, hideOnMobile, columnHeaderId } = this.props;
+
+ let icon = '';
+
+ if (this.props.icon) {
+ icon = <i className={`fa fa-fw fa-${this.props.icon} column-header__icon`} />;
+ }
+
+ return (
+ <div role='button heading' tabIndex='0' className={`column-header ${active ? 'active' : ''} ${hideOnMobile ? 'hidden-on-mobile' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}>
+ {icon}
+ {type}
+ </div>
+ );
+ }
+
+}
+
+ColumnHeader.propTypes = {
+ icon: PropTypes.string,
+ type: PropTypes.string,
+ active: PropTypes.bool,
+ onClick: PropTypes.func,
+ hideOnMobile: PropTypes.bool,
+ columnHeaderId: PropTypes.string
+};
+
+export default ColumnHeader;
diff --git a/app/javascript/mastodon/features/ui/components/column_link.js b/app/javascript/mastodon/features/ui/components/column_link.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link } from 'react-router';
+
+const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => {
+ if (href) {
+ return (
+ <a href={href} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
+ <i className={`fa fa-fw fa-${icon} column-link__icon`} />
+ {text}
+ </a>
+ );
+ } else {
+ return (
+ <Link to={to} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`}>
+ <i className={`fa fa-fw fa-${icon} column-link__icon`} />
+ {text}
+ </Link>
+ );
+ }
+};
+
+ColumnLink.propTypes = {
+ icon: PropTypes.string.isRequired,
+ text: PropTypes.string.isRequired,
+ to: PropTypes.string,
+ href: PropTypes.string,
+ method: PropTypes.string,
+ hideOnMobile: PropTypes.bool
+};
+
+export default ColumnLink;
diff --git a/app/javascript/mastodon/features/ui/components/column_subheading.js b/app/javascript/mastodon/features/ui/components/column_subheading.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const ColumnSubheading = ({ text }) => {
+ return (
+ <div className='column-subheading'>
+ {text}
+ </div>
+ );
+};
+
+ColumnSubheading.propTypes = {
+ text: PropTypes.string.isRequired,
+};
+
+export default ColumnSubheading;
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+class ColumnsArea extends React.PureComponent {
+
+ render () {
+ return (
+ <div className='columns-area'>
+ {this.props.children}
+ </div>
+ );
+ }
+
+}
+
+ColumnsArea.propTypes = {
+ children: PropTypes.node
+};
+
+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
@@ -0,0 +1,51 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import Button from '../../../components/button';
+
+class ConfirmationModal extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ this.handleCancel = this.handleCancel.bind(this);
+ }
+
+ handleClick () {
+ this.props.onClose();
+ this.props.onConfirm();
+ }
+
+ handleCancel (e) {
+ e.preventDefault();
+ this.props.onClose();
+ }
+
+ render () {
+ const { intl, message, confirm, onConfirm, onClose } = this.props;
+
+ return (
+ <div className='modal-root__modal confirmation-modal'>
+ <div className='confirmation-modal__container'>
+ {message}
+ </div>
+
+ <div className='confirmation-modal__action-bar'>
+ <div><a href='#' onClick={this.handleCancel}><FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' /></a></div>
+ <Button text={confirm} onClick={this.handleClick} />
+ </div>
+ </div>
+ );
+ }
+
+}
+
+ConfirmationModal.propTypes = {
+ message: PropTypes.node.isRequired,
+ confirm: PropTypes.string.isRequired,
+ onClose: PropTypes.func.isRequired,
+ onConfirm: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+export default injectIntl(ConfirmationModal);
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -0,0 +1,103 @@
+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';
+import ImageLoader from 'react-imageloader';
+import { defineMessages, injectIntl } from 'react-intl';
+import IconButton from '../../../components/icon_button';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ close: { id: 'lightbox.close', defaultMessage: 'Close' }
+});
+
+class MediaModal extends ImmutablePureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.state = {
+ index: null
+ };
+ this.handleNextClick = this.handleNextClick.bind(this);
+ this.handlePrevClick = this.handlePrevClick.bind(this);
+ this.handleKeyUp = this.handleKeyUp.bind(this);
+ }
+
+ handleNextClick () {
+ this.setState({ index: (this.getIndex() + 1) % this.props.media.size});
+ }
+
+ handlePrevClick () {
+ this.setState({ index: (this.getIndex() - 1) % this.props.media.size});
+ }
+
+ handleKeyUp (e) {
+ switch(e.key) {
+ case 'ArrowLeft':
+ this.handlePrevClick();
+ break;
+ case 'ArrowRight':
+ this.handleNextClick();
+ break;
+ }
+ }
+
+ componentDidMount () {
+ window.addEventListener('keyup', this.handleKeyUp, false);
+ }
+
+ componentWillUnmount () {
+ window.removeEventListener('keyup', this.handleKeyUp);
+ }
+
+ getIndex () {
+ return this.state.index !== null ? this.state.index : this.props.index;
+ }
+
+ render () {
+ const { media, intl, onClose } = this.props;
+
+ const index = this.getIndex();
+ const attachment = media.get(index);
+ const url = attachment.get('url');
+
+ let leftNav, rightNav, content;
+
+ leftNav = rightNav = content = '';
+
+ if (media.size > 1) {
+ leftNav = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>;
+ rightNav = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--right' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>;
+ }
+
+ if (attachment.get('type') === 'image') {
+ content = <ImageLoader src={url} imgProps={{ style: { display: 'block' } }} />;
+ } else if (attachment.get('type') === 'gifv') {
+ content = <ExtendedVideoPlayer src={url} muted={true} controls={false} />;
+ }
+
+ return (
+ <div className='modal-root__modal media-modal'>
+ {leftNav}
+
+ <div className='media-modal__content'>
+ <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
+ {content}
+ </div>
+
+ {rightNav}
+ </div>
+ );
+ }
+
+}
+
+MediaModal.propTypes = {
+ media: ImmutablePropTypes.list.isRequired,
+ index: PropTypes.number.isRequired,
+ onClose: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+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
@@ -0,0 +1,93 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import MediaModal from './media_modal';
+import OnboardingModal from './onboarding_modal';
+import VideoModal from './video_modal';
+import BoostModal from './boost_modal';
+import ConfirmationModal from './confirmation_modal';
+import { TransitionMotion, spring } from 'react-motion';
+
+const MODAL_COMPONENTS = {
+ 'MEDIA': MediaModal,
+ 'ONBOARDING': OnboardingModal,
+ 'VIDEO': VideoModal,
+ 'BOOST': BoostModal,
+ 'CONFIRM': ConfirmationModal
+};
+
+class ModalRoot extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.handleKeyUp = this.handleKeyUp.bind(this);
+ }
+
+ handleKeyUp (e) {
+ if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27)
+ && !!this.props.type) {
+ this.props.onClose();
+ }
+ }
+
+ componentDidMount () {
+ window.addEventListener('keyup', this.handleKeyUp, false);
+ }
+
+ componentWillUnmount () {
+ window.removeEventListener('keyup', this.handleKeyUp);
+ }
+
+ willEnter () {
+ return { opacity: 0, scale: 0.98 };
+ }
+
+ willLeave () {
+ return { opacity: spring(0), scale: spring(0.98) };
+ }
+
+ render () {
+ const { type, props, onClose } = this.props;
+ const items = [];
+
+ if (!!type) {
+ items.push({
+ key: type,
+ data: { type, props },
+ style: { opacity: spring(1), scale: spring(1, { stiffness: 120, damping: 14 }) }
+ });
+ }
+
+ return (
+ <TransitionMotion
+ styles={items}
+ willEnter={this.willEnter}
+ willLeave={this.willLeave}>
+ {interpolatedStyles =>
+ <div className='modal-root'>
+ {interpolatedStyles.map(({ key, data: { type, props }, style }) => {
+ const SpecificComponent = MODAL_COMPONENTS[type];
+
+ return (
+ <div key={key}>
+ <div role='presentation' className='modal-root__overlay' style={{ opacity: style.opacity }} onClick={onClose} />
+ <div className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}>
+ <SpecificComponent {...props} onClose={onClose} />
+ </div>
+ </div>
+ );
+ })}
+ </div>
+ }
+ </TransitionMotion>
+ );
+ }
+
+}
+
+ModalRoot.propTypes = {
+ type: PropTypes.string,
+ props: PropTypes.object,
+ onClose: PropTypes.func.isRequired
+};
+
+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
@@ -0,0 +1,264 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import Permalink from '../../../components/permalink';
+import { TransitionMotion, spring } from 'react-motion';
+import ComposeForm from '../../compose/components/compose_form';
+import Search from '../../compose/components/search';
+import NavigationBar from '../../compose/components/navigation_bar';
+import ColumnHeader from './column_header';
+import Immutable from 'immutable';
+
+const messages = defineMessages({
+ home_title: { id: 'column.home', defaultMessage: 'Home' },
+ notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' },
+ local_title: { id: 'column.community', defaultMessage: 'Local timeline' },
+ federated_title: { id: 'column.public', defaultMessage: 'Federated timeline' }
+});
+
+const PageOne = ({ acct, domain }) => (
+ <div className='onboarding-modal__page onboarding-modal__page-one'>
+ <div style={{ flex: '0 0 auto' }}>
+ <div className='onboarding-modal__page-one__elephant-friend' />
+ </div>
+
+ <div>
+ <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to Mastodon!' /></h1>
+ <p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' /></p>
+ <p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>{acct}@{domain}</strong> }}/></p>
+ </div>
+ </div>
+);
+
+PageOne.propTypes = {
+ acct: PropTypes.string.isRequired,
+ domain: PropTypes.string.isRequired
+};
+
+const PageTwo = ({ me }) => (
+ <div className='onboarding-modal__page onboarding-modal__page-two'>
+ <div className='figure non-interactive'>
+ <div className='pseudo-drawer'>
+ <NavigationBar account={me} />
+ </div>
+ <ComposeForm
+ text='Awoo! #introductions'
+ suggestions={Immutable.List()}
+ mentionedDomains={[]}
+ spoiler={false}
+ onChange={() => {}}
+ onSubmit={() => {}}
+ onPaste={() => {}}
+ onPickEmoji={() => {}}
+ onChangeSpoilerText={() => {}}
+ onClearSuggestions={() => {}}
+ onFetchSuggestions={() => {}}
+ onSuggestionSelected={() => {}}
+ />
+ </div>
+
+ <p><FormattedMessage id='onboarding.page_two.compose' defaultMessage='Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.' /></p>
+ </div>
+);
+
+PageTwo.propTypes = {
+ me: ImmutablePropTypes.map.isRequired,
+};
+
+const PageThree = ({ me, domain }) => (
+ <div className='onboarding-modal__page onboarding-modal__page-three'>
+ <div className='figure non-interactive'>
+ <Search
+ value=''
+ onChange={() => {}}
+ onSubmit={() => {}}
+ onClear={() => {}}
+ onShow={() => {}}
+ />
+
+ <div className='pseudo-drawer'>
+ <NavigationBar account={me} />
+ </div>
+ </div>
+
+ <p><FormattedMessage id='onboarding.page_three.search' defaultMessage='Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.' values={{ illustration: <Permalink to='/timelines/tag/illustration' href='/tags/illustration'>#illustration</Permalink>, introductions: <Permalink to='/timelines/tag/introductions' href='/tags/introductions'>#introductions</Permalink> }}/></p>
+ <p><FormattedMessage id='onboarding.page_three.profile' defaultMessage='Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.' /></p>
+ </div>
+);
+
+PageThree.propTypes = {
+ me: ImmutablePropTypes.map.isRequired,
+ domain: PropTypes.string.isRequired
+};
+
+const PageFour = ({ domain, intl }) => (
+ <div className='onboarding-modal__page onboarding-modal__page-four'>
+ <div className='onboarding-modal__page-four__columns'>
+ <div className='row'>
+ <div>
+ <div className='figure non-interactive'><ColumnHeader icon='home' type={intl.formatMessage(messages.home_title)} /></div>
+ <p><FormattedMessage id='onboarding.page_four.home' defaultMessage='The home timeline shows posts from people you follow.'/></p>
+ </div>
+
+ <div>
+ <div className='figure non-interactive'><ColumnHeader icon='bell' type={intl.formatMessage(messages.notifications_title)} /></div>
+ <p><FormattedMessage id='onboarding.page_four.notifications' defaultMessage='The notifications column shows when someone interacts with you.' /></p>
+ </div>
+ </div>
+
+ <div className='row'>
+ <div>
+ <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='users' type={intl.formatMessage(messages.local_title)} /></div>
+ </div>
+
+ <div>
+ <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='globe' type={intl.formatMessage(messages.federated_title)} /></div>
+ </div>
+ </div>
+
+ <p><FormattedMessage id='onboarding.page_five.public_timelines' defaultMessage='The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.' values={{ domain }} /></p>
+ </div>
+ </div>
+);
+
+PageFour.propTypes = {
+ domain: PropTypes.string.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+const PageSix = ({ admin, domain }) => {
+ let adminSection = '';
+
+ if (admin) {
+ adminSection = (
+ <p>
+ <FormattedMessage id='onboarding.page_six.admin' defaultMessage="Your instance's admin is {admin}." values={{ admin: <Permalink href={admin.get('url')} to={`/accounts/${admin.get('id')}`}>@{admin.get('acct')}</Permalink> }} />
+ <br />
+ <FormattedMessage id='onboarding.page_six.read_guidelines' defaultMessage="Please read {domain}'s {guidelines}!" values={{domain, guidelines: <a href='/about/more' target='_blank'><FormattedMessage id='onboarding.page_six.guidelines' defaultMessage='community guidelines' /></a> }}/>
+ </p>
+ );
+ }
+
+ return (
+ <div className='onboarding-modal__page onboarding-modal__page-six'>
+ <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1>
+ {adminSection}
+ <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
+ <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
+ <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p>
+ </div>
+ );
+};
+
+PageSix.propTypes = {
+ admin: ImmutablePropTypes.map,
+ domain: PropTypes.string.isRequired
+};
+
+const mapStateToProps = state => ({
+ me: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
+ admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]),
+ domain: state.getIn(['meta', 'domain'])
+});
+
+class OnboardingModal extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.state = {
+ currentIndex: 0
+ };
+ this.handleSkip = this.handleSkip.bind(this);
+ this.handleDot = this.handleDot.bind(this);
+ this.handleNext = this.handleNext.bind(this);
+ }
+
+ handleSkip (e) {
+ e.preventDefault();
+ this.props.onClose();
+ }
+
+ handleDot (i, e) {
+ e.preventDefault();
+ this.setState({ currentIndex: i });
+ }
+
+ handleNext (maxNum, e) {
+ e.preventDefault();
+
+ if (this.state.currentIndex < maxNum - 1) {
+ this.setState({ currentIndex: this.state.currentIndex + 1 });
+ } else {
+ this.props.onClose();
+ }
+ }
+
+ render () {
+ const { me, admin, domain, intl } = this.props;
+
+ const pages = [
+ <PageOne acct={me.get('acct')} domain={domain} />,
+ <PageTwo me={me} />,
+ <PageThree me={me} domain={domain} />,
+ <PageFour domain={domain} intl={intl} />,
+ <PageSix admin={admin} domain={domain} />
+ ];
+
+ const { currentIndex } = this.state;
+ const hasMore = currentIndex < pages.length - 1;
+
+ let nextOrDoneBtn;
+
+ if(hasMore) {
+ nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__next'><FormattedMessage id='onboarding.next' defaultMessage='Next' /></a>;
+ } else {
+ nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__done'><FormattedMessage id='onboarding.done' defaultMessage='Done' /></a>;
+ }
+
+ const styles = pages.map((page, i) => ({
+ key: `page-${i}`,
+ style: { opacity: spring(i === currentIndex ? 1 : 0) }
+ }));
+
+ return (
+ <div className='modal-root__modal onboarding-modal'>
+ <TransitionMotion styles={styles}>
+ {interpolatedStyles =>
+ <div className='onboarding-modal__pager'>
+ {pages.map((page, i) =>
+ <div key={`page-${i}`} style={{ opacity: interpolatedStyles[i].style.opacity, pointerEvents: i === currentIndex ? 'auto' : 'none' }}>{page}</div>
+ )}
+ </div>
+ }
+ </TransitionMotion>
+
+ <div className='onboarding-modal__paginator'>
+ <div>
+ <a href='#' className='onboarding-modal__skip' onClick={this.handleSkip}><FormattedMessage id='onboarding.skip' defaultMessage='Skip' /></a>
+ </div>
+
+ <div className='onboarding-modal__dots'>
+ {pages.map((_, i) => <div key={i} onClick={this.handleDot.bind(null, i)} className={`onboarding-modal__dot ${i === currentIndex ? 'active' : ''}`} />)}
+ </div>
+
+ <div>
+ {nextOrDoneBtn}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+OnboardingModal.propTypes = {
+ onClose: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ me: ImmutablePropTypes.map.isRequired,
+ domain: PropTypes.string.isRequired,
+ admin: ImmutablePropTypes.map
+}
+
+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
@@ -0,0 +1,24 @@
+import React from 'react';
+import { Link } from 'react-router';
+import { FormattedMessage } from 'react-intl';
+
+class TabsBar extends React.Component {
+
+ render () {
+ return (
+ <div className='tabs-bar'>
+ <Link className='tabs-bar__link primary' activeClassName='active' to='/statuses/new'><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></Link>
+ <Link className='tabs-bar__link primary' activeClassName='active' to='/timelines/home'><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></Link>
+ <Link className='tabs-bar__link primary' activeClassName='active' to='/notifications'><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></Link>
+
+ <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public/local'><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></Link>
+ <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public'><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></Link>
+
+ <Link className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started'><i className='fa fa-fw fa-asterisk' /></Link>
+ </div>
+ );
+ }
+
+}
+
+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
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Motion, spring } from 'react-motion';
+import { FormattedMessage } from 'react-intl';
+
+class UploadArea extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+
+ this.handleKeyUp = this.handleKeyUp.bind(this);
+ }
+
+ handleKeyUp (e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const keyCode = e.keyCode
+ if (this.props.active) {
+ switch(keyCode) {
+ case 27:
+ this.props.onClose();
+ break;
+ }
+ }
+ }
+
+ componentDidMount () {
+ window.addEventListener('keyup', this.handleKeyUp, false);
+ }
+
+ componentWillUnmount () {
+ window.removeEventListener('keyup', this.handleKeyUp);
+ }
+
+ render () {
+ const { active } = this.props;
+
+ return (
+ <Motion defaultStyle={{ backgroundOpacity: 0, backgroundScale: 0.95 }} style={{ backgroundOpacity: spring(active ? 1 : 0, { stiffness: 150, damping: 15 }), backgroundScale: spring(active ? 1 : 0.95, { stiffness: 200, damping: 3 }) }}>
+ {({ backgroundOpacity, backgroundScale }) =>
+ <div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}>
+ <div className='upload-area__drop'>
+ <div className='upload-area__background' style={{ transform: `translateZ(0) scale(${backgroundScale})` }} />
+ <div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div>
+ </div>
+ </div>
+ }
+ </Motion>
+ );
+ }
+
+}
+
+UploadArea.propTypes = {
+ active: PropTypes.bool,
+ onClose: PropTypes.func
+};
+
+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
@@ -0,0 +1,40 @@
+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';
+import { defineMessages, injectIntl } from 'react-intl';
+import IconButton from '../../../components/icon_button';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+const messages = defineMessages({
+ close: { id: 'lightbox.close', defaultMessage: 'Close' }
+});
+
+class VideoModal extends ImmutablePureComponent {
+
+ render () {
+ const { media, intl, time, onClose } = this.props;
+
+ const url = media.get('url');
+
+ return (
+ <div className='modal-root__modal media-modal'>
+ <div>
+ <div className='media-modal__close'><IconButton title={intl.formatMessage(messages.close)} icon='times' overlay onClick={onClose} /></div>
+ <ExtendedVideoPlayer src={url} muted={false} controls={true} time={time} />
+ </div>
+ </div>
+ );
+ }
+
+}
+
+VideoModal.propTypes = {
+ media: ImmutablePropTypes.map.isRequired,
+ time: PropTypes.number,
+ onClose: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired
+};
+
+export default injectIntl(VideoModal);
diff --git a/app/assets/javascripts/components/features/ui/containers/loading_bar_container.jsx b/app/javascript/mastodon/features/ui/containers/loading_bar_container.js
diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/javascript/mastodon/features/ui/containers/modal_container.js
diff --git a/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx b/app/javascript/mastodon/features/ui/containers/notifications_container.js
diff --git a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx b/app/javascript/mastodon/features/ui/containers/status_list_container.js
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
@@ -0,0 +1,169 @@
+import React from 'react';
+import ColumnsArea from './components/columns_area';
+import NotificationsContainer from './containers/notifications_container';
+import PropTypes from 'prop-types';
+import LoadingBarContainer from './containers/loading_bar_container';
+import HomeTimeline from '../home_timeline';
+import Compose from '../compose';
+import TabsBar from './components/tabs_bar';
+import ModalContainer from './containers/modal_container';
+import Notifications from '../notifications';
+import { connect } from 'react-redux';
+import { isMobile } from '../../is_mobile';
+import { debounce } from 'react-decoration';
+import { uploadCompose } from '../../actions/compose';
+import { refreshTimeline } from '../../actions/timelines';
+import { refreshNotifications } from '../../actions/notifications';
+import UploadArea from './components/upload_area';
+
+const noOp = () => false;
+
+class UI extends React.PureComponent {
+
+ constructor (props, context) {
+ super(props, context);
+ this.state = {
+ width: window.innerWidth,
+ draggingOver: false
+ };
+ this.handleResize = this.handleResize.bind(this);
+ this.handleDragEnter = this.handleDragEnter.bind(this);
+ this.handleDragOver = this.handleDragOver.bind(this);
+ this.handleDrop = this.handleDrop.bind(this);
+ this.handleDragLeave = this.handleDragLeave.bind(this);
+ this.handleDragEnd = this.handleDragLeave.bind(this)
+ this.closeUploadModal = this.closeUploadModal.bind(this)
+ this.setRef = this.setRef.bind(this);
+ }
+
+ @debounce(500)
+ handleResize () {
+ this.setState({ width: window.innerWidth });
+ }
+
+ handleDragEnter (e) {
+ e.preventDefault();
+
+ if (!this.dragTargets) {
+ this.dragTargets = [];
+ }
+
+ if (this.dragTargets.indexOf(e.target) === -1) {
+ this.dragTargets.push(e.target);
+ }
+
+ if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
+ this.setState({ draggingOver: true });
+ }
+ }
+
+ handleDragOver (e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ try {
+ e.dataTransfer.dropEffect = 'copy';
+ } catch (err) {
+
+ }
+
+ return false;
+ }
+
+ handleDrop (e) {
+ e.preventDefault();
+
+ this.setState({ draggingOver: false });
+
+ if (e.dataTransfer && e.dataTransfer.files.length === 1) {
+ this.props.dispatch(uploadCompose(e.dataTransfer.files));
+ }
+ }
+
+ handleDragLeave (e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));
+
+ if (this.dragTargets.length > 0) {
+ return;
+ }
+
+ this.setState({ draggingOver: false });
+ }
+
+ closeUploadModal() {
+ this.setState({ draggingOver: false });
+ }
+
+ componentWillMount () {
+ window.addEventListener('resize', this.handleResize, { passive: true });
+ document.addEventListener('dragenter', this.handleDragEnter, false);
+ document.addEventListener('dragover', this.handleDragOver, false);
+ document.addEventListener('drop', this.handleDrop, false);
+ document.addEventListener('dragleave', this.handleDragLeave, false);
+ document.addEventListener('dragend', this.handleDragEnd, false);
+
+ this.props.dispatch(refreshTimeline('home'));
+ this.props.dispatch(refreshNotifications());
+ }
+
+ componentWillUnmount () {
+ window.removeEventListener('resize', this.handleResize);
+ document.removeEventListener('dragenter', this.handleDragEnter);
+ document.removeEventListener('dragover', this.handleDragOver);
+ document.removeEventListener('drop', this.handleDrop);
+ document.removeEventListener('dragleave', this.handleDragLeave);
+ document.removeEventListener('dragend', this.handleDragEnd);
+ }
+
+ setRef (c) {
+ this.node = c;
+ }
+
+ render () {
+ const { width, draggingOver } = this.state;
+ const { children } = this.props;
+
+ let mountedColumns;
+
+ if (isMobile(width)) {
+ mountedColumns = (
+ <ColumnsArea>
+ {children}
+ </ColumnsArea>
+ );
+ } else {
+ mountedColumns = (
+ <ColumnsArea>
+ <Compose withHeader={true} />
+ <HomeTimeline shouldUpdateScroll={noOp} />
+ <Notifications shouldUpdateScroll={noOp} />
+ <div style={{display: 'flex', flex: '1 1 auto', position: 'relative'}}>{children}</div>
+ </ColumnsArea>
+ );
+ }
+
+ return (
+ <div className='ui' ref={this.setRef}>
+ <TabsBar />
+
+ {mountedColumns}
+
+ <NotificationsContainer />
+ <LoadingBarContainer className="loading-bar" />
+ <ModalContainer />
+ <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
+ </div>
+ );
+ }
+
+}
+
+UI.propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ children: PropTypes.node
+};
+
+export default connect()(UI);
diff --git a/app/assets/javascripts/components/is_mobile.jsx b/app/javascript/mastodon/is_mobile.js
diff --git a/app/assets/javascripts/components/link_header.jsx b/app/javascript/mastodon/link_header.js
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
@@ -0,0 +1,172 @@
+{
+ "account.block": "حظر @{name}",
+ "account.disclaimer": "هذا المستخدم من مثيل خادم آخر. قد يكون هذا الرقم أكبر.",
+ "account.edit_profile": "تعديل الملف الشخصي",
+ "account.follow": "إتبع",
+ "account.followers": "المتابعون",
+ "account.follows": "يتبع",
+ "account.follows_you": "يتابعك",
+ "account.mention": "أُذكُر @{name}",
+ "account.mute": "أكتم @{name}",
+ "account.posts": "المشاركات",
+ "account.report": "أبلغ عن @{name}",
+ "account.requested": "في انتظار الموافقة",
+ "account.unblock": "إلغاء الحظر عن @{name}",
+ "account.unfollow": "إلغاء المتابعة",
+ "account.unmute": "إلغاء الكتم عن @{name}",
+ "boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة",
+ "column.blocks": "الحسابات المحجوبة",
+ "column.community": "الخيط العام المحلي",
+ "column.favourites": "المفضلة",
+ "column.follow_requests": "طلبات المتابعة",
+ "column.home": "الرئيسية",
+ "column.mutes": "الحسابات المكتومة",
+ "column.notifications": "الإشعارات",
+ "column.public": "الخيط العام الموحد",
+ "column_back_button.label": "العودة",
+ "column_subheading.navigation": "التصفح",
+ "column_subheading.settings": "الإعدادات",
+ "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.",
+ "compose_form.lock_disclaimer.lock": "مقفل",
+ "compose_form.placeholder": "فيمَ تفكّر؟",
+ "compose_form.publish": "بوّق !",
+ "compose_form.sensitive": "ضع علامة على الوسيط باعتباره حسّاس",
+ "compose_form.spoiler": "أخفِ النص واعرض تحذيرا",
+ "compose_form.spoiler_placeholder": "تنبيه عن المحتوى",
+ "confirmation_modal.cancel": "إلغاء",
+ "confirmations.block.confirm": "حجب",
+ "confirmations.block.message": "هل أنت متأكد أنك تريد حجب {name} ؟",
+ "confirmations.delete.confirm": "حذف",
+ "confirmations.delete.message": "هل أنت متأكد أنك تريد حذف هذا المنشور ؟",
+ "confirmations.mute.confirm": "أكتم",
+ "confirmations.mute.message": "هل أنت متأكد أنك تريد كتم {name} ؟",
+ "emoji_button.activity": "الأنشطة",
+ "emoji_button.flags": "الأعلام",
+ "emoji_button.food": "الطعام والشراب",
+ "emoji_button.label": "أدرج إيموجي",
+ "emoji_button.nature": "الطبيعة",
+ "emoji_button.objects": "أشياء",
+ "emoji_button.people": "الناس",
+ "emoji_button.search": "ابحث...",
+ "emoji_button.symbols": "رموز",
+ "emoji_button.travel": "أماكن و أسفار",
+ "empty_column.community": "الخط الزمني المحلي فارغ. اكتب شيئا ما للعامة كبداية.",
+ "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
+ "empty_column.home": "إنك لا تتبع بعد أي شخص إلى حد الآن. زر {public} أو استخدام حقل البحث لكي تبدأ على التعرف على مستخدمين آخرين.",
+ "empty_column.home.public_timeline": "الخيط العام",
+ "empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
+ "empty_column.public": "لا يوجد شيء هنا ! قم بتحرير شيء ما بشكل عام، أو اتبع مستخدمين آخرين في الخوادم المثيلة الأخرى لملء خيط المحادثات العام.",
+ "follow_request.authorize": "ترخيص",
+ "follow_request.reject": "رفض",
+ "getting_started.apps": "عدة تطبيقات مختلفة متوفرة",
+ "getting_started.heading": "إستعدّ للبدء",
+ "getting_started.open_source_notice": "ماستدون برنامج مفتوح المصدر. يمكنك المساهمة، أو الإبلاغ عن تقارير الأخطاء، على GitHub {github}. {apps}.",
+ "home.column_settings.advanced": "متقدمة",
+ "home.column_settings.basic": "أساسية",
+ "home.column_settings.filter_regex": "تصفية حسب التعبيرات العادية",
+ "home.column_settings.show_reblogs": "عرض الترقيات",
+ "home.column_settings.show_replies": "عرض الردود",
+ "home.settings": "إعدادات العمود",
+ "lightbox.close": "إغلاق",
+ "loading_indicator.label": "تحميل ...",
+ "media_gallery.toggle_visible": "Toggle visibility",
+ "missing_indicator.label": "تعذر العثور عليه",
+ "navigation_bar.blocks": "الحسابات المحجوبة",
+ "navigation_bar.community_timeline": "الخيط العام المحلي",
+ "navigation_bar.edit_profile": "تعديل الملف الشخصي",
+ "navigation_bar.favourites": "Favourites",
+ "navigation_bar.follow_requests": "Follow requests",
+ "navigation_bar.info": "Extended information",
+ "navigation_bar.logout": "خروج",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "التفضيلات",
+ "navigation_bar.public_timeline": "الخيط العام الموحد",
+ "notification.favourite": "{name} أعجب بمنشورك",
+ "notification.follow": "{name} يتبعك",
+ "notification.reblog": "{name} قام بترقية تبويقك",
+ "notifications.clear": "Clear notifications",
+ "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+ "notifications.column_settings.alert": "إشعارات سطح المكتب",
+ "notifications.column_settings.favourite": "المُفَضَّلة :",
+ "notifications.column_settings.follow": "متابعُون جُدُد :",
+ "notifications.column_settings.mention": "الإشارات :",
+ "notifications.column_settings.reblog": "الترقيّات:",
+ "notifications.column_settings.show": "إعرِضها في عمود",
+ "notifications.column_settings.sound": "Play sound",
+ "notifications.settings": "Column settings",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Adjust status privacy",
+ "privacy.direct.long": "Post to mentioned users only",
+ "privacy.direct.short": "Direct",
+ "privacy.private.long": "Post to followers only",
+ "privacy.private.short": "Followers-only",
+ "privacy.public.long": "Post to public timelines",
+ "privacy.public.short": "Public",
+ "privacy.unlisted.long": "Do not show in public timelines",
+ "privacy.unlisted.short": "Unlisted",
+ "reply_indicator.cancel": "إلغاء",
+ "report.heading": "New report",
+ "report.placeholder": "Additional comments",
+ "report.submit": "Submit",
+ "report.target": "Reporting",
+ "search.placeholder": "ابحث",
+ "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "إحذف",
+ "status.favourite": "أضف إلى المفضلة",
+ "status.load_more": "Load more",
+ "status.media_hidden": "Media hidden",
+ "status.mention": "أذكُر @{name}",
+ "status.open": "وسع هذه المشاركة",
+ "status.reblog": "رَقِّي",
+ "status.reblogged_by": "{name} رقى",
+ "status.reply": "ردّ",
+ "status.replyAll": "Reply to thread",
+ "status.report": "إبلِغ عن @{name}",
+ "status.sensitive_toggle": "اضغط للعرض",
+ "status.sensitive_warning": "محتوى حساس",
+ "status.show_less": "إعرض أقلّ",
+ "status.show_more": "أظهر المزيد",
+ "tabs_bar.compose": "تحرير",
+ "tabs_bar.federated_timeline": "Federated",
+ "tabs_bar.home": "الرئيسية",
+ "tabs_bar.mentions": "الإشارات",
+ "tabs_bar.public": "الخيط العام الموحد",
+ "tabs_bar.notifications": "الإخطارات",
+ "upload_button.label": "إضافة وسائط",
+ "upload_form.undo": "إلغاء",
+ "upload_progress.label": "يرفع...",
+ "notification.follow": "{name} يتبعك",
+ "notification.favourite": "{name} أعجب بمنشورك",
+ "notification.reblog": "{name} قام بترقية تبويقك",
+ "notification.mention": "{name} ذكرك",
+ "notifications.column_settings.alert": "إشعارات سطح المكتب",
+ "notifications.column_settings.show": "إعرِضها في عمود",
+ "notifications.column_settings.follow": "متابعُون جُدُد :",
+ "notifications.column_settings.favourite": "المُفَضَّلة :",
+ "notifications.column_settings.mention": "الإشارات :",
+ "notifications.column_settings.reblog": "الترقيّات:",
+ "video_player.toggle_sound": "تبديل الصوت",
+ "video_player.toggle_visible": "إظهار / إخفاء الفيديو",
+ "video_player.expand": "وسّع الفيديو",
+ "video_player.video_error": "تعذر تشغيل الفيديو"
+}
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Блокирай",
+ "account.disclaimer": "This user is from another instance. This number may be larger.",
+ "account.edit_profile": "Редактирай профила си",
+ "account.follow": "Последвай",
+ "account.followers": "Последователи",
+ "account.follows": "Следвам",
+ "account.follows_you": "Твой последовател",
+ "account.mention": "Споменаване",
+ "account.mute": "Mute @{name}",
+ "account.posts": "Публикации",
+ "account.report": "Report @{name}",
+ "account.requested": "В очакване на одобрение",
+ "account.unblock": "Не блокирай",
+ "account.unfollow": "Не следвай",
+ "account.unmute": "Unmute @{name}",
+ "boost_modal.combo": "You can press {combo} to skip this next time",
+ "column.blocks": "Blocked users",
+ "column.community": "Local timeline",
+ "column.favourites": "Favourites",
+ "column.follow_requests": "Follow requests",
+ "column.home": "Начало",
+ "column.mutes": "Muted users",
+ "column.notifications": "Известия",
+ "column.public": "Публичен канал",
+ "column_back_button.label": "Назад",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Какво си мислиш?",
+ "compose_form.privacy_disclaimer": "Поверителни публикации ще бъдат изпратени до споменатите потребители на {domains}. Доверяваш ли се на {domainsCount, plural, one {that server} other {those servers}}, че няма да издаде твоята публикация?",
+ "compose_form.publish": "Раздумай",
+ "compose_form.sensitive": "Отбележи съдържанието като деликатно",
+ "compose_form.spoiler": "Скрий текста зад предупреждение",
+ "compose_form.spoiler_placeholder": "Content warning",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Insert emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+ "empty_column.hashtag": "There is nothing in this hashtag yet.",
+ "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
+ "empty_column.home.public_timeline": "the public timeline",
+ "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+ "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+ "follow_request.authorize": "Authorize",
+ "follow_request.reject": "Reject",
+ "getting_started.apps": "Various apps are available",
+ "getting_started.heading": "Първи стъпки",
+ "getting_started.open_source_notice": "Mastodon е софтуер с отворен код. Можеш да помогнеш или да докладваш за проблеми в Github: {github}.",
+ "home.column_settings.advanced": "Advanced",
+ "home.column_settings.basic": "Basic",
+ "home.column_settings.filter_regex": "Filter out by regular expressions",
+ "home.column_settings.show_reblogs": "Show boosts",
+ "home.column_settings.show_replies": "Show replies",
+ "home.settings": "Column settings",
+ "lightbox.close": "Затвори",
+ "loading_indicator.label": "Зареждане...",
+ "media_gallery.toggle_visible": "Toggle visibility",
+ "missing_indicator.label": "Not found",
+ "navigation_bar.blocks": "Blocked users",
+ "navigation_bar.community_timeline": "Local timeline",
+ "navigation_bar.edit_profile": "Редактирай профил",
+ "navigation_bar.favourites": "Favourites",
+ "navigation_bar.follow_requests": "Follow requests",
+ "navigation_bar.info": "Extended information",
+ "navigation_bar.logout": "Излизане",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "Предпочитания",
+ "navigation_bar.public_timeline": "Публичен канал",
+ "notification.favourite": "{name} хареса твоята публикация",
+ "notification.follow": "{name} те последва",
+ "notification.reblog": "{name} сподели твоята публикация",
+ "notifications.clear": "Clear notifications",
+ "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+ "notifications.column_settings.alert": "Десктоп известия",
+ "notifications.column_settings.favourite": "Предпочитани:",
+ "notifications.column_settings.follow": "Нови последователи:",
+ "notifications.column_settings.mention": "Споменавания:",
+ "notifications.column_settings.reblog": "Споделяния:",
+ "notifications.column_settings.show": "Покажи в колона",
+ "notifications.column_settings.sound": "Play sound",
+ "notifications.settings": "Column settings",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Adjust status privacy",
+ "privacy.direct.long": "Post to mentioned users only",
+ "privacy.direct.short": "Direct",
+ "privacy.private.long": "Post to followers only",
+ "privacy.private.short": "Followers-only",
+ "privacy.public.long": "Post to public timelines",
+ "privacy.public.short": "Public",
+ "privacy.unlisted.long": "Do not show in public timelines",
+ "privacy.unlisted.short": "Unlisted",
+ "reply_indicator.cancel": "Отказ",
+ "report.heading": "New report",
+ "report.placeholder": "Additional comments",
+ "report.submit": "Submit",
+ "report.target": "Reporting",
+ "search.placeholder": "Търсене",
+ "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Изтриване",
+ "status.favourite": "Предпочитани",
+ "status.load_more": "Load more",
+ "status.media_hidden": "Media hidden",
+ "status.mention": "Споменаване",
+ "status.open": "Expand this status",
+ "status.reblog": "Споделяне",
+ "status.reblogged_by": "{name} сподели",
+ "status.reply": "Отговор",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Report @{name}",
+ "status.sensitive_toggle": "Покажи",
+ "status.sensitive_warning": "Деликатно съдържание",
+ "status.show_less": "Show less",
+ "status.show_more": "Show more",
+ "tabs_bar.compose": "Съставяне",
+ "tabs_bar.federated_timeline": "Federated",
+ "tabs_bar.home": "Начало",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Известия",
+ "upload_area.title": "Drag & drop to upload",
+ "upload_button.label": "Добави медия",
+ "upload_form.undo": "Отмяна",
+ "upload_progress.label": "Uploading...",
+ "video_player.expand": "Expand video",
+ "video_player.toggle_sound": "Звук",
+ "video_player.toggle_visible": "Toggle visibility",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "@{name} blocken",
+ "account.disclaimer": "Dieser Benutzer ist von einer anderen Instanz. Diese Zahl könnte größer sein.",
+ "account.edit_profile": "Profil bearbeiten",
+ "account.follow": "Folgen",
+ "account.followers": "Folgende",
+ "account.follows": "Folgt",
+ "account.follows_you": "Folgt dir",
+ "account.mention": "@{name} erwähnen",
+ "account.mute": "@{name} stummschalten",
+ "account.posts": "Beiträge",
+ "account.report": "@{name} melden",
+ "account.requested": "Warte auf Erlaubnis",
+ "account.unblock": "@{name} entblocken",
+ "account.unfollow": "Entfolgen",
+ "account.unmute": "@{name} nicht mehr stummschalten",
+ "boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen",
+ "column.blocks": "Blockierte Benutzer",
+ "column.community": "Lokale Zeitleiste",
+ "column.favourites": "Favoriten",
+ "column.follow_requests": "Folgeanfragen",
+ "column.home": "Startseite",
+ "column.mutes": "Stummgeschaltete Benutzer",
+ "column.notifications": "Mitteilungen",
+ "column.public": "Gesamtes bekanntes Netz",
+ "column_back_button.label": "Zurück",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Worüber möchtest du schreiben?",
+ "compose_form.privacy_disclaimer": "Dein privater Status wird an die genannten Benutzer auf den Domains {domains} zugestellt. Vertraust du {domainsCount, plural, one {diesem Server} other {diesen Servern}}? Private Beiträge funktionieren nur auf Mastodon-Instanzen. Wenn {domains} {domainsCount, plural, one {keine Mastodon-Instanz ist} other {keine Mastodon-Instanzen sind}}, wird es dort kein Anzeichen geben, dass dein Beitrag privat ist und er könnte geteilt oder anderweitig für unerwünschte Empfänger sichtbar gemacht werden.",
+ "compose_form.publish": "Tröt",
+ "compose_form.sensitive": "Medien als heikel markieren",
+ "compose_form.spoiler": "Text hinter Warnung verbergen",
+ "compose_form.spoiler_placeholder": "Inhaltswarnung",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Emoji einfügen",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe etwas öffentlich, um den Ball ins Rollen zu bringen!",
+ "empty_column.hashtag": "Es gibt noch nichts unter diesem Hashtag.",
+ "empty_column.home": "Du folgst noch niemandem. Besuche {public} oder benutze die Suche, um zu starten oder andere Benutzer anzutreffen.",
+ "empty_column.home.public_timeline": "die öffentliche Zeitleiste",
+ "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um die Konversation zu starten.",
+ "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Benutzern von anderen Instanzen, um es aufzufüllen.",
+ "follow_request.authorize": "Erlauben",
+ "follow_request.reject": "Ablehnen",
+ "getting_started.apps": "Es sind verschiedene Apps verfügbar",
+ "getting_started.heading": "Erste Schritte",
+ "getting_started.open_source_notice": "Mastodon ist quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.",
+ "home.column_settings.advanced": "Fortgeschritten",
+ "home.column_settings.basic": "Einfach",
+ "home.column_settings.filter_regex": "Filter durch reguläre Ausdrücke",
+ "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen",
+ "home.column_settings.show_replies": "Antworten anzeigen",
+ "home.settings": "Spalteneinstellungen",
+ "lightbox.close": "Schließen",
+ "loading_indicator.label": "Lade…",
+ "media_gallery.toggle_visible": "Sichtbarkeit einstellen",
+ "missing_indicator.label": "Nicht gefunden",
+ "navigation_bar.blocks": "Blockierte Benutzer",
+ "navigation_bar.community_timeline": "Lokale Zeitleiste",
+ "navigation_bar.edit_profile": "Profil bearbeiten",
+ "navigation_bar.favourites": "Favoriten",
+ "navigation_bar.follow_requests": "Folgeanfragen",
+ "navigation_bar.info": "Erweiterte Informationen",
+ "navigation_bar.logout": "Abmelden",
+ "navigation_bar.mutes": "Stummgeschaltete Benutzer",
+ "navigation_bar.preferences": "Einstellungen",
+ "navigation_bar.public_timeline": "Föderierte Zeitleiste",
+ "notification.favourite": "{name} favorisierte deinen Status",
+ "notification.follow": "{name} folgt dir",
+ "notification.reblog": "{name} teilte deinen Status",
+ "notifications.clear": "Mitteilungen beseitigen",
+ "notifications.clear_confirmation": "Bist du sicher, dass du alle Mitteilungen beseitigen willst?",
+ "notifications.column_settings.alert": "Desktop-Benachrichtigungen",
+ "notifications.column_settings.favourite": "Favorisierungen:",
+ "notifications.column_settings.follow": "Neue Folgende:",
+ "notifications.column_settings.mention": "Erwähnungen:",
+ "notifications.column_settings.reblog": "Geteilte Beiträge:",
+ "notifications.column_settings.show": "In der Spalte anzeigen",
+ "notifications.column_settings.sound": "Ton abspielen",
+ "notifications.settings": "Spalteneinstellungen",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Privatsphäre des Status anpassen",
+ "privacy.direct.long": "Beitrag nur an erwähnte Benutzer",
+ "privacy.direct.short": "Direkt",
+ "privacy.private.long": "Beitrag nur an Folgende",
+ "privacy.private.short": "Privat",
+ "privacy.public.long": "Beitrag an öffentliche Zeitleisten",
+ "privacy.public.short": "Öffentlich",
+ "privacy.unlisted.long": "Nicht in öffentlichen Zeitleisten anzeigen",
+ "privacy.unlisted.short": "Nicht gelistet",
+ "reply_indicator.cancel": "Abbrechen",
+ "report.heading": "Neue Meldung",
+ "report.placeholder": "Zusätzliche Kommentare",
+ "report.submit": "Absenden",
+ "report.target": "Melden",
+ "search.placeholder": "Suche",
+ "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Löschen",
+ "status.favourite": "Favorisieren",
+ "status.load_more": "Weitere laden",
+ "status.media_hidden": "Medien versteckt",
+ "status.mention": "Erwähnen",
+ "status.open": "Öffnen",
+ "status.reblog": "Teilen",
+ "status.reblogged_by": "{name} teilte",
+ "status.reply": "Antworten",
+ "status.replyAll": "Auf Thread antworten",
+ "status.report": "@{name} melden",
+ "status.sensitive_toggle": "Klicke, um sie zu sehen",
+ "status.sensitive_warning": "Heikle Inhalte",
+ "status.show_less": "Weniger anzeigen",
+ "status.show_more": "Mehr anzeigen",
+ "tabs_bar.compose": "Schreiben",
+ "tabs_bar.federated_timeline": "Föderation",
+ "tabs_bar.home": "Home",
+ "tabs_bar.local_timeline": "Lokal",
+ "tabs_bar.notifications": "Mitteilungen",
+ "upload_area.title": "Hereinziehen zum Hochladen",
+ "upload_button.label": "Mediendatei hinzufügen",
+ "upload_form.undo": "Entfernen",
+ "upload_progress.label": "Lade hoch…",
+ "video_player.expand": "Videoanzeige vergrößern",
+ "video_player.toggle_sound": "Ton umschalten",
+ "video_player.toggle_visible": "Sichtbarkeit umschalten",
+ "video_player.video_error": "Video konnte nicht abgespielt werden"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
@@ -0,0 +1,1068 @@
+[
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Follow",
+ "id": "account.follow"
+ },
+ {
+ "defaultMessage": "Unfollow",
+ "id": "account.unfollow"
+ },
+ {
+ "defaultMessage": "Awaiting approval",
+ "id": "account.requested"
+ },
+ {
+ "defaultMessage": "Unblock @{name}",
+ "id": "account.unblock"
+ },
+ {
+ "defaultMessage": "Unmute @{name}",
+ "id": "account.unmute"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/account.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Back",
+ "id": "column_back_button.label"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/column_back_button_slim.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Back",
+ "id": "column_back_button.label"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/column_back_button.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Load more",
+ "id": "status.load_more"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/load_more.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Loading...",
+ "id": "loading_indicator.label"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/loading_indicator.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Toggle visibility",
+ "id": "media_gallery.toggle_visible"
+ },
+ {
+ "defaultMessage": "Sensitive content",
+ "id": "status.sensitive_warning"
+ },
+ {
+ "defaultMessage": "Media hidden",
+ "id": "status.media_hidden"
+ },
+ {
+ "defaultMessage": "Click to view",
+ "id": "status.sensitive_toggle"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/media_gallery.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Not found",
+ "id": "missing_indicator.label"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/missing_indicator.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Delete",
+ "id": "status.delete"
+ },
+ {
+ "defaultMessage": "Mention @{name}",
+ "id": "status.mention"
+ },
+ {
+ "defaultMessage": "Mute @{name}",
+ "id": "account.mute"
+ },
+ {
+ "defaultMessage": "Block @{name}",
+ "id": "account.block"
+ },
+ {
+ "defaultMessage": "Reply",
+ "id": "status.reply"
+ },
+ {
+ "defaultMessage": "Reply to thread",
+ "id": "status.replyAll"
+ },
+ {
+ "defaultMessage": "Boost",
+ "id": "status.reblog"
+ },
+ {
+ "defaultMessage": "This post cannot be boosted",
+ "id": "status.cannot_reblog"
+ },
+ {
+ "defaultMessage": "Favourite",
+ "id": "status.favourite"
+ },
+ {
+ "defaultMessage": "Expand this status",
+ "id": "status.open"
+ },
+ {
+ "defaultMessage": "Report @{name}",
+ "id": "status.report"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/status_action_bar.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Show more",
+ "id": "status.show_more"
+ },
+ {
+ "defaultMessage": "Show less",
+ "id": "status.show_less"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/status_content.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "{name} boosted",
+ "id": "status.reblogged_by"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/status.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Toggle sound",
+ "id": "video_player.toggle_sound"
+ },
+ {
+ "defaultMessage": "Toggle visibility",
+ "id": "video_player.toggle_visible"
+ },
+ {
+ "defaultMessage": "Expand video",
+ "id": "video_player.expand"
+ },
+ {
+ "defaultMessage": "Video could not be played",
+ "id": "video_player.video_error"
+ },
+ {
+ "defaultMessage": "Sensitive content",
+ "id": "status.sensitive_warning"
+ },
+ {
+ "defaultMessage": "Click to view",
+ "id": "status.sensitive_toggle"
+ },
+ {
+ "defaultMessage": "Media hidden",
+ "id": "status.media_hidden"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/video_player.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Delete",
+ "id": "confirmations.delete.confirm"
+ },
+ {
+ "defaultMessage": "Are you sure you want to delete this status?",
+ "id": "confirmations.delete.message"
+ },
+ {
+ "defaultMessage": "Block",
+ "id": "confirmations.block.confirm"
+ },
+ {
+ "defaultMessage": "Mute",
+ "id": "confirmations.mute.confirm"
+ },
+ {
+ "defaultMessage": "Are you sure you want to block {name}?",
+ "id": "confirmations.block.message"
+ },
+ {
+ "defaultMessage": "Are you sure you want to mute {name}?",
+ "id": "confirmations.mute.message"
+ }
+ ],
+ "path": "app/javascript/mastodon/containers/status_container.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Block",
+ "id": "confirmations.block.confirm"
+ },
+ {
+ "defaultMessage": "Mute",
+ "id": "confirmations.mute.confirm"
+ },
+ {
+ "defaultMessage": "Are you sure you want to block {name}?",
+ "id": "confirmations.block.message"
+ },
+ {
+ "defaultMessage": "Are you sure you want to mute {name}?",
+ "id": "confirmations.mute.message"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/account_timeline/containers/header_container.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Mention @{name}",
+ "id": "account.mention"
+ },
+ {
+ "defaultMessage": "Edit profile",
+ "id": "account.edit_profile"
+ },
+ {
+ "defaultMessage": "Unblock @{name}",
+ "id": "account.unblock"
+ },
+ {
+ "defaultMessage": "Unfollow",
+ "id": "account.unfollow"
+ },
+ {
+ "defaultMessage": "Unmute @{name}",
+ "id": "account.unmute"
+ },
+ {
+ "defaultMessage": "Block @{name}",
+ "id": "account.block"
+ },
+ {
+ "defaultMessage": "Mute @{name}",
+ "id": "account.mute"
+ },
+ {
+ "defaultMessage": "Follow",
+ "id": "account.follow"
+ },
+ {
+ "defaultMessage": "Report @{name}",
+ "id": "account.report"
+ },
+ {
+ "defaultMessage": "This user is from another instance. This number may be larger.",
+ "id": "account.disclaimer"
+ },
+ {
+ "defaultMessage": "Posts",
+ "id": "account.posts"
+ },
+ {
+ "defaultMessage": "Follows",
+ "id": "account.follows"
+ },
+ {
+ "defaultMessage": "Followers",
+ "id": "account.followers"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/account/components/action_bar.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Unfollow",
+ "id": "account.unfollow"
+ },
+ {
+ "defaultMessage": "Follow",
+ "id": "account.follow"
+ },
+ {
+ "defaultMessage": "Awaiting approval",
+ "id": "account.requested"
+ },
+ {
+ "defaultMessage": "Follows you",
+ "id": "account.follows_you"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/account/components/header.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Blocked users",
+ "id": "column.blocks"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/blocks/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Local timeline",
+ "id": "column.community"
+ },
+ {
+ "defaultMessage": "The local timeline is empty. Write something publicly to get the ball rolling!",
+ "id": "empty_column.community"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/community_timeline/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "What is on your mind?",
+ "id": "compose_form.placeholder"
+ },
+ {
+ "defaultMessage": "Content warning",
+ "id": "compose_form.spoiler_placeholder"
+ },
+ {
+ "defaultMessage": "Toot",
+ "id": "compose_form.publish"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/components/compose_form.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Insert emoji",
+ "id": "emoji_button.label"
+ },
+ {
+ "defaultMessage": "Search...",
+ "id": "emoji_button.search"
+ },
+ {
+ "defaultMessage": "People",
+ "id": "emoji_button.people"
+ },
+ {
+ "defaultMessage": "Nature",
+ "id": "emoji_button.nature"
+ },
+ {
+ "defaultMessage": "Food & Drink",
+ "id": "emoji_button.food"
+ },
+ {
+ "defaultMessage": "Activity",
+ "id": "emoji_button.activity"
+ },
+ {
+ "defaultMessage": "Travel & Places",
+ "id": "emoji_button.travel"
+ },
+ {
+ "defaultMessage": "Objects",
+ "id": "emoji_button.objects"
+ },
+ {
+ "defaultMessage": "Symbols",
+ "id": "emoji_button.symbols"
+ },
+ {
+ "defaultMessage": "Flags",
+ "id": "emoji_button.flags"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Edit profile",
+ "id": "navigation_bar.edit_profile"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/components/navigation_bar.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Public",
+ "id": "privacy.public.short"
+ },
+ {
+ "defaultMessage": "Post to public timelines",
+ "id": "privacy.public.long"
+ },
+ {
+ "defaultMessage": "Unlisted",
+ "id": "privacy.unlisted.short"
+ },
+ {
+ "defaultMessage": "Do not show in public timelines",
+ "id": "privacy.unlisted.long"
+ },
+ {
+ "defaultMessage": "Followers-only",
+ "id": "privacy.private.short"
+ },
+ {
+ "defaultMessage": "Post to followers only",
+ "id": "privacy.private.long"
+ },
+ {
+ "defaultMessage": "Direct",
+ "id": "privacy.direct.short"
+ },
+ {
+ "defaultMessage": "Post to mentioned users only",
+ "id": "privacy.direct.long"
+ },
+ {
+ "defaultMessage": "Adjust status privacy",
+ "id": "privacy.change"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/components/privacy_dropdown.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Cancel",
+ "id": "reply_indicator.cancel"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/components/reply_indicator.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "{count, number} {count, plural, one {result} other {results}}",
+ "id": "search_results.total"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/components/search_results.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Search",
+ "id": "search.placeholder"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/components/search.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Add media",
+ "id": "upload_button.label"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/components/upload_button.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Undo",
+ "id": "upload_form.undo"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/components/upload_form.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Uploading...",
+ "id": "upload_progress.label"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/components/upload_progress.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Mark media as sensitive",
+ "id": "compose_form.sensitive"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/containers/sensitive_button_container.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Hide text behind warning",
+ "id": "compose_form.spoiler"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/containers/spoiler_button_container.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "id": "compose_form.lock_disclaimer"
+ },
+ {
+ "defaultMessage": "locked",
+ "id": "compose_form.lock_disclaimer.lock"
+ },
+ {
+ "defaultMessage": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
+ "id": "compose_form.privacy_disclaimer"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/containers/warning_container.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Getting started",
+ "id": "getting_started.heading"
+ },
+ {
+ "defaultMessage": "Federated timeline",
+ "id": "navigation_bar.public_timeline"
+ },
+ {
+ "defaultMessage": "Local timeline",
+ "id": "navigation_bar.community_timeline"
+ },
+ {
+ "defaultMessage": "Preferences",
+ "id": "navigation_bar.preferences"
+ },
+ {
+ "defaultMessage": "Logout",
+ "id": "navigation_bar.logout"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/compose/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Favourites",
+ "id": "column.favourites"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/favourited_statuses/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Authorize",
+ "id": "follow_request.authorize"
+ },
+ {
+ "defaultMessage": "Reject",
+ "id": "follow_request.reject"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/follow_requests/components/account_authorize.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Follow requests",
+ "id": "column.follow_requests"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/follow_requests/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Getting started",
+ "id": "getting_started.heading"
+ },
+ {
+ "defaultMessage": "Federated timeline",
+ "id": "navigation_bar.public_timeline"
+ },
+ {
+ "defaultMessage": "Navigation",
+ "id": "column_subheading.navigation"
+ },
+ {
+ "defaultMessage": "Settings",
+ "id": "column_subheading.settings"
+ },
+ {
+ "defaultMessage": "Local timeline",
+ "id": "navigation_bar.community_timeline"
+ },
+ {
+ "defaultMessage": "Preferences",
+ "id": "navigation_bar.preferences"
+ },
+ {
+ "defaultMessage": "Follow requests",
+ "id": "navigation_bar.follow_requests"
+ },
+ {
+ "defaultMessage": "Logout",
+ "id": "navigation_bar.logout"
+ },
+ {
+ "defaultMessage": "Favourites",
+ "id": "navigation_bar.favourites"
+ },
+ {
+ "defaultMessage": "Blocked users",
+ "id": "navigation_bar.blocks"
+ },
+ {
+ "defaultMessage": "Muted users",
+ "id": "navigation_bar.mutes"
+ },
+ {
+ "defaultMessage": "Extended information",
+ "id": "navigation_bar.info"
+ },
+ {
+ "defaultMessage": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
+ "id": "getting_started.open_source_notice"
+ },
+ {
+ "defaultMessage": "Various apps are available",
+ "id": "getting_started.apps"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/getting_started/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "There is nothing in this hashtag yet.",
+ "id": "empty_column.hashtag"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/hashtag_timeline/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Filter out by regular expressions",
+ "id": "home.column_settings.filter_regex"
+ },
+ {
+ "defaultMessage": "Column settings",
+ "id": "home.settings"
+ },
+ {
+ "defaultMessage": "Basic",
+ "id": "home.column_settings.basic"
+ },
+ {
+ "defaultMessage": "Show boosts",
+ "id": "home.column_settings.show_reblogs"
+ },
+ {
+ "defaultMessage": "Show replies",
+ "id": "home.column_settings.show_replies"
+ },
+ {
+ "defaultMessage": "Advanced",
+ "id": "home.column_settings.advanced"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/home_timeline/components/column_settings.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Home",
+ "id": "column.home"
+ },
+ {
+ "defaultMessage": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
+ "id": "empty_column.home"
+ },
+ {
+ "defaultMessage": "the public timeline",
+ "id": "empty_column.home.public_timeline"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/home_timeline/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Muted users",
+ "id": "column.mutes"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/mutes/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Clear notifications",
+ "id": "notifications.clear"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/notifications/components/clear_column_button.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Column settings",
+ "id": "notifications.settings"
+ },
+ {
+ "defaultMessage": "Desktop notifications",
+ "id": "notifications.column_settings.alert"
+ },
+ {
+ "defaultMessage": "Show in column",
+ "id": "notifications.column_settings.show"
+ },
+ {
+ "defaultMessage": "Play sound",
+ "id": "notifications.column_settings.sound"
+ },
+ {
+ "defaultMessage": "New followers:",
+ "id": "notifications.column_settings.follow"
+ },
+ {
+ "defaultMessage": "Favourites:",
+ "id": "notifications.column_settings.favourite"
+ },
+ {
+ "defaultMessage": "Mentions:",
+ "id": "notifications.column_settings.mention"
+ },
+ {
+ "defaultMessage": "Boosts:",
+ "id": "notifications.column_settings.reblog"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/notifications/components/column_settings.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "{name} followed you",
+ "id": "notification.follow"
+ },
+ {
+ "defaultMessage": "{name} favourited your status",
+ "id": "notification.favourite"
+ },
+ {
+ "defaultMessage": "{name} boosted your status",
+ "id": "notification.reblog"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/notifications/components/notification.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Notifications",
+ "id": "column.notifications"
+ },
+ {
+ "defaultMessage": "Are you sure you want to permanently clear all your notifications?",
+ "id": "notifications.clear_confirmation"
+ },
+ {
+ "defaultMessage": "Clear notifications",
+ "id": "notifications.clear"
+ },
+ {
+ "defaultMessage": "You don't have any notifications yet. Interact with others to start the conversation.",
+ "id": "empty_column.notifications"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/notifications/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Federated timeline",
+ "id": "column.public"
+ },
+ {
+ "defaultMessage": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+ "id": "empty_column.public"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/public_timeline/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "New report",
+ "id": "report.heading"
+ },
+ {
+ "defaultMessage": "Additional comments",
+ "id": "report.placeholder"
+ },
+ {
+ "defaultMessage": "Submit",
+ "id": "report.submit"
+ },
+ {
+ "defaultMessage": "Reporting",
+ "id": "report.target"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/report/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Delete",
+ "id": "status.delete"
+ },
+ {
+ "defaultMessage": "Mention @{name}",
+ "id": "status.mention"
+ },
+ {
+ "defaultMessage": "Reply",
+ "id": "status.reply"
+ },
+ {
+ "defaultMessage": "Boost",
+ "id": "status.reblog"
+ },
+ {
+ "defaultMessage": "This post cannot be boosted",
+ "id": "status.cannot_reblog"
+ },
+ {
+ "defaultMessage": "Favourite",
+ "id": "status.favourite"
+ },
+ {
+ "defaultMessage": "Report @{name}",
+ "id": "status.report"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/status/components/action_bar.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Delete",
+ "id": "confirmations.delete.confirm"
+ },
+ {
+ "defaultMessage": "Are you sure you want to delete this status?",
+ "id": "confirmations.delete.message"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/status/index.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Boost",
+ "id": "status.reblog"
+ },
+ {
+ "defaultMessage": "You can press {combo} to skip this next time",
+ "id": "boost_modal.combo"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/ui/components/boost_modal.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Cancel",
+ "id": "confirmation_modal.cancel"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/ui/components/confirmation_modal.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Close",
+ "id": "lightbox.close"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/ui/components/media_modal.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Home",
+ "id": "column.home"
+ },
+ {
+ "defaultMessage": "Notifications",
+ "id": "column.notifications"
+ },
+ {
+ "defaultMessage": "Local timeline",
+ "id": "column.community"
+ },
+ {
+ "defaultMessage": "Federated timeline",
+ "id": "column.public"
+ },
+ {
+ "defaultMessage": "Welcome to Mastodon!",
+ "id": "onboarding.page_one.welcome"
+ },
+ {
+ "defaultMessage": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "id": "onboarding.page_one.federation"
+ },
+ {
+ "defaultMessage": "You are on {domain}, so your full handle is {handle}",
+ "id": "onboarding.page_one.handle"
+ },
+ {
+ "defaultMessage": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "id": "onboarding.page_two.compose"
+ },
+ {
+ "defaultMessage": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "id": "onboarding.page_three.search"
+ },
+ {
+ "defaultMessage": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "id": "onboarding.page_three.profile"
+ },
+ {
+ "defaultMessage": "The home timeline shows posts from people you follow.",
+ "id": "onboarding.page_four.home"
+ },
+ {
+ "defaultMessage": "The notifications column shows when someone interacts with you.",
+ "id": "onboarding.page_four.notifications"
+ },
+ {
+ "defaultMessage": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "id": "onboarding.page_five.public_timelines"
+ },
+ {
+ "defaultMessage": "Your instance's admin is {admin}.",
+ "id": "onboarding.page_six.admin"
+ },
+ {
+ "defaultMessage": "Please read {domain}'s {guidelines}!",
+ "id": "onboarding.page_six.read_guidelines"
+ },
+ {
+ "defaultMessage": "community guidelines",
+ "id": "onboarding.page_six.guidelines"
+ },
+ {
+ "defaultMessage": "Almost done...",
+ "id": "onboarding.page_six.almost_done"
+ },
+ {
+ "defaultMessage": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "id": "onboarding.page_six.github"
+ },
+ {
+ "defaultMessage": "There are {apps} available for iOS, Android and other platforms.",
+ "id": "onboarding.page_six.apps_available"
+ },
+ {
+ "defaultMessage": "mobile apps",
+ "id": "onboarding.page_six.various_app"
+ },
+ {
+ "defaultMessage": "Bon Appetoot!",
+ "id": "onboarding.page_six.appetoot"
+ },
+ {
+ "defaultMessage": "Next",
+ "id": "onboarding.next"
+ },
+ {
+ "defaultMessage": "Done",
+ "id": "onboarding.done"
+ },
+ {
+ "defaultMessage": "Skip",
+ "id": "onboarding.skip"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/ui/components/onboarding_modal.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Compose",
+ "id": "tabs_bar.compose"
+ },
+ {
+ "defaultMessage": "Home",
+ "id": "tabs_bar.home"
+ },
+ {
+ "defaultMessage": "Notifications",
+ "id": "tabs_bar.notifications"
+ },
+ {
+ "defaultMessage": "Local",
+ "id": "tabs_bar.local_timeline"
+ },
+ {
+ "defaultMessage": "Federated",
+ "id": "tabs_bar.federated_timeline"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/ui/components/tabs_bar.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Drag & drop to upload",
+ "id": "upload_area.title"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/ui/components/upload_area.json"
+ },
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Close",
+ "id": "lightbox.close"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/ui/components/video_modal.json"
+ }
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Block @{name}",
+ "account.disclaimer": "This user is from another instance. This number may be larger.",
+ "account.edit_profile": "Edit profile",
+ "account.follow": "Follow",
+ "account.followers": "Followers",
+ "account.follows": "Follows",
+ "account.follows_you": "Follows you",
+ "account.mention": "Mention @{name}",
+ "account.mute": "Mute @{name}",
+ "account.posts": "Posts",
+ "account.report": "Report @{name}",
+ "account.requested": "Awaiting approval",
+ "account.unblock": "Unblock @{name}",
+ "account.unfollow": "Unfollow",
+ "account.unmute": "Unmute @{name}",
+ "boost_modal.combo": "You can press {combo} to skip this next time",
+ "column.blocks": "Blocked users",
+ "column.community": "Local timeline",
+ "column.favourites": "Favourites",
+ "column.follow_requests": "Follow requests",
+ "column.home": "Home",
+ "column.mutes": "Muted users",
+ "column.notifications": "Notifications",
+ "column.public": "Federated timeline",
+ "column_back_button.label": "Back",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "What is on your mind?",
+ "compose_form.privacy_disclaimer": "Your post will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is not a public post, and it may be boosted or otherwise made visible to unintended recipients.",
+ "compose_form.publish": "Toot",
+ "compose_form.sensitive": "Mark media as sensitive",
+ "compose_form.spoiler": "Hide text behind warning",
+ "compose_form.spoiler_placeholder": "Content warning",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Insert emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+ "empty_column.hashtag": "There is nothing in this hashtag yet.",
+ "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
+ "empty_column.home.public_timeline": "the public timeline",
+ "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+ "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+ "follow_request.authorize": "Authorize",
+ "follow_request.reject": "Reject",
+ "getting_started.apps": "Various apps are available",
+ "getting_started.heading": "Getting started",
+ "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
+ "home.column_settings.advanced": "Advanced",
+ "home.column_settings.basic": "Basic",
+ "home.column_settings.filter_regex": "Filter out by regular expressions",
+ "home.column_settings.show_reblogs": "Show boosts",
+ "home.column_settings.show_replies": "Show replies",
+ "home.settings": "Column settings",
+ "lightbox.close": "Close",
+ "loading_indicator.label": "Loading...",
+ "media_gallery.toggle_visible": "Toggle visibility",
+ "missing_indicator.label": "Not found",
+ "navigation_bar.blocks": "Blocked users",
+ "navigation_bar.community_timeline": "Local timeline",
+ "navigation_bar.edit_profile": "Edit profile",
+ "navigation_bar.favourites": "Favourites",
+ "navigation_bar.follow_requests": "Follow requests",
+ "navigation_bar.info": "Extended information",
+ "navigation_bar.logout": "Logout",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "Preferences",
+ "navigation_bar.public_timeline": "Federated timeline",
+ "notification.favourite": "{name} favourited your status",
+ "notification.follow": "{name} followed you",
+ "notification.reblog": "{name} boosted your status",
+ "notifications.clear": "Clear notifications",
+ "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+ "notifications.column_settings.alert": "Desktop notifications",
+ "notifications.column_settings.favourite": "Favourites:",
+ "notifications.column_settings.follow": "New followers:",
+ "notifications.column_settings.mention": "Mentions:",
+ "notifications.column_settings.reblog": "Boosts:",
+ "notifications.column_settings.show": "Show in column",
+ "notifications.column_settings.sound": "Play sound",
+ "notifications.settings": "Column settings",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Adjust status privacy",
+ "privacy.direct.long": "Post to mentioned users only",
+ "privacy.direct.short": "Direct",
+ "privacy.private.long": "Post to followers only",
+ "privacy.private.short": "Followers-only",
+ "privacy.public.long": "Post to public timelines",
+ "privacy.public.short": "Public",
+ "privacy.unlisted.long": "Do not post to public timelines",
+ "privacy.unlisted.short": "Unlisted",
+ "reply_indicator.cancel": "Cancel",
+ "report.heading": "New report",
+ "report.placeholder": "Additional comments",
+ "report.submit": "Submit",
+ "report.target": "Reporting",
+ "search.placeholder": "Search",
+ "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Delete",
+ "status.favourite": "Favourite",
+ "status.load_more": "Load more",
+ "status.media_hidden": "Media hidden",
+ "status.mention": "Mention @{name}",
+ "status.open": "Expand this status",
+ "status.reblog": "Boost",
+ "status.reblogged_by": "{name} boosted",
+ "status.reply": "Reply",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Report @{name}",
+ "status.sensitive_toggle": "Click to view",
+ "status.sensitive_warning": "Sensitive content",
+ "status.show_less": "Show less",
+ "status.show_more": "Show more",
+ "tabs_bar.compose": "Compose",
+ "tabs_bar.federated_timeline": "Federated",
+ "tabs_bar.home": "Home",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Notifications",
+ "upload_area.title": "Drag & drop to upload",
+ "upload_button.label": "Add media",
+ "upload_form.undo": "Undo",
+ "upload_progress.label": "Uploading...",
+ "video_player.expand": "Expand video",
+ "video_player.toggle_sound": "Toggle sound",
+ "video_player.toggle_visible": "Toggle visibility",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Bloki @{name}",
+ "account.disclaimer": "This user is from another instance. This number may be larger.",
+ "account.edit_profile": "Redakti la profilon",
+ "account.follow": "Sekvi",
+ "account.followers": "Sekvantoj",
+ "account.follows": "Sekvatoj",
+ "account.follows_you": "Sekvas vin",
+ "account.mention": "Mencii @{name}",
+ "account.mute": "Mute @{name}",
+ "account.posts": "Mesaĝoj",
+ "account.report": "Report @{name}",
+ "account.requested": "Atendas aprobon",
+ "account.unblock": "Malbloki @{name}",
+ "account.unfollow": "Malsekvi",
+ "account.unmute": "Unmute @{name}",
+ "boost_modal.combo": "You can press {combo} to skip this next time",
+ "column.blocks": "Blocked users",
+ "column.community": "Loka tempolinio",
+ "column.favourites": "Favourites",
+ "column.follow_requests": "Follow requests",
+ "column.home": "Hejmo",
+ "column.mutes": "Muted users",
+ "column.notifications": "Sciigoj",
+ "column.public": "Fratara tempolinio",
+ "column_back_button.label": "Reveni",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Pri kio vi pensas?",
+ "compose_form.privacy_disclaimer": "Via privata mesaĝo estos sendita nur al menciitaj uzantoj en {domains}. Ĉu vi fidas {domainsCount, plural, one {tiun servilon} other {tiujn servilojn}}? Mesaĝa privateco funkcias nur en aperaĵoj de Mastodon. Se {domains} {domainsCount, plural, one {ne estas aperaĵo de Mastodon} other {ne estas aperaĵoj de Mastodon}}, estos neniu indiko ke via mesaĝo estas privata, kaj ĝi povus esti diskonigita aŭ videbligita al necelitaj ricevantoj.",
+ "compose_form.publish": "Hup",
+ "compose_form.sensitive": "Marki ke la enhavo estas tikla",
+ "compose_form.spoiler": "Kaŝi la tekston malantaŭ averto",
+ "compose_form.spoiler_placeholder": "Content warning",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Insert emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+ "empty_column.hashtag": "There is nothing in this hashtag yet.",
+ "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
+ "empty_column.home.public_timeline": "the public timeline",
+ "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+ "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+ "follow_request.authorize": "Authorize",
+ "follow_request.reject": "Reject",
+ "getting_started.apps": "Various apps are available",
+ "getting_started.heading": "Por komenci",
+ "getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en github je {github}. {apps}.",
+ "home.column_settings.advanced": "Advanced",
+ "home.column_settings.basic": "Basic",
+ "home.column_settings.filter_regex": "Filter out by regular expressions",
+ "home.column_settings.show_reblogs": "Show boosts",
+ "home.column_settings.show_replies": "Show replies",
+ "home.settings": "Column settings",
+ "lightbox.close": "Fermi",
+ "loading_indicator.label": "Ŝarĝanta...",
+ "media_gallery.toggle_visible": "Toggle visibility",
+ "missing_indicator.label": "Not found",
+ "navigation_bar.blocks": "Blocked users",
+ "navigation_bar.community_timeline": "Loka tempolinio",
+ "navigation_bar.edit_profile": "Redakti la profilon",
+ "navigation_bar.favourites": "Favourites",
+ "navigation_bar.follow_requests": "Follow requests",
+ "navigation_bar.info": "Extended information",
+ "navigation_bar.logout": "Elsaluti",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "Preferoj",
+ "navigation_bar.public_timeline": "Fratara tempolinio",
+ "notification.favourite": "{name} favoris vian mesaĝon",
+ "notification.follow": "{name} sekvis vin",
+ "notification.reblog": "{name} diskonigis vian mesaĝon",
+ "notifications.clear": "Clear notifications",
+ "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+ "notifications.column_settings.alert": "Retumilaj atentigoj",
+ "notifications.column_settings.favourite": "Favoroj:",
+ "notifications.column_settings.follow": "Novaj sekvantoj:",
+ "notifications.column_settings.mention": "Mencioj:",
+ "notifications.column_settings.reblog": "Diskonigoj:",
+ "notifications.column_settings.show": "Montri en kolono",
+ "notifications.column_settings.sound": "Play sound",
+ "notifications.settings": "Column settings",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Adjust status privacy",
+ "privacy.direct.long": "Post to mentioned users only",
+ "privacy.direct.short": "Direct",
+ "privacy.private.long": "Post to followers only",
+ "privacy.private.short": "Followers-only",
+ "privacy.public.long": "Post to public timelines",
+ "privacy.public.short": "Public",
+ "privacy.unlisted.long": "Do not show in public timelines",
+ "privacy.unlisted.short": "Unlisted",
+ "reply_indicator.cancel": "Rezigni",
+ "report.heading": "New report",
+ "report.placeholder": "Additional comments",
+ "report.submit": "Submit",
+ "report.target": "Reporting",
+ "search.placeholder": "Serĉi",
+ "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Forigi",
+ "status.favourite": "Favori",
+ "status.load_more": "Load more",
+ "status.media_hidden": "Media hidden",
+ "status.mention": "Mencii @{name}",
+ "status.open": "Expand this status",
+ "status.reblog": "Diskonigi",
+ "status.reblogged_by": "{name} diskonigita",
+ "status.reply": "Respondi",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Report @{name}",
+ "status.sensitive_toggle": "Alklaki por vidi",
+ "status.sensitive_warning": "Tikla enhavo",
+ "status.show_less": "Show less",
+ "status.show_more": "Show more",
+ "tabs_bar.compose": "Ekskribi",
+ "tabs_bar.federated_timeline": "Federated",
+ "tabs_bar.home": "Hejmo",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Sciigoj",
+ "upload_area.title": "Drag & drop to upload",
+ "upload_button.label": "Aldoni enhavaĵon",
+ "upload_form.undo": "Malfari",
+ "upload_progress.label": "Uploading...",
+ "video_player.expand": "Expand video",
+ "video_player.toggle_sound": "Aktivigi sonojn",
+ "video_player.toggle_visible": "Toggle visibility",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Bloquear",
+ "account.disclaimer": "This user is from another instance. This number may be larger.",
+ "account.edit_profile": "Editar perfil",
+ "account.follow": "Seguir",
+ "account.followers": "Seguidores",
+ "account.follows": "Seguir",
+ "account.follows_you": "Te sigue",
+ "account.mention": "Mencionar",
+ "account.mute": "Silenciar",
+ "account.posts": "Publicaciones",
+ "account.report": "Report @{name}",
+ "account.requested": "Esperando aprobación",
+ "account.unblock": "Desbloquear",
+ "account.unfollow": "Dejar de seguir",
+ "account.unmute": "Unmute @{name}",
+ "boost_modal.combo": "You can press {combo} to skip this next time",
+ "column.blocks": "Usuarios bloqueados",
+ "column.community": "Historia local",
+ "column.favourites": "Favoritos",
+ "column.follow_requests": "Solicitudes para seguirte",
+ "column.home": "Inicio",
+ "column.mutes": "Usuarios silenciados",
+ "column.notifications": "Notificaciones",
+ "column.public": "Historia federada",
+ "column_back_button.label": "Atrás",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "¿En qué estás pensando?",
+ "compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
+ "compose_form.publish": "Tootear",
+ "compose_form.sensitive": "Marcar contenido como sensible",
+ "compose_form.spoiler": "Ocultar texto tras advertencia",
+ "compose_form.spoiler_placeholder": "Advertencia de contenido",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Insertar emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+ "empty_column.hashtag": "There is nothing in this hashtag yet.",
+ "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
+ "empty_column.home.public_timeline": "the public timeline",
+ "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+ "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+ "follow_request.authorize": "Authorize",
+ "follow_request.reject": "Reject",
+ "getting_started.apps": "Various apps are available",
+ "getting_started.heading": "Primeros pasos",
+ "getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}. {apps}.",
+ "home.column_settings.advanced": "Advanced",
+ "home.column_settings.basic": "Basic",
+ "home.column_settings.filter_regex": "Filter out by regular expressions",
+ "home.column_settings.show_reblogs": "Show boosts",
+ "home.column_settings.show_replies": "Show replies",
+ "home.settings": "Column settings",
+ "lightbox.close": "Cerrar",
+ "loading_indicator.label": "Cargando...",
+ "media_gallery.toggle_visible": "Toggle visibility",
+ "missing_indicator.label": "Not found",
+ "navigation_bar.blocks": "Usuarios bloqueados",
+ "navigation_bar.community_timeline": "Historia local",
+ "navigation_bar.edit_profile": "Editar perfil",
+ "navigation_bar.favourites": "Favoritos",
+ "navigation_bar.follow_requests": "Solicitudes para seguirte",
+ "navigation_bar.info": "Información adicional",
+ "navigation_bar.logout": "Cerrar sesión",
+ "navigation_bar.mutes": "Usuarios silenciados",
+ "navigation_bar.preferences": "Preferencias",
+ "navigation_bar.public_timeline": "Historia federada",
+ "notification.favourite": "{name} marcó tu estado como favorito",
+ "notification.follow": "{name} te empezó a seguir",
+ "notification.reblog": "{name} ha retooteado tu estado",
+ "notifications.clear": "Clear notifications",
+ "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+ "notifications.column_settings.alert": "Notificaciones de escritorio",
+ "notifications.column_settings.favourite": "Favoritos:",
+ "notifications.column_settings.follow": "Nuevos seguidores:",
+ "notifications.column_settings.mention": "Menciones:",
+ "notifications.column_settings.reblog": "Retoots:",
+ "notifications.column_settings.show": "Mostrar en columna",
+ "notifications.column_settings.sound": "Play sound",
+ "notifications.settings": "Column settings",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Ajustar privacidad",
+ "privacy.direct.long": "Sólo mostrar a los usuarios mencionados",
+ "privacy.direct.short": "Directo",
+ "privacy.private.long": "Sólo mostrar a seguidores",
+ "privacy.private.short": "Privado",
+ "privacy.public.long": "Mostrar en la historia federada",
+ "privacy.public.short": "Público",
+ "privacy.unlisted.long": "No mostrar en la historia federada",
+ "privacy.unlisted.short": "Sin federar",
+ "reply_indicator.cancel": "Cancelar",
+ "report.heading": "New report",
+ "report.placeholder": "Additional comments",
+ "report.submit": "Submit",
+ "report.target": "Reporting",
+ "search.placeholder": "Buscar",
+ "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Borrar",
+ "status.favourite": "Favorito",
+ "status.load_more": "Load more",
+ "status.media_hidden": "Media hidden",
+ "status.mention": "Mencionar",
+ "status.open": "Expandir estado",
+ "status.reblog": "Retoot",
+ "status.reblogged_by": "Retooteado por {name}",
+ "status.reply": "Responder",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Reportar",
+ "status.sensitive_toggle": "Click para ver",
+ "status.sensitive_warning": "Contenido sensible",
+ "status.show_less": "Mostrar menos",
+ "status.show_more": "Mostrar más",
+ "tabs_bar.compose": "Redactar",
+ "tabs_bar.federated_timeline": "Federated",
+ "tabs_bar.home": "Inicio",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Notificaciones",
+ "upload_area.title": "Drag & drop to upload",
+ "upload_button.label": "Subir multimedia",
+ "upload_form.undo": "Deshacer",
+ "upload_progress.label": "Uploading...",
+ "video_player.expand": "Expand video",
+ "video_player.toggle_sound": "Act/Desac. sonido",
+ "video_player.toggle_visible": "Toggle visibility",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "@{name} را مسدود کن",
+ "account.disclaimer": "این کاربر عضو سرور متفاوتی است. شاید عدد واقعی بیشتر از این باشد.",
+ "account.edit_profile": "ویرایش نمایه",
+ "account.follow": "پی بگیرید",
+ "account.followers": "پیگیران",
+ "account.follows": "پی میگیرد",
+ "account.follows_you": "پیگیر شماست",
+ "account.mention": "نامبردن از @{name}",
+ "account.mute": "بیصدا کردن @{name}",
+ "account.posts": "نوشتهها",
+ "account.report": "گزارش @{name}",
+ "account.requested": "در انتظار پذیرش",
+ "account.unblock": "رفع انسداد @{name}",
+ "account.unfollow": "پایان پیگیری",
+ "account.unmute": "باصدا کردن @{name}",
+ "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
+ "column.blocks": "کاربران مسدودشده",
+ "column.community": "نوشتههای محلی",
+ "column.favourites": "پسندیدهها",
+ "column.follow_requests": "درخواستهای پیگیری",
+ "column.home": "خانه",
+ "column.mutes": "کاربران بیصداشده",
+ "column.notifications": "اعلانها",
+ "column.public": "نوشتههای همهجا",
+ "column_back_button.label": "بازگشت",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "تازه چه خبر؟",
+ "compose_form.privacy_disclaimer": "نوشتهٔ خصوصی شما به کاربران نامبردهشده در {domains} فرستاده میشود. آیا به {domainsCount, plural, one {آن سرور} other {آن سرورها}} اعتماد دارید؟ تنظیمات حریم خصوصی نوشتهها تنها در سرورهای ماستدون کار میکند. اگر {domains} {domainsCount, plural, one {یک سرور ماستدون نباشد} other {سرورهای ماستدون نباشند}}، اشارهای به خصوصیبودن نوشتهٔ شما نخواهد شد و شاید نوشتهٔ شما همرسان شود یا برای کاربرانی که نمیخواهید نمایش یابد.",
+ "compose_form.publish": "بوق",
+ "compose_form.sensitive": "تصاویر حساس هستند",
+ "compose_form.spoiler": "نوشته را پشت هشدار پنهان کنید",
+ "compose_form.spoiler_placeholder": "هشدار محتوا",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "فعالیت",
+ "emoji_button.flags": "پرچمها",
+ "emoji_button.food": "غذا و نوشیدنی",
+ "emoji_button.label": "افزودن شکلک",
+ "emoji_button.nature": "طبیعت",
+ "emoji_button.objects": "اشیا",
+ "emoji_button.people": "مردم",
+ "emoji_button.search": "جستجو...",
+ "emoji_button.symbols": "نمادها",
+ "emoji_button.travel": "سفر و مکان",
+ "empty_column.community": "فهرست نوشتههای محلی خالی است. چیزی بنویسید تا چرخش بچرخد!",
+ "empty_column.hashtag": "هنوز هیچ چیزی با این هشتگ نیست.",
+ "empty_column.home": "شما هنوز پیگیر کسی نیستید. {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.",
+ "empty_column.home.public_timeline": "فهرست نوشتههای همهجا",
+ "empty_column.notifications": "هنوز هیچ اعلانی ندارید. به نوشتههای دیگران واکنش نشان دهید تا گفتگو آغاز شود.",
+ "empty_column.public": "اینجا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران دیگر را پی بگیرید تا اینجا پر شود",
+ "follow_request.authorize": "اجازه دهید",
+ "follow_request.reject": "اجازه ندهید",
+ "getting_started.apps": "اپهای گوناگونی در دسترساند",
+ "getting_started.heading": "آغاز کنید",
+ "getting_started.open_source_notice": "ماستدون یک نرمافزار آزاد است. میتوانید در ساخت آن مشارکت کنید یا مشکلاتش را در {github} گزارش دهید. {apps}.",
+ "home.column_settings.advanced": "پیشرفته",
+ "home.column_settings.basic": "اصلی",
+ "home.column_settings.filter_regex": "با عبارتهای باقاعده فیلتر کنید",
+ "home.column_settings.show_reblogs": "نمایش بازبوقها",
+ "home.column_settings.show_replies": "نمایش پاسخها",
+ "home.settings": "تنظیمات ستون",
+ "lightbox.close": "بستن",
+ "loading_indicator.label": "بارگیری...",
+ "media_gallery.toggle_visible": "تغییر پیدایی",
+ "missing_indicator.label": "پیدا نشد",
+ "navigation_bar.blocks": "کاربران مسدودشده",
+ "navigation_bar.community_timeline": "نوشتههای محلی",
+ "navigation_bar.edit_profile": "ویرایش نمایه",
+ "navigation_bar.favourites": "پسندیدهها",
+ "navigation_bar.follow_requests": "درخواستهای پیگیری",
+ "navigation_bar.info": "اطلاعات تکمیلی",
+ "navigation_bar.logout": "خروج",
+ "navigation_bar.mutes": "کاربران بیصداشده",
+ "navigation_bar.preferences": "ترجیحات",
+ "navigation_bar.public_timeline": "نوشتههای همهجا",
+ "notification.favourite": "{name} نوشتهٔ شما را پسندید",
+ "notification.follow": "{name} پیگیر شما شد",
+ "notification.reblog": "{name} نوشتهٔ شما را بازبوقید",
+ "notifications.clear": "پاککردن اعلانها",
+ "notifications.clear_confirmation": "واقعاً میخواهید همهٔ اعلانهایتان را برای همیشه پاک کنید؟",
+ "notifications.column_settings.alert": "اعلان در کامپیوتر",
+ "notifications.column_settings.favourite": "پسندیدهها:",
+ "notifications.column_settings.follow": "پیگیران تازه:",
+ "notifications.column_settings.mention": "نامبردنها:",
+ "notifications.column_settings.reblog": "بازبوقها:",
+ "notifications.column_settings.show": "در ستون نشان بده",
+ "notifications.column_settings.sound": "صدا را پخش کن",
+ "notifications.settings": "تنظیمات ستون",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "تنظیم حریم خصوصی نوشتهها",
+ "privacy.direct.long": "تنها به کاربران نامبردهشده نشان بده",
+ "privacy.direct.short": "مستقیم",
+ "privacy.private.long": "تنها به پیگیران نشان بده",
+ "privacy.private.short": "خصوصی",
+ "privacy.public.long": "در فهرست نوشتههای عمومی نشان بده",
+ "privacy.public.short": "عمومی",
+ "privacy.unlisted.long": "در فهرست نوشتههای همهجا نشان نده",
+ "privacy.unlisted.short": "فهرستنشده",
+ "reply_indicator.cancel": "لغو",
+ "report.heading": "گزارش تازه",
+ "report.placeholder": "توضیح اضافه",
+ "report.submit": "بفرست",
+ "report.target": "گزارشدادن",
+ "search.placeholder": "جستجو",
+ "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
+ "status.cannot_reblog": "این نوشته را نمیشود بازبوقید",
+ "status.delete": "پاککردن",
+ "status.favourite": "پسندیدن",
+ "status.load_more": "بیشتر نشان بده",
+ "status.media_hidden": "تصویر پنهان شده",
+ "status.mention": "از @{name} نام ببرید",
+ "status.open": "این نوشته را باز کن",
+ "status.reblog": "بوق",
+ "status.reblogged_by": "{name} بازبوقید",
+ "status.reply": "پاسخ",
+ "status.replyAll": "به نوشته پاسخ دهید",
+ "status.report": "@{name} را گزارش دهید",
+ "status.sensitive_toggle": "برای دیدن کلیک کنید",
+ "status.sensitive_warning": "محتوای حساس",
+ "status.show_less": "نهفتن",
+ "status.show_more": "نمایش",
+ "tabs_bar.compose": "بنویسید",
+ "tabs_bar.federated_timeline": "همگانی",
+ "tabs_bar.home": "خانه",
+ "tabs_bar.local_timeline": "محلی",
+ "tabs_bar.notifications": "اعلانها",
+ "upload_area.title": "برای بارگذاری به اینجا بکشید",
+ "upload_button.label": "افزودن تصویر",
+ "upload_form.undo": "واگردانی",
+ "upload_progress.label": "بارگذاری...",
+ "video_player.expand": "بازکردن ویدیو",
+ "video_player.toggle_sound": "تغییر صداداری",
+ "video_player.toggle_visible": "تغییر پیدایی",
+ "video_player.video_error": "ویدیو نمیتواند پخش شود"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Estä @{name}",
+ "account.disclaimer": "This user is from another instance. This number may be larger.",
+ "account.edit_profile": "Muokkaa",
+ "account.follow": "Seuraa",
+ "account.followers": "Seuraajia",
+ "account.follows": "Seuraa",
+ "account.follows_you": "Seuraa sinua",
+ "account.mention": "Mainitse @{name}",
+ "account.mute": "Mute @{name}",
+ "account.posts": "Postit",
+ "account.report": "Report @{name}",
+ "account.requested": "Odottaa hyväksyntää",
+ "account.unblock": "Salli @{name}",
+ "account.unfollow": "Lopeta seuraaminen",
+ "account.unmute": "Unmute @{name}",
+ "boost_modal.combo": "You can press {combo} to skip this next time",
+ "column.blocks": "Blocked users",
+ "column.community": "Paikallinen aikajana",
+ "column.favourites": "Favourites",
+ "column.follow_requests": "Follow requests",
+ "column.home": "Koti",
+ "column.mutes": "Muted users",
+ "column.notifications": "Ilmoitukset",
+ "column.public": "Yleinen aikajana",
+ "column_back_button.label": "Takaisin",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Mitä sinulla on mielessä?",
+ "compose_form.privacy_disclaimer": "Sinun yksityinen status toimitetaan mainitsemallesi käyttäjille domaineissa {domains}. Luotatko {domainsCount, plural, one {tähän palvelimeen} other {näihin palvelimiin}}? Postauksen yksityisyys toimii van Mastodon palvelimilla. Jos {domains} {domainsCount, plural, one {ei ole Mastodon palvelin} other {eivät ole Mastodon palvelin}}, viestiin ei tule Yksityinen-merkintää, ja sitä voidaan boostata tai muuten tehdä näkyväksi muille vastaanottajille.",
+ "compose_form.publish": "Toot",
+ "compose_form.sensitive": "Merkitse media herkäksi",
+ "compose_form.spoiler": "Piiloita teksti varoituksen taakse",
+ "compose_form.spoiler_placeholder": "Content warning",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Insert emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+ "empty_column.hashtag": "There is nothing in this hashtag yet.",
+ "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
+ "empty_column.home.public_timeline": "the public timeline",
+ "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+ "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+ "follow_request.authorize": "Authorize",
+ "follow_request.reject": "Reject",
+ "getting_started.apps": "Various apps are available",
+ "getting_started.heading": "Aloitus",
+ "getting_started.open_source_notice": "Mastodon Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}. {apps}.",
+ "home.column_settings.advanced": "Advanced",
+ "home.column_settings.basic": "Basic",
+ "home.column_settings.filter_regex": "Filter out by regular expressions",
+ "home.column_settings.show_reblogs": "Show boosts",
+ "home.column_settings.show_replies": "Show replies",
+ "home.settings": "Column settings",
+ "lightbox.close": "Sulje",
+ "loading_indicator.label": "Ladataan...",
+ "media_gallery.toggle_visible": "Toggle visibility",
+ "missing_indicator.label": "Not found",
+ "navigation_bar.blocks": "Blocked users",
+ "navigation_bar.community_timeline": "Paikallinen aikajana",
+ "navigation_bar.edit_profile": "Muokkaa profiilia",
+ "navigation_bar.favourites": "Favourites",
+ "navigation_bar.follow_requests": "Follow requests",
+ "navigation_bar.info": "Extended information",
+ "navigation_bar.logout": "Kirjaudu ulos",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "Ominaisuudet",
+ "navigation_bar.public_timeline": "Yleinen aikajana",
+ "notification.favourite": "{name} tykkäsi statuksestasi",
+ "notification.follow": "{name} seurasi sinua",
+ "notification.reblog": "{name} buustasi statustasi",
+ "notifications.clear": "Clear notifications",
+ "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+ "notifications.column_settings.alert": "Työpöytä ilmoitukset",
+ "notifications.column_settings.favourite": "Tykkäyksiä:",
+ "notifications.column_settings.follow": "Uusia seuraajia:",
+ "notifications.column_settings.mention": "Mainintoja:",
+ "notifications.column_settings.reblog": "Buusteja:",
+ "notifications.column_settings.show": "Näytä sarakkeessa",
+ "notifications.column_settings.sound": "Play sound",
+ "notifications.settings": "Column settings",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Adjust status privacy",
+ "privacy.direct.long": "Post to mentioned users only",
+ "privacy.direct.short": "Direct",
+ "privacy.private.long": "Post to followers only",
+ "privacy.private.short": "Followers-only",
+ "privacy.public.long": "Post to public timelines",
+ "privacy.public.short": "Public",
+ "privacy.unlisted.long": "Do not show in public timelines",
+ "privacy.unlisted.short": "Unlisted",
+ "reply_indicator.cancel": "Peruuta",
+ "report.heading": "New report",
+ "report.placeholder": "Additional comments",
+ "report.submit": "Submit",
+ "report.target": "Reporting",
+ "search.placeholder": "Hae",
+ "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Poista",
+ "status.favourite": "Tykkää",
+ "status.load_more": "Load more",
+ "status.media_hidden": "Media hidden",
+ "status.mention": "Mainitse @{name}",
+ "status.open": "Expand this status",
+ "status.reblog": "Buustaa",
+ "status.reblogged_by": "{name} buustasi",
+ "status.reply": "Vastaa",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Report @{name}",
+ "status.sensitive_toggle": "Klikkaa nähdäksesi",
+ "status.sensitive_warning": "Arkaluontoista sisältöä",
+ "status.show_less": "Show less",
+ "status.show_more": "Show more",
+ "tabs_bar.compose": "Luo",
+ "tabs_bar.federated_timeline": "Federated",
+ "tabs_bar.home": "Koti",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Ilmoitukset",
+ "upload_area.title": "Drag & drop to upload",
+ "upload_button.label": "Lisää mediaa",
+ "upload_form.undo": "Peru",
+ "upload_progress.label": "Uploading...",
+ "video_player.expand": "Expand video",
+ "video_player.toggle_sound": "Äänet päälle/pois",
+ "video_player.toggle_visible": "Toggle visibility",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Bloquer",
+ "account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.",
+ "account.edit_profile": "Modifier le profil",
+ "account.follow": "Suivre",
+ "account.followers": "Abonné⋅e⋅s",
+ "account.follows": "Abonnements",
+ "account.follows_you": "Vous suit",
+ "account.mention": "Mentionner",
+ "account.mute": "Masquer",
+ "account.posts": "Statuts",
+ "account.report": "Signaler",
+ "account.requested": "Invitation envoyée",
+ "account.unblock": "Débloquer",
+ "account.unfollow": "Ne plus suivre",
+ "account.unmute": "Ne plus masquer",
+ "boost_modal.combo": "You can press {combo} to skip this next time",
+ "column.blocks": "Comptes bloqués",
+ "column.community": "Fil public local",
+ "column.favourites": "Favoris",
+ "column.follow_requests": "Demandes de suivi",
+ "column.home": "Accueil",
+ "column.mutes": "Muted users",
+ "column.notifications": "Notifications",
+ "column.public": "Fil public global",
+ "column_back_button.label": "Retour",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Qu’avez-vous en tête ?",
+ "compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodon. Si {domains} {domainsCount, plural, one {n’est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n’y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d’une autre manière à d’autres personnes imprévues.",
+ "compose_form.private": "Rendre privé",
+ "compose_form.publish": "Pouet",
+ "compose_form.sensitive": "Marquer le média comme délicat",
+ "compose_form.spoiler": "Masquer le texte derrière un avertissement",
+ "compose_form.spoiler_placeholder": "Avertissement",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Insérer un emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "Le fil public local est vide. Écrivez-donc quelque chose pour le remplir !",
+ "empty_column.hashtag": "Il n’y a encore aucun contenu relatif à ce hashtag",
+ "empty_column.home.public_timeline": "le fil public",
+ "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateurs⋅trices.",
+ "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateurs⋅trices pour débuter la conversation.",
+ "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateurs⋅trices d’autres instances pour remplir le fil public.",
+ "follow_request.authorize": "Autoriser",
+ "follow_request.reject": "Rejeter",
+ "getting_started.apps": "Various apps are available",
+ "getting_started.heading": "Pour commencer",
+ "getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {github} sur GitHub.",
+ "home.column_settings.advanced": "Avancé",
+ "home.column_settings.basic": "Basique",
+ "home.column_settings.filter_regex": "Filtrer avec une expression rationnelle",
+ "home.column_settings.show_reblogs": "Afficher les partages",
+ "home.column_settings.show_replies": "Afficher les réponses",
+ "home.settings": "Paramètres de la colonne",
+ "lightbox.close": "Fermer",
+ "loading_indicator.label": "Chargement…",
+ "media_gallery.toggle_visible": "Modifier la visibilité",
+ "missing_indicator.label": "Non trouvé",
+ "navigation_bar.blocks": "Comptes bloqués",
+ "navigation_bar.community_timeline": "Fil public local",
+ "navigation_bar.edit_profile": "Modifier le profil",
+ "navigation_bar.favourites": "Favoris",
+ "navigation_bar.follow_requests": "Demandes de suivi",
+ "navigation_bar.info": "Plus d’informations",
+ "navigation_bar.logout": "Déconnexion",
+ "navigation_bar.mutes": "Comptes silencés",
+ "navigation_bar.preferences": "Préférences",
+ "navigation_bar.public_timeline": "Fil public global",
+ "notification.favourite": "{name} a ajouté à ses favoris :",
+ "notification.follow": "{name} vous suit.",
+ "notification.reblog": "{name} a partagé votre statut :",
+ "notifications.clear": "Nettoyer",
+ "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.mention": "Mentions :",
+ "notifications.column_settings.reblog": "Partages :",
+ "notifications.column_settings.show": "Afficher dans la colonne",
+ "notifications.column_settings.sound": "Émettre un son",
+ "notifications.settings": "Paramètres de la colonne",
+ "onboarding.done": "Done",
+ "onboarding.next": "Suivant",
+ "onboarding.page_five.public_timelines": "Le fil public global affiche les posts de tou⋅te⋅s les utilisateurs⋅trices suivi⋅es par les membres de {domain}. Le fil public local est identique mais se limite aux utilisateurs⋅trices de {domain}.",
+ "onboarding.page_four.home": "L’Accueil affiche les posts de tou⋅te⋅s les utilisateurs⋅trices que vous suivez",
+ "onboarding.page_four.notifications": "Les Notifications vous informent lorsque quelqu’un interagit avec vous",
+ "onboarding.page_one.federation": "Mastodon est un réseau social qui appartient à tou⋅te⋅s.",
+ "onboarding.page_one.handle": "Vous êtes sur {domain}, une des nombreuses instances indépendantes de Mastodon. Votre nom d’utilisateur⋅trice complet est {handle}",
+ "onboarding.page_one.welcome": "Bienvenue sur Mastodon !",
+ "onboarding.page_six.admin": "L’administrateur⋅trice de votre instance est {admin}",
+ "onboarding.page_six.almost_done": "Nous y sommes presque…",
+ "onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres. Et maintenant… Bon Appetoot!",
+ "onboarding.page_six.github": "Mastodon est un logiciel libre, gratuit et open-source. Vous pouvez rapporter des bogues, suggérer des fonctionnalités, ou contribuer à son développement sur {github}.",
+ "onboarding.page_six.guidelines": "règles de la communauté",
+ "onboarding.page_six.read_guidelines": "S’il vous plaît, n’oubliez pas de lire les {guidelines} !",
+ "onboarding.page_six.various_app": "applications mobiles",
+ "onboarding.page_three.profile": "Modifiez votre profil pour changer votre avatar, votre description ainsi que votre nom. Vous y trouverez également d’autres préférences.",
+ "onboarding.page_three.search": "Utilisez la barre de recherche pour trouver des utilisateurs⋅trices et regarder des hashtags tels que {illustration} et {introductions}. Pour trouver quelqu’un qui n’est pas sur cette instance, utilisez son nom d’utilisateur⋅trice complet.",
+ "onboarding.page_two.compose": "Écrivez depuis la colonne de composition. Vous pouvez ajouter des images, changer les réglages de confidentialité, et ajouter des avertissements de contenu (Content Warning) grâce aux icônes en dessous.",
+ "onboarding.skip": "Passer",
+ "privacy.change": "Ajuster la confidentialité du message",
+ "privacy.direct.long": "N’afficher que pour les personnes mentionnées",
+ "privacy.direct.short": "Direct",
+ "privacy.private.long": "N’afficher que pour vos abonné⋅e⋅s",
+ "privacy.private.short": "Privé",
+ "privacy.public.long": "Afficher dans les fils publics",
+ "privacy.public.short": "Public",
+ "privacy.unlisted.long": "Ne pas afficher dans les fils publics",
+ "privacy.unlisted.short": "Non-listé",
+ "reply_indicator.cancel": "Annuler",
+ "report.heading": "Nouveau signalement",
+ "report.placeholder": "Commentaires additionnels",
+ "report.submit": "Envoyer",
+ "report.target": "Signalement",
+ "search.placeholder": "Rechercher",
+ "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Effacer",
+ "status.favourite": "Ajouter aux favoris",
+ "status.load_more": "Charger plus",
+ "status.media_hidden": "Média caché",
+ "status.mention": "Mentionner",
+ "status.open": "Déplier ce statut",
+ "status.reblog": "Partager",
+ "status.reblogged_by": "{name} a partagé :",
+ "status.reply": "Répondre",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Signaler @{name}",
+ "status.sensitive_toggle": "Cliquer pour dévoiler",
+ "status.sensitive_warning": "Contenu délicat",
+ "status.show_less": "Replier",
+ "status.show_more": "Déplier",
+ "tabs_bar.compose": "Composer",
+ "tabs_bar.federated_timeline": "Fil public global",
+ "tabs_bar.home": "Accueil",
+ "tabs_bar.local_timeline": "Fil public local",
+ "tabs_bar.notifications": "Notifications",
+ "upload_area.title": "Glissez et déposez pour envoyer",
+ "upload_button.label": "Joindre un média",
+ "upload_form.undo": "Annuler",
+ "upload_progress.label": "Envoi en cours…",
+ "video_player.expand": "Expand video",
+ "video_player.toggle_sound": "Mettre/Couper le son",
+ "video_player.toggle_visible": "Afficher/Cacher la vidéo",
+ "video_player.video_error": "Video could not be played"
+}
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
@@ -0,0 +1,165 @@
+{
+ "account.block": "חסימת @{name}",
+ "account.disclaimer": "משתמש זה מגיע מקהילה אחרת. המספר הזה עשוי להיות גדול יותר.",
+ "account.edit_profile": "עריכת פרופיל",
+ "account.follow": "מעקב",
+ "account.followers": "עוקבים",
+ "account.follows_you": "במעקב אחריך",
+ "account.follows": "נעקבים",
+ "account.mention": "אזכור של @{name}",
+ "account.mute": "להשתיק את @{name}",
+ "account.posts": "הודעות",
+ "account.report": "לדווח על @{name}",
+ "account.requested": "בהמתנה לאישור",
+ "account.unblock": "הסרת חסימה מעל @{name}",
+ "account.unfollow": "הפסקת מעקב",
+ "account.unmute": "הפסקת השתקת @{name}",
+ "boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
+ "column.blocks": "חסימות",
+ "column.community": "פיד מקומי",
+ "column.favourites": "חיבובים",
+ "column.follow_requests": "בקשות מעקב",
+ "column.home": "בבית",
+ "column.mutes": "השתקות",
+ "column.notifications": "התראות",
+ "column.public": "בפרהסיה",
+ "column_back_button.label": "אחורה",
+ "column_subheading.navigation": "ניווט",
+ "column_subheading.settings": "אפשרויות",
+ "compose_form.lock_disclaimer": "חשבונך אינו {locked}. כל אחד יוכל לעקוב אחריך כדי לקרוא את הודעותיך המיועדות לעוקבים בלבד.",
+ "compose_form.lock_disclaimer.lock": "נעול",
+ "compose_form.placeholder": "מה עובר לך בראש?",
+ "compose_form.privacy_disclaimer": "הודעתך הפרטית תשלח למשתמשים על {domains}. האם ניתן לסמוך על {domainsCount, plural, one {שרת זה} other {שרתים אלו}}? פרטיות ההודעה קיימת רק על שרתי מסטודון. אם {domains} {domainsCount, plural, one {הוא לא שרת מסטודון} other {הם לא שרתי מסטודון}}, לא יהיה שום סימן שההודעה פרטית, והוא עשוי להיות מקודם או להחשף למשתמשים שלא ברשימת היעד.",
+ "compose_form.publish": "לחצרץ",
+ "compose_form.sensitive": "סימון תוכן כרגיש",
+ "compose_form.spoiler": "הסתרה מאחורי אזהרת תוכן",
+ "compose_form.spoiler_placeholder": "אזהרת תוכן",
+ "confirmation_modal.cancel": "ביטול",
+ "confirmations.block.confirm": "לחסום",
+ "confirmations.block.message": "לחסום את {name}?",
+ "confirmations.delete.confirm": "למחוק",
+ "confirmations.delete.message": "למחוק את ההודעה?",
+ "confirmations.mute.confirm": "להשתיק",
+ "confirmations.mute.message": "להשתיק את {name}?",
+ "emoji_button.activity": "פעילות",
+ "emoji_button.flags": "דגלים",
+ "emoji_button.food": "אוכל ושתיה",
+ "emoji_button.label": "הוספת אמוג'י",
+ "emoji_button.nature": "טבע",
+ "emoji_button.objects": "חפצים",
+ "emoji_button.people": "אנשים",
+ "emoji_button.search": "חיפוש...",
+ "emoji_button.symbols": "סמלים",
+ "emoji_button.travel": "טיולים ואתרים",
+ "empty_column.community": "טור הסביבה ריק. יש לפרסם משהו כדי שדברים יתרחילו להתגלגל!",
+ "empty_column.hashtag": "אין כלום בהאשתג הזה עדיין.",
+ "empty_column.home.public_timeline": "בפרהסיה",
+ "empty_column.home": "אף אחד לא במעקב עדיין. אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר חצוצרנים אחרים.",
+ "empty_column.notifications": "אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב!",
+ "empty_column.public": "אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות.",
+ "follow_request.authorize": "קבלה",
+ "follow_request.reject": "דחיה",
+ "getting_started.apps": "קיים מבחר יישומונים לניידים",
+ "getting_started.heading": "על ההתחלה",
+ "getting_started.open_source_notice": "מסטודון היא תוכנה חופשית (בקוד פתוח). ניתן לתרום או לדווח על בעיות בגיטהאב: {github}. {apps}.",
+ "home.column_settings.advanced": "למתקדמים",
+ "home.column_settings.basic": "למתחילים",
+ "home.column_settings.filter_regex": "סינון באמצעות ביטויים רגולריים (regular expressions)",
+ "home.column_settings.show_reblogs": "הצגת הדהודים",
+ "home.column_settings.show_replies": "הצגת תגובות",
+ "home.settings": "הגדרות טור",
+ "lightbox.close": "סגירה",
+ "loading_indicator.label": "טוען...",
+ "media_gallery.toggle_visible": "נראה\\בלתי נראה",
+ "missing_indicator.label": "לא נמצא",
+ "navigation_bar.blocks": "חסימות",
+ "navigation_bar.community_timeline": "פיד מקומי",
+ "navigation_bar.edit_profile": "עריכת פרופיל",
+ "navigation_bar.favourites": "חיבובים",
+ "navigation_bar.follow_requests": "בקשות מעקב",
+ "navigation_bar.info": "מידע נוסף",
+ "navigation_bar.logout": "יציאה",
+ "navigation_bar.mutes": "השתקות",
+ "navigation_bar.preferences": "העדפות",
+ "navigation_bar.public_timeline": "בפרהסיה",
+ "notification.favourite": "חצרוצך חובב על ידי {name}",
+ "notification.follow": "{name} במעקב אחרייך",
+ "notification.mention": "אוזכרת ע\"י {name}",
+ "notification.reblog": "חצרוצך הודהד על ידי {name}",
+ "notifications.clear": "הסרת התראות",
+ "notifications.clear_confirmation": "להסיר את כל ההתראות? בטוח?",
+ "notifications.column_settings.alert": "התראות לשולחן העבודה",
+ "notifications.column_settings.favourite": "מחובבים:",
+ "notifications.column_settings.follow": "עוקבים חדשים:",
+ "notifications.column_settings.mention": "פניות:",
+ "notifications.column_settings.reblog": "הדהודים:",
+ "notifications.column_settings.show": "הצגה בטור",
+ "notifications.column_settings.sound": "שמע מופעל",
+ "notifications.settings": "הגדרות טור",
+ "onboarding.done": "יציאה",
+ "onboarding.next": "הלאה",
+ "onboarding.page_five.public_timelines": "ציר הזמן המקומי מראה הודעות פומביות מכל באי קהילת {domain}. ציר הזמן העולמי מראה הודעות פומביות מאת כי מי שבאי קהילת {domain} עוקבים אחריו. אלו צירי הזמן הפומביים, דרך נהדרת לגלות אנשים חדשים.",
+ "onboarding.page_four.home": "ציר זמן הבית מראה הודעות מהנעקבים שלך.",
+ "onboarding.page_four.notifications": "טור ההתראות מראה כשמישהו מתייחס להודעות שלך.",
+ "onboarding.page_one.federation": "מסטודון היא רשת של שרתים עצמאיים מצורפים ביחד לכדי רשת חברתית אחת גדולה. אנחנו מכנים את השרתים האלו: קהילות",
+ "onboarding.page_one.handle": "אתם בקהילה {domain}, ולכן מזהה המשתמש המלא שלכם הוא {handle}",
+ "onboarding.page_one.welcome": "ברוכים הבאים למסטודון!",
+ "onboarding.page_six.admin": "הקהילה מנוהלת בידי {admin}.",
+ "onboarding.page_six.almost_done": "כמעט סיימנו...",
+ "onboarding.page_six.appetoot": "בתותאבון!",
+ "onboarding.page_six.apps_available": "קיימים {apps} זמינים עבור אנדרואיד, אייפון ופלטפורמות נוספות.",
+ "onboarding.page_six.github": "מסטודון הוא תוכנה חופשית. ניתן לדווח על באגים, לבקש יכולות, או לתרום לקוד באתר {github}.",
+ "onboarding.page_six.guidelines": "חוקי הקהילה",
+ "onboarding.page_six.read_guidelines": "נא לקרוא את {guidelines} של {domain}!",
+ "onboarding.page_six.various_app": "יישומונים ניידים",
+ "onboarding.page_three.profile": "ץתחת 'עריכת פרופיל' ניתן להחליף את תמונת הפרופיל שלך, תיאור קצר, והשם המוצג. שם גם ניתן למצוא אפשרויות והעדפות נוספות.",
+ "onboarding.page_three.search": "בחלונית החיפוש ניתן לחפש אנשים והאשתגים, כמו למשל {illustration} או {introductions}. כדי למצוא מישהו שלא על האינסטנס המקומי, יש להשתמש בכינוי המשתמש המלא.",
+ "onboarding.page_two.compose": "הודעות כותבים מטור הכתיבה. ניתן לנעלות תמונות, לשנות הגדרות פרטיות, ולהוסיף אזהרות תוכן בעזרת האייקונים שמתחת.",
+ "onboarding.skip": "לדלג",
+ "privacy.change": "שינוי פרטיות ההודעה",
+ "privacy.direct.long": "הצג רק למי שהודעה זו פונה אליו",
+ "privacy.direct.short": "הודעה ישירה",
+ "privacy.private.long": "הצג לעוקבים מקומיים בלבד",
+ "privacy.private.short": "לעוקבים בלבד",
+ "privacy.public.long": "פרסם בפומבי",
+ "privacy.public.short": "פומבי",
+ "privacy.unlisted.long": "לא יופיע בפידים הציבוריים המשותפים",
+ "privacy.unlisted.short": "לא לפיד הכללי",
+ "reply_indicator.cancel": "ביטול",
+ "report.heading": "דווח חדש",
+ "report.placeholder": "הערות נוספות",
+ "report.submit": "שליחה",
+ "report.target": "דיווח",
+ "search.placeholder": "חיפוש",
+ "search.status_by": "הודעה מאת {name}",
+ "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}",
+ "status.cannot_reblog": "לא ניתן להדהד הודעה זו",
+ "status.delete": "מחיקה",
+ "status.favourite": "חיבוב",
+ "status.load_more": "עוד",
+ "status.media_hidden": "מדיה מוסתרת",
+ "status.mention": "פניה אל @{name}",
+ "status.open": "הרחבת הודעה",
+ "status.reblog": "הדהוד",
+ "status.reblogged_by": "הודהד על ידי {name}",
+ "status.reply": "תגובה",
+ "status.replyAll": "תגובה לכולם",
+ "status.report": "דיווח על @{name}",
+ "status.sensitive_warning": "תוכן רגיש",
+ "status.sensitive_toggle": "לחצו כדי לראות",
+ "status.show_less": "הראה פחות",
+ "status.show_more": "הראה יותר",
+ "tabs_bar.compose": "חיבור",
+ "tabs_bar.federated_timeline": "בפדרציה",
+ "tabs_bar.home": "בבית",
+ "tabs_bar.local_timeline": "פיד מקומי",
+ "tabs_bar.notifications": "התראות",
+ "upload_area.title": "ניתן להעלות על ידי Drag & drop",
+ "upload_button.label": "הוספת מדיה",
+ "upload_form.undo": "ביטול",
+ "upload_progress.label": "עולה...",
+ "video_player.expand": "הרחבת וידאו",
+ "video_player.toggle_sound": "הפעלת\\ביטול שמע",
+ "video_player.toggle_visible": "הפעלת\\ביטול תצוגה",
+ "video_player.video_error": "לא ניתן לנגן וידאו"
+}
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Blokiraj @{name}",
+ "account.disclaimer": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.",
+ "account.edit_profile": "Uredi profil",
+ "account.follow": "Slijedi",
+ "account.followers": "Sljedbenici",
+ "account.follows": "Slijedi",
+ "account.follows_you": "te slijedi",
+ "account.mention": "Spomeni @{name}",
+ "account.mute": "Utišaj @{name}",
+ "account.posts": "Postovi",
+ "account.report": "Prijavi @{name}",
+ "account.requested": "Čeka pristanak",
+ "account.unblock": "Deblokiraj @{name}",
+ "account.unfollow": "Prestani slijediti",
+ "account.unmute": "Poništi utišavanje @{name}",
+ "boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put",
+ "column.blocks": "Blokirani korisnici",
+ "column.community": "Lokalni timeline",
+ "column.favourites": "Favoriti",
+ "column.follow_requests": "Zahtjevi za slijeđenje",
+ "column.home": "Dom",
+ "column.mutes": "Muted users",
+ "column.notifications": "Notifikacije",
+ "column.public": "Federalni timeline",
+ "column_back_button.label": "Natrag",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Što ti je na umu?",
+ "compose_form.privacy_disclaimer": "Tvoj privatni status će biti dostavljen spomenutim korisnicima na {domains}. Vjeruješ li {domainsCount, plural, one {that server} drugim {those servers}}? Privatnost postova radi samo na Mastodon instancama. Ako {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, neće biti indikacije da je tvoj post privatan, i mogao bit biti podignut ili biti učinjen vidljivim na drugi način neželjenim primateljima.",
+ "compose_form.publish": "Toot",
+ "compose_form.sensitive": "Označi media sadržaj kao osjetljiv",
+ "compose_form.spoiler": "Sakrij text iza upozorenja",
+ "compose_form.spoiler_placeholder": "Upozorenje o sadržaju",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Umetni smajlije",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "Lokalni timeline je prazan. Napiši nešto javno kako bi pokrenuo stvari!",
+ "empty_column.hashtag": "Još ne postoji ništa s ovim hashtagom.",
+ "empty_column.home": "Još ne slijediš nikoga. Posjeti {public} ili koristi tražilicu kako bi počeo i upoznao druge korisnike.",
+ "empty_column.home.public_timeline": "javni timeline",
+ "empty_column.notifications": "Još nemaš notifikacija. Komuniciraj sa drugima kako bi započeo razgovor.",
+ "empty_column.public": "Ovdje nema ništa! Napiši nešto javno, ili ručno slijedi korisnike sa drugih instanci kako bi popunio",
+ "follow_request.authorize": "Authoriziraj",
+ "follow_request.reject": "Odbij",
+ "getting_started.apps": "Dostupne su razne aplikacije",
+ "getting_started.heading": "Počnimo",
+ "getting_started.open_source_notice": "Mastodon je softver otvorenog koda. Možeš pridonijeti ili prijaviti probleme na GitHubu {github}. {apps}.",
+ "home.column_settings.advanced": "Napredno",
+ "home.column_settings.basic": "Osnovno",
+ "home.column_settings.filter_regex": "Filtriraj s regularnim izrazima",
+ "home.column_settings.show_reblogs": "Pokaži boosts",
+ "home.column_settings.show_replies": "Pokaži odgovore",
+ "home.settings": "Postavke Stupca",
+ "lightbox.close": "Zatvori",
+ "loading_indicator.label": "Učitavam...",
+ "media_gallery.toggle_visible": "Preklopi vidljivost",
+ "missing_indicator.label": "Nije nađen",
+ "navigation_bar.blocks": "Blokirani korisnici",
+ "navigation_bar.community_timeline": "Lokalni timeline",
+ "navigation_bar.edit_profile": "Uredi profil",
+ "navigation_bar.favourites": "Favoriti",
+ "navigation_bar.follow_requests": "Zahtjevi za sljeđenje",
+ "navigation_bar.info": "Proširena informacija",
+ "navigation_bar.logout": "Odjavi se",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "Postavke",
+ "navigation_bar.public_timeline": "Federalni timeline",
+ "notification.favourite": "{name} je lajkao tvoj status",
+ "notification.follow": "{name} te sada slijedi",
+ "notification.reblog": "{name} je podigao tvoj status",
+ "notifications.clear": "Očisti notifikacije",
+ "notifications.clear_confirmation": "Želiš li zaista obrisati sve svoje notifikacije?",
+ "notifications.column_settings.alert": "Desktop notifikacije",
+ "notifications.column_settings.favourite": "Favoriti:",
+ "notifications.column_settings.follow": "Novi sljedbenici:",
+ "notifications.column_settings.mention": "Spominjanja:",
+ "notifications.column_settings.reblog": "Boosts:",
+ "notifications.column_settings.show": "Prikaži u stupcu",
+ "notifications.column_settings.sound": "Sviraj zvuk",
+ "notifications.settings": "Postavke rubrike",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Podesi status privatnosti",
+ "privacy.direct.long": "Prikaži samo spomenutim korisnicima",
+ "privacy.direct.short": "Direktno",
+ "privacy.private.long": "Prikaži samo sljedbenicima",
+ "privacy.private.short": "Privatno",
+ "privacy.public.long": "Postaj na javne timeline",
+ "privacy.public.short": "Javno",
+ "privacy.unlisted.long": "Ne prikazuj u javnim timelineovima",
+ "privacy.unlisted.short": "Unlisted",
+ "reply_indicator.cancel": "Otkaži",
+ "report.heading": "Nova prijava",
+ "report.placeholder": "Dodatni komentari",
+ "report.submit": "Pošalji",
+ "report.target": "Prijavljivanje",
+ "search.placeholder": "Traži",
+ "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Obriši",
+ "status.favourite": "Označi omiljenim",
+ "status.load_more": "Učitaj više",
+ "status.media_hidden": "Sakriven media sadržaj",
+ "status.mention": "Spomeni @{name}",
+ "status.open": "Proširi ovaj status",
+ "status.reblog": "Podigni",
+ "status.reblogged_by": "{name} je podigao",
+ "status.reply": "Odgovori",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Prijavi @{name}",
+ "status.sensitive_toggle": "Klikni da bi vidio",
+ "status.sensitive_warning": "Osjetljiv sadržaj",
+ "status.show_less": "Pokaži manje",
+ "status.show_more": "Pokaži više",
+ "tabs_bar.compose": "Sastavi",
+ "tabs_bar.federated_timeline": "Federalni",
+ "tabs_bar.home": "Dom",
+ "tabs_bar.local_timeline": "Lokalno",
+ "tabs_bar.notifications": "Notifikacije",
+ "upload_area.title": "Povuci & spusti kako bi uploadao",
+ "upload_button.label": "Dodaj media",
+ "upload_form.undo": "Poništi",
+ "upload_progress.label": "Uploadam...",
+ "video_player.expand": "Proširi video",
+ "video_player.toggle_sound": "Toggle zvuk",
+ "video_player.toggle_visible": "Preklopi vidljivost",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Blokkolás",
+ "account.disclaimer": "This user is from another instance. This number may be larger.",
+ "account.edit_profile": "Profil szerkesztése",
+ "account.follow": "Követés",
+ "account.followers": "Követők",
+ "account.follows": "Követve",
+ "account.follows_you": "Követnek téged",
+ "account.mention": "Említés",
+ "account.mute": "Mute @{name}",
+ "account.posts": "Posts",
+ "account.report": "Report @{name}",
+ "account.requested": "Awaiting approval",
+ "account.unblock": "Blokkolás levétele",
+ "account.unfollow": "Követés abbahagyása",
+ "account.unmute": "Unmute @{name}",
+ "boost_modal.combo": "You can press {combo} to skip this next time",
+ "column.blocks": "Blocked users",
+ "column.community": "Local timeline",
+ "column.favourites": "Favourites",
+ "column.follow_requests": "Follow requests",
+ "column.home": "Kezdőlap",
+ "column.mutes": "Muted users",
+ "column.notifications": "Értesítések",
+ "column.public": "Nyilvános",
+ "column_back_button.label": "Vissza",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Mire gondolsz?",
+ "compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
+ "compose_form.publish": "Tülk!",
+ "compose_form.sensitive": "Tartalom érzékenynek jelölése",
+ "compose_form.spoiler": "Hide text behind warning",
+ "compose_form.spoiler_placeholder": "Content warning",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Insert emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+ "empty_column.hashtag": "There is nothing in this hashtag yet.",
+ "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
+ "empty_column.home.public_timeline": "the public timeline",
+ "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+ "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+ "follow_request.authorize": "Authorize",
+ "follow_request.reject": "Reject",
+ "getting_started.apps": "Various apps are available",
+ "getting_started.heading": "Első lépések",
+ "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
+ "home.column_settings.advanced": "Advanced",
+ "home.column_settings.basic": "Basic",
+ "home.column_settings.filter_regex": "Filter out by regular expressions",
+ "home.column_settings.show_reblogs": "Show boosts",
+ "home.column_settings.show_replies": "Show replies",
+ "home.settings": "Column settings",
+ "lightbox.close": "Bezárás",
+ "loading_indicator.label": "Betöltés...",
+ "media_gallery.toggle_visible": "Toggle visibility",
+ "missing_indicator.label": "Not found",
+ "navigation_bar.blocks": "Blocked users",
+ "navigation_bar.community_timeline": "Local timeline",
+ "navigation_bar.edit_profile": "Profil szerkesztése",
+ "navigation_bar.favourites": "Favourites",
+ "navigation_bar.follow_requests": "Follow requests",
+ "navigation_bar.info": "Extended information",
+ "navigation_bar.logout": "Kijelentkezés",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "Beállítások",
+ "navigation_bar.public_timeline": "Nyilvános időfolyam",
+ "notification.favourite": "{name} kedvencnek jelölte az állapotod",
+ "notification.follow": "{name} követ téged",
+ "notification.reblog": "{name} reblogolta az állapotod",
+ "notifications.clear": "Clear notifications",
+ "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+ "notifications.column_settings.alert": "Desktop notifications",
+ "notifications.column_settings.favourite": "Favourites:",
+ "notifications.column_settings.follow": "New followers:",
+ "notifications.column_settings.mention": "Mentions:",
+ "notifications.column_settings.reblog": "Boosts:",
+ "notifications.column_settings.show": "Show in column",
+ "notifications.column_settings.sound": "Play sound",
+ "notifications.settings": "Column settings",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Adjust status privacy",
+ "privacy.direct.long": "Post to mentioned users only",
+ "privacy.direct.short": "Direct",
+ "privacy.private.long": "Post to followers only",
+ "privacy.private.short": "Followers-only",
+ "privacy.public.long": "Post to public timelines",
+ "privacy.public.short": "Public",
+ "privacy.unlisted.long": "Do not show in public timelines",
+ "privacy.unlisted.short": "Unlisted",
+ "reply_indicator.cancel": "Mégsem",
+ "report.heading": "New report",
+ "report.placeholder": "Additional comments",
+ "report.submit": "Submit",
+ "report.target": "Reporting",
+ "search.placeholder": "Keresés",
+ "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Törlés",
+ "status.favourite": "Kedvenc",
+ "status.load_more": "Load more",
+ "status.media_hidden": "Media hidden",
+ "status.mention": "Említés",
+ "status.open": "Expand this status",
+ "status.reblog": "Reblog",
+ "status.reblogged_by": "{name} reblogolta",
+ "status.reply": "Válasz",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Report @{name}",
+ "status.sensitive_toggle": "Katt a megtekintéshez",
+ "status.sensitive_warning": "Érzékeny tartalom",
+ "status.show_less": "Show less",
+ "status.show_more": "Show more",
+ "tabs_bar.compose": "Összeállítás",
+ "tabs_bar.federated_timeline": "Federated",
+ "tabs_bar.home": "Kezdőlap",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Notifications",
+ "upload_area.title": "Drag & drop to upload",
+ "upload_button.label": "Média hozzáadása",
+ "upload_form.undo": "Mégsem",
+ "upload_progress.label": "Uploading...",
+ "video_player.expand": "Expand video",
+ "video_player.toggle_sound": "Hang kapcsolása",
+ "video_player.toggle_visible": "Toggle visibility",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
@@ -0,0 +1,167 @@
+{
+ "account.block": "Blokir @{name}",
+ "account.disclaimer": "Pengguna ini berasal dari server lain. Angka berikut mungkin lebih besar.",
+ "account.edit_profile": "Ubah profil",
+ "account.follow": "Ikuti",
+ "account.followers": "Pengikut",
+ "account.follows": "Mengikuti",
+ "account.follows_you": "Mengikuti anda",
+ "account.mention": "Balasan @{name}",
+ "account.mute": "Bisukan @{name}",
+ "account.posts": "Postingan",
+ "account.report": "Laporkan @{name}",
+ "account.requested": "Menunggu persetujuan",
+ "account.unblock": "Hapus blokir @{name}",
+ "account.unfollow": "Berhenti mengikuti",
+ "account.unmute": "Berhenti membisukan @{name}",
+ "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini",
+ "column.blocks": "Pengguna diblokir",
+ "column.community": "Linimasa Lokal",
+ "column.favourites": "Favorit",
+ "column.follow_requests": "Permintaan mengikuti",
+ "column.home": "Beranda",
+ "column.mutes": "Pengguna dibisukan",
+ "column.notifications": "Notifikasi",
+ "column.public": "Linimasa gabunggan",
+ "column_back_button.label": "Kembali",
+ "column_subheading.navigation": "Navigasi",
+ "column_subheading.settings": "Pengaturan",
+ "compose_form.lock_disclaimer": "Akun anda tidak {locked}. Semua orang dapat mengikuti anda untuk melihat postingan khusus untuk pengikut anda.",
+ "compose_form.lock_disclaimer.lock": "dikunci",
+ "compose_form.placeholder": "Apa yang ada di pikiran anda?",
+ "compose_form.privacy_disclaimer": "Status pribadi anda akan dikirim ke pengguna yang disebut dalam {domains}. Apa anda mempercayai {domainsCount, plural, one {server tersebut} other {server tersebut}}? Privasi postingan hanya bekerja dalam server Mastodon. Jika {domains} {domainsCount, plural, one {bukan server Mastodon} other {bukan server Mastodon}}, akan ada indikasi bahwa postingan anda adalah postingan pribadi, dan dapat di-boost atau dapat dilihat oleh orang lain.",
+ "compose_form.publish": "Toot",
+ "compose_form.sensitive": "Tandai media sensitif",
+ "compose_form.spoiler": "Sembunyikan teks dibalik peringatan",
+ "compose_form.spoiler_placeholder": "Peringatan konten",
+ "confirmation_modal.cancel": "Batal",
+ "confirmations.block.confirm": "Blokir",
+ "confirmations.block.message": "Apa anda yakin ingin memblokir {name}?",
+ "confirmations.delete.confirm": "Hapus",
+ "confirmations.delete.message": "Apa anda yakin akan menghapus status ini?",
+ "confirmations.mute.confirm": "Bisukan",
+ "confirmations.mute.message": "Apa anda yakin ingin membisukan {name}?",
+ "emoji_button.activity": "Aktivitas",
+ "emoji_button.flags": "Bendera",
+ "emoji_button.food": "Makanan & Minuman",
+ "emoji_button.label": "Tambahkan emoji",
+ "emoji_button.nature": "Alam",
+ "emoji_button.objects": "Benda-benda",
+ "emoji_button.people": "Orang",
+ "emoji_button.search": "Cari...",
+ "emoji_button.symbols": "Simbol",
+ "emoji_button.travel": "Tempat Wisata",
+ "empty_column.community": "Linimasa lokal masih kosong. Tulis sesuatu secara publik dan buat roda berputar!",
+ "empty_column.hashtag": "Tidak ada apapun dalam hashtag ini.",
+ "empty_column.home": "Anda sedang tidak mengikuti siapapun. Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.",
+ "empty_column.home.public_timeline": "linimasa publik",
+ "empty_column.notifications": "Anda tidak memiliki notifikasi apapun. Berinteraksi dengan orang lain untuk memulai percakapan.",
+ "empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisinya secara manual",
+ "follow_request.authorize": "Izinkan",
+ "follow_request.reject": "Tolak",
+ "getting_started.apps": "Tersedia dalam berbagai aplikasi",
+ "getting_started.heading": "Mulai",
+ "getting_started.open_source_notice": "Mastodon adalah perangkat lunak yang bersifat open source. Anda dapat berkontribusi atau melaporkan permasalahan/bug di Github {github}. {apps}.",
+ "home.column_settings.advanced": "Tingkat Lanjut",
+ "home.column_settings.basic": "Dasar",
+ "home.column_settings.filter_regex": "Penyaringan dengan Regular Expression",
+ "home.column_settings.show_reblogs": "Tampilkan Boost",
+ "home.column_settings.show_replies": "Tampilkan balasan",
+ "home.settings": "Pengaturan kolom",
+ "lightbox.close": "Tutup",
+ "loading_indicator.label": "Tunggu sebentar...",
+ "media_gallery.toggle_visible": "Tampil/Sembunyikan",
+ "missing_indicator.label": "Tidak ditemukan",
+ "navigation_bar.blocks": "Pengguna diblokir",
+ "navigation_bar.community_timeline": "Linimasa lokal",
+ "navigation_bar.edit_profile": "Ubah profil",
+ "navigation_bar.favourites": "Favorit",
+ "navigation_bar.follow_requests": "Permintaan mengikuti",
+ "navigation_bar.info": "Informasi selengkapnya",
+ "navigation_bar.logout": "Keluar",
+ "navigation_bar.mutes": "Pengguna dibisukan",
+ "navigation_bar.preferences": "Pengaturan",
+ "navigation_bar.public_timeline": "Linimasa gabungan",
+ "notification.favourite": "{name} menyukai status anda",
+ "notification.follow": "{name} mengikuti anda",
+ "notification.reblog": "{name} mem-boost status anda",
+ "notifications.clear": "Hapus notifikasi",
+ "notifications.clear_confirmation": "Apa anda yakin hendak menghapus semua notifikasi anda?",
+ "notifications.column_settings.alert": "Notifikasi desktop",
+ "notifications.column_settings.favourite": "Favorit:",
+ "notifications.column_settings.follow": "Pengikut baru:",
+ "notifications.column_settings.mention": "Balasan:",
+ "notifications.column_settings.reblog": "Boost:",
+ "notifications.column_settings.show": "Tampilkan dalam kolom",
+ "notifications.column_settings.sound": "Mainkan suara",
+ "notifications.settings": "Pengaturan kolom",
+ "onboarding.done": "Selesei",
+ "onboarding.next": "Selanjutnya",
+ "onboarding.page_five.public_timelines": "Linimasa lokal menampilkan semua postingan publik dari semua orang di {domain}. Linimasa gabungan menampilkan postingan publik dari semua orang yang diikuti oleh {domain}. Ini semua adalah Linimasa Publik, cara terbaik untuk bertemu orang lain.",
+ "onboarding.page_four.home": "Linimasa beranda menampilkan postingan dari orang-orang yang anda ikuti.",
+ "onboarding.page_four.notifications": "Kolom notifikasi menampilkan ketika seseorang berinteraksi dengan anda.",
+ "onboarding.page_one.federation": "Mastodon adalah jaringan dari beberapa server independen yang bergabung untuk membuat jejaring sosial yang besar.",
+ "onboarding.page_one.handle": "Ada berada dalam {domain}, jadi nama user lengkap anda adalah {handle}",
+ "onboarding.page_one.welcome": "Selamat datang di Mastodon!",
+ "onboarding.page_six.admin": "Admin serveer anda adalah {admin}.",
+ "onboarding.page_six.almost_done": "Hampir selesei...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "Ada beberapa apl yang tersedia untuk iOS, Android, dan platform lainnya.",
+ "onboarding.page_six.github": "Mastodon adalah software open-source. Anda bisa melaporkan bug, meminta fitur, atau berkontribusi dengan kode di {github}.",
+ "onboarding.page_six.guidelines": "pedoman komunitas",
+ "onboarding.page_six.read_guidelines": "Silakan baca {guidelines} {domain}!",
+ "onboarding.page_six.various_app": "apl handphone",
+ "onboarding.page_three.profile": "Ubah profil anda untuk mengganti avatar, bio, dan nama pengguna anda. Disitu, anda juga bisa mengatur opsi lainnya.",
+ "onboarding.page_three.search": "Gunakan kolom pencarian untuk mencari orang atau melihat hashtag, seperti {illustration} dan {introductions}. Untuk mencari pengguna yang tidak berada dalam server ini, gunakan nama pengguna mereka selengkapnya.",
+ "onboarding.page_two.compose": "Tulis postingan melalui kolom posting. Anda dapat mengunggah gambar, mengganti pengaturan privasi, dan menambahkan peringatan konten dengan ikon-ikon dibawah ini.",
+ "onboarding.skip": "Lewati",
+ "privacy.change": "Tentukan privasi status",
+ "privacy.direct.long": "Kirim hanya ke pengguna yang disebut",
+ "privacy.direct.short": "Langsung",
+ "privacy.private.long": "Kirim hanya ke pengikut",
+ "privacy.private.short": "Pribadi",
+ "privacy.public.long": "Kirim ke linimasa publik",
+ "privacy.public.short": "Publik",
+ "privacy.unlisted.long": "Tidak ditampilkan di linimasa publik",
+ "privacy.unlisted.short": "Tak Terdaftar",
+ "reply_indicator.cancel": "Batal",
+ "report.heading": "Laporan baru",
+ "report.placeholder": "Komentar tambahan",
+ "report.submit": "Kirim",
+ "report.target": "Melaporkan",
+ "search.status_by": "Status yang dibuat oleh {name}",
+ "search_results.total": "{count} {count, plural, one {hasil} other {hasil}}",
+ "status.cannot_reblog": "Postingan ini tidak dapat di-boost",
+ "search.placeholder": "Pencarian",
+ "search_results.total": "{count} {count, plural, one {hasil} other {hasil}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Hapus",
+ "status.favourite": "Difavoritkan",
+ "status.load_more": "Tampilkan semua",
+ "status.media_hidden": "Media disembunyikan",
+ "status.mention": "Balasan @{name}",
+ "status.open": "Tampilkan status ini",
+ "status.reblog": "Boost",
+ "status.reblogged_by": "di-boost {name}",
+ "status.reply": "Balas",
+ "status.replyAll": "Balas ke semua",
+ "status.report": "Laporkan @{name}",
+ "status.sensitive_toggle": "Klik untuk menampilkan",
+ "status.sensitive_warning": "Konten sensitif",
+ "status.show_less": "Tampilkan lebih sedikit",
+ "status.show_more": "Tampilkan semua",
+ "tabs_bar.compose": "Tulis",
+ "tabs_bar.federated_timeline": "Gabungan",
+ "tabs_bar.home": "Beranda",
+ "tabs_bar.local_timeline": "Lokal",
+ "tabs_bar.notifications": "Notifikasi",
+ "upload_area.title": "Seret & lepaskan untuk mengunggah",
+ "upload_button.label": "Tambahkan media",
+ "upload_form.undo": "Undo",
+ "upload_progress.label": "Mengunggah...",
+ "video_player.expand": "Tampilkan video",
+ "video_player.toggle_sound": "Suara",
+ "video_player.toggle_visible": "Tampilan",
+ "video_player.expand": "Tampilkan video",
+ "video_player.video_error": "Video tidak dapat diputar"
+}
diff --git a/app/javascript/mastodon/locales/index.js b/app/javascript/mastodon/locales/index.js
@@ -0,0 +1,57 @@
+import ar from './ar.json';
+import en from './en.json';
+import de from './de.json';
+import es from './es.json';
+import fa from './fa.json';
+import he from './he.json';
+import hr from './hr.json';
+import hu from './hu.json';
+import io from './io.json';
+import it from './it.json';
+import fr from './fr.json';
+import nl from './nl.json';
+import no from './no.json';
+import oc from './oc.json';
+import pt from './pt.json';
+import pt_br from './pt-BR.json';
+import uk from './uk.json';
+import fi from './fi.json';
+import eo from './eo.json';
+import ru from './ru.json';
+import ja from './ja.json';
+import zh_hk from './zh-HK.json';
+import zh_cn from './zh-CN.json';
+import bg from './bg.json';
+import id from './id.json';
+
+const locales = {
+ ar,
+ en,
+ de,
+ es,
+ fa,
+ he,
+ hr,
+ hu,
+ io,
+ it,
+ fr,
+ nl,
+ no,
+ oc,
+ pt,
+ 'pt-BR': pt_br,
+ uk,
+ fi,
+ eo,
+ ru,
+ ja,
+ 'zh-HK': zh_hk,
+ 'zh-CN': zh_cn,
+ bg,
+ id,
+};
+
+export default function getMessagesForLocale(locale) {
+ return locales[locale];
+};
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Blokusar @{name}",
+ "account.disclaimer": "Ca uzero esas de altra instaluro. Ca nombro forsan esas plu granda.",
+ "account.edit_profile": "Modifikar profilo",
+ "account.follow": "Sequar",
+ "account.followers": "Sequanti",
+ "account.follows": "Sequas",
+ "account.follows_you": "Sequas tu",
+ "account.mention": "Mencionar @{name}",
+ "account.mute": "Celar @{name}",
+ "account.posts": "Mesaji",
+ "account.report": "Denuncar @{name}",
+ "account.requested": "Vartante aprobo",
+ "account.unblock": "Desblokusar @{name}",
+ "account.unfollow": "Ne plus sequar",
+ "account.unmute": "Ne plus celar @{name}",
+ "boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo",
+ "column.blocks": "Blokusita uzeri",
+ "column.community": "Lokala tempolineo",
+ "column.favourites": "Favorati",
+ "column.follow_requests": "Demandi di sequado",
+ "column.home": "Hemo",
+ "column.mutes": "Celita uzeri",
+ "column.notifications": "Savigi",
+ "column.public": "Federata tempolineo",
+ "column_back_button.label": "Retro",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Quo esas en tua spirito?",
+ "compose_form.privacy_disclaimer": "Tua privata mesajo livresos a mencionata uzeri en {domains}. Ka tu fidas {domainsCount, plural, one {ta servero} other {ta serveri}}? Privateso di mesaji funcionas nur en instaluri di Mastodon. Se {domains} {domainsCount, plural, one {ne esas instaluro di Mastodon} other {ne esas instaluri di Mastodon}}, esos nula indiko, ke tua mesajo esas privata, ed ol povos repetesar od altre divenar videbla da nedezirinda recevanti.",
+ "compose_form.publish": "Siflar",
+ "compose_form.sensitive": "Markizar kontenajo kom trubliva",
+ "compose_form.spoiler": "Celar texto dop averto",
+ "compose_form.spoiler_placeholder": "Averto di kontenajo",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Insertar emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "La lokala tempolineo esas vakua. Skribez ulo publike por iniciar la agiveso!",
+ "empty_column.hashtag": "Esas ankore nulo en ta gretovorto.",
+ "empty_column.home": "Tu sequas ankore nulu. Vizitez {public} od uzez la serchilo por komencar e renkontrar altra uzeri.",
+ "empty_column.home.public_timeline": "la publika tempolineo",
+ "empty_column.notifications": "Tu havas ankore nula savigo. Komunikez kun altri por debutar la konverso.",
+ "empty_column.public": "Esas nulo hike! Skribez ulo publike, o manuale sequez uzeri de altra instaluri por plenigar ol.",
+ "follow_request.authorize": "Yurizar",
+ "follow_request.reject": "Refuzar",
+ "getting_started.apps": "Apliki diversa esas disponebla",
+ "getting_started.heading": "Debuto",
+ "getting_started.open_source_notice": "Mastodon esas programaro kun apertita kodexo. Tu povas kontributar o signalar problemi en GitHub ye {github}. {apps}.",
+ "home.column_settings.advanced": "Komplexa",
+ "home.column_settings.basic": "Simpla",
+ "home.column_settings.filter_regex": "Ekfiltrar per reguloza expresuri",
+ "home.column_settings.show_reblogs": "Montrar repeti",
+ "home.column_settings.show_replies": "Montrar respondi",
+ "home.settings": "Aranji di la kolumno",
+ "lightbox.close": "Klozar",
+ "loading_indicator.label": "Kargante...",
+ "media_gallery.toggle_visible": "Chanjar videbleso",
+ "missing_indicator.label": "Ne trovita",
+ "navigation_bar.blocks": "Blokusita uzeri",
+ "navigation_bar.community_timeline": "Lokala tempolineo",
+ "navigation_bar.edit_profile": "Modifikar profilo",
+ "navigation_bar.favourites": "Favorati",
+ "navigation_bar.follow_requests": "Demandi di sequado",
+ "navigation_bar.info": "Detaloza informi",
+ "navigation_bar.logout": "Ekirar",
+ "navigation_bar.mutes": "Celita uzeri",
+ "navigation_bar.preferences": "Preferi",
+ "navigation_bar.public_timeline": "Federata tempolineo",
+ "notification.favourite": "{name} favorizis tua mesajo",
+ "notification.follow": "{name} sequeskis tu",
+ "notification.reblog": "{name} repetis tua mesajo",
+ "notifications.clear": "Efacar savigi",
+ "notifications.clear_confirmation": "Ka tu esas certa, ke tu volas efacar omna tua savigi?",
+ "notifications.column_settings.alert": "Surtabla savigi",
+ "notifications.column_settings.favourite": "Favorati:",
+ "notifications.column_settings.follow": "Nova sequanti:",
+ "notifications.column_settings.mention": "Mencioni:",
+ "notifications.column_settings.reblog": "Repeti:",
+ "notifications.column_settings.show": "Montrar en kolumno",
+ "notifications.column_settings.sound": "Plear sono",
+ "notifications.settings": "Aranji di kolumno",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Aranjar privateso di mesaji",
+ "privacy.direct.long": "Sendar nur a mencionata uzeri",
+ "privacy.direct.short": "Direte",
+ "privacy.private.long": "Sendar nur a sequanti",
+ "privacy.private.short": "Private",
+ "privacy.public.long": "Sendar a publika tempolinei",
+ "privacy.public.short": "Publike",
+ "privacy.unlisted.long": "Ne montrar en publika tempolinei",
+ "privacy.unlisted.short": "Ne enlistigota",
+ "reply_indicator.cancel": "Nihiligar",
+ "report.heading": "Nova denunco",
+ "report.placeholder": "Plusa komenti",
+ "report.submit": "Sendar",
+ "report.target": "Denuncante",
+ "search.placeholder": "Serchez",
+ "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Efacar",
+ "status.favourite": "Favorizar",
+ "status.load_more": "Kargar pluse",
+ "status.media_hidden": "Kontenajo celita",
+ "status.mention": "Mencionar @{name}",
+ "status.open": "Detaligar ca mesajo",
+ "status.reblog": "Repetar",
+ "status.reblogged_by": "{name} repetita",
+ "status.reply": "Respondar",
+ "status.replyAll": "Respondar a filo",
+ "status.report": "Denuncar @{name}",
+ "status.sensitive_toggle": "Kliktar por vidar",
+ "status.sensitive_warning": "Trubliva kontenajo",
+ "status.show_less": "Montrar mine",
+ "status.show_more": "Montrar plue",
+ "tabs_bar.compose": "Kompozar",
+ "tabs_bar.federated_timeline": "Federata",
+ "tabs_bar.home": "Hemo",
+ "tabs_bar.local_timeline": "Lokala",
+ "tabs_bar.notifications": "Savigi",
+ "upload_area.title": "Tranar faligar por kargar",
+ "upload_button.label": "Adjuntar kontenajo",
+ "upload_form.undo": "Desfacar",
+ "upload_progress.label": "Kargante...",
+ "video_player.expand": "Extensar video",
+ "video_player.toggle_sound": "Acendar sono",
+ "video_player.toggle_visible": "Chanjar videbleso",
+ "video_player.video_error": "Video ne povus pleesar"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Blocca @{name}",
+ "account.disclaimer": "Questo utente si trova su un altro server. Questo numero potrebbe essere maggiore.",
+ "account.edit_profile": "Modifica profilo",
+ "account.follow": "Segui",
+ "account.followers": "Seguaci",
+ "account.follows": "Segue",
+ "account.follows_you": "Ti segue",
+ "account.mention": "Menziona @{name}",
+ "account.mute": "Silenzia @{name}",
+ "account.posts": "Posts",
+ "account.report": "Segnala @{name}",
+ "account.requested": "In attesa di approvazione",
+ "account.unblock": "Sblocca @{name}",
+ "account.unfollow": "Non seguire",
+ "account.unmute": "Non silenziare @{name}",
+ "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta",
+ "column.blocks": "Utenti bloccati",
+ "column.community": "Timeline locale",
+ "column.favourites": "Apprezzati",
+ "column.follow_requests": "Richieste di amicizia",
+ "column.home": "Home",
+ "column.mutes": "Utenti silenziati",
+ "column.notifications": "Notifiche",
+ "column.public": "Timeline federata",
+ "column_back_button.label": "Indietro",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "A cosa stai pensando?",
+ "compose_form.privacy_disclaimer": "Il tuo status privato verrà condiviso con gli utenti menzionati su {domains}. Ti fidi di {domainsCount, plural, one {quel server} other {quei server}}? Le impostazioni sulla privacy valgono solo su server Mastodon. Se {domains} {domainsCount, plural, one {non è un server Mastodon} other {non sono server Mastodon}}, non ci saranno indicazioni sulla privacy del tuo status, e potrebbe essere condiviso o reso visibile a destinatari indesiderati.",
+ "compose_form.publish": "Toot",
+ "compose_form.sensitive": "Segnala file come sensibile",
+ "compose_form.spoiler": "Nascondi testo con avvertimento",
+ "compose_form.spoiler_placeholder": "Content warning",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Inserisci emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!",
+ "empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.",
+ "empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.",
+ "empty_column.home.public_timeline": "la timeline pubblica",
+ "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.",
+ "empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio.",
+ "follow_request.authorize": "Autorizza",
+ "follow_request.reject": "Rifiuta",
+ "getting_started.apps": "Sono disponibili diverse app",
+ "getting_started.heading": "Come iniziare",
+ "getting_started.open_source_notice": "Mastodon è un software open source. Puoi contribuire o segnalare errori su GitHub all'indirizzo {github}. {apps}.",
+ "home.column_settings.advanced": "Avanzato",
+ "home.column_settings.basic": "Semplice",
+ "home.column_settings.filter_regex": "Filtra con espressioni regolari",
+ "home.column_settings.show_reblogs": "Mostra post condivisi",
+ "home.column_settings.show_replies": "Mostra risposte",
+ "home.settings": "Impostazioni colonna",
+ "lightbox.close": "Chiudi",
+ "loading_indicator.label": "Carico...",
+ "media_gallery.toggle_visible": "Imposta visibilità",
+ "missing_indicator.label": "Non trovato",
+ "navigation_bar.blocks": "Utenti bloccati",
+ "navigation_bar.community_timeline": "Timeline locale",
+ "navigation_bar.edit_profile": "Modifica profilo",
+ "navigation_bar.favourites": "Apprezzati",
+ "navigation_bar.follow_requests": "Richieste di amicizia",
+ "navigation_bar.info": "Informazioni estese",
+ "navigation_bar.logout": "Logout",
+ "navigation_bar.mutes": "Utenti silenziati",
+ "navigation_bar.preferences": "Impostazioni",
+ "navigation_bar.public_timeline": "Timeline federata",
+ "notification.favourite": "{name} ha apprezzato il tuo post",
+ "notification.follow": "{name} ha iniziato a seguirti",
+ "notification.reblog": "{name} ha condiviso il tuo post",
+ "notifications.clear": "Cancella notifiche",
+ "notifications.clear_confirmation": "Vuoi davvero cancellare tutte le notifiche?",
+ "notifications.column_settings.alert": "Notifiche desktop",
+ "notifications.column_settings.favourite": "Apprezzati:",
+ "notifications.column_settings.follow": "Nuovi seguaci:",
+ "notifications.column_settings.mention": "Menzioni:",
+ "notifications.column_settings.reblog": "Post condivisi:",
+ "notifications.column_settings.show": "Mostra in colonna",
+ "notifications.column_settings.sound": "Riproduci suono",
+ "notifications.settings": "Impostazioni colonna",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Modifica privacy post",
+ "privacy.direct.long": "Invia solo a utenti menzionati",
+ "privacy.direct.short": "Diretto",
+ "privacy.private.long": "Invia solo ai seguaci",
+ "privacy.private.short": "Privato",
+ "privacy.public.long": "Invia alla timeline pubblica",
+ "privacy.public.short": "Pubblico",
+ "privacy.unlisted.long": "Non mostrare sulla timeline pubblica",
+ "privacy.unlisted.short": "Non elencato",
+ "reply_indicator.cancel": "Annulla",
+ "report.heading": "Nuova segnalazione",
+ "report.placeholder": "Commenti aggiuntivi",
+ "report.submit": "Invia",
+ "report.target": "Invio la segnalazione",
+ "search.placeholder": "Cerca",
+ "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Elimina",
+ "status.favourite": "Apprezzato",
+ "status.load_more": "Mostra di più",
+ "status.media_hidden": "Allegato nascosto",
+ "status.mention": "Nomina @{name}",
+ "status.open": "Espandi questo post",
+ "status.reblog": "Condividi",
+ "status.reblogged_by": "{name} ha condiviso",
+ "status.reply": "Rispondi",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Segnala @{name}",
+ "status.sensitive_toggle": "Clicca per vedere",
+ "status.sensitive_warning": "Materiale sensibile",
+ "status.show_less": "Mostra meno",
+ "status.show_more": "Mostra di più",
+ "tabs_bar.compose": "Scrivi",
+ "tabs_bar.federated_timeline": "Federazione",
+ "tabs_bar.home": "Home",
+ "tabs_bar.local_timeline": "Locale",
+ "tabs_bar.notifications": "Notifiche",
+ "upload_area.title": "Trascina per caricare",
+ "upload_button.label": "Aggiungi file multimediale",
+ "upload_form.undo": "Annulla",
+ "upload_progress.label": "Sto caricando...",
+ "video_player.expand": "Espandi video",
+ "video_player.toggle_sound": "Attiva suono",
+ "video_player.toggle_visible": "Attiva visibilità",
+ "video_player.video_error": "Il video non può essere riprodotto"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "ブロック",
+ "account.disclaimer": "このユーザーは他のインスタンスに所属しているため、数字が正確で無い場合があります。",
+ "account.edit_profile": "プロフィールを編集",
+ "account.follow": "フォロー",
+ "account.followers": "フォロワー",
+ "account.follows": "フォロー",
+ "account.follows_you": "フォローされています",
+ "account.mention": "返信",
+ "account.mute": "ミュート",
+ "account.posts": "投稿",
+ "account.report": "通報",
+ "account.requested": "承認待ち",
+ "account.unblock": "ブロック解除",
+ "account.unfollow": "フォロー解除",
+ "account.unmute": "ミュート解除",
+ "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。",
+ "column.blocks": "ブロックしたユーザー",
+ "column.community": "ローカルタイムライン",
+ "column.favourites": "お気に入り",
+ "column.follow_requests": "フォローリクエスト",
+ "column.home": "ホーム",
+ "column.mutes": "ミュートしたユーザー",
+ "column.notifications": "通知",
+ "column.public": "連合タイムライン",
+ "column_back_button.label": "戻る",
+ "column_subheading.navigation": "ナビゲーション",
+ "column_subheading.settings": "設定",
+ "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。",
+ "compose_form.lock_disclaimer.lock": "非公開",
+ "compose_form.placeholder": "今なにしてる?",
+ "compose_form.privacy_disclaimer": "あなたの非公開トゥートは返信先ユーザーが所属する {domains} に送信されます。{domainsCount, plural, one {このサーバー} other {これらのサーバー}}は信頼できますか?投稿のプライバシー保護はMastodonサーバー内でのみ有効です。 {domains} {domainsCount, plural, one {がMastodonインスタンス} other {がMastodonインスタンス}}でない場合、あなたの投稿がプライベートなものとして扱われず、ブーストされたり予期しないユーザーに見られる可能性があります。",
+ "compose_form.publish": "トゥート",
+ "compose_form.sensitive": "メディアを閲覧注意としてマークする",
+ "compose_form.spoiler": "テキストを隠す",
+ "compose_form.spoiler_placeholder": "警告",
+ "confirmation_modal.cancel": "キャンセル",
+ "confirmations.block.confirm": "ブロック",
+ "confirmations.block.message": "本当に {name} をブロックしますか?",
+ "confirmations.delete.confirm": "削除",
+ "confirmations.delete.message": "本当に削除しますか?",
+ "confirmations.mute.confirm": "ミュート",
+ "confirmations.mute.message": "本当に {name} をミュートしますか?",
+ "emoji_button.activity": "活動",
+ "emoji_button.flags": "国旗",
+ "emoji_button.food": "食べ物",
+ "emoji_button.label": "絵文字を追加",
+ "emoji_button.nature": "自然",
+ "emoji_button.objects": "物",
+ "emoji_button.people": "人々",
+ "emoji_button.search": "検索...",
+ "emoji_button.symbols": "記号",
+ "emoji_button.travel": "旅行と場所",
+ "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!",
+ "empty_column.hashtag": "このハッシュタグはまだ使われていません。",
+ "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
+ "empty_column.home.public_timeline": "連合タイムライン",
+ "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
+ "empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
+ "follow_request.authorize": "許可",
+ "follow_request.reject": "拒否",
+ "getting_started.apps": "さまざまなアプリで利用できます。",
+ "getting_started.heading": "スタート",
+ "getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub({github})から開発に参加したり、問題を報告したりできます。 {apps}",
+ "home.column_settings.advanced": "上級者向け",
+ "home.column_settings.basic": "シンプル",
+ "home.column_settings.filter_regex": "正規表現でフィルター",
+ "home.column_settings.show_reblogs": "ブースト表示",
+ "home.column_settings.show_replies": "返信表示",
+ "home.settings": "カラム設定",
+ "lightbox.close": "閉じる",
+ "loading_indicator.label": "読み込み中...",
+ "media_gallery.toggle_visible": "表示切り替え",
+ "missing_indicator.label": "見つかりません",
+ "navigation_bar.blocks": "ブロックしたユーザー",
+ "navigation_bar.community_timeline": "ローカルタイムライン",
+ "navigation_bar.edit_profile": "プロフィールを編集",
+ "navigation_bar.favourites": "お気に入り",
+ "navigation_bar.follow_requests": "フォローリクエスト",
+ "navigation_bar.info": "サーバー情報",
+ "navigation_bar.logout": "ログアウト",
+ "navigation_bar.mutes": "ミュートしたユーザー",
+ "navigation_bar.preferences": "ユーザー設定",
+ "navigation_bar.public_timeline": "連合タイムライン",
+ "notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました",
+ "notification.follow": "{name} さんにフォローされました",
+ "notification.reblog": "{name} さんがあなたのトゥートをブーストしました",
+ "notifications.clear": "通知を消去",
+ "notifications.clear_confirmation": "本当に通知を消去しますか?",
+ "notifications.column_settings.alert": "デスクトップ通知",
+ "notifications.column_settings.favourite": "お気に入り",
+ "notifications.column_settings.follow": "新しいフォロワー",
+ "notifications.column_settings.mention": "返信",
+ "notifications.column_settings.reblog": "ブースト",
+ "notifications.column_settings.show": "カラムに表示",
+ "notifications.column_settings.sound": "通知音を再生",
+ "notifications.settings": "カラム設定",
+ "onboarding.done": "完了",
+ "onboarding.next": "次へ",
+ "onboarding.page_five.public_timelines": "連合タイムラインでは{domain}の人がフォローしているMastodon全体での公開投稿を表示します。同じくローカルタイムラインでは{domain}のみの公開投稿を表示します。",
+ "onboarding.page_four.home": "「ホーム」タイムラインではあなたがフォローしている人の投稿を表示します。",
+ "onboarding.page_four.notifications": "「通知」ではあなたへの他の人からの関わりを表示します。",
+ "onboarding.page_one.federation": "Mastodonは誰でも参加できるSNSです。",
+ "onboarding.page_one.handle": "あなたは今数あるMastodonインスタンスの1つである{domain}にいます。あなたのフルハンドルは{handle}です。",
+ "onboarding.page_one.welcome": "Mastodonへようこそ!",
+ "onboarding.page_six.admin": "あなたのインスタンスの管理者は{admin}です。",
+ "onboarding.page_six.almost_done": "以上です。",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "iOS、Androidあるいは他のプラットフォームで使える{apps}があります。",
+ "onboarding.page_six.github": "MastodonはOSSです。バグ報告や機能要望あるいは貢献を{github}から行なえます。",
+ "onboarding.page_six.guidelines": "コミュニティガイドライン",
+ "onboarding.page_six.read_guidelines": "{guidelines}を読むことを忘れないようにしてください。",
+ "onboarding.page_six.various_app": "様々なモバイルアプリ",
+ "onboarding.page_three.profile": "「プロフィールを編集」から、あなたの自己紹介や表示名を変更できます。またそこでは他の設定ができます。",
+ "onboarding.page_three.search": "検索バーで、{illustration}や{introductions}のように特定のハッシュタグの投稿を見たり、ユーザーを探したりできます。",
+ "onboarding.page_two.compose": "フォームから投稿できます。イメージや、公開範囲の設定や、表示時の警告の設定は下部のアイコンから行なえます。",
+ "onboarding.skip": "スキップ",
+ "privacy.change": "投稿のプライバシーを変更",
+ "privacy.direct.long": "メンションしたユーザーだけに公開",
+ "privacy.direct.short": "ダイレクト",
+ "privacy.private.long": "フォロワーだけに公開",
+ "privacy.private.short": "非公開",
+ "privacy.public.long": "公開TLに投稿する",
+ "privacy.public.short": "公開",
+ "privacy.unlisted.long": "公開TLで表示しない",
+ "privacy.unlisted.short": "未収載",
+ "reply_indicator.cancel": "キャンセル",
+ "report.heading": "新規通報",
+ "report.placeholder": "コメント",
+ "report.submit": "通報する",
+ "report.target": "問題のユーザー",
+ "search.placeholder": "検索",
+ "search_results.total": "{count, number} 件の結果",
+ "status.cannot_reblog": "この投稿はブーストできません",
+ "status.delete": "削除",
+ "status.favourite": "お気に入り",
+ "status.load_more": "もっと見る",
+ "status.media_hidden": "非表示のメデイア",
+ "status.mention": "返信",
+ "status.open": "詳細を表示",
+ "status.reblog": "ブースト",
+ "status.reblogged_by": "{name} さんにブーストされました",
+ "status.reply": "返信",
+ "status.replyAll": "全員に返信",
+ "status.report": "通報",
+ "status.sensitive_toggle": "クリックして表示",
+ "status.sensitive_warning": "閲覧注意",
+ "status.show_less": "隠す",
+ "status.show_more": "もっと見る",
+ "tabs_bar.compose": "投稿",
+ "tabs_bar.federated_timeline": "連合",
+ "tabs_bar.home": "ホーム",
+ "tabs_bar.local_timeline": "ローカル",
+ "tabs_bar.notifications": "通知",
+ "upload_area.title": "ドラッグ&ドロップでアップロード",
+ "upload_button.label": "メディアを追加",
+ "upload_form.undo": "やり直す",
+ "upload_progress.label": "アップロード中…",
+ "video_player.expand": "動画の詳細",
+ "video_player.toggle_sound": "音の切り替え",
+ "video_player.toggle_visible": "表示切り替え",
+ "video_player.video_error": "動画の再生に失敗しました"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Blokkeer @{name}",
+ "account.disclaimer": "This user is from another instance. This number may be larger.",
+ "account.edit_profile": "Profiel bewerken",
+ "account.follow": "Volgen",
+ "account.followers": "Volgers",
+ "account.follows": "Volgt",
+ "account.follows_you": "Volgt jou",
+ "account.mention": "Vermeld @{name}",
+ "account.mute": "Negeer @{name}",
+ "account.posts": "Berichten",
+ "account.report": "Rapporteer @{name}",
+ "account.requested": "Wacht op goedkeuring",
+ "account.unblock": "Deblokkeer @{name}",
+ "account.unfollow": "Ontvolgen",
+ "account.unmute": "Negeer @{name} niet meer",
+ "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
+ "column.blocks": "Geblokkeerde gebruikers",
+ "column.community": "Lokale tijdlijn",
+ "column.favourites": "Favorieten",
+ "column.follow_requests": "Follow requests",
+ "column.home": "Jouw tijdlijn",
+ "column.mutes": "Genegeerde gebruikers",
+ "column.notifications": "Meldingen",
+ "column.public": "Globale tijdlijn",
+ "column_back_button.label": "terug",
+ "column_subheading.navigation": "Navigatie",
+ "column_subheading.settings": "Instellingen",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Wat wil je kwijt?",
+ "compose_form.privacy_disclaimer": "Jouw privétoot wordt afgeleverd aan de vermelde gebruikers op {domains}. Vertrouw jij {domainsCount, plural, one {die server} other {die servers}}? Het privé plaatsen van toots werkt alleen op Mastodon-servers. Wanneer {domains} {domainsCount, plural, one {geen Mastodon-server is} other {geen Mastodon-servers zijn}}, dan wordt er niet aangegeven dat de toot privé is, waardoor het kan worden geboost of op een andere manier zichtbaar wordt gemaakt voor mensen waarvoor het niet was bedoeld.",
+ "compose_form.publish": "Toot",
+ "compose_form.sensitive": "Media als gevoelig markeren",
+ "compose_form.spoiler": "Tekst achter waarschuwing verbergen",
+ "compose_form.spoiler_placeholder": "Waarschuwingstekst",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activiteiten",
+ "emoji_button.flags": "Vlaggen",
+ "emoji_button.food": "Eten en drinken",
+ "emoji_button.label": "Emoji toevoegen",
+ "emoji_button.nature": "Natuur",
+ "emoji_button.objects": "Voorwerpen",
+ "emoji_button.people": "Mensen",
+ "emoji_button.search": "Zoeken...",
+ "emoji_button.symbols": "Symbolen",
+ "emoji_button.travel": "Reizen en plekken",
+ "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+ "empty_column.hashtag": "There is nothing in this hashtag yet.",
+ "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
+ "empty_column.home.public_timeline": "the public timeline",
+ "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+ "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+ "follow_request.authorize": "Authorize",
+ "follow_request.reject": "Reject",
+ "getting_started.apps": "Er zijn meerdere apps beschikbaar",
+ "getting_started.heading": "Beginnen",
+ "getting_started.open_source_notice": "Mastodon is open-sourcesoftware. Je kunt bijdragen of problemen melden op GitHub via {github}. {apps}.",
+ "home.column_settings.advanced": "Advanced",
+ "home.column_settings.basic": "Basic",
+ "home.column_settings.filter_regex": "Filter out by regular expressions",
+ "home.column_settings.show_reblogs": "Show boosts",
+ "home.column_settings.show_replies": "Show replies",
+ "home.settings": "Column settings",
+ "lightbox.close": "Sluiten",
+ "loading_indicator.label": "Laden…",
+ "media_gallery.toggle_visible": "Toggle visibility",
+ "missing_indicator.label": "Not found",
+ "navigation_bar.blocks": "Geblokkeerde gebruikers",
+ "navigation_bar.community_timeline": "Lokale tijdlijn",
+ "navigation_bar.edit_profile": "Profiel bewerken",
+ "navigation_bar.favourites": "Favorieten",
+ "navigation_bar.follow_requests": "Volgverzoeken",
+ "navigation_bar.info": "Uitgebreide informatie",
+ "navigation_bar.logout": "Afmelden",
+ "navigation_bar.mutes": "Genegeerde gebruikers",
+ "navigation_bar.preferences": "Instellingen",
+ "navigation_bar.public_timeline": "Globale tijdlijn",
+ "notification.favourite": "{name} markeerde jouw toot als favoriet",
+ "notification.follow": "{name} volgt jou nu",
+ "notification.reblog": "{name} boostte jouw toot",
+ "notifications.clear": "Meldingen verwijderen",
+ "notifications.clear_confirmation": "Weet je zeker dat je al jouw meldingen wilt verwijderen?",
+ "notifications.column_settings.alert": "Desktopmeldingen",
+ "notifications.column_settings.favourite": "Favorieten:",
+ "notifications.column_settings.follow": "Nieuwe volgers:",
+ "notifications.column_settings.mention": "Vermeldingen:",
+ "notifications.column_settings.reblog": "Boosts:",
+ "notifications.column_settings.show": "In kolom tonen",
+ "notifications.column_settings.sound": "Geluid afspelen",
+ "notifications.settings": "Kolom-instellingen",
+ "onboarding.done": "Done",
+ "onboarding.next": "Volgende",
+ "onboarding.page_five.public_timelines": "De lokale tijdlijn toont openbare toots van iedereen op {domain}. De globale tijdlijn toont openbare toots van iedereen die door gebruikers van {domain} worden gevolgd, dus ook mensen van andere Mastodon-servers. Dit zijn de openbare tijdlijnen en vormen een uitstekende manier om nieuwe mensen te ontdekken.",
+ "onboarding.page_four.home": "Jouw tijdlijn laat toots zien van mensen die jij volgt.",
+ "onboarding.page_four.notifications": "De kolom met meldingen toont alle interacties die je met andere Mastodon-gebruikers hebt.",
+ "onboarding.page_one.federation": "Mastodon is een netwerk van onafhankelijke servers die samen een groot sociaal netwerk vormen.",
+ "onboarding.page_one.handle": "Je bevindt je nu op {domain}, dus is jouw volledige Mastodon-adres {handle}",
+ "onboarding.page_one.welcome": "Welkom op Mastodon!",
+ "onboarding.page_six.admin": "De beheerder van jouw Mastodon-server is {admin}.",
+ "onboarding.page_six.almost_done": "Bijna klaar...",
+ "onboarding.page_six.appetoot": "Veel succes!",
+ "onboarding.page_six.apps_available": "Er zijn {apps} beschikbaar voor iOS, Android en andere platformen.",
+ "onboarding.page_six.github": "Mastodon kost niets, en is open-source- en vrije software. Je kan bugs melden, nieuwe mogelijkheden aanvragen en als ontwikkelaar meewerken op {github}.",
+ "onboarding.page_six.guidelines": "communityrichtlijnen",
+ "onboarding.page_six.read_guidelines": "Vergeet niet de {guidelines} van {domain} te lezen!",
+ "onboarding.page_six.various_app": "mobiele apps",
+ "onboarding.page_three.profile": "Bewerk jouw profiel om jouw avatar, bio en weergavenaam te veranderen. Daar vind je ook andere instellingen.",
+ "onboarding.page_three.search": "Gebruik de zoekbalk linksboven om andere mensen op Mastodon te vinden en om te zoeken op hashtags, zoals {illustration} en {introductions}. Om iemand te vinden die niet op deze Mastodon-server zit, moet je het volledige Mastodon-adres van deze persoon invoeren.",
+ "onboarding.page_two.compose": "Schrijf berichten (wij noemen dit toots) in het tekstvak in de linkerkolom. Je kan met de pictogrammen daaronder afbeeldingen uploaden, privacy-instellingen veranderen en je tekst een waarschuwing meegeven.",
+ "onboarding.skip": "Overslaan",
+ "privacy.change": "Privacy toot aanpassen",
+ "privacy.direct.long": "Toot alleen naar vermelde gebruikers",
+ "privacy.direct.short": "Direct",
+ "privacy.private.long": "Alleen aan volgers tonen",
+ "privacy.private.short": "Alleen volgers",
+ "privacy.public.long": "Op openbare tijdlijnen tonen",
+ "privacy.public.short": "Openbaar",
+ "privacy.unlisted.long": "Niet op openbare tijdlijnen tonen",
+ "privacy.unlisted.short": "Minder openbaar",
+ "reply_indicator.cancel": "Annuleren",
+ "report.heading": "New report",
+ "report.placeholder": "Additional comments",
+ "report.submit": "Submit",
+ "report.target": "Reporting",
+ "search.placeholder": "Zoeken",
+ "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Verwijderen",
+ "status.favourite": "Favoriet",
+ "status.load_more": "Load more",
+ "status.media_hidden": "Media hidden",
+ "status.mention": "@{name} vermelden",
+ "status.open": "Expand this status",
+ "status.reblog": "Boost",
+ "status.reblogged_by": "{name} boostte",
+ "status.reply": "Reageren",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Report @{name}",
+ "status.sensitive_toggle": "Klik om te zien",
+ "status.sensitive_warning": "Gevoelige inhoud",
+ "status.show_less": "Minder tonen",
+ "status.show_more": "Meer tonen",
+ "tabs_bar.compose": "Schrijven",
+ "tabs_bar.federated_timeline": "Federated",
+ "tabs_bar.home": "Jouw tijdlijn",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Meldingen",
+ "upload_area.title": "Drag & drop to upload",
+ "upload_button.label": "Media toevoegen",
+ "upload_form.undo": "Ongedaan maken",
+ "upload_progress.label": "Uploading...",
+ "video_player.expand": "Expand video",
+ "video_player.toggle_sound": "Geluid in-/uitschakelen",
+ "video_player.toggle_visible": "Toggle visibility",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Blokkér @{name}",
+ "account.disclaimer": "Denne brukeren er fra en annen instans. Dette tallet kan være høyere.",
+ "account.edit_profile": "Rediger profil",
+ "account.follow": "Følg",
+ "account.followers": "Følgere",
+ "account.follows_you": "Følger deg",
+ "account.follows": "Følger",
+ "account.mention": "Nevn @{name}",
+ "account.mute": "Demp @{name}",
+ "account.posts": "Innlegg",
+ "account.report": "Rapportér @{name}",
+ "account.requested": "Venter på godkjennelse",
+ "account.unblock": "Avblokker @{name}",
+ "account.unfollow": "Avfølg",
+ "account.unmute": "Avdemp @{name}",
+ "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
+ "column.blocks": "Blokkerte brukere",
+ "column.community": "Lokal tidslinje",
+ "column.favourites": "Likt",
+ "column.follow_requests": "Følgeforespørsler",
+ "column.home": "Hjem",
+ "column.mutes": "Muted users",
+ "column.notifications": "Varslinger",
+ "column.public": "Felles tidslinje",
+ "column_back_button.label": "Tilbake",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Hva har du på hjertet?",
+ "compose_form.privacy_disclaimer": "Din private status vil leveres til nevnte brukere på {domains}. Stoler du på {domainsCount, plural, one {den serveren} other {de serverne}}? Synlighet fungerer kun på Mastodon-instanser. Hvis {domains} {domainsCount, plural, one {ike er en Mastodon-instans} other {ikke er Mastodon-instanser}}, vil det ikke indikeres at posten din er privat, og den kan kanskje bli fremhevd eller på annen måte bli synlig for uventede mottakere.",
+ "compose_form.publish": "Tut",
+ "compose_form.sensitive": "Merk media som følsomt",
+ "compose_form.spoiler": "Skjul tekst bak advarsel",
+ "compose_form.spoiler_placeholder": "Innholdsadvarsel",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Sett inn emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "Den lokale tidslinjen er tom. Skriv noe offentlig for å få snøballen til å rulle!",
+ "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.",
+ "empty_column.home": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.",
+ "empty_column.home.public_timeline": "en offentlig tidslinje",
+ "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.",
+ "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp",
+ "follow_request.authorize": "Autorisér",
+ "follow_request.reject": "Avvis",
+ "getting_started.apps": "Diverse apper er tilgjengelige",
+ "getting_started.heading": "Kom i gang",
+ "getting_started.open_source_notice": "Mastodon er fri programvare. Du kan bidra eller rapportere problemer på GitHub på {github}. {apps}.",
+ "home.column_settings.advanced": "Advansert",
+ "home.column_settings.basic": "Enkel",
+ "home.column_settings.filter_regex": "Filtrér med regulære uttrykk",
+ "home.column_settings.show_reblogs": "Vis fremhevinger",
+ "home.column_settings.show_replies": "Vis svar",
+ "home.settings": "Kolonneinnstillinger",
+ "lightbox.close": "Lukk",
+ "loading_indicator.label": "Laster...",
+ "media_gallery.toggle_visible": "Veksle synlighet",
+ "missing_indicator.label": "Ikke funnet",
+ "navigation_bar.blocks": "Blokkerte brukere",
+ "navigation_bar.community_timeline": "Lokal tidslinje",
+ "navigation_bar.edit_profile": "Rediger profil",
+ "navigation_bar.favourites": "Likt",
+ "navigation_bar.follow_requests": "Følgeforespørsler",
+ "navigation_bar.info": "Utvidet informasjon",
+ "navigation_bar.logout": "Logg ut",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "Preferanser",
+ "navigation_bar.public_timeline": "Felles tidslinje",
+ "notification.favourite": "{name} likte din status",
+ "notification.follow": "{name} fulgte deg",
+ "notification.reblog": "{name} fremhevde din status",
+ "notifications.clear": "Fjern varsler",
+ "notifications.clear_confirmation": "Er du sikker på at du vil fjerne alle dine varsler?",
+ "notifications.column_settings.alert": "Skrivebordsvarslinger",
+ "notifications.column_settings.favourite": "Likt:",
+ "notifications.column_settings.follow": "Nye følgere:",
+ "notifications.column_settings.mention": "Nevninger:",
+ "notifications.column_settings.reblog": "Fremhevinger:",
+ "notifications.column_settings.show": "Vis i kolonne",
+ "notifications.column_settings.sound": "Spill lyd",
+ "notifications.settings": "Kolonneinstillinger",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Justér synlighet",
+ "privacy.direct.long": "Post kun til nevnte brukere",
+ "privacy.direct.short": "Direkte",
+ "privacy.private.long": "Post kun til følgere",
+ "privacy.private.short": "Privat",
+ "privacy.public.long": "Post kun til offentlige tidslinjer",
+ "privacy.public.short": "Offentlig",
+ "privacy.unlisted.long": "Ikke vis i offentlige tidslinjer",
+ "privacy.unlisted.short": "Uoppført",
+ "reply_indicator.cancel": "Avbryt",
+ "report.heading": "Ny rapport",
+ "report.placeholder": "Tilleggskommentarer",
+ "report.submit": "Send inn",
+ "report.target": "Rapporterer",
+ "search.placeholder": "Søk",
+ "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Slett",
+ "status.favourite": "Lik",
+ "status.load_more": "Last mer",
+ "status.media_hidden": "Media skjult",
+ "status.mention": "Nevn @{name}",
+ "status.open": "Utvid denne statusen",
+ "status.reblog": "Fremhev",
+ "status.reblogged_by": "Fremhevd av {name}",
+ "status.reply": "Svar",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Rapporter @{name}",
+ "status.sensitive_toggle": "Klikk for å vise",
+ "status.sensitive_warning": "Følsomt innhold",
+ "status.show_less": "Vis mindre",
+ "status.show_more": "Vis mer",
+ "tabs_bar.compose": "Komponer",
+ "tabs_bar.federated_timeline": "Felles",
+ "tabs_bar.home": "Hjem",
+ "tabs_bar.local_timeline": "Lokal",
+ "tabs_bar.notifications": "Varslinger",
+ "upload_area.title": "Dra og slipp for å laste opp",
+ "upload_button.label": "Legg til media",
+ "upload_form.undo": "Angre",
+ "upload_progress.label": "Laster opp...",
+ "video_player.expand": "Utvid video",
+ "video_player.toggle_sound": "Veksle lyd",
+ "video_player.toggle_visible": "Veksle synlighet",
+ "video_player.video_error": "Video could not be played"
+}
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Blocar",
+ "account.disclaimer": "Aqueste compte es sus una autra instància. Los nombres pòdon èsser mai grandes.",
+ "account.edit_profile": "Modificar lo perfil",
+ "account.follow": "Sègre",
+ "account.followers": "Abonats",
+ "account.follows": "Abonaments",
+ "account.follows_you": "Vos sèc",
+ "account.mention": "Mencionar",
+ "account.mute": "Rescondre",
+ "account.posts": "Estatuts",
+ "account.report": "Senhalar",
+ "account.requested": "Invitacion mandada",
+ "account.unblock": "Desblocar",
+ "account.unfollow": "Quitar de sègre",
+ "account.unmute": "Quitar de rescondre",
+ "boost_modal.combo": "You can press {combo} to skip this next time",
+ "column.blocks": "Personas blocadas",
+ "column.community": "Fil public local",
+ "column.favourites": "Favorits",
+ "column.follow_requests": "Demandas d’abonament",
+ "column.home": "Acuèlh",
+ "column.mutes": "Muted users",
+ "column.notifications": "Notificacions",
+ "column.public": "Fil public global",
+ "column_back_button.label": "Tornar",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "A de qué pensatz ?",
+ "compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz d’aqueste{domainsCount, plural, one { servidor} other {s servidors}} per divulgar pas vòstre estatut ? Los estatuts privats foncionan pas que sus las instàncias a Mastodons. Se {domains} {domainsCount, plural, one {es pas una instància a Mastodon} other {son pas d'instàncias a Mastodon}}, i aurà pas d’indicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists",
+ "compose_form.publish": "Tut",
+ "compose_form.sensitive": "Marcar lo mèdia coma embarrassant",
+ "compose_form.spoiler": "Rescondre lo tèxte darrièr un avertiment",
+ "compose_form.spoiler_placeholder": "Avertiment",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Inserir un emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "Lo fil public local es void. Escribètz quicòm per lo garnir !",
+ "empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag",
+ "empty_column.home": "Pel moment segètz pas segun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
+ "empty_column.home.public_timeline": "lo fil public",
+ "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
+ "empty_column.public": "I a pas res aquí ! Escribètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo fil public.",
+ "follow_request.authorize": "Autorizar",
+ "follow_request.reject": "Regetar",
+ "getting_started.apps": "Various apps are available",
+ "getting_started.heading": "Per començar",
+ "getting_started.open_source_notice": "Mastodon es un logicial liure. Podètz contribuir e mandar vòstres comentaris e rapòrt de bug via{github} sus GitHub.",
+ "home.column_settings.advanced": "Avançat",
+ "home.column_settings.basic": "Basic",
+ "home.column_settings.filter_regex": "Filtrar amb una expression racionala",
+ "home.column_settings.show_reblogs": "Mostrar los partatges",
+ "home.column_settings.show_replies": "Mostrar las responsas",
+ "home.settings": "Paramètres de la colomna",
+ "lightbox.close": "Tampar",
+ "loading_indicator.label": "Cargament…",
+ "media_gallery.toggle_visible": "Modificar la visibilitat",
+ "missing_indicator.label": "Pas trobat",
+ "navigation_bar.blocks": "Personas blocadas",
+ "navigation_bar.community_timeline": "Fil public local",
+ "navigation_bar.edit_profile": "Modificar lo perfil",
+ "navigation_bar.favourites": "Favorits",
+ "navigation_bar.follow_requests": "Demandas d'abonament",
+ "navigation_bar.info": "Mai informacions",
+ "navigation_bar.logout": "Desconnexion",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "Preferéncias",
+ "navigation_bar.public_timeline": "Fil public global",
+ "notification.favourite": "{name} a apondut a sos favorits :",
+ "notification.follow": "{name} vos sèc.",
+ "notification.reblog": "{name} a partejat vòstre estatut :",
+ "notifications.clear": "Levar",
+ "notifications.clear_confirmation": "Volètz vertadièrament levar totas vòstras las notificacions ?",
+ "notifications.column_settings.alert": "Notificacions localas",
+ "notifications.column_settings.favourite": "Favorits :",
+ "notifications.column_settings.follow": "Nòus abonats :",
+ "notifications.column_settings.mention": "Mencions :",
+ "notifications.column_settings.reblog": "Partatges :",
+ "notifications.column_settings.show": "Mostrar dins la colomna",
+ "notifications.column_settings.sound": "Emetre un son",
+ "notifications.settings": "Paramètres de la colomna",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Ajustar la confidencialitat del messatge",
+ "privacy.direct.long": "Mostrar pas qu'a las personas mencionadas",
+ "privacy.direct.short": "Dirècte",
+ "privacy.private.long": "Mostrar pas qu'a vòstres abonats",
+ "privacy.private.short": "Privat",
+ "privacy.public.long": "Mostrar dins los fils publics",
+ "privacy.public.short": "Public",
+ "privacy.unlisted.long": "Mostrar pas dins los fils publics",
+ "privacy.unlisted.short": "Pas-listat",
+ "reply_indicator.cancel": "Anullar",
+ "report.heading": "Nòu senhalament",
+ "report.placeholder": "Comentaris addicionals",
+ "report.submit": "Mandat",
+ "report.target": "Senhalament",
+ "search.placeholder": "Recercar",
+ "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Escafar",
+ "status.favourite": "Apondre als favorits",
+ "status.load_more": "Cargar mai",
+ "status.media_hidden": "Mèdia rescondut",
+ "status.mention": "Mencionar",
+ "status.open": "Desplegar aqueste estatut",
+ "status.reblog": "Partejar",
+ "status.reblogged_by": "{name} a partejat :",
+ "status.reply": "Respondre",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Senhalar @{name}",
+ "status.sensitive_toggle": "Clicar per mostrar",
+ "status.sensitive_warning": "Contengut embarrassant",
+ "status.show_less": "Tornar plegar",
+ "status.show_more": "Desplegar",
+ "tabs_bar.compose": "Compausar",
+ "tabs_bar.federated_timeline": "Fil public global",
+ "tabs_bar.home": "Acuèlh",
+ "tabs_bar.local_timeline": "Fil public local",
+ "tabs_bar.notifications": "Notifications",
+ "upload_area.title": "Lisatz e depausatz per mandar",
+ "upload_button.label": "Apondre un mèdia",
+ "upload_form.undo": "Anullar",
+ "upload_progress.label": "Mandadís…",
+ "video_player.expand": "Expand video",
+ "video_player.toggle_sound": "Activar/Desactivar lo son",
+ "video_player.toggle_visible": "Mostrar/Rescondre la vidèo",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Bloquear @{name}",
+ "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
+ "account.edit_profile": "Editar perfil",
+ "account.follow": "Seguir",
+ "account.followers": "Seguidores",
+ "account.follows": "Segue",
+ "account.follows_you": "É teu seguidor",
+ "account.mention": "Mencionar @{name}",
+ "account.mute": "Silenciar @{name}",
+ "account.posts": "Posts",
+ "account.report": "Denunciar @{name}",
+ "account.requested": "A aguardar aprovação",
+ "account.unblock": "Não bloquear @{name}",
+ "account.unfollow": "Deixar de seguir",
+ "account.unmute": "Não silenciar @{name}",
+ "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
+ "column.blocks": "Utilizadores Bloqueados",
+ "column.community": "Local",
+ "column.favourites": "Favoritos",
+ "column.follow_requests": "Seguidores Pendentes",
+ "column.home": "Home",
+ "column.mutes": "Utilizadores silenciados",
+ "column.notifications": "Notificações",
+ "column.public": "Global",
+ "column_back_button.label": "Voltar",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Em que estás a pensar?",
+ "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
+ "compose_form.publish": "Publicar",
+ "compose_form.sensitive": "Marcar media como conteúdo sensível",
+ "compose_form.spoiler": "Esconder texto com aviso",
+ "compose_form.spoiler_placeholder": "Aviso de conteúdo",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Inserir Emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "Ainda não existem conteúdo local para mostrar!",
+ "empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag",
+ "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
+ "empty_column.home.public_timeline": "global",
+ "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
+ "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
+ "follow_request.authorize": "Autorizar",
+ "follow_request.reject": "Rejeitar",
+ "getting_started.apps": "Existem várias aplicações disponíveis",
+ "getting_started.heading": "Primeiros passos",
+ "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
+ "home.column_settings.advanced": "Avançado",
+ "home.column_settings.basic": "Básico",
+ "home.column_settings.filter_regex": "Filtrar com uma expressão regular",
+ "home.column_settings.show_reblogs": "Mostrar as partilhas",
+ "home.column_settings.show_replies": "Mostrar as respostas",
+ "home.settings": "Parâmetros da listagem Home",
+ "lightbox.close": "Fechar",
+ "loading_indicator.label": "Carregando...",
+ "media_gallery.toggle_visible": "Esconder/Mostrar",
+ "missing_indicator.label": "Não encontrado",
+ "navigation_bar.blocks": "Utilizadores bloqueados",
+ "navigation_bar.community_timeline": "Local",
+ "navigation_bar.edit_profile": "Editar perfil",
+ "navigation_bar.favourites": "Favoritos",
+ "navigation_bar.follow_requests": "Seguidores pendentes",
+ "navigation_bar.info": "Mais informações",
+ "navigation_bar.logout": "Sair",
+ "navigation_bar.mutes": "Utilizadores silenciados",
+ "navigation_bar.preferences": "Preferências",
+ "navigation_bar.public_timeline": "Global",
+ "notification.favourite": "{name} adicionou o teu post aos favoritos",
+ "notification.follow": "{name} seguiu-te",
+ "notification.reblog": "{name} partilhou o teu post",
+ "notifications.clear": "Limpar notificações",
+ "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
+ "notifications.column_settings.alert": "Notificações no computador",
+ "notifications.column_settings.favourite": "Favoritos:",
+ "notifications.column_settings.follow": "Novos seguidores:",
+ "notifications.column_settings.mention": "Menções:",
+ "notifications.column_settings.reblog": "Partilhas:",
+ "notifications.column_settings.show": "Mostrar nas colunas",
+ "notifications.column_settings.sound": "Reproduzir som",
+ "notifications.settings": "Parâmetros da listagem de Notificações",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Ajustar a privacidade da mensagem",
+ "privacy.direct.long": "Apenas para utilizadores mencionados",
+ "privacy.direct.short": "Directo",
+ "privacy.private.long": "Apenas para os seguidores",
+ "privacy.private.short": "Privado",
+ "privacy.public.long": "Publicar em todos os feeds",
+ "privacy.public.short": "Público",
+ "privacy.unlisted.long": "Não publicar nos feeds públicos",
+ "privacy.unlisted.short": "Não listar",
+ "reply_indicator.cancel": "Cancelar",
+ "report.heading": "Nova denúncia",
+ "report.placeholder": "Comentários adicionais",
+ "report.submit": "Enviar",
+ "report.target": "Denunciar",
+ "search.placeholder": "Pesquisar",
+ "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Eliminar",
+ "status.favourite": "Adicionar aos favoritos",
+ "status.load_more": "Carregar mais",
+ "status.media_hidden": "Media escondida",
+ "status.mention": "Mencionar @{name}",
+ "status.open": "Expandir",
+ "status.reblog": "Partilhar",
+ "status.reblogged_by": "{name} partilhou",
+ "status.reply": "Responder",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Denúnciar @{name}",
+ "status.sensitive_toggle": "Clique para ver",
+ "status.sensitive_warning": "Conteúdo sensível",
+ "status.show_less": "Mostrar menos",
+ "status.show_more": "Mostrar mais",
+ "tabs_bar.compose": "Criar",
+ "tabs_bar.federated_timeline": "Global",
+ "tabs_bar.home": "Home",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Notificações",
+ "upload_area.title": "Arraste e solte para enviar",
+ "upload_button.label": "Adicionar media",
+ "upload_form.undo": "Anular",
+ "upload_progress.label": "A gravar...",
+ "video_player.expand": "Expandir vídeo",
+ "video_player.toggle_sound": "Ligar/Desligar som",
+ "video_player.toggle_visible": "Ligar/Desligar vídeo",
+ "video_player.video_error": "Não é possível ver o vídeo"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Bloquear @{name}",
+ "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
+ "account.edit_profile": "Editar perfil",
+ "account.follow": "Seguir",
+ "account.followers": "Seguidores",
+ "account.follows": "Segue",
+ "account.follows_you": "É teu seguidor",
+ "account.mention": "Mencionar @{name}",
+ "account.mute": "Silenciar @{name}",
+ "account.posts": "Posts",
+ "account.report": "Denunciar @{name}",
+ "account.requested": "A aguardar aprovação",
+ "account.unblock": "Não bloquear @{name}",
+ "account.unfollow": "Deixar de seguir",
+ "account.unmute": "Não silenciar @{name}",
+ "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
+ "column.blocks": "Utilizadores Bloqueados",
+ "column.community": "Local",
+ "column.favourites": "Favoritos",
+ "column.follow_requests": "Seguidores Pendentes",
+ "column.home": "Home",
+ "column.mutes": "Utilizadores silenciados",
+ "column.notifications": "Notificações",
+ "column.public": "Global",
+ "column_back_button.label": "Voltar",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Em que estás a pensar?",
+ "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
+ "compose_form.publish": "Publicar",
+ "compose_form.sensitive": "Marcar media como conteúdo sensível",
+ "compose_form.spoiler": "Esconder texto com aviso",
+ "compose_form.spoiler_placeholder": "Aviso de conteúdo",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Inserir Emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "Ainda não existem conteúdo local para mostrar!",
+ "empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag",
+ "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
+ "empty_column.home.public_timeline": "global",
+ "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
+ "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
+ "follow_request.authorize": "Autorizar",
+ "follow_request.reject": "Rejeitar",
+ "getting_started.apps": "Existem várias aplicações disponíveis",
+ "getting_started.heading": "Primeiros passos",
+ "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
+ "home.column_settings.advanced": "Avançado",
+ "home.column_settings.basic": "Básico",
+ "home.column_settings.filter_regex": "Filtrar com uma expressão regular",
+ "home.column_settings.show_reblogs": "Mostrar as partilhas",
+ "home.column_settings.show_replies": "Mostrar as respostas",
+ "home.settings": "Parâmetros da listagem Home",
+ "lightbox.close": "Fechar",
+ "loading_indicator.label": "Carregando...",
+ "media_gallery.toggle_visible": "Esconder/Mostrar",
+ "missing_indicator.label": "Não encontrado",
+ "navigation_bar.blocks": "Utilizadores bloqueados",
+ "navigation_bar.community_timeline": "Local",
+ "navigation_bar.edit_profile": "Editar perfil",
+ "navigation_bar.favourites": "Favoritos",
+ "navigation_bar.follow_requests": "Seguidores pendentes",
+ "navigation_bar.info": "Mais informações",
+ "navigation_bar.logout": "Sair",
+ "navigation_bar.mutes": "Utilizadores silenciados",
+ "navigation_bar.preferences": "Preferências",
+ "navigation_bar.public_timeline": "Global",
+ "notification.favourite": "{name} adicionou o teu post aos favoritos",
+ "notification.follow": "{name} seguiu-te",
+ "notification.reblog": "{name} partilhou o teu post",
+ "notifications.clear": "Limpar notificações",
+ "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
+ "notifications.column_settings.alert": "Notificações no computador",
+ "notifications.column_settings.favourite": "Favoritos:",
+ "notifications.column_settings.follow": "Novos seguidores:",
+ "notifications.column_settings.mention": "Menções:",
+ "notifications.column_settings.reblog": "Partilhas:",
+ "notifications.column_settings.show": "Mostrar nas colunas",
+ "notifications.column_settings.sound": "Reproduzir som",
+ "notifications.settings": "Parâmetros da listagem de Notificações",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Ajustar a privacidade da mensagem",
+ "privacy.direct.long": "Apenas para utilizadores mencionados",
+ "privacy.direct.short": "Directo",
+ "privacy.private.long": "Apenas para os seguidores",
+ "privacy.private.short": "Privado",
+ "privacy.public.long": "Publicar em todos os feeds",
+ "privacy.public.short": "Público",
+ "privacy.unlisted.long": "Não publicar nos feeds públicos",
+ "privacy.unlisted.short": "Não listar",
+ "reply_indicator.cancel": "Cancelar",
+ "report.heading": "Nova denúncia",
+ "report.placeholder": "Comentários adicionais",
+ "report.submit": "Enviar",
+ "report.target": "Denunciar",
+ "search.placeholder": "Pesquisar",
+ "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Eliminar",
+ "status.favourite": "Adicionar aos favoritos",
+ "status.load_more": "Carregar mais",
+ "status.media_hidden": "Media escondida",
+ "status.mention": "Mencionar @{name}",
+ "status.open": "Expandir",
+ "status.reblog": "Partilhar",
+ "status.reblogged_by": "{name} partilhou",
+ "status.reply": "Responder",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Denúnciar @{name}",
+ "status.sensitive_toggle": "Clique para ver",
+ "status.sensitive_warning": "Conteúdo sensível",
+ "status.show_less": "Mostrar menos",
+ "status.show_more": "Mostrar mais",
+ "tabs_bar.compose": "Criar",
+ "tabs_bar.federated_timeline": "Global",
+ "tabs_bar.home": "Home",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Notificações",
+ "upload_area.title": "Arraste e solte para enviar",
+ "upload_button.label": "Adicionar media",
+ "upload_form.undo": "Anular",
+ "upload_progress.label": "A gravar...",
+ "video_player.expand": "Expandir vídeo",
+ "video_player.toggle_sound": "Ligar/Desligar som",
+ "video_player.toggle_visible": "Ligar/Desligar vídeo",
+ "video_player.video_error": "Não é possível ver o vídeo"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Блокировать",
+ "account.disclaimer": "Это пользователь с другого узла. Число может быть больше.",
+ "account.edit_profile": "Изменить профиль",
+ "account.follow": "Подписаться",
+ "account.followers": "Подписаны",
+ "account.follows": "Подписки",
+ "account.follows_you": "Подписан(а) на Вас",
+ "account.mention": "Упомянуть",
+ "account.mute": "Заглушить",
+ "account.posts": "Посты",
+ "account.report": "Пожаловаться",
+ "account.requested": "Ожидает подтверждения",
+ "account.unblock": "Разблокировать",
+ "account.unfollow": "Отписаться",
+ "account.unmute": "Снять глушение",
+ "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз",
+ "column.blocks": "Список блокировки",
+ "column.community": "Локальная лента",
+ "column.favourites": "Понравившееся",
+ "column.follow_requests": "Запросы на подписку",
+ "column.home": "Главная",
+ "column.mutes": "Список глушения",
+ "column.notifications": "Уведомления",
+ "column.public": "Глобальная лента",
+ "column_back_button.label": "Назад",
+ "column_subheading.navigation": "Навигация",
+ "column_subheading.settings": "Настройки",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "О чем Вы думаете?",
+ "compose_form.privacy_disclaimer": "Ваш приватный статус будет доставлен упомянутым пользователям на доменах {domains}. Доверяете ли вы {domainsCount, plural, one {этому серверу} other {этим серверам}}? Приватность постов работает только на узлах Mastodon. Если {domains} {domainsCount, plural, one {не является узлом Mastodon} other {не являются узлами Mastodon}}, приватность поста не будет указана, и он может оказаться продвинут или иным образом показан не обозначенным Вами пользователям.",
+ "compose_form.publish": "Трубить",
+ "compose_form.sensitive": "Отметить как чувствительный контент",
+ "compose_form.spoiler": "Скрыть текст за предупреждением",
+ "compose_form.spoiler_placeholder": "Предупреждение о скрытом тексте",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Занятия",
+ "emoji_button.flags": "Флаги",
+ "emoji_button.food": "Еда и напитки",
+ "emoji_button.label": "Вставить эмодзи",
+ "emoji_button.nature": "Природа",
+ "emoji_button.objects": "Предметы",
+ "emoji_button.people": "Люди",
+ "emoji_button.search": "Найти...",
+ "emoji_button.symbols": "Символы",
+ "emoji_button.travel": "Путешествия",
+ "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
+ "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.",
+ "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.",
+ "empty_column.home.public_timeline": "публичные ленты",
+ "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.",
+ "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.",
+ "follow_request.authorize": "Авторизовать",
+ "follow_request.reject": "Отказать",
+ "getting_started.apps": "Доступны различные приложения.",
+ "getting_started.heading": "Добро пожаловать",
+ "getting_started.open_source_notice": "Mastodon - программа с открытым исходным кодом. Вы можете помочь проекту или сообщить о проблемах на GitHub по адресу {github}. {apps}.",
+ "home.column_settings.advanced": "Дополнительные",
+ "home.column_settings.basic": "Основные",
+ "home.column_settings.filter_regex": "Отфильтровать регулярным выражением",
+ "home.column_settings.show_reblogs": "Показывать продвижения",
+ "home.column_settings.show_replies": "Показывать ответы",
+ "home.settings": "Настройки колонки",
+ "lightbox.close": "Закрыть",
+ "loading_indicator.label": "Загрузка...",
+ "media_gallery.toggle_visible": "Показать/скрыть",
+ "missing_indicator.label": "Не найдено",
+ "navigation_bar.blocks": "Список блокировки",
+ "navigation_bar.community_timeline": "Локальная лента",
+ "navigation_bar.edit_profile": "Изменить профиль",
+ "navigation_bar.favourites": "Понравившееся",
+ "navigation_bar.follow_requests": "Запросы на подписку",
+ "navigation_bar.info": "Об узле",
+ "navigation_bar.logout": "Выйти",
+ "navigation_bar.mutes": "Список глушения",
+ "navigation_bar.preferences": "Опции",
+ "navigation_bar.public_timeline": "Глобальная лента",
+ "notification.favourite": "{name} понравился Ваш статус",
+ "notification.follow": "{name} подписался(-лась) на Вас",
+ "notification.reblog": "{name} продвинул(а) Ваш статус",
+ "notifications.clear": "Очистить уведомления",
+ "notifications.clear_confirmation": "Вы уверены, что хотите очистить все уведомления?",
+ "notifications.column_settings.alert": "Десктопные уведомления",
+ "notifications.column_settings.favourite": "Нравится:",
+ "notifications.column_settings.follow": "Новые подписчики:",
+ "notifications.column_settings.mention": "Упоминания:",
+ "notifications.column_settings.reblog": "Продвижения:",
+ "notifications.column_settings.show": "Показывать в колонке",
+ "notifications.column_settings.sound": "Проигрывать звук",
+ "notifications.settings": "Настройки колонки",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Изменить видимость статуса",
+ "privacy.direct.long": "Показать только упомянутым",
+ "privacy.direct.short": "Направленный",
+ "privacy.private.long": "Показать только подписчикам",
+ "privacy.private.short": "Приватный",
+ "privacy.public.long": "Показать в публичных лентах",
+ "privacy.public.short": "Публичный",
+ "privacy.unlisted.long": "Не показывать в лентах",
+ "privacy.unlisted.short": "Скрытый",
+ "reply_indicator.cancel": "Отмена",
+ "report.heading": "Новая жалоба",
+ "report.placeholder": "Комментарий",
+ "report.submit": "Отправить",
+ "report.target": "Жалуемся на",
+ "search.placeholder": "Поиск",
+ "search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}",
+ "status.cannot_reblog": "Этот статус не может быть продвинут",
+ "status.delete": "Удалить",
+ "status.favourite": "Нравится",
+ "status.load_more": "Показать еще",
+ "status.media_hidden": "Медиаконтент скрыт",
+ "status.mention": "Упомянуть @{name}",
+ "status.open": "Развернуть статус",
+ "status.reblog": "Продвинуть",
+ "status.reblogged_by": "{name} продвинул(а)",
+ "status.reply": "Ответить",
+ "status.replyAll": "Ответить на тред",
+ "status.report": "Пожаловаться",
+ "status.sensitive_toggle": "Нажмите для просмотра",
+ "status.sensitive_warning": "Чувствительный контент",
+ "status.show_less": "Свернуть",
+ "status.show_more": "Развернуть",
+ "tabs_bar.compose": "Написать",
+ "tabs_bar.federated_timeline": "Глобальная",
+ "tabs_bar.home": "Главная",
+ "tabs_bar.local_timeline": "Локальная",
+ "tabs_bar.notifications": "Уведомления",
+ "upload_area.title": "Перетащите сюда, чтобы загрузить",
+ "upload_button.label": "Добавить медиаконтент",
+ "upload_form.undo": "Отменить",
+ "upload_progress.label": "Загрузка...",
+ "video_player.expand": "Развернуть видео",
+ "video_player.toggle_sound": "Вкл./выкл. звук",
+ "video_player.toggle_visible": "Показать/скрыть",
+ "video_player.video_error": "Видео не может быть проиграно"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "Заблокувати",
+ "account.disclaimer": "This user is from another instance. This number may be larger.",
+ "account.edit_profile": "Налаштування профілю",
+ "account.follow": "Підписатися",
+ "account.followers": "Підписники",
+ "account.follows": "Підписки",
+ "account.follows_you": "Підписаний",
+ "account.mention": "Згадати",
+ "account.mute": "Mute @{name}",
+ "account.posts": "Пости",
+ "account.report": "Report @{name}",
+ "account.requested": "Awaiting approval",
+ "account.unblock": "Розблокувати",
+ "account.unfollow": "Відписатися",
+ "account.unmute": "Unmute @{name}",
+ "boost_modal.combo": "You can press {combo} to skip this next time",
+ "column.blocks": "Blocked users",
+ "column.community": "Local timeline",
+ "column.favourites": "Favourites",
+ "column.follow_requests": "Follow requests",
+ "column.home": "Головна",
+ "column.mutes": "Muted users",
+ "column.notifications": "Сповіщення",
+ "column.public": "Стіна",
+ "column_back_button.label": "Назад",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "Що у Вас на думці?",
+ "compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
+ "compose_form.publish": "Дмухнути",
+ "compose_form.sensitive": "Непристойний зміст",
+ "compose_form.spoiler": "Hide text behind warning",
+ "compose_form.spoiler_placeholder": "Content warning",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "Insert emoji",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+ "empty_column.hashtag": "There is nothing in this hashtag yet.",
+ "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
+ "empty_column.home.public_timeline": "the public timeline",
+ "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+ "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+ "follow_request.authorize": "Authorize",
+ "follow_request.reject": "Reject",
+ "getting_started.apps": "Various apps are available",
+ "getting_started.heading": "Ласкаво просимо",
+ "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
+ "home.column_settings.advanced": "Advanced",
+ "home.column_settings.basic": "Basic",
+ "home.column_settings.filter_regex": "Filter out by regular expressions",
+ "home.column_settings.show_reblogs": "Show boosts",
+ "home.column_settings.show_replies": "Show replies",
+ "home.settings": "Column settings",
+ "lightbox.close": "Закрити",
+ "loading_indicator.label": "Завантаження...",
+ "media_gallery.toggle_visible": "Toggle visibility",
+ "missing_indicator.label": "Not found",
+ "navigation_bar.blocks": "Blocked users",
+ "navigation_bar.community_timeline": "Local timeline",
+ "navigation_bar.edit_profile": "Редагувати профіль",
+ "navigation_bar.favourites": "Favourites",
+ "navigation_bar.follow_requests": "Follow requests",
+ "navigation_bar.info": "Extended information",
+ "navigation_bar.logout": "Вийти",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "Налаштування",
+ "navigation_bar.public_timeline": "Публічна стіна",
+ "notification.favourite": "{name} сподобався ваш допис",
+ "notification.follow": "{name} підписався(-лась) на Вас",
+ "notification.reblog": "{name} передмухнув(-ла) Ваш статус",
+ "notifications.clear": "Clear notifications",
+ "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+ "notifications.column_settings.alert": "Desktop notifications",
+ "notifications.column_settings.favourite": "Favourites:",
+ "notifications.column_settings.follow": "New followers:",
+ "notifications.column_settings.mention": "Mentions:",
+ "notifications.column_settings.reblog": "Boosts:",
+ "notifications.column_settings.show": "Show in column",
+ "notifications.column_settings.sound": "Play sound",
+ "notifications.settings": "Column settings",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "Adjust status privacy",
+ "privacy.direct.long": "Post to mentioned users only",
+ "privacy.direct.short": "Direct",
+ "privacy.private.long": "Post to followers only",
+ "privacy.private.short": "Followers-only",
+ "privacy.public.long": "Post to public timelines",
+ "privacy.public.short": "Public",
+ "privacy.unlisted.long": "Do not show in public timelines",
+ "privacy.unlisted.short": "Unlisted",
+ "reply_indicator.cancel": "Відмінити",
+ "report.heading": "New report",
+ "report.placeholder": "Additional comments",
+ "report.submit": "Submit",
+ "report.target": "Reporting",
+ "search.placeholder": "Пошук",
+ "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "Видалити",
+ "status.favourite": "Подобається",
+ "status.load_more": "Load more",
+ "status.media_hidden": "Media hidden",
+ "status.mention": "Згадати",
+ "status.open": "Expand this status",
+ "status.reblog": "Передмухнути",
+ "status.reblogged_by": "{name} передмухнув(-ла)",
+ "status.reply": "Відповісти",
+ "status.replyAll": "Reply to thread",
+ "status.report": "Report @{name}",
+ "status.sensitive_toggle": "Натисніть, щоб подивитися",
+ "status.sensitive_warning": "Непристойний зміст",
+ "status.show_less": "Show less",
+ "status.show_more": "Show more",
+ "tabs_bar.compose": "Написати",
+ "tabs_bar.federated_timeline": "Federated",
+ "tabs_bar.home": "Головна",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Сповіщення",
+ "upload_area.title": "Drag & drop to upload",
+ "upload_button.label": "Додати медіа",
+ "upload_form.undo": "Відмінити",
+ "upload_progress.label": "Uploading...",
+ "video_player.expand": "Expand video",
+ "video_player.toggle_sound": "Увімкнути/вимкнути звук",
+ "video_player.toggle_visible": "Toggle visibility",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_ar.json b/app/javascript/mastodon/locales/whitelist_ar.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_bg.json b/app/javascript/mastodon/locales/whitelist_bg.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_de.json b/app/javascript/mastodon/locales/whitelist_de.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_en.json b/app/javascript/mastodon/locales/whitelist_en.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_eo.json b/app/javascript/mastodon/locales/whitelist_eo.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_es.json b/app/javascript/mastodon/locales/whitelist_es.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_fa.json b/app/javascript/mastodon/locales/whitelist_fa.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_fi.json b/app/javascript/mastodon/locales/whitelist_fi.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_fr.json b/app/javascript/mastodon/locales/whitelist_fr.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_hr.json b/app/javascript/mastodon/locales/whitelist_hr.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_hu.json b/app/javascript/mastodon/locales/whitelist_hu.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_id.json b/app/javascript/mastodon/locales/whitelist_id.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_io.json b/app/javascript/mastodon/locales/whitelist_io.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_it.json b/app/javascript/mastodon/locales/whitelist_it.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_ja.json b/app/javascript/mastodon/locales/whitelist_ja.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_nl.json b/app/javascript/mastodon/locales/whitelist_nl.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_no.json b/app/javascript/mastodon/locales/whitelist_no.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_oc.json b/app/javascript/mastodon/locales/whitelist_oc.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_pt-BR.json b/app/javascript/mastodon/locales/whitelist_pt-BR.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_pt.json b/app/javascript/mastodon/locales/whitelist_pt.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_ru.json b/app/javascript/mastodon/locales/whitelist_ru.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_uk.json b/app/javascript/mastodon/locales/whitelist_uk.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_zh-CN.json b/app/javascript/mastodon/locales/whitelist_zh-CN.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_zh-HK.json b/app/javascript/mastodon/locales/whitelist_zh-HK.json
@@ -0,0 +1,2 @@
+[
+]+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "屏蔽 @{name}",
+ "account.disclaimer": "由于这个账户处于另一个服务站,实际数字会比这个更多。",
+ "account.edit_profile": "修改个人资料",
+ "account.follow": "关注",
+ "account.followers": "关注者",
+ "account.follows": "正关注",
+ "account.follows_you": "关注你",
+ "account.mention": "提及 @{name}",
+ "account.mute": "将 @{name} 静音",
+ "account.posts": "嘟文",
+ "account.report": "举报 @{name}",
+ "account.requested": "等候审批",
+ "account.unblock": "解除对 @{name} 的屏蔽",
+ "account.unfollow": "取消关注",
+ "account.unmute": "取消 @{name} 的静音",
+ "boost_modal.combo": "如你想在下次路过时显示,请按{combo},",
+ "column.blocks": "屏蔽用户",
+ "column.community": "本站时间轴",
+ "column.favourites": "赞过的嘟文",
+ "column.follow_requests": "关注请求",
+ "column.home": "主页",
+ "column.mutes": "Muted users",
+ "column.notifications": "通知",
+ "column.public": "跨站公共时间轴",
+ "column_back_button.label": "返回",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "在想啥?",
+ "compose_form.privacy_disclaimer": "你的私人嘟文,将被发送至你所提及的 {domains} 用户。你是否信任{domainsCount, plural, one {这个网站} other {这些网站}}?请留意,嘟文隐私设置只适用于各 Mastodon 服务站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服务站} other {之中有些不是 Mastodon 服务站}},对方将无法收到这篇嘟文的隐私设置,然后可能被转嘟给不能预知的用户阅读。",
+ "compose_form.publish": "嘟嘟",
+ "compose_form.sensitive": "将媒体文件标示为“敏感内容”",
+ "compose_form.spoiler": "将部分文本藏于警告消息之后",
+ "compose_form.spoiler_placeholder": "敏感内容的警告消息",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "加入表情符号",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "本站时间轴暂时未有内容,快贴文来抢头香啊!",
+ "empty_column.hashtag": "这个标签暂时未有内容。",
+ "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
+ "empty_column.home.public_timeline": "公共时间轴",
+ "empty_column.notifications": "你没有任何通知纪录,快向其他用户搭讪吧。",
+ "empty_column.public": "跨站公共时间轴暂时没有内容!快写一些公共的嘟文,或者关注另一些服务站的用户吧!你和本站、友站的交流,将决定这里出现的内容。",
+ "follow_request.authorize": "批准",
+ "follow_request.reject": "拒绝",
+ "getting_started.apps": "手机或桌面应用程序",
+ "getting_started.heading": "开始使用",
+ "getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。你亦可通过{apps}阅读 Mastodon 上的消息。",
+ "home.column_settings.advanced": "高端",
+ "home.column_settings.basic": "基本",
+ "home.column_settings.filter_regex": "使用正则表达式 (regex) 过滤",
+ "home.column_settings.show_reblogs": "显示被转的嘟文",
+ "home.column_settings.show_replies": "显示回应嘟文",
+ "home.settings": "字段设置",
+ "lightbox.close": "关闭",
+ "loading_indicator.label": "加载中……",
+ "media_gallery.toggle_visible": "打开或关上",
+ "missing_indicator.label": "找不到内容",
+ "navigation_bar.blocks": "被屏蔽的用户",
+ "navigation_bar.community_timeline": "本站时间轴",
+ "navigation_bar.edit_profile": "修改个人资料",
+ "navigation_bar.favourites": "赞的内容",
+ "navigation_bar.follow_requests": "关注请求",
+ "navigation_bar.info": "关于本服务站",
+ "navigation_bar.logout": "注销",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "首选项",
+ "navigation_bar.public_timeline": "跨站公共时间轴",
+ "notification.favourite": "{name} 赞你的嘟文",
+ "notification.follow": "{name} 开始关注你",
+ "notification.reblog": "{name} 转嘟你的嘟文",
+ "notifications.clear": "清空通知纪录",
+ "notifications.clear_confirmation": "你确定要清空通知纪录吗?",
+ "notifications.column_settings.alert": "显示桌面通知",
+ "notifications.column_settings.favourite": "赞你的嘟文:",
+ "notifications.column_settings.follow": "关注你:",
+ "notifications.column_settings.mention": "提及你:",
+ "notifications.column_settings.reblog": "转你的嘟文:",
+ "notifications.column_settings.show": "在通知栏显示",
+ "notifications.column_settings.sound": "播放音效",
+ "notifications.settings": "字段设置",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "调整隐私设置",
+ "privacy.direct.long": "只有提及的用户能看到",
+ "privacy.direct.short": "私人消息",
+ "privacy.private.long": "只有关注你用户能看到",
+ "privacy.private.short": "关注者",
+ "privacy.public.long": "在公共时间轴显示",
+ "privacy.public.short": "公共",
+ "privacy.unlisted.long": "公开,但不在公共时间轴显示",
+ "privacy.unlisted.short": "公开",
+ "reply_indicator.cancel": "取消",
+ "report.heading": "举报",
+ "report.placeholder": "额外消息",
+ "report.submit": "提交",
+ "report.target": "Reporting",
+ "search.placeholder": "搜索",
+ "search_results.total": "{count, number} 项结果",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "删除",
+ "status.favourite": "赞",
+ "status.load_more": "加载更多",
+ "status.media_hidden": "隐藏媒体内容",
+ "status.mention": "提及 @{name}",
+ "status.open": "展开嘟文",
+ "status.reblog": "转嘟",
+ "status.reblogged_by": "{name} 转嘟",
+ "status.reply": "回应",
+ "status.replyAll": "Reply to thread",
+ "status.report": "举报 @{name}",
+ "status.sensitive_toggle": "点击显示",
+ "status.sensitive_warning": "敏感内容",
+ "status.show_less": "减少显示",
+ "status.show_more": "显示更多",
+ "tabs_bar.compose": "撰写",
+ "tabs_bar.federated_timeline": "跨站",
+ "tabs_bar.home": "主页",
+ "tabs_bar.local_timeline": "本站",
+ "tabs_bar.notifications": "通知",
+ "upload_area.title": "将文件拖放至此上传",
+ "upload_button.label": "上传媒体文件",
+ "upload_form.undo": "还原",
+ "upload_progress.label": "上传中……",
+ "video_player.expand": "展开影片",
+ "video_player.toggle_sound": "开关音效",
+ "video_player.toggle_visible": "打开或关上",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
@@ -0,0 +1,163 @@
+{
+ "account.block": "封鎖 @{name}",
+ "account.disclaimer": "由於這個用戶在另一個服務站,實際數字會比這個更多。",
+ "account.edit_profile": "修改個人資料",
+ "account.follow": "關注",
+ "account.followers": "關注的人",
+ "account.follows": "正在關注",
+ "account.follows_you": "關注你",
+ "account.mention": "提及 @{name}",
+ "account.mute": "將 @{name} 靜音",
+ "account.posts": "文章",
+ "account.report": "舉報 @{name}",
+ "account.requested": "等候審批",
+ "account.unblock": "解除對 @{name} 的封鎖",
+ "account.unfollow": "取消關注",
+ "account.unmute": "取消 @{name} 的靜音",
+ "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},",
+ "column.blocks": "封鎖用戶",
+ "column.community": "本站時間軸",
+ "column.favourites": "喜歡的文章",
+ "column.follow_requests": "關注請求",
+ "column.home": "主頁",
+ "column.mutes": "Muted users",
+ "column.notifications": "通知",
+ "column.public": "跨站公共時間軸",
+ "column_back_button.label": "返回",
+ "column_subheading.navigation": "Navigation",
+ "column_subheading.settings": "Settings",
+ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+ "compose_form.lock_disclaimer.lock": "locked",
+ "compose_form.placeholder": "你在想甚麼?",
+ "compose_form.privacy_disclaimer": "你的私人文章,將被遞送至你所提及的 {domains} 用戶。你是否信任{domainsCount, plural, one {這個網站} other {這些網站}}?請留意,文章私隱設定只適用於各 Mastodon 服務站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服務站} other {之中有些不是 Mastodon 服務站}},對方將無法收到這篇文章的私隱設定,然後可能被轉推給不能預知的用戶閱讀。",
+ "compose_form.publish": "發文",
+ "compose_form.sensitive": "將媒體檔案標示為「敏感內容」",
+ "compose_form.spoiler": "將部份文字藏於警告訊息之後",
+ "compose_form.spoiler_placeholder": "敏感警告訊息",
+ "confirmation_modal.cancel": "Cancel",
+ "confirmations.block.confirm": "Block",
+ "confirmations.block.message": "Are you sure you want to block {name}?",
+ "confirmations.delete.confirm": "Delete",
+ "confirmations.delete.message": "Are you sure you want to delete this status?",
+ "confirmations.mute.confirm": "Mute",
+ "confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "emoji_button.activity": "Activity",
+ "emoji_button.flags": "Flags",
+ "emoji_button.food": "Food & Drink",
+ "emoji_button.label": "加入表情符號",
+ "emoji_button.nature": "Nature",
+ "emoji_button.objects": "Objects",
+ "emoji_button.people": "People",
+ "emoji_button.search": "Search...",
+ "emoji_button.symbols": "Symbols",
+ "emoji_button.travel": "Travel & Places",
+ "empty_column.community": "本站時間軸暫時未有內容,快貼文來搶頭香啊!",
+ "empty_column.hashtag": "這個標籤暫時未有內容。",
+ "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
+ "empty_column.home.public_timeline": "公共時間軸",
+ "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。",
+ "empty_column.public": "跨站公共時間軸暫時沒有內容!快寫一些公共的文章,或者關注另一些服務站的用戶吧!你和本站、友站的交流,將決定這裏出現的內容。",
+ "follow_request.authorize": "批准",
+ "follow_request.reject": "拒絕",
+ "getting_started.apps": "手機或桌面應用程式",
+ "getting_started.heading": "開始使用",
+ "getting_started.open_source_notice": "Mastodon 是一個開放源碼的軟件。你可以在官方 GitHub ({github}) 貢獻或者回報問題。你亦可透過{apps}閱讀 Mastodon 上的消息。",
+ "home.column_settings.advanced": "進階",
+ "home.column_settings.basic": "基本",
+ "home.column_settings.filter_regex": "使用正規表達式 (regular expression) 過濾",
+ "home.column_settings.show_reblogs": "顯示被轉推的文章",
+ "home.column_settings.show_replies": "顯示回應文章",
+ "home.settings": "欄位設定",
+ "lightbox.close": "Close",
+ "loading_indicator.label": "載入中...",
+ "media_gallery.toggle_visible": "打開或關上",
+ "missing_indicator.label": "找不到內容",
+ "navigation_bar.blocks": "被封鎖的用戶",
+ "navigation_bar.community_timeline": "本站時間軸",
+ "navigation_bar.edit_profile": "修改個人資料",
+ "navigation_bar.favourites": "喜歡的內容",
+ "navigation_bar.follow_requests": "關注請求",
+ "navigation_bar.info": "關於本服務站",
+ "navigation_bar.logout": "登出",
+ "navigation_bar.mutes": "Muted users",
+ "navigation_bar.preferences": "偏好設定",
+ "navigation_bar.public_timeline": "跨站公共時間軸",
+ "notification.favourite": "{name} 喜歡你的文章",
+ "notification.follow": "{name} 開始關注你",
+ "notification.reblog": "{name} 轉推你的文章",
+ "notifications.clear": "清空通知紀錄",
+ "notifications.clear_confirmation": "你確定要清空通知紀錄嗎?",
+ "notifications.column_settings.alert": "顯示桌面通知",
+ "notifications.column_settings.favourite": "喜歡你的文章:",
+ "notifications.column_settings.follow": "關注你:",
+ "notifications.column_settings.mention": "提及你:",
+ "notifications.column_settings.reblog": "轉推你的文章:",
+ "notifications.column_settings.show": "在通知欄顯示",
+ "notifications.column_settings.sound": "播放音效",
+ "notifications.settings": "欄位設定",
+ "onboarding.done": "Done",
+ "onboarding.next": "Next",
+ "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+ "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+ "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+ "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+ "onboarding.page_one.welcome": "Welcome to Mastodon!",
+ "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+ "onboarding.page_six.almost_done": "Almost done...",
+ "onboarding.page_six.appetoot": "Bon Appetoot!",
+ "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+ "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+ "onboarding.page_six.guidelines": "community guidelines",
+ "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+ "onboarding.page_six.various_app": "mobile apps",
+ "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+ "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+ "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+ "onboarding.skip": "Skip",
+ "privacy.change": "調整私隱設定",
+ "privacy.direct.long": "只有提及的用戶能看到",
+ "privacy.direct.short": "私人訊息",
+ "privacy.private.long": "只有關注你用戶能看到",
+ "privacy.private.short": "關注者",
+ "privacy.public.long": "在公共時間軸顯示",
+ "privacy.public.short": "公共",
+ "privacy.unlisted.long": "公開,但不在公共時間軸顯示",
+ "privacy.unlisted.short": "公開",
+ "reply_indicator.cancel": "取消",
+ "report.heading": "舉報",
+ "report.placeholder": "額外訊息",
+ "report.submit": "提交",
+ "report.target": "Reporting",
+ "search.placeholder": "搜尋",
+ "search_results.total": "{count, number} 項結果",
+ "status.cannot_reblog": "This post cannot be boosted",
+ "status.delete": "刪除",
+ "status.favourite": "喜歡",
+ "status.load_more": "載入更多",
+ "status.media_hidden": "隱藏媒體內容",
+ "status.mention": "提及 @{name}",
+ "status.open": "展開文章",
+ "status.reblog": "轉推",
+ "status.reblogged_by": "{name} 轉推",
+ "status.reply": "回應",
+ "status.replyAll": "Reply to thread",
+ "status.report": "舉報 @{name}",
+ "status.sensitive_toggle": "點擊顯示",
+ "status.sensitive_warning": "敏感內容",
+ "status.show_less": "減少顯示",
+ "status.show_more": "顯示更多",
+ "tabs_bar.compose": "撰寫",
+ "tabs_bar.federated_timeline": "跨站",
+ "tabs_bar.home": "主頁",
+ "tabs_bar.local_timeline": "本站",
+ "tabs_bar.notifications": "通知",
+ "upload_area.title": "將檔案拖放至此上載",
+ "upload_button.label": "上載媒體檔案",
+ "upload_form.undo": "還原",
+ "upload_progress.label": "上載中……",
+ "video_player.expand": "展開影片",
+ "video_player.toggle_sound": "開關音效",
+ "video_player.toggle_visible": "打開或關上",
+ "video_player.video_error": "Video could not be played"
+}+
\ No newline at end of file
diff --git a/app/javascript/mastodon/middleware/errors.js b/app/javascript/mastodon/middleware/errors.js
@@ -0,0 +1,33 @@
+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) {
+ const { data, status, statusText } = action.error.response;
+
+ let message = statusText;
+ let title = `${status}`;
+
+ if (data.error) {
+ message = data.error;
+ }
+
+ dispatch(showAlert(title, message));
+ } else {
+ console.error(action.error); // eslint-disable-line no-console
+ dispatch(showAlert('Oops!', 'An unexpected error occurred.'));
+ }
+ }
+ }
+
+ return next(action);
+ };
+};
diff --git a/app/javascript/mastodon/middleware/loading_bar.js b/app/javascript/mastodon/middleware/loading_bar.js
@@ -0,0 +1,25 @@
+import { showLoading, hideLoading } from 'react-redux-loading-bar';
+
+const defaultTypeSuffixes = ['PENDING', 'FULFILLED', 'REJECTED'];
+
+export default function loadingBarMiddleware(config = {}) {
+ const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypeSuffixes;
+
+ return ({ dispatch }) => next => (action) => {
+ if (action.type && !action.skipLoading) {
+ const [PENDING, FULFILLED, REJECTED] = promiseTypeSuffixes;
+
+ const isPending = new RegExp(`${PENDING}$`, 'g');
+ const isFulfilled = new RegExp(`${FULFILLED}$`, 'g');
+ const isRejected = new RegExp(`${REJECTED}$`, 'g');
+
+ if (action.type.match(isPending)) {
+ dispatch(showLoading());
+ } else if (action.type.match(isFulfilled) || action.type.match(isRejected)) {
+ dispatch(hideLoading());
+ }
+ }
+
+ return next(action);
+ };
+};
diff --git a/app/javascript/mastodon/middleware/sounds.js b/app/javascript/mastodon/middleware/sounds.js
@@ -0,0 +1,22 @@
+const play = audio => {
+ if (!audio.paused) {
+ audio.pause();
+ audio.fastSeek(0);
+ }
+
+ audio.play();
+};
+
+export default function soundsMiddleware() {
+ const soundCache = {
+ boop: new Audio(['/sounds/boop.mp3'])
+ };
+
+ return ({ dispatch }) => next => (action) => {
+ if (action.meta && action.meta.sound && soundCache[action.meta.sound]) {
+ play(soundCache[action.meta.sound]);
+ }
+
+ return next(action);
+ };
+};
diff --git a/app/javascript/mastodon/reducers/accounts.js b/app/javascript/mastodon/reducers/accounts.js
@@ -0,0 +1,133 @@
+import {
+ ACCOUNT_FETCH_SUCCESS,
+ FOLLOWERS_FETCH_SUCCESS,
+ FOLLOWERS_EXPAND_SUCCESS,
+ FOLLOWING_FETCH_SUCCESS,
+ FOLLOWING_EXPAND_SUCCESS,
+ ACCOUNT_TIMELINE_FETCH_SUCCESS,
+ ACCOUNT_TIMELINE_EXPAND_SUCCESS,
+ FOLLOW_REQUESTS_FETCH_SUCCESS,
+ FOLLOW_REQUESTS_EXPAND_SUCCESS
+} from '../actions/accounts';
+import {
+ BLOCKS_FETCH_SUCCESS,
+ BLOCKS_EXPAND_SUCCESS
+} from '../actions/blocks';
+import {
+ MUTES_FETCH_SUCCESS,
+ MUTES_EXPAND_SUCCESS
+} from '../actions/mutes';
+import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose';
+import {
+ REBLOG_SUCCESS,
+ UNREBLOG_SUCCESS,
+ FAVOURITE_SUCCESS,
+ UNFAVOURITE_SUCCESS,
+ REBLOGS_FETCH_SUCCESS,
+ FAVOURITES_FETCH_SUCCESS
+} from '../actions/interactions';
+import {
+ TIMELINE_REFRESH_SUCCESS,
+ TIMELINE_UPDATE,
+ TIMELINE_EXPAND_SUCCESS
+} from '../actions/timelines';
+import {
+ STATUS_FETCH_SUCCESS,
+ CONTEXT_FETCH_SUCCESS
+} from '../actions/statuses';
+import { SEARCH_FETCH_SUCCESS } from '../actions/search';
+import {
+ NOTIFICATIONS_UPDATE,
+ NOTIFICATIONS_REFRESH_SUCCESS,
+ NOTIFICATIONS_EXPAND_SUCCESS
+} from '../actions/notifications';
+import {
+ FAVOURITED_STATUSES_FETCH_SUCCESS,
+ FAVOURITED_STATUSES_EXPAND_SUCCESS
+} from '../actions/favourites';
+import { STORE_HYDRATE } from '../actions/store';
+import Immutable from 'immutable';
+
+const normalizeAccount = (state, account) => {
+ account = { ...account };
+
+ delete account.followers_count;
+ delete account.following_count;
+ delete account.statuses_count;
+
+ return state.set(account.id, Immutable.fromJS(account))
+};
+
+const normalizeAccounts = (state, accounts) => {
+ accounts.forEach(account => {
+ state = normalizeAccount(state, account);
+ });
+
+ return state;
+};
+
+const normalizeAccountFromStatus = (state, status) => {
+ state = normalizeAccount(state, status.account);
+
+ if (status.reblog && status.reblog.account) {
+ state = normalizeAccount(state, status.reblog.account);
+ }
+
+ return state;
+};
+
+const normalizeAccountsFromStatuses = (state, statuses) => {
+ statuses.forEach(status => {
+ state = normalizeAccountFromStatus(state, status);
+ });
+
+ return state;
+};
+
+const initialState = Immutable.Map();
+
+export default function accounts(state = initialState, action) {
+ switch(action.type) {
+ case STORE_HYDRATE:
+ return state.merge(action.state.get('accounts'));
+ case ACCOUNT_FETCH_SUCCESS:
+ case NOTIFICATIONS_UPDATE:
+ return normalizeAccount(state, action.account);
+ case FOLLOWERS_FETCH_SUCCESS:
+ case FOLLOWERS_EXPAND_SUCCESS:
+ case FOLLOWING_FETCH_SUCCESS:
+ case FOLLOWING_EXPAND_SUCCESS:
+ case REBLOGS_FETCH_SUCCESS:
+ case FAVOURITES_FETCH_SUCCESS:
+ case COMPOSE_SUGGESTIONS_READY:
+ case FOLLOW_REQUESTS_FETCH_SUCCESS:
+ case FOLLOW_REQUESTS_EXPAND_SUCCESS:
+ case BLOCKS_FETCH_SUCCESS:
+ case BLOCKS_EXPAND_SUCCESS:
+ case MUTES_FETCH_SUCCESS:
+ case MUTES_EXPAND_SUCCESS:
+ return normalizeAccounts(state, action.accounts);
+ case NOTIFICATIONS_REFRESH_SUCCESS:
+ case NOTIFICATIONS_EXPAND_SUCCESS:
+ case SEARCH_FETCH_SUCCESS:
+ return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses);
+ case TIMELINE_REFRESH_SUCCESS:
+ case TIMELINE_EXPAND_SUCCESS:
+ case ACCOUNT_TIMELINE_FETCH_SUCCESS:
+ case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
+ case CONTEXT_FETCH_SUCCESS:
+ case FAVOURITED_STATUSES_FETCH_SUCCESS:
+ case FAVOURITED_STATUSES_EXPAND_SUCCESS:
+ return normalizeAccountsFromStatuses(state, action.statuses);
+ case REBLOG_SUCCESS:
+ case FAVOURITE_SUCCESS:
+ case UNREBLOG_SUCCESS:
+ case UNFAVOURITE_SUCCESS:
+ return normalizeAccountFromStatus(state, action.response);
+ case TIMELINE_UPDATE:
+ case STATUS_FETCH_SUCCESS:
+ return normalizeAccountFromStatus(state, action.status);
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/accounts_counters.js b/app/javascript/mastodon/reducers/accounts_counters.js
@@ -0,0 +1,135 @@
+import {
+ ACCOUNT_FETCH_SUCCESS,
+ FOLLOWERS_FETCH_SUCCESS,
+ FOLLOWERS_EXPAND_SUCCESS,
+ FOLLOWING_FETCH_SUCCESS,
+ FOLLOWING_EXPAND_SUCCESS,
+ ACCOUNT_TIMELINE_FETCH_SUCCESS,
+ ACCOUNT_TIMELINE_EXPAND_SUCCESS,
+ FOLLOW_REQUESTS_FETCH_SUCCESS,
+ FOLLOW_REQUESTS_EXPAND_SUCCESS,
+ ACCOUNT_FOLLOW_SUCCESS,
+ ACCOUNT_UNFOLLOW_SUCCESS
+} from '../actions/accounts';
+import {
+ BLOCKS_FETCH_SUCCESS,
+ BLOCKS_EXPAND_SUCCESS
+} from '../actions/blocks';
+import {
+ MUTES_FETCH_SUCCESS,
+ MUTES_EXPAND_SUCCESS
+} from '../actions/mutes';
+import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose';
+import {
+ REBLOG_SUCCESS,
+ UNREBLOG_SUCCESS,
+ FAVOURITE_SUCCESS,
+ UNFAVOURITE_SUCCESS,
+ REBLOGS_FETCH_SUCCESS,
+ FAVOURITES_FETCH_SUCCESS
+} from '../actions/interactions';
+import {
+ TIMELINE_REFRESH_SUCCESS,
+ TIMELINE_UPDATE,
+ TIMELINE_EXPAND_SUCCESS
+} from '../actions/timelines';
+import {
+ STATUS_FETCH_SUCCESS,
+ CONTEXT_FETCH_SUCCESS
+} from '../actions/statuses';
+import { SEARCH_FETCH_SUCCESS } from '../actions/search';
+import {
+ NOTIFICATIONS_UPDATE,
+ NOTIFICATIONS_REFRESH_SUCCESS,
+ NOTIFICATIONS_EXPAND_SUCCESS
+} from '../actions/notifications';
+import {
+ FAVOURITED_STATUSES_FETCH_SUCCESS,
+ FAVOURITED_STATUSES_EXPAND_SUCCESS
+} from '../actions/favourites';
+import { STORE_HYDRATE } from '../actions/store';
+import Immutable from 'immutable';
+
+const normalizeAccount = (state, account) => state.set(account.id, Immutable.fromJS({
+ followers_count: account.followers_count,
+ following_count: account.following_count,
+ statuses_count: account.statuses_count,
+}));
+
+const normalizeAccounts = (state, accounts) => {
+ accounts.forEach(account => {
+ state = normalizeAccount(state, account);
+ });
+
+ return state;
+};
+
+const normalizeAccountFromStatus = (state, status) => {
+ state = normalizeAccount(state, status.account);
+
+ if (status.reblog && status.reblog.account) {
+ state = normalizeAccount(state, status.reblog.account);
+ }
+
+ return state;
+};
+
+const normalizeAccountsFromStatuses = (state, statuses) => {
+ statuses.forEach(status => {
+ state = normalizeAccountFromStatus(state, status);
+ });
+
+ return state;
+};
+
+const initialState = Immutable.Map();
+
+export default function accountsCounters(state = initialState, action) {
+ switch(action.type) {
+ case STORE_HYDRATE:
+ return state.merge(action.state.get('accounts_counters'));
+ case ACCOUNT_FETCH_SUCCESS:
+ case NOTIFICATIONS_UPDATE:
+ return normalizeAccount(state, action.account);
+ case FOLLOWERS_FETCH_SUCCESS:
+ case FOLLOWERS_EXPAND_SUCCESS:
+ case FOLLOWING_FETCH_SUCCESS:
+ case FOLLOWING_EXPAND_SUCCESS:
+ case REBLOGS_FETCH_SUCCESS:
+ case FAVOURITES_FETCH_SUCCESS:
+ case COMPOSE_SUGGESTIONS_READY:
+ case FOLLOW_REQUESTS_FETCH_SUCCESS:
+ case FOLLOW_REQUESTS_EXPAND_SUCCESS:
+ case BLOCKS_FETCH_SUCCESS:
+ case BLOCKS_EXPAND_SUCCESS:
+ case MUTES_FETCH_SUCCESS:
+ case MUTES_EXPAND_SUCCESS:
+ return normalizeAccounts(state, action.accounts);
+ case NOTIFICATIONS_REFRESH_SUCCESS:
+ case NOTIFICATIONS_EXPAND_SUCCESS:
+ case SEARCH_FETCH_SUCCESS:
+ return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses);
+ case TIMELINE_REFRESH_SUCCESS:
+ case TIMELINE_EXPAND_SUCCESS:
+ case ACCOUNT_TIMELINE_FETCH_SUCCESS:
+ case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
+ case CONTEXT_FETCH_SUCCESS:
+ case FAVOURITED_STATUSES_FETCH_SUCCESS:
+ case FAVOURITED_STATUSES_EXPAND_SUCCESS:
+ return normalizeAccountsFromStatuses(state, action.statuses);
+ case REBLOG_SUCCESS:
+ case FAVOURITE_SUCCESS:
+ case UNREBLOG_SUCCESS:
+ case UNFAVOURITE_SUCCESS:
+ return normalizeAccountFromStatus(state, action.response);
+ case TIMELINE_UPDATE:
+ case STATUS_FETCH_SUCCESS:
+ return normalizeAccountFromStatus(state, action.status);
+ case ACCOUNT_FOLLOW_SUCCESS:
+ return state.updateIn([action.relationship.id, 'followers_count'], num => num + 1);
+ case ACCOUNT_UNFOLLOW_SUCCESS:
+ return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1));
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/alerts.js b/app/javascript/mastodon/reducers/alerts.js
@@ -0,0 +1,25 @@
+import {
+ ALERT_SHOW,
+ ALERT_DISMISS,
+ ALERT_CLEAR
+} from '../actions/alerts';
+import Immutable from 'immutable';
+
+const initialState = Immutable.List([]);
+
+export default function alerts(state = initialState, action) {
+ switch(action.type) {
+ case ALERT_SHOW:
+ return state.push(Immutable.Map({
+ key: state.size > 0 ? state.last().get('key') + 1 : 0,
+ title: action.title,
+ message: action.message
+ }));
+ case ALERT_DISMISS:
+ return state.filterNot(item => item.get('key') === action.alert.key);
+ case ALERT_CLEAR:
+ return state.clear();
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/cards.js b/app/javascript/mastodon/reducers/cards.js
@@ -0,0 +1,14 @@
+import { STATUS_CARD_FETCH_SUCCESS } from '../actions/cards';
+
+import Immutable from 'immutable';
+
+const initialState = Immutable.Map();
+
+export default function cards(state = initialState, action) {
+ switch(action.type) {
+ case STATUS_CARD_FETCH_SUCCESS:
+ return state.set(action.id, Immutable.fromJS(action.card));
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
@@ -0,0 +1,232 @@
+import {
+ COMPOSE_MOUNT,
+ COMPOSE_UNMOUNT,
+ COMPOSE_CHANGE,
+ COMPOSE_REPLY,
+ COMPOSE_REPLY_CANCEL,
+ COMPOSE_MENTION,
+ COMPOSE_SUBMIT_REQUEST,
+ COMPOSE_SUBMIT_SUCCESS,
+ COMPOSE_SUBMIT_FAIL,
+ COMPOSE_UPLOAD_REQUEST,
+ COMPOSE_UPLOAD_SUCCESS,
+ COMPOSE_UPLOAD_FAIL,
+ COMPOSE_UPLOAD_UNDO,
+ COMPOSE_UPLOAD_PROGRESS,
+ COMPOSE_SUGGESTIONS_CLEAR,
+ COMPOSE_SUGGESTIONS_READY,
+ COMPOSE_SUGGESTION_SELECT,
+ COMPOSE_SENSITIVITY_CHANGE,
+ 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';
+import { STORE_HYDRATE } from '../actions/store';
+import Immutable from 'immutable';
+import uuid from '../uuid';
+
+const initialState = Immutable.Map({
+ mounted: false,
+ sensitive: false,
+ spoiler: false,
+ spoiler_text: '',
+ privacy: null,
+ text: '',
+ focusDate: null,
+ preselectDate: null,
+ in_reply_to: null,
+ is_submitting: false,
+ is_uploading: false,
+ progress: 0,
+ media_attachments: Immutable.List(),
+ suggestion_token: null,
+ suggestions: Immutable.List(),
+ me: null,
+ default_privacy: 'public',
+ resetFileKey: Math.floor((Math.random() * 0x10000)),
+ idempotencyKey: null
+});
+
+function statusToTextMentions(state, status) {
+ let set = Immutable.OrderedSet([]);
+ let me = state.get('me');
+
+ if (status.getIn(['account', 'id']) !== me) {
+ set = set.add(`@${status.getIn(['account', 'acct'])} `);
+ }
+
+ return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
+};
+
+function clearAll(state) {
+ return state.withMutations(map => {
+ map.set('text', '');
+ map.set('spoiler', false);
+ map.set('spoiler_text', '');
+ map.set('is_submitting', false);
+ map.set('in_reply_to', null);
+ map.set('privacy', state.get('default_privacy'));
+ map.set('sensitive', false);
+ map.update('media_attachments', list => list.clear());
+ map.set('idempotencyKey', uuid());
+ });
+};
+
+function appendMedia(state, media) {
+ return state.withMutations(map => {
+ map.update('media_attachments', list => list.push(media));
+ map.set('is_uploading', false);
+ map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
+ map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
+ map.set('focusDate', new Date());
+ map.set('idempotencyKey', uuid());
+ });
+};
+
+function removeMedia(state, mediaId) {
+ const media = state.get('media_attachments').find(item => item.get('id') === mediaId);
+ const prevSize = state.get('media_attachments').size;
+
+ return state.withMutations(map => {
+ map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
+ map.update('text', text => text.replace(media.get('text_url'), '').trim());
+ map.set('idempotencyKey', uuid());
+
+ if (prevSize === 1) {
+ map.set('sensitive', false);
+ }
+ });
+};
+
+const insertSuggestion = (state, position, token, completion) => {
+ return state.withMutations(map => {
+ map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`);
+ map.set('suggestion_token', null);
+ map.update('suggestions', Immutable.List(), list => list.clear());
+ map.set('focusDate', new Date());
+ map.set('idempotencyKey', uuid());
+ });
+};
+
+const insertEmoji = (state, position, emojiData) => {
+ const emoji = emojiData.shortname;
+
+ return state.withMutations(map => {
+ map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`);
+ map.set('focusDate', new Date());
+ map.set('idempotencyKey', uuid());
+ });
+};
+
+const privacyPreference = (a, b) => {
+ if (a === 'direct' || b === 'direct') {
+ return 'direct';
+ } else if (a === 'private' || b === 'private') {
+ return 'private';
+ } else if (a === 'unlisted' || b === 'unlisted') {
+ return 'unlisted';
+ } else {
+ return 'public';
+ }
+};
+
+export default function compose(state = initialState, action) {
+ switch(action.type) {
+ case STORE_HYDRATE:
+ return clearAll(state.merge(action.state.get('compose')));
+ case COMPOSE_MOUNT:
+ return state.set('mounted', true);
+ case COMPOSE_UNMOUNT:
+ return state.set('mounted', false);
+ case COMPOSE_SENSITIVITY_CHANGE:
+ return state
+ .set('sensitive', !state.get('sensitive'))
+ .set('idempotencyKey', uuid());
+ case COMPOSE_SPOILERNESS_CHANGE:
+ return state.withMutations(map => {
+ map.set('spoiler_text', '');
+ map.set('spoiler', !state.get('spoiler'));
+ map.set('idempotencyKey', uuid());
+ });
+ case COMPOSE_SPOILER_TEXT_CHANGE:
+ return state
+ .set('spoiler_text', action.text)
+ .set('idempotencyKey', uuid());
+ case COMPOSE_VISIBILITY_CHANGE:
+ return state
+ .set('privacy', action.value)
+ .set('idempotencyKey', uuid());
+ case COMPOSE_CHANGE:
+ return state
+ .set('text', action.text)
+ .set('idempotencyKey', uuid());
+ case COMPOSE_REPLY:
+ return state.withMutations(map => {
+ map.set('in_reply_to', action.status.get('id'));
+ map.set('text', statusToTextMentions(state, action.status));
+ map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
+ map.set('focusDate', new Date());
+ map.set('preselectDate', new Date());
+ map.set('idempotencyKey', uuid());
+
+ if (action.status.get('spoiler_text').length > 0) {
+ map.set('spoiler', true);
+ map.set('spoiler_text', action.status.get('spoiler_text'));
+ } else {
+ map.set('spoiler', false);
+ map.set('spoiler_text', '');
+ }
+ });
+ case COMPOSE_REPLY_CANCEL:
+ return state.withMutations(map => {
+ map.set('in_reply_to', null);
+ map.set('text', '');
+ map.set('spoiler', false);
+ map.set('spoiler_text', '');
+ map.set('privacy', state.get('default_privacy'));
+ map.set('idempotencyKey', uuid());
+ });
+ case COMPOSE_SUBMIT_REQUEST:
+ return state.set('is_submitting', true);
+ case COMPOSE_SUBMIT_SUCCESS:
+ return clearAll(state);
+ case COMPOSE_SUBMIT_FAIL:
+ return state.set('is_submitting', false);
+ case COMPOSE_UPLOAD_REQUEST:
+ return state.withMutations(map => {
+ map.set('is_uploading', true);
+ });
+ case COMPOSE_UPLOAD_SUCCESS:
+ return appendMedia(state, Immutable.fromJS(action.media));
+ case COMPOSE_UPLOAD_FAIL:
+ return state.set('is_uploading', false);
+ case COMPOSE_UPLOAD_UNDO:
+ return removeMedia(state, action.media_id);
+ case COMPOSE_UPLOAD_PROGRESS:
+ return state.set('progress', Math.round((action.loaded / action.total) * 100));
+ case COMPOSE_MENTION:
+ return state
+ .update('text', text => `${text}@${action.account.get('acct')} `)
+ .set('focusDate', new Date())
+ .set('idempotencyKey', uuid());
+ case COMPOSE_SUGGESTIONS_CLEAR:
+ return state.update('suggestions', Immutable.List(), list => list.clear()).set('suggestion_token', null);
+ case COMPOSE_SUGGESTIONS_READY:
+ return state.set('suggestions', Immutable.List(action.accounts.map(item => item.id))).set('suggestion_token', action.token);
+ case COMPOSE_SUGGESTION_SELECT:
+ return insertSuggestion(state, action.position, action.token, action.completion);
+ case TIMELINE_DELETE:
+ if (action.id === state.get('in_reply_to')) {
+ return state.set('in_reply_to', null);
+ } else {
+ return state;
+ }
+ case COMPOSE_EMOJI_INSERT:
+ return insertEmoji(state, action.position, action.emoji);
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js
@@ -0,0 +1,38 @@
+import { combineReducers } from 'redux-immutable';
+import timelines from './timelines';
+import meta from './meta';
+import compose from './compose';
+import alerts from './alerts';
+import { loadingBarReducer } from 'react-redux-loading-bar';
+import modal from './modal';
+import user_lists from './user_lists';
+import accounts from './accounts';
+import accounts_counters from './accounts_counters';
+import statuses from './statuses';
+import relationships from './relationships';
+import search from './search';
+import notifications from './notifications';
+import settings from './settings';
+import status_lists from './status_lists';
+import cards from './cards';
+import reports from './reports';
+
+export default combineReducers({
+ timelines,
+ meta,
+ compose,
+ alerts,
+ loadingBar: loadingBarReducer,
+ modal,
+ user_lists,
+ status_lists,
+ accounts,
+ accounts_counters,
+ statuses,
+ relationships,
+ search,
+ notifications,
+ settings,
+ cards,
+ reports
+});
diff --git a/app/javascript/mastodon/reducers/meta.js b/app/javascript/mastodon/reducers/meta.js
@@ -0,0 +1,17 @@
+import { STORE_HYDRATE } from '../actions/store';
+import Immutable from 'immutable';
+
+const initialState = Immutable.Map({
+ streaming_api_base_url: null,
+ access_token: null,
+ me: null
+});
+
+export default function meta(state = initialState, action) {
+ switch(action.type) {
+ case STORE_HYDRATE:
+ return state.merge(action.state.get('meta'));
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/modal.js b/app/javascript/mastodon/reducers/modal.js
@@ -0,0 +1,18 @@
+import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal';
+import Immutable from 'immutable';
+
+const initialState = {
+ modalType: null,
+ modalProps: {}
+};
+
+export default function modal(state = initialState, action) {
+ switch(action.type) {
+ case MODAL_OPEN:
+ return { modalType: action.modalType, modalProps: action.modalProps };
+ case MODAL_CLOSE:
+ return initialState;
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js
@@ -0,0 +1,104 @@
+import {
+ NOTIFICATIONS_UPDATE,
+ NOTIFICATIONS_REFRESH_SUCCESS,
+ NOTIFICATIONS_EXPAND_SUCCESS,
+ NOTIFICATIONS_REFRESH_REQUEST,
+ NOTIFICATIONS_EXPAND_REQUEST,
+ NOTIFICATIONS_REFRESH_FAIL,
+ NOTIFICATIONS_EXPAND_FAIL,
+ NOTIFICATIONS_CLEAR,
+ NOTIFICATIONS_SCROLL_TOP
+} from '../actions/notifications';
+import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts';
+import Immutable from 'immutable';
+
+const initialState = Immutable.Map({
+ items: Immutable.List(),
+ next: null,
+ top: true,
+ unread: 0,
+ loaded: false,
+ isLoading: true
+});
+
+const notificationToMap = notification => Immutable.Map({
+ id: notification.id,
+ type: notification.type,
+ account: notification.account.id,
+ status: notification.status ? notification.status.id : null
+});
+
+const normalizeNotification = (state, notification) => {
+ if (!state.get('top')) {
+ state = state.update('unread', unread => unread + 1);
+ }
+
+ return state.update('items', list => list.unshift(notificationToMap(notification)));
+};
+
+const normalizeNotifications = (state, notifications, next) => {
+ let items = Immutable.List();
+ const loaded = state.get('loaded');
+
+ notifications.forEach((n, i) => {
+ items = items.set(i, notificationToMap(n));
+ });
+
+ if (state.get('next') === null) {
+ state = state.set('next', next);
+ }
+
+ return state
+ .update('items', list => loaded ? items.concat(list) : list.concat(items))
+ .set('loaded', true)
+ .set('isLoading', false);
+};
+
+const appendNormalizedNotifications = (state, notifications, next) => {
+ let items = Immutable.List();
+
+ notifications.forEach((n, i) => {
+ items = items.set(i, notificationToMap(n));
+ });
+
+ return state
+ .update('items', list => list.concat(items))
+ .set('next', next)
+ .set('isLoading', false);
+};
+
+const filterNotifications = (state, relationship) => {
+ return state.update('items', list => list.filterNot(item => item.get('account') === relationship.id));
+};
+
+const updateTop = (state, top) => {
+ if (top) {
+ state = state.set('unread', 0);
+ }
+
+ return state.set('top', top);
+};
+
+export default function notifications(state = initialState, action) {
+ switch(action.type) {
+ case NOTIFICATIONS_REFRESH_REQUEST:
+ case NOTIFICATIONS_EXPAND_REQUEST:
+ case NOTIFICATIONS_REFRESH_FAIL:
+ case NOTIFICATIONS_EXPAND_FAIL:
+ return state.set('isLoading', true);
+ case NOTIFICATIONS_SCROLL_TOP:
+ return updateTop(state, action.top);
+ case NOTIFICATIONS_UPDATE:
+ return normalizeNotification(state, action.notification);
+ case NOTIFICATIONS_REFRESH_SUCCESS:
+ return normalizeNotifications(state, action.notifications, action.next);
+ case NOTIFICATIONS_EXPAND_SUCCESS:
+ return appendNormalizedNotifications(state, action.notifications, action.next);
+ case ACCOUNT_BLOCK_SUCCESS:
+ return filterNotifications(state, action.relationship);
+ case NOTIFICATIONS_CLEAR:
+ return state.set('items', Immutable.List()).set('next', null);
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js
@@ -0,0 +1,38 @@
+import {
+ ACCOUNT_FOLLOW_SUCCESS,
+ ACCOUNT_UNFOLLOW_SUCCESS,
+ ACCOUNT_BLOCK_SUCCESS,
+ ACCOUNT_UNBLOCK_SUCCESS,
+ ACCOUNT_MUTE_SUCCESS,
+ ACCOUNT_UNMUTE_SUCCESS,
+ RELATIONSHIPS_FETCH_SUCCESS
+} from '../actions/accounts';
+import Immutable from 'immutable';
+
+const normalizeRelationship = (state, relationship) => state.set(relationship.id, Immutable.fromJS(relationship));
+
+const normalizeRelationships = (state, relationships) => {
+ relationships.forEach(relationship => {
+ state = normalizeRelationship(state, relationship);
+ });
+
+ return state;
+};
+
+const initialState = Immutable.Map();
+
+export default function relationships(state = initialState, action) {
+ switch(action.type) {
+ case ACCOUNT_FOLLOW_SUCCESS:
+ case ACCOUNT_UNFOLLOW_SUCCESS:
+ case ACCOUNT_BLOCK_SUCCESS:
+ case ACCOUNT_UNBLOCK_SUCCESS:
+ case ACCOUNT_MUTE_SUCCESS:
+ case ACCOUNT_UNMUTE_SUCCESS:
+ return normalizeRelationship(state, action.relationship);
+ case RELATIONSHIPS_FETCH_SUCCESS:
+ return normalizeRelationships(state, action.relationships);
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/reports.js b/app/javascript/mastodon/reducers/reports.js
@@ -0,0 +1,60 @@
+import {
+ REPORT_INIT,
+ REPORT_SUBMIT_REQUEST,
+ REPORT_SUBMIT_SUCCESS,
+ REPORT_SUBMIT_FAIL,
+ REPORT_CANCEL,
+ REPORT_STATUS_TOGGLE,
+ REPORT_COMMENT_CHANGE
+} from '../actions/reports';
+import Immutable from 'immutable';
+
+const initialState = Immutable.Map({
+ new: Immutable.Map({
+ isSubmitting: false,
+ account_id: null,
+ status_ids: Immutable.Set(),
+ comment: ''
+ })
+});
+
+export default function reports(state = initialState, action) {
+ switch(action.type) {
+ case REPORT_INIT:
+ return state.withMutations(map => {
+ map.setIn(['new', 'isSubmitting'], false);
+ map.setIn(['new', 'account_id'], action.account.get('id'));
+
+ if (state.getIn(['new', 'account_id']) !== action.account.get('id')) {
+ map.setIn(['new', 'status_ids'], action.status ? Immutable.Set([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : Immutable.Set());
+ map.setIn(['new', 'comment'], '');
+ } else {
+ map.updateIn(['new', 'status_ids'], Immutable.Set(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id'))));
+ }
+ });
+ case REPORT_STATUS_TOGGLE:
+ return state.updateIn(['new', 'status_ids'], Immutable.Set(), set => {
+ if (action.checked) {
+ return set.add(action.statusId);
+ }
+
+ return set.remove(action.statusId);
+ });
+ case REPORT_COMMENT_CHANGE:
+ return state.setIn(['new', 'comment'], action.comment);
+ case REPORT_SUBMIT_REQUEST:
+ return state.setIn(['new', 'isSubmitting'], true);
+ case REPORT_SUBMIT_FAIL:
+ return state.setIn(['new', 'isSubmitting'], false);
+ case REPORT_CANCEL:
+ case REPORT_SUBMIT_SUCCESS:
+ return state.withMutations(map => {
+ map.setIn(['new', 'account_id'], null);
+ map.setIn(['new', 'status_ids'], Immutable.Set());
+ map.setIn(['new', 'comment'], '');
+ map.setIn(['new', 'isSubmitting'], false);
+ });
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js
@@ -0,0 +1,96 @@
+import {
+ SEARCH_CHANGE,
+ SEARCH_CLEAR,
+ SEARCH_FETCH_SUCCESS,
+ SEARCH_SHOW
+} from '../actions/search';
+import { COMPOSE_MENTION, COMPOSE_REPLY } from '../actions/compose';
+import Immutable from 'immutable';
+
+const initialState = Immutable.Map({
+ value: '',
+ submitted: false,
+ hidden: false,
+ 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:
+ return state.set('value', action.value);
+ case SEARCH_CLEAR:
+ return state.withMutations(map => {
+ map.set('value', '');
+ map.set('results', Immutable.Map());
+ map.set('submitted', false);
+ map.set('hidden', false);
+ });
+ case SEARCH_SHOW:
+ return state.set('hidden', false);
+ case COMPOSE_REPLY:
+ case COMPOSE_MENTION:
+ return state.set('hidden', true);
+ case SEARCH_FETCH_SUCCESS:
+ return state.set('results', Immutable.Map({
+ accounts: Immutable.List(action.results.accounts.map(item => item.id)),
+ statuses: Immutable.List(action.results.statuses.map(item => item.id)),
+ hashtags: Immutable.List(action.results.hashtags)
+ })).set('submitted', true);
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js
@@ -0,0 +1,52 @@
+import { SETTING_CHANGE } from '../actions/settings';
+import { STORE_HYDRATE } from '../actions/store';
+import Immutable from 'immutable';
+
+const initialState = Immutable.Map({
+ onboarded: false,
+
+ home: Immutable.Map({
+ shows: Immutable.Map({
+ reblog: true,
+ reply: true
+ }),
+
+ regex: Immutable.Map({
+ body: ''
+ })
+ }),
+
+ notifications: Immutable.Map({
+ alerts: Immutable.Map({
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ }),
+
+ shows: Immutable.Map({
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ }),
+
+ sounds: Immutable.Map({
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ })
+ })
+});
+
+export default function settings(state = initialState, action) {
+ switch(action.type) {
+ case STORE_HYDRATE:
+ return state.mergeDeep(action.state.get('settings'));
+ case SETTING_CHANGE:
+ return state.setIn(action.key, action.value);
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/status_lists.js b/app/javascript/mastodon/reducers/status_lists.js
@@ -0,0 +1,39 @@
+import {
+ FAVOURITED_STATUSES_FETCH_SUCCESS,
+ FAVOURITED_STATUSES_EXPAND_SUCCESS
+} from '../actions/favourites';
+import Immutable from 'immutable';
+
+const initialState = Immutable.Map({
+ favourites: Immutable.Map({
+ next: null,
+ loaded: false,
+ items: Immutable.List()
+ })
+});
+
+const normalizeList = (state, listType, statuses, next) => {
+ return state.update(listType, listMap => listMap.withMutations(map => {
+ map.set('next', next);
+ map.set('loaded', true);
+ map.set('items', Immutable.List(statuses.map(item => item.id)));
+ }));
+};
+
+const appendToList = (state, listType, statuses, next) => {
+ return state.update(listType, listMap => listMap.withMutations(map => {
+ map.set('next', next);
+ map.set('items', map.get('items').concat(statuses.map(item => item.id)));
+ }));
+};
+
+export default function statusLists(state = initialState, action) {
+ switch(action.type) {
+ case FAVOURITED_STATUSES_FETCH_SUCCESS:
+ return normalizeList(state, 'favourites', action.statuses, action.next);
+ case FAVOURITED_STATUSES_EXPAND_SUCCESS:
+ return appendToList(state, 'favourites', action.statuses, action.next);
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js
@@ -0,0 +1,124 @@
+import {
+ REBLOG_REQUEST,
+ REBLOG_SUCCESS,
+ REBLOG_FAIL,
+ UNREBLOG_SUCCESS,
+ FAVOURITE_REQUEST,
+ FAVOURITE_SUCCESS,
+ FAVOURITE_FAIL,
+ UNFAVOURITE_SUCCESS
+} from '../actions/interactions';
+import {
+ STATUS_FETCH_SUCCESS,
+ CONTEXT_FETCH_SUCCESS
+} from '../actions/statuses';
+import {
+ TIMELINE_REFRESH_SUCCESS,
+ TIMELINE_UPDATE,
+ TIMELINE_DELETE,
+ TIMELINE_EXPAND_SUCCESS
+} from '../actions/timelines';
+import {
+ ACCOUNT_TIMELINE_FETCH_SUCCESS,
+ ACCOUNT_TIMELINE_EXPAND_SUCCESS,
+ ACCOUNT_BLOCK_SUCCESS
+} from '../actions/accounts';
+import {
+ NOTIFICATIONS_UPDATE,
+ NOTIFICATIONS_REFRESH_SUCCESS,
+ NOTIFICATIONS_EXPAND_SUCCESS
+} from '../actions/notifications';
+import {
+ FAVOURITED_STATUSES_FETCH_SUCCESS,
+ FAVOURITED_STATUSES_EXPAND_SUCCESS
+} from '../actions/favourites';
+import { SEARCH_FETCH_SUCCESS } from '../actions/search';
+import Immutable from 'immutable';
+
+const normalizeStatus = (state, status) => {
+ if (!status) {
+ return state;
+ }
+
+ const normalStatus = { ...status };
+ normalStatus.account = status.account.id;
+
+ if (status.reblog && status.reblog.id) {
+ state = normalizeStatus(state, status.reblog);
+ normalStatus.reblog = status.reblog.id;
+ }
+
+ const linebreakComplemented = status.content.replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
+ normalStatus.unescaped_content = new DOMParser().parseFromString(linebreakComplemented, 'text/html').documentElement.textContent;
+
+ return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(normalStatus)));
+};
+
+const normalizeStatuses = (state, statuses) => {
+ statuses.forEach(status => {
+ state = normalizeStatus(state, status);
+ });
+
+ return state;
+};
+
+const deleteStatus = (state, id, references) => {
+ references.forEach(ref => {
+ state = deleteStatus(state, ref[0], []);
+ });
+
+ return state.delete(id);
+};
+
+const filterStatuses = (state, relationship) => {
+ state.forEach(status => {
+ if (status.get('account') !== relationship.id) {
+ return;
+ }
+
+ state = deleteStatus(state, status.get('id'), state.filter(item => item.get('reblog') === status.get('id')));
+ });
+
+ return state;
+};
+
+const initialState = Immutable.Map();
+
+export default function statuses(state = initialState, action) {
+ switch(action.type) {
+ case TIMELINE_UPDATE:
+ case STATUS_FETCH_SUCCESS:
+ case NOTIFICATIONS_UPDATE:
+ return normalizeStatus(state, action.status);
+ case REBLOG_SUCCESS:
+ case UNREBLOG_SUCCESS:
+ case FAVOURITE_SUCCESS:
+ case UNFAVOURITE_SUCCESS:
+ return normalizeStatus(state, action.response);
+ case FAVOURITE_REQUEST:
+ return state.setIn([action.status.get('id'), 'favourited'], true);
+ case FAVOURITE_FAIL:
+ return state.setIn([action.status.get('id'), 'favourited'], false);
+ case REBLOG_REQUEST:
+ return state.setIn([action.status.get('id'), 'reblogged'], true);
+ case REBLOG_FAIL:
+ return state.setIn([action.status.get('id'), 'reblogged'], false);
+ case TIMELINE_REFRESH_SUCCESS:
+ case TIMELINE_EXPAND_SUCCESS:
+ case ACCOUNT_TIMELINE_FETCH_SUCCESS:
+ case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
+ case CONTEXT_FETCH_SUCCESS:
+ case NOTIFICATIONS_REFRESH_SUCCESS:
+ case NOTIFICATIONS_EXPAND_SUCCESS:
+ case FAVOURITED_STATUSES_FETCH_SUCCESS:
+ case FAVOURITED_STATUSES_EXPAND_SUCCESS:
+ case SEARCH_FETCH_SUCCESS:
+ return normalizeStatuses(state, action.statuses);
+ case TIMELINE_DELETE:
+ return deleteStatus(state, action.id, action.references);
+ case ACCOUNT_BLOCK_SUCCESS:
+ return filterStatuses(state, action.relationship);
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js
@@ -0,0 +1,317 @@
+import {
+ TIMELINE_REFRESH_REQUEST,
+ TIMELINE_REFRESH_SUCCESS,
+ TIMELINE_REFRESH_FAIL,
+ TIMELINE_UPDATE,
+ TIMELINE_DELETE,
+ TIMELINE_EXPAND_SUCCESS,
+ TIMELINE_EXPAND_REQUEST,
+ TIMELINE_EXPAND_FAIL,
+ TIMELINE_SCROLL_TOP,
+ TIMELINE_CONNECT,
+ TIMELINE_DISCONNECT
+} from '../actions/timelines';
+import {
+ REBLOG_SUCCESS,
+ UNREBLOG_SUCCESS,
+ FAVOURITE_SUCCESS,
+ UNFAVOURITE_SUCCESS
+} from '../actions/interactions';
+import {
+ ACCOUNT_TIMELINE_FETCH_REQUEST,
+ ACCOUNT_TIMELINE_FETCH_SUCCESS,
+ ACCOUNT_TIMELINE_FETCH_FAIL,
+ ACCOUNT_TIMELINE_EXPAND_REQUEST,
+ ACCOUNT_TIMELINE_EXPAND_SUCCESS,
+ ACCOUNT_TIMELINE_EXPAND_FAIL,
+ ACCOUNT_BLOCK_SUCCESS,
+ ACCOUNT_MUTE_SUCCESS
+} from '../actions/accounts';
+import {
+ CONTEXT_FETCH_SUCCESS
+} from '../actions/statuses';
+import Immutable from 'immutable';
+
+const initialState = Immutable.Map({
+ home: Immutable.Map({
+ path: () => '/api/v1/timelines/home',
+ next: null,
+ isLoading: false,
+ online: false,
+ loaded: false,
+ top: true,
+ unread: 0,
+ items: Immutable.List()
+ }),
+
+ public: Immutable.Map({
+ path: () => '/api/v1/timelines/public',
+ next: null,
+ isLoading: false,
+ online: false,
+ loaded: false,
+ top: true,
+ unread: 0,
+ items: Immutable.List()
+ }),
+
+ community: Immutable.Map({
+ path: () => '/api/v1/timelines/public',
+ next: null,
+ params: { local: true },
+ isLoading: false,
+ online: false,
+ loaded: false,
+ top: true,
+ unread: 0,
+ items: Immutable.List()
+ }),
+
+ tag: Immutable.Map({
+ path: (id) => `/api/v1/timelines/tag/${id}`,
+ next: null,
+ isLoading: false,
+ id: null,
+ loaded: false,
+ top: true,
+ unread: 0,
+ items: Immutable.List()
+ }),
+
+ accounts_timelines: Immutable.Map(),
+ ancestors: Immutable.Map(),
+ descendants: Immutable.Map()
+});
+
+const normalizeStatus = (state, status) => {
+ const replyToId = status.get('in_reply_to_id');
+ const id = status.get('id');
+
+ if (replyToId) {
+ if (!state.getIn(['descendants', replyToId], Immutable.List()).includes(id)) {
+ state = state.updateIn(['descendants', replyToId], Immutable.List(), set => set.push(id));
+ }
+
+ if (!state.getIn(['ancestors', id], Immutable.List()).includes(replyToId)) {
+ state = state.updateIn(['ancestors', id], Immutable.List(), set => set.push(replyToId));
+ }
+ }
+
+ return state;
+};
+
+const normalizeTimeline = (state, timeline, statuses, next) => {
+ let ids = Immutable.List();
+ const loaded = state.getIn([timeline, 'loaded']);
+
+ statuses.forEach((status, i) => {
+ state = normalizeStatus(state, status);
+ ids = ids.set(i, status.get('id'));
+ });
+
+ state = state.setIn([timeline, 'loaded'], true);
+ state = state.setIn([timeline, 'isLoading'], false);
+
+ if (state.getIn([timeline, 'next']) === null) {
+ state = state.setIn([timeline, 'next'], next);
+ }
+
+ return state.updateIn([timeline, 'items'], Immutable.List(), list => (loaded ? ids.concat(list) : ids));
+};
+
+const appendNormalizedTimeline = (state, timeline, statuses, next) => {
+ let moreIds = Immutable.List();
+
+ statuses.forEach((status, i) => {
+ state = normalizeStatus(state, status);
+ moreIds = moreIds.set(i, status.get('id'));
+ });
+
+ state = state.setIn([timeline, 'isLoading'], false);
+ state = state.setIn([timeline, 'next'], next);
+
+ return state.updateIn([timeline, 'items'], Immutable.List(), list => list.concat(moreIds));
+};
+
+const normalizeAccountTimeline = (state, accountId, statuses, replace = false) => {
+ let ids = Immutable.List();
+
+ statuses.forEach((status, i) => {
+ state = normalizeStatus(state, status);
+ ids = ids.set(i, status.get('id'));
+ });
+
+ return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map
+ .set('isLoading', false)
+ .set('loaded', true)
+ .set('next', true)
+ .update('items', Immutable.List(), list => (replace ? ids : ids.concat(list))));
+};
+
+const appendNormalizedAccountTimeline = (state, accountId, statuses, next) => {
+ let moreIds = Immutable.List([]);
+
+ statuses.forEach((status, i) => {
+ state = normalizeStatus(state, status);
+ moreIds = moreIds.set(i, status.get('id'));
+ });
+
+ return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map
+ .set('isLoading', false)
+ .set('next', next)
+ .update('items', list => list.concat(moreIds)));
+};
+
+const updateTimeline = (state, timeline, status, references) => {
+ const top = state.getIn([timeline, 'top']);
+
+ state = normalizeStatus(state, status);
+
+ if (!top) {
+ state = state.updateIn([timeline, 'unread'], unread => unread + 1);
+ }
+
+ state = state.updateIn([timeline, 'items'], Immutable.List(), list => {
+ if (top && list.size > 40) {
+ list = list.take(20);
+ }
+
+ if (list.includes(status.get('id'))) {
+ return list;
+ }
+
+ const reblogOfId = status.getIn(['reblog', 'id'], null);
+
+ if (reblogOfId !== null) {
+ list = list.filterNot(itemId => references.includes(itemId));
+ }
+
+ return list.unshift(status.get('id'));
+ });
+
+ return state;
+};
+
+const deleteStatus = (state, id, accountId, references, reblogOf) => {
+ if (reblogOf) {
+ // If we are deleting a reblog, just replace reblog with its original
+ return state.updateIn(['home', 'items'], list => list.map(item => item === id ? reblogOf : item));
+ }
+
+ // Remove references from timelines
+ ['home', 'public', 'community', 'tag'].forEach(function (timeline) {
+ state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id));
+ });
+
+ // Remove references from account timelines
+ state = state.updateIn(['accounts_timelines', accountId, 'items'], Immutable.List([]), list => list.filterNot(item => item === id));
+
+ // Remove references from context
+ state.getIn(['descendants', id], Immutable.List()).forEach(descendantId => {
+ state = state.updateIn(['ancestors', descendantId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
+ });
+
+ state.getIn(['ancestors', id], Immutable.List()).forEach(ancestorId => {
+ state = state.updateIn(['descendants', ancestorId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
+ });
+
+ state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]);
+
+ // Remove reblogs of deleted status
+ references.forEach(ref => {
+ state = deleteStatus(state, ref[0], ref[1], []);
+ });
+
+ return state;
+};
+
+const filterTimelines = (state, relationship, statuses) => {
+ let references;
+
+ statuses.forEach(status => {
+ if (status.get('account') !== relationship.id) {
+ return;
+ }
+
+ references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
+ state = deleteStatus(state, status.get('id'), status.get('account'), references);
+ });
+
+ return state;
+};
+
+const normalizeContext = (state, id, ancestors, descendants) => {
+ const ancestorsIds = ancestors.map(ancestor => ancestor.get('id'));
+ const descendantsIds = descendants.map(descendant => descendant.get('id'));
+
+ return state.withMutations(map => {
+ map.setIn(['ancestors', id], ancestorsIds);
+ map.setIn(['descendants', id], descendantsIds);
+ });
+};
+
+const resetTimeline = (state, timeline, id) => {
+ if (timeline === 'tag' && typeof id !== 'undefined' && state.getIn([timeline, 'id']) !== id) {
+ state = state.update(timeline, map => map
+ .set('id', id)
+ .set('isLoading', true)
+ .set('loaded', false)
+ .set('next', null)
+ .set('top', true)
+ .update('items', list => list.clear()));
+ } else {
+ state = state.setIn([timeline, 'isLoading'], true);
+ }
+
+ return state;
+};
+
+const updateTop = (state, timeline, top) => {
+ if (top) {
+ state = state.setIn([timeline, 'unread'], 0);
+ }
+
+ return state.setIn([timeline, 'top'], top);
+};
+
+export default function timelines(state = initialState, action) {
+ switch(action.type) {
+ case TIMELINE_REFRESH_REQUEST:
+ case TIMELINE_EXPAND_REQUEST:
+ return resetTimeline(state, action.timeline, action.id);
+ case TIMELINE_REFRESH_FAIL:
+ case TIMELINE_EXPAND_FAIL:
+ return state.setIn([action.timeline, 'isLoading'], false);
+ case TIMELINE_REFRESH_SUCCESS:
+ return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
+ case TIMELINE_EXPAND_SUCCESS:
+ return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
+ case TIMELINE_UPDATE:
+ return updateTimeline(state, action.timeline, Immutable.fromJS(action.status), action.references);
+ case TIMELINE_DELETE:
+ return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
+ case CONTEXT_FETCH_SUCCESS:
+ return normalizeContext(state, action.id, Immutable.fromJS(action.ancestors), Immutable.fromJS(action.descendants));
+ case ACCOUNT_TIMELINE_FETCH_REQUEST:
+ case ACCOUNT_TIMELINE_EXPAND_REQUEST:
+ return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', true));
+ case ACCOUNT_TIMELINE_FETCH_FAIL:
+ case ACCOUNT_TIMELINE_EXPAND_FAIL:
+ return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', false));
+ case ACCOUNT_TIMELINE_FETCH_SUCCESS:
+ return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace);
+ case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
+ return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.next);
+ case ACCOUNT_BLOCK_SUCCESS:
+ case ACCOUNT_MUTE_SUCCESS:
+ return filterTimelines(state, action.relationship, action.statuses);
+ case TIMELINE_SCROLL_TOP:
+ return updateTop(state, action.timeline, action.top);
+ case TIMELINE_CONNECT:
+ return state.setIn([action.timeline, 'online'], true);
+ case TIMELINE_DISCONNECT:
+ return state.setIn([action.timeline, 'online'], false);
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/reducers/user_lists.js b/app/javascript/mastodon/reducers/user_lists.js
@@ -0,0 +1,80 @@
+import {
+ FOLLOWERS_FETCH_SUCCESS,
+ FOLLOWERS_EXPAND_SUCCESS,
+ FOLLOWING_FETCH_SUCCESS,
+ FOLLOWING_EXPAND_SUCCESS,
+ FOLLOW_REQUESTS_FETCH_SUCCESS,
+ FOLLOW_REQUESTS_EXPAND_SUCCESS,
+ FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
+ FOLLOW_REQUEST_REJECT_SUCCESS
+} from '../actions/accounts';
+import {
+ REBLOGS_FETCH_SUCCESS,
+ FAVOURITES_FETCH_SUCCESS
+} from '../actions/interactions';
+import {
+ BLOCKS_FETCH_SUCCESS,
+ BLOCKS_EXPAND_SUCCESS
+} from '../actions/blocks';
+import {
+ MUTES_FETCH_SUCCESS,
+ MUTES_EXPAND_SUCCESS
+} from '../actions/mutes';
+import Immutable from 'immutable';
+
+const initialState = Immutable.Map({
+ followers: Immutable.Map(),
+ following: Immutable.Map(),
+ reblogged_by: Immutable.Map(),
+ favourited_by: Immutable.Map(),
+ follow_requests: Immutable.Map(),
+ blocks: Immutable.Map(),
+ mutes: Immutable.Map()
+});
+
+const normalizeList = (state, type, id, accounts, next) => {
+ return state.setIn([type, id], Immutable.Map({
+ next,
+ items: Immutable.List(accounts.map(item => item.id))
+ }));
+};
+
+const appendToList = (state, type, id, accounts, next) => {
+ return state.updateIn([type, id], map => {
+ return map.set('next', next).update('items', list => list.concat(accounts.map(item => item.id)));
+ });
+};
+
+export default function userLists(state = initialState, action) {
+ switch(action.type) {
+ case FOLLOWERS_FETCH_SUCCESS:
+ return normalizeList(state, 'followers', action.id, action.accounts, action.next);
+ case FOLLOWERS_EXPAND_SUCCESS:
+ return appendToList(state, 'followers', action.id, action.accounts, action.next);
+ case FOLLOWING_FETCH_SUCCESS:
+ return normalizeList(state, 'following', action.id, action.accounts, action.next);
+ case FOLLOWING_EXPAND_SUCCESS:
+ return appendToList(state, 'following', action.id, action.accounts, action.next);
+ case REBLOGS_FETCH_SUCCESS:
+ return state.setIn(['reblogged_by', action.id], Immutable.List(action.accounts.map(item => item.id)));
+ case FAVOURITES_FETCH_SUCCESS:
+ return state.setIn(['favourited_by', action.id], Immutable.List(action.accounts.map(item => item.id)));
+ case FOLLOW_REQUESTS_FETCH_SUCCESS:
+ return state.setIn(['follow_requests', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next);
+ case FOLLOW_REQUESTS_EXPAND_SUCCESS:
+ return state.updateIn(['follow_requests', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next);
+ case FOLLOW_REQUEST_AUTHORIZE_SUCCESS:
+ case FOLLOW_REQUEST_REJECT_SUCCESS:
+ return state.updateIn(['follow_requests', 'items'], list => list.filterNot(item => item === action.id));
+ case BLOCKS_FETCH_SUCCESS:
+ return state.setIn(['blocks', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next);
+ case BLOCKS_EXPAND_SUCCESS:
+ return state.updateIn(['blocks', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next);
+ case MUTES_FETCH_SUCCESS:
+ return state.setIn(['mutes', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
+ case MUTES_EXPAND_SUCCESS:
+ return state.updateIn(['mutes', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/mastodon/rtl.js b/app/javascript/mastodon/rtl.js
@@ -0,0 +1,27 @@
+// U+0590 to U+05FF - Hebrew
+// U+0600 to U+06FF - Arabic
+// U+0700 to U+074F - Syriac
+// U+0750 to U+077F - Arabic Supplement
+// U+0780 to U+07BF - Thaana
+// U+07C0 to U+07FF - N'Ko
+// U+0800 to U+083F - Samaritan
+// U+08A0 to U+08FF - Arabic Extended-A
+// U+FB1D to U+FB4F - Hebrew presentation forms
+// U+FB50 to U+FDFF - Arabic presentation forms A
+// U+FE70 to U+FEFF - Arabic presentation forms B
+
+const rtlChars = /[\u0590-\u083F]|[\u08A0-\u08FF]|[\uFB1D-\uFDFF]|[\uFE70-\uFEFF]/mg;
+
+export function isRtl(text) {
+ if (text.length === 0) {
+ return false;
+ }
+
+ const matches = text.match(rtlChars);
+
+ if (!matches) {
+ return false;
+ }
+
+ return matches.length / text.trim().length > 0.3;
+};
diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js
@@ -0,0 +1,73 @@
+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);
+
+export const makeGetAccount = () => {
+ return createSelector([getAccountBase, getAccountCounters, getAccountRelationship], (base, counters, relationship) => {
+ if (base === null) {
+ return null;
+ }
+
+ return base.merge(counters).set('relationship', relationship);
+ });
+};
+
+export const makeGetStatus = () => {
+ return createSelector(
+ [
+ (state, id) => state.getIn(['statuses', id]),
+ (state, id) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]),
+ (state, id) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
+ (state, id) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
+ ],
+
+ (statusBase, statusReblog, accountBase, accountReblog) => {
+ if (!statusBase) {
+ return null;
+ }
+
+ if (statusReblog) {
+ statusReblog = statusReblog.set('account', accountReblog);
+ } else {
+ statusReblog = null;
+ }
+
+ return statusBase.withMutations(map => {
+ map.set('reblog', statusReblog);
+ map.set('account', accountBase);
+ });
+ }
+ );
+};
+
+const getAlertsBase = state => state.get('alerts');
+
+export const getAlerts = createSelector([getAlertsBase], (base) => {
+ let arr = [];
+
+ base.forEach(item => {
+ arr.push({
+ message: item.get('message'),
+ title: item.get('title'),
+ key: item.get('key'),
+ dismissAfter: 5000
+ });
+ });
+
+ return arr;
+});
+
+export const makeGetNotification = () => {
+ return createSelector([
+ (_, base) => base,
+ (state, _, accountId) => state.getIn(['accounts', accountId])
+ ], (base, account) => {
+ return base.set('account', account);
+ });
+};
diff --git a/app/javascript/mastodon/store/configureStore.js b/app/javascript/mastodon/store/configureStore.js
@@ -0,0 +1,16 @@
+import { createStore, applyMiddleware, compose } from 'redux';
+import thunk from 'redux-thunk';
+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(
+ thunk,
+ loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
+ errorsMiddleware(),
+ soundsMiddleware()
+ ), window.devToolsExtension ? window.devToolsExtension() : f => f));
+};
diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js
@@ -0,0 +1,22 @@
+import WebSocketClient from 'websocket.js';
+
+const createWebSocketURL = (url) => {
+ const a = document.createElement('a');
+
+ a.href = url;
+ a.href = a.href;
+ a.protocol = a.protocol.replace('http', 'ws');
+
+ return a.href;
+};
+
+export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) {
+ const ws = new WebSocketClient(`${createWebSocketURL(streamingAPIBaseURL)}/api/v1/streaming/?access_token=${accessToken}&stream=${stream}`);
+
+ ws.onopen = connected;
+ ws.onmessage = e => received(JSON.parse(e.data));
+ ws.onclose = disconnected;
+ ws.onreconnect = reconnected;
+
+ return ws;
+};
diff --git a/app/javascript/mastodon/uuid.js b/app/javascript/mastodon/uuid.js
@@ -0,0 +1,3 @@
+export default function uuid(a) {
+ return a ? (a^Math.random() * 16 >> a / 4).toString(16) : ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, uuid);
+};
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
@@ -0,0 +1,29 @@
+import Mastodon from 'mastodon/containers/mastodon';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import 'font-awesome/css/font-awesome.css';
+import '../styles/application.scss';
+
+if (!window.Intl) {
+ require('intl');
+ require('intl/locale-data/jsonp/en.js');
+}
+
+window.jQuery = window.$ = require('jquery');
+window.Perf = require('react-addons-perf');
+
+require('jquery-ujs');
+require.context('../images/', true);
+
+const customContext = require.context('../../assets/stylesheets/', false);
+
+if (customContext.keys().indexOf('./custom.scss') !== -1) {
+ customContext('./custom.scss');
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ const mountNode = document.getElementById('mastodon');
+ const props = JSON.parse(mountNode.getAttribute('data-props'));
+
+ ReactDOM.render(<Mastodon {...props} />, mountNode);
+});
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
@@ -0,0 +1,53 @@
+import emojify from 'mastodon/emoji';
+import { length } from 'stringz';
+
+window.jQuery = window.$ = require('jquery');
+require('jquery-ujs');
+require.context('../images/', true);
+
+$(() => {
+ $.each($('.emojify'), (_, content) => {
+ const $content = $(content);
+ $content.html(emojify($content.html()));
+ });
+
+ $('.video-player video').on('click', e => {
+ if (e.target.paused) {
+ e.target.play();
+ } else {
+ e.target.pause();
+ }
+ });
+
+ $('.media-spoiler').on('click', e => {
+ $(e.target).hide();
+ });
+
+ $('.webapp-btn').on('click', e => {
+ if (e.button === 0) {
+ e.preventDefault();
+ window.location.href = $(e.target).attr('href');
+ }
+ });
+
+ $('.status__content__spoiler-link').on('click', e => {
+ e.preventDefault();
+ const contentEl = $(e.target).parent().parent().find('div');
+
+ if (contentEl.is(':visible')) {
+ contentEl.hide();
+ $(e.target).parent().attr('style', 'margin-bottom: 0');
+ } else {
+ contentEl.show();
+ $(e.target).parent().attr('style', null);
+ }
+ });
+
+ $('.account_display_name').on('input', e => {
+ $('.name-counter').text(30 - length($(e.target).val()));
+ });
+
+ $('.account_note').on('input', e => {
+ $('.note-counter').text(160 - length($(e.target).val()));
+ });
+});
diff --git a/app/javascript/styles/about.scss b/app/javascript/styles/about.scss
@@ -0,0 +1,374 @@
+.about-body {
+ .wrapper {
+ max-width: 600px;
+ margin: 0 auto;
+ color: $color3;
+ padding-top: 50px;
+ padding-bottom: 50px;
+
+ &.thicc {
+ max-width: 700px;
+ }
+ }
+
+ h1 {
+ font: 46px/52px 'Roboto', sans-serif;
+ font-weight: 600;
+ margin-bottom: 20px;
+ color: $color4;
+ padding: 20px 0;
+
+ img {
+ margin-bottom: -5px;
+ margin-right: 5px;
+ width: 46px;
+ height: 46px;
+ }
+ }
+
+ h2 {
+ font-family: 'Montserrat', sans-serif;
+ font-size: 24px;
+ line-height: 28px;
+ font-weight: 400;
+ margin-bottom: 20px;
+ color: $color5;
+ }
+
+ h3 {
+ font-family: 'Montserrat', sans-serif;
+ font-size: 20px;
+ line-height: 28px;
+ font-weight: 400;
+ margin-bottom: 20px;
+ color: $color2;
+ }
+
+ ul, ol {
+ list-style: inherit;
+ margin-left: 20px;
+
+ &[type='a'] {
+ list-style-type: lower-alpha;
+ }
+
+ &[type='i'] {
+ list-style-type: lower-roman;
+ }
+ }
+
+ li > ol, li > ul {
+ margin-top: 20px;
+ }
+
+ p, li {
+ font: 16px/28px 'Montserrat', sans-serif;
+ font-weight: 400;
+ margin-bottom: 12px;
+
+ a {
+ color: $color4;
+ text-decoration: underline;
+ }
+ }
+
+ em {
+ display: inline-block;
+ padding: 7px 7px 5px 7px;
+ margin: 0 2px;
+ background: $color3;
+ color: $color1;
+ font: 16px/16px 'Montserrat', sans-serif;
+ font-weight: 300;
+ }
+
+ .screenshot {
+ box-shadow: 0 0 15px rgba($color8, 0.4);
+ margin-bottom: 26px;
+
+ img {
+ max-width: 100%;
+ height: auto;
+ display: block;
+ }
+ }
+
+ .actions {
+ overflow: hidden;
+ margin-bottom: 20px;
+
+ .info {
+ float: right;
+ text-align: right;
+ line-height: 36px;
+
+ a {
+ color: $color3;
+ text-decoration: underline;
+ }
+ }
+ }
+
+ @media screen and (max-width: 625px) {
+ .wrapper {
+ padding: 20px;
+ }
+
+ .features-list {
+ display: block;
+ }
+ }
+}
+
+.information-board {
+ margin: 20px 0;
+ display: flex;
+ justify-content: space-between;
+ border-top: 1px solid lighten($color1, 10%);
+ border-bottom: 1px solid lighten($color1, 10%);
+ padding-right: 14px;
+
+ .section {
+ flex: 1 0 0;
+ padding: 14px;
+ text-align: right;
+ font: 16px/28px 'Montserrat', sans-serif;
+
+ span, strong {
+ display: block;
+ }
+
+ span {
+ font-size: 16px;
+
+ &:last-child {
+ color: $color2;
+ font-size: 14px;
+ }
+ }
+
+ strong {
+ font-weight: 500;
+ font-size: 32px;
+ line-height: 48px;
+ color: $color5;
+ }
+ }
+
+ @media screen and (max-width: 500px) {
+ flex-direction: column;
+
+ .section {
+ text-align: left;
+ }
+ }
+}
+
+.owner {
+ text-align: center;
+
+ .avatar {
+ width: 80px;
+ height: 80px;
+ margin: 0 auto;
+ margin-bottom: 15px;
+
+ img {
+ display: block;
+ width: 80px;
+ height: 80px;
+ border-radius: 48px;
+ }
+ }
+
+ .name {
+ font-size: 14px;
+
+ a {
+ display: block;
+ color: $color5;
+ text-decoration: none;
+
+ &:hover {
+ .display_name {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .username {
+ display: block;
+ color: $color3;
+ }
+ }
+}
+
+.contact-email {
+ text-align: center;
+ margin: 40px 0;
+
+ strong {
+ display: block;
+ color: $color5;
+ word-break: break-word;
+ }
+}
+
+.sidebar-layout {
+ display: flex;
+
+ .main {
+ flex: 1 1 auto;
+ padding: 14px 0;
+
+ .panel {
+ padding-right: 14px;
+ }
+ }
+
+ .sidebar {
+ border-left: 1px solid lighten($color1, 10%);
+ width: 180px;
+ flex: 0 0 auto;
+ }
+
+ .panel {
+ .panel-header {
+ background: lighten($color1, 10%);
+ padding: 7px 14px;
+ text-transform: uppercase;
+ font-size: 12px;
+ font-weight: 500;
+ }
+
+ .panel-body {
+ padding: 14px;
+ }
+
+ .panel-list {
+ ul {
+ list-style: none;
+ margin: 0;
+
+ li {
+ margin: 0;
+ font-family: inherit;
+ font-size: 13px;
+ line-height: 18px;
+
+ a {
+ display: block;
+ padding: 7px 14px;
+ color: rgba($color5, 0.7);
+ text-decoration: none;
+ transition: all 200ms linear;
+
+ i.fa {
+ margin-right: 5px;
+ }
+
+ &:hover {
+ color: $color5;
+ background-color: darken($color1, 5%);
+ transition: all 100ms linear;
+ }
+
+ &.selected {
+ color: $color5;
+ background-color: $color4;
+
+ &:hover {
+ background-color: lighten($color4, 5%);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @media screen and (max-width: 625px) {
+ flex-direction: column;
+
+ .sidebar {
+ border: 1px solid lighten($color1, 10%);
+ width: auto;
+ }
+ }
+}
+
+.features-list {
+ display: flex;
+ margin-bottom: 20px;
+
+ .features-list__column {
+ flex: 1 1 0;
+
+ ul {
+ list-style: none;
+ }
+
+ li {
+ margin: 0;
+ }
+ }
+}
+
+.screenshot-with-signup {
+ display: flex;
+ margin-bottom: 20px;
+
+ .mascot {
+ flex: 1 1 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+
+ img {
+ display: block;
+ margin: 0 auto;
+ max-width: 100%;
+ height: auto;
+ }
+ }
+
+ .simple_form, .closed-registrations-message {
+ width: 300px;
+ flex: 0 0 auto;
+ background: rgba(darken($color1, 7%), 0.5);
+ padding: 14px;
+ border-radius: 4px;
+ box-shadow: 0 0 15px rgba($color8, 0.4);
+
+ .actions {
+ margin-bottom: 0;
+ }
+
+ .info {
+ text-align: center;
+
+ a {
+ color: $color2;
+ }
+ }
+ }
+
+ @media screen and (max-width: 625px) {
+ .mascot {
+ display: none;
+ }
+
+ .simple_form, .closed-registrations-message {
+ flex: auto;
+ }
+ }
+}
+
+.closed-registrations-message {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+}
diff --git a/app/javascript/styles/accounts.scss b/app/javascript/styles/accounts.scss
@@ -0,0 +1,391 @@
+.card {
+ background: $color1;
+ background-size: cover;
+ padding: 60px 0;
+ padding-bottom: 0;
+ border-radius: 4px 4px 0 0;
+ box-shadow: 0 0 15px rgba($color8, 0.2);
+ overflow: hidden;
+ position: relative;
+
+ @media screen and (max-width: 700px) {
+ border-radius: 0;
+ box-shadow: none;
+ }
+
+ &:after {
+ background: linear-gradient(rgba($color8, 0.5), rgba($color8, 0.8));
+ display: block;
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ }
+
+ .name {
+ display: block;
+ font-size: 20px;
+ line-height: 18px * 1.5;
+ color: $color5;
+ font-weight: 500;
+ text-align: center;
+ position: relative;
+ z-index: 2;
+ text-shadow: 0 0 2px $color8;
+
+ small {
+ display: block;
+ font-size: 14px;
+ color: $color4;
+ font-weight: 400;
+ }
+ }
+
+ .avatar {
+ width: 120px;
+ margin: 0 auto;
+ margin-bottom: 15px;
+ position: relative;
+ z-index: 2;
+
+ img {
+ width: 120px;
+ height: 120px;
+ display: block;
+ border-radius: 120px;
+ }
+ }
+
+ .controls {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ z-index: 2;
+ }
+
+ .details {
+ display: flex;
+ margin-top: 30px;
+ position: relative;
+ z-index: 2;
+ flex-direction: row;
+ }
+
+ .details-counters {
+ display: flex;
+ flex-direction: row;
+ order: 0;
+ }
+
+ .counter {
+ width: 80px;
+ color: $color3;
+ padding: 5px 10px 0px;
+ margin-bottom: 10px;
+ border-right: 1px solid $color3;
+ cursor: default;
+ position: relative;
+
+ a {
+ display: block;
+ }
+
+ &:after {
+ display: block;
+ content: "";
+ position: absolute;
+ bottom: -10px;
+ left: 0;
+ width: 100%;
+ border-bottom: 4px solid $color3;
+ opacity: 0.5;
+ transition: all 0.8s ease;
+ }
+
+ &.active {
+ &:after {
+ border-bottom: 4px solid $color4;
+ opacity: 1;
+ }
+ }
+
+ &:hover {
+ &:after {
+ opacity: 1;
+ transition-duration: 0.2s;
+ }
+ }
+
+ a {
+ text-decoration: none;
+ color: inherit;
+ }
+
+ .counter-label {
+ font-size: 12px;
+ text-transform: uppercase;
+ display: block;
+ margin-bottom: 5px;
+ text-shadow: 0 0 2px $color8;
+ }
+
+ .counter-number {
+ font-weight: 500;
+ font-size: 18px;
+ color: $color5;
+ }
+ }
+
+ .bio {
+ flex: 1;
+ font-size: 14px;
+ line-height: 18px;
+ padding: 5px 10px;
+ color: $color2;
+ order: 1;
+ }
+
+ @media screen and (max-width: 480px) {
+ .details {
+ display: block;
+ }
+
+ .bio {
+ text-align: center;
+ margin-bottom: 20px;
+ }
+
+ .counter {
+ flex: 1 1 auto;
+ }
+
+ .counter:last-child {
+ border-right: none;
+ }
+ }
+}
+
+.pagination {
+ padding: 30px 0;
+ text-align: center;
+ overflow: hidden;
+
+ a, .current, .next, .prev, .page, .gap {
+ font-size: 14px;
+ color: $color5;
+ font-weight: 500;
+ display: inline-block;
+ padding: 6px 10px;
+ text-decoration: none;
+ }
+
+ .current {
+ background: $color5;
+ border-radius: 100px;
+ color: $color1;
+ cursor: default;
+ margin: 0 10px;
+ }
+
+ .gap {
+ cursor: default;
+ }
+
+ .prev, .next {
+ text-transform: uppercase;
+ color: $color2;
+ }
+
+ .prev {
+ float: left;
+ padding-left: 0;
+
+ .fa {
+ display: inline-block;
+ margin-right: 5px;
+ }
+ }
+
+ .next {
+ float: right;
+ padding-right: 0;
+
+ .fa {
+ display: inline-block;
+ margin-left: 5px;
+ }
+ }
+
+ .disabled {
+ cursor: default;
+ color: lighten($color1, 10%);
+ }
+
+ @media screen and (max-width: 360px) {
+ padding: 30px 20px;
+
+ a, .current, .next, .prev, .gap {
+ display: none;
+ }
+
+ .next, .prev {
+ display: inline-block;
+ }
+ }
+}
+
+.accounts-grid {
+ box-shadow: 0 0 15px rgba($color8, 0.2);
+ background: $color5;
+ border-radius: 0 0 4px 4px;
+ padding: 20px 10px;
+ padding-bottom: 10px;
+ overflow: hidden;
+ display: flex;
+ flex-wrap: wrap;
+
+ @media screen and (max-width: 700px) {
+ border-radius: 0;
+ box-shadow: none;
+ }
+
+ .account-grid-card {
+ box-sizing: border-box;
+ width: 335px;
+ border: 1px solid $color2;
+ border-radius: 4px;
+ color: $color1;
+ margin-bottom: 10px;
+
+ &:nth-child(odd) {
+ margin-right: 10px;
+ }
+
+ .account-grid-card__header {
+ overflow: hidden;
+ padding: 10px;
+ border-bottom: 1px solid $color2;
+ }
+
+ .avatar {
+ width: 60px;
+ height: 60px;
+ float: left;
+ margin-right: 15px;
+
+ img {
+ display: block;
+ width: 60px;
+ height: 60px;
+ border-radius: 60px;
+ }
+ }
+
+ .name {
+ padding-top: 10px;
+
+ a {
+ display: block;
+ color: $color1;
+ text-decoration: none;
+
+ &:hover {
+ .display_name {
+ text-decoration: underline;
+ }
+ }
+ }
+ }
+
+ .display_name {
+ font-size: 14px;
+ display: block;
+ }
+
+ .username {
+ color: $color4;
+ }
+
+ .note {
+ padding: 10px;
+ padding-top: 15px;
+ color: $color3;
+ word-wrap: break-word;
+ }
+ }
+}
+
+.nothing-here {
+ color: $color3;
+ font-size: 14px;
+ font-weight: 500;
+ text-align: center;
+ padding: 15px 0;
+ padding-bottom: 25px;
+ cursor: default;
+}
+
+.account-card {
+ padding: 14px 10px;
+ background: $color5;
+ border-radius: 4px;
+ text-align: left;
+ box-shadow: 0 0 15px rgba($color8, 0.2);
+
+ .detailed-status__display-name {
+ display: block;
+ overflow: hidden;
+ margin-bottom: 15px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ & > div {
+ float: left;
+ margin-right: 10px;
+ width: 48px;
+ height: 48px;
+ }
+
+ .avatar {
+ display: block;
+ border-radius: 4px;
+ }
+
+ .display-name {
+ display: block;
+ max-width: 100%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ cursor: default;
+
+ strong {
+ font-weight: 500;
+ color: $color1;
+ }
+
+ span {
+ font-size: 14px;
+ color: $color3;
+ }
+ }
+
+ &:hover {
+ .display-name {
+ strong {
+ text-decoration: none;
+ }
+ }
+ }
+ }
+
+ .account__header__content {
+ font-size: 14px;
+ color: $color1;
+ }
+}
diff --git a/app/javascript/styles/admin.scss b/app/javascript/styles/admin.scss
@@ -0,0 +1,245 @@
+.admin-wrapper {
+ display: flex;
+ justify-content: center;
+ height: 100%;
+
+ .sidebar-wrapper {
+ flex: 1;
+ height: 100%;
+ background: $color1;
+ display: flex;
+ justify-content: flex-end;
+ }
+
+ .sidebar {
+ width: 240px;
+ height: 100%;
+ padding: 0;
+ overflow-y: auto;
+
+ .logo {
+ display: block;
+ margin: 40px auto;
+ width: 100px;
+ height: 100px;
+ }
+
+ ul {
+ list-style: none;
+ border-radius: 4px 0 0 4px;
+ overflow: hidden;
+ margin-bottom: 20px;
+
+ a {
+ display: block;
+ padding: 15px 25px;
+ color: rgba($color5, 0.7);
+ text-decoration: none;
+ transition: all 200ms linear;
+ border-radius: 4px 0 0 4px;
+
+ i.fa {
+ margin-right: 5px;
+ }
+
+ &:hover {
+ color: $color5;
+ background-color: darken($color1, 5%);
+ transition: all 100ms linear;
+ }
+
+ &.selected {
+ background: darken($color1, 2%);
+ border-radius: 4px 0 0 0;
+ }
+ }
+
+ ul {
+ background: darken($color1, 4%);
+ border-radius: 0 0 0 4px;
+ margin: 0;
+
+ a {
+ border: 0;
+
+ &.selected {
+ color: $color5;
+ background-color: $color4;
+ border-bottom: 0;
+ border-radius: 0;
+
+ &:hover {
+ background-color: lighten($color4, 5%);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .content-wrapper {
+ flex: 2;
+ overflow: auto;
+ }
+
+ .content {
+ max-width: 700px;
+ padding: 20px 15px;
+ padding-top: 60px;
+ padding-left: 25px;
+
+ h2 {
+ color: $color2;
+ font-size: 24px;
+ line-height: 28px;
+ font-weight: 400;
+ margin-bottom: 40px;
+ }
+
+ & > p {
+ font-size: 14px;
+ line-height: 18px;
+ color: $color2;
+ margin-bottom: 20px;
+
+ strong {
+ color: $color5;
+ font-weight: 500;
+ }
+ }
+
+ hr {
+ margin: 20px 0;
+ border: 0;
+ background: transparent;
+ border-bottom: 1px solid $color1;
+ }
+ }
+
+ .simple_form {
+ max-width: 400px;
+ .label_input {
+ label.select {
+ width: 50%;
+ }
+ select {
+ width: 50%;
+ float: right;
+ }
+ }
+ }
+
+ @media screen and (max-width: 600px) {
+ display: block;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+
+ .sidebar-wrapper, .content-wrapper {
+ flex: 0 0 auto;
+ height: auto;
+ overflow: initial;
+ }
+
+ .sidebar {
+ width: 100%;
+ padding: 10px 0;
+ height: auto;
+
+ .logo {
+ margin: 20px auto;
+ }
+ }
+
+ .content {
+ padding-top: 20px;
+ }
+ }
+}
+
+.filters {
+ display: flex;
+ margin-bottom: 20px;
+
+ .filter-subset {
+ flex: 0 0 auto;
+ margin-right: 40px;
+
+ ul {
+ margin-top: 5px;
+ list-style: none;
+
+ li {
+ display: inline-block;
+ margin-right: 5px;
+ }
+ }
+
+ strong {
+ font-weight: 500;
+ text-transform: uppercase;
+ font-size: 12px;
+ }
+
+ a {
+ display: inline-block;
+ color: rgba($color5, 0.7);
+ text-decoration: none;
+ text-transform: uppercase;
+ font-size: 12px;
+ font-weight: 500;
+ border-bottom: 2px solid $color1;
+
+ &:hover {
+ color: $color5;
+ border-bottom: 2px solid lighten($color1, 5%);
+ }
+
+ &.selected {
+ color: $color4;
+ border-bottom: 2px solid $color4;
+ }
+ }
+ }
+}
+
+.report-accounts {
+ display: flex;
+ margin-bottom: 20px;
+}
+
+.report-accounts__item {
+ flex: 1 1 0;
+ display: flex;
+ flex-direction: column;
+
+ & > strong {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: 500;
+ font-size: 14px;
+ line-height: 18px;
+ color: $color2;
+ }
+
+ &:first-child {
+ margin-right: 10px;
+ }
+
+ .account-card {
+ flex: 1 1 auto;
+ }
+}
+
+.report-status {
+ display: flex;
+ margin-bottom: 10px;
+
+ .activity-stream {
+ flex: 2 0 0;
+ margin-right: 20px;
+ }
+}
+
+.report-status__actions {
+ flex: 0 0 auto;
+}
diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss
@@ -0,0 +1,20 @@
+@import 'variables';
+@import 'fonts/roboto';
+@import 'fonts/roboto-mono';
+@import 'fonts/montserrat';
+
+@import 'reset';
+@import 'basics';
+@import 'containers';
+@import 'lists';
+@import 'footer';
+@import 'compact_header';
+@import 'landing_strip';
+@import 'forms';
+@import 'accounts';
+@import 'stream_entries';
+@import 'components';
+@import 'about';
+@import 'tables';
+@import 'admin';
+@import 'rtl';
diff --git a/app/javascript/styles/basics.scss b/app/javascript/styles/basics.scss
@@ -0,0 +1,58 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background: $color1 url('../images/background-photo.jpg');
+ background-size: cover;
+ background-attachment: fixed;
+ font-size: 13px;
+ line-height: 18px;
+ font-weight: 400;
+ color: $color5;
+ padding-bottom: 140px;
+ text-rendering: optimizelegibility;
+ font-feature-settings: "kern";
+ text-size-adjust: none;
+
+ &.app-body {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ background: $color1;
+ }
+
+ &.embed {
+ background: transparent;
+ margin: 0;
+
+ .container {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ }
+ }
+
+ &.admin {
+ background: darken($color1, 4%);
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ }
+
+ @media screen and (max-width: 360px) {
+ padding-bottom: 0;
+ }
+}
+
+button:focus {
+ outline: none;
+}
+
+.app-holder {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ align-items: center;
+ justify-content: center;
+}
diff --git a/app/javascript/styles/boost.scss b/app/javascript/styles/boost.scss
@@ -0,0 +1,11 @@
+@function url-friendly-colour($colour) {
+ @return '%23' + str-slice('#{$colour}', 2, -1)
+}
+
+button.icon-button i.fa-retweet {
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='209'><path d='M4.97 3.16c-.1.03-.17.1-.22.18L.8 8.24c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77L5.5 3.35c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.02-2.4.02H7.1l2.32 2.85.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour(lighten($color1, 26%))}' stroke-width='0'/><path d='M7.78 19.66c-.24.02-.44.25-.44.5v2.46h-.06c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v4.47c0 4.26-.56 3.62 3.65 3.62H8.5l-1.3-1.06c-.1-.08-.18-.2-.2-.3-.02-.17.06-.35.2-.45l1.33-1.1H7.28c-.44 0-.72-.3-.72-.7v-4.48c0-.44.28-.72.72-.72h.06v2.5c0 .38.54.63.82.38l4.9-3.93c.25-.18.25-.6 0-.78l-4.9-3.92c-.1-.1-.24-.14-.38-.12zm9.34 2.93c-.54-.02-1.3.02-2.4.02h-1.25l1.3 1.07c.1.07.18.2.2.33.02.16-.06.3-.2.4l-1.33 1.1h1.28c.42 0 .72.28.72.72v4.47c0 .42-.3.72-.72.72h-.1v-2.47c0-.3-.3-.53-.6-.47-.07 0-.14.05-.2.1l-4.9 3.93c-.26.18-.26.6 0 .78l4.9 3.92c.27.25.82 0 .8-.38v-2.5h.1c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.15.4-3.62-1.25-3.66zM10.34 38.66c-.24.02-.44.25-.43.5v2.47H7.3c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.47c0 3.66-.23 3.7 2.34 3.66l-1.34-1.1c-.1-.08-.18-.2-.2-.3 0-.17.07-.35.2-.45l1.96-1.6c-.03-.06-.04-.13-.04-.2v-4.48c0-.44.28-.72.72-.72H9.9v2.5c0 .36.5.6.8.38l4.93-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.08-.23-.13-.36-.12zm5.63 2.93l1.34 1.1c.1.07.18.2.2.33.02.16-.03.3-.16.4l-1.96 1.6c.02.07.06.13.06.22v4.47c0 .42-.3.72-.72.72h-2.66v-2.47c0-.3-.3-.53-.6-.47-.06.02-.12.05-.18.1l-4.94 3.93c-.24.18-.24.6 0 .78l4.94 3.92c.28.22.78-.02.78-.38v-2.5h2.66c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.66.34-3.7-2.4-3.66zM13.06 57.66c-.23.03-.4.26-.4.5v2.47H7.28c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.87l2.93-2.37v-2.5c0-.44.28-.72.72-.72h5.38v2.5c0 .36.5.6.78.38l4.94-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.1-.24-.14-.38-.12zm5.3 6.15l-2.92 2.4v2.52c0 .42-.3.72-.72.72h-5.4v-2.47c0-.3-.32-.53-.6-.47-.07.02-.13.05-.2.1L3.6 70.52c-.25.18-.25.6 0 .78l4.93 3.92c.28.22.78-.02.78-.38v-2.5h5.42c4.27 0 3.65.67 3.65-3.62v-4.47-.44zM19.25 78.8c-.1.03-.2.1-.28.17l-.9.9c-.44-.3-1.36-.25-3.35-.25H7.28c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v.7l2.93.3v-1c0-.44.28-.72.72-.72h7.44c.2 0 .37.08.5.2l-1.8 1.8c-.25.26-.08.76.27.8l6.27.7c.28.03.56-.25.53-.53l-.7-6.25c0-.27-.3-.48-.55-.44zm-17.2 6.1c-.2.07-.36.3-.33.54l.7 6.25c.02.36.58.55.83.27l.8-.8c.02 0 .04-.02.04 0 .46.24 1.37.17 3.18.17h7.44c4.27 0 3.65.67 3.65-3.62v-.75l-2.93-.3v1.05c0 .42-.3.72-.72.72H7.28c-.15 0-.3-.03-.4-.1L8.8 86.4c.3-.24.1-.8-.27-.84l-6.28-.65h-.2zM4.88 98.6c-1.33 0-1.34.48-1.3 2.3l1.14-1.37c.08-.1.22-.17.34-.2.16 0 .34.08.44.2l1.66 2.03c.04 0 .07-.03.12-.03h7.44c.34 0 .57.2.65.5h-2.43c-.34.05-.53.52-.3.78l3.92 4.95c.18.24.6.24.78 0l3.94-4.94c.22-.27-.02-.76-.37-.77H18.4c.02-3.9.6-3.4-3.66-3.4H7.28c-1.08 0-1.86-.04-2.4-.04zm.15 2.46c-.1.03-.2.1-.28.2l-3.94 4.9c-.2.28.03.77.4.78H3.6c-.02 3.94-.45 3.4 3.66 3.4h7.44c3.65 0 3.74.3 3.7-2.25l-1.1 1.34c-.1.1-.2.17-.32.2-.16 0-.34-.08-.44-.2l-1.65-2.03c-.06.02-.1.04-.18.04H7.28c-.35 0-.57-.2-.66-.5h2.44c.37 0 .63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.23-.47-.2zM4.88 117.6c-1.16 0-1.3.3-1.3 1.56l1.14-1.38c.08-.1.22-.14.34-.16.16 0 .34.04.44.16l2.22 2.75h7c.42 0 .72.28.72.72v.53h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-.53c0-4.2.72-3.63-3.66-3.63H7.28c-1.08 0-1.86-.03-2.4-.03zm.1 1.74c-.1.03-.17.1-.23.16L.8 124.44c-.2.28.03.77.4.78H3.6v.5c0 4.26-.55 3.62 3.66 3.62h7.44c1.03 0 1.74.02 2.28 0-.16.02-.34-.03-.44-.15l-2.22-2.76H7.28c-.44 0-.72-.3-.72-.72v-.5h2.5c.37.02.63-.5.4-.78L5.5 119.5c-.12-.15-.34-.22-.53-.16zm12.02 10c1.2-.02 1.4-.25 1.4-1.53l-1.1 1.36c-.07.1-.17.17-.3.18zM5.94 136.6l2.37 2.93h6.42c.42 0 .72.28.72.72v1.25h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.25c0-4.2.72-3.63-3.66-3.63H7.28c-.6 0-.92-.02-1.34-.03zm-1.72.06c-.4.08-.54.3-.6.75l.6-.74zm.84.93c-.12 0-.24.08-.3.18l-3.95 4.9c-.24.3 0 .83.4.82H3.6v1.22c0 4.26-.55 3.62 3.66 3.62h7.44c.63 0 .97.02 1.4.03l-2.37-2.93H7.28c-.44 0-.72-.3-.72-.72v-1.22h2.5c.4.04.67-.53.4-.8l-3.96-4.92c-.1-.13-.27-.2-.44-.2zm13.28 10.03l-.56.7c.36-.07.5-.3.56-.7zM17.13 155.6c-.55-.02-1.32.03-2.4.03h-8.2l2.38 2.9h5.82c.42 0 .72.28.72.72v1.97H12.9c-.32.06-.48.52-.28.78l3.94 4.94c.2.23.6.22.78-.03l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.97c0-3.15.4-3.62-1.25-3.66zm-12.1.28c-.1.02-.2.1-.28.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v1.96c0 4.26-.55 3.62 3.66 3.62h8.24l-2.36-2.9H7.28c-.44 0-.72-.3-.72-.72v-1.97h2.5c.37.02.63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.22-.47-.2zM5.13 174.5c-.15 0-.3.07-.38.2L.8 179.6c-.24.27 0 .82.4.8H3.6v2.32c0 4.26-.55 3.62 3.66 3.62h7.94l-2.35-2.9h-5.6c-.43 0-.7-.3-.7-.72v-2.3h2.5c.38.03.66-.54.4-.83l-3.97-4.9c-.1-.13-.23-.2-.38-.2zm12 .1c-.55-.02-1.32.03-2.4.03H6.83l2.35 2.9h5.52c.42 0 .72.28.72.72v2.34h-2.6c-.3.1-.43.53-.2.78l3.92 4.9c.18.24.6.24.78 0l3.94-4.9c.22-.3-.02-.78-.37-.8H18.4v-2.33c0-3.15.4-3.62-1.25-3.66zM4.97 193.16c-.1.03-.17.1-.22.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77l-3.96-4.9c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.03-2.4.03H7.1l2.32 2.84.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour($color4)}' stroke-width='0'/></svg>");
+
+ &:hover {
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='209'><path d='M4.97 3.16c-.1.03-.17.1-.22.18L.8 8.24c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77L5.5 3.35c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.02-2.4.02H7.1l2.32 2.85.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour(lighten($color1, 33%))}' stroke-width='0'/><path d='M7.78 19.66c-.24.02-.44.25-.44.5v2.46h-.06c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v4.47c0 4.26-.56 3.62 3.65 3.62H8.5l-1.3-1.06c-.1-.08-.18-.2-.2-.3-.02-.17.06-.35.2-.45l1.33-1.1H7.28c-.44 0-.72-.3-.72-.7v-4.48c0-.44.28-.72.72-.72h.06v2.5c0 .38.54.63.82.38l4.9-3.93c.25-.18.25-.6 0-.78l-4.9-3.92c-.1-.1-.24-.14-.38-.12zm9.34 2.93c-.54-.02-1.3.02-2.4.02h-1.25l1.3 1.07c.1.07.18.2.2.33.02.16-.06.3-.2.4l-1.33 1.1h1.28c.42 0 .72.28.72.72v4.47c0 .42-.3.72-.72.72h-.1v-2.47c0-.3-.3-.53-.6-.47-.07 0-.14.05-.2.1l-4.9 3.93c-.26.18-.26.6 0 .78l4.9 3.92c.27.25.82 0 .8-.38v-2.5h.1c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.15.4-3.62-1.25-3.66zM10.34 38.66c-.24.02-.44.25-.43.5v2.47H7.3c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.47c0 3.66-.23 3.7 2.34 3.66l-1.34-1.1c-.1-.08-.18-.2-.2-.3 0-.17.07-.35.2-.45l1.96-1.6c-.03-.06-.04-.13-.04-.2v-4.48c0-.44.28-.72.72-.72H9.9v2.5c0 .36.5.6.8.38l4.93-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.08-.23-.13-.36-.12zm5.63 2.93l1.34 1.1c.1.07.18.2.2.33.02.16-.03.3-.16.4l-1.96 1.6c.02.07.06.13.06.22v4.47c0 .42-.3.72-.72.72h-2.66v-2.47c0-.3-.3-.53-.6-.47-.06.02-.12.05-.18.1l-4.94 3.93c-.24.18-.24.6 0 .78l4.94 3.92c.28.22.78-.02.78-.38v-2.5h2.66c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.66.34-3.7-2.4-3.66zM13.06 57.66c-.23.03-.4.26-.4.5v2.47H7.28c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.87l2.93-2.37v-2.5c0-.44.28-.72.72-.72h5.38v2.5c0 .36.5.6.78.38l4.94-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.1-.24-.14-.38-.12zm5.3 6.15l-2.92 2.4v2.52c0 .42-.3.72-.72.72h-5.4v-2.47c0-.3-.32-.53-.6-.47-.07.02-.13.05-.2.1L3.6 70.52c-.25.18-.25.6 0 .78l4.93 3.92c.28.22.78-.02.78-.38v-2.5h5.42c4.27 0 3.65.67 3.65-3.62v-4.47-.44zM19.25 78.8c-.1.03-.2.1-.28.17l-.9.9c-.44-.3-1.36-.25-3.35-.25H7.28c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v.7l2.93.3v-1c0-.44.28-.72.72-.72h7.44c.2 0 .37.08.5.2l-1.8 1.8c-.25.26-.08.76.27.8l6.27.7c.28.03.56-.25.53-.53l-.7-6.25c0-.27-.3-.48-.55-.44zm-17.2 6.1c-.2.07-.36.3-.33.54l.7 6.25c.02.36.58.55.83.27l.8-.8c.02 0 .04-.02.04 0 .46.24 1.37.17 3.18.17h7.44c4.27 0 3.65.67 3.65-3.62v-.75l-2.93-.3v1.05c0 .42-.3.72-.72.72H7.28c-.15 0-.3-.03-.4-.1L8.8 86.4c.3-.24.1-.8-.27-.84l-6.28-.65h-.2zM4.88 98.6c-1.33 0-1.34.48-1.3 2.3l1.14-1.37c.08-.1.22-.17.34-.2.16 0 .34.08.44.2l1.66 2.03c.04 0 .07-.03.12-.03h7.44c.34 0 .57.2.65.5h-2.43c-.34.05-.53.52-.3.78l3.92 4.95c.18.24.6.24.78 0l3.94-4.94c.22-.27-.02-.76-.37-.77H18.4c.02-3.9.6-3.4-3.66-3.4H7.28c-1.08 0-1.86-.04-2.4-.04zm.15 2.46c-.1.03-.2.1-.28.2l-3.94 4.9c-.2.28.03.77.4.78H3.6c-.02 3.94-.45 3.4 3.66 3.4h7.44c3.65 0 3.74.3 3.7-2.25l-1.1 1.34c-.1.1-.2.17-.32.2-.16 0-.34-.08-.44-.2l-1.65-2.03c-.06.02-.1.04-.18.04H7.28c-.35 0-.57-.2-.66-.5h2.44c.37 0 .63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.23-.47-.2zM4.88 117.6c-1.16 0-1.3.3-1.3 1.56l1.14-1.38c.08-.1.22-.14.34-.16.16 0 .34.04.44.16l2.22 2.75h7c.42 0 .72.28.72.72v.53h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-.53c0-4.2.72-3.63-3.66-3.63H7.28c-1.08 0-1.86-.03-2.4-.03zm.1 1.74c-.1.03-.17.1-.23.16L.8 124.44c-.2.28.03.77.4.78H3.6v.5c0 4.26-.55 3.62 3.66 3.62h7.44c1.03 0 1.74.02 2.28 0-.16.02-.34-.03-.44-.15l-2.22-2.76H7.28c-.44 0-.72-.3-.72-.72v-.5h2.5c.37.02.63-.5.4-.78L5.5 119.5c-.12-.15-.34-.22-.53-.16zm12.02 10c1.2-.02 1.4-.25 1.4-1.53l-1.1 1.36c-.07.1-.17.17-.3.18zM5.94 136.6l2.37 2.93h6.42c.42 0 .72.28.72.72v1.25h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.25c0-4.2.72-3.63-3.66-3.63H7.28c-.6 0-.92-.02-1.34-.03zm-1.72.06c-.4.08-.54.3-.6.75l.6-.74zm.84.93c-.12 0-.24.08-.3.18l-3.95 4.9c-.24.3 0 .83.4.82H3.6v1.22c0 4.26-.55 3.62 3.66 3.62h7.44c.63 0 .97.02 1.4.03l-2.37-2.93H7.28c-.44 0-.72-.3-.72-.72v-1.22h2.5c.4.04.67-.53.4-.8l-3.96-4.92c-.1-.13-.27-.2-.44-.2zm13.28 10.03l-.56.7c.36-.07.5-.3.56-.7zM17.13 155.6c-.55-.02-1.32.03-2.4.03h-8.2l2.38 2.9h5.82c.42 0 .72.28.72.72v1.97H12.9c-.32.06-.48.52-.28.78l3.94 4.94c.2.23.6.22.78-.03l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.97c0-3.15.4-3.62-1.25-3.66zm-12.1.28c-.1.02-.2.1-.28.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v1.96c0 4.26-.55 3.62 3.66 3.62h8.24l-2.36-2.9H7.28c-.44 0-.72-.3-.72-.72v-1.97h2.5c.37.02.63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.22-.47-.2zM5.13 174.5c-.15 0-.3.07-.38.2L.8 179.6c-.24.27 0 .82.4.8H3.6v2.32c0 4.26-.55 3.62 3.66 3.62h7.94l-2.35-2.9h-5.6c-.43 0-.7-.3-.7-.72v-2.3h2.5c.38.03.66-.54.4-.83l-3.97-4.9c-.1-.13-.23-.2-.38-.2zm12 .1c-.55-.02-1.32.03-2.4.03H6.83l2.35 2.9h5.52c.42 0 .72.28.72.72v2.34h-2.6c-.3.1-.43.53-.2.78l3.92 4.9c.18.24.6.24.78 0l3.94-4.9c.22-.3-.02-.78-.37-.8H18.4v-2.33c0-3.15.4-3.62-1.25-3.66zM4.97 193.16c-.1.03-.17.1-.22.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77l-3.96-4.9c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.03-2.4.03H7.1l2.32 2.84.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour($color4)}' stroke-width='0'/></svg>");
+ }
+}
diff --git a/app/javascript/styles/compact_header.scss b/app/javascript/styles/compact_header.scss
@@ -0,0 +1,28 @@
+.compact-header {
+ h1 {
+ font-size: 24px;
+ line-height: 28px;
+ color: $color3;
+ overflow: hidden;
+ font-weight: 500;
+ margin-bottom: 20px;
+
+ a {
+ color: inherit;
+ text-decoration: none;
+ }
+
+ small {
+ font-weight: 400;
+ color: $color2;
+ }
+
+ img {
+ display: inline-block;
+ margin-bottom: -5px;
+ margin-right: 15px;
+ width: 36px;
+ height: 36px;
+ }
+ }
+}
diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss
@@ -0,0 +1,3189 @@
+@import 'variables';
+
+.app-body {
+ -webkit-overflow-scrolling: touch;
+ -ms-overflow-style: -ms-autohiding-scrollbar;
+}
+
+.button {
+ background-color: darken($color4, 3%);
+ border: 10px none;
+ border-radius: 4px;
+ box-sizing: border-box;
+ color: $color5;
+ cursor: pointer;
+ display: inline-block;
+ font-family: inherit;
+ font-size: 14px;
+ font-weight: 500;
+ height: 36px;
+ letter-spacing: 0;
+ line-height: 36px;
+ overflow: hidden;
+ padding: 0 16px;
+ position: relative;
+ text-align: center;
+ text-transform: uppercase;
+ text-decoration: none;
+ text-overflow: ellipsis;
+ transition: all 100ms ease-in;
+ white-space: nowrap;
+
+ &:active,
+ &:focus,
+ &:hover {
+ background-color: lighten($color4, 7%);
+ transition: all 200ms ease-out;
+ }
+
+ &:disabled {
+ background-color: $color3;
+ cursor: default;
+ }
+
+ &.button-secondary {
+ //
+ }
+}
+
+.column-collapsable {
+ position: relative;
+}
+
+.column-icon {
+ background: lighten($color1, 4%);
+ color: $color3;
+ cursor: pointer;
+ font-size: 16px;
+ padding: 15px;
+ position: absolute;
+ right: 0;
+ top: -48px;
+ z-index: 3;
+
+ &:hover {
+ color: lighten($color3, 7%);
+ }
+}
+
+.column-icon-clear {
+ font-size: 16px;
+ padding: 15px;
+ position: absolute;
+ right: 48px;
+ top: 0;
+ cursor: pointer;
+ z-index: 2;
+}
+
+@media screen and (min-width: 1025px) {
+ .column-icon-clear {
+ top: 10px;
+ }
+}
+
+.icon-button {
+ display: inline-block;
+ padding: 0;
+ color: lighten($color1, 26%);
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ transition: color 100ms ease-in;
+
+ &:hover, &:active, &:focus {
+ color: lighten($color1, 33%);
+ transition: color 200ms ease-out;
+ }
+
+ &.disabled {
+ color: lighten($color1, 13%);
+ cursor: default;
+ }
+
+ &.active {
+ color: $color4;
+ }
+
+ &::-moz-focus-inner {
+ border: 0;
+ }
+
+ &::-moz-focus-inner, &:focus, &:active {
+ outline: 0 !important;
+ }
+
+ &.inverted {
+ color: lighten($color1, 33%);
+
+ &:hover, &:active, &:focus {
+ color: lighten($color1, 26%);
+ }
+
+ &.active {
+ color: $color4;
+ }
+
+ &.disabled {
+ color: $color3;
+ }
+ }
+
+ &.overlayed {
+ box-sizing: content-box;
+ background: rgba($color8, 0.6);
+ color: rgba($color5, 0.7);
+ border-radius: 4px;
+ padding: 2px;
+
+ &:hover {
+ background: rgba($color8, 0.9);
+ }
+ }
+}
+
+.text-icon-button {
+ color: lighten($color1, 33%);
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ font-weight: 600;
+ font-size: 11px;
+ padding: 0 3px;
+ line-height: 27px;
+ outline: 0;
+ transition: color 100ms ease-in;
+
+ &:hover, &:active, &:focus {
+ color: lighten($color1, 26%);
+ transition: color 200ms ease-out;
+ }
+
+ &.disabled {
+ color: lighten($color1, 13%);
+ cursor: default;
+ }
+
+ &.active {
+ color: $color4;
+ }
+
+ &::-moz-focus-inner {
+ border: 0;
+ }
+
+ &::-moz-focus-inner, &:focus, &:active {
+ outline: 0 !important;
+ }
+}
+
+.dropdown--active .icon-button {
+ color: $color4;
+}
+
+.dropdown--active:after {
+ content: "";
+ display: block;
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 0 4.5px 7.8px 4.5px;
+ border-color: transparent transparent $color2 transparent;
+ bottom: 8px;
+ right: 104px;
+}
+
+.invisible {
+ font-size: 0;
+ line-height: 0;
+ display: inline-block;
+ width: 0;
+}
+
+.ellipsis {
+ &:after {
+ content: "…";
+ }
+}
+
+.lightbox .icon-button {
+ color: $color1;
+}
+
+.compose-form {
+ padding: 10px;
+}
+
+.compose-form__warning {
+ color: darken($color3, 33%);
+ margin-bottom: 15px;
+ background: $color3;
+ box-shadow: 0 2px 6px rgba($color8, 0.3);
+ padding: 8px 10px;
+ border-radius: 4px;
+ font-size: 13px;
+ font-weight: 400;
+
+ strong {
+ color: darken($color3, 33%);
+ font-weight: 500;
+ }
+
+ a {
+ color: darken($color3, 33%);
+ font-weight: 500;
+ text-decoration: underline;
+
+ &:hover, &:active, &:focus {
+ text-decoration: none;
+ }
+ }
+}
+
+.compose-form__modifiers {
+ color: $color1;
+ font-family: inherit;
+ font-size: 14px;
+ background: $color5;
+ border-radius: 0 0 4px 0;
+}
+
+.compose-form__buttons-wrapper {
+ display: flex;
+ justify-content: space-between;
+}
+
+.compose-form__buttons {
+ padding: 10px;
+ background: darken($color5, 8%);
+ box-shadow: inset 0 5px 5px rgba($color8, 0.05);
+ border-radius: 0 0 4px 4px;
+ display: flex;
+
+ .icon-button {
+ box-sizing: content-box;
+ padding: 0 3px;
+ }
+}
+
+.compose-form__upload-button-icon {
+ line-height: 27px;
+}
+
+.compose-form__upload-wrapper {
+ overflow: hidden;
+}
+
+.compose-form__uploads-wrapper {
+ display: flex;
+ padding: 5px;
+}
+
+.compose-form__upload {
+ flex: 1 1 0;
+ margin: 5px;
+}
+
+.compose-form__upload-thumbnail {
+ border-radius: 4px;
+ background-position: center;
+ background-size: cover;
+ background-repeat: no-repeat;
+ height: 100px;
+ width: 100%;
+}
+
+.compose-form__upload-cancel {
+ background-size: cover;
+ border-radius: 4px;
+ height: 100px;
+ width: 100px;
+}
+
+.compose-form__label {
+ display: block;
+ line-height: 24px;
+ vertical-align: middle;
+
+ &.with-border {
+ border-top: 1px solid $color1;
+ padding-top: 10px;
+ }
+
+ .compose-form__label__text {
+ display: inline-block;
+ vertical-align: middle;
+ margin-bottom: 14px;
+ margin-left: 8px;
+ color: $color3;
+ }
+}
+
+.compose-form__textarea, .follow-form__input {
+ background: $color5;
+
+ &:disabled {
+ background: $color2;
+ }
+}
+
+.compose-form__autosuggest-wrapper {
+ position: relative;
+
+ .dropdown--active:after {
+ border-color: transparent transparent $color5 transparent;
+ bottom: -1px;
+ right: 8px;
+ }
+}
+
+.compose-form__publish {
+ display: flex;
+ min-width: 0;
+}
+
+.compose-form__publish-button-wrapper {
+ overflow: hidden;
+ padding-top: 10px;
+}
+
+.emojione {
+ display: inline-block;
+ font-size: inherit;
+ vertical-align: middle;
+ margin: -.2ex .15em .2ex;
+ width: 16px;
+ height: 16px;
+
+ img {
+ width: auto;
+ }
+}
+
+.reply-indicator {
+ border-radius: 4px 4px 0 0;
+ position: relative;
+ bottom: -2px;
+ background: $color3;
+ padding: 10px;
+}
+
+.reply-indicator__header {
+ margin-bottom: 5px;
+ overflow: hidden;
+}
+
+.reply-indicator__cancel {
+ float: right;
+ line-height: 24px;
+}
+
+.reply-indicator__display-name {
+ color: $color1;
+ display: block;
+ max-width: 100%;
+ line-height: 24px;
+ overflow: hidden;
+ padding-right: 25px;
+ text-decoration: none;
+}
+
+.reply-indicator__display-avatar {
+ float: left;
+ margin-right: 5px;
+}
+
+.status__content {
+ cursor: pointer;
+}
+
+.status__content--no-action {
+ cursor: default;
+}
+
+.status__content,
+.reply-indicator__content {
+ font-size: 15px;
+ line-height: 20px;
+ word-wrap: break-word;
+ font-weight: 400;
+ overflow: hidden;
+ white-space: pre-wrap;
+
+ .emojione {
+ width: 18px;
+ height: 18px;
+ }
+
+ p {
+ margin-bottom: 20px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ a {
+ color: $color2;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+
+ .fa {
+ color: lighten($color1, 40%);
+ }
+ }
+
+ &.mention {
+ &:hover {
+ text-decoration: none;
+
+ span {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .fa {
+ color: lighten($color1, 30%);
+ }
+ }
+
+ .status__content__spoiler-link {
+ background: lighten($color1, 30%);
+
+ &:hover {
+ background: lighten($color1, 33%);
+ text-decoration: none;
+ }
+ }
+}
+
+a.status__content__spoiler-link {
+ display: inline-block;
+ border-radius: 2px;
+ color: lighten($color1, 8%);
+ font-weight: 500;
+ font-size: 11px;
+ padding: 0px 6px;
+ text-transform: uppercase;
+ line-height: inherit;
+}
+
+.status__prepend-icon-wrapper {
+ left: -26px;
+ position: absolute;
+}
+
+.status {
+ padding: 8px 10px;
+ padding-left: 68px;
+ position: relative;
+ min-height: 48px;
+ border-bottom: 1px solid lighten($color1, 8%);
+ cursor: default;
+
+ &.light {
+ .status__relative-time {
+ color: $color3;
+ }
+
+ .status__display-name {
+ color: $color1;
+ }
+
+ .display-name {
+ strong {
+ color: $color1;
+ }
+
+ span {
+ color: $color3;
+ }
+ }
+
+ .status__content {
+ color: $color1;
+
+ a {
+ color: $color4;
+ }
+
+ a.status__content__spoiler-link {
+ color: $color5;
+ background: $color3;
+
+ &:hover {
+ background: lighten($color3, 8%);
+ }
+ }
+ }
+ }
+}
+
+.status__relative-time {
+ color: lighten($color1, 26%);
+}
+
+.status__display-name {
+ color: lighten($color1, 26%);
+}
+
+.status__info .status__display-name {
+ display: block;
+ max-width: 100%;
+ padding-right: 25px;
+}
+
+.status__info {
+ font-size: 15px;
+}
+
+.status__info-time {
+ float: right;
+ font-size: 14px;
+}
+
+.status-check-box {
+ border-bottom: 1px solid lighten($color1, 8%);
+ display: flex;
+
+ .status__content {
+ background: lighten($color1, 4%);
+ flex: 1 1 auto;
+ padding: 10px;
+ }
+}
+
+.status-check-box-toggle {
+ align-items: center;
+ display: flex;
+ flex: 0 0 auto;
+ justify-content: center;
+ padding: 10px;
+}
+
+.status__prepend {
+ margin-left: 68px;
+ color: lighten($color1, 26%);
+ padding: 8px 0;
+ padding-bottom: 2px;
+ font-size: 14px;
+ position: relative;
+
+ .status__display-name strong {
+ color: lighten($color1, 26%);
+ }
+}
+
+.status__action-bar {
+ align-items: center;
+ display: flex;
+ margin-top: 10px;
+}
+
+.status__action-bar-button-wrapper {
+ float: left;
+ margin-right: 18px;
+}
+
+.status__action-bar-dropdown {
+ float: left;
+ height: 18px;
+ width: 18px;
+}
+
+.detailed-status {
+ background: lighten($color1, 4%);
+ padding: 14px 10px;
+
+ .status__content {
+ font-size: 19px;
+ line-height: 24px;
+
+ .emojione {
+ width: 22px;
+ height: 22px;
+ }
+ }
+}
+
+.detailed-status__meta {
+ margin-top: 15px;
+ color: lighten($color1, 26%);
+ font-size: 14px;
+ line-height: 18px;
+}
+
+.detailed-status__action-bar {
+ background: lighten($color1, 4%);
+ border-top: 1px solid lighten($color1, 8%);
+ border-bottom: 1px solid lighten($color1, 8%);
+ display: flex;
+ flex-direction: row;
+ padding: 10px 0;
+}
+
+.detailed-status__link {
+ color: inherit;
+ text-decoration: none;
+}
+
+.detailed-status__favorites,
+.detailed-status__reblogs {
+ display: inline-block;
+ font-weight: 500;
+ font-size: 12px;
+ margin-left: 6px;
+}
+
+.reply-indicator__content {
+ color: $color1;
+ font-size: 14px;
+
+ a {
+ color: lighten($color1, 20%);
+ }
+}
+
+.account {
+ padding: 10px;
+ border-bottom: 1px solid lighten($color1, 8%);
+
+ .account__display-name {
+ flex: 1 1 auto;
+ display: block;
+ color: $color3;
+ overflow: hidden;
+ text-decoration: none;
+ font-size: 14px;
+ }
+}
+
+.account__wrapper {
+ display: flex;
+}
+
+.account__avatar-wrapper {
+ float: left;
+ margin-left: 12px;
+ margin-right: 12px;
+}
+
+.account__avatar {
+ border-radius: 4px;
+ background: transparent no-repeat;
+ background-position: 50%;
+ background-clip: padding-box;
+ position: relative;
+}
+
+.account__relationship {
+ height: 18px;
+ padding: 10px;
+}
+
+.account__header {
+ flex: 0 0 auto;
+ background: lighten($color1, 4%);
+ text-align: center;
+ background-size: cover;
+ background-position: center;
+ position: relative;
+
+ & > div {
+ background: rgba(lighten($color1, 4%), 0.9);
+ }
+
+ .account__header__content {
+ color: $color2;
+ }
+
+ .account__header__display-name {
+ color: $color5;
+ }
+
+ .account__header__username {
+ color: $color4;
+ }
+}
+
+.account__header__content {
+ color: $color3;
+ font-size: 14px;
+ font-weight: 400;
+ overflow: hidden;
+ word-break: normal;
+ word-wrap: break-word;
+
+ p {
+ margin-bottom: 20px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ a {
+ color: inherit;
+ text-decoration: underline;
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+}
+
+.account__header__display-name {
+ .emojione {
+ width: 25px;
+ height: 25px;
+ }
+}
+
+.account__action-bar {
+ border-top: 1px solid lighten($color1, 8%);
+ border-bottom: 1px solid lighten($color1, 8%);
+ line-height: 36px;
+ overflow: hidden;
+ flex: 0 0 auto;
+ display: flex;
+}
+
+.account__action-bar-dropdown {
+ flex: 1 1 auto;
+ padding: 10px;
+
+ .dropdown--active {
+ .dropdown__content.dropdown__right {
+ left: 6px;
+ right: initial;
+ }
+
+ &:after {
+ bottom: initial;
+ margin-left: 11px;
+ margin-top: -7px;
+ right: initial;
+ }
+ }
+}
+
+.account__action-bar-links {
+ display: flex;
+ flex: 1 1 auto;
+ line-height: 18px;
+}
+
+.account__action-bar__tab {
+ text-decoration: none;
+ overflow: hidden;
+ width: 80px;
+ border-left: 1px solid lighten($color1, 8%);
+ padding: 10px 5px;
+
+ & > span {
+ display: block;
+ text-transform: uppercase;
+ font-size: 11px;
+ color: $color3;
+ }
+
+ strong {
+ display: block;
+ font-size: 15px;
+ font-weight: 500;
+ color: $color5;
+ }
+
+ abbr {
+ color: lighten($color1, 26%);
+ }
+}
+
+.account__header__avatar {
+ background-size: 90px 90px;
+ display: block;
+ height: 90px;
+ margin: 0 auto 10px;
+ overflow: hidden;
+ width: 90px;
+}
+
+.account-authorize {
+ padding: 14px 10px;
+
+ .detailed-status__display-name {
+ display: block;
+ margin-bottom: 15px;
+ overflow: hidden;
+ }
+}
+
+.account-authorize__avatar {
+ float: left;
+ margin-right: 10px;
+}
+
+.status__display-name,
+.status__relative-time,
+.detailed-status__display-name,
+.detailed-status__datetime,
+.detailed-status__application,
+.account__display-name {
+ text-decoration: none;
+}
+
+.status__display-name,
+.account__display-name {
+ strong {
+ color: $color5;
+ }
+
+ &.muted {
+ .emojione {
+ opacity: 0.5;
+ }
+ }
+}
+
+.status__display-name,
+.reply-indicator__display-name,
+.detailed-status__display-name,
+.account__display-name {
+ &:hover strong {
+ text-decoration: underline;
+ }
+}
+
+.account__display-name strong {
+ display: block;
+}
+
+.detailed-status__application,
+.detailed-status__datetime {
+ color: inherit;
+}
+
+.detailed-status__display-name {
+ color: $color2;
+ display: block;
+ line-height: 24px;
+ margin-bottom: 15px;
+ overflow: hidden;
+
+ strong,
+ span {
+ display: block;
+ }
+
+ strong {
+ font-size: 16px;
+ color: $color5;
+ }
+}
+
+.detailed-status__display-avatar {
+ float: left;
+ margin-right: 10px;
+}
+
+.status__avatar {
+ height: 48px;
+ left: 10px;
+ position: absolute;
+ top: 10px;
+ width: 48px;
+}
+
+.muted {
+ .status__content p,
+ .status__content a {
+ color: lighten($color1, 26%);
+ }
+
+ .status__display-name strong {
+ color: lighten($color1, 26%);
+ }
+
+ .status__avatar {
+ opacity: 0.5;
+ }
+
+ a.status__content__spoiler-link {
+ background: lighten($color1, 26%);
+ color: lighten($color1, 4%);
+
+ &:hover {
+ background: lighten($color1, 29%);
+ text-decoration: none;
+ }
+ }
+}
+
+.notification__message {
+ margin-left: 68px;
+ padding: 8px 0;
+ padding-bottom: 0;
+ cursor: default;
+ color: $color3;
+ font-size: 15px;
+ position: relative;
+
+ .fa {
+ color: $color4;
+ }
+}
+
+.notification__favourite-icon-wrapper {
+ left: -26px;
+ position: absolute;
+
+ .star-icon {
+ color: #ca8f04;
+ }
+}
+
+.star-icon.active {
+ color: #ca8f04;
+}
+
+.notification__display-name {
+ color: inherit;
+ font-weight: 500;
+ text-decoration: none;
+
+ &:hover {
+ color: $color5;
+ text-decoration: underline;
+ }
+}
+
+.display-name {
+ display: block;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.display-name__html {
+ font-weight: 500;
+}
+
+.display-name__account {
+ font-size: 14px;
+}
+
+.status__relative-time,
+.detailed-status__datetime {
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+.transparent-background, .imageloader {
+ background: url('../images/void.png');
+}
+
+.imageloader {
+ display: block;
+}
+
+.navigation-bar {
+ padding: 10px;
+ display: flex;
+ flex-shrink: 0;
+ cursor: default;
+ color: $color3;
+
+ strong {
+ color: $color5;
+ }
+
+ .permalink {
+ text-decoration: none;
+ }
+}
+
+.navigation-bar__profile {
+ flex: 1 1 auto;
+ margin-left: 8px;
+}
+
+.navigation-bar__profile-account {
+ display: block;
+ font-weight: 500;
+}
+
+.navigation-bar__profile-edit {
+ color: inherit;
+ text-decoration: none;
+}
+
+.dropdown {
+ display: inline-block;
+}
+
+.dropdown__content {
+ display: none;
+ position: absolute;
+}
+
+.dropdown__sep {
+ border-bottom: 1px solid darken($color2, 8%);
+ margin: 5px 7px 6px;
+ padding-top: 1px;
+}
+
+.dropdown--active .dropdown__content {
+ display: block;
+ line-height: 18px;
+ max-width: 311px;
+ right: 0;
+ text-align: left;
+ z-index: 9999;
+
+ & > ul {
+ list-style: none;
+ background: $color2;
+ padding: 4px 0;
+ border-radius: 4px;
+ box-shadow: 0 0 15px rgba($color8, 0.4);
+ min-width: 140px;
+ position: relative;
+ }
+
+ &.dropdown__right {
+ right: 0;
+ }
+
+ &.dropdown__left {
+ & > ul {
+ left: -98px;
+ }
+ }
+
+ & > ul > li > a {
+ font-size: 13px;
+ line-height: 18px;
+ display: block;
+ padding: 4px 14px;
+ box-sizing: border-box;
+ text-decoration: none;
+ background: $color2;
+ color: $color1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &:focus {
+ outline: 0;
+ }
+
+ &:hover {
+ background: $color4;
+ color: $color2;
+ }
+ }
+}
+
+.dropdown__icon {
+ vertical-align: middle;
+}
+
+.static-content {
+ padding: 10px;
+ padding-top: 20px;
+ color: lighten($color1, 26%);
+
+ h1 {
+ font-size: 16px;
+ font-weight: 500;
+ margin-bottom: 40px;
+ text-align: center;
+ }
+
+ p {
+ font-size: 13px;
+ margin-bottom: 20px;
+ }
+}
+
+.columns-area {
+ display: flex;
+ flex: 1 1 auto;
+ flex-direction: row;
+ justify-content: flex-start;
+ overflow-x: auto;
+ position: relative;
+}
+
+@media screen and (min-width: 360px) {
+ .columns-area {
+ padding: 10px;
+ }
+}
+
+.column {
+ width: 330px;
+ position: relative;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+
+ > .scrollable {
+ background: $color1;
+ }
+}
+
+.ui {
+ flex: 0 0 auto;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+ background: darken($color1, 7%);
+}
+
+.drawer {
+ width: 300px;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ overflow-y: hidden;
+}
+
+.drawer__tab {
+ display: block;
+ flex: 1 1 auto;
+ padding: 15px;
+ padding-bottom: 13px;
+ color: $color3;
+ text-decoration: none;
+ text-align: center;
+ font-size: 16px;
+ border-bottom: 2px solid transparent;
+}
+
+.column, .drawer {
+ flex: 1 1 100%;
+ overflow: hidden;
+}
+
+@media screen and (min-width: 360px) {
+ .tabs-bar {
+ margin: 10px;
+ margin-bottom: 0;
+ }
+
+ .search {
+ margin-bottom: 10px;
+ }
+}
+
+@media screen and (max-width: 1024px) {
+ .column, .drawer {
+ width: 100%;
+ padding: 0;
+ }
+
+ .columns-area {
+ flex-direction: column;
+ }
+
+ .search__input, .autosuggest-textarea__textarea {
+ font-size: 16px;
+ }
+}
+
+@media screen and (min-width: 1025px) {
+ .columns-area {
+ padding: 0;
+ }
+
+ .column, .drawer {
+ flex: 0 0 auto;
+ padding: 10px;
+ padding-left: 5px;
+ padding-right: 5px;
+
+ &:first-child {
+ padding-left: 10px;
+ }
+
+ &:last-child {
+ padding-right: 10px;
+ }
+ }
+
+ .columns-area > div {
+ .column, .drawer {
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+ }
+}
+
+@media screen and (min-width: 1397px) { /* Width of 4 columns with margins */
+ .columns-area {
+ margin-left: auto;
+ margin-right: auto;
+ }
+}
+
+@media screen and (min-width: 1900px) {
+ .column, .drawer {
+ width: 400px;
+ border-radius: 4px;
+ height: 96vh;
+ margin-top: 2vh;
+ }
+}
+
+.drawer__pager {
+ box-sizing: border-box;
+ padding: 0;
+ flex-grow: 1;
+ position: relative;
+ overflow: hidden;
+ display: flex;
+}
+
+.drawer__inner {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background: lighten($color1, 13%);
+ box-sizing: border-box;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ overflow-y: auto;
+ width: 100%;
+ height: 100%;
+
+ &.darker {
+ background: $color1;
+ }
+}
+
+.pseudo-drawer {
+ background: lighten($color1, 13%);
+ font-size: 13px;
+ text-align: left;
+}
+
+.drawer__header {
+ flex: 0 0 auto;
+ font-size: 16px;
+ background: lighten($color1, 8%);
+ margin-bottom: 10px;
+ display: flex;
+ flex-direction: row;
+
+ a {
+ transition: background 100ms ease-in;
+
+ &:hover {
+ background: lighten($color1, 3%);
+ transition: background 200ms ease-out;
+ }
+ }
+}
+
+.tabs-bar {
+ display: flex;
+ background: lighten($color1, 8%);
+ flex: 0 0 auto;
+ overflow-y: auto;
+}
+
+.tabs-bar__link {
+ display: block;
+ flex: 1 1 auto;
+ padding: 15px 10px;
+ color: $color5;
+ text-decoration: none;
+ text-align: center;
+ font-size: 14px;
+ font-weight: 500;
+ border-bottom: 2px solid lighten($color1, 8%);
+ transition: all 200ms linear;
+
+ .fa {
+ font-weight: 400;
+ font-size: 16px;
+ }
+
+ &.active {
+ border-bottom: 2px solid $color4;
+ color: $color4;
+ }
+
+ &:hover, &:focus, &:active {
+ background: lighten($color1, 14%);
+ transition: all 100ms linear;
+ }
+
+ span {
+ margin-left: 5px;
+ display: none;
+ }
+}
+
+@media screen and (min-width: 600px) {
+ .tabs-bar__link {
+ span {
+ display: inline;
+ }
+ }
+}
+
+@media screen and (min-width: 1025px) {
+ .tabs-bar {
+ display: none;
+ }
+}
+
+.react-autosuggest__container {
+ position: relative;
+}
+
+.react-autosuggest__suggestions-container {
+ position: absolute;
+ top: 100%;
+ width: 100%;
+ z-index: 99;
+ box-shadow: 0 0 15px rgba($color8, 0.4);
+}
+
+.react-autosuggest__section-title {
+ background: $color3;
+ padding: 4px 10px;
+ font-weight: 500;
+ cursor: default;
+ color: $color1;
+ text-transform: uppercase;
+ font-size: 11px;
+}
+
+.react-autosuggest__suggestions-list {
+ background: $color2;
+ color: $color1;
+ font-size: 14px;
+}
+
+.react-autosuggest__suggestion {
+ padding: 10px;
+ cursor: pointer;
+}
+
+.react-autosuggest__suggestion--focused {
+ background: $color4;
+ color: $color5;
+}
+
+.scrollable {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ flex: 1 1 auto;
+ backface-visibility: hidden;
+ -webkit-overflow-scrolling: touch;
+
+ &.optionally-scrollable {
+ overflow-y: auto;
+ }
+}
+
+.column-back-button {
+ background: lighten($color1, 4%);
+ color: $color4;
+ cursor: pointer;
+ flex: 0 0 auto;
+ font-size: 16px;
+ padding: 15px;
+ z-index: 3;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+.column-back-button__icon {
+ display: inline-block;
+ margin-right: 5px;
+}
+
+.column-back-button--slim {
+ position: relative;
+}
+
+.column-back-button--slim-button {
+ cursor: pointer;
+ flex: 0 0 auto;
+ font-size: 16px;
+ padding: 15px;
+ position: absolute;
+ right: 0;
+ top: -48px;
+}
+
+.react-toggle {
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ background-color: transparent;
+ border: 0;
+ padding: 0;
+ user-select: none;
+ -webkit-tap-highlight-color: rgba($color8, 0);
+ -webkit-tap-highlight-color: transparent;
+}
+
+.react-toggle-screenreader-only {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+
+.react-toggle--disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+ transition: opacity 0.25s;
+}
+
+.react-toggle-track {
+ width: 50px;
+ height: 24px;
+ padding: 0;
+ border-radius: 30px;
+ background-color: $color1;
+ transition: all 0.2s ease;
+}
+
+.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
+ background-color: darken($color1, 10%);
+}
+
+.react-toggle--checked .react-toggle-track {
+ background-color: $color4;
+}
+
+.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
+ background-color: lighten($color4, 10%);
+}
+
+.react-toggle-track-check {
+ position: absolute;
+ width: 14px;
+ height: 10px;
+ top: 0px;
+ bottom: 0px;
+ margin-top: auto;
+ margin-bottom: auto;
+ line-height: 0;
+ left: 8px;
+ opacity: 0;
+ transition: opacity 0.25s ease;
+}
+
+.react-toggle--checked .react-toggle-track-check {
+ opacity: 1;
+ transition: opacity 0.25s ease;
+}
+
+.react-toggle-track-x {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ top: 0px;
+ bottom: 0px;
+ margin-top: auto;
+ margin-bottom: auto;
+ line-height: 0;
+ right: 10px;
+ opacity: 1;
+ transition: opacity 0.25s ease;
+}
+
+.react-toggle--checked .react-toggle-track-x {
+ opacity: 0;
+}
+
+.react-toggle-thumb {
+ transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
+ position: absolute;
+ top: 1px;
+ left: 1px;
+ width: 22px;
+ height: 22px;
+ border: 1px solid $color1;
+ border-radius: 50%;
+ background-color: darken($color5, 2%);
+ box-sizing: border-box;
+ transition: all 0.25s ease;
+}
+
+.react-toggle--checked .react-toggle-thumb {
+ left: 27px;
+ border-color: $color4;
+}
+
+.column-link {
+ background: lighten($color1, 8%);
+ color: $color5;
+ display: block;
+ font-size: 16px;
+ padding: 15px;
+ text-decoration: none;
+
+ &:hover {
+ background: lighten($color1, 11%);
+ }
+
+ &.hidden-on-mobile {
+ @media screen and (max-width: 1024px) {
+ display: none;
+ }
+ }
+}
+
+.column-link__icon {
+ display: inline-block;
+ margin-right: 5px;
+}
+
+.column-subheading {
+ background: $color1;
+ color: lighten($color1, 26%);
+ padding: 8px 20px;
+ font-size: 12px;
+ font-weight: 500;
+ text-transform: uppercase;
+ cursor: default;
+}
+
+.autosuggest-textarea,
+.spoiler-input {
+ position: relative;
+}
+
+.autosuggest-textarea__textarea,
+.spoiler-input__input {
+ display: block;
+ box-sizing: border-box;
+ width: 100%;
+ margin: 0;
+ color: $color1;
+ padding: 10px;
+ font-family: inherit;
+ font-size: 14px;
+ resize: vertical;
+ border: 0;
+ outline: 0;
+
+ &:focus {
+ outline: 0;
+ }
+
+ @media screen and (max-width: 600px) {
+ font-size: 16px;
+ }
+}
+
+.spoiler-input__input {
+ border-radius: 4px;
+}
+
+.autosuggest-textarea__textarea {
+ min-height: 100px;
+ background: $color5;
+ border-radius: 4px 4px 0 0;
+ padding-bottom: 0;
+ padding-right: 10px + 22px;
+ resize: none;
+
+ @media screen and (max-width: 600px) {
+ height: 100px !important; // prevent auto-resize textarea
+ resize: vertical;
+ }
+}
+
+.autosuggest-textarea__suggestions {
+ position: absolute;
+ top: 100%;
+ width: 100%;
+ z-index: 99;
+ box-shadow: 0 0 15px rgba($color8, 0.4);
+ background: $color2;
+ color: $color1;
+ font-size: 14px;
+}
+
+.autosuggest-textarea__suggestions__item {
+ padding: 10px;
+ cursor: pointer;
+
+ &:hover {
+ background: darken($color2, 10%);
+ }
+
+ &.selected {
+ background: $color4;
+ color: $color5;
+ }
+}
+
+.autosuggest-account {
+ overflow: hidden;
+}
+
+.autosuggest-account-icon {
+ float: left;
+ margin-right: 5px;
+}
+
+.autosuggest-status {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+ strong {
+ font-weight: 500;
+ }
+}
+
+.character-counter__wrapper {
+ line-height: 36px;
+ margin-right: 16px;
+ padding-top: 10px;
+}
+
+.character-counter {
+ cursor: default;
+ font-size: 16px;
+}
+
+.character-counter--over {
+ color: #ff5050;
+}
+
+.getting-started__wrapper {
+ position: relative;
+}
+
+.getting-started {
+ box-sizing: border-box;
+ padding-bottom: 235px;
+ background: url('../images/mastodon-getting-started.png') no-repeat 0 100%/contain local;
+ flex: 1 0 auto;
+
+ p {
+ color: $color2;
+ }
+
+ a {
+ color: lighten($color1, 26%);
+ }
+}
+
+.setting-text {
+ color: $color3;
+ background: transparent;
+ border: none;
+ border-bottom: 2px solid $color3;
+ box-sizing: border-box;
+ display: block;
+ font-family: inherit;
+ margin-bottom: 10px;
+ padding: 7px 0px;
+ width: 100%;
+
+ &:focus, &:active {
+ color: $color5;
+ border-bottom-color: $color4;
+ }
+
+ @media screen and (max-width: 600px) {
+ font-size: 16px;
+ }
+}
+
+@import 'boost';
+
+button.icon-button i.fa-retweet {
+ background-position: 0 0;
+ height: 19px;
+ transition: background-position 0.9s steps(10);
+ transition-duration: 0s;
+ vertical-align: middle;
+ width: 22px;
+
+ &::before {
+ display: none !important;
+ }
+}
+
+button.icon-button.active i.fa-retweet {
+ transition-duration: 0.9s;
+ background-position: 0 100%;
+}
+
+.status-card {
+ display: flex;
+ cursor: pointer;
+ font-size: 14px;
+ border: 1px solid lighten($color1, 8%);
+ border-radius: 4px;
+ color: lighten($color1, 26%);
+ margin-top: 14px;
+ text-decoration: none;
+ overflow: hidden;
+
+ &:hover {
+ background: lighten($color1, 8%);
+ }
+}
+
+.status-card-video, .status-card-rich, .status-card-photo {
+ margin-top: 14px;
+ overflow: hidden;
+
+ iframe {
+ width: 100%;
+ height: auto;
+ }
+}
+
+.status-card-photo {
+ display: block;
+ text-decoration: none;
+
+ img {
+ display: block;
+ width: 100%;
+ height: auto;
+ margin: 0;
+ }
+}
+
+.status-card__title {
+ display: block;
+ font-weight: 500;
+ margin-bottom: 5px;
+ color: $color3;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.status-card__content {
+ flex: 1 1 auto;
+ overflow: hidden;
+ padding: 14px 14px 14px 8px;
+}
+
+.status-card__description {
+ color: $color3;
+}
+
+.status-card__image {
+ flex: 0 0 100px;
+ background: lighten($color1, 8%);
+}
+
+.status-card__image-image {
+ border-radius: 4px 0px 0px 4px;
+ display: block;
+ height: auto;
+ margin: 0;
+ width: 100%;
+}
+
+.load-more {
+ display: block;
+ color: lighten($color1, 26%);
+ text-align: center;
+ padding: 15px;
+ text-decoration: none;
+
+ &:hover {
+ background: lighten($color1, 2%);
+ }
+}
+
+.missing-indicator {
+ text-align: center;
+ font-size: 16px;
+ font-weight: 500;
+ color: lighten($color1, 16%);
+ background: $color1;
+ cursor: default;
+ display: flex;
+ flex: 1 1 auto;
+ align-items: center;
+ justify-content: center;
+
+ & > div {
+ background: url('../images/mastodon-not-found.png') no-repeat center -50px;
+ padding-top: 210px;
+ width: 100%;
+ }
+}
+
+.column-header {
+ padding: 15px;
+ font-size: 16px;
+ background: lighten($color1, 4%);
+ flex: 0 0 auto;
+ cursor: pointer;
+ position: relative;
+ z-index: 2;
+ outline: 0;
+
+ &.active {
+ box-shadow: 0 1px 0 rgba($color4, 0.3);
+ }
+
+ &.active .fa {
+ color: $color4;
+ text-shadow: 0 0 10px rgba($color4, 0.4);
+ }
+
+ &.hidden-on-mobile {
+ @media screen and (max-width: 1024px) {
+ display: none;
+ }
+ }
+
+ &:focus, &:active {
+ outline: 0;
+ }
+}
+
+.column-header__icon {
+ display: inline-block;
+ margin-right: 5px;
+}
+
+.loading-indicator {
+ color: $color2;
+ font-size: 16px;
+ font-weight: 500;
+ padding-top: 120px;
+ text-align: center;
+}
+
+.collapsable-collapsed {
+ color: $color3;
+ background: lighten($color1, 4%);
+}
+
+.collapsable {
+ color: $color5;
+ background: lighten($color1, 8%);
+
+ &:hover {
+ color: $color5;
+ background: lighten($color1, 8%);
+ }
+}
+
+.video-error-cover {
+ align-items: center;
+ background: $color8;
+ color: $color5;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ justify-content: center;
+ margin-top: 8px;
+ position: relative;
+ text-align: center;
+ z-index: 100;
+}
+
+.media-spoiler {
+ align-items: center;
+ background: $color8;
+ color: $color5;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ justify-content: center;
+ position: relative;
+ text-align: center;
+ z-index: 100;
+}
+
+.media-spoiler__warning {
+ display: block;
+ font-size: 14px;
+}
+
+.media-spoiler__trigger {
+ display: block;
+ font-size: 11px;
+ font-weight: 500;
+}
+
+.spoiler-button {
+ left: 4px;
+ position: absolute;
+ text-shadow: 0px 1px 1px #000, 1px 0px 1px #000;
+ top: 4px;
+ z-index: 100;
+}
+
+.modal-container--preloader {
+ background: lighten($color1, 8%);
+}
+
+.account--panel {
+ background: lighten($color1, 4%);
+ border-top: 1px solid lighten($color1, 8%);
+ border-bottom: 1px solid lighten($color1, 8%);
+ display: flex;
+ flex-direction: row;
+ padding: 10px 0px;
+}
+
+.account--panel__button,
+.detailed-status__button {
+ flex: 1 1 auto;
+ text-align: center;
+}
+
+.column-settings__outer {
+ background: lighten($color1, 8%);
+ padding: 15px;
+}
+
+.column-settings__section {
+ color: $color3;
+ cursor: default;
+ display: block;
+ font-weight: 500;
+ margin-bottom: 10px;
+}
+
+.modal-container__nav {
+ align-items: center;
+ background: rgba(0, 0, 0, 0.5);
+ box-sizing: border-box;
+ color: $color5;
+ cursor: pointer;
+ display: flex;
+ font-size: 24px;
+ height: 100%;
+ padding: 30px 15px;
+ position: absolute;
+ top: 0;
+}
+
+.modal-container__nav--left {
+ left: -61px;
+}
+
+.modal-container__nav--right {
+ right: -61px;
+}
+
+.account--follows-info {
+ color: $color5;
+}
+
+.setting-toggle__label {
+ display: block;
+ line-height: 24px;
+ vertical-align: middle;
+}
+
+.setting-toggle {
+ color: $color3;
+ display: inline-block;
+ margin-bottom: 14px;
+ margin-left: 8px;
+ vertical-align: middle;
+}
+
+.report.scrollable {
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ max-height: 100%;
+}
+
+.report__target {
+ border-bottom: 1px solid lighten($color1, 4%);
+ color: $color2;
+ flex: 0 0 auto;
+ padding: 10px;
+
+ strong {
+ display: block;
+ color: $color5;
+ font-weight: 500;
+ }
+}
+
+.report__statuses {
+ flex: 1 1 auto;
+}
+
+.report__textarea-wrapper {
+ flex: 0 0 100px;
+ padding: 10px;
+}
+
+.report__textarea {
+ background: transparent;
+ box-sizing: border-box;
+ border: 0;
+ border-bottom: 2px solid $color3;
+ border-radius: 2px 2px 0 0;
+ color: $color5;
+ display: block;
+ font-family: inherit;
+ font-size: 14px;
+ margin-bottom: 10px;
+ outline: 0;
+ padding: 7px 4px;
+ resize: vertical;
+ width: 100%;
+
+ &:active, &:focus {
+ border-bottom-color: $color4;
+ background: rgba($color8, 0.1);
+ }
+}
+
+.report__submit {
+ margin-top: 10px;
+ overflow: hidden;
+}
+
+.report__submit-button {
+ float: right;
+}
+
+.empty-column-indicator {
+ color: lighten($color1, 20%);
+ background: $color1;
+ text-align: center;
+ padding: 20px;
+ font-size: 15px;
+ font-weight: 400;
+ cursor: default;
+ display: flex;
+ flex: 1 1 auto;
+ align-items: center;
+
+ a {
+ color: $color4;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
+
+.status-list__unread-indicator, .notifications__unread-indicator {
+ position: absolute;
+ top: 35px;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
+ width: 60%;
+ pointer-events: none;
+ height: 28px;
+ z-index: 1;
+ background: radial-gradient(ellipse, rgba($color4, 0.23) 0%, rgba($color4, 0) 60%);
+}
+
+.emoji-dialog {
+ width: 245px;
+ height: 270px;
+ background: $color5;
+ box-sizing: border-box;
+ border-radius: 4px;
+ overflow: hidden;
+ position: relative;
+ box-shadow: 0 0 8px rgba($color8, 0.2);
+
+ .emojione {
+ margin: 0;
+ width: 100%;
+ height: auto;
+ }
+
+ .emoji-dialog-header {
+ padding: 0 10px;
+
+ ul {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ }
+
+ li {
+ display: inline-block;
+ box-sizing: border-box;
+ padding: 10px 5px;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+
+ .emoji {
+ width: 18px;
+ height: 18px;
+ }
+
+ img, svg {
+ width: 18px;
+ height: 18px;
+ filter: grayscale(100%);
+ }
+
+ &:hover {
+ img, svg {
+ filter: grayscale(0);
+ }
+ }
+
+ &.active {
+ border-bottom-color: $color4;
+
+ img, svg {
+ filter: grayscale(0);
+ }
+ }
+ }
+ }
+
+ .emoji-row {
+ box-sizing: border-box;
+ overflow-y: hidden;
+ padding-left: 10px;
+
+ .emoji {
+ display: inline-block;
+ padding: 2.5px;
+ border-radius: 4px;
+ }
+ }
+
+ .emoji-category-header {
+ box-sizing: border-box;
+ overflow-y: hidden;
+ padding: 10px 8px 10px 16px;
+ display: table;
+
+ > * {
+ display: table-cell;
+ vertical-align: middle;
+ }
+ }
+
+ .emoji-category-title {
+ font-size: 12px;
+ text-transform: uppercase;
+ font-weight: 500;
+ color: darken($color2, 18%);
+ cursor: default;
+ }
+
+ .emoji-category-heading-decoration {
+ text-align: right;
+ }
+
+ .modifiers {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ vertical-align: middle;
+ white-space: nowrap;
+ margin-top: 4px;
+
+ li {
+ display: inline-block;
+ padding: 0 2px;
+
+ &:last-of-type {
+ padding-right: 0;
+ }
+ }
+
+ .modifier {
+ display: inline-block;
+ border-radius: 10px;
+ width: 15px;
+ height: 15px;
+ position: relative;
+ cursor: pointer;
+
+ &.active:after {
+ content: "";
+ display: block;
+ position: absolute;
+ width: 7px;
+ height: 7px;
+ border-radius: 10px;
+ border: 2px solid $color5;
+ top: 2px;
+ left: 2px;
+ }
+ }
+ }
+
+ .emoji-search-wrapper {
+ padding: 10px;
+ border-bottom: 1px solid lighten($color2, 4%);
+ }
+
+ .emoji-search {
+ font-size: 14px;
+ font-weight: 400;
+ padding: 7px 9px;
+ font-family: inherit;
+ display: block;
+ width: 100%;
+ background: rgba($color2, 0.3);
+ color: darken($color2, 18%);
+ border: 1px solid $color2;
+ border-radius: 4px;
+ }
+
+ .emoji-categories-wrapper {
+ position: absolute;
+ top: 42px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ }
+
+ .emoji-search-wrapper + .emoji-categories-wrapper {
+ top: 93px;
+ }
+
+ .emoji-row .emoji {
+ img, svg {
+ transition: transform 60ms ease-in-out;
+ }
+
+ &:hover {
+ background: lighten($color2, 3%);
+
+ img, svg {
+ transform: translateZ(0) scale(1.2);
+ }
+ }
+ }
+
+ .emoji {
+ width: 22px;
+ height: 22px;
+ cursor: pointer;
+
+ &:focus {
+ outline: 0;
+ }
+ }
+}
+
+.upload-area {
+ align-items: center;
+ background: rgba($color8, 0.8);
+ display: flex;
+ height: 100%;
+ justify-content: center;
+ left: 0;
+ opacity: 0;
+ position: absolute;
+ top: 0;
+ visibility: hidden;
+ width: 100%;
+ z-index: 2000;
+
+ * {
+ pointer-events: none;
+ }
+}
+
+.upload-area__drop {
+ width: 320px;
+ height: 160px;
+ display: flex;
+ box-sizing: border-box;
+ position: relative;
+ padding: 8px;
+}
+
+.upload-area__background {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: -1;
+ border-radius: 4px;
+ background: $color1;
+ box-shadow: 0 0 5px rgba($color8, 0.2);
+}
+
+.upload-area__content {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: $color2;
+ font-size: 18px;
+ font-weight: 500;
+ border: 2px dashed lighten($color1, 26%);
+ border-radius: 4px;
+}
+
+.upload-progress {
+ padding: 10px;
+ color: lighten($color1, 26%);
+ overflow: hidden;
+ display: flex;
+
+ .fa {
+ font-size: 34px;
+ margin-right: 10px;
+ }
+
+ span {
+ font-size: 12px;
+ text-transform: uppercase;
+ font-weight: 500;
+ display: block;
+ }
+}
+
+.upload-progess__message {
+ flex: 1 1 auto;
+}
+
+.upload-progress__backdrop {
+ width: 100%;
+ height: 6px;
+ border-radius: 6px;
+ background: lighten($color1, 26%);
+ position: relative;
+ margin-top: 5px;
+}
+
+.upload-progress__tracker {
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 6px;
+ background: $color4;
+ border-radius: 6px;
+}
+
+.emoji-button {
+ outline: 0;
+
+ &:active, &:focus {
+ outline: 0 !important;
+ }
+
+ img {
+ filter: grayscale(100%);
+ opacity: 0.8;
+ display: block;
+ margin: 0;
+ width: 22px;
+ height: 22px;
+ margin-top: 2px;
+ }
+
+ &:hover, &:active, &:focus {
+ img {
+ opacity: 1;
+ filter: none;
+ }
+ }
+}
+
+.dropdown--active .emoji-button img {
+ opacity: 1;
+ filter: none;
+}
+
+.privacy-dropdown {
+ position: relative;
+}
+
+.privacy-dropdown__dropdown {
+ display: none;
+ position: absolute;
+ left: 0;
+ top: 27px;
+ width: 230px;
+ background: $color5;
+ border-radius: 0 4px 4px 4px;
+ z-index: 2;
+ overflow: hidden;
+}
+
+.privacy-dropdown__option {
+ color: $color1;
+ padding: 10px;
+ cursor: pointer;
+ display: flex;
+
+ &:hover, &.active {
+ background: $color4;
+ color: $color5;
+
+ .privacy-dropdown__option__content {
+ color: $color5;
+
+ strong {
+ color: $color5;
+ }
+ }
+ }
+
+ &.active:hover {
+ background: lighten($color4, 4%);
+ }
+}
+
+.privacy-dropdown__option__icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 10px;
+}
+
+.privacy-dropdown__option__content {
+ flex: 1 1 auto;
+ color: darken($color3, 24%);
+
+ strong {
+ font-weight: 500;
+ display: block;
+ color: $color1;
+ }
+}
+
+.privacy-dropdown.active {
+ .privacy-dropdown__value {
+ background: $color5;
+ border-radius: 4px 4px 0 0;
+ box-shadow: 0 -4px 4px rgba($color8, 0.1);
+ }
+
+ .privacy-dropdown__dropdown {
+ display: block;
+ box-shadow: 2px 4px 6px rgba($color8, 0.1);
+ }
+}
+
+.search {
+ position: relative;
+}
+
+.search__input {
+ padding-right: 30px;
+ color: $color2;
+ outline: 0;
+ box-sizing: border-box;
+ display: block;
+ width: 100%;
+ border: none;
+ padding: 10px;
+ padding-right: 30px;
+ font-family: inherit;
+ background: $color1;
+ color: $color3;
+ font-size: 14px;
+ margin: 0;
+
+ &::-moz-focus-inner {
+ border: 0;
+ }
+
+ &::-moz-focus-inner, &:focus, &:active {
+ outline: 0 !important;
+ }
+
+ &:focus {
+ background: lighten($color1, 4%);
+ }
+
+ @media screen and (max-width: 600px) {
+ font-size: 16px;
+ }
+}
+
+.search__icon {
+ .fa {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ z-index: 2;
+ display: inline-block;
+ opacity: 0;
+ transition: all 100ms linear;
+ font-size: 18px;
+ width: 18px;
+ height: 18px;
+ color: $color2;
+ cursor: default;
+ pointer-events: none;
+
+ &.active {
+ pointer-events: auto;
+ opacity: 0.3;
+ }
+ }
+
+ .fa-search {
+ transform: translateZ(0) rotate(90deg);
+
+ &.active {
+ pointer-events: none;
+ transform: translateZ(0) rotate(0deg);
+ }
+ }
+
+ .fa-times-circle {
+ top: 11px;
+ transform: translateZ(0) rotate(0deg);
+ cursor: pointer;
+
+ &.active {
+ transform: translateZ(0) rotate(90deg);
+ }
+
+ &:hover {
+ color: $color5;
+ }
+ }
+}
+
+.search-results__header {
+ color: lighten($color1, 26%);
+ background: lighten($color1, 2%);
+ border-bottom: 1px solid darken($color1, 4%);
+ padding: 15px 10px;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.search-results__hashtag {
+ display: block;
+ padding: 10px;
+ color: $color2;
+ text-decoration: none;
+
+ &:hover, &:active, &:focus {
+ color: lighten($color2, 4%);
+ text-decoration: underline;
+ }
+}
+
+.modal-root__overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 9999;
+ opacity: 0;
+ background: rgba($color8, 0.7);
+ transform: translateZ(0px);
+}
+
+.modal-root__container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ align-content: space-around;
+ z-index: 9999;
+ opacity: 0;
+ pointer-events: none;
+ user-select: none;
+}
+
+.modal-root__modal {
+ pointer-events: auto;
+ display: flex;
+ z-index: 9999;
+}
+
+.media-modal {
+ max-width: 80vw;
+ max-height: 80vh;
+ position: relative;
+
+ img, video {
+ max-width: 80vw;
+ max-height: 80vh;
+ }
+}
+
+.media-modal__close {
+ position: absolute;
+ right: 4px;
+ top: 4px;
+ z-index: 100;
+}
+
+.onboarding-modal {
+ background: $color2;
+ color: $color1;
+ border-radius: 8px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.onboarding-modal__pager {
+ height: 80vh;
+ width: 80vw;
+ max-width: 520px;
+ max-height: 420px;
+ position: relative;
+
+ & > div {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ padding: 25px;
+ display: none;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ display: flex;
+ opacity: 0;
+ user-select: text;
+ }
+}
+
+@media screen and (max-width: 550px) {
+ .onboarding-modal {
+ width: 100%;
+ height: 100%;
+ border-radius: 0;
+ }
+
+ .onboarding-modal__pager {
+ width: 100%;
+ height: auto;
+ max-width: none;
+ max-height: none;
+ flex: 1 1 auto;
+ }
+}
+
+.onboarding-modal__paginator {
+ flex: 0 0 auto;
+ background: darken($color2, 8%);
+ display: flex;
+ padding: 25px;
+
+ & > div {
+ min-width: 33px;
+ }
+
+ a {
+ color: darken($color2, 34%);
+ text-decoration: none;
+ font-size: 14px;
+ font-weight: 500;
+
+ &:hover, &:focus, &:active {
+ color: darken($color2, 38%);
+ }
+
+ &.onboarding-modal__done, &.onboarding-modal__next {
+ color: $color4;
+ }
+ }
+}
+
+.onboarding-modal__dots {
+ flex: 1 1 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.onboarding-modal__dot {
+ width: 14px;
+ height: 14px;
+ border-radius: 14px;
+ background: darken($color2, 16%);
+ margin: 0 3px;
+ cursor: pointer;
+
+ &:hover {
+ background: darken($color2, 18%);
+ }
+
+ &.active {
+ cursor: default;
+ background: darken($color2, 24%);
+ }
+}
+
+.onboarding-modal__page {
+ cursor: default;
+ line-height: 21px;
+
+ h1 {
+ font-size: 18px;
+ font-weight: 500;
+ color: $color1;
+ margin-bottom: 20px;
+ }
+
+ a {
+ color: $color4;
+
+ &:hover, &:focus, &:active {
+ color: lighten($color4, 4%);
+ }
+ }
+
+ p {
+ font-size: 16px;
+ color: lighten($color1, 8%);
+ margin-top: 10px;
+ margin-bottom: 10px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ strong {
+ font-weight: 500;
+ background: $color1;
+ color: $color2;
+ border-radius: 4px;
+ font-size: 14px;
+ padding: 3px 6px;
+ }
+ }
+}
+
+.onboarding-modal__page-one {
+ display: flex;
+}
+
+.onboarding-modal__page-one__elephant-friend {
+ background: url('../images/elephant-friend.png') no-repeat center center/contain;
+ width: 147px;
+ height: 160px;
+ margin-right: 10px;
+}
+
+.onboarding-modal__page-two,
+.onboarding-modal__page-three,
+.onboarding-modal__page-four,
+.onboarding-modal__page-five {
+ p {
+ text-align: left;
+ }
+
+ .figure {
+ background: darken($color1, 8%);
+ color: $color2;
+ margin-bottom: 20px;
+ border-radius: 4px;
+ padding: 10px;
+ text-align: center;
+ font-size: 14px;
+ box-shadow: 1px 2px 6px rgba($color8, 0.3);
+
+ .onboarding-modal__image {
+ border-radius: 4px;
+ margin-bottom: 10px;
+ }
+
+ &.non-interactive {
+ pointer-events: none;
+ text-align: left;
+ }
+ }
+}
+
+.onboarding-modal__page-four__columns {
+ .row {
+ display: flex;
+ margin-bottom: 20px;
+
+ & > div {
+ flex: 1 1 0;
+ margin: 0 10px;
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ p {
+ text-align: center;
+ }
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .column-header {
+ color: $color5;
+ }
+}
+
+.onboarding-modal__image {
+ border-radius: 8px;
+ width: 70vw;
+ max-width: 450px;
+ max-height: auto;
+ display: block;
+ margin: auto;
+ margin-bottom: 20px;
+}
+
+.onboard-sliders {
+ display: inline-block;
+ max-width: 30px;
+ max-height: auto;
+ margin-left: 10px;
+}
+
+.boost-modal, .confirmation-modal {
+ background: lighten($color2, 8%);
+ color: $color1;
+ border-radius: 8px;
+ overflow: hidden;
+ max-width: 90vw;
+ width: 480px;
+ position: relative;
+ flex-direction: column;
+
+ .status__display-name {
+ display: block;
+ max-width: 100%;
+ padding-right: 25px;
+ }
+
+ .status__avatar {
+ height: 28px;
+ left: 10px;
+ position: absolute;
+ top: 10px;
+ width: 48px;
+ }
+}
+
+.boost-modal__container {
+ overflow-x: scroll;
+ padding: 10px;
+
+ .status {
+ user-select: text;
+ border-bottom: 0;
+ }
+}
+
+.boost-modal__action-bar, .confirmation-modal__action-bar {
+ display: flex;
+ background: $color2;
+ padding: 10px;
+ line-height: 36px;
+
+ & > div {
+ flex: 1 1 auto;
+ text-align: right;
+ color: lighten($color1, 33%);
+ padding-right: 10px;
+ }
+
+ .button {
+ flex: 0 0 auto;
+ }
+}
+
+.boost-modal__status-header {
+ font-size: 15px;
+}
+
+.boost-modal__status-time {
+ float: right;
+ font-size: 14px;
+}
+
+.confirmation-modal {
+ max-width: 380px;
+}
+
+.confirmation-modal__action-bar {
+ & > div {
+ text-align: left;
+ padding: 0 16px;
+ }
+
+ a {
+ color: darken($color2, 34%);
+ text-decoration: none;
+ font-size: 14px;
+ font-weight: 500;
+
+ &:hover, &:focus, &:active {
+ color: darken($color2, 38%);
+ }
+ }
+}
+
+.confirmation-modal__container {
+ padding: 30px;
+ font-size: 16px;
+ text-align: center;
+
+ strong {
+ font-weight: 500;
+ }
+}
+
+.loading-bar {
+ background-color: $color4;
+ height: 3px;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.media-gallery__gifv__label {
+ display: block;
+ position: absolute;
+ color: $color5;
+ background: rgba($color8, 0.5);
+ bottom: 6px;
+ left: 6px;
+ padding: 2px 6px;
+ border-radius: 2px;
+ font-size: 11px;
+ font-weight: 600;
+ z-index: 1;
+ pointer-events: none;
+ opacity: 0.9;
+ transition: opacity 0.1s ease;
+}
+
+.media-gallery__gifv {
+ &.autoplay {
+ .media-gallery__gifv__label {
+ display: none;
+ }
+ }
+
+ &:hover {
+ .media-gallery__gifv__label {
+ opacity: 1;
+ }
+ }
+}
+
+.attachment-list {
+ display: flex;
+ font-size: 14px;
+ border: 1px solid lighten($color1, 8%);
+ border-radius: 4px;
+ margin-top: 14px;
+ overflow: hidden;
+}
+
+.attachment-list__icon {
+ flex: 0 0 auto;
+ color: lighten($color1, 26%);
+ padding: 8px 18px;
+ cursor: default;
+ border-right: 1px solid lighten($color1, 8%);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: 26px;
+
+ .fa {
+ display: block;
+ }
+}
+
+.attachment-list__list {
+ list-style: none;
+ padding: 4px 0;
+ padding-left: 8px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+
+ li {
+ display: block;
+ padding: 4px 0;
+ }
+
+ a {
+ text-decoration: none;
+ color: lighten($color1, 26%);
+ font-weight: 500;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
+
+/* Media Gallery */
+.media-gallery {
+ box-sizing: border-box;
+ margin-top: 8px;
+ overflow: hidden;
+ position: relative;
+ width: 100%;
+}
+
+.media-gallery__item {
+ border: none;
+ box-sizing: border-box;
+ display: block;
+ float: left;
+ position: relative;
+}
+
+.media-gallery__item-thumbnail {
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: cover;
+ cursor: zoom-in;
+ display: block;
+ height: 100%;
+ text-decoration: none;
+ width: 100%;
+}
+
+.media-gallery__gifv {
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+ width: 100%;
+}
+
+.media-gallery__item-gifv-thumbnail {
+ cursor: zoom-in;
+ height: 100%;
+ object-fit: cover;
+ position: relative;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 100%;
+ z-index: 1;
+}
+
+.media-gallery__item-thumbnail-label {
+ clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
+ clip: rect(1px, 1px, 1px, 1px);
+ overflow: hidden;
+ position: absolute;
+}
+/* End Media Gallery */
+
+/* Status Video Player */
+.status__video-player {
+ background: #000;
+ box-sizing: border-box;
+ cursor: default; /* May not be needed */
+ margin-top: 8px;
+ overflow: hidden;
+ position: relative;
+}
+
+.status__video-player-video {
+ height: 100%;
+ object-fit: cover;
+ position: relative;
+ top: 50%;
+ transform: translateY(-35%);
+ width: 100%;
+ z-index: 1;
+}
+
+.status__video-player-expand,
+.status__video-player-mute {
+ color: #fff;
+ opacity: 0.8;
+ position: absolute;
+ right: 4px;
+ text-shadow: 0px 1px 1px #000, 1px 0px 1px #000;
+}
+
+.status__video-player-spoiler {
+ color: #fff;
+ left: 4px;
+ position: absolute;
+ text-shadow: 0px 1px 1px #000, 1px 0px 1px #000;
+ top: 4px;
+ z-index: 100;
+}
+
+.status__video-player-expand {
+ bottom: 4px;
+ z-index: 100;
+}
+
+.status__video-player-mute {
+ top: 4px;
+ z-index: 5;
+}
+
+.media-spoiler-video {
+ background-size: cover;
+ cursor: pointer;
+ margin-top: 8px;
+ position: relative;
+}
+
+.media-spoiler-video-play-icon {
+ border-radius: 100px;
+ color: rgba(255, 255, 255, 0.8);
+ font-size: 36px;
+ left: 50%;
+ padding: 5px;
+ position: absolute;
+ top: 50%;
+ transform: translate(-50%, -50%);
+}
+/* End Video Player */
diff --git a/app/javascript/styles/containers.scss b/app/javascript/styles/containers.scss
@@ -0,0 +1,71 @@
+.container {
+ width: 700px;
+ margin: 0 auto;
+ margin-top: 40px;
+
+ @media screen and (max-width: 700px) {
+ width: 100%;
+ margin: 0;
+ }
+}
+
+.mastodon-column-container {
+ display: flex;
+ height: 100%;
+ width: 100%;
+
+ // 707568 - height 100% doesn't work on child of a flex item - chromium - Monorail
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=707568
+ flex: 1 1 auto;
+}
+
+.logo-container {
+ max-width: 400px;
+ margin: 100px auto;
+ margin-bottom: 0;
+ cursor: default;
+
+ @media screen and (max-width: 360px) {
+ margin: 30px auto;
+ }
+
+ h1 {
+ display: block;
+ text-align: center;
+ color: $color5;
+ font-size: 48px;
+ font-weight: 500;
+
+ img {
+ display: block;
+ margin: 20px auto;
+ width: 180px;
+ height: 180px;
+ }
+
+ a {
+ color: inherit;
+ text-decoration: none;
+ outline: 0;
+
+ img {
+ opacity: 0.8;
+ transition: opacity 0.8s ease;
+ }
+
+ &:hover {
+ img {
+ opacity: 1;
+ transition-duration: 0.2s;
+ }
+ }
+ }
+
+ small {
+ display: block;
+ font-size: 12px;
+ font-weight: 400;
+ font-family: 'Roboto Mono', monospace;
+ }
+ }
+}
diff --git a/app/javascript/styles/fonts/montserrat.scss b/app/javascript/styles/fonts/montserrat.scss
@@ -0,0 +1,11 @@
+@font-face {
+ font-family: 'Montserrat';
+ src: local('Montserrat');
+ src: url('../fonts/montserrat/Montserrat-Regular.eot');
+ src: url('../fonts/montserrat/Montserrat-Regular.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/montserrat/Montserrat-Regular.woff2') format('woff2'),
+ url('../fonts/montserrat/Montserrat-Regular.woff') format('woff'),
+ url('../fonts/montserrat/Montserrat-Regular.ttf') format('truetype');
+ font-weight: 400;
+ font-style: normal;
+}
diff --git a/app/javascript/styles/fonts/roboto-mono.scss b/app/javascript/styles/fonts/roboto-mono.scss
@@ -0,0 +1,12 @@
+@font-face {
+ font-family: 'Roboto Mono';
+ src: local('Roboto Mono');
+ src: url('../fonts/roboto-mono/robotomono-regular-webfont.eot');
+ src: url('../fonts/roboto-mono/robotomono-regular-webfont.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/roboto-mono/robotomono-regular-webfont.woff2') format('woff2'),
+ url('../fonts/roboto-mono/robotomono-regular-webfont.woff') format('woff'),
+ url('../fonts/roboto-mono/robotomono-regular-webfont.ttf') format('truetype'),
+ url('../fonts/roboto-mono/robotomono-regular-webfont.svg#roboto_monoregular') format('svg');
+ font-weight: 400;
+ font-style: normal;
+}
diff --git a/app/javascript/styles/fonts/roboto.scss b/app/javascript/styles/fonts/roboto.scss
@@ -0,0 +1,52 @@
+@font-face {
+ font-family: 'Roboto';
+ src: local('Roboto');
+ src: url('../fonts/roboto/roboto-italic-webfont.eot');
+ src: url('../fonts/roboto/roboto-italic-webfont.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/roboto/roboto-italic-webfont.woff2') format('woff2'),
+ url('../fonts/roboto/roboto-italic-webfont.woff') format('woff'),
+ url('../fonts/roboto/roboto-italic-webfont.ttf') format('truetype'),
+ url('../fonts/roboto/roboto-italic-webfont.svg#roboto-italic-webfont') format('svg');
+ font-weight: normal;
+ font-style: italic;
+}
+
+@font-face {
+ font-family: 'Roboto';
+ src: local('Roboto');
+ src: url('../fonts/roboto/roboto-bold-webfont.eot');
+ src: local('Roboto bold'), local('roboto-bold'),
+ url('../fonts/roboto/roboto-bold-webfont.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/roboto/roboto-bold-webfont.woff2') format('woff2'),
+ url('../fonts/roboto/roboto-bold-webfont.woff') format('woff'),
+ url('../fonts/roboto/roboto-bold-webfont.ttf') format('truetype'),
+ url('../fonts/roboto/roboto-bold-webfont.svg#roboto-bold-webfont') format('svg');
+ font-weight: bold;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Roboto';
+ src: local('Roboto');
+ src: url('../fonts/roboto/roboto-medium-webfont.eot');
+ src: url('../fonts/roboto/roboto-medium-webfont.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/roboto/roboto-medium-webfont.woff2') format('woff2'),
+ url('../fonts/roboto/roboto-medium-webfont.woff') format('woff'),
+ url('../fonts/roboto/roboto-medium-webfont.ttf') format('truetype'),
+ url('../fonts/roboto/roboto-medium-webfont.svg#roboto-medium-webfont') format('svg');
+ font-weight: 500;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Roboto';
+ src: local('Roboto');
+ src: url('../fonts/roboto/roboto-regular-webfont.eot');
+ src: url('../fonts/roboto/roboto-regular-webfont.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/roboto/roboto-regular-webfont.woff2') format('woff2'),
+ url('../fonts/roboto/roboto-regular-webfont.woff') format('woff'),
+ url('../fonts/roboto/roboto-regular-webfont.ttf') format('truetype'),
+ url('../fonts/roboto/roboto-regular-webfont.svg#roboto-regular-webfont') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
diff --git a/app/javascript/styles/footer.scss b/app/javascript/styles/footer.scss
@@ -0,0 +1,29 @@
+.footer {
+ text-align: center;
+ margin-top: 30px;
+ font-size: 12px;
+ color: darken($color2, 25%);
+
+ .domain {
+ font-weight: 500;
+
+ a {
+ color: inherit;
+ text-decoration: none;
+ }
+ }
+
+ .powered-by, .single-user-login {
+ font-weight: 400;
+
+ a {
+ color: inherit;
+ text-decoration: underline;
+ font-weight: 500;
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+ }
+}
diff --git a/app/javascript/styles/forms.scss b/app/javascript/styles/forms.scss
@@ -0,0 +1,335 @@
+code {
+ font-family: 'Roboto Mono', monospace;
+ font-weight: 400;
+}
+
+.form-container {
+ max-width: 400px;
+ padding: 20px;
+ margin: 0 auto;
+}
+
+.simple_form {
+ .input {
+ margin-bottom: 15px;
+ }
+
+ span.hint {
+ display: block;
+ color: $color3;
+ font-size: 12px;
+ margin-top: 4px;
+ }
+
+ p.hint {
+ margin-bottom: 15px;
+ }
+
+ strong {
+ font-weight: 500;
+ }
+
+ .label_input {
+ display: flex;
+
+ label {
+ flex: 0 0 auto;
+ width: 100px;
+ }
+
+ input {
+ flex: 1 1 auto;
+ }
+ }
+
+ .input.file, .input.select, .input.radio_buttons {
+ padding: 15px 0;
+ margin-bottom: 0;
+
+ label {
+ font-family: inherit;
+ font-size: 16px;
+ color: $color5;
+ display: block;
+ padding-top: 5px;
+ }
+ }
+
+ .fields-group {
+ margin-bottom: 25px;
+ }
+
+ .input.radio_buttons .radio label {
+ margin-bottom: 5px;
+ font-family: inherit;
+ font-size: 14px;
+ color: white;
+ display: block;
+ width: auto;
+ }
+
+ .input.boolean {
+ margin-bottom: 5px;
+
+ label {
+ font-family: inherit;
+ font-size: 14px;
+ color: white;
+ display: block;
+ width: auto;
+ }
+
+ label.checkbox {
+ position: relative;
+ padding-left: 25px;
+ flex: 1 1 auto;
+ }
+
+ input[type=checkbox] {
+ position: absolute;
+ left: 0;
+ top: 1px;
+ margin: 0;
+ }
+
+ .hint {
+ padding-left: 25px;
+ margin-left: 0;
+ }
+ }
+
+ input[type=text], input[type=number], input[type=email], input[type=password], textarea {
+ background: transparent;
+ box-sizing: border-box;
+ border: 0;
+ border-bottom: 2px solid $color3;
+ border-radius: 2px 2px 0 0;
+ padding: 7px 4px;
+ font-size: 16px;
+ color: $color5;
+ display: block;
+ width: 100%;
+ outline: 0;
+ font-family: inherit;
+ resize: vertical;
+
+ &:invalid {
+ box-shadow: none;
+ }
+
+ &:focus:invalid {
+ border-bottom-color: $color6;
+ }
+
+ &:required:valid {
+ border-bottom-color: $color7;
+ }
+
+ &:active, &:focus {
+ border-bottom-color: $color4;
+ background: rgba($color8, 0.1);
+ }
+ }
+
+ .input.field_with_errors {
+ label {
+ color: $color6;
+ }
+
+ input[type=text], input[type=email], input[type=password] {
+ border-bottom-color: $color6;
+ }
+
+ .error {
+ display: block;
+ font-weight: 500;
+ color: $color6;
+ margin-top: 4px;
+ }
+ }
+
+ .actions {
+ margin-top: 30px;
+ }
+
+ button, .block-button {
+ display: block;
+ width: 100%;
+ border: 0;
+ border-radius: 4px;
+ background: $color4;
+ color: $color5;
+ font-size: 18px;
+ padding: 10px;
+ text-transform: uppercase;
+ text-decoration: none;
+ text-align: center;
+ box-sizing: border-box;
+ cursor: pointer;
+ font-weight: 500;
+ outline: 0;
+ margin-bottom: 10px;
+
+ &:hover {
+ background-color: lighten($color4, 5%);
+ }
+
+ &:active, &:focus {
+ position: relative;
+ top: 1px;
+ background-color: darken($color4, 5%);
+ }
+
+ &.negative {
+ background: $color6;
+
+ &:hover {
+ background-color: lighten($color6, 5%);
+ }
+
+ &:active, &:focus {
+ background-color: darken($color6, 5%);
+ }
+ }
+ }
+
+ select {
+ font-size: 16px;
+ }
+}
+
+.flash-message {
+ background: $color1;
+ color: $color3;
+ border-radius: 4px;
+ padding: 15px 10px;
+ margin-bottom: 30px;
+ box-shadow: 0 0 5px rgba($color8, 0.2);
+ text-align: center;
+
+ strong {
+ font-weight: 500;
+ }
+}
+
+.form-footer {
+ margin-top: 30px;
+ text-align: center;
+
+ a {
+ color: $color5;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
+
+.oauth-prompt, .follow-prompt {
+ margin-bottom: 30px;
+ text-align: center;
+ color: $color3;
+
+ h2 {
+ font-size: 16px;
+ margin-bottom: 30px;
+ }
+
+ strong {
+ color: $color2;
+ font-weight: 500;
+ }
+}
+
+.qr-wrapper {
+ display: flex;
+}
+
+.qr-code {
+ flex: 0 0 auto;
+ background: #fff;
+ padding: 4px;
+ margin-bottom: 20px;
+ box-shadow: 0 0 15px rgba($color8, 0.2);
+ display: inline-block;
+
+ svg {
+ display: block;
+ margin: 0;
+ }
+}
+
+.qr-alternative {
+ margin-left: 10px;
+ color: $color3;
+
+ samp {
+ display: block;
+ font-size: 14px;
+ }
+}
+
+.table-form {
+ p {
+ max-width: 400px;
+ margin-bottom: 15px;
+
+ strong {
+ font-weight: 500;
+ }
+ }
+
+ .warning {
+ max-width: 400px;
+ box-sizing: border-box;
+ background: rgba($color6, 0.5);
+ color: $color5;
+ text-shadow: 1px 1px 0 rgba($color8, 0.3);
+ box-shadow: 0 2px 6px rgba($color8, 0.4);
+ border-radius: 4px;
+ padding: 10px;
+ margin-bottom: 15px;
+
+ a {
+ color: $color5;
+ text-decoration: underline;
+
+ &:hover, &:focus, &:active {
+ text-decoration: none;
+ }
+ }
+
+ strong {
+ font-weight: 600;
+ display: block;
+ margin-bottom: 5px;
+
+ .fa {
+ font-weight: 400;
+ }
+ }
+ }
+}
+
+.action-pagination {
+ display: flex;
+ align-items: center;
+
+ .actions, .pagination {
+ flex: 1 1 auto;
+ }
+
+ .actions {
+ padding: 30px 0;
+ padding-right: 20px;
+ flex: 0 0 auto;
+ }
+}
+
+.user_allowed_languages {
+ li {
+ float: left;
+ width: 50%;
+ }
+}
diff --git a/app/javascript/styles/landing_strip.scss b/app/javascript/styles/landing_strip.scss
@@ -0,0 +1,17 @@
+.landing-strip {
+ background: rgba(darken($color1, 7%), 0.8);
+ color: $color3;
+ font-weight: 400;
+ padding: 14px;
+ border-radius: 4px;
+ margin-bottom: 20px;
+
+ strong, a {
+ font-weight: 500;
+ }
+
+ a {
+ color: inherit;
+ text-decoration: underline;
+ }
+}
diff --git a/app/javascript/styles/lists.scss b/app/javascript/styles/lists.scss
@@ -0,0 +1,20 @@
+.no-list {
+ list-style: none;
+
+ li {
+ display: inline-block;
+ margin: 0 5px;
+ }
+}
+
+.recovery-codes {
+ list-style: none;
+ margin: 0 auto;
+ text-align: center;
+
+ li {
+ font-size: 125%;
+ line-height: 1.5;
+ letter-spacing: 1px;
+ }
+}
diff --git a/app/javascript/styles/reset.scss b/app/javascript/styles/reset.scss
@@ -0,0 +1,91 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+
+body {
+ line-height: 1;
+}
+
+ol, ul {
+ list-style: none;
+}
+
+blockquote, q {
+ quotes: none;
+}
+
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-thumb {
+ background: lighten($color1, 4%);
+ border: 0px none $color5;
+ border-radius: 50px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: lighten($color1, 6%);
+}
+
+::-webkit-scrollbar-thumb:active {
+ background: lighten($color1, 4%);
+}
+
+::-webkit-scrollbar-track {
+ border: 0px none $color5;
+ border-radius: 0;
+ background: rgba($color8, 0.1);
+}
+
+::-webkit-scrollbar-track:hover {
+ background: $color1;
+}
+
+::-webkit-scrollbar-track:active {
+ background: $color1;
+}
+
+::-webkit-scrollbar-corner {
+ background: transparent;
+}
diff --git a/app/javascript/styles/rtl.scss b/app/javascript/styles/rtl.scss
@@ -0,0 +1,136 @@
+body.rtl {
+ direction: rtl;
+
+ .column-link__icon, .column-header__icon {
+ margin-right: 0;
+ margin-left: 5px;
+ }
+
+ .character-counter__wrapper {
+ margin-right: 0;
+ margin-left: 16px;
+ }
+
+ .navigation-bar__profile {
+ margin-left: 0;
+ margin-right: 8px;
+ }
+
+ .search__input {
+ padding-right: 10px;
+ padding-left: 30px;
+ }
+
+ .search__icon .fa {
+ right: auto;
+ left: 10px;
+ }
+
+ .column-icon-clear {
+ right: auto;
+ left: 48px;
+ }
+
+ .column-icon {
+ right: auto;
+ left: 5px;
+ }
+
+ .setting-toggle {
+ margin-left: 0;
+ margin-right: 8px;
+ }
+
+ .status__avatar {
+ left: auto;
+ right: 10px;
+ }
+
+ .status {
+ padding-left: 10px;
+ padding-right: 68px;
+ }
+
+ .status__info .status__display-name {
+ padding-left: 25px;
+ padding-right: 0;
+ }
+
+ .column-back-button--slim-button {
+ right: auto;
+ left: 0;
+ }
+
+ .status__info-time {
+ float: left;
+ }
+
+ .status__action-bar-button-wrapper {
+ float: right;
+ margin-right: 0;
+ margin-left: 18px;
+ }
+
+ .status__action-bar-dropdown {
+ float: right;
+ }
+
+ .privacy-dropdown__dropdown {
+ left: auto;
+ right: 0;
+ }
+
+ .dropdown--active .dropdown__content {
+ text-align: right;
+ }
+
+ .dropdown--active .dropdown__content::before {
+ left: auto;
+ right: 8px;
+ }
+
+ .dropdown--active .dropdown__content > ul {
+ left: auto;
+ right: -10px;
+ }
+
+ .privacy-dropdown__option__icon {
+ margin-left: 10px;
+ margin-right: 0;
+ }
+
+ .detailed-status__display-avatar {
+ margin-right: 0;
+ margin-left: 10px;
+ float: right;
+ }
+
+ .detailed-status__favorites, .detailed-status__reblogs {
+ margin-left: 0;
+ margin-right: 6px;
+ }
+
+ @media screen and (min-width: 1025px) {
+ .column, .drawer {
+ padding-left: 5px;
+ padding-right: 5px;
+
+ &:first-child {
+ padding-left: 5px;
+ padding-right: 10px;
+ }
+
+ &:last-child {
+ padding-right: 0px;
+ padding-left: 10px;
+ }
+ }
+
+ .columns-area > div {
+ .column, .drawer {
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+ }
+ }
+}
diff --git a/app/javascript/styles/stream_entries.scss b/app/javascript/styles/stream_entries.scss
@@ -0,0 +1,372 @@
+.activity-stream {
+ clear: both;
+ box-shadow: 0 0 15px rgba($color8, 0.2);
+
+ .entry {
+ background: $color5;
+
+ .detailed-status.light, .status.light {
+ border-bottom: 1px solid $color2;
+ }
+
+ &:last-child {
+ &, .detailed-status.light, .status.light {
+ border-bottom: 0;
+ border-radius: 0 0 4px 4px;
+ }
+ }
+
+ &:first-child {
+ &, .detailed-status.light, .status.light {
+ border-radius: 4px 4px 0 0;
+ }
+
+ &:last-child {
+ &, .detailed-status.light, .status.light {
+ border-radius: 4px;
+ }
+ }
+ }
+ }
+
+ .status.light {
+ padding: 14px 14px 14px (48px + 14px*2);
+ position: relative;
+ min-height: 48px;
+ cursor: default;
+
+ .status__header {
+ font-size: 15px;
+
+ .status__meta {
+ float: right;
+ font-size: 14px;
+
+ .status__relative-time {
+ color: $color4;
+ }
+ }
+ }
+
+ .status__display-name {
+ display: block;
+ max-width: 100%;
+ padding-right: 25px;
+ color: $color1;
+ }
+
+ .status__avatar {
+ position: absolute;
+ left: 14px;
+ top: 14px;
+ width: 48px;
+ height: 48px;
+
+ & > div {
+ width: 48px;
+ height: 48px;
+ }
+
+ img {
+ display: block;
+ border-radius: 4px;
+ }
+ }
+
+ .display-name {
+ display: block;
+ max-width: 100%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+ strong {
+ font-weight: 500;
+ color: $color1;
+ }
+
+ span {
+ font-size: 14px;
+ color: $color4;
+ }
+ }
+
+ .status__content {
+ color: $color1;
+
+ a {
+ color: $color4;
+ }
+
+ a.status__content__spoiler-link {
+ color: $color5;
+ background: $color3;
+
+ &:hover {
+ background: lighten($color3, 8%);
+ }
+ }
+ }
+
+ .status__attachments {
+ margin-top: 8px;
+ overflow: hidden;
+ width: 100%;
+ box-sizing: border-box;
+ position: relative;
+
+ .status__attachments__inner {
+ display: flex;
+ height: 214px;
+ }
+ }
+ }
+
+ .detailed-status.light {
+ padding: 14px;
+ background: $color5;
+ cursor: default;
+
+ .detailed-status__display-name {
+ display: block;
+ overflow: hidden;
+ margin-bottom: 15px;
+
+ & > div {
+ float: left;
+ margin-right: 10px;
+ }
+
+ .display-name {
+ display: block;
+ max-width: 100%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+ strong {
+ font-weight: 500;
+ color: $color1;
+ }
+
+ span {
+ font-size: 14px;
+ color: $color3;
+ }
+ }
+ }
+
+ .avatar {
+ width: 48px;
+ height: 48px;
+
+ img {
+ display: block;
+ border-radius: 4px;
+ }
+ }
+
+ .status__content {
+ color: $color1;
+
+ a {
+ color: $color4;
+ }
+
+ a.status__content__spoiler-link {
+ color: $color5;
+ background: $color3;
+
+ &:hover {
+ background: lighten($color3, 8%);
+ }
+ }
+ }
+
+ .detailed-status__meta {
+ margin-top: 15px;
+ color: $color3;
+ font-size: 14px;
+ line-height: 18px;
+
+ a {
+ color: inherit;
+ }
+
+ span > span {
+ font-weight: 500;
+ font-size: 12px;
+ margin-left: 6px;
+ display: inline-block;
+ }
+ }
+
+ .detailed-status__attachments {
+ margin-top: 8px;
+ overflow: hidden;
+ width: 100%;
+ box-sizing: border-box;
+ position: relative;
+
+ .status__attachments__inner {
+ display: flex;
+ height: 360px;
+ }
+ }
+
+ .video-player {
+ margin-top: 8px;
+ height: 300px;
+ overflow: hidden;
+ position: relative;
+
+ video {
+ position: relative;
+ z-index: 1;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+ }
+ }
+
+ .media-item, .video-item {
+ box-sizing: border-box;
+ position: relative;
+ left: auto;
+ top: auto;
+ right: auto;
+ bottom: auto;
+ float: left;
+ border: medium none;
+ display: block;
+ flex: 1 1 auto;
+ height: 100%;
+ margin-right: 2px;
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ a {
+ display: block;
+ width: 100%;
+ height: 100%;
+ background: no-repeat scroll center center / cover;
+ text-decoration: none;
+ cursor: zoom-in;
+ }
+
+ video {
+ position: relative;
+ z-index: 1;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+ }
+
+ .video-item {
+ a {
+ cursor: pointer;
+ }
+
+ .video-item__play {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ font-size: 36px;
+ transform: translate(-50%, -50%);
+ padding: 5px;
+ border-radius: 100px;
+ color: rgba($color5, 0.8);
+ z-index: 1;
+ }
+ }
+
+ .media-spoiler {
+ background: $color3;
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ text-align: center;
+ transition: all 100ms linear;
+ z-index: 2;
+
+ &:hover {
+ background: darken($color3, 5%);
+ }
+
+ span {
+ display: block;
+
+ &:first-child {
+ font-size: 14px;
+ }
+
+ &:last-child {
+ font-size: 11px;
+ font-weight: 500;
+ }
+ }
+ }
+
+ .pre-header {
+ padding: 14px 0px;
+ padding-left: (48px + 14px*2);
+ padding-bottom: 0;
+ margin-bottom: -4px;
+ color: $color3;
+ font-size: 14px;
+ position: relative;
+
+ .pre-header__icon {
+ position: absolute;
+ left: (48px + 14px*2 - 30px);
+ }
+
+ .status__display-name.muted strong {
+ color: $color3;
+ }
+ }
+
+ .open-in-web-link {
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
+
+.embed {
+ .activity-stream {
+ border-radius: 4px;
+ box-shadow: none;
+
+ .entry {
+ &:last-child {
+ border-radius: 0 0 4px 4px;
+ }
+
+ &:first-child {
+ border-radius: 4px 4px 0 0;
+
+ &:last-child {
+ border-radius: 4px;
+ }
+ }
+ }
+ }
+}
diff --git a/app/javascript/styles/tables.scss b/app/javascript/styles/tables.scss
@@ -0,0 +1,65 @@
+.table {
+ width: 100%;
+ max-width: 100%;
+ border-spacing: 0;
+ border-collapse: collapse;
+ margin-bottom: 20px;
+
+ th, td {
+ padding: 8px;
+ line-height: 18px;
+ vertical-align: top;
+ border-top: 1px solid $color1;
+ text-align: left;
+ }
+
+ & > thead > tr > th {
+ vertical-align: bottom;
+ border-bottom: 2px solid $color1;
+ border-top: 0;
+ font-weight: 500;
+ }
+
+ & > tbody > tr > th {
+ font-weight: 500;
+ }
+
+ & > tbody > tr:nth-child(odd) > td, & > tbody > tr:nth-child(odd) > th {
+ background: $color1;
+ }
+
+ a {
+ color: $color4;
+ text-decoration: underline;
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+
+ strong {
+ font-weight: 500;
+ }
+}
+
+samp {
+ font-family: 'Roboto Mono', monospace;
+}
+
+a.table-action-link {
+ text-decoration: none;
+ display: inline-block;
+ margin-right: 5px;
+ padding: 0 10px;
+ color: rgba($color5, 0.7);
+ font-weight: 500;
+
+ &:hover {
+ color: $color5;
+ }
+
+ i.fa {
+ font-weight: 400;
+ margin-right: 5px;
+ }
+}
diff --git a/app/javascript/styles/variables.scss b/app/javascript/styles/variables.scss
@@ -0,0 +1,8 @@
+$color1: #282c37 !default; // darkest
+$color2: #d9e1e8 !default; // lightest
+$color3: #9baec8 !default; // lighter
+$color4: #2b90d9 !default; // vibrant
+$color5: #ffffff !default; // white
+$color6: #df405a !default; // error red
+$color7: #79bd9a !default; // succ green
+$color8: #000000 !default; // black
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
@@ -1,5 +1,5 @@
- content_for :header_tags do
- = javascript_include_tag 'application_public', integrity: true, crossorigin: 'anonymous'
+ = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
- content_for :page_title do
= site_hostname
@@ -10,20 +10,20 @@
%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_url('mastodon_small.jpg') }/
+ %meta{ property: 'og:image', content: asset_pack_path('mastodon_small.jpg') }/
%meta{ property: 'og:image:width', content: '400' }/
%meta{ property: 'og:image:height', content: '400' }/
%meta{ property: 'twitter:card', content: 'summary' }/
.wrapper
%h1
- = image_tag 'logo.png'
+ = image_tag asset_pack_path('logo.png')
= Setting.site_title
%p= t('about.about_mastodon').html_safe
.screenshot-with-signup
- .mascot= image_tag 'fluffy-elephant-friend.png'
+ .mascot= image_tag asset_pack_path('fluffy-elephant-friend.png')
- if @instance_presenter.open_registrations
= render 'registration'
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
@@ -1,6 +1,6 @@
- content_for :header_tags do
%script#initial-state{:type => 'application/json'}!= json_escape(render(file: 'home/initial_state', formats: :json))
- = javascript_include_tag 'application', integrity: true, crossorigin: 'anonymous'
+ = javascript_pack_tag 'application', integrity: true, crossorigin: 'anonymous'
-= react_component 'Mastodon', default_props, class: 'app-holder', prerender: false
+.app-holder#mastodon{ data: { props: Oj.dump(default_props) }}
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
@@ -1,12 +1,12 @@
- content_for :header_tags do
- = javascript_include_tag 'application_public', integrity: true, crossorigin: 'anonymous'
+ = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
- content_for :content do
.admin-wrapper
.sidebar-wrapper
.sidebar
= link_to root_path do
- = image_tag 'logo.png', class: 'logo'
+ = image_tag asset_pack_path('logo.png'), class: 'logo'
= render_navigation
.content-wrapper
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
@@ -19,7 +19,9 @@
= ' - '
= title
- = stylesheet_link_tag stylesheet_for_layout, media: 'all'
+ = stylesheet_pack_tag 'vendor', media: 'all'
+ = stylesheet_pack_tag 'application', media: 'all'
+ = javascript_pack_tag 'vendor', integrity: true, crossorigin: 'anonymous'
= csrf_meta_tags
= yield :header_tags
diff --git a/app/views/layouts/auth.html.haml b/app/views/layouts/auth.html.haml
@@ -1,12 +1,12 @@
- content_for :header_tags do
- = javascript_include_tag 'application_public', integrity: true, crossorigin: 'anonymous'
+ = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
- content_for :content do
.container
.logo-container
%h1
= link_to root_path do
- = image_tag 'logo.png'
+ = image_tag asset_pack_path('logo.png')
.form-container
= render 'flashes'
diff --git a/app/views/layouts/embedded.html.haml b/app/views/layouts/embedded.html.haml
@@ -3,6 +3,6 @@
%head
%meta{:charset => 'utf-8'}/
= stylesheet_link_tag 'application', media: 'all'
- = javascript_include_tag 'application_public', integrity: true, crossorigin: 'anonymous'
+ = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
%body.embed
= yield
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
@@ -1,5 +1,5 @@
- content_for :header_tags do
- = javascript_include_tag 'application_public', integrity: true, crossorigin: 'anonymous'
+ = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
- content_for :content do
.container= yield
diff --git a/bin/webpack b/bin/webpack
@@ -0,0 +1,33 @@
+#!/usr/bin/env ruby
+$stdout.sync = true
+
+require "shellwords"
+require "yaml"
+
+ENV["RAILS_ENV"] ||= "development"
+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")
+
+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"
+ 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"
+
+Dir.chdir(APP_PATH) do
+ exec "NODE_PATH=#{NODE_MODULES_PATH} #{WEBPACK_BIN} --config #{WEBPACK_CONFIG}" \
+ " #{ARGV.join(" ")}"
+end
diff --git a/bin/webpack-dev-server b/bin/webpack-dev-server
@@ -0,0 +1,33 @@
+#!/usr/bin/env ruby
+$stdout.sync = true
+
+require "shellwords"
+require "yaml"
+
+ENV["RAILS_ENV"] ||= "development"
+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")
+
+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"])
+
+ 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 "Please run bundle exec rails webpacker:install to install webpacker"
+ exit!
+end
+
+Dir.chdir(APP_PATH) do
+ exec "NODE_PATH=#{NODE_MODULES_PATH} #{WEBPACK_BIN} --progress --color " \
+ "--config #{DEV_SERVER_CONFIG} #{ARGV.join(" ")}"
+end
diff --git a/bin/yarn b/bin/yarn
@@ -0,0 +1,11 @@
+#!/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/application.rb b/config/application.rb
@@ -74,10 +74,6 @@ module Mastodon
config.middleware.use Rack::Attack
config.middleware.use Rack::Deflater
- # babel config can be found in .babelrc
- config.browserify_rails.commandline_options = '--transform babelify --extension=".jsx"'
- config.browserify_rails.evaluate_node_modules = true
-
config.to_prepare do
Doorkeeper::AuthorizationsController.layout 'public'
Doorkeeper::AuthorizedApplicationsController.layout 'admin'
diff --git a/config/environments/development.rb b/config/environments/development.rb
@@ -75,8 +75,6 @@ Rails.application.configure do
Bullet.add_whitelist type: :n_plus_one_query, class_name: 'User', association: :account
end
-
- config.react.variant = :development
end
require 'sidekiq/testing'
diff --git a/config/environments/production.rb b/config/environments/production.rb
@@ -106,8 +106,6 @@ Rails.application.configure do
config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym
- config.react.variant = :production
-
config.to_prepare do
StatsD.backend = StatsD::Instrument::Backends::NullBackend.new if ENV['STATSD_ADDR'].blank?
end
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
@@ -8,6 +8,6 @@ Rails.application.config.assets.version = '1.0'
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
-Rails.application.config.assets.precompile += %w(application_public.js custom.css)
+# Rails.application.config.assets.precompile += %w(application_public.js custom.css)
Rails.application.config.assets.initialize_on_precompile = true
diff --git a/config/webpack/configuration.js b/config/webpack/configuration.js
@@ -0,0 +1,26 @@
+// Common configuration for webpacker loaded from config/webpack/paths.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 loadersDir = join(__dirname, 'loaders')
+const paths = safeLoad(readFileSync(join(configPath, 'paths.yml'), 'utf8'))[env.NODE_ENV]
+const devServer = safeLoad(readFileSync(join(configPath, 'development.server.yml'), '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://${devServer.host}:${devServer.port}/${paths.entry}/`
+const publicUrl = ifHasCDN ? `${env.CDN_HOST}/${paths.entry}/` : `/${paths.entry}/`
+const publicPath = env.NODE_ENV !== 'production' ? devServerUrl : publicUrl
+
+module.exports = {
+ devServer,
+ env,
+ paths,
+ loadersDir,
+ publicUrl,
+ publicPath
+}
diff --git a/config/webpack/development.js b/config/webpack/development.js
@@ -0,0 +1,16 @@
+// Note: You must restart bin/webpack-dev-server for changes to take effect
+
+const merge = require('webpack-merge')
+const sharedConfig = require('./shared.js')
+
+module.exports = merge(sharedConfig, {
+ devtool: 'sourcemap',
+
+ stats: {
+ errorDetails: true
+ },
+
+ output: {
+ pathinfo: true
+ }
+})
diff --git a/config/webpack/development.server.js b/config/webpack/development.server.js
@@ -0,0 +1,18 @@
+// Note: You must restart bin/webpack-dev-server for changes to take effect
+
+const { resolve } = require('path')
+const merge = require('webpack-merge')
+const devConfig = require('./development.js')
+const { devServer, publicPath, paths } = require('./configuration.js')
+
+module.exports = merge(devConfig, {
+ devServer: {
+ host: devServer.host,
+ port: devServer.port,
+ headers: { "Access-Control-Allow-Origin": "*" },
+ compress: true,
+ historyApiFallback: true,
+ contentBase: resolve(paths.output, paths.entry),
+ publicPath
+ }
+})
diff --git a/config/webpack/development.server.yml b/config/webpack/development.server.yml
@@ -0,0 +1,17 @@
+# 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/assets.js b/config/webpack/loaders/assets.js
@@ -0,0 +1,12 @@
+const { env, publicPath } = require('../configuration.js')
+
+module.exports = {
+ test: /\.(jpg|jpeg|png|gif|svg|eot|ttf|woff|woff2)$/i,
+ use: [{
+ loader: 'file-loader',
+ options: {
+ publicPath,
+ name: env.NODE_ENV === 'production' ? '[name]-[hash].[ext]' : '[name].[ext]'
+ }
+ }]
+}
diff --git a/config/webpack/loaders/babel.js b/config/webpack/loaders/babel.js
@@ -0,0 +1,5 @@
+module.exports = {
+ test: /\.js(\.erb)?$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader'
+}
diff --git a/config/webpack/loaders/coffee.js b/config/webpack/loaders/coffee.js
@@ -0,0 +1,4 @@
+module.exports = {
+ test: /\.coffee(\.erb)?$/,
+ loader: 'coffee-loader'
+}
diff --git a/config/webpack/loaders/erb.js b/config/webpack/loaders/erb.js
@@ -0,0 +1,9 @@
+module.exports = {
+ test: /\.erb$/,
+ enforce: 'pre',
+ exclude: /node_modules/,
+ loader: 'rails-erb-loader',
+ options: {
+ runner: 'bin/rails runner'
+ }
+}
diff --git a/config/webpack/loaders/sass.js b/config/webpack/loaders/sass.js
@@ -0,0 +1,14 @@
+const ExtractTextPlugin = require('extract-text-webpack-plugin')
+const { env } = require('../configuration.js')
+
+module.exports = {
+ test: /\.(scss|sass|css)$/i,
+ use: ExtractTextPlugin.extract({
+ fallback: 'style-loader',
+ use: [
+ { loader: 'css-loader', options: { minimize: env.NODE_ENV === 'production' } },
+ 'postcss-loader',
+ 'sass-loader'
+ ]
+ })
+}
diff --git a/config/webpack/paths.yml b/config/webpack/paths.yml
@@ -0,0 +1,33 @@
+# 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
+ extensions:
+ - .coffee
+ - .js
+ - .jsx
+ - .ts
+ - .vue
+ - .sass
+ - .scss
+ - .css
+ - .png
+ - .svg
+ - .gif
+ - .jpeg
+ - .jpg
+
+development:
+ <<: *default
+
+test:
+ <<: *default
+ manifest: manifest-test.json
+
+production:
+ <<: *default
diff --git a/config/webpack/production.js b/config/webpack/production.js
@@ -0,0 +1,44 @@
+// Note: You must restart bin/webpack-dev-server for changes to take effect
+
+/* eslint global-require: 0 */
+
+const webpack = require('webpack')
+const merge = require('webpack-merge')
+const CompressionPlugin = require('compression-webpack-plugin')
+const sharedConfig = require('./shared.js')
+
+module.exports = merge(sharedConfig, {
+ output: { filename: '[name]-[chunkhash].js' },
+
+ plugins: [
+ new webpack.optimize.UglifyJsPlugin({
+ compress: {
+ unused: true,
+ evaluate: true,
+ booleans: true,
+ drop_debugger: true,
+ dead_code: true,
+ pure_getters: true,
+ negate_iife: true,
+ conditionals: true,
+ loops: true,
+ cascade: true,
+ keep_fargs: false,
+ warnings: true
+ },
+
+ mangle: false,
+
+ output: {
+ comments: false
+ },
+
+ sourceMap: false
+ }),
+ new CompressionPlugin({
+ asset: '[path].gz[query]',
+ algorithm: 'gzip',
+ test: /\.(js|css|svg|eot|ttf|woff|woff2)$/
+ })
+ ]
+})
diff --git a/config/webpack/shared.js b/config/webpack/shared.js
@@ -0,0 +1,59 @@
+// Note: You must restart bin/webpack-dev-server for changes to take effect
+
+/* eslint global-require: 0 */
+/* eslint import/no-dynamic-require: 0 */
+
+const webpack = require('webpack')
+const { basename, dirname, join, relative, resolve } = require('path')
+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 extensionGlob = `**/*{${paths.extensions.join(',')}}*`
+const packPaths = sync(join(paths.source, paths.entry, extensionGlob))
+
+module.exports = {
+ entry: packPaths.reduce(
+ (map, entry) => {
+ const localMap = map
+ const namespace = relative(join(paths.source, paths.entry), dirname(entry))
+ localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry)
+ return localMap
+ }, {}
+ ),
+
+ output: {
+ filename: '[name].js',
+ chunkFilename: '[name]-[chunkhash].js',
+ path: resolve(paths.output, paths.entry),
+ publicPath
+ },
+
+ module: {
+ rules: sync(join(loadersDir, '*.js')).map(loader => require(loader))
+ },
+
+ 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 webpack.optimize.CommonsChunkPlugin({
+ name: 'vendor',
+ minChunks: ({ resource }) => /node_modules/.test(resource)
+ })
+ ],
+
+ resolve: {
+ extensions: paths.extensions,
+ modules: [
+ resolve(paths.source),
+ resolve(paths.node_modules)
+ ]
+ },
+
+ resolveLoader: {
+ modules: [paths.node_modules]
+ }
+}
diff --git a/config/webpack/test.js b/config/webpack/test.js
@@ -0,0 +1,6 @@
+// Note: You must restart bin/webpack-dev-server for changes to take effect
+
+const merge = require('webpack-merge')
+const sharedConfig = require('./shared.js')
+
+module.exports = merge(sharedConfig, {})
diff --git a/config/webpack/translationRunner.js b/config/webpack/translationRunner.js
@@ -0,0 +1,34 @@
+const manageTranslations = require('react-intl-translations-manager').default;
+
+manageTranslations({
+ messagesDirectory: 'build/messages',
+ translationsDirectory: 'app/javascript/mastodon/locales/',
+ detectDuplicateIds: false,
+ singleMessagesFile: true,
+ languages: [
+ 'ar',
+ 'en',
+ 'de',
+ 'es',
+ 'fa',
+ 'hr',
+ 'hu',
+ 'io',
+ 'it',
+ 'fr',
+ 'nl',
+ 'no',
+ 'oc',
+ 'pt',
+ 'pt-BR',
+ 'uk',
+ 'fi',
+ 'eo',
+ 'ru',
+ 'ja',
+ 'zh-HK',
+ 'zh-CN',
+ 'bg',
+ 'id',
+ ],
+})
diff --git a/docker-compose.yml b/docker-compose.yml
@@ -15,10 +15,14 @@ services:
# volumes:
# - ./redis:/data
- web:
- restart: always
+ app:
build: .
image: gargron/mastodon
+
+ web:
+ extends:
+ service: app
+ restart: always
env_file: .env.production
command: bundle exec rails s -p 3000 -b '0.0.0.0'
ports:
@@ -28,12 +32,13 @@ services:
- redis
volumes:
- ./public/assets:/mastodon/public/assets
+ - ./public/packs:/mastodon/public/packs
- ./public/system:/mastodon/public/system
streaming:
+ extends:
+ service: app
restart: always
- build: .
- image: gargron/mastodon
env_file: .env.production
command: npm run start
ports:
@@ -43,9 +48,9 @@ services:
- redis
sidekiq:
+ extends:
+ service: app
restart: always
- build: .
- image: gargron/mastodon
env_file: .env.production
command: bundle exec sidekiq -q default -q mailers -q pull -q push
depends_on:
diff --git a/package.json b/package.json
@@ -3,10 +3,11 @@
"license": "AGPL-3.0",
"scripts": {
"postversion": "git push --tags",
+ "manage:translations": "node ./config/webpack/translationRunner.js",
"start": "babel-node ./streaming/index.js --presets es2015,stage-2",
"storybook": "start-storybook -p 9001 -c storybook",
"test": "npm run test:lint && npm run test:mocha",
- "test:lint": "eslint -c .eslintrc.json --ext=js --ext=jsx app/assets/javascripts/",
+ "test:lint": "eslint -c .eslintrc.json --ext=js --ext=jsx app/javascript/",
"test:mocha": "mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.jsx"
},
"repository": {
@@ -14,39 +15,55 @@
"url": "https://github.com/tootsuite/mastodon.git"
},
"dependencies": {
- "@kadira/storybook": "^2.35.3",
+ "autoprefixer": "^6.7.7",
"axios": "^0.15.3",
"babel-cli": "^6.23.0",
+ "babel-core": "^6.24.1",
+ "babel-loader": "7.x",
+ "babel-plugin-react-intl": "^2.3.1",
"babel-plugin-react-transform": "^2.0.2",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
+ "babel-plugin-transform-react-constant-elements": "^6.23.0",
+ "babel-plugin-transform-react-inline-elements": "^6.22.0",
+ "babel-plugin-transform-react-jsx-self": "^6.22.0",
+ "babel-plugin-transform-react-jsx-source": "^6.22.0",
+ "babel-plugin-transform-react-pure-class-to-function": "^1.0.1",
+ "babel-plugin-transform-react-remove-prop-types": "^0.4.3",
+ "babel-preset-env": "^1.4.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-react": "^6.11.1",
"babel-preset-stage-2": "^6.22.0",
- "babelify": "^7.3.0",
- "browserify": "^14.1.0",
- "browserify-incremental": "^3.1.1",
"bufferutil": "^2.0.1",
- "chai": "^3.5.0",
- "chai-enzyme": "^0.6.1",
- "css-loader": "^0.26.2",
+ "coffee-loader": "^0.7.3",
+ "coffee-script": "^1.12.5",
+ "compression-webpack-plugin": "^0.4.0",
+ "css-loader": "^0.28.0",
"dotenv": "^4.0.0",
"emojione": "^2.2.7",
"emojione-picker": "^2.1.2",
- "enzyme": "^2.8.2",
"es6-promise": "^3.2.1",
"escape-html": "^1.0.3",
"eventsource": "^0.2.1",
"express": "^4.14.1",
+ "extract-text-webpack-plugin": "^2.1.0",
+ "file-loader": "^0.11.1",
+ "font-awesome": "^4.7.0",
+ "glob": "^7.1.1",
"http-link-header": "^0.8.0",
"immutable": "^3.8.1",
"intl": "^1.2.5",
- "jsdom": "^9.11.0",
- "mocha": "^3.2.0",
+ "jquery-ujs": "^1.2.2",
+ "js-yaml": "^3.8.3",
"node-sass": "^4.5.2",
"npmlog": "^4.0.2",
+ "path-complete-extname": "^0.1.0",
"pg": "^6.1.2",
+ "postcss-loader": "^1.3.3",
+ "postcss-smart-import": "^0.6.12",
+ "precss": "^1.4.0",
"prop-types": "^15.5.8",
+ "rails-erb-loader": "^5.0.0",
"react": "^15.5.4",
"react-addons-perf": "^15.4.2",
"react-addons-shallow-compare": "^15.5.2",
@@ -55,6 +72,7 @@
"react-dom": "^15.5.4",
"react-imageloader": "^2.1.0",
"react-immutable-proptypes": "^2.1.0",
+ "react-immutable-pure-component": "^0.0.4",
"react-intl": "^2.1.5",
"react-motion": "^0.4.5",
"react-notification": "^6.6.0",
@@ -72,21 +90,31 @@
"redux-immutable": "^3.1.0",
"redux-thunk": "^2.2.0",
"reselect": "^2.5.4",
- "sass-loader": "^6.0.2",
+ "sass-loader": "^6.0.3",
"sinon": "^1.17.6",
"stringz": "^0.1.2",
- "style-loader": "^0.13.2",
+ "style-loader": "^0.16.1",
"utf-8-validate": "^3.0.1",
"uuid": "^3.0.1",
- "webpack": "^2.2.1",
+ "webpack": "^2.4.1",
+ "webpack-manifest-plugin": "^1.1.0",
+ "webpack-merge": "^4.1.0",
"websocket.js": "^0.1.7",
"ws": "^2.1.0"
},
"devDependencies": {
+ "@kadira/storybook": "^2.35.3",
"babel-eslint": "^7.2.2",
+ "chai": "^3.5.0",
+ "chai-enzyme": "^0.6.1",
+ "enzyme": "^2.8.2",
"eslint": "^3.19.0",
"eslint-plugin-jsx-a11y": "^4.0.0",
- "eslint-plugin-react": "^6.10.3"
+ "eslint-plugin-react": "^6.10.3",
+ "jsdom": "^9.11.0",
+ "mocha": "^3.2.0",
+ "react-intl-translations-manager": "^5.0.0",
+ "webpack-dev-server": "^2.4.5"
},
"optionalDependencies": {
"fsevents": "*"
diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb
@@ -27,14 +27,11 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
describe 'PATCH #update_credentials' do
describe 'with valid data' do
before do
- avatar = File.read(Rails.root.join('app', 'assets', 'images', 'logo.png'))
- header = File.read(Rails.root.join('app', 'assets', 'images', 'mastodon-getting-started.png'))
-
patch :update_credentials, params: {
display_name: "Alice Isn't Dead",
note: "Hi!\n\nToot toot!",
- avatar: "data:image/png;base64,#{Base64.encode64(avatar)}",
- header: "data:image/png;base64,#{Base64.encode64(header)}",
+ avatar: fixture_file_upload('files/avatar.gif', 'image/gif'),
+ header: fixture_file_upload('files/attachment.jpg', 'image/jpeg'),
}
end
diff --git a/spec/features/log_in_spec.rb b/spec/features/log_in_spec.rb
@@ -11,6 +11,6 @@ feature "Log in" do
fill_in "user_password", with: password
click_on "Log in"
- expect(page).to have_css "div.app-holder[data-react-class=Mastodon]"
+ expect(page).to have_css "div.app-holder"
end
end
diff --git a/spec/javascript/components/avatar.test.jsx b/spec/javascript/components/avatar.test.jsx
@@ -1,7 +1,7 @@
import { expect } from 'chai';
import { render } from 'enzyme';
-import Avatar from '../../../app/assets/javascripts/components/components/avatar'
+import Avatar from '../../../app/javascript/mastodon/components/avatar'
describe('<Avatar />', () => {
const src = '/path/to/image.jpg';
diff --git a/spec/javascript/components/button.test.jsx b/spec/javascript/components/button.test.jsx
@@ -2,7 +2,7 @@ import { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon';
-import Button from '../../../app/assets/javascripts/components/components/button';
+import Button from '../../../app/javascript/mastodon/components/button';
describe('<Button />', () => {
it('renders a button element', () => {
diff --git a/spec/javascript/components/display_name.test.jsx b/spec/javascript/components/display_name.test.jsx
@@ -2,7 +2,7 @@ import { expect } from 'chai';
import { render } from 'enzyme';
import Immutable from 'immutable';
-import DisplayName from '../../../app/assets/javascripts/components/components/display_name'
+import DisplayName from '../../../app/javascript/mastodon/components/display_name'
describe('<DisplayName />', () => {
it('renders display name + account name', () => {
diff --git a/spec/javascript/components/dropdown_menu.test.jsx b/spec/javascript/components/dropdown_menu.test.jsx
@@ -2,7 +2,7 @@ import { expect } from 'chai';
import { shallow, mount } from 'enzyme';
import sinon from 'sinon';
-import DropdownMenu from '../../../app/assets/javascripts/components/components/dropdown_menu';
+import DropdownMenu from '../../../app/javascript/mastodon/components/dropdown_menu';
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
describe('<DropdownMenu />', () => {
diff --git a/spec/javascript/components/features/ui/components/column.test.jsx b/spec/javascript/components/features/ui/components/column.test.jsx
@@ -2,8 +2,8 @@ import { expect } from 'chai';
import { mount } from 'enzyme';
import sinon from 'sinon';
-import Column from '../../../../../../app/assets/javascripts/components/features/ui/components/column';
-import ColumnHeader from '../../../../../../app/assets/javascripts/components/features/ui/components/column_header';
+import Column from '../../../../../../app/javascript/mastodon/features/ui/components/column';
+import ColumnHeader from '../../../../../../app/javascript/mastodon/features/ui/components/column_header';
describe('<Column />', () => {
describe('<ColumnHeader /> click handler', () => {
diff --git a/spec/javascript/components/loading_indicator.test.jsx b/spec/javascript/components/loading_indicator.test.jsx
@@ -1,7 +1,7 @@
import { expect } from 'chai';
import { shallow } from 'enzyme';
-import LoadingIndicator from '../../../app/assets/javascripts/components/components/loading_indicator'
+import LoadingIndicator from '../../../app/javascript/mastodon/components/loading_indicator'
describe('<LoadingIndicator />', () => {
diff --git a/yarn.lock b/yarn.lock
@@ -99,20 +99,6 @@
webpack-dev-middleware "^1.6.0"
webpack-hot-middleware "^2.13.2"
-JSONStream@^0.10.0:
- version "0.10.0"
- resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-0.10.0.tgz#74349d0d89522b71f30f0a03ff9bd20ca6f12ac0"
- dependencies:
- jsonparse "0.0.5"
- through ">=2.2.7 <3"
-
-JSONStream@^1.0.3:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.2.1.tgz#32aa5790e799481083b49b4b7fa94e23bae69bf9"
- dependencies:
- jsonparse "^1.2.0"
- through ">=2.2.7 <3"
-
abab@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d"
@@ -146,14 +132,6 @@ acorn-jsx@^3.0.0:
dependencies:
acorn "^3.0.4"
-acorn@^1.0.3:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014"
-
-acorn@^2.7.0:
- version "2.7.0"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7"
-
acorn@^3.0.0, acorn@^3.0.4:
version "3.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
@@ -162,7 +140,7 @@ acorn@^4.0.3, acorn@^4.0.4:
version "4.0.11"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0"
-acorn@^5.0.1:
+acorn@^5.0.0, acorn@^5.0.1:
version "5.0.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d"
@@ -183,7 +161,7 @@ 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@^4.7.0:
+ajv@^4.11.2, ajv@^4.7.0:
version "4.11.3"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.3.tgz#ce30bdb90d1254f762c75af915fb3a63e7183d22"
dependencies:
@@ -222,6 +200,10 @@ ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+any-promise@^0.1.0, any-promise@~0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-0.1.0.tgz#830b680aa7e56f33451d4b049f3bd8044498ee27"
+
anymatch@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507"
@@ -270,10 +252,6 @@ array-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
-array-filter@~0.0.0:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec"
-
array-find-index@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
@@ -289,14 +267,6 @@ array-includes@^3.0.2:
define-properties "^1.1.2"
es-abstract "^1.5.0"
-array-map@~0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
-
-array-reduce@~0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
-
array-union@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
@@ -346,7 +316,7 @@ assert-plus@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
-assert@^1.1.1, assert@^1.4.0:
+assert@^1.1.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
dependencies:
@@ -364,12 +334,6 @@ ast-types@0.9.5:
version "0.9.5"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.5.tgz#1a660a09945dbceb1f9c9cbb715002617424e04a"
-astw@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/astw/-/astw-2.0.0.tgz#08121ac8288d35611c0ceec663f6cd545604897d"
- dependencies:
- acorn "^1.0.3"
-
async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@@ -378,6 +342,10 @@ async-foreach@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
+async@0.2.x, async@~0.2.6:
+ version "0.2.10"
+ resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
+
async@^0.9.0:
version "0.9.2"
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
@@ -392,10 +360,6 @@ async@^2.1.2, async@^2.1.5:
dependencies:
lodash "^4.14.0"
-async@~0.2.6:
- version "0.2.10"
- resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
-
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -404,15 +368,15 @@ autobind-decorator@1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-1.3.3.tgz#41b1915ee742859c872b5d1743d10745254b83b4"
-autoprefixer@^6.3.1, autoprefixer@^6.3.7:
- version "6.5.0"
- resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.5.0.tgz#910de0aa0f22af4c7d50367cbc9d4d412945162f"
+autoprefixer@^6.3.1, autoprefixer@^6.3.7, autoprefixer@^6.7.7:
+ version "6.7.7"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014"
dependencies:
- browserslist "~1.4.0"
- caniuse-db "^1.0.30000540"
+ browserslist "^1.7.6"
+ caniuse-db "^1.0.30000634"
normalize-range "^0.1.2"
num2fraction "^1.2.2"
- postcss "^5.2.2"
+ postcss "^5.2.16"
postcss-value-parser "^3.2.3"
aws-sign2@~0.6.0:
@@ -450,15 +414,7 @@ babel-cli@^6.23.0:
optionalDependencies:
chokidar "^1.6.1"
-babel-code-frame@^6.11.0:
- version "6.16.0"
- resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.16.0.tgz#f90e60da0862909d3ce098733b5d3987c97cb8de"
- dependencies:
- chalk "^1.1.0"
- esutils "^2.0.2"
- js-tokens "^2.0.0"
-
-babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
+babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
dependencies:
@@ -466,43 +422,19 @@ babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
esutils "^2.0.2"
js-tokens "^3.0.0"
-babel-core@^6.0.14, babel-core@^6.23.0:
- version "6.23.1"
- resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.23.1.tgz#c143cb621bb2f621710c220c5d579d15b8a442df"
+babel-core@^6.11.4, babel-core@^6.23.0, babel-core@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.24.1.tgz#8c428564dce1e1f41fb337ec34f4c3b022b5ad83"
dependencies:
babel-code-frame "^6.22.0"
- babel-generator "^6.23.0"
- babel-helpers "^6.23.0"
+ babel-generator "^6.24.1"
+ babel-helpers "^6.24.1"
babel-messages "^6.23.0"
- babel-register "^6.23.0"
+ babel-register "^6.24.1"
babel-runtime "^6.22.0"
- babel-template "^6.23.0"
- babel-traverse "^6.23.1"
- babel-types "^6.23.0"
- babylon "^6.11.0"
- convert-source-map "^1.1.0"
- debug "^2.1.1"
- json5 "^0.5.0"
- lodash "^4.2.0"
- minimatch "^3.0.2"
- path-is-absolute "^1.0.0"
- private "^0.1.6"
- slash "^1.0.0"
- source-map "^0.5.0"
-
-babel-core@^6.11.4:
- version "6.22.1"
- resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.22.1.tgz#9c5fd658ba1772d28d721f6d25d968fc7ae21648"
- dependencies:
- babel-code-frame "^6.22.0"
- babel-generator "^6.22.0"
- babel-helpers "^6.22.0"
- babel-messages "^6.22.0"
- babel-register "^6.22.0"
- babel-runtime "^6.22.0"
- babel-template "^6.22.0"
- babel-traverse "^6.22.1"
- babel-types "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
babylon "^6.11.0"
convert-source-map "^1.1.0"
debug "^2.1.1"
@@ -523,25 +455,13 @@ babel-eslint@^7.2.2:
babel-types "^6.23.0"
babylon "^6.16.1"
-babel-generator@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.22.0.tgz#d642bf4961911a8adc7c692b0c9297f325cda805"
- dependencies:
- babel-messages "^6.22.0"
- babel-runtime "^6.22.0"
- babel-types "^6.22.0"
- detect-indent "^4.0.0"
- jsesc "^1.3.0"
- lodash "^4.2.0"
- source-map "^0.5.0"
-
-babel-generator@^6.23.0:
- version "6.23.0"
- resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.23.0.tgz#6b8edab956ef3116f79d8c84c5a3c05f32a74bc5"
+babel-generator@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.24.1.tgz#e715f486c58ded25649d888944d52aa07c5d9497"
dependencies:
babel-messages "^6.23.0"
babel-runtime "^6.22.0"
- babel-types "^6.23.0"
+ babel-types "^6.24.1"
detect-indent "^4.0.0"
jsesc "^1.3.0"
lodash "^4.2.0"
@@ -573,22 +493,22 @@ babel-helper-builder-react-jsx@^6.8.0:
esutils "^2.0.0"
lodash "^4.2.0"
-babel-helper-call-delegate@^6.22.0, babel-helper-call-delegate@^6.8.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.22.0.tgz#119921b56120f17e9dae3f74b4f5cc7bcc1b37ef"
+babel-helper-call-delegate@^6.24.1, babel-helper-call-delegate@^6.8.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
dependencies:
- babel-helper-hoist-variables "^6.22.0"
+ babel-helper-hoist-variables "^6.24.1"
babel-runtime "^6.22.0"
- babel-traverse "^6.22.0"
- babel-types "^6.22.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
-babel-helper-define-map@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.22.0.tgz#9544e9502b2d6dfe7d00ff60e82bd5a7a89e95b7"
+babel-helper-define-map@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz#7a9747f258d8947d32d515f6aa1c7bd02204a080"
dependencies:
- babel-helper-function-name "^6.22.0"
+ babel-helper-function-name "^6.24.1"
babel-runtime "^6.22.0"
- babel-types "^6.22.0"
+ babel-types "^6.24.1"
lodash "^4.2.0"
babel-helper-explode-assignable-expression@^6.22.0:
@@ -608,36 +528,40 @@ babel-helper-explode-class@^6.22.0:
babel-traverse "^6.22.0"
babel-types "^6.22.0"
-babel-helper-function-name@^6.22.0, babel-helper-function-name@^6.8.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.22.0.tgz#51f1bdc4bb89b15f57a9b249f33d742816dcbefc"
+babel-helper-function-name@^6.22.0, babel-helper-function-name@^6.24.1, babel-helper-function-name@^6.8.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
dependencies:
- babel-helper-get-function-arity "^6.22.0"
+ babel-helper-get-function-arity "^6.24.1"
babel-runtime "^6.22.0"
- babel-template "^6.22.0"
- babel-traverse "^6.22.0"
- babel-types "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
-babel-helper-get-function-arity@^6.22.0, babel-helper-get-function-arity@^6.8.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.22.0.tgz#0beb464ad69dc7347410ac6ade9f03a50634f5ce"
+babel-helper-get-function-arity@^6.24.1, babel-helper-get-function-arity@^6.8.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d"
dependencies:
babel-runtime "^6.22.0"
- babel-types "^6.22.0"
+ babel-types "^6.24.1"
-babel-helper-hoist-variables@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.22.0.tgz#3eacbf731d80705845dd2e9718f600cfb9b4ba72"
+babel-helper-hoist-variables@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76"
dependencies:
babel-runtime "^6.22.0"
- babel-types "^6.22.0"
+ babel-types "^6.24.1"
-babel-helper-optimise-call-expression@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.22.0.tgz#f8d5d4b40a6e2605a6a7f9d537b581bea3756d15"
+babel-helper-is-react-class@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-is-react-class/-/babel-helper-is-react-class-1.0.0.tgz#ef6f3678b05c76dbdeedadead7af98c2724d8431"
+
+babel-helper-optimise-call-expression@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257"
dependencies:
babel-runtime "^6.22.0"
- babel-types "^6.22.0"
+ babel-types "^6.24.1"
babel-helper-regex@^6.22.0:
version "6.22.0"
@@ -657,30 +581,31 @@ babel-helper-remap-async-to-generator@^6.22.0:
babel-traverse "^6.22.0"
babel-types "^6.22.0"
-babel-helper-replace-supers@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.22.0.tgz#1fcee2270657548908c34db16bcc345f9850cf42"
+babel-helper-replace-supers@^6.22.0, babel-helper-replace-supers@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a"
dependencies:
- babel-helper-optimise-call-expression "^6.22.0"
- babel-messages "^6.22.0"
+ babel-helper-optimise-call-expression "^6.24.1"
+ babel-messages "^6.23.0"
babel-runtime "^6.22.0"
- babel-template "^6.22.0"
- babel-traverse "^6.22.0"
- babel-types "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
-babel-helpers@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.22.0.tgz#d275f55f2252b8101bff07bc0c556deda657392c"
+babel-helpers@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
dependencies:
babel-runtime "^6.22.0"
- babel-template "^6.22.0"
+ babel-template "^6.24.1"
-babel-helpers@^6.23.0:
- version "6.23.0"
- resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.23.0.tgz#4f8f2e092d0b6a8808a4bde79c27f1e2ecf0d992"
+babel-loader@7.x:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.0.0.tgz#2e43a66bee1fff4470533d0402c8a4532fafbaf7"
dependencies:
- babel-runtime "^6.22.0"
- babel-template "^6.23.0"
+ find-cache-dir "^0.1.1"
+ loader-utils "^1.0.2"
+ mkdirp "^0.5.1"
babel-loader@^6.2.4:
version "6.2.5"
@@ -690,12 +615,6 @@ babel-loader@^6.2.4:
mkdirp "^0.5.1"
object-assign "^4.0.1"
-babel-messages@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.22.0.tgz#36066a214f1217e4ed4164867669ecb39e3ea575"
- dependencies:
- babel-runtime "^6.22.0"
-
babel-messages@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
@@ -716,6 +635,14 @@ babel-plugin-react-docgen@^1.4.2:
lodash "4.x.x"
react-docgen "^2.12.1"
+babel-plugin-react-intl@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-react-intl/-/babel-plugin-react-intl-2.3.1.tgz#3d43912e824da005e08e8e8239d5ba784374bb00"
+ dependencies:
+ babel-runtime "^6.2.0"
+ intl-messageformat-parser "^1.2.0"
+ mkdirp "^0.5.1"
+
babel-plugin-react-transform@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/babel-plugin-react-transform/-/babel-plugin-react-transform-2.0.2.tgz#515bbfa996893981142d90b1f9b1635de2995109"
@@ -825,29 +752,29 @@ babel-plugin-transform-es2015-block-scoped-functions@^6.22.0, babel-plugin-trans
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-transform-es2015-block-scoping@^6.22.0, babel-plugin-transform-es2015-block-scoping@^6.6.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.22.0.tgz#00d6e3a0bebdcfe7536b9d653b44a9141e63e47e"
+babel-plugin-transform-es2015-block-scoping@^6.22.0, babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es2015-block-scoping@^6.6.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz#76c295dc3a4741b1665adfd3167215dcff32a576"
dependencies:
babel-runtime "^6.22.0"
- babel-template "^6.22.0"
- babel-traverse "^6.22.0"
- babel-types "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
lodash "^4.2.0"
-babel-plugin-transform-es2015-classes@^6.22.0, babel-plugin-transform-es2015-classes@^6.6.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.22.0.tgz#54d44998fd823d9dca15292324161c331c1b6f14"
+babel-plugin-transform-es2015-classes@^6.22.0, babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-classes@^6.6.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
dependencies:
- babel-helper-define-map "^6.22.0"
- babel-helper-function-name "^6.22.0"
- babel-helper-optimise-call-expression "^6.22.0"
- babel-helper-replace-supers "^6.22.0"
- babel-messages "^6.22.0"
+ babel-helper-define-map "^6.24.1"
+ babel-helper-function-name "^6.24.1"
+ babel-helper-optimise-call-expression "^6.24.1"
+ babel-helper-replace-supers "^6.24.1"
+ babel-messages "^6.23.0"
babel-runtime "^6.22.0"
- babel-template "^6.22.0"
- babel-traverse "^6.22.0"
- babel-types "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.3.13:
version "6.22.0"
@@ -862,9 +789,9 @@ babel-plugin-transform-es2015-destructuring@6.16.0:
dependencies:
babel-runtime "^6.9.0"
-babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.6.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.22.0.tgz#8e0af2f885a0b2cf999d47c4c1dd23ce88cfa4c6"
+babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.23.0, babel-plugin-transform-es2015-destructuring@^6.6.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
dependencies:
babel-runtime "^6.22.0"
@@ -875,9 +802,9 @@ babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2
babel-runtime "^6.22.0"
babel-types "^6.22.0"
-babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.6.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.22.0.tgz#180467ad63aeea592a1caeee4bf1c8b3e2616265"
+babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.23.0, babel-plugin-transform-es2015-for-of@^6.6.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
dependencies:
babel-runtime "^6.22.0"
@@ -895,7 +822,7 @@ babel-plugin-transform-es2015-literals@^6.22.0, babel-plugin-transform-es2015-li
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.8.0:
+babel-plugin-transform-es2015-modules-amd@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.22.0.tgz#bf69cd34889a41c33d90dfb740e0091ccff52f21"
dependencies:
@@ -903,30 +830,38 @@ babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015
babel-runtime "^6.22.0"
babel-template "^6.22.0"
-babel-plugin-transform-es2015-modules-commonjs@^6.22.0, babel-plugin-transform-es2015-modules-commonjs@^6.6.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.22.0.tgz#6ca04e22b8e214fb50169730657e7a07dc941145"
+babel-plugin-transform-es2015-modules-amd@^6.24.1, babel-plugin-transform-es2015-modules-amd@^6.8.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
dependencies:
- babel-plugin-transform-strict-mode "^6.22.0"
+ babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
babel-runtime "^6.22.0"
- babel-template "^6.22.0"
- babel-types "^6.22.0"
+ babel-template "^6.24.1"
-babel-plugin-transform-es2015-modules-systemjs@^6.12.0, babel-plugin-transform-es2015-modules-systemjs@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.22.0.tgz#810cd0cd025a08383b84236b92c6e31f88e644ad"
+babel-plugin-transform-es2015-modules-commonjs@^6.22.0, babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1, babel-plugin-transform-es2015-modules-commonjs@^6.6.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz#d3e310b40ef664a36622200097c6d440298f2bfe"
dependencies:
- babel-helper-hoist-variables "^6.22.0"
+ babel-plugin-transform-strict-mode "^6.24.1"
babel-runtime "^6.22.0"
- babel-template "^6.22.0"
+ babel-template "^6.24.1"
+ babel-types "^6.24.1"
-babel-plugin-transform-es2015-modules-umd@^6.12.0, babel-plugin-transform-es2015-modules-umd@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.22.0.tgz#60d0ba3bd23258719c64391d9bf492d648dc0fae"
+babel-plugin-transform-es2015-modules-systemjs@^6.12.0, babel-plugin-transform-es2015-modules-systemjs@^6.22.0, babel-plugin-transform-es2015-modules-systemjs@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
dependencies:
- babel-plugin-transform-es2015-modules-amd "^6.22.0"
+ babel-helper-hoist-variables "^6.24.1"
babel-runtime "^6.22.0"
- babel-template "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-modules-umd@^6.12.0, babel-plugin-transform-es2015-modules-umd@^6.22.0, babel-plugin-transform-es2015-modules-umd@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
+ dependencies:
+ babel-plugin-transform-es2015-modules-amd "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.3.13:
version "6.22.0"
@@ -946,16 +881,16 @@ babel-plugin-transform-es2015-parameters@6.17.0:
babel-traverse "^6.16.0"
babel-types "^6.16.0"
-babel-plugin-transform-es2015-parameters@^6.22.0, babel-plugin-transform-es2015-parameters@^6.6.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.22.0.tgz#57076069232019094f27da8c68bb7162fe208dbb"
+babel-plugin-transform-es2015-parameters@^6.22.0, babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-parameters@^6.6.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
dependencies:
- babel-helper-call-delegate "^6.22.0"
- babel-helper-get-function-arity "^6.22.0"
+ babel-helper-call-delegate "^6.24.1"
+ babel-helper-get-function-arity "^6.24.1"
babel-runtime "^6.22.0"
- babel-template "^6.22.0"
- babel-traverse "^6.22.0"
- babel-types "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.3.13:
version "6.22.0"
@@ -984,9 +919,9 @@ babel-plugin-transform-es2015-template-literals@^6.22.0, babel-plugin-transform-
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.6.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.22.0.tgz#87faf2336d3b6a97f68c4d906b0cd0edeae676e1"
+babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.23.0, babel-plugin-transform-es2015-typeof-symbol@^6.6.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
dependencies:
babel-runtime "^6.22.0"
@@ -1033,26 +968,52 @@ babel-plugin-transform-react-constant-elements@6.9.1:
dependencies:
babel-runtime "^6.9.1"
+babel-plugin-transform-react-constant-elements@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-constant-elements/-/babel-plugin-transform-react-constant-elements-6.23.0.tgz#2f119bf4d2cdd45eb9baaae574053c604f6147dd"
+ dependencies:
+ babel-runtime "^6.22.0"
+
babel-plugin-transform-react-display-name@^6.3.13:
version "6.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.8.0.tgz#f7a084977383d728bdbdc2835bba0159577f660e"
dependencies:
babel-runtime "^6.0.0"
-babel-plugin-transform-react-jsx-self@6.11.0, babel-plugin-transform-react-jsx-self@^6.11.0:
+babel-plugin-transform-react-inline-elements@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-inline-elements/-/babel-plugin-transform-react-inline-elements-6.22.0.tgz#6687211a32b49a52f22c573a2b5504a25ef17c53"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-react-jsx-self@6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.11.0.tgz#605c9450c1429f97a930f7e1dfe3f0d9d0dbd0f4"
dependencies:
babel-plugin-syntax-jsx "^6.8.0"
babel-runtime "^6.9.0"
-babel-plugin-transform-react-jsx-source@6.9.0, babel-plugin-transform-react-jsx-source@^6.3.13:
+babel-plugin-transform-react-jsx-self@^6.11.0, babel-plugin-transform-react-jsx-self@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e"
+ dependencies:
+ babel-plugin-syntax-jsx "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-react-jsx-source@6.9.0:
version "6.9.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.9.0.tgz#af684a05c2067a86e0957d4f343295ccf5dccf00"
dependencies:
babel-plugin-syntax-jsx "^6.8.0"
babel-runtime "^6.9.0"
+babel-plugin-transform-react-jsx-source@^6.22.0, babel-plugin-transform-react-jsx-source@^6.3.13:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6"
+ dependencies:
+ babel-plugin-syntax-jsx "^6.8.0"
+ babel-runtime "^6.22.0"
+
babel-plugin-transform-react-jsx@^6.3.13:
version "6.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.8.0.tgz#94759942f70af18c617189aa7f3593f1644a71ab"
@@ -1061,6 +1022,16 @@ babel-plugin-transform-react-jsx@^6.3.13:
babel-plugin-syntax-jsx "^6.8.0"
babel-runtime "^6.0.0"
+babel-plugin-transform-react-pure-class-to-function@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-pure-class-to-function/-/babel-plugin-transform-react-pure-class-to-function-1.0.1.tgz#32a649c97d653250b419cfd1489331b0290d9ee4"
+ dependencies:
+ babel-helper-is-react-class "^1.0.0"
+
+babel-plugin-transform-react-remove-prop-types@^0.4.3:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.3.tgz#fdff5c12933efc3ac979817adcdc0f612c5e3563"
+
babel-plugin-transform-regenerator@6.16.1:
version "6.16.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.16.1.tgz#a75de6b048a14154aae14b0122756c5bed392f59"
@@ -1081,12 +1052,12 @@ babel-plugin-transform-runtime@6.15.0:
dependencies:
babel-runtime "^6.9.0"
-babel-plugin-transform-strict-mode@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.22.0.tgz#e008df01340fdc87e959da65991b7e05970c8c7c"
+babel-plugin-transform-strict-mode@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758"
dependencies:
babel-runtime "^6.22.0"
- babel-types "^6.22.0"
+ babel-types "^6.24.1"
babel-polyfill@^6.23.0:
version "6.23.0"
@@ -1129,6 +1100,40 @@ babel-preset-env@0.0.6:
babel-plugin-transform-regenerator "^6.6.0"
browserslist "^1.4.0"
+babel-preset-env@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.4.0.tgz#c8e02a3bcc7792f23cded68e0355b9d4c28f0f7a"
+ dependencies:
+ babel-plugin-check-es2015-constants "^6.22.0"
+ babel-plugin-syntax-trailing-function-commas "^6.22.0"
+ babel-plugin-transform-async-to-generator "^6.22.0"
+ babel-plugin-transform-es2015-arrow-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoping "^6.23.0"
+ babel-plugin-transform-es2015-classes "^6.23.0"
+ babel-plugin-transform-es2015-computed-properties "^6.22.0"
+ babel-plugin-transform-es2015-destructuring "^6.23.0"
+ babel-plugin-transform-es2015-duplicate-keys "^6.22.0"
+ babel-plugin-transform-es2015-for-of "^6.23.0"
+ babel-plugin-transform-es2015-function-name "^6.22.0"
+ babel-plugin-transform-es2015-literals "^6.22.0"
+ babel-plugin-transform-es2015-modules-amd "^6.22.0"
+ babel-plugin-transform-es2015-modules-commonjs "^6.23.0"
+ babel-plugin-transform-es2015-modules-systemjs "^6.23.0"
+ babel-plugin-transform-es2015-modules-umd "^6.23.0"
+ babel-plugin-transform-es2015-object-super "^6.22.0"
+ babel-plugin-transform-es2015-parameters "^6.23.0"
+ babel-plugin-transform-es2015-shorthand-properties "^6.22.0"
+ babel-plugin-transform-es2015-spread "^6.22.0"
+ babel-plugin-transform-es2015-sticky-regex "^6.22.0"
+ babel-plugin-transform-es2015-template-literals "^6.22.0"
+ babel-plugin-transform-es2015-typeof-symbol "^6.23.0"
+ babel-plugin-transform-es2015-unicode-regex "^6.22.0"
+ babel-plugin-transform-exponentiation-operator "^6.22.0"
+ babel-plugin-transform-regenerator "^6.22.0"
+ browserslist "^1.4.0"
+ invariant "^2.2.2"
+
babel-preset-es2015@^6.16.0, babel-preset-es2015@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.22.0.tgz#af5a98ecb35eb8af764ad8a5a05eb36dc4386835"
@@ -1228,7 +1233,7 @@ babel-preset-stage-3@^6.22.0:
babel-plugin-transform-exponentiation-operator "^6.22.0"
babel-plugin-transform-object-rest-spread "^6.22.0"
-babel-register@^6.22.0, babel-register@^6.23.0:
+babel-register@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.23.0.tgz#c9aa3d4cca94b51da34826c4a0f9e08145d74ff3"
dependencies:
@@ -1240,6 +1245,18 @@ babel-register@^6.22.0, babel-register@^6.23.0:
mkdirp "^0.5.1"
source-map-support "^0.4.2"
+babel-register@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.24.1.tgz#7e10e13a2f71065bdfad5a1787ba45bca6ded75f"
+ dependencies:
+ babel-core "^6.24.1"
+ babel-runtime "^6.22.0"
+ core-js "^2.4.0"
+ home-or-tmp "^2.0.0"
+ lodash "^4.2.0"
+ mkdirp "^0.5.1"
+ source-map-support "^0.4.2"
+
babel-runtime@6.11.6:
version "6.11.6"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.11.6.tgz#6db707fef2d49c49bfa3cb64efdb436b518b8222"
@@ -1247,53 +1264,53 @@ babel-runtime@6.11.6:
core-js "^2.4.0"
regenerator-runtime "^0.9.5"
-babel-runtime@6.x.x, babel-runtime@^6.0.0, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.5.0, babel-runtime@^6.9.0, babel-runtime@^6.9.1, babel-runtime@^6.9.2:
+babel-runtime@6.x.x, babel-runtime@^6.0.0, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.23.0, babel-runtime@^6.5.0, babel-runtime@^6.9.0, babel-runtime@^6.9.1, babel-runtime@^6.9.2:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b"
+ dependencies:
+ core-js "^2.4.0"
+ regenerator-runtime "^0.10.0"
+
+babel-runtime@^6.2.0, babel-runtime@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.22.0.tgz#1cf8b4ac67c77a4ddb0db2ae1f74de52ac4ca611"
dependencies:
core-js "^2.4.0"
regenerator-runtime "^0.10.0"
-babel-template@^6.16.0, babel-template@^6.22.0, babel-template@^6.23.0, babel-template@^6.3.0:
- version "6.23.0"
- resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.23.0.tgz#04d4f270adbb3aa704a8143ae26faa529238e638"
+babel-template@^6.16.0, babel-template@^6.22.0, babel-template@^6.24.1, babel-template@^6.3.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.24.1.tgz#04ae514f1f93b3a2537f2a0f60a5a45fb8308333"
dependencies:
babel-runtime "^6.22.0"
- babel-traverse "^6.23.0"
- babel-types "^6.23.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
babylon "^6.11.0"
lodash "^4.2.0"
-babel-traverse@^6.16.0, babel-traverse@^6.22.0, babel-traverse@^6.22.1, babel-traverse@^6.23.0, babel-traverse@^6.23.1:
- version "6.23.1"
- resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.23.1.tgz#d3cb59010ecd06a97d81310065f966b699e14f48"
+babel-traverse@^6.16.0, babel-traverse@^6.22.0, babel-traverse@^6.23.1, babel-traverse@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.24.1.tgz#ab36673fd356f9a0948659e7b338d5feadb31695"
dependencies:
babel-code-frame "^6.22.0"
babel-messages "^6.23.0"
babel-runtime "^6.22.0"
- babel-types "^6.23.0"
+ babel-types "^6.24.1"
babylon "^6.15.0"
debug "^2.2.0"
globals "^9.0.0"
invariant "^2.2.0"
lodash "^4.2.0"
-babel-types@^6.16.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23.0, babel-types@^6.9.0:
- version "6.23.0"
- resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.23.0.tgz#bb17179d7538bad38cd0c9e115d340f77e7e9acf"
+babel-types@^6.16.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.9.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.24.1.tgz#a136879dc15b3606bda0d90c1fc74304c2ff0975"
dependencies:
babel-runtime "^6.22.0"
esutils "^2.0.2"
lodash "^4.2.0"
to-fast-properties "^1.0.1"
-babelify@^7.3.0:
- version "7.3.0"
- resolved "https://registry.yarnpkg.com/babelify/-/babelify-7.3.0.tgz#aa56aede7067fd7bd549666ee16dc285087e88e5"
- dependencies:
- babel-core "^6.0.14"
- object-assign "^4.0.0"
-
babylon@^6.11.0, babylon@^6.15.0, babylon@^6.16.1:
version "6.16.1"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3"
@@ -1308,18 +1325,26 @@ backoff@^2.4.1:
dependencies:
precond "0.2"
+balanced-match@0.1.0, balanced-match@~0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.1.0.tgz#b504bd05869b39259dd0c5efc35d843176dccc4a"
+
+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:
version "0.4.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
-balanced-match@~0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.1.0.tgz#b504bd05869b39259dd0c5efc35d843176dccc4a"
-
base64-js@^1.0.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
+batch@0.5.3:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/batch/-/batch-0.5.3.tgz#3f3414f380321743bfc1042f9a83ff1d5824d464"
+
bcrypt-pbkdf@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz#3ca76b85241c7170bf7d9703e7b9aa74630040d4"
@@ -1338,7 +1363,7 @@ bindings@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11"
-bl@^1.0.0, bl@~1.1.2:
+bl@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/bl/-/bl-1.1.2.tgz#fdca871a99713aa00d19e3bbba41c44787a65398"
dependencies:
@@ -1383,22 +1408,6 @@ brorand@^1.0.1:
version "1.0.6"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.0.6.tgz#4028706b915f91f7b349a2e0bf3c376039d216e5"
-browser-pack@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.0.1.tgz#779887c792eaa1f64a46a22c8f1051cdcd96755f"
- dependencies:
- JSONStream "^1.0.3"
- combine-source-map "~0.7.1"
- defined "^1.0.0"
- through2 "^2.0.0"
- umd "^3.0.0"
-
-browser-resolve@^1.11.0, browser-resolve@^1.7.0:
- version "1.11.2"
- resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce"
- dependencies:
- resolve "1.1.7"
-
browser-stdout@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
@@ -1419,14 +1428,6 @@ browserify-aes@^1.0.0, browserify-aes@^1.0.4:
evp_bytestokey "^1.0.0"
inherits "^2.0.1"
-browserify-cache-api@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/browserify-cache-api/-/browserify-cache-api-3.0.1.tgz#96247e853f068fd6e0d45cc73f0bb2cd9778ef02"
- dependencies:
- async "^1.5.2"
- through2 "^2.0.0"
- xtend "^4.0.0"
-
browserify-cipher@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a"
@@ -1443,15 +1444,6 @@ browserify-des@^1.0.0:
des.js "^1.0.0"
inherits "^2.0.1"
-browserify-incremental@^3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/browserify-incremental/-/browserify-incremental-3.1.1.tgz#0713cb7587247a632a9f08cf1bd169b878b62a8a"
- dependencies:
- JSONStream "^0.10.0"
- browserify-cache-api "^3.0.0"
- through2 "^2.0.0"
- xtend "^4.0.0"
-
browserify-rsa@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
@@ -1471,69 +1463,18 @@ browserify-sign@^4.0.0:
inherits "^2.0.1"
parse-asn1 "^5.0.0"
-browserify-zlib@^0.1.4, browserify-zlib@~0.1.2:
+browserify-zlib@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
dependencies:
pako "~0.2.0"
-browserify@^14.1.0:
- version "14.1.0"
- resolved "https://registry.yarnpkg.com/browserify/-/browserify-14.1.0.tgz#0508cc1e7bf4c152312c2fa523e676c0b0b92311"
- dependencies:
- JSONStream "^1.0.3"
- assert "^1.4.0"
- browser-pack "^6.0.1"
- browser-resolve "^1.11.0"
- browserify-zlib "~0.1.2"
- buffer "^5.0.2"
- cached-path-relative "^1.0.0"
- concat-stream "~1.5.1"
- console-browserify "^1.1.0"
- constants-browserify "~1.0.0"
- crypto-browserify "^3.0.0"
- defined "^1.0.0"
- deps-sort "^2.0.0"
- domain-browser "~1.1.0"
- duplexer2 "~0.1.2"
- events "~1.1.0"
- glob "^7.1.0"
- has "^1.0.0"
- htmlescape "^1.1.0"
- https-browserify "~0.0.0"
- inherits "~2.0.1"
- insert-module-globals "^7.0.0"
- labeled-stream-splicer "^2.0.0"
- module-deps "^4.0.8"
- os-browserify "~0.1.1"
- parents "^1.0.1"
- path-browserify "~0.0.0"
- process "~0.11.0"
- punycode "^1.3.2"
- querystring-es3 "~0.2.0"
- read-only-stream "^2.0.0"
- readable-stream "^2.0.2"
- resolve "^1.1.4"
- shasum "^1.0.0"
- shell-quote "^1.6.1"
- stream-browserify "^2.0.0"
- stream-http "^2.0.0"
- string_decoder "~0.10.0"
- subarg "^1.0.0"
- syntax-error "^1.1.1"
- through2 "^2.0.0"
- timers-browserify "^1.0.1"
- tty-browserify "~0.0.0"
- url "~0.11.0"
- util "~0.10.1"
- vm-browserify "~0.0.1"
- xtend "^4.0.0"
-
-browserslist@^1.4.0, browserslist@~1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.4.0.tgz#9cfdcf5384d9158f5b70da2aa00b30e8ff019049"
+browserslist@^1.4.0, browserslist@^1.7.6:
+ version "1.7.7"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9"
dependencies:
- caniuse-db "^1.0.30000539"
+ caniuse-db "^1.0.30000639"
+ electron-to-chromium "^1.2.7"
buffer-shims@^1.0.0:
version "1.0.0"
@@ -1555,13 +1496,6 @@ buffer@^4.3.0, buffer@^4.9.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
-buffer@^5.0.2:
- version "5.0.5"
- resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.0.5.tgz#35c9393244a90aff83581063d16f0882cecc9418"
- dependencies:
- base64-js "^1.0.2"
- ieee754 "^1.1.4"
-
bufferutil@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-2.0.1.tgz#8de37f5a300730c305fc3edd9f93348ee8a46288"
@@ -1578,9 +1512,9 @@ builtin-status-codes@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-2.0.0.tgz#6f22003baacf003ccd287afe6872151fddc58579"
-cached-path-relative@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7"
+bytes@2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.3.0.tgz#d5b680a165b6201739acb611542aabc2d8ceb070"
caller-path@^0.1.0:
version "0.1.0"
@@ -1611,9 +1545,9 @@ camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
-caniuse-db@^1.0.30000539, caniuse-db@^1.0.30000540:
- version "1.0.30000554"
- resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000554.tgz#cd1dbe423d00b6203ba93f05973a476428dec919"
+caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
+ version "1.0.30000664"
+ resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000664.tgz#e16316e5fdabb9c7209b2bf0744ffc8a14201f22"
case-sensitive-paths-webpack-plugin@^1.1.2:
version "1.1.4"
@@ -1676,7 +1610,7 @@ cheerio@^0.22.0:
lodash.reject "^4.4.0"
lodash.some "^4.4.0"
-chokidar@^1.0.0, chokidar@^1.4.3, chokidar@^1.6.1:
+chokidar@^1.0.0, chokidar@^1.4.3, chokidar@^1.6.0, chokidar@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2"
dependencies:
@@ -1771,6 +1705,16 @@ code-point-at@^1.0.0:
dependencies:
number-is-nan "^1.0.0"
+coffee-loader@^0.7.3:
+ version "0.7.3"
+ resolved "https://registry.yarnpkg.com/coffee-loader/-/coffee-loader-0.7.3.tgz#fadbc6efd6fc7ecc88c5b3046a2c292066bcb54a"
+ dependencies:
+ loader-utils "^1.0.2"
+
+coffee-script@^1.12.5:
+ version "1.12.5"
+ resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.5.tgz#809f4585419112bbfe46a073ad7543af18c27346"
+
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"
@@ -1809,15 +1753,6 @@ colors@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
-combine-source-map@~0.7.1:
- version "0.7.2"
- resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.7.2.tgz#0870312856b307a87cc4ac486f3a9a62aeccc09e"
- dependencies:
- convert-source-map "~1.1.0"
- inline-source-map "~0.6.0"
- lodash.memoize "~3.0.3"
- source-map "~0.5.3"
-
combined-stream@^1.0.5, combined-stream@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
@@ -1840,11 +1775,41 @@ commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
+complex.js@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.1.tgz#ea90c7a05aeceaf3a376d2c0f6a78421727d6879"
+
+compressible@~2.0.8:
+ version "2.0.10"
+ resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd"
+ dependencies:
+ mime-db ">= 1.27.0 < 2"
+
+compression-webpack-plugin@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-0.4.0.tgz#811de04215f811ea6a12d4d8aed8457d758f13ac"
+ dependencies:
+ async "0.2.x"
+ webpack-sources "^0.1.0"
+ optionalDependencies:
+ node-zopfli "^2.0.0"
+
+compression@^1.5.2:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/compression/-/compression-1.6.2.tgz#cceb121ecc9d09c52d7ad0c3350ea93ddd402bc3"
+ dependencies:
+ accepts "~1.3.3"
+ bytes "2.3.0"
+ compressible "~2.0.8"
+ debug "~2.2.0"
+ on-headers "~1.0.1"
+ vary "~1.1.0"
+
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-concat-stream@^1.4.7, concat-stream@^1.5.2, concat-stream@~1.5.0, concat-stream@~1.5.1:
+concat-stream@^1.4.7, concat-stream@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266"
dependencies:
@@ -1866,6 +1831,10 @@ configstore@^2.0.0:
write-file-atomic "^1.1.2"
xdg-basedir "^2.0.0"
+connect-history-api-fallback@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz#e51d17f8f0ef0db90a64fdb47de3051556e9f169"
+
console-browserify@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
@@ -1876,7 +1845,7 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
-constants-browserify@^1.0.0, constants-browserify@~1.0.0:
+constants-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
@@ -1896,10 +1865,6 @@ convert-source-map@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.3.0.tgz#e9f3e9c6e2728efc2676696a70eb382f73106a67"
-convert-source-map@~1.1.0:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860"
-
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -1983,7 +1948,7 @@ crypto-browserify@3.3.0:
ripemd160 "0.2.0"
sha.js "2.2.6"
-crypto-browserify@^3.0.0, crypto-browserify@^3.11.0:
+crypto-browserify@^3.11.0:
version "3.11.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.0.tgz#3652a0906ab9b2a7e0c3ce66a408e957a2485522"
dependencies:
@@ -1998,11 +1963,20 @@ crypto-browserify@^3.0.0, crypto-browserify@^3.11.0:
public-encrypt "^4.0.0"
randombytes "^2.0.0"
+css-color-function@^1.2.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/css-color-function/-/css-color-function-1.3.0.tgz#72c767baf978f01b8a8a94f42f17ba5d22a776fc"
+ dependencies:
+ balanced-match "0.1.0"
+ color "^0.11.0"
+ debug "~0.7.4"
+ rgb "~0.1.0"
+
css-color-names@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
-css-loader@^0.26.1, css-loader@^0.26.2:
+css-loader@^0.26.1:
version "0.26.2"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.26.2.tgz#a9cd4c2b1a559b45d8efc04fc311ab5d2aaccb9d"
dependencies:
@@ -2019,6 +1993,23 @@ css-loader@^0.26.1, css-loader@^0.26.2:
postcss-modules-values "^1.1.0"
source-list-map "^0.1.7"
+css-loader@^0.28.0:
+ version "0.28.0"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.0.tgz#417cfa9789f8cde59a30ccbf3e4da7a806889bad"
+ dependencies:
+ babel-code-frame "^6.11.0"
+ css-selector-tokenizer "^0.7.0"
+ cssnano ">=2.6.1 <4"
+ loader-utils "^1.0.2"
+ lodash.camelcase "^4.3.0"
+ object-assign "^4.0.1"
+ postcss "^5.0.6"
+ postcss-modules-extract-imports "^1.0.0"
+ postcss-modules-local-by-default "^1.0.1"
+ postcss-modules-scope "^1.0.0"
+ postcss-modules-values "^1.1.0"
+ source-list-map "^0.1.7"
+
css-select@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
@@ -2138,10 +2129,18 @@ debug@2.2.0, debug@^2.1.1, debug@^2.2.0, debug@~2.2.0:
dependencies:
ms "0.7.1"
+debug@~0.7.4:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39"
+
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+decimal.js@7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-7.1.1.tgz#1adcad7d70d7a91c426d756f1eb6566c3be6cbcf"
+
deep-eql@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2"
@@ -2160,6 +2159,12 @@ deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
+defaults@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
+ dependencies:
+ clone "^1.0.2"
+
define-properties@^1.1.1, define-properties@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
@@ -2195,15 +2200,6 @@ depd@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
-deps-sort@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.0.tgz#091724902e84658260eb910748cccd1af6e21fb5"
- dependencies:
- JSONStream "^1.0.3"
- shasum "^1.0.0"
- subarg "^1.0.0"
- through2 "^2.0.0"
-
des.js@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
@@ -2221,13 +2217,6 @@ detect-indent@^4.0.0:
dependencies:
repeating "^2.0.0"
-detective@^4.0.0:
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/detective/-/detective-4.3.1.tgz#9fb06dd1ee8f0ea4dbcc607cda39d9ce1d4f726f"
- dependencies:
- acorn "^1.0.3"
- defined "^1.0.0"
-
diff@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf"
@@ -2265,7 +2254,7 @@ dom-serializer@0, dom-serializer@~0.1.0:
domelementtype "~1.1.1"
entities "~1.1.1"
-domain-browser@^1.1.1, domain-browser@~1.1.0:
+domain-browser@^1.1.1:
version "1.1.7"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
@@ -2304,12 +2293,6 @@ double-ended-queue@^2.1.0-0:
version "2.1.0-0"
resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
-duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
- dependencies:
- readable-stream "^2.0.2"
-
ecc-jsbn@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
@@ -2324,6 +2307,10 @@ ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+electron-to-chromium@^1.2.7:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.8.tgz#b2c8a2c79bb89fbbfd3724d9555e15095b5f5fb6"
+
element-class@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/element-class/-/element-class-0.2.2.tgz#9d3bbd0767f9013ef8e1c8ebe722c1402a60050e"
@@ -2604,7 +2591,7 @@ esprima@^2.6.0, esprima@^2.7.1:
version "2.7.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
-esprima@~3.1.0:
+esprima@^3.1.1, esprima@~3.1.0:
version "3.1.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
@@ -2648,10 +2635,20 @@ event-emitter@~0.3.5:
d "1"
es5-ext "~0.10.14"
-events@^1.0.0, events@^1.1.1, events@~1.1.0:
+eventemitter3@1.x.x:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
+
+events@^1.0.0, events@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
+eventsource@0.1.6:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232"
+ dependencies:
+ original ">=0.0.5"
+
eventsource@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.2.1.tgz#662bf85f376e73b5c34c2ee17db566b8419a6232"
@@ -2729,6 +2726,15 @@ extglob@^0.3.1:
dependencies:
is-extglob "^1.0.0"
+extract-text-webpack-plugin@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-2.1.0.tgz#69315b885f876dbf96d3819f6a9f1cca7aebf159"
+ dependencies:
+ ajv "^4.11.2"
+ async "^2.1.2"
+ loader-utils "^1.0.2"
+ webpack-sources "^0.1.0"
+
extsprintf@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550"
@@ -2741,6 +2747,18 @@ fastparse@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
+faye-websocket@^0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+faye-websocket@~0.11.0:
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38"
+ dependencies:
+ websocket-driver ">=0.5.1"
+
fbjs@^0.8.4, fbjs@^0.8.9:
version "0.8.12"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04"
@@ -2767,6 +2785,12 @@ file-entry-cache@^2.0.0:
flat-cache "^1.2.1"
object-assign "^4.0.1"
+file-loader@^0.11.1:
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.11.1.tgz#6b328ee1234a729e4e47d36375dd6d35c0e1db84"
+ dependencies:
+ loader-utils "^1.0.2"
+
file-loader@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.9.0.tgz#1d2daddd424ce6d1b07cfe3f79731bed3617ab42"
@@ -2831,6 +2855,10 @@ follow-redirects@1.0.0:
dependencies:
debug "^2.2.0"
+font-awesome@^4.7.0:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
+
for-in@^0.1.3, for-in@^0.1.5:
version "0.1.6"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8"
@@ -2849,14 +2877,6 @@ forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
-form-data@~2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.0.0.tgz#6f0aebadcc5da16c13e1ecc11137d85f9b883b25"
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.5"
- mime-types "^2.1.11"
-
form-data@~2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4"
@@ -2875,10 +2895,39 @@ forwarded@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363"
+fraction.js@4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.0.tgz#73974e2f8b51ef709536d624cc90782e2bb61274"
+
fresh@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f"
+fs-extra@^0.24.0:
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.24.0.tgz#d4e4342a96675cb7846633a6099249332b539952"
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^2.1.0"
+ path-is-absolute "^1.0.0"
+ rimraf "^2.2.8"
+
+fs-extra@^0.30.0:
+ version "0.30.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0"
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^2.1.0"
+ klaw "^1.0.0"
+ path-is-absolute "^1.0.0"
+ rimraf "^2.2.8"
+
+fs-promise@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/fs-promise/-/fs-promise-0.3.1.tgz#bf34050368f24d6dc9dfc6688ab5cead8f86842a"
+ dependencies:
+ any-promise "~0.1.0"
+
fs-readdir-recursive@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz#8cd1745c8b4f8a29c8caec392476921ba195f560"
@@ -2996,7 +3045,7 @@ glob-parent@^2.0.0:
dependencies:
is-glob "^2.0.0"
-glob@7.0.5, glob@^7.0.0:
+glob@7.0.5:
version "7.0.5"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.5.tgz#b4202a69099bbb4d292a7c1b95b6682b67ebdc95"
dependencies:
@@ -3007,7 +3056,17 @@ glob@7.0.5, glob@^7.0.0:
once "^1.3.0"
path-is-absolute "^1.0.0"
-glob@^7.0.3, glob@^7.0.5, glob@^7.1.0, glob@~7.1.1:
+glob@^5.0.3:
+ version "5.0.15"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
+ dependencies:
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "2 || 3"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@~7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
dependencies:
@@ -3022,6 +3081,17 @@ globals@^9.0.0, globals@^9.14.0:
version "9.14.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034"
+globby@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-3.0.1.tgz#2094af8421e19152150d5893eb6416b312d9a22f"
+ dependencies:
+ array-union "^1.0.1"
+ arrify "^1.0.0"
+ glob "^5.0.3"
+ object-assign "^4.0.1"
+ pify "^2.0.0"
+ pinkie-promise "^1.0.0"
+
globby@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
@@ -3041,7 +3111,13 @@ globule@^1.0.0:
lodash "~4.16.4"
minimatch "~3.0.2"
-graceful-fs@^4.1.2, graceful-fs@^4.1.4:
+gonzales-pe@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.0.3.tgz#36148e18e267184fbfdc929af28f29ad9fbf9746"
+ dependencies:
+ minimist "1.1.x"
+
+graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
version "4.1.9"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.9.tgz#baacba37d19d11f9d146d3578bc99958c3787e29"
@@ -3053,6 +3129,10 @@ growl@1.9.2:
version "1.9.2"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f"
+handle-thing@^1.2.4:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
+
har-validator@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
@@ -3076,7 +3156,7 @@ has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
-has@^1.0.0, has@^1.0.1:
+has@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
dependencies:
@@ -3125,6 +3205,15 @@ hosted-git-info@^2.1.4:
version "2.1.5"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.1.5.tgz#0ba81d90da2e25ab34a332e6ec77936e1598118b"
+hpack.js@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
+ dependencies:
+ inherits "^2.0.1"
+ obuf "^1.0.0"
+ readable-stream "^2.0.1"
+ wbuf "^1.1.0"
+
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"
@@ -3145,10 +3234,6 @@ html@^1.0.0:
dependencies:
concat-stream "^1.4.7"
-htmlescape@^1.1.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351"
-
htmlparser2@^3.9.1:
version "3.9.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
@@ -3160,7 +3245,11 @@ htmlparser2@^3.9.1:
inherits "^2.0.1"
readable-stream "^2.0.2"
-http-errors@~1.5.1:
+http-deceiver@^1.2.4:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
+
+http-errors@~1.5.0, http-errors@~1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750"
dependencies:
@@ -3172,6 +3261,22 @@ http-link-header@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-0.8.0.tgz#a22b41a0c9b1e2d8fac1bf1b697c6bd532d5f5e4"
+http-proxy-middleware@~0.17.4:
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833"
+ dependencies:
+ http-proxy "^1.16.2"
+ is-glob "^3.1.0"
+ lodash "^4.17.2"
+ micromatch "^2.3.11"
+
+http-proxy@^1.16.2:
+ version "1.16.2"
+ resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742"
+ dependencies:
+ eventemitter3 "1.x.x"
+ requires-port "1.x.x"
+
http-signature@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
@@ -3180,7 +3285,7 @@ http-signature@~1.1.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
-https-browserify@0.0.1, https-browserify@~0.0.0:
+https-browserify@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
@@ -3245,12 +3350,6 @@ ini@~1.3.0:
version "1.3.4"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
-inline-source-map@~0.6.0:
- version "0.6.2"
- resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5"
- dependencies:
- source-map "~0.5.3"
-
inquirer@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
@@ -3269,19 +3368,6 @@ inquirer@^0.12.0:
strip-ansi "^3.0.0"
through "^2.3.6"
-insert-module-globals@^7.0.0:
- version "7.0.1"
- resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.0.1.tgz#c03bf4e01cb086d5b5e5ace8ad0afe7889d638c3"
- dependencies:
- JSONStream "^1.0.3"
- combine-source-map "~0.7.1"
- concat-stream "~1.5.1"
- is-buffer "^1.1.0"
- lexical-scope "^1.2.0"
- process "~0.11.0"
- through2 "^2.0.0"
- xtend "^4.0.0"
-
interpret@^0.6.4:
version "0.6.6"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-0.6.6.tgz#fecd7a18e7ce5ca6abfb953e1f86213a49f1625b"
@@ -3298,7 +3384,7 @@ intl-format-cache@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/intl-format-cache/-/intl-format-cache-2.0.5.tgz#b484cefcb9353f374f25de389a3ceea1af18d7c9"
-intl-messageformat-parser@1.2.0:
+intl-messageformat-parser@1.2.0, intl-messageformat-parser@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.2.0.tgz#5906b7f953ab7470e0dc8549097b648b991892ff"
@@ -3314,7 +3400,7 @@ intl-relativeformat@^1.3.0:
dependencies:
intl-messageformat "1.3.0"
-invariant@2.x.x, invariant@^2.0.0, invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1:
+invariant@2.x.x, invariant@^2.0.0, invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
dependencies:
@@ -3342,7 +3428,7 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
-is-buffer@^1.0.2, is-buffer@^1.1.0:
+is-buffer@^1.0.2:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b"
@@ -3382,6 +3468,10 @@ is-extglob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
+is-extglob@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+
is-finite@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
@@ -3404,6 +3494,12 @@ is-glob@^2.0.0, is-glob@^2.0.1:
dependencies:
is-extglob "^1.0.0"
+is-glob@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
+ dependencies:
+ is-extglob "^2.1.0"
+
is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4:
version "2.15.0"
resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz#936edda3ca3c211fd98f3b2d3e08da43f7b2915b"
@@ -3505,10 +3601,6 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
-isarray@~0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
-
isexe@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0"
@@ -3540,6 +3632,16 @@ jodid25519@^1.0.0:
dependencies:
jsbn "~0.1.0"
+jquery-ujs@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/jquery-ujs/-/jquery-ujs-1.2.2.tgz#6a8ef1020e6b6dda385b90a4bddc128c21c56397"
+ dependencies:
+ jquery ">=1.8.0"
+
+jquery@>=1.8.0:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787"
+
js-base64@^2.1.9:
version "2.1.9"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce"
@@ -3548,15 +3650,18 @@ js-tokens@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-1.0.3.tgz#14e56eb68c8f1a92c43d59f5014ec29dc20f2ae1"
-js-tokens@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-2.0.0.tgz#79903f5563ee778cc1162e6dcf1a0027c97f9cb5"
-
js-tokens@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
-js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@~3.6.1:
+js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.8.3:
+ version "3.8.3"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.3.tgz#33a05ec481c850c8875929166fe1beb61c728766"
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^3.1.1"
+
+js-yaml@~3.6.1:
version "3.6.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30"
dependencies:
@@ -3613,17 +3718,11 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
dependencies:
jsonify "~0.0.0"
-json-stable-stringify@~0.0.0:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45"
- dependencies:
- jsonify "~0.0.0"
-
json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
-json3@3.3.2:
+json3@3.3.2, json3@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
@@ -3631,18 +3730,20 @@ json5@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.0.tgz#9b20715b026cbe3778fd769edccd822d8332a5b2"
+json5@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+
+jsonfile@^2.1.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
-jsonparse@0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-0.0.5.tgz#330542ad3f0a654665b778f3eb2d9a9fa507ac64"
-
-jsonparse@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd"
-
jsonpointer@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.0.tgz#6661e161d2fc445f19f98430231343722e1fcbd5"
@@ -3677,13 +3778,11 @@ kind-of@^3.0.2:
dependencies:
is-buffer "^1.0.2"
-labeled-stream-splicer@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz#a52e1d138024c00b86b1c0c91f677918b8ae0a59"
- dependencies:
- inherits "^2.0.1"
- isarray "~0.0.1"
- stream-splicer "^2.0.0"
+klaw@^1.0.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
+ optionalDependencies:
+ graceful-fs "^4.1.9"
lazy-cache@^0.2.3:
version "0.2.7"
@@ -3706,12 +3805,6 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
-lexical-scope@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/lexical-scope/-/lexical-scope-1.2.0.tgz#fcea5edc704a4b3a8796cdca419c3a0afaf22df4"
- dependencies:
- astw "^2.0.0"
-
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -3735,7 +3828,15 @@ loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.
json5 "^0.5.0"
object-assign "^4.0.1"
-loader-utils@^1.0.1, loader-utils@^1.0.2:
+loader-utils@^1.0.1, loader-utils@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
+ dependencies:
+ big.js "^3.1.3"
+ emojis-list "^2.0.0"
+ json5 "^0.5.0"
+
+loader-utils@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.0.2.tgz#a9f923c865a974623391a8602d031137fad74830"
dependencies:
@@ -3818,7 +3919,7 @@ lodash.create@3.1.1:
lodash._basecreate "^3.0.0"
lodash._isiterateecall "^3.0.0"
-lodash.defaults@^4.0.1:
+lodash.defaults@^4.0.1, lodash.defaults@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
@@ -3858,10 +3959,6 @@ lodash.map@^4.4.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
-lodash.memoize@~3.0.3:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
-
lodash.merge@^4.4.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5"
@@ -3898,7 +3995,7 @@ lodash.tail@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
-lodash@4.x.x, lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.3.0:
+lodash@4.x.x, "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.3.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -3914,18 +4011,18 @@ longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
-loose-envify@^1.0.0, loose-envify@^1.3.0:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
- dependencies:
- js-tokens "^3.0.0"
-
-loose-envify@^1.1.0, loose-envify@^1.2.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.2.0.tgz#69a65aad3de542cf4ee0f4fe74e8e33c709ccb0f"
dependencies:
js-tokens "^1.0.1"
+loose-envify@^1.3.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
+ dependencies:
+ js-tokens "^3.0.0"
+
loud-rejection@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
@@ -3962,6 +4059,17 @@ math-expression-evaluator@^1.2.14:
dependencies:
lodash.indexof "^4.0.5"
+mathjs@^3.11.5:
+ version "3.12.1"
+ resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-3.12.1.tgz#a165abdbc6b55da1ec600b85311e2962850551b5"
+ dependencies:
+ complex.js "2.0.1"
+ decimal.js "7.1.1"
+ fraction.js "4.0.0"
+ seed-random "2.2.0"
+ tiny-emitter "1.0.2"
+ typed-function "0.10.5"
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -4007,7 +4115,7 @@ methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
-micromatch@^2.1.5:
+micromatch@^2.1.5, micromatch@^2.3.11:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
dependencies:
@@ -4032,11 +4140,15 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
+"mime-db@>= 1.27.0 < 2":
+ version "1.27.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1"
+
mime-db@~1.26.0:
version "1.26.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff"
-mime-types@^2.1.11, mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7:
+mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7:
version "2.1.14"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee"
dependencies:
@@ -4054,7 +4166,7 @@ minimalistic-assert@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3"
-minimatch@^3.0.0, minimatch@^3.0.2, minimatch@~3.0.2:
+"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@~3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774"
dependencies:
@@ -4064,7 +4176,11 @@ minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0:
+minimist@1.1.x:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8"
+
+minimist@^1.1.3, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
@@ -4075,7 +4191,7 @@ mixin-object@^2.0.1:
for-in "^0.1.3"
is-extendable "^0.1.1"
-mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
dependencies:
@@ -4101,26 +4217,6 @@ mocha@^3.2.0:
mkdirp "0.5.1"
supports-color "3.1.2"
-module-deps@^4.0.8:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-4.1.1.tgz#23215833f1da13fd606ccb8087b44852dcb821fd"
- dependencies:
- JSONStream "^1.0.3"
- browser-resolve "^1.7.0"
- cached-path-relative "^1.0.0"
- concat-stream "~1.5.0"
- defined "^1.0.0"
- detective "^4.0.0"
- duplexer2 "^0.1.2"
- inherits "^2.0.1"
- parents "^1.0.0"
- readable-stream "^2.0.2"
- resolve "^1.1.3"
- stream-combiner2 "^1.1.1"
- subarg "^1.0.0"
- through2 "^2.0.0"
- xtend "^4.0.0"
-
ms@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
@@ -4133,7 +4229,7 @@ mute-stream@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
-nan@^2.3.0, nan@^2.3.2, nan@~2.5.0:
+nan@^2.0.0, nan@^2.3.0, nan@^2.3.2, nan@~2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2"
@@ -4236,7 +4332,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.29, node-pre-gyp@^0.6.4:
version "0.6.30"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.30.tgz#64d3073a6f573003717ccfe30c89023297babba1"
dependencies:
@@ -4273,9 +4369,14 @@ node-sass@^4.5.2:
sass-graph "^2.1.1"
stdout-stream "^1.4.0"
-node-uuid@~1.4.7:
- version "1.4.7"
- resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.7.tgz#6da5a17668c4b3dd59623bda11cf7fa4c1f60a6f"
+node-zopfli@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/node-zopfli/-/node-zopfli-2.0.2.tgz#a7a473ae92aaea85d4c68d45bbf2c944c46116b8"
+ dependencies:
+ commander "^2.8.1"
+ defaults "^1.0.2"
+ nan "^2.0.0"
+ node-pre-gyp "^0.6.4"
noop-logger@^0.1.1:
version "0.1.1"
@@ -4344,7 +4445,7 @@ oauth-sign@~0.8.1:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
-object-assign@4.1.0, object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0:
+object-assign@4.1.0, object-assign@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
@@ -4352,7 +4453,7 @@ object-assign@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
-object-assign@^4.1.1:
+object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -4404,12 +4505,20 @@ object.values@^1.0.3:
function-bind "^1.0.2"
has "^1.0.1"
+obuf@^1.0.0, obuf@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e"
+
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
dependencies:
ee-first "1.1.1"
+on-headers@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
+
once@^1.3.0, once@^1.3.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -4426,6 +4535,13 @@ onetime@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
+opn@4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95"
+ dependencies:
+ object-assign "^4.0.1"
+ pinkie-promise "^2.0.0"
+
optimist@~0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
@@ -4444,7 +4560,7 @@ optionator@^0.8.1, optionator@^0.8.2:
type-check "~0.3.2"
wordwrap "~1.0.0"
-original@^1.0.0:
+original@>=0.0.5, original@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b"
dependencies:
@@ -4454,10 +4570,6 @@ os-browserify@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f"
-os-browserify@~0.1.1:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.1.2.tgz#49ca0293e0b19590a5f5de10c7f265a617d8fe54"
-
os-homedir@^1.0.0, os-homedir@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
@@ -4492,14 +4604,8 @@ packet-reader@0.2.0:
resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-0.2.0.tgz#819df4d010b82d5ea5671f8a1a3acf039bcd7700"
pako@~0.2.0:
- version "0.2.9"
- resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
-
-parents@^1.0.0, parents@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751"
- dependencies:
- path-platform "~0.11.15"
+ version "0.2.9"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
parse-asn1@^5.0.0:
version "5.0.0"
@@ -4534,10 +4640,14 @@ parseurl@~1.3.0, parseurl@~1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
-path-browserify@0.0.0, path-browserify@~0.0.0:
+path-browserify@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
+path-complete-extname@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/path-complete-extname/-/path-complete-extname-0.1.0.tgz#c454702669f31452f8193aa6168915fa31692f4a"
+
path-exists@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
@@ -4552,9 +4662,9 @@ path-is-inside@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
-path-platform@~0.11.15:
- version "0.11.15"
- resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2"
+path-parse@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
path-to-regexp@0.1.7:
version "0.1.7"
@@ -4625,12 +4735,22 @@ pify@^2.0.0, pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+pinkie-promise@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-1.0.0.tgz#d1da67f5482563bb7cf57f286ae2822ecfbf3670"
+ dependencies:
+ pinkie "^1.0.0"
+
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
dependencies:
pinkie "^2.0.0"
+pinkie@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-1.0.0.tgz#5a47f28ba1015d0201bda7bf0f358e47bec8c7e4"
+
pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
@@ -4652,6 +4772,26 @@ podda@^1.2.1:
babel-runtime "^6.11.6"
immutable "^3.8.1"
+portfinder@^1.0.9:
+ version "1.0.13"
+ resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
+ dependencies:
+ async "^1.5.2"
+ debug "^2.2.0"
+ mkdirp "0.5.x"
+
+postcss-advanced-variables@1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/postcss-advanced-variables/-/postcss-advanced-variables-1.2.2.tgz#90a6213262e66a050a368b4a9c5d4778d72dbd74"
+ dependencies:
+ postcss "^5.0.10"
+
+postcss-atroot@^0.1.2:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/postcss-atroot/-/postcss-atroot-0.1.3.tgz#6752c0230c745140549345b2b0e30ebeda01a405"
+ dependencies:
+ postcss "^5.0.5"
+
postcss-calc@^5.2.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e"
@@ -4660,6 +4800,15 @@ postcss-calc@^5.2.0:
postcss-message-helpers "^2.0.0"
reduce-css-calc "^1.2.6"
+postcss-color-function@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-color-function/-/postcss-color-function-2.0.1.tgz#9ad226f550e8a7c7f8b8a77860545b6dd7f55241"
+ dependencies:
+ css-color-function "^1.2.0"
+ postcss "^5.0.4"
+ postcss-message-helpers "^2.0.0"
+ postcss-value-parser "^3.3.0"
+
postcss-colormin@^2.1.8:
version "2.2.1"
resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.1.tgz#dc5421b6ae6f779ef6bfd47352b94abe59d0316b"
@@ -4675,6 +4824,27 @@ postcss-convert-values@^2.3.4:
postcss "^5.0.11"
postcss-value-parser "^3.1.2"
+postcss-custom-media@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-5.0.1.tgz#138d25a184bf2eb54de12d55a6c01c30a9d8bd81"
+ dependencies:
+ postcss "^5.0.0"
+
+postcss-custom-properties@^5.0.0:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-5.0.2.tgz#9719d78f2da9cf9f53810aebc23d4656130aceb1"
+ dependencies:
+ balanced-match "^0.4.2"
+ postcss "^5.0.0"
+
+postcss-custom-selectors@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-3.0.0.tgz#8f81249f5ed07a8d0917cf6a39fe5b056b7f96ac"
+ dependencies:
+ balanced-match "^0.2.0"
+ postcss "^5.0.0"
+ postcss-selector-matches "^2.0.0"
+
postcss-discard-comments@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d"
@@ -4707,6 +4877,12 @@ postcss-discard-unused@^2.2.1:
postcss "^5.0.14"
uniqs "^2.0.0"
+postcss-extend@^1.0.1:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/postcss-extend/-/postcss-extend-1.0.5.tgz#5ea98bf787ba3cacf4df4609743f80a833b1d0e7"
+ dependencies:
+ postcss "^5.0.4"
+
postcss-filter-plugins@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c"
@@ -4714,7 +4890,7 @@ postcss-filter-plugins@^2.0.0:
postcss "^5.0.4"
uniqid "^4.0.0"
-postcss-load-config@^1.0.0:
+postcss-load-config@^1.0.0, 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:
@@ -4746,6 +4922,21 @@ postcss-loader@1.1.0:
postcss "^5.2.5"
postcss-load-config "^1.0.0"
+postcss-loader@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-1.3.3.tgz#a621ea1fa29062a83972a46f54486771301916eb"
+ dependencies:
+ loader-utils "^1.0.2"
+ object-assign "^4.1.1"
+ postcss "^5.2.15"
+ postcss-load-config "^1.2.0"
+
+postcss-media-minmax@^2.1.0:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-2.1.2.tgz#444c5cf8926ab5e4fd8a2509e9297e751649cdf8"
+ dependencies:
+ postcss "^5.0.4"
+
postcss-merge-idents@^2.1.5:
version "2.1.7"
resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270"
@@ -4803,6 +4994,14 @@ postcss-minify-selectors@^2.0.4:
postcss "^5.0.14"
postcss-selector-parser "^2.0.0"
+postcss-mixins@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-2.1.1.tgz#b141a0803efa8e2d744867f8d91596890cf9241b"
+ dependencies:
+ globby "^3.0.1"
+ postcss "^5.0.10"
+ postcss-simple-vars "^1.0.1"
+
postcss-modules-extract-imports@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.0.1.tgz#8fb3fef9a6dd0420d3f6d4353cf1ff73f2b2a341"
@@ -4830,6 +5029,18 @@ postcss-modules-values@^1.1.0:
icss-replace-symbols "^1.0.2"
postcss "^5.0.14"
+postcss-nested@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-1.0.1.tgz#91f28f4e6e23d567241ac154558a0cfab4cc0d8f"
+ dependencies:
+ postcss "^5.2.17"
+
+postcss-nesting@^2.0.6:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-2.3.1.tgz#94a6b6a4ef707fbec20a87fee5c957759b4e01cf"
+ dependencies:
+ postcss "^5.0.19"
+
postcss-normalize-charset@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.0.tgz#2fbd30e12248c442981d31ea2484d46fd0628970"
@@ -4852,6 +5063,24 @@ postcss-ordered-values@^2.1.0:
postcss "^5.0.4"
postcss-value-parser "^3.0.1"
+postcss-partial-import@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-partial-import/-/postcss-partial-import-1.3.0.tgz#2f4b773a76c7b0a69b389dcf475c4d362d0d2576"
+ dependencies:
+ fs-extra "^0.24.0"
+ fs-promise "^0.3.1"
+ object-assign "^4.0.1"
+ postcss "^5.0.5"
+ string-hash "^1.1.0"
+
+postcss-property-lookup@^1.1.3:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/postcss-property-lookup/-/postcss-property-lookup-1.2.1.tgz#30450a1361b7aae758bbedd5201fbe057bb8270b"
+ dependencies:
+ object-assign "^4.0.1"
+ postcss "^5.0.4"
+ tcomb "^2.5.1"
+
postcss-reduce-idents@^2.2.2:
version "2.3.0"
resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.3.0.tgz#a697b52953ed6825ffea404e26a4f105d8b8d569"
@@ -4872,6 +5101,34 @@ postcss-reduce-transforms@^1.0.3:
postcss "^5.0.8"
postcss-value-parser "^3.0.1"
+postcss-sass@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.1.0.tgz#0d2a655b5d241ec8f419bb3da38de5ca11746ddb"
+ dependencies:
+ gonzales-pe "^4.0.3"
+ mathjs "^3.11.5"
+ postcss "^5.2.6"
+
+postcss-scss@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-0.4.1.tgz#ad771b81f0f72f5f4845d08aa60f93557653d54c"
+ dependencies:
+ postcss "^5.2.13"
+
+postcss-selector-matches@^2.0.0:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-2.0.5.tgz#fa0f43be57b68e77aa4cd11807023492a131027f"
+ dependencies:
+ balanced-match "^0.4.2"
+ postcss "^5.0.0"
+
+postcss-selector-not@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-2.0.0.tgz#c73ad21a3f75234bee7fee269e154fd6a869798d"
+ dependencies:
+ balanced-match "^0.2.0"
+ postcss "^5.0.0"
+
postcss-selector-parser@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.1.tgz#fdbf696103b12b0a64060e5610507f410491f7c8"
@@ -4880,6 +5137,28 @@ postcss-selector-parser@^2.0.0:
indexes-of "^1.0.1"
uniq "^1.0.1"
+postcss-simple-vars@^1.0.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-1.2.0.tgz#2e6689921144b74114e765353275a3c32143f150"
+ dependencies:
+ postcss "^5.0.13"
+
+postcss-smart-import@^0.6.12:
+ version "0.6.12"
+ resolved "https://registry.yarnpkg.com/postcss-smart-import/-/postcss-smart-import-0.6.12.tgz#6bd50846547383d15332206615265b87b4a6ae89"
+ dependencies:
+ babel-runtime "^6.23.0"
+ lodash "^4.17.4"
+ object-assign "^4.1.1"
+ postcss "^5.2.17"
+ postcss-sass "^0.1.0"
+ postcss-scss "^0.4.1"
+ postcss-value-parser "^3.3.0"
+ promise-each "^2.2.0"
+ read-cache "^1.0.0"
+ resolve "^1.3.3"
+ sugarss "^0.2.0"
+
postcss-svgo@^2.1.1:
version "2.1.5"
resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.5.tgz#46fc0363f01bab6a36a9abb01c229fcc45363094"
@@ -4897,7 +5176,7 @@ postcss-unique-selectors@^2.0.2:
postcss "^5.0.4"
uniqs "^2.0.0"
-postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.1.3, postcss-value-parser@^3.2.3:
+postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.1.3, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15"
@@ -4908,24 +5187,15 @@ postcss-zindex@^2.0.1:
postcss "^5.0.4"
uniqs "^2.0.0"
-postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.8, postcss@^5.2.5:
- version "5.2.15"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.15.tgz#a9e8685e50e06cc5b3fdea5297273246c26f5b30"
+postcss@^5.0.0, postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.19, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.13, postcss@^5.2.15, postcss@^5.2.16, postcss@^5.2.17, postcss@^5.2.4, postcss@^5.2.5, postcss@^5.2.6:
+ version "5.2.17"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.17.tgz#cf4f597b864d65c8a492b2eabe9d706c879c388b"
dependencies:
chalk "^1.1.3"
js-base64 "^2.1.9"
source-map "^0.5.6"
supports-color "^3.2.3"
-postcss@^5.0.6, postcss@^5.2.2:
- version "5.2.4"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.4.tgz#8eb4bee3e5c4e091585b116df32d8db24a535f21"
- dependencies:
- chalk "^1.1.3"
- js-base64 "^2.1.9"
- source-map "^0.5.6"
- supports-color "^3.1.2"
-
postgres-array@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-1.0.2.tgz#8e0b32eb03bf77a5c0a7851e0441c169a256a238"
@@ -4966,6 +5236,27 @@ precond@0.2:
version "0.2.3"
resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac"
+precss@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/precss/-/precss-1.4.0.tgz#8d7c3ae70f10a00a3955287f85a66e0f8b31cda3"
+ dependencies:
+ postcss "^5.0.10"
+ postcss-advanced-variables "1.2.2"
+ postcss-atroot "^0.1.2"
+ postcss-color-function "^2.0.0"
+ postcss-custom-media "^5.0.0"
+ postcss-custom-properties "^5.0.0"
+ postcss-custom-selectors "^3.0.0"
+ postcss-extend "^1.0.1"
+ postcss-media-minmax "^2.1.0"
+ postcss-mixins "^2.1.0"
+ postcss-nested "^1.0.0"
+ postcss-nesting "^2.0.6"
+ postcss-partial-import "^1.3.0"
+ postcss-property-lookup "^1.1.3"
+ postcss-selector-matches "^2.0.0"
+ postcss-selector-not "^2.0.0"
+
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -4986,7 +5277,7 @@ process-nextick-args@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
-process@^0.11.0, process@~0.11.0:
+process@^0.11.0:
version "0.11.9"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1"
@@ -4994,6 +5285,12 @@ progress@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
+promise-each@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/promise-each/-/promise-each-2.2.0.tgz#3353174eff2694481037e04e01f77aa0fb6d1b60"
+ dependencies:
+ 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"
@@ -5042,7 +5339,7 @@ punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
-punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1:
+punycode@^1.2.4, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
@@ -5050,11 +5347,11 @@ q@^1.1.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
-qs@6.2.0, qs@^6.1.0, qs@^6.2.0, qs@~6.2.0:
+qs@6.2.0, qs@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b"
-qs@~6.3.0:
+qs@^6.2.0, qs@~6.3.0:
version "6.3.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.1.tgz#918c0b3bcd36679772baf135b1acb4c1651ed79d"
@@ -5071,7 +5368,7 @@ query-string@^4.1.0:
object-assign "^4.1.0"
strict-uri-encode "^1.0.0"
-querystring-es3@^0.2.0, querystring-es3@~0.2.0:
+querystring-es3@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -5089,6 +5386,13 @@ raf@^3.1.0:
dependencies:
performance-now "~0.2.0"
+rails-erb-loader@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/rails-erb-loader/-/rails-erb-loader-5.0.0.tgz#33fcfbae3fd4584801283c4d5bc1195ae7b56723"
+ dependencies:
+ loader-utils "^1.1.0"
+ lodash.defaults "^4.2.0"
+
randomatic@^1.1.3:
version "1.1.5"
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.5.tgz#5e9ef5f2d573c67bd2b8124ae90b5156e457840b"
@@ -5172,7 +5476,7 @@ react-docgen@^2.12.1:
node-dir "^0.1.10"
recast "^0.11.5"
-react-dom@^15.5.4:
+react-dom@^15.4.2, react-dom@^15.5.4:
version "15.5.4"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.5.4.tgz#ba0c28786fd52ed7e4f2135fe0288d462aef93da"
dependencies:
@@ -5208,12 +5512,29 @@ 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"
+react-immutable-pure-component@^0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/react-immutable-pure-component/-/react-immutable-pure-component-0.0.4.tgz#a7c438167e5b82b5231e4ec9246827ebe3257629"
+ dependencies:
+ immutable "^3.8.1"
+ react "^15.4.2"
+ react-dom "^15.4.2"
+
react-inspector@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-1.1.0.tgz#8d55bb94ffc9fd3982a222eb257dbe9cdd4f1b87"
dependencies:
is-dom "^1.0.5"
+react-intl-translations-manager@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/react-intl-translations-manager/-/react-intl-translations-manager-5.0.0.tgz#3c78d3e3e44c5804d7a15c60e89c3aefd9d06615"
+ dependencies:
+ chalk "^1.1.3"
+ glob "^7.0.3"
+ json-stable-stringify "^1.0.1"
+ mkdirp "^0.5.1"
+
react-intl@^2.1.5:
version "2.1.5"
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-2.1.5.tgz#f9795ea34b790dcb5d0d8ef7060dddbe85bf8763"
@@ -5364,7 +5685,7 @@ react-virtualized@^8.11.4:
dom-helpers "^2.4.0 || ^3.0.0"
loose-envify "^1.3.0"
-react@^15.5.4:
+react@^15.4.2, react@^15.5.4:
version "15.5.4"
resolved "https://registry.yarnpkg.com/react/-/react-15.5.4.tgz#fa83eb01506ab237cdc1c8c3b1cea8de012bf047"
dependencies:
@@ -5373,11 +5694,11 @@ react@^15.5.4:
object-assign "^4.1.0"
prop-types "^15.5.7"
-read-only-stream@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0"
+read-cache@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
dependencies:
- readable-stream "^2.0.2"
+ pify "^2.3.0"
read-pkg-up@^1.0.1:
version "1.0.1"
@@ -5572,7 +5893,7 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
-request@2, request@2.x, request@^2.79.0:
+request@2, request@2.x, request@^2.74.0, request@^2.79.0:
version "2.79.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
dependencies:
@@ -5597,32 +5918,6 @@ request@2, request@2.x, request@^2.79.0:
tunnel-agent "~0.4.1"
uuid "^3.0.0"
-request@^2.74.0:
- version "2.75.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.75.0.tgz#d2b8268a286da13eaa5d01adf5d18cc90f657d93"
- dependencies:
- aws-sign2 "~0.6.0"
- aws4 "^1.2.1"
- bl "~1.1.2"
- caseless "~0.11.0"
- combined-stream "~1.0.5"
- extend "~3.0.0"
- forever-agent "~0.6.1"
- form-data "~2.0.0"
- har-validator "~2.0.6"
- hawk "~3.1.3"
- http-signature "~1.1.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.7"
- node-uuid "~1.4.7"
- oauth-sign "~0.8.1"
- qs "~6.2.0"
- stringstream "~0.0.4"
- tough-cookie "~2.3.0"
- tunnel-agent "~0.4.1"
-
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -5642,7 +5937,7 @@ require-uncached@^1.0.2:
caller-path "^0.1.0"
resolve-from "^1.0.0"
-requires-port@1.0.x:
+requires-port@1.0.x, requires-port@1.x.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
@@ -5654,9 +5949,11 @@ resolve-from@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
-resolve@1.1.7, resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
+resolve@^1.1.6, resolve@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5"
+ dependencies:
+ path-parse "^1.0.5"
restore-cursor@^1.0.1:
version "1.0.1"
@@ -5665,6 +5962,10 @@ restore-cursor@^1.0.1:
exit-hook "^1.0.0"
onetime "^1.0.0"
+rgb@~0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/rgb/-/rgb-0.1.0.tgz#be27b291e8feffeac1bd99729721bfa40fc037b5"
+
right-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
@@ -5707,9 +6008,9 @@ sass-graph@^2.1.1:
lodash "^4.0.0"
yargs "^4.7.1"
-sass-loader@^6.0.2:
- version "6.0.2"
- resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.2.tgz#96343a9f5c585780149321c7bda9e1da633d2c73"
+sass-loader@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.3.tgz#33983b1f90d27ddab0e57d0dac403dce9bc7ecfd"
dependencies:
async "^2.1.5"
clone-deep "^0.2.4"
@@ -5732,6 +6033,14 @@ section-iterator@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a"
+seed-random@2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54"
+
+select-hose@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
+
"semver@2 || 3 || 4 || 5", semver@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
@@ -5767,6 +6076,18 @@ serve-favicon@^2.3.0:
ms "0.7.1"
parseurl "~1.3.0"
+serve-index@^1.7.2:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.8.0.tgz#7c5d96c13fb131101f93c1c5774f8516a1e78d3b"
+ dependencies:
+ accepts "~1.3.3"
+ batch "0.5.3"
+ debug "~2.2.0"
+ escape-html "~1.0.3"
+ http-errors "~1.5.0"
+ mime-types "~2.1.11"
+ parseurl "~1.3.1"
+
serve-static@~1.11.2:
version "1.11.2"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.2.tgz#2cf9889bd4435a320cc36895c9aa57bd662e6ac7"
@@ -5796,7 +6117,7 @@ sha.js@2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba"
-sha.js@^2.3.6, sha.js@~2.4.4:
+sha.js@^2.3.6:
version "2.4.5"
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.5.tgz#27d171efcc82a118b99639ff581660242b506e7c"
dependencies:
@@ -5821,22 +6142,6 @@ shallowequal@0.2.x, shallowequal@^0.2.2:
dependencies:
lodash.keys "^3.1.2"
-shasum@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f"
- dependencies:
- json-stable-stringify "~0.0.0"
- sha.js "~2.4.4"
-
-shell-quote@^1.6.1:
- version "1.6.1"
- resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767"
- dependencies:
- array-filter "~0.0.0"
- array-map "~0.0.0"
- array-reduce "~0.0.0"
- jsonify "~0.0.0"
-
shelljs@^0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.4.tgz#b8f04b3a74ddfafea22acf98e0be45ded53d59c8"
@@ -5892,6 +6197,24 @@ sntp@1.x.x:
dependencies:
hoek "2.x.x"
+sockjs-client@1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.2.tgz#f0212a8550e4c9468c8cceaeefd2e3493c033ad5"
+ dependencies:
+ debug "^2.2.0"
+ eventsource "0.1.6"
+ faye-websocket "~0.11.0"
+ inherits "^2.0.1"
+ json3 "^3.3.2"
+ url-parse "^1.1.1"
+
+sockjs@0.3.18:
+ version "0.3.18"
+ resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.18.tgz#d9b289316ca7df77595ef299e075f0f937eb4207"
+ dependencies:
+ faye-websocket "^0.10.0"
+ uuid "^2.0.2"
+
sort-keys@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
@@ -5908,6 +6231,10 @@ source-list-map@^0.1.7, source-list-map@~0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.7.tgz#d4b5ce2a46535c72c7e8527c71a77d250618172e"
+source-list-map@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.1.tgz#1a33ac210ca144d1e561f906ebccab5669ff4cb4"
+
source-map-support@^0.4.2:
version "0.4.3"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.3.tgz#693c8383d4389a4569486987c219744dfc601685"
@@ -5944,6 +6271,26 @@ spdx-license-ids@^1.0.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
+spdy-transport@^2.0.15:
+ version "2.0.18"
+ resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.18.tgz#43fc9c56be2cccc12bb3e2754aa971154e836ea6"
+ dependencies:
+ debug "^2.2.0"
+ hpack.js "^2.1.6"
+ obuf "^1.1.0"
+ readable-stream "^2.0.1"
+ wbuf "^1.4.0"
+
+spdy@^3.4.1:
+ version "3.4.4"
+ resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.4.tgz#e0406407ca90ff01b553eb013505442649f5a819"
+ dependencies:
+ debug "^2.2.0"
+ handle-thing "^1.2.4"
+ http-deceiver "^1.2.4"
+ select-hose "^2.0.0"
+ spdy-transport "^2.0.15"
+
split@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/split/-/split-1.0.0.tgz#c4395ce683abcd254bc28fe1dabb6e5c27dcffae"
@@ -5983,21 +6330,14 @@ store@^1.3.20:
version "1.3.20"
resolved "https://registry.yarnpkg.com/store/-/store-1.3.20.tgz#13ea7e3fb2d6c239868265d686b1d84e99c5be3e"
-stream-browserify@^2.0.0, stream-browserify@^2.0.1:
+stream-browserify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
dependencies:
inherits "~2.0.1"
readable-stream "^2.0.2"
-stream-combiner2@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe"
- dependencies:
- duplexer2 "~0.1.0"
- readable-stream "^2.0.2"
-
-stream-http@^2.0.0, stream-http@^2.3.1:
+stream-http@^2.3.1:
version "2.4.0"
resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.4.0.tgz#9599aa8e263667ce4190e0dc04a1d065d3595a7e"
dependencies:
@@ -6007,17 +6347,14 @@ stream-http@^2.0.0, stream-http@^2.3.1:
to-arraybuffer "^1.0.0"
xtend "^4.0.0"
-stream-splicer@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.0.tgz#1b63be438a133e4b671cc1935197600175910d83"
- dependencies:
- inherits "^2.0.1"
- readable-stream "^2.0.2"
-
strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
+string-hash@^1.1.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b"
+
string-width@^1.0.1, string-width@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -6049,7 +6386,7 @@ string.prototype.padstart@^3.0.0:
es-abstract "^1.4.3"
function-bind "^1.0.2"
-string_decoder@^0.10.25, string_decoder@~0.10.0, string_decoder@~0.10.x:
+string_decoder@^0.10.25, string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
@@ -6104,19 +6441,19 @@ style-loader@0.13.1:
dependencies:
loader-utils "^0.2.7"
-style-loader@^0.13.2:
- version "0.13.2"
- resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.13.2.tgz#74533384cf698c7104c7951150b49717adc2f3bb"
+style-loader@^0.16.1:
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.16.1.tgz#50e325258d4e78421dd9680636b41e8661595d10"
dependencies:
loader-utils "^1.0.2"
-subarg@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
+sugarss@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-0.2.0.tgz#ac34237563327c6ff897b64742bf6aec190ad39e"
dependencies:
- minimist "^1.1.0"
+ postcss "^5.2.4"
-supports-color@3.1.2, supports-color@^3.1.0, supports-color@^3.1.2:
+supports-color@3.1.2, supports-color@^3.1.0, supports-color@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
dependencies:
@@ -6156,12 +6493,6 @@ symbol-tree@^3.2.1:
version "3.2.2"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
-syntax-error@^1.1.1:
- version "1.1.6"
- resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.1.6.tgz#b4549706d386cc1c1dc7c2423f18579b6cade710"
- dependencies:
- acorn "^2.7.0"
-
table@^3.7.8:
version "3.8.3"
resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
@@ -6220,33 +6551,28 @@ tar@^2.0.0, tar@~2.2.0, tar@~2.2.1:
fstream "^1.0.2"
inherits "2"
+tcomb@^2.5.1:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0"
+
text-table@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
-through2@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.1.tgz#384e75314d49f32de12eebb8136b8eb6b5d59da9"
- dependencies:
- readable-stream "~2.0.0"
- xtend "~4.0.0"
-
-through@2, "through@>=2.2.7 <3", through@^2.3.6:
+through@2, through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
-timers-browserify@^1.0.1:
- version "1.4.2"
- resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d"
- dependencies:
- process "~0.11.0"
-
timers-browserify@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86"
dependencies:
setimmediate "^1.0.4"
+tiny-emitter@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-1.0.2.tgz#8e49470d3f55f89e247210368a6bb9fb51aa1601"
+
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
@@ -6281,7 +6607,7 @@ tryit@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb"
-tty-browserify@0.0.0, tty-browserify@~0.0.0:
+tty-browserify@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@@ -6314,6 +6640,10 @@ type-is@~1.6.14:
media-typer "0.3.0"
mime-types "~2.1.13"
+typed-function@0.10.5:
+ version "0.10.5"
+ resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-0.10.5.tgz#2e0f18abd065219fab694a446a65c6d1981832c0"
+
typedarray@~0.0.5:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@@ -6322,7 +6652,16 @@ ua-parser-js@^0.7.9:
version "0.7.10"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.10.tgz#917559ddcce07cbc09ece7d80495e4c268f4ef9f"
-uglify-js@^2.7.5, uglify-js@~2.7.3:
+uglify-js@^2.8.5:
+ version "2.8.22"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.22.tgz#d54934778a8da14903fa29a326fb24c0ab51a1a0"
+ dependencies:
+ source-map "~0.5.1"
+ yargs "~3.10.0"
+ optionalDependencies:
+ uglify-to-browserify "~1.0.0"
+
+uglify-js@~2.7.3:
version "2.7.5"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8"
dependencies:
@@ -6343,10 +6682,6 @@ ultron@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864"
-umd@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.1.tgz#8ae556e11011f63c2596708a8837259f01b3d60e"
-
uniq@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
@@ -6383,7 +6718,14 @@ url-parse@1.0.x:
querystringify "0.0.x"
requires-port "1.0.x"
-url@^0.11.0, url@~0.11.0:
+url-parse@^1.1.1:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.8.tgz#7a65b3a8d57a1e86af6b4e2276e34774167c0156"
+ dependencies:
+ querystringify "0.0.x"
+ requires-port "1.0.x"
+
+url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
dependencies:
@@ -6412,7 +6754,7 @@ util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
-util@0.10.3, "util@>=0.10.3 <1", util@^0.10.3, util@~0.10.1:
+util@0.10.3, "util@>=0.10.3 <1", util@^0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
dependencies:
@@ -6422,7 +6764,7 @@ 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.1, uuid@^2.0.3:
+uuid@^2.0.1, uuid@^2.0.2, uuid@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
@@ -6457,7 +6799,7 @@ verror@1.3.6:
dependencies:
extsprintf "1.0.2"
-vm-browserify@0.0.4, vm-browserify@~0.0.1:
+vm-browserify@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
dependencies:
@@ -6483,7 +6825,7 @@ watchpack@^0.2.1:
chokidar "^1.0.0"
graceful-fs "^4.1.2"
-watchpack@^1.2.0:
+watchpack@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87"
dependencies:
@@ -6491,6 +6833,12 @@ watchpack@^1.2.0:
chokidar "^1.4.3"
graceful-fs "^4.1.2"
+wbuf@^1.1.0, wbuf@^1.4.0:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.2.tgz#d697b99f1f59512df2751be42769c1580b5801fe"
+ dependencies:
+ minimalistic-assert "^1.0.0"
+
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
@@ -6506,6 +6854,15 @@ webpack-core@~0.6.9:
source-list-map "~0.1.7"
source-map "~0.4.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.6.0:
version "1.8.4"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.8.4.tgz#e8765c9122887ce9e3abd4cc9c3eb31b61e0948d"
@@ -6515,6 +6872,28 @@ webpack-dev-middleware@^1.6.0:
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"
+ dependencies:
+ ansi-html "0.0.7"
+ chokidar "^1.6.0"
+ compression "^1.5.2"
+ connect-history-api-fallback "^1.3.0"
+ express "^4.13.3"
+ html-entities "^1.2.0"
+ http-proxy-middleware "~0.17.4"
+ opn "4.0.2"
+ portfinder "^1.0.9"
+ 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"
+ yargs "^6.0.0"
+
webpack-hot-middleware@^2.13.2:
version "2.17.1"
resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.17.1.tgz#0c8fbf6f93ff29c095d684b07ab6d6c0f2f951d7"
@@ -6524,13 +6903,33 @@ webpack-hot-middleware@^2.13.2:
querystring "^0.2.0"
strip-ansi "^3.0.0"
-webpack-sources@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.4.tgz#ccc2c817e08e5fa393239412690bb481821393cd"
+webpack-manifest-plugin@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-1.1.0.tgz#6b6c718aade8a2537995784b46bd2e9836057caa"
+ dependencies:
+ fs-extra "^0.30.0"
+ lodash ">=3.5 <5"
+
+webpack-merge@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.0.tgz#6ad72223b3e0b837e531e4597c199f909361511e"
+ dependencies:
+ lodash "^4.17.4"
+
+webpack-sources@^0.1.0:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.5.tgz#aa1f3abf0f0d74db7111c40e500b84f966640750"
dependencies:
source-list-map "~0.1.7"
source-map "~0.5.3"
+webpack-sources@^0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.3.tgz#17c62bfaf13c707f9d02c479e0dcdde8380697fb"
+ dependencies:
+ source-list-map "^1.1.1"
+ source-map "~0.5.3"
+
webpack@^1.13.1:
version "1.14.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-1.14.0.tgz#54f1ffb92051a328a5b2057d6ae33c289462c823"
@@ -6551,11 +6950,11 @@ webpack@^1.13.1:
watchpack "^0.2.1"
webpack-core "~0.6.9"
-webpack@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.2.1.tgz#7bb1d72ae2087dd1a4af526afec15eed17dda475"
+webpack@^2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.4.1.tgz#15a91dbe34966d8a4b99c7d656efd92a2e5a6f6a"
dependencies:
- acorn "^4.0.4"
+ acorn "^5.0.0"
acorn-dynamic-import "^2.0.0"
ajv "^4.7.0"
ajv-keywords "^1.1.1"
@@ -6563,6 +6962,7 @@ webpack@^2.2.1:
enhanced-resolve "^3.0.0"
interpret "^1.0.0"
json-loader "^0.5.4"
+ json5 "^0.5.1"
loader-runner "^2.3.0"
loader-utils "^0.2.16"
memory-fs "~0.4.1"
@@ -6571,11 +6971,21 @@ webpack@^2.2.1:
source-map "^0.5.3"
supports-color "^3.1.0"
tapable "~0.2.5"
- uglify-js "^2.7.5"
- watchpack "^1.2.0"
- webpack-sources "^0.1.4"
+ uglify-js "^2.8.5"
+ watchpack "^1.3.1"
+ webpack-sources "^0.2.3"
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"
+ dependencies:
+ websocket-extensions ">=0.1.1"
+
+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.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/websocket.js/-/websocket.js-0.1.7.tgz#8d24cefb1a080c259e7e4740c02cab8f142df2b0"
@@ -6680,7 +7090,7 @@ xml-name-validator@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
-xtend@4.0.1, xtend@^4.0.0, xtend@~4.0.0:
+xtend@4.0.1, xtend@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"