Merge branch 'master' into skylight
@ -35,11 +35,14 @@ OTP_SECRET=
|
||||
|
||||
# E-mail configuration
|
||||
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
|
||||
# If you want to use an SMTP server without authentication (e.g local Postfix relay)
|
||||
# then set SMTP_AUTH_METHOD to 'none' and leave SMTP_LOGIN and SMTP_PASSWORD blank
|
||||
SMTP_SERVER=smtp.mailgun.org
|
||||
SMTP_PORT=587
|
||||
SMTP_LOGIN=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM_ADDRESS=notifications@example.com
|
||||
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
|
||||
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
|
||||
#SMTP_AUTH_METHOD=plain
|
||||
#SMTP_OPENSSL_VERIFY_MODE=peer
|
||||
@ -71,6 +74,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
|
||||
# S3_PROTOCOL=https
|
||||
# S3_HOSTNAME=
|
||||
# S3_ENDPOINT=
|
||||
# S3_SIGNATURE_VERSION=
|
||||
|
||||
# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
|
||||
# S3_CLOUDFRONT_HOST=
|
||||
@ -81,3 +85,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
|
||||
# Advanced settings
|
||||
# If you need to use pgBouncer, you need to disable prepared statements:
|
||||
# PREPARED_STATEMENTS=false
|
||||
|
||||
# Cluster number setting for streaming API server.
|
||||
# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
|
||||
STREAMING_CLUSTER_NUM=1
|
||||
|
3
.gitignore
vendored
@ -4,8 +4,9 @@
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile '~/.gitignore_global'
|
||||
|
||||
# Ignore bundler config.
|
||||
# Ignore bundler config and downloaded libraries.
|
||||
/.bundle
|
||||
/vendor/bundle
|
||||
|
||||
# Ignore the default SQLite database.
|
||||
/db/*.sqlite3
|
||||
|
@ -7,7 +7,7 @@ There are three ways in which you can contribute to this repository:
|
||||
2. By working on the back-end application
|
||||
3. By working on the front-end application
|
||||
|
||||
Choosing what to work on in a large open source project is not easy. The list of GitHub issues may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise. If your addition creates a new feature or setting, or otherwise changes how things work in some substantial way, please remember to submit a correlating pull request to document your changes in the [documentation](http://github.com/tootsuite/documentation).
|
||||
Choosing what to work on in a large open source project is not easy. The list of [GitHub issues](https://github.com/tootsuite/mastodon/issues) may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise. If your addition creates a new feature or setting, or otherwise changes how things work in some substantial way, please remember to submit a correlating pull request to document your changes in the [documentation](http://github.com/tootsuite/documentation).
|
||||
|
||||
Below are the guidelines for working on pull requests:
|
||||
|
||||
@ -21,9 +21,17 @@ Below are the guidelines for working on pull requests:
|
||||
- No orthographic mistakes
|
||||
- No Markdown syntax errors
|
||||
|
||||
## Requirements
|
||||
|
||||
- Ruby
|
||||
- Node.js
|
||||
- PostgreSQL
|
||||
- Redis
|
||||
- Nginx (optional)
|
||||
|
||||
## Back-end application
|
||||
|
||||
It is expected that you have a working development environment set up. The development environment includes rubocop, which checks your Ruby code for compliance with our style guide and best practices. Sublime Text, likely like other editors, has a Rubocop plugin that runs checks on files as you edit them. The codebase also has a test suite.
|
||||
It is expected that you have a working development environment set up. The development environment includes [rubocop](https://github.com/bbatsov/rubocop), which checks your Ruby code for compliance with our style guide and best practices. Sublime Text, likely like other editors, has a [Rubocop plugin](https://github.com/pderichs/sublime_rubocop) that runs checks on files as you edit them. The codebase also has a test suite.
|
||||
|
||||
* The codebase is not perfect, at the time of writing, but it is expected that you do not introduce new code style violations
|
||||
* The rspec test suite must pass
|
||||
@ -41,4 +49,3 @@ It is expected that you have a working development environment set up (see back-
|
||||
* If you are introducing new strings, they must be using localization methods
|
||||
|
||||
If the JavaScript or CSS assets won't compile due to a syntax error, it's a good sign that the pull request isn't ready for submission yet.
|
||||
|
||||
|
123
README.md
@ -9,7 +9,7 @@ Mastodon
|
||||
|
||||
Mastodon is a free, open-source social network server. A decentralized solution to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the social network seamlessly.
|
||||
|
||||
An alternative implementation of the GNU social project. Based on ActivityStreams, Webfinger, PubsubHubbub and Salmon.
|
||||
An alternative implementation of the GNU social project. Based on [ActivityStreams](https://en.wikipedia.org/wiki/Activity_Streams_(format)), [Webfinger](https://en.wikipedia.org/wiki/WebFinger), [PubsubHubbub](https://en.wikipedia.org/wiki/PubSubHubbub) and [Salmon](https://en.wikipedia.org/wiki/Salmon_(protocol)).
|
||||
|
||||
Click on the screenshot to watch a demo of the UI:
|
||||
|
||||
@ -48,126 +48,9 @@ If you would like, you can [support the development of this project on Patreon][
|
||||
- **Deployable via Docker**
|
||||
You don't need to mess with dependencies and configuration if you want to try Mastodon, if you have Docker and Docker Compose the deployment is extremely easy
|
||||
|
||||
## Checking out
|
||||
## Deployment
|
||||
|
||||
If you want a stable release for production use, you should use tagged releases. To checkout the latest available tagged version:
|
||||
|
||||
git clone https://github.com/tootsuite/mastodon.git
|
||||
cd mastodon
|
||||
git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
## Configuration
|
||||
|
||||
- `LOCAL_DOMAIN` should be the domain/hostname of your instance. This is **absolutely required** as it is used for generating unique IDs for everything federation-related
|
||||
- `LOCAL_HTTPS` set it to `true` if HTTPS works on your website. This is used to generate canonical URLs, which is also important when generating and parsing federation-related IDs
|
||||
|
||||
Consult the example configuration file, `.env.production.sample` for the full list. Among other things you need to set details for the SMTP server you are going to use.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Ruby
|
||||
- Node.js
|
||||
- PostgreSQL
|
||||
- Redis
|
||||
- Nginx
|
||||
|
||||
## Running with Docker and Docker-Compose
|
||||
|
||||
[](https://microbadger.com/images/gargron/mastodon "Get your own version badge on microbadger.com") [](https://microbadger.com/images/gargron/mastodon "Get your own image badge on microbadger.com")
|
||||
|
||||
The project now includes a `Dockerfile` and a `docker-compose.yml` file (which requires at least docker-compose version `1.10.0`).
|
||||
|
||||
Review the settings in `docker-compose.yml`. Note that it is not default to store the postgresql database and redis databases in a persistent storage location,
|
||||
so you may need or want to adjust the settings there.
|
||||
|
||||
Then, you need to fill in the `.env.production` file:
|
||||
|
||||
cp .env.production.sample .env.production
|
||||
nano .env.production
|
||||
|
||||
Do NOT change the `REDIS_*` or `DB_*` settings when running with the default docker configurations.
|
||||
|
||||
You will need to fill in, at least: `LOCAL_DOMAIN`, `LOCAL_HTTPS`, `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, `OTP_SECRET`, and the `SMTP_*` settings. To generate the `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, and `OTP_SECRET`, you may use:
|
||||
|
||||
Before running the first time, you need to build the images:
|
||||
|
||||
docker-compose build
|
||||
|
||||
|
||||
docker-compose run --rm web rake secret
|
||||
|
||||
Do this once for each of those keys, and copy the result into the `.env.production` file in the appropriate field.
|
||||
|
||||
Then you should run the `db:migrate` command to create the database, or migrate it from an older release:
|
||||
|
||||
docker-compose run --rm web rails db:migrate
|
||||
|
||||
Then, you will also need to precompile the assets:
|
||||
|
||||
docker-compose run --rm web rails assets:precompile
|
||||
|
||||
before you can launch the docker image with:
|
||||
|
||||
docker-compose up
|
||||
|
||||
If you wish to run this as a daemon process instead of monitoring it on console, use instead:
|
||||
|
||||
docker-compose up -d
|
||||
|
||||
Then you may login to your new Mastodon instance by browsing to http://localhost:3000/
|
||||
|
||||
Following that, make sure that you read the [production guide](docs/Running-Mastodon/Production-guide.md). You are probably going to want to understand how
|
||||
to configure Nginx to make your Mastodon instance available to the rest of the world.
|
||||
|
||||
The container has two volumes, for the assets and for user uploads, and optionally two more, for the postgresql and redis databases.
|
||||
|
||||
The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up.
|
||||
|
||||
**Note**: The `--rm` option for docker-compose will remove the container that is created to run a one-off command after it completes. As data is stored in volumes it is not affected by that container clean-up.
|
||||
|
||||
### Tasks
|
||||
|
||||
- `rake mastodon:media:clear` removes uploads that have not been attached to any status after a while, you would want to run this from a periodic cronjob
|
||||
- `rake mastodon:push:clear` unsubscribes from PuSH notifications for remote users that have no local followers. You may not want to actually do that, to keep a fuller footprint of the fediverse or in case your users will soon re-follow
|
||||
- `rake mastodon:push:refresh` re-subscribes PuSH for expiring remote users, this should be run periodically from a cronjob and quite often as the expiration time depends on the particular hub of the remote user
|
||||
- `rake mastodon:feeds:clear_all` removes all timelines, which forces them to be re-built on the fly next time a user tries to fetch their home/mentions timeline. Only for troubleshooting
|
||||
- `rake mastodon:feeds:clear` removes timelines of users who haven't signed in lately, which allows to save RAM and improve message distribution. This is required to be run periodically so that when they login again the regeneration process will trigger
|
||||
|
||||
Running any of these tasks via docker-compose would look like this:
|
||||
|
||||
docker-compose run --rm web rake mastodon:media:clear
|
||||
|
||||
### Updating
|
||||
|
||||
This approach makes updating to the latest version a real breeze.
|
||||
|
||||
1. `git pull` to download updates from the repository
|
||||
2. `docker-compose build` to compile the Docker image out of the changed source files
|
||||
3. (optional) `docker-compose run --rm web rails db:migrate` to perform database migrations. Does nothing if your database is up to date
|
||||
4. (optional) `docker-compose run --rm web rails assets:precompile` to compile new JS and CSS assets
|
||||
5. `docker-compose up -d` to re-create (restart) containers and pick up the changes
|
||||
|
||||
## Deployment without Docker
|
||||
|
||||
Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md) for examples, configuration and instructions.
|
||||
|
||||
## Deployment on Scalingo
|
||||
|
||||
[](https://my.scalingo.com/deploy?source=https://github.com/tootsuite/mastodon#master)
|
||||
|
||||
[You can view a guide for deployment on Scalingo here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Scalingo-guide.md)
|
||||
|
||||
## Deployment on Heroku (experimental)
|
||||
|
||||
[](https://heroku.com/deploy)
|
||||
|
||||
Mastodon can run on [Heroku](https://heroku.com), but it gets expensive and impractical due to how Heroku prices resource usage. [You can view a guide for deployment on Heroku here](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Heroku-guide.md), but you have been warned.
|
||||
|
||||
## Development with Vagrant
|
||||
|
||||
A quick way to get a development environment up and running is with Vagrant. You will need recent versions of [Vagrant](https://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) installed.
|
||||
|
||||
[You can find the guide for setting up a Vagrant development environment here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Vagrant-guide.md)
|
||||
There are guides in the documentation repository for [deploying on various platforms](https://github.com/tootsuite/documentation#running-mastodon).
|
||||
|
||||
## Contributing
|
||||
|
||||
|
6
Vagrantfile
vendored
@ -107,7 +107,11 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
config.hostsupdater.remove_on_suspend = false
|
||||
end
|
||||
|
||||
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'vers=3', 'tcp']
|
||||
if config.vm.networks.any? { |type, options| type == :private_network }
|
||||
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'vers=3', 'tcp']
|
||||
else
|
||||
config.vm.synced_folder ".", "/vagrant"
|
||||
end
|
||||
|
||||
# Otherwise, you can access the site at http://localhost:3000
|
||||
config.vm.network :forwarded_port, guest: 80, host: 3000
|
||||
|
Before Width: | Height: | Size: 334 KiB |
Before Width: | Height: | Size: 369 KiB |
Before Width: | Height: | Size: 366 KiB |
Before Width: | Height: | Size: 349 KiB |
Before Width: | Height: | Size: 373 KiB |
Before Width: | Height: | Size: 331 KiB |
Before Width: | Height: | Size: 370 KiB |
Before Width: | Height: | Size: 362 KiB |
Before Width: | Height: | Size: 378 KiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 7.6 KiB |
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><g fill="#189efc"><path d="M500 0A500 500 0 0 0 0 500a500 500 0 0 0 500 500 500 500 0 0 0 500-500A500 500 0 0 0 500 0zm-2.5 271.1h107.24c-20.56 14.471-27.24 57.064-27.24 78.927v202.145c0 43.726-35.202 78.928-80 78.928s-80-35.202-80-78.928V350.027c0-43.725 35.202-78.927 80-78.927zm-276 48.9c44.798 0 80 35.202 80 78.928v202.144c0 21.863 6.68 64.456 27.24 78.928H221.5c-44.798 0-80-35.202-80-78.928V398.928c0-43.726 35.202-78.928 80-78.928zm550.24 0c44.799 0 80 35.202 80 78.928v202.144c0 43.726-35.201 78.928-80 78.928H664.5c20.56-14.472 27.24-57.065 27.24-78.928V398.928c0-43.726 35.202-78.928 80-78.928z"/><g transform="translate(-2)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g><g transform="matrix(1 0 0 -1 274 951)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g><g transform="matrix(-1 0 0 1 995 0)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><path d="M500 0a500 500 0 0 0-353.553 146.447 500 500 0 1 0 707.106 707.106A500 500 0 0 0 500 0zm-.059 280.05h107.12c-19.071 13.424-26.187 51.016-27.12 73.843V562.05c0 44.32-35.68 80-80 80s-80-35.68-80-80v-202c0-44.32 35.68-80 80-80zm-.441 52c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zm-279.059 7.9c44.32 0 80 35.68 80 80v206.157c.933 22.827 8.049 60.42 27.12 73.842H220.44c-44.32 0-80-35.68-80-80v-200c0-44.32 35.68-80 80-80zm559.12 0c44.32 0 80 35.68 80 80v200c0 44.32-35.68 80-80 80H672.44c19.071-13.424 26.187-51.016 27.12-73.843V419.95c0-44.32 35.68-80 80-80zM220 392c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm560 0c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm-280.5 40.05c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zM220 491.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zM499.5 532c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zM220 591.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28z" fill="#189efc"/></svg>
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.5 KiB |
@ -126,7 +126,8 @@ export function expandAccountTimeline(id) {
|
||||
max_id: lastId
|
||||
}
|
||||
}).then(response => {
|
||||
dispatch(expandAccountTimelineSuccess(id, response.data));
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(expandAccountTimelineSuccess(id, response.data, next));
|
||||
}).catch(error => {
|
||||
dispatch(expandAccountTimelineFail(id, error));
|
||||
});
|
||||
@ -257,11 +258,12 @@ export function expandAccountTimelineRequest(id) {
|
||||
};
|
||||
};
|
||||
|
||||
export function expandAccountTimelineSuccess(id, statuses) {
|
||||
export function expandAccountTimelineSuccess(id, statuses, next) {
|
||||
return {
|
||||
type: ACCOUNT_TIMELINE_EXPAND_SUCCESS,
|
||||
id,
|
||||
statuses
|
||||
statuses,
|
||||
next
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -73,9 +73,13 @@ export function mentionCompose(account, router) {
|
||||
|
||||
export function submitCompose() {
|
||||
return function (dispatch, getState) {
|
||||
const status = emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], ''));
|
||||
if (!status || !status.length) {
|
||||
return;
|
||||
}
|
||||
dispatch(submitComposeRequest());
|
||||
api(getState).post('/api/v1/statuses', {
|
||||
status: emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], '')),
|
||||
status,
|
||||
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
|
||||
media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
|
||||
sensitive: getState().getIn(['compose', 'sensitive']),
|
||||
|
@ -0,0 +1,34 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
||||
|
||||
const AttachmentList = React.createClass({
|
||||
propTypes: {
|
||||
media: ImmutablePropTypes.list.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default AttachmentList;
|
@ -63,6 +63,10 @@ const AutosuggestTextarea = React.createClass({
|
||||
this.props.onSuggestionsClearRequested();
|
||||
}
|
||||
|
||||
// auto-resize textarea
|
||||
e.target.style.height = 'auto';
|
||||
e.target.style.height = `${e.target.scrollHeight}px`;
|
||||
|
||||
this.props.onChange(e);
|
||||
},
|
||||
|
||||
|
@ -78,7 +78,8 @@ const Item = React.createClass({
|
||||
attachment: ImmutablePropTypes.map.isRequired,
|
||||
index: React.PropTypes.number.isRequired,
|
||||
size: React.PropTypes.number.isRequired,
|
||||
onClick: React.PropTypes.func.isRequired
|
||||
onClick: React.PropTypes.func.isRequired,
|
||||
autoPlayGif: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
@ -158,15 +159,21 @@ const Item = React.createClass({
|
||||
/>
|
||||
);
|
||||
} else if (attachment.get('type') === 'gifv') {
|
||||
const autoPlay = !isIOS() && this.props.autoPlayGif;
|
||||
|
||||
thumbnail = (
|
||||
<video
|
||||
src={attachment.get('url')}
|
||||
onClick={this.handleClick}
|
||||
autoPlay={!isIOS()}
|
||||
loop={true}
|
||||
muted={true}
|
||||
style={gifvThumbStyle}
|
||||
/>
|
||||
<div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }} className={`media-gallery__gifv ${autoPlay ? 'autoplay' : ''}`}>
|
||||
<video
|
||||
src={attachment.get('url')}
|
||||
onClick={this.handleClick}
|
||||
autoPlay={autoPlay}
|
||||
loop={true}
|
||||
muted={true}
|
||||
style={gifvThumbStyle}
|
||||
/>
|
||||
|
||||
<span className='media-gallery__gifv__label'>GIF</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -192,7 +199,8 @@ const MediaGallery = React.createClass({
|
||||
media: ImmutablePropTypes.list.isRequired,
|
||||
height: React.PropTypes.number.isRequired,
|
||||
onOpenMedia: React.PropTypes.func.isRequired,
|
||||
intl: React.PropTypes.object.isRequired
|
||||
intl: React.PropTypes.object.isRequired,
|
||||
autoPlayGif: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
@ -227,7 +235,7 @@ const MediaGallery = React.createClass({
|
||||
);
|
||||
} else {
|
||||
const size = media.take(4).size;
|
||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={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 (
|
||||
|
@ -5,6 +5,7 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
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';
|
||||
@ -29,6 +30,7 @@ const Status = React.createClass({
|
||||
onBlock: React.PropTypes.func,
|
||||
me: React.PropTypes.number,
|
||||
boostModal: React.PropTypes.bool,
|
||||
autoPlayGif: React.PropTypes.bool,
|
||||
muted: React.PropTypes.bool
|
||||
},
|
||||
|
||||
@ -76,10 +78,12 @@ const Status = React.createClass({
|
||||
}
|
||||
|
||||
if (status.get('media_attachments').size > 0 && !this.props.muted) {
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
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} />;
|
||||
media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ const messages = defineMessages({
|
||||
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: 'Reblog' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||
@ -95,10 +96,19 @@ const StatusActionBar = React.createClass({
|
||||
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 style={{ marginTop: '10px', overflow: 'hidden' }}>
|
||||
<div style={{ float: 'left', marginRight: '18px'}}><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_id', null) === null ? 'reply' : 'reply-all'} onClick={this.handleReplyClick} /></div>
|
||||
<div style={{ float: 'left', marginRight: '18px'}}><IconButton title={reply_title} icon={reply_icon} onClick={this.handleReplyClick} /></div>
|
||||
<div style={{ float: 'left', marginRight: '18px'}}><IconButton disabled={status.get('visibility') === 'private' || status.get('visibility') === 'direct'} active={status.get('reblogged')} title={intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
|
||||
<div style={{ float: 'left', marginRight: '18px'}}><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
|
||||
|
||||
|
@ -48,6 +48,7 @@ import es from 'react-intl/locale-data/es';
|
||||
import fi from 'react-intl/locale-data/fi';
|
||||
import fr from 'react-intl/locale-data/fr';
|
||||
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';
|
||||
@ -57,6 +58,7 @@ 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 { localeData as zh_hk } from '../locales/zh-hk';
|
||||
import pt_br from '../locales/pt-br';
|
||||
import getMessagesForLocale from '../locales';
|
||||
import { hydrateStore } from '../actions/store';
|
||||
import createStream from '../stream';
|
||||
@ -77,8 +79,10 @@ addLocaleData([
|
||||
...fi,
|
||||
...fr,
|
||||
...hu,
|
||||
...it,
|
||||
...ja,
|
||||
...pt,
|
||||
...pt_br,
|
||||
...nl,
|
||||
...no,
|
||||
...ru,
|
||||
|