Compare commits

...

1076 Commits
v1.0 ... v1.2

Author SHA1 Message Date
Eugen
1955a3f444 Do not display "reset password" in admin UI for remote accounts (#1960) 2017-04-16 23:15:58 +02:00
Eugen
8ebed7fc68 Fix #1957 - WhatLanguage can return null. Fallback to 'en' (#1959) 2017-04-16 23:12:19 +02:00
Eugen
8f2ed79a0b Fix bug mentioned in #1565 (#1954) 2017-04-16 21:02:54 +02:00
Matt Jankowski
2b6b89491d Remove unused registrations: key from PT yml locale (#1952) 2017-04-16 20:53:16 +02:00
Eugen
f902a335f9 Fix #1870 - Strip control characters out of strings in AtomSerializer (#1876)
* Fix #1870 - Strip control characters out of strings in AtomSerializer

* Adjust according to comment by @alpaca-tc
2017-04-16 20:32:27 +02:00
Eugen
e4af4898de Add language detection (#1772)
* Add language detection via WhatLanguage and (de)serialization of it through Atom

* Fix default language in ProcessFeedService

* Re-add newline before 'react-rails' Gem to fix groupings

Fixes Code Climate issue
2017-04-16 20:32:17 +02:00
Eugen
6d70a80263 Onboarding modal (#1883)
* Basic onboarding modal that's shown to users once

* Lay out pages 2 through 5, add images, style modals (#1509)

* Lay out pages 2 through 5

Added images and laid out pages 2 through 5 in the jsx file. SCSS will
come, still working on just seeing if this works at all.

* Fix jsx errors, add images to modal pages, style modal pages

* Add animations to onboarding pager changes, improve wording and styling

* Finishing touches on the onboarding

* Add missing propTypes

* Update wording
2017-04-16 20:32:00 +02:00
Wonderfall
0cbcc5e297 Update node.js and imagemagick (#1951)
* update Dockerfile: latest nodejs LTS

* also update imagemagick
2017-04-16 20:28:25 +02:00
Matt Jankowski
f87b51fda8 I18n health warnings (#1949)
* Rename admin.domain_block to admin.domain_blocks in prep for i18n improvement

* Use implicit controller/action path for i18n in admin/domain_blocks

* Add DomainBlock#accounts has_many

* Avoid i18n health warning for `en` locale by using symbol scope with :count

* Remove unused i18n key: plaintext_secret_html

* Remove unused i18n key two_factor_auth.warning

* Remove final will_paginate i18n keys

* Remove unused key two_factor_auth.recovery_codes

* Remove unused key: admin.reports.comment.none

* Remove unused reports. i18n namespace (moved to admin.reports)

* Ignore keys from locales which override activemodel and activerecord errors

* Revert "Remove unused key: admin.reports.comment.none"

This reverts commit 350ef2685fadc069e619bb6d1066190de195d942.

* Update i18n key reference to match moved location

* Add missing `en` keys to i18n

* Tell i18n-tasks to ignore missing attributes that dont need overwriting

* Add i18n-tasks unused to travis
2017-04-16 19:37:01 +02:00
Eugen
7e2e0d6dcc Fix #1670 - Update OStatus2 gem (#1936) 2017-04-16 19:32:47 +02:00
Eugen
181115422c Disable CodeClimate duplication checks (#1943) 2017-04-16 18:26:46 +02:00
oliverkeeble
86d1dcc97a Fix translation of 'reblogged' in eo.yml (#1942) 2017-04-16 18:11:07 +02:00
Eduardo Elias
86eaaf0761 Add missing keys to PT locale (#1941) 2017-04-16 18:04:19 +02:00
alpaca-tc
19f63ff801 Check @recipient.user at the first (#1939) 2017-04-16 18:04:05 +02:00
Eugen
babbb2135e Fix #1813 - Alleviate extra requests when processing mentions (#1938)
The <link rel="mentioned" /> tag refers to accounts by href. So we were
matching the DB by the url attribute, and falling back to HTTP look-up.
However, GS and Mastodon use profile URLs as URIs, too, and the match
for that was missing. This could potentially alleviate some extra network
requests
2017-04-16 18:01:48 +02:00
Ash Furrow
99226aba93 Adds note for instance admins. (#1925)
* Adds note for instance admins.

* Addresses feedback from #1925.
2017-04-16 16:59:53 +02:00
Eugen
42c9d5111a Add README note about tagged releases (#1927) 2017-04-16 16:42:46 +02:00
Matt Jankowski
e0b5a94a4b Clean up check that account needs a webfinger update (#1932) 2017-04-16 16:38:29 +02:00
Matt Jankowski
26ec042f38 Remove trailing whitespace in terms.no.html (#1933) 2017-04-16 16:38:13 +02:00
Matt Jankowski
73b0af5c93 Simplify the og:image and og:description code in stream_entries/show (#1934) 2017-04-16 16:38:02 +02:00
Matt Jankowski
7efde22c3a Use local vars in partials (#1935)
* Use local vars in accounts/header partial

* Use local variable in 2fa recovery codes partial
2017-04-16 16:37:49 +02:00
abcang
90760eae4c fix regex filter (#1845)
* fix regex filter

* fixed br to linebreak and, stlip tags.

* change to send raw content

* changed to unescape in reducer
2017-04-16 16:33:38 +02:00
tackeyy
fc34e0e191 Remove .keep in models (#1892) 2017-04-16 16:28:52 +02:00
alpaca-tc
de72db99fa Add presence validation to Import (#1928)
```
*An* `ActiveRecord::StatementInvalid` *occurred while* `POST </settings/import>` *was processed by* `imports#create`
Exception
----------------
PG::NotNullViolation: ERROR:  null value in column "type" violates not-null constraint
```
2017-04-16 16:28:26 +02:00
Alda Marteau-Hardi
77d1447ac4 Add the licence key in package.json (#1914) 2017-04-16 16:08:11 +02:00
mshrtkch
2c329f2b69 Fix translation related to "mute" (#1926) 2017-04-16 16:07:58 +02:00
Kazuhiro NISHIYAMA
c2762fa498 Add missing Japanese translations (#1923)
And `i18n-tasks add-missing -l ja` changes some quotes.
2017-04-16 16:07:45 +02:00
Akihiko Odaki
0611209141 Relax Ruby version requirement (#1901) 2017-04-16 14:57:30 +02:00
Eduardo Elias
da302a43cd Improve PT locale on simple_form (#1917)
* Improve PT locale on simple_form

* Add missing keys to PT locale
2017-04-16 14:56:04 +02:00
alpaca-tc
8f8319852c Fixed NoMethodError in UnfollowService (#1918) 2017-04-16 14:55:43 +02:00
Matt Jankowski
75f416a492 Fix yaml issue in ja locale (#1916)
* Fix yaml parse issue in ja.yml locale

* Fix issue in locales/ja.jsx
2017-04-16 14:55:04 +02:00
Naouak
24baaa17e8 Syntax error in japanese localisation (#1920)
assets:precompile was failling because of this missing comma.
2017-04-16 14:54:09 +02:00
Erwan Leboucher
c99fc08a0d Add missing french translation. (#1906)
* Add missing french translation.

Causing this error:
[React Intl] Missing message: "navigation_bar.mutes" for locale: "fr",

* Update fr.jsx
2017-04-16 14:15:03 +02:00
Yuki Nakagawa
a8f45c0838 Enlarge font size to avoid autozooming of iPhone. (#1911) 2017-04-16 14:05:16 +02:00
George Hattori
6df63465b9 Improve Japanese translation (#1909) 2017-04-16 13:44:57 +02:00
Matt Jankowski
13b11ddc8c Add binstub for rspec from rspec-core (#1913) 2017-04-16 13:42:45 +02:00
新都心(Neet Shin)
f2997c9715 Update Japanese Translate (#1903)
* [Update ja.jsx] Add Muted, Video_error and sorted

* [Update ja] Added Recovery code's translation

* [Update ja] Added Two-fact/Recovery's translation

* Update ja.jsx
2017-04-16 13:42:16 +02:00
Eugen
e17f9d5e1a Unite all mandatory rake tasks in mastodon:daily (#1887)
* Unite all mandatory rake tasks in mastodon:daily
Add mastodon:media:remove_remote task
Make mastodon:maintenance:add_static_avatars more resilient to exceptions

* Fix typo in task description
2017-04-16 12:53:58 +02:00
Alex Dunn
865cb39e9b lock capistrano to 3.8.0 (#1890) 2017-04-16 12:52:18 +02:00
Eugen
5d710b1139 Make file attachment on MediaAttachment optional (#1865)
Create MediaAttachment but without actual file download when domain is blocked with reject_media set to true
Clean up old media files when creating a new domain block with reject_media set to true
Return remote_url in media attachments API if local file is not present
Undo domain block action in admin UI
Ability to enable reject_media from admin UI
2017-04-16 12:51:30 +02:00
Eugen
8a58942c80 Fix up recovery codes design a little (#1866) 2017-04-16 04:32:57 +02:00
luigi
f97272549c Remove unused gem: coffee-rails (#1885) 2017-04-16 04:02:29 +02:00
Joachim Viide
363de2dffd Leave out the "Expires" header from S3 uploads (#1886) 2017-04-16 04:01:58 +02:00
Alex Dunn
6a1ac9b31f capistrano: allow overriding repo_url and branch (#1889) 2017-04-16 04:01:27 +02:00
Matt Jankowski
3834e1e69b View spec fix (#1888)
* Add option to disable verify partial doubles

* Add show_landing_strip? helper method

* Use show_landing_strip? helper in accounts and stream entries views

* Fix naming in view specs
2017-04-16 03:40:33 +02:00
Eugen
95bcbaa434 Fix #1852 - Ensure feeds have valid <title> tags (#1875) 2017-04-16 02:40:36 +02:00
Isabelle Knott
911338bdcc Show error message if video cannot be loaded (#1879) 2017-04-16 01:12:47 +02:00
Ash Furrow
0f8b7d0660 Removed Heroku in-process spawning. (#1873) 2017-04-15 23:02:13 +02:00
Eugen
e332552816 Indicate when a toot is part of a chain with a "reply all" icon (#1869) 2017-04-15 22:48:41 +02:00
Eugen
0d83569899 Fix cross-origin integrity (#1871)
See <https://glitch.social/users/bea/updates/434>
2017-04-15 22:48:30 +02:00
Eugen
515434ed87 English localization for mention notification was missing (#1867) 2017-04-15 22:48:17 +02:00
Naouak
3d3e32befb Check for a custom css file to help customization of instances (#1368)
* User can create a custom.scss to customize their instance without modifying gitted files.

* Add documentation for customization.

* Forgot the helper file

* Fix Style to pass codeclimate

* Requests from maintainer.
2017-04-15 22:47:48 +02:00
Eugen
fa08b5079d Make the rake mastodon:users:clear task properly clear out unconfirmed users (#1777)
Before it cleared out user records only (e-mail, password) without
freeing up the associated username (account record). Furthermore, since
these records have no dependent records (due to no user activity)
they can be deleted quickly with delete_all instead of destroy
2017-04-15 21:55:28 +02:00
alpaca-tc
00392d3c63 ActiveRecord::NotFound is not defined (#1864) 2017-04-15 21:17:59 +02:00
oliverkeeble
28606d730a Fix typo in simple_form.eo.yml (#1848) 2017-04-15 16:46:48 +02:00
Marcin Cieślak
1c8477eab2 Give SINGLE_USER a chance to register (#1820)
An attempt to open a brand new Mastodon instance configured
as SINGLE_USER_MODE=true will cause an exception.

Enable temporary registration if we have no users in the database

Fixes #1817
2017-04-15 16:46:27 +02:00
Matt Jankowski
6670e6d33f Add password reset for users from admin accounts area (#1841) 2017-04-15 16:44:59 +02:00
Henry Smith
9d2f55ecc3 Remove isRequired from optional prop (#1843) 2017-04-15 16:44:28 +02:00
Henry Smith
b7ec2fd492 Fix function call name typo (#1851) 2017-04-15 16:41:22 +02:00
Matt Jankowski
3b8908c114 About page contact email (#1839)
* Correct site_contact_email typo

* Separate about more page into partials, add specs
2017-04-15 13:33:25 +02:00
Hiromi Kai
7b10794afb Add rails-i18n gem (#1837) 2017-04-15 13:32:53 +02:00
Matt Jankowski
355965c17b Silence Devise deprecation warning about TestHelpers (#1840)
Devise changed their approach -
3f3ec236bb

This change silences a deprecation warning about `TestHelpers`
2017-04-15 13:30:57 +02:00
Alex Dunn
0f889523e4 views/about: use Setting.site_title instead of hardcoding (#1508) 2017-04-15 13:28:09 +02:00
Zac Anger
f4045ba3d9 Add eslint-plugin-jsx-a11y (#1651)
* Add eslint-plugin-jsx-a11y.

* Fix npm script.

* Adjust npm scripts so test also runs lint.

* Fix existing lint errors.

* Don't break on a11y issues.

* Add role and tabIndex.

* Add vim and Mac files to .gitignore and .dockerignore.

* Handle htmlFor (partially), a that's actually a button.

* Fix missing tabIndex.

* Add cursor:pointer to load-more

* Revert change to load_more.

* Fixes based on review.

* Update yarn.lock.

* Don't try to install fsevents on Linux (hides warning noise).
2017-04-15 13:27:27 +02:00
Patrick Figel
df4ff9a8e1 Add recovery code support for two-factor auth (#1773)
* Add recovery code support for two-factor auth

When users enable two-factor auth, the app now generates ten
single-use recovery codes. Users are encouraged to print the codes
and store them in a safe place.

The two-factor prompt during login now accepts both OTP codes and
recovery codes.

The two-factor settings UI allows users to regenerated lost
recovery codes. Users who have set up two-factor auth prior to
this feature being added can use it to generate recovery codes
for the first time.

Fixes #563 and fixes #987

* Set OTP_SECRET in test enviroment

* add missing .html to view file names
2017-04-15 13:26:03 +02:00
luigi
67ad84b7eb Add some missing Spanish translations (#1818)
* Add missing spanish translations on user settings

simple_form.es.yml

* Fix typo: Aute/ción. should be Autenticación
2017-04-15 13:25:00 +02:00
Amakasu Ryoma
f0f6a3279a Update ja.jsx (#1822)
* Add getting_started.apps
* Add search_results.total
* Fix notifications.settings
2017-04-15 13:24:33 +02:00
Setuu
9e620ca16c Fix Japanese translation of ja.yml (#1835) 2017-04-15 13:18:08 +02:00
Alda Marteau-Hardi
bddd36f260 Add a classname to the loading bar (#1826) 2017-04-15 13:17:54 +02:00
Keiji, Yoshimi
b857551617 fixed privacy mistake of japanese translation of privacy.change (#1832) 2017-04-15 13:17:20 +02:00
Andrew
e28a5aab08 Add tasks for open/close registration (#1823)
* Add tasks for open/close registration

* Code climate style fix

* Use true instead of string 'true'
2017-04-15 13:17:07 +02:00
alpaca-tc
f9d7ec8971 ActiveRecord::Relation does not respond to #id (#1834) 2017-04-15 13:16:24 +02:00
Matt Jankowski
40fd1de488 Account search service refactor (#1791)
* Begin coverage for account search service

* Coverage for hashtag query

* Coverage for calling local vs remote find based on domain presence

* Spec to check that exact matches are not duped

* Coverage of resolve option

* Coverage for account being provided

* Start to refactor account search service

* Isolate query username and domain methods

* Isolate exact_match method

* Extract methods for local and remote results

* Simplify local vs remote and account isoliation

* Extract methods for local and remote results

* Simplify de-dupe of exact match

* Simplify logic to check for non exact remotes

* Cache some methods

* Remove nil from exact_match from results array

* Return exact matches first

* Use find_remote even with no domain

Account.find_local is just an alias for Account.find_remote(user, nil) - so we
can not bother with the conditional here, and call find_remote directly.
2017-04-15 03:17:07 +02:00
ThibG
31f0bcf804 Refresh webfinger (#1323)
* Refresh local info for remote accounts when webfinger returns new values

It only refreshes account info if one of the URLs or the public-key changes,
in which cases it refreshes the full info, re-downloading the feeds from that
user.

Some special handling should probably be done when the public key changes,
but I have been unable to find any use for it in Mastodon yet.

* Re-fetch remote users we aren't subscribed to.

This might induce performance issues, we might want to only do that for users
we explicitly attempted to subscribe but failed to.

* Refactor changes

* Do not refresh existing remote account details more than once a day

* Avoid re-fetching webfinger info in tests unless otherwise specified
2017-04-15 03:16:05 +02:00
Matt Jankowski
09540192c9 Rename admin/accounts_helper to admin/filter_helper (#1816)
- Add some spec coverage for both methods
- Add explicit constant call-outs for where the params are from
2017-04-15 03:09:55 +02:00
Koala Yeung
08059ddda9 jslint: fix jslint warnings (#1704)
* jslint: remove trailing space

* jslint: fix no-nested-ternary issue

Follow the jslint [no-nested-ternary](http://eslint.org/docs/rules/no-nested-ternary)
rule. Rewritten ternary with if-then.
2017-04-15 02:57:26 +02:00
goofy-bz
a3aa9381c4 Update devise.fr.yml (#1728)
Very light grammar fix to improve gender-neutral
2017-04-15 02:55:43 +02:00
Shouko Yu
120a37a197 Add translations for zh-TW (Taiwan) (#1794) 2017-04-15 02:49:43 +02:00
Koala Yeung
ec9999cdfe Update Traditional Chinese, HK (zh-HK) translation (#1759)
* Fix missing string in javascript locale (zh-HK)

* Change javascript locale (zh-HK)

* Fix some other strings that were still English.
* Improve `search.status_by`.
* Fix `notification.follow`. ("開始開始你")
* Changes according to user feedback.
  * "Back": "較前顯示" -> "返回"
  * "Home": "家" -> "主頁"

* Update ruby locale (zh-HK)

* Fix missing translation
* Normalize yml
* Update translation strings
2017-04-15 02:48:30 +02:00
Fjoerfoks
e806d3c3f0 Update nl.yml (#1764)
Some rewording, typos and switching to infinitve
2017-04-15 02:45:46 +02:00
Fjoerfoks
4ba6acd518 Update nl.jxs (#1766)
Some rewording and switching to infinitive localization.
2017-04-15 02:44:33 +02:00
Olivier Humbert
061922b38c Update devise.fr.yml (#1792)
* Update devise.fr.yml

Consistencies across the French translation

* Update doorkeeper.fr.yml
2017-04-15 02:44:10 +02:00
Ratmir Karabut
5d8d827436 Update Russian translation (#1733)
* Add Russian translation (ru)

* Fix a missing comma

* Fix the wording for better consistency

* Update Russian translation

* Arrange Russian setting alphabetically

* Fix syntax error

* Update Russian translation

* Fix formatting error
2017-04-15 02:43:51 +02:00
Matt Jankowski
cde1f37d93 Use site title on tags#show page (#1802) 2017-04-15 02:37:20 +02:00
Ben Roberts
89707ad0ac add basic microformats tests (#1803)
as suggested, moving to view tests rather than a controller test
replaces https://github.com/tootsuite/mastodon/pull/1786 which i will
close momentary
2017-04-15 02:37:00 +02:00
Thor Harald Johansen
4bebeb27d3 More Norwegian translations (#1805)
* Working translation for Norwegian.

* Fixes to Norwegian translation.

* Further adjustments to Norwegian translation.

* Further adjustments to Norwegian translation.

* Yet more improvements to the Norwegian translation.

* More Norwegian translations. Better terminology.
2017-04-15 02:33:37 +02:00
Joachim Viide
ef879a8839 Send initial state in a <script type="application/json"> tag (#1806) 2017-04-15 02:32:42 +02:00
Darío Hereñú
9240ca6cef Mispelling & minor fixes (#1814) 2017-04-15 02:31:16 +02:00
Matt Jankowski
619817d29e Remove unused will_paginate.page_gap key from i18n (#1815)
This value was changed recently, and every locale which had it set was using the
same value as the default. This value is still the default in the new location.
2017-04-15 02:30:55 +02:00
Alex Dunn
0655f16cc1 [css] reduce spacing between text elements on about pages (#1510) 2017-04-15 02:29:32 +02:00
rysiekpl
ade004b5ee Polish translation (needs more love though) (#807)
* Polish translation (needs more love though)

* Polish translation bugfix

* bugfix for new colon-containing texts

* another bugfix. yaml is evil

* minor fix

* fixing issues pointed out by reviewers

* Uwierzytelnianie dwustopniowe -> dwuetapowe, as suggested by a reviewer

* Etyczny dizajn zbyt sarkastyczny, to będą założenia
2017-04-15 02:22:46 +02:00
Les Orchard
7609593e48 Add REDIS_DB env variable to configure Redis database (#1366) 2017-04-15 02:21:13 +02:00
ThibG
a9529d3b4b Allow running mastodon on a different domain as the one used for identifying users (#1267)
* Allow running mastodon on a different domain as the one used for identifying users

* Alter documentation of WEB_DOMAIN to make clear it shouldn't be used unless the admin knows what they are doing

* Compare to web_domain instead of local_domain when dealing with feeds/API

* Correctly identify mentions to local accounts

Mentions URLs point to the person's web profile, i.e., the user page served on WEB_DOMAIN.
2017-04-15 02:15:46 +02:00
Pierre Ozoux
0d2910478a Use image too in docker-compose (#1109)
* Use image too in docker-compose

It is possible with version 2 of compose to use both `build` and `image`

* Update docker-compose.yml
2017-04-15 02:07:59 +02:00
Valentin Lorentz
5ab0ffc6c8 Custom Paperclip path. (#778)
* Custom Paperclip path.

* Document PAPERCLIP_ROOT.

* Add PAPERCLIP_ROOT_URL (and rename PAPERCLIP_ROOT to PAPERCLIP_ROOT_PATH).
2017-04-15 02:07:21 +02:00
Alex Dunn
8482f67caf update Node to 6.x LTS (#1228)
The 4.x branch [entered maintenance](https://github.com/nodejs/LTS#lts-schedule1) on 1 April.
2017-04-15 02:05:41 +02:00
Patrick Figel
fe8dd58bc1 Add list of muted user to UI and Getting Started (#1799)
Add the same UI that already exists for blocked users for muted
ones and add it to the "Getting Started" menu.
2017-04-15 01:23:49 +02:00
Chad Pytel
92cd207c50 Introduce capybara and first feature spec (#1801)
This commit introduces Capybara and the first feature spec.

I focused on coverage for log in for the first feature spec because that would
have prevented 624a9a7136 causing #1236.
2017-04-15 01:21:02 +02:00
Matt Jankowski
f5cd138323 Improve i18n chooser (#1804)
* Add locale spec with failing locale plus region check

* Use a more accurate locale when supplied by browser headers

Previously we were using a matching option which would use the first locale
available which matched the locale portion, even if a region was specified.

This changes to first try to find an exact match, and then fall back to the
region, and then fall back to the  default.

* Clean up default_locale method
2017-04-15 01:12:39 +02:00
Kazuhiro NISHIYAMA
66ea015a01 Remove current directory from PATH (#1779) 2017-04-14 19:10:38 +02:00
Kazuhiro NISHIYAMA
1616cf98a1 Add missing Japanese translations (#1785) 2017-04-14 19:10:12 +02:00
Takayuki KUSANO
5aae6384ff Fix Japanese translation for remote_follow (#1771)
Current remote_follow.acct translation inappropriate. Users may input their own acccount. So fix the wording.
2017-04-14 19:09:52 +02:00
Luc Didry
fa89deb4eb Add title attribute to URLs in statuses (#1755)
Since URLs in statuses are truncated, it would be pleasant to see the
full URL when hovering the URL (like on twitter, yes).
2017-04-14 13:22:56 +02:00
Matt Jankowski
26a892dd90 Fix invalid byte sequence issue in nl.jsx (#1751) 2017-04-14 13:00:03 +02:00
Effy Elden
8321884eef Change usage of gsub to delete, as per Code Climate/Rubocop recommendation (#1753) 2017-04-14 12:50:00 +02:00
西小倉宏信
290e8ef854 Remove .keep in models (#1748) 2017-04-14 12:43:29 +02:00
Bryce Chidester
067eb220c6 Include pkg-config as a gem dependency. (#1717)
The pkg-config gem was necessary in order for nokogiri to find the system
libraries when building on OpenBSD.

Closes #1637

Signed-off-by: Bryce Chidester <bryce@cobryce.com>
2017-04-14 11:15:03 +02:00
JP DeVries
1467515d3d Use landmark regions on columns (#1732)
Closes mastodon#1720
2017-04-14 11:12:59 +02:00
Matt Jankowski
8b74aa4217 Admin reports controller improvements (#1714)
* Simplify admin/reports controller filtering for index

* Rename parameter to resolved

* Fix issue where reports view could not access filter_link_to

* Add coverage for admin/reports controller

* DRY up resolution of related reports for target account

* Clean up admin/reports routes

* Add Report#statuses method

* DRY up current account action taken params

* Rubocop styles
2017-04-14 11:10:28 +02:00
maxypy
a6807201d2 Implementing Subresource Integrity (#1729)
* Add sprockets-rails to Gemfile

* Add sprockets-rails to Gemfile.lock

* Update show.html.haml

* Update index.html.haml

* Update admin.html.haml

* Update auth.html.haml

* Update embedded.html.haml

* Update public.html.haml
2017-04-14 11:09:20 +02:00
Ian Kelling
4445ebdad2 Fix getting started commands out of order (#1737)
In current order, docker-compose build results in
ERROR: Couldn't find env file: /a/hopt/mastodon/.env.production
2017-04-14 11:04:19 +02:00
Effy Elden
c019b0acfd Modify ActionMailer configuration to default to ENV['LOCAL_DOMAIN'] if ENV['SMTP_DOMAIN'] is not set. The previous fallback, config.x.local_domain, is undefined when the code is called. (#1745) 2017-04-14 11:02:25 +02:00
Eugen Rochko
7447e7a2ea Merge branch 'matteoaquila-master' 2017-04-13 22:26:54 +02:00
Eugen Rochko
c8be05a4a7 Fix italian translation 2017-04-13 22:26:32 +02:00
Eugen Rochko
41c697fd81 Merge branch 'master' of git://github.com/matteoaquila/mastodon into matteoaquila-master 2017-04-13 22:25:07 +02:00
Eugen
c5afe573da Fix drag & drop overlay not appearing on firefox (#1721) 2017-04-13 22:21:34 +02:00
Eugen
485310a43c Make browserify respect changes in node_modules for asset compilation (#1722) 2017-04-13 22:21:20 +02:00
Matteo Aquila
9aae9ae40c Update devise.it.yml 2017-04-13 22:09:47 +02:00
Matteo Aquila
1fb125b630 Update devise.it.yml 2017-04-13 22:06:19 +02:00
Eugen
057567d548 Make boost modal off by default (#1719) 2017-04-13 21:53:59 +02:00
Yusuke Abe
169c68a739 Add filename extension to paperclip (#1718) 2017-04-13 21:52:56 +02:00
Matt Jankowski
9f182346d7 Use english in reported by translation (#1713) 2017-04-13 21:49:46 +02:00
spf
a58c935c3d Fr missing strings (#1708)
* Fix missing translation for username already taken

* fr - note too long translation
2017-04-13 21:49:34 +02:00
Thomas Citharel
c0c56db0fa Translate admin (#1702)
* Translate the domain_block panel

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* Translate PubSubHubbub section

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* translate account section and correct typos

* move reports translation & translate sidebar

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* normalize l18n
2017-04-13 21:49:07 +02:00
James Moore
d9dc0fe84e smtp delivery type fix (#1556)
* delivery fix

# Conflicts:
#	config/environments/production.rb

* added stub in .env file

* reordered and added a comment
2017-04-13 19:51:49 +02:00
Alyssa Ross
55b56e3f95 terms: remove redundant words (#1578)
"at least X" and "X or older" have identical meanings.
Using both together feels a little jarring.
2017-04-13 19:50:19 +02:00
R Tucker
c4d39b1b3d quick typo fix in en.jsx: Rejec -> Reject (#1701) 2017-04-13 19:37:04 +02:00
Eugen
ac54da9394 Fix #1220, fix #1671 - Hook up comment box to the Redux comment value (#1699)
Fix username styling regression introduced in #1063
Fix report screen background regression introduced in #1415
2017-04-13 19:36:41 +02:00
Eugen
043862f411 Fix #1609, fix #1628 - Revert #1397 (#1700)
When transmitting data in a HTML-encoded element like <content type="html" />,
relying on newlines being preserved is not wise, since HTML by itself
does not care for newlines - it cares for <p> and <br>

Additional fix: reset NSFW toggle after sending toot
2017-04-13 19:23:36 +02:00
Eugen
9e5c1c487e Apply i18n-tasks normalize to locales (#1696) 2017-04-13 19:18:32 +02:00
INAGAKI Hiroshi
5619099564 translation: Fix comment out issue in en.jsx (#1698) 2017-04-13 17:11:18 +02:00
Koala Yeung
ce80d0b0a9 Fix minor jslint issue (#1697)
* Undefined propType in react component `ColumnSettings`.
  Add proper PropTypes definition.
2017-04-13 17:10:48 +02:00
Eugen Rochko
6327f69cab Merge branch 'blackle-videolightbox' 2017-04-13 17:01:52 +02:00
Eugen Rochko
5f8155482a Add overlay style to buttons, continue video after expanding it 2017-04-13 17:01:09 +02:00
Matteo Aquila
efcf9448da Update settings_helper.rb 2017-04-13 16:16:39 +02:00
Eugen Rochko
e70b84b1dc Merge branch 'videolightbox' of git://github.com/blackle/mastodon into blackle-videolightbox 2017-04-13 16:14:11 +02:00
Lukas Burk
d7a4e8739a Ignore postgres/redis folder from docker-compose (#1645) 2017-04-13 16:04:20 +02:00
Rachel H
9362700137 Convert emoji shortnames when sending status (#1666) 2017-04-13 16:03:45 +02:00
Koala Yeung
1206627c59 Add Dutch translation (nl) (#1682)
* Dutch language files for Ruby code

 * Created RoR translation ymls:
   * config/locales/devise.nl.yml
   * config/locales/doorkeeper.nl.yml
   * config/locales/nl.yml
   * config/locales/simple_form.nl.yml

 * Modified RoR config and helper
   * app/helpers/settings_helper.rb
   * config/application.rb

* Dutch language javascript locale

 * Created javascript locale files:
   * app/assets/javascripts/components/locales/index.jsx
   * app/assets/javascripts/components/locales/nl.jsx

 * Reference the newly created locale files:
   * app/assets/javascripts/components/containers/mastodon.jsx

* Fix syntax error in locale file (nl)

* Fix missing translate in js locale (nl)

* Convert all ruby Dutch locale (nl) file to utf8

Fix yml conversion issues.

* Fix duplicated key in devise.nl.yml

* Fix indentation error in doorkeeper.nl.yml
2017-04-13 16:02:23 +02:00
Isabelle Knott
edefcfcf42 Fix issue where 'sensitive content click to show' item takes up whole screen on public view (#1692) 2017-04-13 16:00:56 +02:00
Matt Jankowski
b330d1f000 Organize coverage dirs (#1695)
* Add `Presenters` group to SimpleCov configuration

* Move validators to app/validators, add to simplecov config
2017-04-13 16:00:31 +02:00
Eugen
1a5a54eb4b Merge branch 'master' into master 2017-04-13 16:00:20 +02:00
Isabelle Knott
447b8bc44e Do not show media attachment as og:image if it was marked as NSFW (#1693) 2017-04-13 15:59:43 +02:00
Matt Jankowski
093879c177 Fix language export variables (#1689)
* Fix naming of JS locale constants

* Improve the translation instructions re: const names
2017-04-13 15:59:12 +02:00
Matteo Aquila
d2c20936f3 Merge remote-tracking branch 'origin/master' 2017-04-13 15:33:53 +02:00
Matteo Aquila
0220f3a171 Update application.rb 2017-04-13 15:33:45 +02:00
Matteo Aquila
905a4faa1c Update settings_helper.rb 2017-04-13 15:32:31 +02:00
Matteo Aquila
5355b7d930 Update settings_helper.rb 2017-04-13 15:31:38 +02:00
Matteo Aquila
aec2458d81 Merge remote-tracking branch 'tootsuite/master' 2017-04-13 15:30:04 +02:00
Matt Jankowski
4fe5e04ea4 Fix csv export coverage in export spec (#1691) 2017-04-13 15:29:30 +02:00
Matteo Aquila
3f42ad7d1a Rename simple_form.it.yml to config/locales/simple_form.it.yml 2017-04-13 15:28:30 +02:00
Matteo Aquila
f7c466c8d8 Rename devise.it.yml to config/locales/devise.it.yml 2017-04-13 15:28:15 +02:00
Matteo Aquila
245b9cb4ba Rename doorkeeper.it.yml to config/locales/doorkeeper.it.yml 2017-04-13 15:27:56 +02:00
Matteo Aquila
9275f92972 Rename it.yml to config/locales/it.yml 2017-04-13 15:27:37 +02:00
Matteo Aquila
4a6d3bac86 Merge pull request #1 from tootsuite/master
update
2017-04-13 15:23:20 +02:00
blackle
00cc3066a2 Allow video to be expanded into lightbox 2017-04-13 09:09:45 -04:00
YOSHIOKA Eiichiro
a57d30c680 [l10n] ja: update missing Japanese translations (#1687) 2017-04-13 14:39:14 +02:00
Musee U
467d32fce3 [l10n] ja: update missing Japanese translations (#1684) 2017-04-13 13:40:58 +02:00
Koala Yeung
8aadb7b0b2 Exclude javascript locale file from dup check (#1677)
* Exclude javascript locale files form Code Climate's duplication
  engine. It is silly to have duplication check with locale files.
  They are supposed to look similar.

* Prevent unnecessary blocking for translation updates (like #1661)
2017-04-13 13:40:25 +02:00
Matteo Aquila
79546799af Added Italian language (it) (#1679) 2017-04-13 13:39:00 +02:00
May Kittens Devour Your Soul
90d0018fd5 Adds Croatian language [Hrvatski] (#1000)
* Create simple.form.hr.yml

* Create hr.yml

* Update hr.yml

* Update hr.yml

* Create doorkeeper.hr.yml

* Create devise.hr.yml
2017-04-13 13:38:44 +02:00
Eugen
1a12fd14d4 Merge branch 'master' into master 2017-04-13 13:26:16 +02:00
Hugo Gameiro
282bb55c3c fix Portuguese translation (#1661)
* update portuguese translation

added the missing fields and improved the translation

* pt translations fix

* improve last translation commit

* fix damn quotes
2017-04-13 13:25:34 +02:00
新都心(Neet Shin)
0e4479bb3a Update Japanese translation files (#1640)
* [l10n] ja: Improve Japanese Translations

* ja: about: Fix highlighting
* ja: Update Translations
* ja: Translate admin settings

Signed-off-by: lindwurm <lindwurm.q@gmail.com>

* Update ja.jsx

* Update doorkeeper.ja.yml

* Update ja.yml

* Update ja.jsx

* Update ja.jsx
2017-04-13 13:23:45 +02:00
Ratmir Karabut
af7e880df5 Update Russian translation (#1570)
* Add Russian translation (ru)

* Fix a missing comma

* Fix the wording for better consistency

* Update Russian translation

* Arrange Russian setting alphabetically

* Fix syntax error
2017-04-13 13:23:23 +02:00
Svetlozar Todorov
aa7bf1515c Fix #624 - Add localization for Bulgarian (#645)
* Add translation files and declarations for Bulgarian

* Add a bunch of translations to bg.jsx

* Add rest of translations to bg.jsx

* Add devise translations

* Fix devise translations
2017-04-13 13:16:28 +02:00
Daijiro Wachi
4f781b17cc Use input type number for Two-factor code (#1683) 2017-04-13 13:13:17 +02:00
Matt Jankowski
137100dcf3 Clean up well-known routes/controllers (#1649)
* Add request spec for host meta route returning xml

* Add routing spec for xrd routes

* Update well-known routes

* Move webfinger and host-meta actions to their own controllers
2017-04-13 13:09:07 +02:00
Matt Jankowski
3a9eb81a80 Admin accounts controller cleanup (#1664)
* Remove unused account_params method in admin/accounts controller

* Introduce AccountFilter to find accounts

* Use AccountFilter in admin/accounts controller

* Use more restful routes admin silence and suspension area

* Add admin/silences and admin/suspensions controllers
2017-04-13 13:04:23 +02:00
Matt Jankowski
0e39cc6a35 Settings export refactor (#1646)
* Refactor Export to take an account and know about the export types

* Use Export instance in settings/exports#show
2017-04-13 13:02:02 +02:00
Koala Yeung
faefd8ec8f Update javascript English translation files and some defaultValue (#1676)
* Reorder javascript English locale file

 * Reorder translation string in order of the locale key.

* Add javascript English locale missing language keys

 * Search all javascript language keys by command:
   `grep -REho '<FormattedMessage .*\/>' ./app/assets/javascripts/.`

 * Add all the missing language keys and their values to `en.jsx`.

* Add javascript English locale missing language keys (2)

* Find all `defineMessages` calls with this command:
  `grep -Rl 'defineMessages({.*' ./app/assets/javascripts/.`

* Open all these files. Find the language key (`id`) in these
  statements.

* Add all the missing language keys and their values to `en.jsx`.

* Remove javascript English locale obsoleted language keys

 * Find all language keys that no longer exists in the source code
  and remove them. The removed keys include:

    * "compose_form.private"
    * "compose_form.unlisted"
    * "getting_started.about_addressing"
    * "getting_started.about_shortcuts"
    * "notification.mention"
    * "search.account"
    * "search.hashtag"
    * "tabs_bar.mentions"
    * "tabs_bar.public"

* Javascript English locale file add note

 * Add notes to contributors about the English translation files.
   Hope that will make translation process smoother.

* Update javascript locale defaultValue in code

 * Update the defaultValue in code according to the relevant
   translation in English locale file.
2017-04-13 12:57:41 +02:00
matteoaquila
a18fd491b9 Added Italian language (it) 2017-04-13 11:47:03 +02:00
Isabelle Knott
96715d9af5 Clear spoiler when replying to toot that doesn't also have a spoiler (#1662) 2017-04-13 03:17:34 +02:00
Matt Jankowski
f24daa399b Remove pending specs for methods that dont exist (#1658)
* Remote spec for non-existent entry_classes helper method

This method no longer exists, and is handled by a local variable in a partial
instead.

* Remove spec for non-existent Account#ping! method
2017-04-13 02:51:13 +02:00
tom
af96e71883 Smoother scrolling on older iOS devices (#1654)
This may address the scrolling issues mentioned here: https://github.com/tootsuite/mastodon/issues/1622
2017-04-13 02:50:56 +02:00
Eugen
5dc73339ae Fix tests issue introduced in #1607 (#1639) 2017-04-13 02:17:40 +02:00
Thor Harald Johansen
ccaf3dbc5a Adjustments to the Norwegian translation (#1648)
* Working translation for Norwegian.

* Fixes to Norwegian translation.

* Further adjustments to Norwegian translation.

* Further adjustments to Norwegian translation.
2017-04-13 02:17:26 +02:00
Isabelle Knott
1ea662963f Use shift+click instead of alt+click to bypass boost dialog (#1638) 2017-04-13 02:15:45 +02:00
Eugen
bd834add56 Fix visuals introduced in #1463 (#1634) 2017-04-12 20:54:49 +02:00
lindwurm
9966bd27c2 [l10n] ja: Improve Japanese Translations (#1631)
* ja: about: Fix highlighting
* ja: Update Translations
* ja: Translate admin settings

Signed-off-by: lindwurm <lindwurm.q@gmail.com>
2017-04-12 20:41:50 +02:00
Isabelle Knott
b0ab632531 Fix missing compose box when viewport width is exactly 1024px (#1632) 2017-04-12 20:40:03 +02:00
Knut Erik
e1264bbd92 Added norwegian version of terms of service and privacy policy (#1625) 2017-04-12 20:28:56 +02:00
pinfort
38e24a699b fix Japanese translation (#1623)
* add device.ja.yml

* update device.ja.yml

* add file simple_form.ja.yml

* 👍Added doorkeeper.ja.yml

* add ja.yml

* Update doorkeeper.ja.yml

fixed url to uri

* update ja.yml

* fix some translations

* fix japanese grammar of a translate

* fix some translates

* fix ja.yml

* add ja.jsx

* add Japanese user mail views

* Added japanese translate locales

* Added :ja to available_locales

こっちも

* Added "日本語" to HUMAN_LOCALES

* Imported/Added ja to addLocaleData

* update ja.jsx

* fix translations
翻訳の誤りの修正と改善

* freeの訳修正

* いいねをお気に入りに統一

* Revert "いいねをお気に入りに統一"

This reverts commit 568d5cccfa0b6620ccb6c9db8346c52c0396d99f.
間違ってtranslateブランチにコミットしたため取り消し

* Revert "freeの訳修正"

This reverts commit 565658a60583ff7e1e7a63ef597bf3ac6118e1bf.

* revertでミスったので戻す

* Revert "ミスったので戻す"

This reverts commit 00be7a748a6a2ae85a62be847172424278c52ec7, reversing
changes made to b4e1e06503c77e079cb2569a53ab4d6fcfcfd116.

* freeの訳修正

* いいねをお気に入りに統一

* 一回戻す

* 戻しきれてなかった

* 再度変更

* 戻す

* 再度変更

* 再度変更

* Update doorkeeper.ja.yml

Fixed a lot of unnatural translations

* Update ja.yml

資格情報 isn't easy to understanding. so fixed to ログイン情報.

* Update ja.yml

fixed some unnatural translations

* Update simple_form.ja.yml

Fixed wrong translate 「アカウント」 to 「アイコン画像」

* Update simple_form.ja.yml

* Update doorkeeper.ja.yml

* Revert "多くの不自然な翻訳を解消しました。"

* Update ja.jsx

fixed typo

* 文字化け修正
2017-04-12 20:19:38 +02:00
Manato Kameya
bf3e56b8ad Fix incorrect notation in simple_form.ja.yml (#1620) 2017-04-12 20:19:27 +02:00
goofy-bz
9b698bf448 Update fr.jsx (#1551)
fixing to minor typos
2017-04-12 20:11:09 +02:00
Ben Roberts
0254ee9795 significant improvement in microformats markup (#1063)
* significant improvement in microformats markup

This is a huge improvement and I believe will close #965.

Had these microformats reviewed by others in the community to help
ensure they are at least correct, if not complete.

I did not want to change the structure of the page, and so there it does
not fully mark up the entire ancestry chain, or reply chain, only the
direct decendants and direct ancestors are correctly associated, but
this is likely fine as the most important bit is to have access to the
urls for those toots which are now correctly fetchable.

* improve code climate

* trying to pass code climate tests

* code climate

* fix p-summary for content warning posts

* fix error introduced when merging via github
2017-04-12 20:04:33 +02:00
CgX
e32edd247f Update fr.yml (#1600)
Mistake on %{link} variable
2017-04-12 19:56:09 +02:00
karlyeurl
dab9b5bd3a locale-fr: fix typo (#1603)
In this case, the adjective applies to a feminine noun.
2017-04-12 19:54:16 +02:00
Eugen
e17b5b228d Improve docker update instructions (#1619) 2017-04-12 19:34:40 +02:00
Julien Deswaef
c4baa9fb6b vector (svg) logo with correct inner shape and colors (#1362)
* vector (svg) logo with correct inner shape and colors

* minimized svg logo
2017-04-12 19:10:52 +02:00
David Libeau
c2a31b8032 Fix target blank on "open_in_web" link (#1612)
When you render the "embed" view in an iframe, this link bugs when clicked, due to missing target blank.
2017-04-12 13:09:58 -04:00
Shel R
9e63bf446e Request documentation (#1616)
This addition to the submission guidelines requests that contributors remember to document their code. It's not a hard fast rule just a reminder.
2017-04-12 18:27:33 +02:00
Matt Jankowski
c44a700252 Quick best practice cleanup of views/helpers (#1546)
* Remove trailing whitespace

* Use query methods instead of explicit .blank? checks
2017-04-12 18:24:18 +02:00
Matt Jankowski
aa90798386 Webfinger resource to extract username from resource string (#1607)
* Add WebfingerResource class to extract usernames

* Use WebfingerResource in xrd#webfinger
2017-04-12 18:22:38 +02:00
Rachel H
0930ce5560 Focus textarea when text is inserted (#1320)
Place space after uploaded filenames
2017-04-12 18:21:07 +02:00
Matt Jankowski
7f0a865b05 Allow import/export of mutes list (#1541)
* Allow export of mutes list

* Allow importing of mutes list

* Refactor to use Settings::Exports::BaseController and DRY up exports code
2017-04-12 18:20:44 +02:00
Henry Smith
08fce08217 Add unit tests for https://github.com/tootsuite/mastodon/pull/1574 (#1584) 2017-04-12 17:16:59 +02:00
Carlos A. Escobar
3064ef96a1 Added missing Spanish translations strings (#1292)
* Added missing Spanish translations strings

* fix bad translation

* resolve conflict with tootsuite/master

* agreements from spanish community

* Add some missing strings
2017-04-12 17:16:40 +02:00
lindwurm
ee69ece7b5 [web] Moving docs link to tootsuite/documentation (#1581)
related: 1236529e39 and fc47c1d00e

Signed-off-by: lindwurm <lindwurm.q@gmail.com>
2017-04-12 17:13:19 +02:00
Thomas Citharel
d90d23699c Make Reporting admin section translatable (#1549)
* Make Reporting admin section translatable

And translate it into english and french

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* Make subject of emails translatable and improve french translation

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* Make error pages translatable and translate them in english and french

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* Translate site setting section

* Insert instance in registration emails and improve them a bit

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2017-04-12 17:11:49 +02:00
Thor Harald Johansen
1f5ff46fd9 Working translation for Norwegian. (#1611) 2017-04-12 17:07:51 +02:00
Matt Jankowski
13528f50c3 Params compact deprecation warning (#1580)
* Move filter_link_to class formation to separate method in admin/accounts helper

* Remove deprecated #compact method usage on strong parameters
2017-04-12 16:12:56 +02:00
Matt Jankowski
dd1ae3b109 Simplify the way the embed view is created (#1590)
* Add coverage for embedded status view

* Refactor embed view to eliminate @external_links variable
2017-04-12 16:12:42 +02:00
Matt Jankowski
b352a8e5d4 Default to json type for webfinger requests (#1583) 2017-04-12 16:03:37 +02:00
Matt Jankowski
fd102059aa Clean up stylesheet organization (#1591) 2017-04-12 16:01:59 +02:00
Matt Jankowski
323671a653 Silence more scope order warnings (#1604) 2017-04-12 15:58:08 +02:00
Matt Jankowski
b155e6ccf5 Fix issue with intermittent api/v1/notifications failure (#1606)
The spec was checking the activity_id of the activities held in notifications
within the controller.

Because the activities are different models, it is possible that they are
created with the same database IDs, and when they are this spec fails because an
activity which should not count as a match is counted as one.
2017-04-12 15:53:54 +02:00
Christopher Su
f16b9a4928 Fix redirect link on Tuning.md (#1595) 2017-04-12 12:40:37 +02:00
Koala Yeung
24eb45425e Add Traditional Chinese, Hong Kong translation (zh-HK) (#1544)
* Added Chinese Traditional Hong Kong (zh-HK) for Ruby

* Added translations for Ruby.

* Added Chinese Traditional Hong Kong (zh-HK) for JS

* Added translations for javascript code.

* Rearrange language references in mastodon.jsx

* Break `addLocaleData` into multiple lines. Make future commit more readable.
* Roughly re-sort the languages in alphabetical orders
  (only manually put English on top because it is default).

* Sort application.rb locale with alphabetical order

With exception that English (default language) goes first.
Improve code readability.

* Resort language selection box alphabetically

Sort HUMAN_LOCALES in the alphabetical order of display name
(except English, the default language, come first).

Improve usability.
2017-04-12 08:50:50 +02:00
Eric Blade
3442bc0ea3 update Docker section of README (#1231)
Re-ordered the steps so it doesn't read "Do this, but first, do this
other step"
Added note about keeping the REDIS and DB settings as they are for
Docker use
Add which variables you will NEED to set to make the Mastodon work
Add how to generate the secrets
Add how to connect to your Mastodon
Add a note to read the Production-guide
2017-04-12 01:14:56 +02:00
Eugen
40bdf43297 Change default log level in production from :debug to :info for less I/O (#1579) 2017-04-11 23:28:23 +02:00
Eugen
8ead070b94 Improve emoji picker look (#1493)
* Improve emoji picker look

* Add hover animation to emojis in picker
2017-04-11 23:23:34 +02:00
Eugen
b22b2cbfac Fix #1491 - Fix broken notifications, broken Link header parsing for exclude_types (#1548) 2017-04-11 22:53:58 +02:00
d0p1
2f2b84bfbb Add task in order to delete unconfirmed users (older than 2days) (#1571)
* add task in order to delete unconfirmed user

* change 7 days to 2
2017-04-11 22:51:17 +02:00
Eugen
5cdd2c2414 Fix #1535 - #1372 set a wrong default on :openssl_verify_mode (#1563) 2017-04-11 22:49:53 +02:00
Matt Jankowski
3ddd936b03 Refactor exports controller (#1567)
* Add basic coverage for settings/exports controller

* Remove unused @account variable from settings/exports controller

* Add coverage for download export actions

* Remove deprecated `render :text` in favor of `send_data` for csv downloads

* Add model to handle exports

* Use Export class in settings/exports controller

* Simplify settings/exports controller methods

* Move settings/export to more restful routes
2017-04-11 22:00:43 +02:00
Henry Smith
1921c5416b Fix console error when scrolling a column with no scrollable content (#1574) 2017-04-11 21:58:28 +02:00
lindwurm
fc47c1d00e [about] Moving docs link to tootsuite/documentation (#1573)
related: 1236529e39

Signed-off-by: lindwurm <lindwurm.q@gmail.com>
2017-04-11 21:57:05 +02:00
Thomas Citharel
327a6e166f Update french locale (#1525)
* Update devise.fr.yml fixing minor typos

* add missing strings

* Fix a translation and add translation for devise part

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2017-04-11 21:54:40 +02:00
pinfort
6f5268b02d Add Japanese translation (#1552)
* add device.ja.yml

* update device.ja.yml

* add file simple_form.ja.yml

* 👍Added doorkeeper.ja.yml

* add ja.yml

* Update doorkeeper.ja.yml

fixed url to uri

* update ja.yml

* fix some translations

* fix japanese grammar of a translate

* fix some translates

* fix ja.yml

* add ja.jsx

* add Japanese user mail views

* Added japanese translate locales

* Added :ja to available_locales

こっちも

* Added "日本語" to HUMAN_LOCALES

* Imported/Added ja to addLocaleData

* update ja.jsx
2017-04-11 21:52:09 +02:00
jukper
4964433190 Revised finnish translation (#1537) 2017-04-11 21:41:40 +02:00
Jantso Porali
9e3c4fd2d7 Update language files (#1516)
* update faq with default language

* update translation for about page

* update Minio config

Thanks to @Gargon for helping me. I hope this will help others as well

* update import and export translation

* translate emails to finnish

* add finnish translation for emails

* add finnish translation for emails

* add finnish translation

* add missing dot

* update finnish language to emails

* add finnish translation for emails

* add dot and fix typo

* updated some minor typos

* remove language change due breaking emails

And by dev request

* updated minio config by dev request

* updated about page translation

* fix for Amazon S3/Minio instance setups

If you were using S3 or Minio for your files, this will fix the Import issue :)

* update translation according to issue #1515

See issue #1515

* update translation for issue #1515

see issue #1515
2017-04-11 21:41:29 +02:00
Matt Jankowski
89e8e110c8 Imports controller errors (#1553)
* Add spec for settings/imports controller

* Add failing spec for settings/imports#create

* Fix broken imports

* Refactor ImportWorker
2017-04-11 21:40:14 +02:00
Eugen Rochko
9f7ea77d0c Merge branch 'master' of github.com:tootsuite/mastodon 2017-04-11 21:30:48 +02:00
Eugen Rochko
5f74397ef0 Merge branch 'blackle-master' 2017-04-11 21:30:34 +02:00
Eugen Rochko
960181fd99 Fix look of the modals 2017-04-11 21:24:17 +02:00
Eugen Rochko
2a7602cad4 Merge branch 'master' of https://github.com/blackle/mastodon into blackle-master 2017-04-11 20:43:56 +02:00
Komic
47aacb773b Better background-photo.jpeg (#1560)
Fixed the nasty banding & recompressed it at about the same level
2017-04-11 19:51:13 +02:00
Matt Jankowski
82d9336114 Increase breakpoint size for bio on account show (#1559) 2017-04-11 19:35:06 +02:00
Matt Jankowski
e60286a344 Remove unused methods from StreamEntriesHelper (#1365)
Removes:

- avatar_for_status_url
- relative_time
- reblogged_by_me_class
- favourited_by_me_class
2017-04-11 19:32:02 +02:00
Matt Jankowski
53850bce93 Remove unused AtomBuilderHelper (#1364)
This was used in the views/atom/user_stream.xml.ruby file, which no longer is
used.
2017-04-11 19:31:22 +02:00
Eugen
1236529e39 Moving docs to tootsuite/documentation (#1550) 2017-04-11 12:03:02 -04:00
blackle
06444bf050 Allow user to disable the boost confirm dialog in preferences 2017-04-11 10:10:16 -04:00
Valentin Ouvrard
b723ee73fc Add (commented) volume in docker-compose && Mitigating the HTTPoxy Vulnerability (#1253)
* enable commented volume in docker-compose.yml

* Disable unworking Nginx root directory && Mitigating the HTTPoxy Vulnerability

* add my instance to the list

* enable GZIP on nginx.conf

* readd root /home/mastodon/live/public;
2017-04-11 16:04:56 +02:00
Gavin Mogan
c35bda0551 fix(*): ruby version was updated in .ruby-version but not Vagrant. Make them match (#1502) 2017-04-11 15:06:07 +02:00
blackle
f53fb6aa66 Bypass boost confirm modal if alt is pressed 2017-04-11 08:34:14 -04:00
Yann GUERN
a85d4473aa Avoid user enumeration with devise paranoid mode (#1527) 2017-04-11 14:21:15 +02:00
Corey Dutson
c9b9225951 Adjust css for user detail page (#1463)
- details a background for contrast
- add 5px padding to the top of the `details-counters` children to line them up with the bio to the right (Which has a 5px padding on the top)
2017-04-11 14:20:18 +02:00
David Libeau
11898a6461 Add Mastodon.tools (#1457) 2017-04-10 23:30:40 -04:00
blackle
01e5447e35 Add boost confirm modal 2017-04-10 22:35:55 -04:00
Matt Jankowski
4ada50985a Pagination improvements (#1445)
* Replace will_paginate with kaminari

* Use #page instead of #paginate in controllers

* Replace will_paginate.page_gap with pagination.truncate in i18n

* Customize kaminari views to match prior styles

* Set kaminari options to match prior behavior

* Replace will_paginate with paginate in views
2017-04-11 01:11:41 +02:00
Effy Elden
a283786463 Add note about minimum docker-compose version (#1264)
Add a note to ensure users are using the right version of docker-compose.
2017-04-11 00:39:39 +02:00
Eugen
12f72e1740 When avatar/header are GIF, generate static versions (#1428)
* When avatar/header are GIF, generate static versions.
Account API returns "avatar"/"avatar_static", "header"/"header_static"
Static version is the same as original for other cases
Web UI de-animates avatars in toots, lists of users

Fix #441, fix #596, prerequisite for #1064

* Fix JS test

* Add rake task to generate static avatars/headers from GIF ones, add test
2017-04-11 00:38:58 +02:00
Matt Jankowski
b57eed4584 Remove order prior to .find_in_batches (#1470)
The `Status` class has a default order on it, so when this query gets built and
gets all the way to `find_in_batches` there is an order already there.

When `find_in_batches` is run it discards any existing order on the query, and
emits a warning to the logs if there is one there.

This change removes the order prior calling `find_in_batches`, which will stop
the logged warning from occurring as well.
2017-04-11 00:38:34 +02:00
Gavin Mogan
3672a799d4 Dev Tooling fixes (eslint/editorconfig) (#1398)
* Add eslint to dev dependancies so it gets installed for the repo

yarn add --dev eslint babel-eslint eslint-plugin-reac

project specific version of eslint, you can globally install eslint-cli
if you want the global runtime, or add .bin to your path

* fix eslint errors about inconsitent returns

* eslint ignore the same as git ignore. allows for eslint .

* Add editorconfig file so everyones editor will be setup to follow the same standards
2017-04-11 00:36:03 +02:00
Matthias Jouan
3fd5385e7b Add username as a title for mentions (#1385)
Add a title attribute on mention links for both notifications
and mentions in statuses.

Related to #1350
2017-04-11 00:35:35 +02:00
Ash Furrow
d439855a6d Adds error message to mastodon:confirm_email task. (#1476) 2017-04-11 00:13:08 +02:00
Eugen
2810013b93 API param to exclude notification types from response (#1341)
* Add exclude_types param to /api/v1/notifications

* Exclude notification types in web UI through exclude_types in the API
2017-04-10 23:45:29 +02:00
Matt Jankowski
0687ab8ae3 Clean up generation of account webfinger string (#1477)
* Consolidate webfinger string creation under Account#to_webfinger_s

* Introduce Account#local_username_and_domain for consolidation
2017-04-10 22:58:06 +02:00
Matt Jankowski
64dbde0dbf Version bumps for ruby and misc gems (#1159)
* Update rspec-rails to version 3.5.2

* Update addressable to version 2.5.1

* Update autoprefixer-rails to version 6.7.7.1

* Update bullet to version 5.5.1

* Update domain_name to version 0.5.20170404

* Update letter_opener_web to version 1.3.1

* Upate redis-rails to version 5.0.2

* Update active_record_query_trace to version 1.5.4

* Update capistrano-rails to version 1.2.3

* Update dotenv-rails to version 2.2.0

* Update pg to version 0.20.0

* Update tilt to version 2.0.7

* Update warden to version 1.2.7

* Update tins to version 1.13.2

* Update terminal-table to version 1.7.3

* Update oj to version 2.18.5

* Update simplecov to version 0.14.1

* Update uglifier to version 3.1.13

* Update hashdiff to version 0.3.2

* Update webmock to version 2.3.2

* Update devise to version 4.2.1

* Use ruby version 2.4.1

* Update sass to version 3.4.23

* Update puma to version 3.8.2

* Update will_paginate to version 3.1.5

* Update font-awesome-rails to version 4.7.0.1

* Update fuubar to version 2.2.0

* Update pry-rails to version 0.3.6

* Update simple-navigation to version 4.0.5

* Update rubocop to version 0.48.1

* Update doorkeeper to version 4.2.5

* Update faker to version 1.7.3

* Update aws-sdk to version 2.9.5

* Update fabrication to version 2.16.1

* Update hamlit-rails to version 0.2.0

* Update http to version 2.2.1

* Update httplog to version 0.99.2

* Update sidekiq to version 4.2.10

* Update rspec-sidekiq to version 3.0.0

* Update pghero to version 1.6.4

* Update rack-cors to version 0.4.1

* Update i18n-tasks to version 0.9.13

* Update ruby-oembed to version 0.12.0

* Update jquery-rails to version 4.3.1

* Update simple_form to version 3.4.0

* Update react-rails to version 1.11.0

* Update aws-sdk to version 2.9.6

* Update sidekiq-unique-jobs to version 5.0.0

* Update uglifier to version 3.2.0
2017-04-10 22:47:41 +02:00
Chris Martin
ae57b3a8c5 Add more specific class names to notification divs (#1120) 2017-04-10 22:41:52 +02:00
Alexander Mankuta
0dbbc16c69 More SMTP customization (#1372)
* Allow SMTP auth method customization

* Add SMTP openssl_verify_mode option support

Allows one use self-signed certs with their SMTP server.

* Add SMTP enable_starttls_auto option support
2017-04-10 21:48:30 +02:00
Rachel H
f690320fb9 Keep newlines in xml (#1397) 2017-04-10 21:32:45 +02:00
Stephen Burgess
553170b77a Fix #1097 When onClick is falsy, do not make status content clickable (#1434) 2017-04-10 21:31:26 +02:00
Jessica Stokes
8a6096a3de Allow typing a toot while an image uploads (#1429)
This patch stops disabling the toot text field when an image is uploading. Instead, you can type to your heart's content and when the image uploads it'll append the image URL, and restore the position of your cursor to wherever it was in the toot text just prior. Effectively, the image URL is appended to the toot, and typing is not interrupted at all! 
2017-04-10 21:30:58 +02:00
Matt Jankowski
d2f6d9b9fb Fix issue with missing emojify class in views (#1455)
* Add missing emojify class to landing strip

* Add missing emojify class to simple_status partial
2017-04-10 21:27:52 +02:00
Matt Jankowski
dbe9f33fdc Admin base controller (#1465)
* Add Admin::BaseController to wrap admin area

Extracts the setting of the `admin` layout and verifying that users are admins
to a common base class for the admin/ controllers.

* Add basic coverage for admin/reports and admin/settings controllers
2017-04-10 21:27:03 +02:00
Alexsander Akers
1be6aa0c7f Fix references to "v1" API (#1460)
References to `vi` API version replaced with `v1`
2017-04-10 21:11:59 +02:00
Corey Dutson
087ca3009b Adjust background of emoji panel (#1461)
Addresses #1451 which notes the emoji picker is too light. I agree, so I submit this adjustment.

Changes:
Changed the background to a darkened version of another system color
2017-04-10 20:56:14 +02:00
Jessica Stokes
db7c7d1af1 Improve scrolling behaviour (#1415)
* Replace column margin with padding

This improves horizontal scrolling behaviour significantly; scrolled flex elements are... a little weird.

* Move clear column button styling to css
2017-04-10 16:27:30 +02:00
Ram Lmn
42fb4faa0f Fix icon and emoji sizes (#1403)
Fixes icons and emoji size issues described in #1233.
2017-04-10 13:02:09 +02:00
Ryan Freebern
9bb398ee91 Minor clarifications (#1386) 2017-04-10 13:01:43 +02:00
Ratmir Karabut
9043b32183 Add Russian translation (ru) (#1023)
* Add Russian translation (ru)

* Fix a missing comma

* Fix the wording for better consistency
2017-04-10 12:52:06 +02:00
Jantso Porali
e30bbb1cb0 Fix for Import issue from URL (#1416) 2017-04-10 12:49:34 +02:00
Gavin Mogan
8bdf02812c Add search to emoji picker (#1395) 2017-04-10 09:58:09 +02:00
Eugen
93db265be7 Do not store last visited URL from API controllers (#1330)
Sign-in redirects you back to last visited URL, but in case of API requests,
this sometimes redirected users to an API URL that, of course, greeted them
with an {"error":"The access token is invalid"}
2017-04-09 22:21:52 +02:00
Eugen
c172919745 Fix #1339 - better Atom titles (#1343) 2017-04-09 20:55:54 +02:00
Eugen
15d442cf9d Fix /api/v1/accounts/update_credentials tests (#1357) 2017-04-09 20:23:14 +02:00
Ornithologist Coder
43f955e31f Minor change to Entity on API.md (Relationship) (#1356) 2017-04-09 19:10:38 +02:00
Hugo Gameiro
4ea4ef9d0f update portuguese translation (#1280)
added the missing fields and improved the translation
2017-04-09 18:49:26 +02:00
ThibG
d19ed18388 Get handle from atom feed's author/email field instead of guessing from URL (#1344)
The goal of this change is to enhance Mastodon's handling of remote domains
for which the APIs reside on a different host (see issue #1032).

Indeed, when a remote user unknown to Mastodon is mentionned, only its profile
URL (e.g. https://social.example.org/users/User) is known, and Mastodon has to
build a @username@domain handle for it. To do so, Mastodon fetches the user's
atom feed (e.g., https://social.example.org/users/User.atom) and uses its
content to get the username part of the handle, and the URL's host part to
build the domain (e.g., @User@social.example.org). This handle is then used
for a Webfinger request.

In the case where example.org serves the Webfinger info for @User@example.org
and all feeds and APIs are hosted at social.example.org, Mastodon will still
build @User@social.example.org and fail at resolving the account's details
through Webfinger.

This patch changes this behaviour by using the author's email address from
the atom feed to build the handle. In Mastodon-generated atom feeds, the
email address is always the handle it expects for federation.
2017-04-09 18:43:48 +02:00
David Authier
f0bd439486 Use HTTP Accept-Language to detect locale (#1166)
* Use HTTP Accept-Language to detect locale

* Fix gem order to comply with codeclimate

* Sort gem to comply with rubocop

* I18n.default_locale fallback when there is no accept-language header
2017-04-09 18:40:24 +02:00
Ornithologist Coder
b16fbd52b2 Minor API.md changes (#1351) 2017-04-09 18:36:03 +02:00
Ornithologist Coder
3b34c28bee Minor change on API.md (#1352) 2017-04-09 18:35:51 +02:00
R Tucker
8bfdbf0aa6 Add comment to settings.yml to nudge admins towards editing values via Web UI (#1289)
* Put a useful message for new admins on /about/more

I totally failed to realize this file was just defaults.  I think
this message would be a good default for people like me.

* Revert default site description, expand comment

This will keep setup-related stuff from leaking into public views,
while still hopefully keeping over-eager admins from editing this
file unnecessarily before RTFMing.  (e.g., me)
2017-04-09 18:34:29 +02:00
David Celis
d4fe6cd2bf Allow users to update their Account in the API (#1179)
* Allow users to update their Account in the API

It would be nice for API clients to be able to allow users to update
their accounts without having to wrap Mastodon in a web view. This patch
adds an API endpoint to let users submit a PATCH for their account.

Signed-off-by: David Celis <me@davidcel.is>

* Add /api/v1/accounts/update_credentials to the API docs

Signed-off-by: David Celis <me@davidcel.is>
2017-04-09 18:33:40 +02:00
Matt Jankowski
ea6c930c04 Helper cleanup (#1348)
* Remove unused helper files

* Add coverage for application helper

* Add coverage for StreamEntriesHelper #display_name
2017-04-09 17:11:37 +02:00
StefOfficiel
12e29c9660 Update fr.jsx (#1329)
* Update fr.jsx

* Remove duplicate translation
2017-04-09 14:58:08 +02:00
Olivier Humbert
082bef3027 French translation update (#1271)
* Update confirmation_instructions.fr.html.erb

consistency across the French translation

* Update 

consistency across the French translation

* Update fr.yml

a bunch of consistency across the French translation + a few typos

* Update doorkeeper.fr.yml

consistency across the French translation (punctuation)
2017-04-09 14:55:58 +02:00
spf
e6b48a7048 French typo (#1257)
* French typo

* Datetime french translation
2017-04-09 14:54:47 +02:00
Jonathan Klee
ba2aea3a80 add empty notifications french translation (#1111) 2017-04-09 14:54:02 +02:00
Matt Jankowski
e5282e4ec0 Clean up about page (#1282)
* Add InstancePresenter to expose site details

* Clean up about controller, use instance presenter
2017-04-09 14:47:25 +02:00
Brian Mock
53eb31f124 Fixes #1311 margin shouldn't stay fixed (#1312) 2017-04-09 14:45:26 +02:00
Matt Jankowski
388ec0d5b6 Search cleanup (#1333)
* Clean up SQL output in Tag and Account search methods

* Add basic coverage for Tag.search_for

* Add coverage for Account.search_for

* Add coverage for Account.advanced_search_for
2017-04-09 14:45:01 +02:00
Matt Jankowski
71706f21c2 Ignore implied formats for catch all route requests (#1340)
A request to `/test` would show the custom 404 page, but a request to
`/test.test` would return a 404 with an empty body.

This change ignores the format on incoming catch all route requests, so that the
html 404 page is returned on these requests.
2017-04-09 14:39:41 +02:00
Rachel H
b1881a3d48 Fix nonworking clear notices button (#1316) 2017-04-09 11:35:23 +02:00
Eugen
d5a675099a Add env variable to disable prepared statements (#1293) 2017-04-09 05:46:32 +02:00
Ash Furrow
c3e7bac1cc Allows setting log level in env variable (#1290)
* Allows setting log level in env variable.

* Made changes based on feedback in #1290.
2017-04-09 01:42:13 +02:00
Ash Furrow
6e3925521d Adds user confirmation rake task (#1300)
* Adds task to confirm user by email.

* Adds documentation for manual confirmation.
2017-04-09 01:09:46 +02:00
Eugen
b89f007862 Make public timelines API not require user context/app credentials (#1291)
* Make /api/v1/timelines/public and /api/v1/timelines/tag/:id public
Fix #1156 - respect query params when generating pagination links in API

* Apply pagination fix to more APIs
2017-04-08 23:39:31 +02:00
Eugen
9acdb166e8 Fix #795, fix #704, fix #835 - 2FA requires confirmation to be enabled (#1278)
* Fix #795, fix #704, fix #835 - 2FA requires confirmation to be enabled
TOTP secret is not shown again after 2FA is enabled

* Clean up
2017-04-08 22:20:08 +02:00
Pavel Djundik
470eb0042e Improve responsiveness of registration form and closed banner (#1265) 2017-04-08 18:31:50 +02:00
Pavel Djundik
fc146a19cc Improve about page responsiveness (#1252) 2017-04-08 15:28:23 +02:00
Elizabeth Myers
941a593ea8 Add mst3k.interlinked.me to list of instances (#1235) 2017-04-08 21:48:16 +10:00
Eugen
982fef811e Fix #1141, fix #1126 - Avatar/profile info fetching (#1215)
* Fix #1141, fix #1126 - Work through UpdateRemoteProfileService for both <feed> and <entry> top-level tags

* Improve code quality, remove line unrelated to fix
2017-04-08 13:26:03 +02:00
Thomas Citharel
41f8fde83e Update french translation (#1148)
Add french translation for emails sent

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

Add non-breaking spaces

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

changes and fixes to the nbsps

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

French update

a few fixes

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

fixes

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2017-04-08 13:11:16 +02:00
Markus Amalthea Magnuson
157f0a2aa7 Add titles to more icons, and change clear notifications icon. (#1101) 2017-04-08 13:07:55 +02:00
kadiix
49043f644d Update mastodon.brussels instance name on List of Instances
Correct mastodon.brussels.fr to mastodon.brussels
2017-04-08 21:05:49 +10:00
Nicolai von Neudeck
c803f5b440 Updated German translation (#1248)
Fixed various spelling and grammar mistakes.
Used more gender-neutral language.
2017-04-08 13:04:58 +02:00
Sebastian Hübner
f860eb7d71 Fixed two translation errors (#1139)
Changed followers from Follower to Folger and following from Gefolgt to Folgt, to make it identical with the translation in de.jsx
2017-04-08 13:03:11 +02:00
Korbinian
8f9a11b642 Update simple_form.de.yml (#971) 2017-04-08 13:02:07 +02:00
Thibaut (Eychics)
c6b0707cf6 Update mastodon.nuzgo.net (#1242)
Add ipv6 support
2017-04-08 20:34:53 +10:00
James Smith
b79c80b620 Add OTP_SECRET to Heroku app.json (#1246) 2017-04-08 20:31:33 +10:00
Eugen
211920b622 Revert "add persistance to Postresql container" (#1251) 2017-04-08 12:25:23 +02:00
Jantso Porali
01d8003867 Updated about page in Finnish language (#1170)
* update faq with default language

* update translation for about page

* update Minio config

Thanks to @Gargon for helping me. I hope this will help others as well

* update import and export translation

* translate emails to finnish

* add finnish translation for emails

* add finnish translation for emails

* add finnish translation

* add missing dot

* update finnish language to emails

* add finnish translation for emails

* add dot and fix typo

* updated some minor typos

* remove language change due breaking emails

And by dev request

* updated minio config by dev request

* updated about page translation
2017-04-08 12:17:34 +02:00
Olivier Humbert
ed3dfd0bee French translation update (#1188)
* Update fr.yml

one typo fix

* Update simple_form.fr.yml

one translation consistency
2017-04-08 12:16:49 +02:00
Alda Marteau-Hardi
35eff3f2d0 Add some missing strings to prevent some React warning in the console (#1230) 2017-04-08 12:16:16 +02:00
Matt Jankowski
ca44c13455 Use Setting.site_title value for og:site_name occurrences (#1194)
* Add helper method to return Setting.site_title

* Use site_title helper in application layout

* Use site_title value for og:site_name
2017-04-08 12:15:40 +02:00
Eugen
a345eb44fc Merge pull request #1239 from ineffyble/patch-2
Fix my URL
2017-04-08 11:40:43 +02:00
Eugen
7b814d5bcb Merge pull request #1238 from milmazz/hunter-client
Add reference to Elixir client for Mastodon API
2017-04-08 11:40:20 +02:00
Eugen
9f9f4b248e Merge pull request #887 from valentin2105/master
add persistance to Postresql container
2017-04-08 11:39:25 +02:00
Eugen
3660f01f60 Merge pull request #1115 from vmincev/patch-1
Update Production-guide.md
2017-04-08 11:38:54 +02:00
Eugen
a2ec54a20b Merge pull request #1198 from huertanix/patch-1
Update Heroku-guide.md
2017-04-08 11:38:33 +02:00
Eugen
e33dcb79c6 Merge pull request #1221 from rbaumert/patch-1
fixed a sentence in readme
2017-04-08 11:37:51 +02:00
Eugen
7d5ea5c170 Merge pull request #1224 from chrisheninger/patch-1
Add SVG version of logo to repo
2017-04-08 11:37:37 +02:00
Eugen
33849acfa7 Merge pull request #1218 from R0ckweb/patch-2
Fix #1141 on remote follow
2017-04-08 11:37:13 +02:00
Eugen
c141f0a886 Merge pull request #1216 from tootsuite/fix-default-locale-regression
Fix #1165 - Default locale no longer breaks form submissions
2017-04-08 11:36:43 +02:00
Eugen
55d03da303 Merge pull request #1213 from tootsuite/fix-accounts-initial-case
Fix #801 - Respect webfinger's canonical response of username/domain
2017-04-08 11:36:35 +02:00
Eugen
2c3a730eae Merge pull request #1225 from tootsuite/yiskah-patch-1
Close instance list to additions
2017-04-08 11:35:40 +02:00
Milton Mazzarri
b04cbb9f5d Add reference to Elixir client for Mastodon API 2017-04-08 02:48:52 -05:00
Effy Elden
75aade3de2 Fix my URL
Update my URL since toot.zone was shut down.
2017-04-08 16:22:07 +10:00
rbaumert
ac0b84534e Merge branch 'master' into patch-1 2017-04-07 21:39:24 -07:00
Valentin Ouvrard
3c48c9ac2e Merge branch 'master' into master 2017-04-08 15:11:20 +11:00
Shel R
ecf0320a78 Close instance list to additions 2017-04-07 23:51:30 -04:00
Kurtis Rainbolt-Greene
40703b96fa Merge branch 'master' into fix-default-locale-regression 2017-04-07 20:50:21 -07:00
Kurtis Rainbolt-Greene
1e4453405b Merge branch 'master' into patch-2 2017-04-07 20:48:27 -07:00
Chris Heninger
0ad694f96b Add SVG version of logo to repo 2017-04-07 20:40:18 -07:00
Shel R
541c538f9b Merge pull request #1137 from Noiwex/patch-1
Update List-of-Mastodon-instances.md
2017-04-07 23:16:46 -04:00
Shel R
50910d1543 Merge branch 'master' into patch-1 2017-04-07 23:15:17 -04:00
Shel R
4ff8653d6c Merge pull request #1197 from blakebarnett/add_indigo_zone_instance
Add indigo.zone to list of instances
2017-04-07 23:10:25 -04:00
Shel R
17690f51a2 Merge branch 'master' into add_indigo_zone_instance 2017-04-07 23:09:50 -04:00
Shel R
cf80cb5e8b Merge pull request #1196 from mouse-reeve/list-oulipo-social
Adds Oulipo.social to Mastodons list
2017-04-07 23:09:09 -04:00
Shel R
315ff648c8 Merge branch 'master' into list-oulipo-social 2017-04-07 23:08:41 -04:00
Shel R
41923d1c6b Merge pull request #1186 from Aguay-val/patch-2
Add mastodon.fun
2017-04-07 23:07:50 -04:00
Shel R
63686fd36f Merge branch 'master' into patch-2 2017-04-07 23:05:08 -04:00
Shel R
ae9d2f4a32 Merge branch 'master' into patch-1 2017-04-07 23:03:07 -04:00
Shel R
741bbba6ff Merge branch 'master' into patch-1 2017-04-07 22:59:07 -04:00
Shel R
a25a384af3 Merge pull request #1107 from YDrogen/patch-1
Added masto.razrnet.fr
2017-04-07 22:54:44 -04:00
Shel R
971c4de18c Merge branch 'master' into patch-1 2017-04-07 22:54:25 -04:00
Shel R
394c8ef680 Merge pull request #1090 from gled-rs/master
Added mastodon.host in the list of instances
2017-04-07 22:54:14 -04:00
Shel R
3c5b0c55cb Merge branch 'master' into master 2017-04-07 22:53:42 -04:00
Shel R
0dcf3c6abe Merge pull request #1085 from isati/patch-2
Add manx.social instance
2017-04-07 22:49:34 -04:00
Shel R
b67b60fec3 Merge branch 'master' into patch-2 2017-04-07 22:49:14 -04:00
rbaumert
2c0ef75f58 fixed a sentence in readme 2017-04-07 19:48:46 -07:00
Shel R
cc16fa7513 Merge pull request #1084 from genesixx/patch-4
Update List-of-Mastodon-instances.md
2017-04-07 22:48:02 -04:00
Shel R
d6827e38a6 Merge branch 'master' into patch-4 2017-04-07 22:47:42 -04:00
Shel R
29ed448445 Merge pull request #1074 from kadiix/patch-1
Add an instance.
2017-04-07 22:47:00 -04:00
Shel R
e7d7a99fbc Merge branch 'master' into patch-1 2017-04-07 22:46:36 -04:00
Shel R
8d27de32b3 Merge pull request #1069 from Awea/master
Add an instance
2017-04-07 22:45:15 -04:00
Shel R
abab82ec1e Merge branch 'master' into master 2017-04-07 22:44:40 -04:00
Shel R
f0797bf8ce Merge pull request #1066 from jack-michaud/master
Add Northeastern University Mastodon
2017-04-07 22:43:46 -04:00
Shel R
38c5130930 Merge branch 'master' into master 2017-04-07 22:43:13 -04:00
Shel R
ee8af9083a Merge pull request #1051 from Tiwy57/patch-1
Add an instance
2017-04-07 22:42:34 -04:00
Shel R
c9f15f7991 Merge branch 'master' into patch-1 2017-04-07 22:42:14 -04:00
Shel R
91afe1f8fd Merge pull request #1050 from lfuelling/patch-1
add my instance
2017-04-07 22:41:37 -04:00
Shel R
9edee2e64f Merge branch 'master' into patch-1 2017-04-07 22:41:07 -04:00
Shel R
d7e1a282fe Merge pull request #1046 from raymestalez/patch-2
Added an instance to the list
2017-04-07 22:40:29 -04:00
Shel R
c890b86ef6 Merge branch 'master' into patch-2 2017-04-07 22:39:59 -04:00
Valentin Ouvrard
5b571fc434 Merge branch 'master' into master 2017-04-08 13:39:31 +11:00
Valentin Ouvrard
3e4eb9c95f Merge branch 'master' into master 2017-04-08 13:39:09 +11:00
Shel R
ceba26d527 Merge pull request #1041 from R0ckweb/patch-1
Update List-of-Mastodon-instances.md
2017-04-07 22:39:05 -04:00
Shel R
7826b5e93d Merge branch 'master' into patch-1 2017-04-07 22:38:37 -04:00
Shel R
fc7b830719 Merge pull request #1036 from wirehack7/patch-1
Update List-of-Mastodon-instances.md
2017-04-07 22:37:51 -04:00
Shel R
c945f29e96 Merge branch 'master' into patch-1 2017-04-07 22:35:45 -04:00
Shel R
0d4d42dce6 Merge pull request #1035 from gfaivre/add-elao-com-instance
Add mastodon.elao.com instance
2017-04-07 22:35:08 -04:00
Shel R
cf03634e74 Merge branch 'master' into add-elao-com-instance 2017-04-07 22:34:45 -04:00
Shel R
698d74a15f Merge pull request #1030 from derekcecillewis/add-infinimatix.net-instance
Add infinimatix.net to instance list
2017-04-07 22:34:10 -04:00
Shel R
d7d165db5b Merge branch 'master' into add-infinimatix.net-instance 2017-04-07 22:33:40 -04:00
Shel R
d7f4300ee3 Merge pull request #1009 from ngerakines/patch-1
Added off-the-clock.us to the list of instances
2017-04-07 22:25:45 -04:00
Shel R
7632178300 Merge branch 'master' into patch-1 2017-04-07 22:23:50 -04:00
Shel R
e4e948a21b Merge pull request #797 from AndreLewin/master
Added Esperanto translation (eo)
2017-04-07 22:21:48 -04:00
Shel R
fef478781d Merge branch 'master' into master 2017-04-07 22:21:21 -04:00
Shel R
c1a553d2c2 Merge pull request #1006 from Ninetailed/master
Description in instance list for mastodon.ninetailed.uk
2017-04-07 22:18:11 -04:00
Shel R
3e8c1a1c36 Merge branch 'master' into master 2017-04-07 22:17:53 -04:00
Shel R
9fd8bbe15c Merge pull request #992 from Eychics/master
Add mastodon.nuzgo.net in instance list
2017-04-07 22:16:49 -04:00
Shel R
1b42f717f2 Merge branch 'master' into master 2017-04-07 22:15:55 -04:00
Shel R
7004c69204 Merge pull request #991 from vladooku/patch-1
Update List-of-Mastodon-instances.md
2017-04-07 22:15:12 -04:00
Shel R
9981972844 Merge branch 'master' into patch-1 2017-04-07 22:14:08 -04:00
Shel R
5f61ef2417 Merge pull request #989 from MTNDevelopment/master
Updates instance list
2017-04-07 22:13:34 -04:00
Shel R
cf13c97cb2 Merge branch 'master' into master 2017-04-07 22:13:06 -04:00
Shel R
90a408f592 Merge pull request #986 from estuans/patch-2
Update List-of-Mastodon-instances.md
2017-04-07 22:11:55 -04:00
Shel R
451b7431c9 Merge branch 'master' into patch-2 2017-04-07 22:11:24 -04:00
Shel R
530725ba3c Merge pull request #985 from Motoma/patch-1
Add Rich.GOP
2017-04-07 22:10:22 -04:00
Shel R
6efaee30b1 Merge branch 'master' into patch-1 2017-04-07 22:09:55 -04:00
Shel R
698fe3686a Merge pull request #982 from foxiehkins/master
Adding good-dragon.com instance to list
2017-04-07 22:09:15 -04:00
Shel R
05be34a94b Merge branch 'master' into master 2017-04-07 22:08:54 -04:00
Shel R
cb58694a81 Merge pull request #976 from shug0/patch-1
Adding masto.raildecake.fr, french instance 🌻🐘
2017-04-07 22:06:06 -04:00
Shel R
e51b6bba94 Merge branch 'master' into patch-1 2017-04-07 22:05:38 -04:00
Shel R
7f393a0b68 Merge pull request #975 from tomfhowe/patch-4
Use autohiding scrollbars in Microsoft Edge
2017-04-07 22:03:35 -04:00
Shel R
9f43e3b428 Merge branch 'master' into patch-4 2017-04-07 22:02:26 -04:00
Shel R
4a40b40324 Merge pull request #973 from ZiiX/patch-1
added instance
2017-04-07 21:59:51 -04:00
Shel R
4c05f0e630 Merge branch 'master' into patch-1 2017-04-07 21:59:21 -04:00
Shel R
d438eab673 Merge pull request #972 from amandavisconti/master
Added digitalhumanities.club instance
2017-04-07 21:56:57 -04:00
Shel R
7b7bff04df Merge branch 'master' into master 2017-04-07 21:54:32 -04:00
Shel R
e659608797 Merge pull request #970 from estuans/patch-1
Update Production-guide.md
2017-04-07 21:49:57 -04:00
Shel R
ed332693fe Merge branch 'master' into patch-1 2017-04-07 21:49:18 -04:00
Shel R
881e4277fd Merge pull request #969 from Fortyseven/patch-1
Add social.bytestemplar.com to instances list
2017-04-07 21:48:58 -04:00
Shel R
5dfc9854f1 Merge branch 'master' into patch-1 2017-04-07 21:48:19 -04:00
Shel R
b300bb3b4e Merge pull request #959 from mkody/patch-1
Added im-in.space
2017-04-07 21:45:06 -04:00
Shel R
6d519e6fd1 Merge branch 'master' into patch-1 2017-04-07 21:44:16 -04:00
Shel R
d2c9cc31de Merge pull request #953 from Technowix/patch-1
Add niu.moe, cuz it's kawaii
2017-04-07 21:43:20 -04:00
Shel R
065defefac Merge branch 'master' into patch-1 2017-04-07 21:42:10 -04:00
Shel R
6bfe068904 Merge pull request #944 from VirtuBox/patch-1
Update List-of-Mastodon-instances.md
2017-04-07 21:35:34 -04:00
Shel R
bc237d17a7 Merge branch 'master' into patch-1 2017-04-07 21:34:41 -04:00
Yann GUERN
485d75a805 #1141 on remote follow
The async action is send before persist, account.id not yet generated

Pull queue receive 'nil' so no profile update.
2017-04-08 03:24:35 +02:00
Eugen Rochko
4b621188ad Fix #1165 - before_action was called before protect_from_forgery 2017-04-08 02:30:50 +02:00
David Huerta
4fb24a70d3 Merge branch 'master' into patch-1 2017-04-07 19:46:07 -04:00
Eugen Rochko
b2a7218ab7 Fix #801 - Respect webfinger's canonical response of username/domain 2017-04-08 01:07:42 +02:00
Eugen
a872f2f4c6 Merge pull request #1204 from benklop/patch-1
Update Administration-guide.md
2017-04-08 01:00:32 +02:00
Eugen
32a6f0884c Merge pull request #1200 from huertanix/patch-2
Update Heroku-guide.md
2017-04-08 01:00:06 +02:00
Eugen
a54af44975 Merge pull request #1210 from raymestalez/patch-3
Add HackerNewsBot
2017-04-08 00:59:02 +02:00
Ray Alez
f113af5350 Add HackerNewsBot
I have created a bot that will post Hacker News stories with 100+ points. Adding it to the list.
2017-04-07 15:56:02 -07:00
benklop
f578cf8331 Update Administration-guide.md
the syntax for running the rake task wasn't correct.
2017-04-07 16:31:50 -06:00
Eugen
3cb13bdd84 Merge pull request #1125 from jasonrhodes/patch-1
Email service options :P
2017-04-08 00:24:38 +02:00
Eugen
0bf31f5436 Merge pull request #1147 from fmauNeko/docker_smaller_image
Fix npm/yarn cache cleaning
2017-04-07 23:45:48 +02:00
David Huerta
a7ab2204d4 Update Heroku-guide.md
Cleaning up the heroku admin command bit to match the form used in Administration-guide.md and clarify the wording a bit.
2017-04-07 17:44:32 -04:00
David Huerta
36a83cc4f9 Update Heroku-guide.md
Removing some of the confusion around what format S3 bucket names and regions should be entered as well as providing an example of an S3 policy that follows best security practices for this sort of thing.
2017-04-07 17:29:21 -04:00
Blake
30903d5f02 Add indigo.zone to list of instances 2017-04-07 14:10:37 -07:00
Mouse Reeve
94536af96d Adds Oulipo.social to Mastodons list
This is a Mastodon with a particular constraint about
what symbols you can post.
2017-04-07 14:06:49 -07:00
Eugen
0b32b5108e Merge pull request #1191 from d3vgru/master
change suggested cipher for nginx
2017-04-07 23:02:49 +02:00
Florian Maunier
12f1cdeed1 Fix npm/yarn cache cleaning 2017-04-07 22:51:52 +02:00
Eugen
e2f024147c Merge pull request #1172 from mjankowski/mj-heroku-docs
Update heroku instructions
2017-04-07 22:50:31 +02:00
Eugen
1961825ff9 Merge pull request #1183 from thoughtbot/cp-post-status-service-specs
Add specs for PostStatusService
2017-04-07 22:30:28 +02:00
Eugen
32748c0f71 Merge pull request #1184 from thoughtbot/extract-proper-status
DRY up reblog vs original status check
2017-04-07 22:28:15 +02:00
Eugen
37a36b0bec Merge pull request #1192 from seekr/patch-2
typo
2017-04-07 22:25:48 +02:00
Eugen
c7d9b81d41 Merge pull request #1193 from thoughtbot/status-specs
Implement pending specs on Status
2017-04-07 22:25:10 +02:00
Joël Quenneville
4fdeac21f4 Implement pending specs on Status
Implement the two pending specs on `Status`: `reblogs_count` and
`favourites_count`.
2017-04-07 15:36:06 -04:00
seekr
131f505fd0 typo 2017-04-07 16:33:13 -03:00
Ed Knutson
27012aaeb6 change suggested cipher for nginx 2017-04-07 14:10:39 -05:00
Aguay-val
4c751d25e5 Add mastodon.fun 2017-04-07 20:33:52 +02:00
Chad Pytel
ad5ddd5e95 Use I18n for media attachment validation errors
These are currently user facing errors, but are not localized. This adds the
ability for these messages to be localized.
2017-04-07 14:23:18 -04:00
Chad Pytel
13c0077003 Add specs for PostStatusService
This implements all pending specs, and adds additional coverage for the
following functionality:

* Normal status creation
* Creating a reply status
* Creating a sensitive status
* Creating a status with spoiler text
* A status with no spoiler text gets an empty string for spoiler text
* Creating a status with custom visibility
* Creating a status for an application
* Processing mentions
* Processing Hashtags
* Pinging PuSH hubs
* Crawling links
* Attaching media
2017-04-07 14:21:16 -04:00
Joël Quenneville
d4c94fa004 DRY up reblog vs original status check
Checking reblog vs original status was happening in multiple places
across the app. For views, this logic was encapsulated in a helper
method named `proper_status` but in the other layers of the app, the
logic was duplicated.

Because the logic is used at all layers of the app, we extracted it into
a `Status#proper` method on the model and changed all uses of the logic
to use this method. There is now a single source of truth for this
condition.

We added test coverage to untested methods that got refactored.
2017-04-07 14:18:30 -04:00
Chad Pytel
38bec79811 Add specs for media attachment validations
There are currently not specs for the two media validations that are performed
by `PostStatusService`. This adds specs for the validations that ensure that you
cannot attach more than four files, and that a status cannot have both image and
video attachments.
2017-04-07 12:50:43 -04:00
Matt Jankowski
0f4fa59812 Update heroku instructions 2017-04-07 12:50:29 -04:00
foxiehkins
41396de7a9 Merge branch 'master' into master 2017-04-07 14:01:20 +01:00
Jason Rhodes
2ac8a590cd Moved into a comment per feedback 2017-04-07 07:43:44 -04:00
Eugen
4e41cd9ab8 Merge pull request #1146 from tootsuite/fix-object-type-nil-exception
Fix nil#object_type error
2017-04-07 13:08:51 +02:00
Eugen
10459241a8 Merge pull request #1145 from tootsuite/feature-customized-default-locale
Allow setting of default language through config
2017-04-07 13:08:27 +02:00
Eugen
c9b23a93c7 Merge branch 'master' into fix-object-type-nil-exception 2017-04-07 13:07:36 +02:00
Eugen
0adee18d73 Merge branch 'master' into feature-customized-default-locale 2017-04-07 13:07:03 +02:00
Eugen Rochko
8a6d8de60a Fix nil#object_type error 2017-04-07 13:05:34 +02:00
Jantso Porali
786e6f94b9 Update Finnish translations, add sample Minio config (#954) 2017-04-07 12:58:12 +02:00
Erwan Leboucher
0c4e9fdda0 Merge branch 'master' into patch-4 2017-04-07 12:43:56 +02:00
Eugen Rochko
e3a3422a65 Allow setting of default language through config
Setting of locale in controller extracted to Localized concern,
the doorkeeper authorized applications controller moved under
custom namespace with inclusion of Localized, which resolves the
"it sometimes appears in a different random language" bug
2017-04-07 12:40:26 +02:00
Eugen Rochko
624a9a7136 Re-add forgotten <author> element on standalone <entry> 2017-04-07 12:21:00 +02:00
Eugen
1c351709bc Force UTF8 encoding on generated XML (#1140) 2017-04-07 11:09:14 +02:00
Sergei Č
8e7d0bda40 Update List-of-Mastodon-instances.md 2017-04-07 11:54:41 +03:00
Eugen
6d6a429af8 Rewrite Atom generation from stream entries to use Ox instead of Nokogiri (#1124)
* Rewrite Atom generation from stream entries to use Ox instead of Nokogiri::Builder

StreamEntry is now limited to only statuses, which allows some optimization. Removed
extra queries on AccountsController#show. AtomSerializer instead of AtomBuilderHelper
used in AccountsController#show, StreamEntriesController#show, StreamEntryRenderer
and PubSubHubbub::DistributionWorker

PubSubHubbub::DistributionWorker moves n+1 DomainBlock query to PubSubHubbub::DeliveryWorker
instead.

All Salmon slaps that aren't based on StreamEntry still use AtomBuilderHelper and Nokogiri

* All Salmon slaps now use Ox instead of Nokogiri. No touch from status on account
2017-04-07 05:56:56 +02:00
Jason Rhodes
5d43a9cae2 Email service options :P
Small addition in case people want email service options, sparkpost.com gives you 100k/mo free
2017-04-06 22:48:17 -04:00
Tristan Mahé
8a4ff30ceb updated instance hostname 2017-04-06 17:18:40 -07:00
Vladimir Mincev
56f4a94e7b Update Production-guide.md
Under ## General dependencies:
apt-get needs sudo and install was typed wrongly.
2017-04-07 01:05:32 +02:00
Eugen Rochko
31597fd377 Low-hanging fruit of query optimization, these indices were missing 2017-04-07 00:04:38 +02:00
YDrogen
4b08b7c502 Added masto.razrnet.fr 2017-04-06 23:59:56 +02:00
Eugen
8cb7d157bd Merge pull request #1100 from mjankowski/mj-reduce-image-size
Reduce size of background-photo.jpeg
2017-04-06 22:59:48 +02:00
Eugen
acf10a2c87 Merge pull request #1103 from alimony/vagrant-tweaks-01
Add a couple of network performance tweaks to Vagrantfile.
2017-04-06 22:58:44 +02:00
Markus Amalthea Magnuson
7596de1aec Add a couple of network performance tweaks to Vagrantfile. 2017-04-06 22:34:59 +02:00
Matt Jankowski
97ae53daa8 Reduce size of background-photo.jpeg
Reduced by running through `guetzli` image optimizer.
2017-04-06 16:24:57 -04:00
Eugen
cd77c75d6c Merge pull request #1088 from Wonderfall/master
Add metadata to Dockerfile
2017-04-06 21:35:10 +02:00
Tristan Mahé
cfbd90cf44 Added pericles.world in the list of instances 2017-04-06 12:05:37 -07:00
Wonderfall
ed2bfdee67 add Docker microbadger to README.md 2017-04-06 20:59:20 +02:00
Wonderfall
a3318814e1 add metadata to Dockerfile 2017-04-06 20:57:16 +02:00
isati
acf6436a99 Add manx.social instance 2017-04-06 19:31:25 +01:00
Erwan Leboucher
a0f1f9c664 Update List-of-Mastodon-instances.md 2017-04-06 20:18:56 +02:00
kadiix
f0d1107c53 Merge branch 'master' into patch-1 2017-04-06 19:33:36 +02:00
kadiix
02c1ad5347 Update List-of-Mastodon-instances.md 2017-04-06 19:33:17 +02:00
Eugen
fa494dbb1d Merge pull request #1038 from wade-r/optimize-dockerfile
Optimize Dockerfile
2017-04-06 19:32:50 +02:00
kadiix
a736b28646 Merge branch 'master' into patch-1 2017-04-06 19:32:31 +02:00
Eugen
c392c54271 Merge pull request #1042 from johnsudaar/feature/scalingo_one_click
Cosmetic changes to the scalingo deployment
2017-04-06 19:32:12 +02:00
kadiix
0d5d3c7abe Add an instance. 2017-04-06 19:15:57 +02:00
VirtuBox
c441208e29 Merge branch 'master' into patch-1 2017-04-06 19:11:39 +02:00
Eugen
5e63828917 Merge pull request #1048 from ashfurrow/slugignore
Updates slugignore
2017-04-06 19:09:45 +02:00
Eugen
ea86f4db15 Merge pull request #1070 from alexgleason/patch-4
Linux users must enable NFS for Vagrant
2017-04-06 19:02:14 +02:00
Alex Gleason
a3d204e982 Linux users must enable NFS for Vagrant 2017-04-06 12:16:39 -04:00
awea
07495cc13f Update List-of-Mastodon-instances.md 2017-04-06 18:07:15 +02:00
Julien
6a88151eda Add meow.social 2017-04-06 14:51:42 +02:00
Lukas Fülling
30619a6716 add my instance 2017-04-06 14:22:01 +02:00
Ash Furrow
a2adf84858 Updates slugignore. 2017-04-06 07:53:48 -04:00
Ray Alez
eadac4e7f4 Added an instance to the list
Just launched https://hackertribe.io/, added it to the list.
2017-04-06 03:12:34 -07:00
Yann GUERN
0209b7d1b5 Update List-of-Mastodon-instances.md
Add mastodon.land instance
2017-04-06 11:07:33 +02:00
Ryan Wade
a2637c1720 Optimize Dockerfile
Optimize Dockerfile, reduce build time.
2017-04-06 15:42:16 +08:00
Markus R
c62696bc46 Update List-of-Mastodon-instances.md 2017-04-06 08:52:15 +02:00
Guewen FAIVRE
83530f0eef Add mastodon.elao.com instance 2017-04-06 08:46:16 +02:00
Derek Lewis
73b8e67f4b Add infinimatix.net to instance list 2017-04-06 01:15:16 -04:00
Eugen Rochko
51d7caaf19 Fix wrong pubsub channel on public timelines 2017-04-06 04:03:23 +02:00
Jack Michaud
b5d87500d2 Add Northeastern University Mastodon 2017-04-05 20:58:21 -04:00
Eugen
a9c0062e80 Merge pull request #1013 from blackle/master
Catch more errors in process_follows so it doesn't fail
2017-04-06 02:35:34 +02:00
Eugen Rochko
dbd529109e Fix notifications delivered to wrong pubsub channel, optimized RemoveStatusService,
slightly optimized FanOutOnWriteService again
2017-04-06 02:26:59 +02:00
blackle
540d6efe88 Catch more errors in process_follows so it doesn't fail 2017-04-05 20:04:13 -04:00
Nick Gerakines
d025c5e593 Added off-the-clock.us to the list of instances 2017-04-05 19:19:44 -04:00
Ninetailed
7aede8e720 Description in instance list for mastodon.ninetailed.uk 2017-04-05 22:35:35 +01:00
Eugen
dea6e47de0 Merge pull request #993 from foozmeat/patch-1
Use NFS for the shared folder because it dramatically decreases latency for git operations.
2017-04-05 23:26:44 +02:00
Eugen Rochko
5442083b3c Split SalmonWorker into smaller parts, move profile updating into another job 2017-04-05 21:43:10 +02:00
James Moore
c19e0f1212 Use NFS for the shared folder because it dramatically decreases latency for git operations. 2017-04-05 11:53:39 -07:00
Eugen
bafbf63fcc Merge pull request #958 from yiskah/patch-1
Correct innacurate info and other fixes
2017-04-05 20:48:19 +02:00
Thibaut (Eychics)
eb98c99924 Add mastodon.nuzgo.net 2017-04-05 20:45:23 +02:00
ava
6b41fb2e6f Update List-of-Mastodon-instances.md 2017-04-05 21:44:13 +03:00
André Lewin
bf7cefa516 Merge branch 'master' into master 2017-04-05 20:28:58 +02:00
Brad Janke
65b3a2a5a6 Adds mtndevelopment 2017-04-05 13:14:27 -05:00
Eugen Rochko
d13d169922 Merge branch 'krainboltgreene-broadcast-to-worker' 2017-04-05 19:46:28 +02:00
Eugen Rochko
5b95be1c42 Replace calls to FeedManager#inline_render and #broadcast 2017-04-05 19:45:18 +02:00
Eugen Rochko
d755ce96da Merge branch 'broadcast-to-worker' of https://github.com/krainboltgreene/mastodon into krainboltgreene-broadcast-to-worker 2017-04-05 19:29:46 +02:00
Eugen Rochko
29ffe1cad3 Make sure Rabl is using Oj 2017-04-05 19:29:30 +02:00
Ben Field
8fa8004a2b Update List-of-Mastodon-instances.md 2017-04-05 18:20:08 +01:00
Christopher Gilbert
e5566ac6a6 Add Rich.GOP 2017-04-05 13:05:27 -04:00
Eugen
c9ebd5d19f Fix wrong variable used in publish channel 2017-04-05 18:58:32 +02:00
foxiehkins
ac1989d2c0 Update description on good-dragon.com 2017-04-05 17:49:04 +01:00
Eugen
1b8c244dff Add proper message to PushUpdateWorker, use redis directly 2017-04-05 18:48:41 +02:00
foxiehkins
32d4b51939 Add good-dragon.com 2017-04-05 17:47:52 +01:00
Eugen
3d8b80e1cc Merge branch 'master' into broadcast-to-worker 2017-04-05 18:44:33 +02:00
Thomas Alberola
b38bd58921 Adding masto.raildecake.fr, french instance 🌻🐘 2017-04-05 18:15:39 +02:00
tom
8989569dd4 Update components.scss
Use nicer scrollbars in MS edge
2017-04-05 12:10:25 -04:00
ZiiX
96812a6c79 added instance 2017-04-05 09:05:57 -07:00
Amanda Visconti
a31f5765af Added digitalhumanities.club instance 2017-04-05 12:04:21 -04:00
Ben Field
41a78be25e Update Production-guide.md
Corrected spelling error for "install"
2017-04-05 16:56:51 +01:00
Toby Deshane
9cf0b5b255 Add social.bytestemplar.com to instances list 2017-04-05 11:49:36 -04:00
Jonathan Hurter
b8218ca482 Make scalingo doc clearer 2017-04-05 17:48:55 +02:00
Jonathan Hurter
4335dffe35 Fix wrong url in scalingo.json 2017-04-05 17:28:11 +02:00
Kody
4de3182dc8 Added im-in.space 2017-04-05 17:00:03 +02:00
shel
ee758551d1 Correct innacurate info and other fixes
This guide was pretty out of date and also contained outright errors which were never true. I have updated it.
2017-04-05 10:58:41 -04:00
Eugen
b142a2ebf5 Merge pull request #947 from johnsudaar/feature/scalingo_one_click
Add Scalingo deployment support
2017-04-05 16:50:46 +02:00
Technowix
9a534d1df6 Add niu.moe, cuz it's kawaii 2017-04-05 16:18:52 +02:00
VirtuBox
28fb01c71a Update List-of-Mastodon-instances.md 2017-04-05 16:02:47 +02:00
Jonathan Hurter
d6bab0c71c Add doc 2017-04-05 16:00:48 +02:00
Jonathan Hurter
5e7ec0fe57 Use root repository url 2017-04-05 15:52:06 +02:00
Jonathan Hurter
152a1e578c Add Scalingo one click on readme 2017-04-05 15:33:19 +02:00
Eugen
a371a9e002 Merge pull request #940 from fahy/patch-1
Adding mastodon.irish
2017-04-05 15:24:52 +02:00
Eugen
be807ff7bd Merge pull request #930 from nicobz25/patch-1
Update List-of-Mastodon-instances.md
2017-04-05 15:24:40 +02:00
Eugen
68e0421577 Merge pull request #939 from JantsoP/master
More translation updates
2017-04-05 15:24:24 +02:00
Padraig Fahy
cbcfd92a14 Adding mastodon.irish 2017-04-05 14:00:35 +01:00
Eugen
b45b4865a7 Merge pull request #938 from wxcafe/patch-3
Updates description for `social.wxcafe.net`
2017-04-05 14:57:38 +02:00
Eugen
188172c401 Merge pull request #931 from farlistener/patch-3
Typography on partY
2017-04-05 14:57:14 +02:00
Eugen
7c4dde56a3 Merge pull request #936 from Angristan/patch-7
Add git and curl as dependencies
2017-04-05 14:57:01 +02:00
Jantso Porali
837030db98 updated blocking translation 2017-04-05 14:53:35 +02:00
Jantso Porali
deb001bba8 updated two-way auth and preferences translation 2017-04-05 14:49:29 +02:00
Jantso Porali
d3bf0307db updated translation for about page 2017-04-05 14:47:42 +02:00
Jonathan Hurter
bf523fcd16 Add node_modules and .cache to slugignore 2017-04-05 14:28:14 +02:00
Eugen Rochko
220bc48e8e Only render public payload once in FanOutOnWrite 2017-04-05 14:26:17 +02:00
wxcafé
259e626165 Update List-of-Mastodon-instances.md 2017-04-05 14:21:45 +02:00
Angristan
5ed2de6be2 Add git and curl as dependencies
In some VPS templates, they are not installed by default.
2017-04-05 14:11:08 +02:00
Jonathan Hurter
79765d61f5 Install nodejs before ruby 2017-04-05 13:53:30 +02:00
nicobz25
6201fba2d3 Merge branch 'master' into patch-1 2017-04-05 13:48:31 +02:00
Jonathan Hurter
0a984e90d3 Add scalingo support 2017-04-05 13:45:03 +02:00
Eugen Rochko
cfe91ac984 Add index on mentions status_id 2017-04-05 13:32:57 +02:00
Eugen Rochko
8530f9413b Replace ActionCable broadcast call with simple redis publish 2017-04-05 13:28:46 +02:00
Cédric Levieux
85c768bf16 Typography on partY 2017-04-05 13:19:34 +02:00
nicobz25
9572282a55 Update List-of-Mastodon-instances.md
Add our mastodon instance :) Thanks !
2017-04-05 13:13:09 +02:00
Eugen
f8a5ff95ec Merge pull request #923 from jguerder/patch-1
Added mastodon.cx
2017-04-05 12:33:02 +02:00
Eugen
af0decf597 Merge pull request #925 from Angristan/patch-6
Add mstdn.io
2017-04-05 12:26:17 +02:00
Eugen
ba5e23ecce Merge pull request #926 from farlistener/patch-2
Add mastodon.partipirate.org
2017-04-05 12:26:02 +02:00
Eugen
a107de07c9 Merge pull request #922 from Angristan/patch-5
ECDH only
2017-04-05 12:18:57 +02:00
Valentin_NC
93e53a3311 add mastodon.cloud to List of instances 2017-04-05 20:36:21 +11:00
Cédric Levieux
bdf3ac95b8 Add mastodon.partipirate.org 2017-04-05 11:24:21 +02:00
Angristan
fa6f7c8898 Add mstdn.io 2017-04-05 11:16:56 +02:00
Jordan Guerder
5963fce131 Added mastodon.cx 2017-04-05 10:52:56 +02:00
Angristan
5dbcd92193 ECDH only
Disable DHE ciphers. We don't loose any compatibility as we already use TLS 1.2, and ECDH is faster and safer.
Also, it's better so specify the curve.

This is the conf I use here : https://tls.imirhil.fr/https/mstdn.io
2017-04-05 10:44:08 +02:00
Eugen
792389da38 Merge pull request #909 from scriptjunkie/master
Add https://securitymastod.one/
2017-04-05 10:29:58 +02:00
Eugen
8b8839978a Merge branch 'master' into master 2017-04-05 10:29:52 +02:00
Eugen
78cf0fe1c7 Merge pull request #901 from ashfurrow/patch-3
Adds mastodon.technology instance
2017-04-05 10:29:14 +02:00
Eugen
18d0e817dd Merge pull request #911 from IMcD23/patch-1
Added mastodon.network instance.
2017-04-05 10:28:58 +02:00
Eugen
1904a1aa14 Merge pull request #919 from JantsoP/master
More updates to Finnish language
2017-04-05 10:27:12 +02:00
Eugen
b5f8273312 Merge pull request #902 from asm/ssl_best_practices
SSL best practices for nginx
2017-04-05 10:26:32 +02:00
Eugen
3504da5cac Fix API method URL typo 2017-04-05 10:25:05 +02:00
Eugen
8814f90eb5 Merge pull request #903 from yiskah/patch-6
Corrected misinformation regarding Direct Posts
2017-04-05 10:24:25 +02:00
Eugen
6b566c6b88 Merge pull request #908 from krainboltgreene/application-worker-for-shared-logic
ApplicationWorker for shared worker behavior
2017-04-05 10:23:52 +02:00
Eugen
6b02591fa3 Merge pull request #910 from krainboltgreene/master
Mastodon isn't using jbuilder or sdoc, and it prevents an upgrade to 2.4.0
2017-04-05 10:23:32 +02:00
Eugen
aab818717e Merge pull request #914 from bradurani/bu/query_cleanup
Eliminate unnecessary queries and query clauses with none and all
2017-04-05 10:23:10 +02:00
JantsoP
20b53e6add Merge branch 'master' into master 2017-04-05 10:07:17 +02:00
JantsoP
3ec221d3b7 updated reblog to boost translation
Since it is that :D
2017-04-05 09:57:25 +02:00
JantsoP
b8a867adcc updated translation
Updated some translations after seeing them in service. Should be better now
2017-04-05 09:56:10 +02:00
JantsoP
473e4f7813 udpdated display_name translation 2017-04-05 09:52:31 +02:00
JantsoP
b845ef395d updated reblog translation 2017-04-05 09:51:35 +02:00
Brad Urani
6a1da87cd3 Eliminate unnecessary queries and query clauses with none and all 2017-04-05 06:02:58 +00:00
Ian McDowell
8040d1d8ce Update List-of-Mastodon-instances.md
Added mastodon.network.
2017-04-04 23:43:57 -05:00
Kurtis Rainbolt-Greene
03adb5d727 Mastodon isn't using jbuilder or sdoc, and it prevents an upgrade to 2.4.0 2017-04-04 21:31:02 -07:00
scriptjunkie
29efeecb9e Add https://securitymastod.one/ 2017-04-04 23:23:15 -05:00
Kurtis Rainbolt-Greene
22dcadedb4 We're going to want these nice helper methods, lets share them with a parent class that matches Rails 5 practices (application level abstraction) 2017-04-04 21:14:37 -07:00
Kurtis Rainbolt-Greene
7bed4e51db Moved to the worker 2017-04-04 20:51:44 -07:00
Kurtis Rainbolt-Greene
9638894233 Moving in the inline render 2017-04-04 20:51:18 -07:00
Kurtis Rainbolt-Greene
220051b8b2 I don't actually think we need that. 2017-04-04 20:48:22 -07:00
Kurtis Rainbolt-Greene
0069c01285 Moving the queue_at into the worker 2017-04-04 20:39:14 -07:00
Kurtis Rainbolt-Greene
dc5704b0b0 This method isn't used anymore 2017-04-04 20:38:07 -07:00
Kurtis Rainbolt-Greene
96ef933820 Replacing the broadcast method with the one defined in the feed manager 2017-04-04 20:36:03 -07:00
Kurtis Rainbolt-Greene
1e96ce378e By pushing this into a worker we can reduce the amount of time the feed manager using workers eat up a connection 2017-04-04 20:23:40 -07:00
Valentin_NC
b73cee9774 add volume for redis container 2017-04-05 14:13:22 +11:00
shel
7d354cc8c5 Corrected misinformation regarding Direct Posts
Unless something changed recently I have no clue why this said that direct posts do not federate because they do.
2017-04-04 21:47:13 -04:00
Jason Snell
fa7b74cf51 SSL best practices for nginx 2017-04-04 18:43:21 -07:00
Ash Furrow
128dcb2825 Adds mastodon.technology 2017-04-04 21:35:45 -04:00
Eugen Rochko
ccb6a658fd Merge branch 'fakenine-add_more_tests_to_models' 2017-04-05 03:31:45 +02:00
Eugen Rochko
667ffafef8 Fix spec 2017-04-05 03:31:26 +02:00
Eugen
4c92f15664 Merge branch 'master' into add_more_tests_to_models 2017-04-05 03:27:38 +02:00
Eugen
94d00f2788 Merge pull request #885 from optikfluffel/patch-1
Update Production-guide.md
2017-04-05 03:24:08 +02:00
Eugen
afdcdce551 Merge pull request #839 from SirCmpwn/profile-readability
Improve readability of text on profiles
2017-04-05 03:23:16 +02:00
Eugen
731f397b08 Merge pull request #860 from pierreozoux/patch-1
Update the list of instances
2017-04-05 03:20:51 +02:00
Eugen
aa464aa0d8 Merge branch 'master' into patch-1 2017-04-05 03:20:37 +02:00
Eugen
a23123e49c Merge pull request #859 from dereckson/patch-1
Add social.nasqueron.org instance
2017-04-05 03:20:01 +02:00
Eugen
78c1e2e958 Merge branch 'master' into patch-1 2017-04-05 03:19:14 +02:00
Eugen
ab98591af8 Merge pull request #843 from fmauNeko/patch-1
Update List-of-Mastodon-instances.md
2017-04-05 03:18:48 +02:00
Eugen
bdaf31bcc9 Merge branch 'master' into patch-1 2017-04-05 03:18:42 +02:00
Drew DeVault
c106b6d3e0 Improve readability of text on profiles 2017-04-04 21:13:23 -04:00
Eugen
086a88f3bb Merge pull request #863 from Eychics/master
Add closed_registrations message on French language
2017-04-05 03:11:13 +02:00
Eugen
e76dd52b08 Merge pull request #818 from JantsoP/master
Updated Finnish Translation
2017-04-05 03:09:20 +02:00
Eugen
f735efdbc1 Merge pull request #846 from TrollDecker/master
Update social.diskseven.com's IPv6 status
2017-04-05 03:08:59 +02:00
Eugen
1709dee0f1 Merge pull request #842 from Angristan/patch-2
Missing quotes
2017-04-05 03:08:24 +02:00
Eugen
1c634ad21a Merge pull request #845 from thurloat/patch-1
Add mastodon.club to running instances list
2017-04-05 03:07:59 +02:00
Eugen
682c8a6fc8 Merge pull request #848 from ProgVal/patch-3
Add oc.todon.fr to the list of instances.
2017-04-05 03:07:46 +02:00
Eugen
66ec005bf8 Merge pull request #854 from Angristan/patch-3
Fix crontab edit
2017-04-05 03:07:28 +02:00
Eugen
c12214963e Merge pull request #866 from kklleemm/patch-1
Fix typos on french translations
2017-04-05 03:06:41 +02:00
Eugen
aa8dacbc8a Merge pull request #792 from chapeaumeinfreund/master
Updated and fixed german orthography
2017-04-05 03:05:50 +02:00
Eugen
ce524cbb49 Merge pull request #790 from chapeaumeinfreund/patch-1
Updated and fixed german orthography
2017-04-05 03:05:31 +02:00
Eugen
117b22e905 Merge pull request #852 from peterkeen/email-whitelist-817
[#817] Add email whitelist
2017-04-05 03:04:58 +02:00
Eugen
d3dab68978 Merge pull request #891 from ashfurrow/patch-1
Adds instructions for adding admin users for Heroku installs
2017-04-05 03:01:43 +02:00
Eugen
57466d542b Merge pull request #805 from nevillepark/master
Changed "reblogs" to "boosts"
2017-04-05 03:00:57 +02:00
Eugen
417273326a Merge pull request #894 from asm/mastodon_cc
Adding https://mastodon.cc
2017-04-05 02:56:54 +02:00
Eugen
909d81923e Merge pull request #898 from SirCmpwn/remote-follow-improvements
Remote follow improvements
2017-04-05 02:54:03 +02:00
Eugen
2edeb3fe1c Merge pull request #858 from krainboltgreene/patch-6
Use active record shorthand
2017-04-05 02:53:39 +02:00
Ash Furrow
dd441606aa Updates instructions. 2017-04-04 20:53:31 -04:00
Eugen
feea517046 Merge pull request #872 from kwaio/patch-1
typo in admin doc
2017-04-05 02:53:09 +02:00
Drew DeVault
f7e35d90db Remote follow improvements
This stores the @username@instance you provide in your session and
reuses it the next time you remote follow someone from this instance.
2017-04-04 20:52:31 -04:00
Eugen
e55fbdede3 Merge pull request #868 from Angristan/patch-4
Add file package
2017-04-05 02:52:00 +02:00
Eugen Rochko
bda37489ac Remove PuSH subscriptions when delivery is answered with a 4xx error 2017-04-05 02:34:33 +02:00
Jason Snell
50a88d6a6e Adding https://mastodon.cc 2017-04-04 16:35:57 -07:00
Samy KACIMI
5af0ecbcd9 alphebatically order test gem group as required by rubocop 2017-04-05 00:52:55 +02:00
Samy KACIMI
79ef756f64 fix rubocop issues 2017-04-05 00:47:17 +02:00
Ash Furrow
04225ed72e Adds instructions for adding admin users. 2017-04-04 18:45:24 -04:00
Samy KACIMI
073f92fc76 Merge branch 'master' into add_more_tests_to_models 2017-04-05 00:43:10 +02:00
Samy KACIMI
46c0e8b0e7 update account_spec 2017-04-05 00:37:23 +02:00
Samy KACIMI
7762467b47 rollback database.yml update 2017-04-05 00:31:31 +02:00
Samy KACIMI
81c76fe375 add more tests to models 2017-04-05 00:29:56 +02:00
Valentin_NC
4512fde181 add persistance to Postresql container 2017-04-05 09:06:08 +11:00
Udo Kramer
1e5a1b9abd Update Production-guide.md 2017-04-04 23:45:29 +02:00
Eugen
ed22f65b3c Merge pull request #876 from krainboltgreene/patch-7
Quick attempt to get pull requests passing
2017-04-04 23:11:34 +02:00
Kurtis Rainbolt-Greene
9ae9ecdebe Quick attempt to get pull requests passing 2017-04-04 12:14:44 -07:00
axolotl
8736ef50ad Added Esperanto translation inside the javascripts folder 2017-04-04 20:54:42 +02:00
Nope Nope
dcda852b5f typo in admin doc
s/rails/rake/
2017-04-04 20:45:32 +02:00
Angristan
6091b9b1a9 Add file package
If the file package is not installed, we get "Validation failed: File has contents that are not what they are reported to be" when upload media.
2017-04-04 19:23:53 +02:00
Eugen Rochko
6fd865c000 Spawn FeedInsertWorker to deliver status into personal feed 2017-04-04 19:21:37 +02:00
Clément D
350958babf Fix typos on french translations 2017-04-04 19:09:54 +02:00
Thibaut (Eychics)
9a5d6e9715 Add closed_registrations message on French language 2017-04-04 18:58:19 +02:00
Pierre Ozoux
41ba74b511 Update the list of instances 2017-04-04 17:28:29 +01:00
Valentin Lorentz
22000ef7a9 Add oc.todon.fr to the list of instances.
[SKIP CI]
2017-04-04 18:11:41 +02:00
Sébastien Santoro
7015578655 Add social.nasqueron.org instance 2017-04-04 18:11:14 +02:00
Kurtis Rainbolt-Greene
731e650681 Use active record shorthand 2017-04-04 09:04:07 -07:00
Angristan
2fcf8d79ad Fix crontab edit
Missing -u parameter to specify the mastodon user.
2017-04-04 17:23:56 +02:00
Pete Keen
e9a6da6bc7 [#817] Add email whitelist
This adds the ability to filter user signup with a whitelist
instead of or in addition to a blacklist.

Fixes #817
2017-04-04 11:20:15 -04:00
Jo Decker
d8855150a0 Update social.diskseven.com's IPv6 status
As far as I'm aware, my instance should be supporting IPv6 now. Was an error on my part that it wasn't working before.
2017-04-04 15:29:07 +01:00
Adam Thurlow
192f079776 Add mastodon.club to running instances list 2017-04-04 11:14:51 -03:00
Florian Maunier
58bdb9b42e Update List-of-Mastodon-instances.md
Add my own instance
2017-04-04 16:03:05 +02:00
Angristan
665ec615e3 Missing quotes 2017-04-04 15:57:37 +02:00
Eugen Rochko
5f54981846 New admin setting: open/close registrations, with custom message, from the admin UI 2017-04-04 15:28:12 +02:00
JantsoP
10a8666e04 updated line 28 about GitHub 2017-04-04 15:07:15 +02:00
Eugen
405c495c23 Merge pull request #804 from fhalna/development
Accessibility Fix.
2017-04-04 14:52:27 +02:00
Eugen
fee3312193 Merge pull request #803 from 0xa/patch-1
Add octodon.social
2017-04-04 14:51:47 +02:00
Eugen
48dfdad492 Merge branch 'master' into patch-1 2017-04-04 14:51:42 +02:00
Eugen
ed4a723a82 Merge pull request #800 from unascribed/patch-1
Add fern.surgeplay.com to the instances list
2017-04-04 14:51:01 +02:00
Eugen
c359e60034 Merge pull request #799 from Wonderfall/master
update social.targaryen.house info
2017-04-04 14:50:35 +02:00
Eugen
4bb623a595 Merge pull request #832 from ndarville/patch-1
Fix typo in ISSUE_TEMPLATE
2017-04-04 14:50:14 +02:00
Eugen
7c075b5551 Merge pull request #788 from yiskah/patch-5
Add instances from instances.mastodon.xyz
2017-04-04 14:49:48 +02:00
Niclas Darville
904f9266ef Fix typo in ISSUE_TEMPLATE 2017-04-04 14:49:31 +02:00
Eugen
1e81cad3f3 Merge pull request #815 from walfie/patch-1
Fix typo in Heroku guide
2017-04-04 14:49:06 +02:00
Eugen
8e693b8e41 Merge pull request #816 from leowzukw/patch-1
More consistent typography
2017-04-04 14:48:51 +02:00
Eugen
60d07c06c3 Merge pull request #831 from ndarville/patch-1
Create ISSUE_TEMPLATE.md to bring down duplicate issues
2017-04-04 14:48:22 +02:00
Niclas Darville
be2e7e1802 Create ISSUE_TEMPLATE.md 2017-04-04 14:46:08 +02:00
Eugen Rochko
38b504b7a7 Remove sidekiq-merger 2017-04-04 14:28:57 +02:00
Eugen Rochko
82aaedec46 Reduce number of items in feeds, optimize regeneration worker slightly,
make regeneration worker unique, (only schedule/execute once at a time)
2017-04-04 13:58:34 +02:00
Eugen Rochko
b1f3499c38 Optimize FeedManager#unmerge, and slightly optimize FeedManager#merge 2017-04-04 13:43:36 +02:00
Eugen Rochko
b21f7c28f6 Move OStatus processing back into default queue 2017-04-04 13:02:49 +02:00
Eugen Rochko
ce9df2fa82 Optimize filter methods in FeedManager a bit, use redis pipelining on merge/unmerge feed methods,
do not re-create a dynamic class on each feed push call, make sure redis-rb uses hiredis
2017-04-04 13:01:14 +02:00
halna_Tanaguru
3abb0f7bc7 Merge branch 'master' into development 2017-04-04 12:06:53 +02:00
JantsoP
db4a41cf58 Merge branch 'master' into master 2017-04-04 08:41:46 +02:00
JantsoP
dc89fc17cc updated translation 2017-04-04 08:29:53 +02:00
JantsoP
b8243c1b49 changed line 25 for better translation 2017-04-04 08:26:59 +02:00
Leo Wzukw
e81ba26be9 More consistent typography 2017-04-04 06:58:17 +02:00
walfie
c22388fc79 Fix typo in Heroku guide 2017-04-04 00:00:56 -04:00
Eugen Rochko
eb023beb49 Fix #808 - smaller elephant friend PNG for frontpage 2017-04-04 02:03:16 +02:00
Eugen Rochko
b510a56c0c Only call regeneration worker after first login after a 14 day break 2017-04-04 02:00:10 +02:00
Eugen Rochko
4c53af64f0 Fix ActionController::Parameters in API issue 2017-04-04 01:33:34 +02:00
Eugen Rochko
f722bd2387 Separate background jobs into different queues. ATTENTION: new queue "pull"
must be added to the Sidekiq invokation in your systemd file

The pull queue will handle link crawling, thread resolving, and OStatus
processing. Such tasks are more likely to hang for a longer time (due to
network requests) so it is more sensible to not make the "in-house" tasks
wait for them.
2017-04-04 00:53:20 +02:00
Neville Park
ce1ca28594 Changed "reblogs" to "boosts" 2017-04-03 17:45:36 -04:00
Alice
5b6f4fdeb4 Add octodon.social 2017-04-03 23:05:03 +02:00
Eugen Rochko
8232f76c48 Add check for visibility.nil? even though it can't ever be, to check for race conditions 2017-04-03 22:54:46 +02:00
halna_Tanaguru
3f30ae1f97 accessibility fix
eanable focus on ClearColumnButton
2017-04-03 22:45:29 +02:00
Aesen
1ed37fae9b Add fern.surgeplay.com to the instances list
It's hard to tell if this is supposed to be alphabetically sorted or not. I put it after epiktistes since it starts with F - let me know if it should go elsewhere.
2017-04-03 15:52:33 -04:00
Wonderfall
cc451e1fcb update social.targaryen.house info 2017-04-03 21:36:28 +02:00
axolotl
0700521ef3 added Esperanto (eo) 2017-04-03 20:22:50 +02:00
Eugen Rochko
98a93aa07e Fix norwegian translation being malformed 2017-04-03 19:50:55 +02:00
Eugen Rochko
68f829e11c Add basic logging of who resolved report 2017-04-03 19:35:00 +02:00
Eugen Rochko
71458dc6df When taking action on a report (silence/suspend), it dismisses all other
reports for that user automatically
2017-04-03 19:19:54 +02:00
Korbinian
ec8029a955 Updated and fixed orthography 2017-04-03 19:10:48 +02:00
Korbinian
7dd5ba42a3 Updated and fixed german orthography 2017-04-03 19:01:17 +02:00
Eugen Rochko
b7c1b12367 Make default admin UI page reports. Add admin UI for creating a domain block 2017-04-03 18:55:06 +02:00
shel
8a45a97e2e Add instances from instances.mastodon.xyz
Updated list with lots of instances that have been added to instances.mastodon.xyz but not this list
2017-04-03 12:28:36 -04:00
Eugen
f6e9251054 Merge pull request #777 from chriswk/master
Add Norwegian locale
2017-04-03 18:06:37 +02:00
Eugen
9738aaf5fb Merge pull request #782 from wxcafe/patch-1
Adds social.wxcafe.net
2017-04-03 18:06:16 +02:00
Eugen
ce433e22f6 Merge pull request #784 from singingwolfboy/github-capitalized
GitHub should be capitalized
2017-04-03 18:06:02 +02:00
David Baumgold
5652f00d81 GitHub should be capitalized 2017-04-03 11:44:11 -04:00
wxcafé
d06c810b16 Adds social.wxcafe.net 2017-04-03 17:32:25 +02:00
Christopher Kolstad
5cb011b66b Add Norwegian locale 2017-04-03 16:32:03 +02:00
Eugen
663e5378c0 Merge pull request #775 from JantsoP/master
Finnish Translation
2017-04-03 15:04:51 +02:00
JantsoP
65d667dc6c another typo. fuck me 2017-04-03 14:56:13 +02:00
JantsoP
d3fde60297 fixed an sneaky peaky tpy 2017-04-03 14:54:53 +02:00
JantsoP
85a8b62ca2 add finnish translation
add finnish translation
2017-04-03 14:43:07 +02:00
JantsoP
97803600ed add finnish translation
add finnish translation
2017-04-03 14:20:50 +02:00
JantsoP
a229840ffe fixed typo 2017-04-03 14:16:03 +02:00
JantsoP
bfa99981e5 Merge branch 'master' into master 2017-04-03 13:50:57 +02:00
JantsoP
6501ffdadc add finnish translation
add finnish translation
2017-04-03 13:35:12 +02:00
JantsoP
ae95f35fe6 add finnish translation
add finnish translation
2017-04-03 13:34:26 +02:00
JantsoP
22f88b845a add finnish translation
add finnish translation
2017-04-03 13:33:43 +02:00
JantsoP
eabb86b124 add finnish language
add finnish language
2017-04-03 13:32:10 +02:00
JantsoP
b0f4c9b91f finnish translation
finnish translation
2017-04-03 13:25:46 +02:00
JantsoP
f9b4f30de6 updated final translation
updated final translation
2017-04-03 13:07:09 +02:00
Eugen
01b79beca4 Merge pull request #769 from Angristan/patch-1
Improve french translation
2017-04-03 12:59:41 +02:00
Eugen
5d854f37b4 Merge pull request #764 from ticky/tweak-quick-start-area
Fix the position of the Mastodon mascot in the UI
2017-04-03 12:59:16 +02:00
Eugen
92a0b3d73a Merge pull request #765 from marrus-sh/mastodon-API-update
Update API docs
2017-04-03 12:58:38 +02:00
Eugen
2f7139c6b5 Merge pull request #768 from marvinkopf/mutedropdown
add mute option in status dropdown
2017-04-03 12:53:49 +02:00
JantsoP
87854745e9 Create new translation file
Still in progress. Should be done shortly
2017-04-03 12:42:09 +02:00
JantsoP
69fc95a2f5 Create Finnish translation for Mastodon
Create Finnish translation for Mastodon
2017-04-03 12:09:33 +02:00
Angristan
c97fc5c1f4 Improve french translation 2017-04-03 11:04:00 +02:00
Marvin Kopf
1236a12cae add mute option in status dropdown 2017-04-03 10:44:08 +02:00
Kibigo!
b455cbc6d3 "direct" in statuses#create 2017-04-03 01:23:37 -07:00
Kibigo!
a9db64cf26 Forgot direct statuses :P 2017-04-02 22:46:10 -07:00
Kibigo!
5d2f1d600d Typofixes 2017-04-02 21:17:07 -07:00
Kibigo!
5d65aa3bf9 Code now code 2017-04-02 21:13:15 -07:00
Kibigo!
565568dd9c Merging 'upstream' updates 2017-04-02 21:07:07 -07:00
Kibigo!
6e41ef55ed Updated API docs 2017-04-02 21:03:49 -07:00
Jessica Stokes
2d384850cb Fix the position of the Mastodon mascot in the UI
The Mastodon mascot was previously anchored to the bottom, and that was since broken. This restores that behaviour!

It also disables the double-scrollbar behaviour that was caused by this area allowing overflow-y in addition to its parent doing so.
2017-04-03 13:16:14 +10:00
Eugen
1a5ab538de Merge pull request #760 from ProgVal/patch-2
Improve French translation
2017-04-03 03:40:34 +02:00
Eugen
3f650c06b6 Merge pull request #757 from yiskah/patch-4
Add social.imirhil.fr
2017-04-03 03:40:20 +02:00
Eugen
71b9205679 Merge pull request #759 from Wonderfall/master
use alpine-based official images
2017-04-03 03:40:00 +02:00
Eugen
6b9c50117f Merge pull request #748 from ProgVal/patch-1
Fix install instructions.
2017-04-03 03:39:35 +02:00
Valentin Lorentz
fbcba65f81 Improve French translation
Fix typos and typography. Make vocabulary and grammar more uniform.
2017-04-03 03:16:37 +02:00
Wonderfall
b7beb4368c use alpine-based official images 2017-04-03 03:09:56 +02:00
shel
e2526a62e7 Add social.imirhil.fr 2017-04-02 20:11:28 -04:00
Eugen
61f90f15b0 Merge pull request #743 from Kazhnuz/master
Update French Translation
2017-04-03 01:10:36 +02:00
Eugen
d2358aefec Merge branch 'master' into master 2017-04-03 01:07:53 +02:00
Eugen
30df1c8476 Merge pull request #751 from Wonderfall/master
update Dockerfile
2017-04-03 01:03:42 +02:00
Wonderfall
b8d2373e0b Update Dockerfile 2017-04-03 00:56:05 +02:00
Wonderfall
92d35c52d9 update Dockerfile 2017-04-03 00:46:44 +02:00
Eugen
d7d60073f9 Merge pull request #744 from fpiesche/master
Add in-depth user guide
2017-04-03 00:26:24 +02:00
Eugen
61894582b8 Merge pull request #746 from eramdam/feature/improve-french-locales
Feature/improve french locales (again)
2017-04-03 00:25:56 +02:00
Eugen
ce495138ac Merge pull request #747 from Themimitoof/patch-1
add link to another mastodon instance.
2017-04-03 00:25:37 +02:00
Valentin Lorentz
9ca548c17e Also fix the start command. 2017-04-03 00:10:51 +02:00
Valentin Lorentz
82ae07e48b Fix install instructions.
Closes GH-745.
2017-04-03 00:08:40 +02:00
Michael Vieira
b69e97c40b add link to another mastodon instance. 2017-04-03 00:04:54 +02:00
Kazhnuz
c76d20c2a0 Add forgotten comma 2017-04-02 23:39:41 +02:00
Florian Piesche
590558d52c Update images to standard sizes. Write up Federated/Local timelines and rename relevant image. 2017-04-02 22:31:43 +01:00
Damien Erambert
4f7cce25ac Add more lcoales in fr.jsx 2017-04-02 14:23:40 -07:00
Kazhnuz
633e5ec6f6 Update French Translation 2017-04-02 23:18:01 +02:00
Florian Piesche
e92796b49c Add info on CW tags not automatically hiding images. 2017-04-02 22:07:23 +01:00
Florian Piesche
a729d0c2b1 Add user guide to README toc 2017-04-02 22:05:21 +01:00
Damien Erambert
71cf9d0608 Fix typo in simple_form.fr.yml 2017-04-02 14:04:06 -07:00
Damien Erambert
5e45982ed9 Improve translation of data import in fr.yml 2017-04-02 14:03:57 -07:00
Damien Erambert
b829460b4a Better translation for revoked token in doorkeeper.fr.yml 2017-04-02 14:03:41 -07:00
Damien Erambert
65e6a00817 Fix date format in doorkeeper.fr.yml 2017-04-02 14:03:31 -07:00
Florian Piesche
60a82c9022 Update to catch up with new features and add missing images. 2017-04-02 22:03:00 +01:00
Florian Piesche
b277e88aae Write up reporting. 2017-04-02 21:45:51 +01:00
Florian Piesche
03b2f37b46 Add information about the Direct privacy setting. 2017-04-02 21:39:05 +01:00
Florian Piesche
0f274589dd Add information on following remote users or using the Search box to find them. 2017-04-02 21:33:39 +01:00
Eugen
2cd792fc3d Merge pull request #737 from nevillepark/patch-2
Corrected spelling mistake
2017-04-02 22:23:11 +02:00
Eugen
09b4b65fde Merge pull request #740 from 0x70b1a5/master
remove black border on video mute/spoiler buttons
2017-04-02 22:22:56 +02:00
Eugen
48cb2dccd2 Merge pull request #741 from eramdam/feature/improve-french-locales
Improve/add some French locales
2017-04-02 22:22:44 +02:00
Eugen Rochko
a23e4380b2 Avoid re-loading already loaded relationships. Also fixes issue where wrong
button would be displayed in account lists for unloaded relationships
2017-04-02 22:02:38 +02:00
Eugen Rochko
aaa4d1b0fb Keep track of which timelines are connected live to avoid redundant
refreshes on navigation
2017-04-02 21:44:06 +02:00
Eugen Rochko
3618cc04ff Add heartbeat to websockets streaming API connections 2017-04-02 21:27:14 +02:00
Eugen Rochko
2d07cb5771 Catching rack timeout from rails doesn't work 2017-04-02 21:12:18 +02:00
Tobias Merkle
f25fc04ea1 single-quotes 2017-04-02 14:55:13 -04:00
Tobias Merkle
ca21be3e16 remove black border on buttons 2017-04-02 14:54:24 -04:00
Eugen Rochko
5b12624847 Add proper error page for request timeouts 2017-04-02 19:43:44 +02:00
Neville Park
54c796c8df Corrected spelling mistake 2017-04-02 13:01:55 -04:00
Eugen
34ff11c496 Merge pull request #729 from Abzol/master
Fix word-break in account profiles
2017-04-02 17:53:00 +02:00
Eugen
25ed563cdb Merge pull request #735 from TheKinrar/patch-3
Add link to instances.mastodon.xyz
2017-04-02 17:50:15 +02:00
TheKinrar
9f219d8968 Add link to instances.mastodon.xyz 2017-04-02 17:48:25 +02:00
Olivia Mossberg
30da6440d0 Merge branch 'master' of github.com:tootsuite/mastodon
It's just an upstream merge
2017-04-02 17:04:31 +02:00
Olivia Mossberg
f4b5fe9caf Fix word-break in account profiles
word-break:break-all is a surefire way to break things. It should be set
to normal.
This merge just set it back to what it should be.
Tested on Firefox 52.0.2 and Chrome 56.0.2924.87 with no detected
errors.
2017-04-02 16:54:24 +02:00
Eugen Rochko
4b7dca4713 Fix wording "show reblogs" -> "show boosts", order reports chronologically in
admin UI
2017-04-02 16:45:49 +02:00
Eugen
3a62721e54 Merge pull request #728 from bottitytto/add-mashek
Adding social.mashek.net
2017-04-02 16:30:30 +02:00
Eugen Rochko
d6b965cf08 Fix issue with feed merge-in code as well 2017-04-02 15:58:25 +02:00
Eugen Rochko
e809caa0e1 Fix feed regeneration bug 2017-04-02 15:46:31 +02:00
Sina Mashek
aaea7e1e7c Adding social.mashek.net 2017-04-02 16:22:13 +03:00
Eugen Rochko
750662d9e2 Merge branch 'maximeborges-master' 2017-04-02 12:38:11 +02:00
Eugen Rochko
34aff3e269 Merge branch 'master' of https://github.com/maximeborges/mastodon into maximeborges-master 2017-04-02 12:36:26 +02:00
Eugen
e70661b495 Merge pull request #723 from jkap/patch-1
Updated list of instances for gay.crime.team
2017-04-02 12:33:34 +02:00
Eugen
a2fa09518f Merge branch 'master' into patch-1 2017-04-02 12:33:29 +02:00
Eugen
3e84a86eb8 Merge pull request #727 from TheKinrar/patch-2
Add IPv6 column in list and remove dead instance
2017-04-02 12:32:17 +02:00
TheKinrar
f5d434e433 Add IPv6 column in list and remove dead instance
This is done after some complaints I saw on Twitter about the lack of IPv6 support on Mastodon instances.
Also, seeing this may encourage instances owner to add IPv6 support to their own.
2017-04-02 12:05:32 +02:00
Damien Erambert
3ed75efc31 Add fr locale for community_timeline in fr.jsx 2017-04-01 23:45:53 -07:00
Damien Erambert
cb1989cbd8 Add locale in devise.fr.yml 2017-04-01 23:38:40 -07:00
Damien Erambert
03d676faa8 Add french translation for "scopes" in doorkeeper.fr.yml 2017-04-01 23:35:11 -07:00
Damien Erambert
9e26af264e Fix small typo in doorkeeper.fr.yml 2017-04-01 23:34:37 -07:00
Damien Erambert
f9f6098e22 Complete localization of in simple_form.fr.yml 2017-04-01 23:26:26 -07:00
Damien Erambert
2c881e6717 Improve french locales on the backend (WIP) 2017-04-01 23:15:49 -07:00
jenn kaplan
1389d5d7fd Updated list of instances for gay.crime.team
Updated out-of-date info for gay.crime.team
2017-04-01 22:37:10 -07:00
Eugen Rochko
433cb198fa Fix landing page sign up form ignoring username field 2017-04-02 04:13:22 +02:00
Eugen
3ffa27e812 Merge pull request #720 from 0x70b1a5/master
improve video button visibililty (issue #713)
2017-04-02 03:15:22 +02:00
Tobias Merkle
ae43978433 improve video button visibililty 2017-04-01 21:02:30 -04:00
Eugen
0f05643f5d Merge pull request #718 from Wonderfall/master
Add social.targaryen.house to instances list
2017-04-02 01:27:02 +02:00
Wonderfall
7ccc9b8b02 Add social.targaryen.house to instances list 2017-04-01 23:53:00 +02:00
Eugen
55d275d30f Merge pull request #714 from TheKinrar/patch-1
Add mastodon.xyz to instances list
2017-04-01 23:10:39 +02:00
Eugen Rochko
a8c2e44fee Fix broken reference 2017-04-01 22:29:20 +02:00
Eugen Rochko
808017ff18 Paperclip will complain on its own if this variable is missing 2017-04-01 22:16:26 +02:00
Eugen Rochko
60ebfa182f Made modal system more generic 2017-04-01 22:11:28 +02:00
Maxime BORGES
f693ab69f3 Fix word-break in profile's note on profile page and profile component 2017-04-01 20:17:28 +02:00
TheKinrar
355168b939 Add mastodon.xyz to instances list 2017-04-01 18:00:52 +02:00
Eugen Rochko
13dfd8d109 Improve mobile tabs a little 2017-04-01 15:17:35 +02:00
Eugen Rochko
d93d6f5124 Fix reworked search 2017-03-31 22:45:56 +02:00
Eugen
7ddda65269 Merge pull request #706 from alexgleason/patch-3
Put line breaks between API methods in docs
2017-03-31 21:25:31 +02:00
Eugen Rochko
b4046c5957 Rework search 2017-03-31 21:11:09 +02:00
Alex Gleason
d835647ec0 Put line breaks between API methods in docs 2017-03-31 14:55:08 -04:00
Eugen
553e6dd07c Merge pull request #685 from yiskah/patch-2
Clarify post privacy warning
2017-03-31 16:36:29 +02:00
Eugen
1ee292b4cc Merge pull request #703 from alexgleason/patch-1
Mastodon is not an alternative, it is a solution
2017-03-31 16:36:21 +02:00
Alex Gleason
e4c10b32b3 Mastodon is not an alternative, it is a solution
This month I traveled to Cambridge, Massachusetts with my friends and attended LibrePlanet 2017. At the talk *Technology for direct actions*, Andrew Seeder pointed out that we often call free software an "alternative" to what exists. But an alternative is something that is mostly similar, and differentiated by preference rather than need. For instance, a trackball is an alternative to a mouse, but a jail is not an alternative to a bedroom. Andrew suggested that we start looking at free software as "solutions" rather than alternatives. So I propose boldly proclaiming that Mastodon is not an alternative to Twitter, but rather a solution to it.
2017-03-31 10:34:21 -04:00
shel
f20f6b25b9 Merge branch 'master' into patch-2 2017-03-31 10:34:14 -04:00
Eugen Rochko
680f9efe9c Fix web UI profile clickable area overlapping with follow button area 2017-03-31 14:23:44 +02:00
Eugen Rochko
bde5c0eaf9 Fix some views still not using counter caches 2017-03-31 14:02:07 +02:00
Eugen Rochko
5e26295e06 Fix #700 - hide spoilers on static pages 2017-03-31 13:54:36 +02:00
Eugen Rochko
188cddefe9 Add maintenance task for updating counter caches 2017-03-31 13:01:00 +02:00
Eugen Rochko
3ac4455160 :active and :focus states same as :hover for buttons 2017-03-31 12:08:51 +02:00
Eugen Rochko
bf61bc1b96 Fix drag & drop overlay flickering 2017-03-31 11:48:25 +02:00
Eugen Rochko
e8875c6046 Import feature for following/blocking lists (addresses #62, #177, #201, #454) 2017-03-30 19:42:33 +02:00
Eugen Rochko
03fb6c16ec Fix up null values on latest migration, add notes 2017-03-30 16:06:27 +02:00
Eugen Rochko
87513b31e0 Do NOT try to update the new fields from the migration. Takes too long on a live DB
Needs to be a separate task with no locking
2017-03-30 15:50:34 +02:00
Eugen Rochko
30964350b2 Merge branch 'master' into development 2017-03-30 15:07:27 +02:00
Eugen Rochko
de22c202f5 Add counter caches for a large performance increase on API requests 2017-03-30 15:06:59 +02:00
Eugen Rochko
35933167c0 Add counter caches for a large performance increase on API requests 2017-03-30 04:47:04 +02:00
Eugen Rochko
333e44c3fc Improve emojis - use SVGs where possible 2017-03-29 22:27:24 +02:00
Eugen Rochko
bb7006bda1 Hide drag & drop area on drop event regardless of success 2017-03-28 14:17:24 +02:00
Eugen Rochko
139fc994e2 Fix #408 - link @ names in bios 2017-03-28 14:16:08 +02:00
Eugen Rochko
1c6b02f936 Fix #690 - Webfinger should handle new shortform profile URLs now (nice) 2017-03-28 11:25:43 +02:00
Eugen
448a07cc3f Merge pull request #688 from yiskah/patch-3
Update amaroq name
2017-03-26 21:45:49 +02:00
shel
5181702a60 Update amaroq name 2017-03-26 15:45:16 -04:00
shel
76188d61f2 Clarify post privacy warning
I was informed that the current warning if you @ a remote server in a private post is inadequate. These are suggested changes to better inform users.
2017-03-26 12:49:14 -04:00
Eugen Rochko
c334541011 Add tooltip to privacy dropdown 2017-03-26 13:30:57 +02:00
Eugen Rochko
f864fee116 Fix up some localizations 2017-03-26 13:22:38 +02:00
Eugen Rochko
ebe53cb38c Merge branch 'leopku-feature/language_zh-cn' 2017-03-26 13:13:57 +02:00
Eugen Rochko
efbbd42216 Fix style issue 2017-03-26 13:13:41 +02:00
Eugen Rochko
dddb2eb84f Merge branch 'feature/language_zh-cn' of https://github.com/leopku/mastodon into leopku-feature/language_zh-cn 2017-03-26 13:10:04 +02:00
Eugen Rochko
6ec6fe259a Compose form layout fixes 2017-03-26 13:08:15 +02:00
Eugen Rochko
874d91126c Merge branch 'yiskah-patch-1' 2017-03-26 12:49:31 +02:00
leopku
d5d5afff9c 🌐 add simplified chinese language 2017-03-26 14:13:18 +08:00
shel
5f7c997654 Change to using variables
Thanks garg
2017-03-25 20:42:49 -04:00
shel
2b7e3d56c8 Increase color contrast
Privacy descriptions were very difficult to reason. This is the minimal level of color contrast to be WCAG AA compliant.
2017-03-25 20:02:36 -04:00
shel
c66dc0d114 Clarify privacy options
Descriptions of differences between scope are more clear
2017-03-25 19:59:38 -04:00
Eugen Rochko
1fd88e3bad Fix up visual indicators for compose form privacy 2017-03-25 20:24:30 +01:00
Eugen Rochko
b8f9de8636 Improve sign up page on small screens 2017-03-25 20:13:34 +01:00
Eugen Rochko
a9303e7062 When replying to status with content warning, prefill content warning 2017-03-25 19:30:56 +01:00
Eugen Rochko
ad1af951fb Temporary spacing fix 2017-03-25 19:27:31 +01:00
Eugen Rochko
bd1ceb1daa Privacy dropdown i18n 2017-03-25 19:22:24 +01:00
Eugen Rochko
99ca63a543 Fix public profile pagination links 2017-03-25 19:16:17 +01:00
Eugen Rochko
d8c5a83827 Redesigned compose form 2017-03-25 19:14:12 +01:00
leopku
fc9bbdfd34 🚧 chinese translation WIP 2017-03-24 19:03:14 +08:00
leopku
349c6cfa2b 🌐 add chinese language file 2017-03-24 18:53:41 +08:00
Eugen Rochko
9bf4c34919 Fix #675 - return created_at in notifications API 2017-03-24 03:55:45 +01:00
Eugen Rochko
d7c6c6dbe1 Fancier drag & drop indicator, emoji icon for emoji, upload progress (fix #295) 2017-03-24 03:55:45 +01:00
Eugen
3e2d6ea408 Merge pull request #676 from leopku/feature/s3_protocol_from_env
🔧 S3 protocol  from ENV
2017-03-24 00:13:08 +01:00
leopku
c46843c65c 🔧 S3 protocol from ENV
add support for reading S3 protocol from ENV
also add S3_HOSTNAME in .env.production.sample
2017-03-23 15:44:55 +08:00
Eugen Rochko
08faeedff7 Merge branch 'feature-omnisearch' 2017-03-22 19:56:38 +01:00
Eugen Rochko
d6ed2eb512 Prettier account and stream entry URLs 2017-03-22 19:55:14 +01:00
Eugen Rochko
33fac87e81 Shorter timeout on FetchAtomService 2017-03-22 17:41:52 +01:00
Eugen Rochko
5aa3df017b Fix full-text search query quotation, improve tag search performance with an index,
add ability to open status by URL from search (fix #53)
2017-03-22 17:36:34 +01:00
Eugen Rochko
c89ccbab09 Merge branch 'master' into feature-omnisearch 2017-03-22 16:28:17 +01:00
Eugen Rochko
22e06a4077 Upgrade status IDs to bigint 2017-03-22 15:46:58 +01:00
Eugen Rochko
7637386228 Upgrade Rails to 5.0.2 2017-03-22 15:37:03 +01:00
Eugen Rochko
88f32708c3 Web UI support for the new omnisearch 2017-03-22 04:09:28 +01:00
Eugen Rochko
acfee0945c Merge branch 'master' into feature-omnisearch
this merge is necessary,
2017-03-22 03:34:58 +01:00
Eugen Rochko
8aae42f3d8 German toot -> "tröt" 2017-03-22 03:22:44 +01:00
Eugen Rochko
1b09c3cb17 More efficient single account retrieval (0.9ms vs 50ms before) 2017-03-22 03:21:38 +01:00
Eugen Rochko
05cf086766 New API method: /api/v1/search
Returns accounts, statuses, hashtags arrays
2017-03-22 02:32:27 +01:00
Eugen Rochko
98571b0ce4 Don't list "direct" under default post privacy settings 2017-03-21 19:45:25 +01:00
Eugen Rochko
8803ca9efe Improved landing page 2017-03-21 19:18:37 +01:00
Eugen Rochko
6b1db5c2b2 Add landing strip 2017-03-20 03:36:29 +01:00
Eugen Rochko
56d998cbdb Export follow/block lists as CSV 2017-03-19 20:29:41 +01:00
Eugen Rochko
08b96f1b9f Fix wrong HTTP status codes on error pages 2017-03-19 20:03:28 +01:00
Eugen Rochko
8c7277acd4 Add link to contribution guidelines to README 2017-03-19 04:05:19 +01:00
Eugen Rochko
50f3a81f63 Added contribution guidelines 2017-03-19 03:52:21 +01:00
Eugen Rochko
2816b1bf8e Federate header images, fix open-uri http->https redirection error 2017-03-18 22:51:20 +01:00
Eugen Rochko
5cfc9c7487 Forgot to hook up API with the latest method 2017-03-17 21:02:47 +01:00
Eugen Rochko
ac406a31b0 Start page links a bit more readable 2017-03-17 20:49:34 +01:00
Eugen Rochko
ad0d82d3ce Make account search blazing fast and rank followers/followees higher in the results 2017-03-17 20:48:14 +01:00
Eugen
22f9399cc3 Merge pull request #671 from yiskah/patch-9
Update Matador name to 11t
2017-03-17 18:08:50 +01:00
shel
dd64baeba2 Update Matador name to 11t
The project was renamed 11t, the link goes to a project titled "11t" this is just an update
2017-03-17 11:49:31 -04:00
shel
38dceb3bf7 Promote Apps (#670)
"Various apps are available" is now a hyperlink
2017-03-17 16:16:39 +01:00
Eugen Rochko
017350e0ea Add method for retrieving triadic closures 2017-03-16 20:10:51 +01:00
Eugen Rochko
a2696cf542 Merge branch 'marrus-sh-mastodon-site-api' 2017-03-15 23:13:18 +01:00
Eugen Rochko
6be7bde243 Fix #525 - Add instance information API 2017-03-15 23:12:48 +01:00
Eugen Rochko
7b58c1a694 Merge branch 'mastodon-site-api' of https://github.com/marrus-sh/ardipithecus into marrus-sh-mastodon-site-api 2017-03-15 22:55:58 +01:00
Eugen Rochko
74ae158c2f Add "direct" visibility level in the backend. Web UI is not yet
adjusted to allow choosing it, yet
2017-03-15 22:55:45 +01:00
Eugen
e245115f47 Merge branch 'master' into mastodon-site-api 2017-03-15 22:55:22 +01:00
Eugen
c1124228e8 Update list of apps 2017-03-15 18:44:37 +01:00
Eugen Rochko
02349b3269 Obfuscate filenames better, double rate limits 2017-03-14 15:59:21 +01:00
Eugen Rochko
952bce3023 Replace howler.js with simple <audio />, will hopefully resolve
some weird mobile browser behaviours
2017-03-13 17:12:30 +01:00
Eugen Rochko
a5daa806f2 Fix casuality of processing remote mentions such that notifications
about them would be processed only after the entire status is processed
2017-03-13 16:34:15 +01:00
Eugen Rochko
47bf592db7 Try to detect iOS and *not* autoplay any videos
Technically the check for iOS from components is a side effect, because it's
neither in state nor props, but the user agent does not change, so I think
that's okay.
2017-03-07 09:54:57 +01:00
Eugen Rochko
1fb3e8988b Revert earlier fix due to new bug reports 2017-03-06 02:25:41 +01:00
Eugen Rochko
d6cb4bbe99 Performance improvement for profiles 2017-03-06 01:50:35 +01:00
Eugen Rochko
03a857f59a Use more widely supported MP4 format for gifv's 2017-03-06 00:30:03 +01:00
Eugen Rochko
8d93f0ca56 Increase max bitrate of converted webms, slightly optimized counter queries
(Because postgres can tell that count(*) needs no extra checks, but
counting a specific column requires them)
2017-03-05 23:43:58 +01:00
Eugen Rochko
6382ef2bc6 Add vsync -cfr option when converting gif to webm 2017-03-05 23:27:25 +01:00
Eugen Rochko
79b08c5f0a Higher quality webms? Might improve conversation of some types of gifs 2017-03-05 23:13:21 +01:00
Eugen Rochko
ebc01bf0f6 Make the paperclip filename interpolator smarter about the :original style
If an :original gets converted into another format, it would get saved as
original_filename *anyway*, so generating the extension is pointless and
yields bad results for when you change the style definition later. This way,
old gifs will still have correct URLs
2017-03-05 23:03:49 +01:00
Eugen Rochko
85fce04d1b Detect videos with no sound, handle them like gifv 2017-03-05 22:55:24 +01:00
Eugen Rochko
4fb95c91fb Fix wrongful matching of last period in extended usernames
Fix anchor tags in some wikipedia URLs being matches as a hashtag
2017-03-05 18:08:19 +01:00
Eugen Rochko
5f4e402204 Improved /api/v1/accounts/:id/statuses with new params: only_media, exclude_replies
Redirect /:username to /users/:username
Redirect /:username/:id to /users/:username/updates/:id
Updated API documentation and sponsors
2017-03-05 17:27:17 +01:00
Eugen Rochko
07b166af64 Make gifvs always use media gallery component 2017-03-05 02:04:31 +01:00
Eugen Rochko
caf5b8e975 Fix #431 - convert gif to webm during upload. Web UI treats them like it did
before. In the API, attachments now can be either image, video or gifv. Gifv
is to be treated like images in terms of behaviour, but are videos by file
type.
2017-03-04 23:02:24 +01:00
Eugen
4cbeb9a7eb Merge pull request #663 from console-cowboy/patch-1
add social.gestaltzerfall.net instance information
2017-03-04 12:17:50 +01:00
Alexander Acevedo
851b730373 add social.gestaltzerfall.net instance information
Hello fediverse!
2017-03-03 19:17:58 -05:00
Eugen Rochko
5d100293fb Rename "the whole known network" timeline to "federated timeline"
Remove note about following Gargron@mastodon.social from getting started text
2017-03-04 00:19:18 +01:00
Eugen Rochko
6b81d10030 Add digest e-mails 2017-03-04 00:00:48 +01:00
Eugen
f5457cc3d2 Merge pull request #662 from undrskr/patch-1
gnusocial.me doesn't use Mastodon anymore
2017-03-03 22:46:56 +01:00
undrskr
fac8bfc03f gnusocial.me doesn't use Mastodon anymore
The host uses postActiv now.
2017-03-03 22:38:10 +02:00
Eugen Rochko
4690236724 Make confirmation text more specific 2017-03-02 19:25:11 +01:00
Eugen Rochko
b9345b3fc6 Fix #620 - Add confirmation dialog for notifications clear 2017-03-02 19:24:12 +01:00
Eugen Rochko
4d23a85c29 Fix up storybook 2017-03-02 18:55:15 +01:00
Eugen Rochko
4c0e9f85c5 Merge branch 'KitRedgrave-add-mute-button' 2017-03-02 18:50:33 +01:00
Eugen Rochko
c64a1c25c4 Fix #231 - Muting 2017-03-02 18:49:32 +01:00
Kit Redgrave
442fdbfc53 Mute button progress so far. WIP, doesn't entirely work correctly. 2017-03-01 22:31:21 -06:00
Eugen Rochko
89fc2d7f48 Fix #372 - Emoji picker 2017-03-02 00:57:55 +01:00
Eugen Rochko
6a1b738e0b Merge branch 'marrus-sh-mastodon-paste-support' 2017-03-01 13:57:44 +01:00
Eugen Rochko
95ebfa5610 Simplify passing of prop 2017-03-01 13:57:30 +01:00
Eugen Rochko
1fa2e7cc86 Merge branch 'mastodon-paste-support' of https://github.com/marrus-sh/ardipithecus into marrus-sh-mastodon-paste-support 2017-03-01 13:54:52 +01:00
Kibigo
91c79f2445 Better smart/dumb component separation 2017-03-01 02:56:15 -08:00
Eugen
0924eee81b Merge pull request #660 from marrus-sh/mastodon-smart-back-button
#467 – Making the back button a little smarter
2017-03-01 10:50:27 +01:00
Kibigo
5f6f7aaa27 Making the back button a little smarter 2017-02-28 22:24:34 -08:00
Kibigo
e84c1dc95f Support for pasting images from clipboard 2017-02-28 18:52:46 -08:00
Eugen Rochko
5960bac11e Fix incorrect detection of local vs remote user in web UI 2017-03-01 02:00:21 +01:00
Eugen Rochko
3208979655 Fix typo 2017-03-01 01:57:48 +01:00
Eugen Rochko
fbdb3bcf1e Revert infinite scroll in timelines back to looking at ID of oldest
loaded status; do not preload submitted statuses into community/public
timelines, unless those timelines have already been loaded; do not
close streaming API connections for community/public timelines, once
they have been established (most users navigate back to them eventually)
2017-03-01 01:43:29 +01:00
Eugen Rochko
e1b00757a6 Fix #291 - Add visual indication that numbers for remote users may be inaccurate 2017-03-01 01:18:34 +01:00
Eugen Rochko
7f0d1b8cc0 If a status is within 40 statuses from the top of a home feed, do not
reinsert it when someone boosts it
2017-03-01 01:07:11 +01:00
Eugen Rochko
92569b1f0d Improved dropdowns 2017-03-01 00:53:11 +01:00
Eugen Rochko
955e9088d7 Fix #561 - Detect presence of audio in video, hide mute toggle when none 2017-02-28 23:48:41 +01:00
Eugen
70a56b92a6 Merge pull request #656 from yiskah/patch-7
Add social.diskseven.com
2017-02-28 21:48:05 +01:00
shel
b4a12c88e7 Add social.dickseven.com
New instance
2017-02-28 13:02:39 -05:00
Eugen
0327ab9616 Updated sponsors 2017-02-28 14:58:11 +01:00
Eugen Rochko
d180aaa2a7 Fix #186 - Add RTL support to the compose form textarea and statuses output 2017-02-28 01:52:31 +01:00
Eugen Rochko
809455aaae Add elephant friend to missing indicator 2017-02-28 00:43:36 +01:00
Eugen
9214b5d02e Add new illustration 2017-02-27 22:51:24 +01:00
Eugen
5a7590d94c Merge pull request #655 from marrus-sh/mastodon-frontend-character-count
Fixes #263; makes JavaScript's character counting the same as Ruby's
2017-02-27 15:51:57 +01:00
Kibigo
4d2af0d664 Character counter fix 2017-02-27 04:32:41 -08:00
Kibigo
620f70e42c Adds site metadata access to the API 2017-02-27 04:06:25 -08:00
Eugen Rochko
af5cb0f853 Only use bigger compose form/search form font on mobile layout 2017-02-27 00:46:45 +01:00
Eugen Rochko
175a9b9caa Fix #104 - Style OAuth authorized applications page
Add ability to search accounts by display name
2017-02-27 00:15:00 +01:00
Eugen Rochko
2826e6dada Fix #612 - Set font size to 16px in compose form and search form to prevent iOS
from zooming in on them
2017-02-26 23:30:18 +01:00
Eugen Rochko
a0df694c24 Merge branch 'rmhasan-fix_462' 2017-02-26 23:23:47 +01:00
Eugen Rochko
5f511324b6 Add validation of media attachments, clean up mastodon-own exception classes 2017-02-26 23:23:06 +01:00
Eugen Rochko
063432d7e3 Merge branch 'fix_462' of https://github.com/rmhasan/mastodon into rmhasan-fix_462 2017-02-26 23:09:18 +01:00
Eugen Rochko
1b8d3375c8 Merge branch 'rmhasan-fix_626' 2017-02-26 23:07:14 +01:00
Eugen Rochko
4fbdf100c4 Add <MissingIndicator /> when status or account are not found, skip alerts
for those errors
2017-02-26 23:06:27 +01:00
Eugen Rochko
4bb8ff7c8e Merge branch 'fix_626' of https://github.com/rmhasan/mastodon into rmhasan-fix_626 2017-02-26 22:44:31 +01:00
Eugen Rochko
03000fee5f Fix JS require-issue 2017-02-26 14:10:01 +01:00
Eugen Rochko
b44dd38360 Fix broken dependency on inner react utility function 2017-02-26 01:34:56 +01:00
Eugen Rochko
e2209e1104 Fix local timeline showing unread status of TWKN timeline 2017-02-26 01:27:22 +01:00
Eugen Rochko
2c50687279 Improve compose form performance, upgrade JS dependencies. LightingBox
now allows to cycle through multiple images
2017-02-26 01:23:44 +01:00
Eugen Rochko
3e9d794ea5 Add tuning documentation, add <content> tags back to most salmons,
make status pagination headers generation more lax about next page
existing
2017-02-25 03:34:37 +01:00
Eugen Rochko
9d4cad6307 Improve glow 2017-02-23 02:14:35 +01:00
Eugen Rochko
53ae431867 Skip remote media URLs that don't have a hostname 2017-02-22 19:55:14 +01:00
Eugen Rochko
3202bdd744 Fix #555 - Use a better URL parser 2017-02-22 19:35:11 +01:00
Eugen Rochko
c96fd24f48 Improve status reselect, do not display "load more" when no next link available 2017-02-22 16:30:09 +01:00
Eugen Rochko
c77a54fe0a Fix #651 - Do not reinsert original status into all followers feeds
upon un-reblogging. Check if the reblog was in the feed in the first
place. It might have been filtered on distribution.
2017-02-22 15:52:47 +01:00
Eugen Rochko
974d712fbe Improve performance of compose form 2017-02-22 15:43:07 +01:00
Eugen Rochko
5997bb47a8 Fix #38 - Unread indicator when new content appears above the fold 2017-02-21 00:10:49 +01:00
Eugen
f338cc6c94 Fix scroll to top not registering because of wrong debounce usage 2017-02-20 19:01:47 +01:00
Eugen Rochko
e8ea9669c9 Fix links and wording 2017-02-19 22:42:38 +01:00
Eugen Rochko
6b4ef92c6f Adjust naming of public timeline -> "local timeline" vs "whole known network" 2017-02-19 21:49:14 +01:00
Eugen Rochko
c50256d25c Fix infinite scrolling breaking after navigation 2017-02-19 21:37:04 +01:00
Eugen Rochko
4aa5ebe591 Split public timeline into "public timeline" which is local, and
"whole known network" which is what public timeline used to be

Only domain blocks with suspend severity will block PuSH subscriptions
Silenced accounts should not appear in conversations unless followed
2017-02-19 20:25:54 +01:00
Rakib Hasan
dfd4a42b35 added if else clause in fetchContext
So that if we get an error, then we will
only delete status if it is an 404 error
2017-02-19 10:55:22 +00:00
Rakib Hasan
9433d03705 Removed try clause from create action in status controller
Using catch statement in api_controller.rb to catch NotPermitted
Exception, and render error message
2017-02-19 08:29:56 +00:00
Rakib Hasan
87a6bed9e9 previous commit was creating the status regardless
of mix of video and images in status, just wasn't rendering
the show action. I moved the validation before the status creation
2017-02-19 08:28:33 +00:00
Rakib Hasan
6f9ecd899e revisted fix for #462
Moved validation to services/post_status_service.rb
2017-02-19 08:28:33 +00:00
Rakib Hasan
6d2301988f Fix for issue #462
Modified uploadCompose action to send media ids of attached
media when sending a request. Modified create method in MediaController
to check if when posting a video, there are no other media attached
to the status by looking at the media ids sent from the uploadCompose
action.
2017-02-19 08:28:33 +00:00
Rakib Hasan
910df0f795 Removing dispatch of fetchContextFail
No need to flash error message when status is not found.
The column will display the message "status not found"
2017-02-19 08:21:45 +00:00
Rakib Hasan
8e760d5f62 adding new react class StatusNotFound 2017-02-19 08:21:37 +00:00
Rakib Hasan
9e0dbb7337 Fixing issue #626
The status is not showing anymore after clicking on it
2017-02-19 08:21:05 +00:00
Eugen Rochko
9e99b8c068 Fix #642 - Add "empty column" text to home/notifications 2017-02-18 02:37:59 +01:00
Eugen
1d5dfda3d4 Merge pull request #643 from yiskah/patch-7
Add target="_blank" to github link under getting started
2017-02-17 21:13:47 +01:00
shel
6a6d8f60c4 Add target="_blank" to github link
This makes so clicking the link to the github opens in a new tab or windows.

I just keep getting frustrated clicking the link to github and then losing my place in my home timeline >_> that's all
2017-02-17 14:50:50 -05:00
Florian Piesche
dad204a54d Add user guide. 2017-02-17 16:14:10 +00:00
Eugen
2940deb51d Merge pull request #640 from mcat-ee/master
Added SMTP_FROM_ADDRESS to heroku deploy config
2017-02-17 13:09:21 +01:00
nicolas
ca57f17d3d French translation update 2017-02-17 10:27:00 +01:00
Tom McAtee
8a93ae4f18 Added SMTP_FROM_ADDRESS to heroku deploy config
Allows the user to set the address mastodon emails are maraked as being sent from
2017-02-17 14:45:31 +10:30
Eugen Rochko
a1e96ae94f Add foreign key to prevent reblogs of non-existent (after race conditions) statuses from happening
Fix issue with detailed status view not supporting unreblogging/unfavouriting
2017-02-17 02:33:10 +01:00
Eugen Rochko
5ddad41245 Do not display non-Status stream entries anymore 2017-02-17 02:20:52 +01:00
Eugen Rochko
dbd80465c8 Fix small styling issues 2017-02-17 02:05:15 +01:00
Eugen Rochko
f2931af61e Quick fix for avatars with the same URL not being perceived as updated and in need of re-download 2017-02-17 01:50:01 +01:00
Eugen Rochko
8b16f81882 Close dropdown when clicking items 2017-02-17 01:44:06 +01:00
Eugen Rochko
0cbf3a146f Speed up/optimize loading of statuses, context and cards by not re-fetching what is already loaded 2017-02-17 01:34:37 +01:00
Eugen Rochko
8132cf8153 Add GET /api/v1/accounts/:id/statuses/media that returns only statuses with media attachments
Make replies default to privacy settings of the status being replied to
2017-02-17 01:30:24 +01:00
Eugen Rochko
d0f087db2d Add UI to view report details, remove reported statuses, quick links to resolve/silence/suspend from report 2017-02-17 00:42:52 +01:00
Eugen Rochko
9c88d1b99e Speed up capistrano deployments 2017-02-16 02:34:21 +01:00
Eugen Rochko
24ba7c9762 Adding index overview for reports in admin UI 2017-02-16 02:28:10 +01:00
Eugen Rochko
f722aa8c75 Fix Salmon issue 2017-02-16 01:59:34 +01:00
Eugen Rochko
ff49649130 Expose Link headers via CORS 2017-02-15 23:26:17 +01:00
Eugen Rochko
5426f06ac2 Fix admin UI for accounts somewhat 2017-02-15 00:22:58 +01:00
Eugen Rochko
0a2427f79b Fix #634 - Only include innermost-level statuses in report UI
Fix #635 - Include the initially-reported status in report UI even if it's not
among the user's most recent statuses
2017-02-15 00:01:52 +01:00
Eugen
e571a01853 Fix outdated info 2017-02-14 21:33:31 +01:00
Eugen Rochko
3b81baaaaf Adding POST /api/v1/reports API, and a UI for submitting reports 2017-02-14 20:59:26 +01:00
Eugen Rochko
40a4053732 Disable PuSH for blocked domains 2017-02-14 04:01:37 +01:00
Eugen
94b61bdcf6 Merge pull request #632 from mcat-ee/patch-1
Fixed the URL
2017-02-14 03:20:44 +01:00
Tom McAtee
68dba5f2eb Fixed the URL
Heroku uses the referrer URL to point at the repo that should be deployed; from this page that includes part of a path that breaks the deployment (specifically /blob/master/docs/Running-Mastodon/Heroku-guide.md).

I've replaced the vanilla address with one that includes a specific reference to the root of the repo
2017-02-14 12:46:18 +10:30
Eugen
f3ce9322b0 Fix #631 - Docker-deployed streaming API wasn't launched with the right environment 2017-02-13 23:10:11 +01:00
Eugen Rochko
63886bdc59 Fix #587 - Display TOTP secret next to QR code 2017-02-13 20:56:03 +01:00
Eugen
bae7cf8cce Merge pull request #630 from yiskah/patch-6
Add memetastic.space to instance list
2017-02-13 20:42:40 +01:00
Eugen Rochko
fae1799646 Add rake task for making someone into an admin 2017-02-13 20:42:02 +01:00
Eugen Rochko
138d21aea8 Update service timeout setting from 15s to 90s 2017-02-13 20:42:02 +01:00
shel
7a99699e2d Add memetastic.space
It's uh, it's an instance now.
2017-02-13 14:37:58 -05:00
Eugen
ed9ae70487 Merge pull request #629 from halcy/patch-1
Add https://icosahedron.website/
2017-02-13 18:43:58 +01:00
Eugen Rochko
77df3785f1 Merge branch 'yiskah-patch-5' 2017-02-13 18:38:31 +01:00
Eugen Rochko
ec521e6bfc Add remote mentions warning when composing a private status 2017-02-13 18:38:00 +01:00
Lorenz Diener
73210a93df Add https://icosahedron.website/ 2017-02-13 17:25:21 +01:00
Eugen Rochko
2ab7bd13e2 Merge branch 'patch-5' of https://github.com/yiskah/mastodon into yiskah-patch-5 2017-02-13 17:22:40 +01:00
Eugen Rochko
677e95031e Clean up collapsible components 2017-02-13 17:20:18 +01:00
Eugen Rochko
2d8a4c4390 Add "next" link to Atom feeds 2017-02-13 15:38:45 +01:00
Eugen
63b3cb37db Merge pull request #627 from halcy/style-fixes
Activity stream background should use $color5
2017-02-13 15:16:15 +01:00
Your Name
e4a7e8222e Activity stream background should use $color5 2017-02-13 12:31:52 +00:00
Eugen Rochko
8e08ae5bb9 Add IDs to salmon slaps even if they are transient. Add title, too, mostly
in case of debugging incoming salmons. Add <thr:in-reply-to /> to favourite
salmons because it seems to be required by GS.
2017-02-13 13:30:33 +01:00
Eugen Rochko
df63461ff0 Add handler for salmons of undoing favourites 2017-02-12 19:50:18 +01:00
Eugen Rochko
720ff55262 Adding more unit tests. Fixing Salmon slaps XML 2017-02-12 17:30:15 +01:00
Eugen Rochko
446267d1bf Deduplicate delete salmons (send only one per mentioned-account domain) 2017-02-12 17:30:15 +01:00
Eugen
78b3b52663 Merge pull request #625 from ineffyble/patch-1
Remove toot.zone
2017-02-12 13:27:40 +01:00
Effy Elden
0eee63f074 Remove toot.zone
toot.zone is being discontinued
2017-02-12 14:51:53 +11:00
Eugen Rochko
db814543c0 Take out private toot distribution out of PuSH again 2017-02-12 01:31:04 +01:00
Eugen Rochko
0518492158 Stop trying to shoehorn all Salmon updates into the poor database-connected
StreamEntry model. Simply render Salmon slaps as they are needed
2017-02-12 01:19:14 +01:00
shel
968fae2603 Add privacy disclaimer
I haven't had a chance to test this, because I don't currently have a fully functioning computer I can set up a dev-environment on, but this *should* make it so when a user toggles a post to be private, a disclaimer appears clarifying what "private" really means.
2017-02-11 17:42:19 -05:00
shel
3893ff1604 Add privacy disclaimer
Add variable for text of privacy disclaimer
2017-02-11 16:47:01 -05:00
Eugen Rochko
94d2182717 Resolve issue with rendering authorize/reject Salmon slaps 2017-02-11 19:42:05 +01:00
Eugen Rochko
31c633e528 Fix Status.permitted_for scope query 2017-02-11 19:16:10 +01:00
Eugen Rochko
50660d54e8 Fix semantics of follow requests another slaps 2017-02-11 17:13:11 +01:00
Eugen Rochko
0b95eb3612 Improve docs 2017-02-11 16:21:07 +01:00
Eugen Rochko
e6408b2e7a Merge branch 'feature-privacy-federation' into development 2017-02-11 15:43:09 +01:00
Eugen Rochko
446aad4ce2 Make PuSH only distribute to subscriptions that match follower domains
Allow PuSH to distribute private toots with that condition
2017-02-11 15:41:39 +01:00
Eugen Rochko
dc851c922e Mentions in private statuses allow mentioned people to see them 2017-02-11 15:10:22 +01:00
Eugen Rochko
00b5731ecb After FollowService, re-fetch remote account asynchronously, do nothing
if account lock info was up to date, otherwise re-do the FollowService
with now updated information
2017-02-11 14:25:01 +01:00
Eugen Rochko
e610555e10 Fix processing of incoming authorizations/rejections 2017-02-11 13:55:07 +01:00
Eugen Rochko
514fdfa268 Don't PuSH-resubscribe if already subscribed 2017-02-11 13:48:28 +01:00
Eugen Rochko
149887a0ff Make follow requests federate 2017-02-11 02:58:00 +01:00
Eugen Rochko
d551e43a9b Improve public tag page 2017-02-10 23:21:01 +01:00
Eugen Rochko
8cca6bc58c Fix spoiler link color in muted context 2017-02-10 23:01:02 +01:00
Eugen Rochko
5c4c046132 Finish up moving colors from JSX to Sass (#584, #348) 2017-02-10 22:58:29 +01:00
Eugen Rochko
d2619e0b53 Site title is now a setting 2017-02-10 22:37:11 +01:00
Eugen
02cbfcfe2c Merge pull request #619 from yiskah/patch-4
Add gnusocial.me to instance list
2017-02-10 21:13:01 +01:00
shel
894a0515d9 Add gnusocial.me to instance list
I'm never going to get tired of saying "gnusocial.me is a mastodon instance"
2017-02-10 15:07:44 -05:00
Eugen
b7a92867de Merge pull request #618 from yiskah/patch-3
Small Changes for Promotion of Other Instances
2017-02-10 19:15:21 +01:00
Eugen
874fffb7dc Merge branch 'master' into patch-3 2017-02-10 19:15:13 +01:00
Eugen
73e388e0d8 Merge pull request #617 from halcy/style-changes
Style changes
2017-02-10 19:10:15 +01:00
shel
2d6f603c2b Add link to other instances text
Text for link to other instances
2017-02-10 13:08:47 -05:00
shel
3f1f3d0827 Add link to other instances list
This should help federation
2017-02-10 13:06:38 -05:00
Eugen
1f1acd98f1 Merge pull request #615 from nevillepark/patch-1
Added awoo.space
2017-02-10 18:45:19 +01:00
shel
a0e6c80b60 Clean up markdown
There were some markdown processing errors which I've cleaned up
2017-02-10 12:14:49 -05:00
shel
63a18efb27 Change List of Instances to Table
Change list of known instances to a table with additional information per instance. Add awoo.space and gay.crime.team
2017-02-10 11:37:35 -05:00
Lorenz Diener
cbb962fd77 Move more colours to scss 2017-02-10 16:30:06 +00:00
Lorenz Diener
c9f42a7b85 Move more inline colours to scss 2017-02-10 15:35:19 +00:00
Neville Park
e9fa557ead Added awoo.space 2017-02-09 17:57:56 -05:00
Eugen Rochko
0afed995ce Fix the fix 2017-02-09 21:22:49 +01:00
Eugen Rochko
6331ed16e5 Fix #614 - extra reply-boolean on statuses to account for cases when replied-to
status is not in the system at time of distribution; fix #607 - reset privacy
settings to defaults when cancelling replies
2017-02-09 20:25:39 +01:00
Eugen Rochko
c424df5192 Progress on moving color styles to Sass 2017-02-09 01:20:09 +01:00
Eugen
942a2e7d68 Fix reblogs not having content warnings forwarded 2017-02-08 18:05:46 +01:00
Eugen
d96e031dfc Fix #611 - Layout setting in registrations controller 2017-02-08 03:04:29 +01:00
Eugen Rochko
aa1213e089 Add postgres setup instructions to docs 2017-02-07 23:57:30 +01:00
Isabelle Knott
1d273e4430 Change blocked users icon to fa-ban in getting started view (#608) 2017-02-07 23:48:50 +01:00
Eugen Rochko
02e91a96dd Make streaming API use one pattern-matching redis pubsub connection
Refresh timelines when streaming API reconnects in the UI
2017-02-07 14:39:08 +01:00
Eugen
9d5fb49cd8 Merge pull request #603 from evanminto/activitypub-account
Expose ActivityStreams 2.0 representation of accounts
2017-02-07 02:08:40 +01:00
Evan Minto
28cbb6dc21 Add AS2 format to RABL files 2017-02-06 17:00:55 -08:00
Eugen Rochko
8a081ce588 Fix wrong prediction of whether toot will apear on public timeline in UI 2017-02-07 00:40:35 +01:00
Eugen Rochko
714e41d472 Fix preferences save 2017-02-07 00:23:38 +01:00
Eugen Rochko
ac035108aa Add "clear notifications" button, exclude posts from people who have blocked *you* from public/hashtag timelines 2017-02-07 00:06:40 +01:00
Eugen Rochko
c8252759df Add streaming API channels for local-only statuses 2017-02-06 23:46:14 +01:00
Eugen Rochko
347a153b3d Add API modifiers to limit returned toots from public/hashtag timelines
to only those from local users; Add link to "extended information" to
getting started in the UI; Add defaults for posting privacy; Change
how publish button looks depending on posting privacy chosen
2017-02-06 23:16:20 +01:00
Evan Minto
53234e5947 Add trailing newline 2017-02-06 12:15:47 -08:00
Evan Minto
da7f24c238 Add test 2017-02-06 12:14:02 -08:00
Evan Minto
3fa5d05997 Simplify RABL 2017-02-06 11:39:08 -08:00
Evan Minto
94e213c6c1 Reuse existing controller and route 2017-02-06 01:19:26 -08:00
Eugen Rochko
4d2be9f432 Add unique request IDs to streaming API to improve logs 2017-02-05 23:37:25 +01:00
Eugen Rochko
0af3401553 Don't allow people to follow people they blocked without unblocking first 2017-02-05 21:04:22 +01:00
Evan Minto
8bd8ea7c04 Remove unnecessary leftover code 2017-02-04 14:49:24 -08:00
Evan Minto
e2fbf8bc74 Add an account endpoint for ActivityPub and link to it on HTML profile pages 2017-02-04 14:46:23 -08:00
2539 changed files with 24720 additions and 6456 deletions

2
.buildpacks Normal file
View File

@@ -0,0 +1,2 @@
https://github.com/Scalingo/nodejs-buildpack
https://github.com/Scalingo/ruby-buildpack

View File

@@ -1,10 +1,6 @@
engines:
duplication:
enabled: true
config:
languages:
- ruby
- javascript
enabled: false
rubocop:
enabled: true
eslint:

View File

@@ -5,3 +5,7 @@ public/assets
node_modules
storybook
neo4j
vendor/bundle
.DS_Store
*.swp
*~

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2

View File

@@ -1,6 +1,7 @@
# Service dependencies
REDIS_HOST=redis
REDIS_PORT=6379
# REDIS_DB=0
DB_HOST=db
DB_USER=postgres
DB_NAME=postgres
@@ -11,6 +12,10 @@ DB_PORT=5432
LOCAL_DOMAIN=example.com
LOCAL_HTTPS=true
# Use this only if you need to run mastodon on a different domain than the one used for federation.
# Do not use this unless you know exactly what you are doing.
# WEB_DOMAIN=mastodon.example.com
# Application secrets
# Generate each with the `rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose)
PAPERCLIP_SECRET=
@@ -22,13 +27,28 @@ OTP_SECRET=
# SINGLE_USER_MODE=true
# Prevent registrations with following e-mail domains
# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
# Only allow registrations with the following e-mail domains
# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc
# Optionally change default language
# DEFAULT_LOCALE=de
# E-mail configuration
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
SMTP_SERVER=smtp.mailgun.org
SMTP_PORT=587
SMTP_LOGIN=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=notifications@example.com
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
#SMTP_AUTH_METHOD=plain
#SMTP_OPENSSL_VERIFY_MODE=peer
#SMTP_ENABLE_STARTTLS_AUTO=true
# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
# PAPERCLIP_ROOT_URL=/system
# Optional asset host for multi-server setups
# CDN_HOST=assets.example.com
@@ -39,9 +59,25 @@ SMTP_FROM_ADDRESS=notifications@example.com
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# S3_REGION=
# S3_PROTOCOL=http
# S3_HOSTNAME=192.168.1.123:9000
# S3 (Minio Config (optional) Please check Minio instance for details)
# S3_ENABLED=true
# S3_BUCKET=
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# S3_REGION=
# S3_PROTOCOL=https
# S3_HOSTNAME=
# S3_ENDPOINT=
# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
# S3_CLOUDFRONT_HOST=
# Streaming API integration
# STREAMING_API_BASE_URL=
# Advanced settings
# If you need to use pgBouncer, you need to disable prepared statements:
# PREPARED_STATEMENTS=false

View File

@@ -1,3 +1,4 @@
# Federation
LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true
OTP_SECRET=100c7faeef00caa29242f6b04156742bf76065771fd4117990c4282b8748ff3d99f8fdae97c982ab5bd2e6756a159121377cce4421f4a8ecd2d67bd7749a3fb4

30
.eslintignore Normal file
View File

@@ -0,0 +1,30 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
/log/*
!/log/.keep
/tmp
coverage
public/system
public/assets
.env
.env.production
node_modules/
neo4j/
# Ignore Vagrant files
.vagrant/
# Ignore Capistrano customizations
config/deploy/*

View File

@@ -8,7 +8,8 @@
"parser": "babel-eslint",
"plugins": [
"react"
"react",
"jsx-a11y"
],
"parserOptions": {
@@ -43,9 +44,36 @@
"no-mixed-spaces-and-tabs": 1,
"no-nested-ternary": 1,
"no-trailing-spaces": 1,
"react/wrap-multilines": 2,
"react/jsx-wrap-multilines": 2,
"react/self-closing-comp": 2,
"react/prop-types": 2,
"react/no-multi-comp": 0
"react/no-multi-comp": 0,
"jsx-a11y/accessible-emoji": 1,
"jsx-a11y/anchor-has-content": 1,
"jsx-a11y/aria-activedescendant-has-tabindex": 1,
"jsx-a11y/aria-props": 1,
"jsx-a11y/aria-proptypes": 1,
"jsx-a11y/aria-role": 1,
"jsx-a11y/aria-unsupported-elements": 1,
"jsx-a11y/heading-has-content": 1,
"jsx-a11y/href-no-hash": 1,
"jsx-a11y/html-has-lang": 1,
"jsx-a11y/iframe-has-title": 1,
"jsx-a11y/img-has-alt": 1,
"jsx-a11y/img-redundant-alt": 1,
"jsx-a11y/label-has-for": 1,
"jsx-a11y/mouse-events-have-key-events": 1,
"jsx-a11y/no-access-key": 1,
"jsx-a11y/no-distracting-elements": 1,
"jsx-a11y/no-onchange": 1,
"jsx-a11y/no-redundant-roles": 1,
"jsx-a11y/onclick-has-focus": 1,
"jsx-a11y/onclick-has-role": 1,
"jsx-a11y/role-has-required-aria-props": 1,
"jsx-a11y/role-supports-aria-props": 1,
"jsx-a11y/scope": 1,
"jsx-a11y/tabindex-no-positive": 1
}
}

14
.gitignore vendored
View File

@@ -28,3 +28,17 @@ neo4j/
# Ignore Capistrano customizations
config/deploy/*
# Ignore IDE files
.vscode/
# Ignore postgres + redis volume optionally created by docker-compose
postgres
redis
# Ignore Apple files
.DS_Store
# Ignore vim files
*~
*.swp

2
.nvmrc
View File

@@ -1 +1 @@
6.7.0
6

View File

@@ -1 +1 @@
2.3.1
2.4.1

5
.slugignore Normal file
View File

@@ -0,0 +1,5 @@
node_modules/
.cache/
docs/
spec/
storybook/

View File

@@ -5,8 +5,6 @@ notifications:
email: false
env:
matrix:
- TRAVIS_NODE_VERSION="4"
global:
- LOCAL_DOMAIN=cb6e6126.ngrok.io
- LOCAL_HTTPS=true
@@ -16,7 +14,8 @@ addons:
postgresql: 9.4
rvm:
- 2.3.1
- 2.3.4
- 2.4.1
services:
- redis-server
@@ -28,8 +27,7 @@ before_install:
- sudo apt-get -qq update
- sudo apt-get -qq install g++-4.8
install:
- nvm install $TRAVIS_NODE_VERSION
- npm install -g npm@3
- nvm install
- npm install -g yarn
- bundle install
- yarn install
@@ -40,3 +38,4 @@ before_script:
script:
- bundle exec rspec
- npm test
- i18n-tasks unused

44
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,44 @@
CONTRIBUTING
============
There are three ways in which you can contribute to this repository:
1. By improving the documentation
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).
Below are the guidelines for working on pull requests:
## General
- 2 spaces indentation
## Documentation
- No spelling mistakes
- No orthographic mistakes
- No Markdown syntax errors
## 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.
* 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
* To the extent that it is possible, verify your changes. In the best case, by adding new tests to the test suite. At the very least, by running the server or console and checking it manually
* If you are introducing new strings to the user interface, they must be using localization methods
If your code has syntax errors that won't let it run, it's a good sign that the pull request isn't ready for submission yet.
## Front-end application
It is expected that you have a working development environment set up (see back-end application section). This project includes an ESLint configuration file, with which you can lint your changes.
* Avoid grave ESLint violations
* Verify that your changes work
* 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.

View File

@@ -8,6 +8,7 @@ require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/yarn'
require 'capistrano/rails/assets'
require 'capistrano/faster_assets'
require 'capistrano/rails/migrations'
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

View File

@@ -1,23 +1,41 @@
FROM ruby:2.3.1
FROM ruby:2.4.1-alpine
ENV RAILS_ENV=production
LABEL maintainer="https://github.com/tootsuite/mastodon" \
description="A GNU Social-compatible microblogging server"
RUN echo 'deb http://httpredir.debian.org/debian jessie-backports main contrib non-free' >> /etc/apt/sources.list
RUN curl -sL https://deb.nodesource.com/setup_4.x | bash -
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev libxml2-dev libxslt1-dev nodejs ffmpeg && rm -rf /var/lib/apt/lists/*
RUN npm install -g npm@3 && npm install -g yarn
RUN mkdir /mastodon
ENV RAILS_ENV=production \
NODE_ENV=production
EXPOSE 3000 4000
WORKDIR /mastodon
ADD Gemfile /mastodon/Gemfile
ADD Gemfile.lock /mastodon/Gemfile.lock
RUN bundle install --deployment --without test development
COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
ADD package.json /mastodon/package.json
ADD yarn.lock /mastodon/yarn.lock
RUN yarn
RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories \
&& BUILD_DEPS=" \
postgresql-dev \
libxml2-dev \
libxslt-dev \
build-base" \
&& apk -U upgrade && apk add \
$BUILD_DEPS \
nodejs@edge \
nodejs-npm@edge \
libpq \
libxml2 \
libxslt \
ffmpeg \
file \
imagemagick@edge \
&& 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 \
&& apk del $BUILD_DEPS \
&& rm -rf /tmp/* /var/cache/apk/*
ADD . /mastodon
COPY . /mastodon
VOLUME ["/mastodon/public/system", "/mastodon/public/assets"]
VOLUME /mastodon/public/system /mastodon/public/assets

61
Gemfile
View File

@@ -1,15 +1,14 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby '2.3.1'
ruby '>= 2.3.0', '< 2.5.0'
gem 'rails', '~> 5.0.1.0'
gem 'pkg-config'
gem 'rails', '~> 5.0.2'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.1.0'
gem 'jquery-rails'
gem 'jbuilder', '~> 2.0'
gem 'sdoc', '~> 0.4.0', group: :doc
gem 'puma'
gem 'hamlit-rails'
@@ -23,33 +22,41 @@ gem 'paperclip', '~> 5.1'
gem 'paperclip-av-transcoder'
gem 'aws-sdk', '>= 2.0'
gem 'http'
gem 'httplog'
gem 'addressable'
gem 'nokogiri'
gem 'link_header'
gem 'ostatus2'
gem 'goldfinger'
gem 'devise'
gem 'devise-two-factor'
gem 'doorkeeper'
gem 'rabl'
gem 'rqrcode'
gem 'oj'
gem 'hiredis'
gem 'redis', '~>3.2'
gem 'fast_blank'
gem 'goldfinger'
gem 'hiredis'
gem 'htmlentities'
gem 'simple_form'
gem 'will_paginate'
gem 'http'
gem 'http_accept_language'
gem 'httplog'
gem 'kaminari'
gem 'link_header'
gem 'nokogiri'
gem 'oj'
gem 'ostatus2', '~> 1.1'
gem 'ox'
gem 'rabl'
gem 'rack-attack'
gem 'rack-cors', require: 'rack/cors'
gem 'sidekiq'
gem 'rack-timeout'
gem 'rails-i18n'
gem 'rails-settings-cached'
gem 'pg_search'
gem 'simple-navigation'
gem 'statsd-instrument'
gem 'redis', '~>3.2', require: ['redis', 'redis/connection/hiredis']
gem 'rqrcode'
gem 'ruby-oembed', require: 'oembed'
gem 'sidekiq'
gem 'sidekiq-unique-jobs'
gem 'simple-navigation'
gem 'simple_form'
gem 'sprockets-rails', :require => 'sprockets/railtie'
gem 'statsd-instrument'
gem 'twitter-text'
gem 'tzinfo-data'
gem 'whatlanguage'
gem 'react-rails'
gem 'browserify-rails'
@@ -64,9 +71,13 @@ group :development, :test do
end
group :test do
gem 'capybara'
gem 'faker'
gem 'microformats2'
gem 'rails-controller-testing'
gem 'rspec-sidekiq'
gem 'simplecov', require: false
gem 'webmock'
gem 'rspec-sidekiq'
end
group :development do
@@ -78,15 +89,15 @@ group :development do
gem 'bullet'
gem 'active_record_query_trace'
gem 'capistrano'
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 'rails_12factor'
gem 'redis-rails'
gem 'lograge'
gem 'rack-timeout'
end

View File

@@ -1,63 +1,63 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (5.0.1)
actionpack (= 5.0.1)
nio4r (~> 1.2)
actioncable (5.0.2)
actionpack (= 5.0.2)
nio4r (>= 1.2, < 3.0)
websocket-driver (~> 0.6.1)
actionmailer (5.0.1)
actionpack (= 5.0.1)
actionview (= 5.0.1)
activejob (= 5.0.1)
actionmailer (5.0.2)
actionpack (= 5.0.2)
actionview (= 5.0.2)
activejob (= 5.0.2)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.0.1)
actionview (= 5.0.1)
activesupport (= 5.0.1)
actionpack (5.0.2)
actionview (= 5.0.2)
activesupport (= 5.0.2)
rack (~> 2.0)
rack-test (~> 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.0.1)
activesupport (= 5.0.1)
actionview (5.0.2)
activesupport (= 5.0.2)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
active_record_query_trace (1.5.3)
activejob (5.0.1)
activesupport (= 5.0.1)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_record_query_trace (1.5.4)
activejob (5.0.2)
activesupport (= 5.0.2)
globalid (>= 0.3.6)
activemodel (5.0.1)
activesupport (= 5.0.1)
activerecord (5.0.1)
activemodel (= 5.0.1)
activesupport (= 5.0.1)
activemodel (5.0.2)
activesupport (= 5.0.2)
activerecord (5.0.2)
activemodel (= 5.0.2)
activesupport (= 5.0.2)
arel (~> 7.0)
activesupport (5.0.1)
activesupport (5.0.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.0)
addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
airbrussh (1.1.2)
airbrussh (1.2.0)
sshkit (>= 1.6.1, != 1.7.0)
arel (7.1.4)
ast (2.3.0)
attr_encrypted (3.0.3)
encryptor (~> 3.0.0)
autoprefixer-rails (6.5.0.2)
autoprefixer-rails (6.7.7.1)
execjs
av (0.9.0)
cocaine (~> 0.5.3)
aws-sdk (2.6.28)
aws-sdk-resources (= 2.6.28)
aws-sdk-core (2.6.28)
aws-sdk (2.9.6)
aws-sdk-resources (= 2.9.6)
aws-sdk-core (2.9.6)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0)
aws-sdk-resources (2.6.28)
aws-sdk-core (= 2.6.28)
aws-sdk-resources (2.9.6)
aws-sdk-core (= 2.9.6)
aws-sigv4 (1.0.0)
babel-source (5.8.35)
babel-transpiler (0.7.0)
@@ -73,24 +73,25 @@ GEM
rack (>= 0.9.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
browserify-rails (3.1.0)
browserify-rails (4.1.0)
addressable (>= 2.4.0)
railties (>= 4.0.0, < 5.1)
sprockets (>= 3.5.2)
sprockets (>= 3.6.0)
builder (3.2.3)
bullet (5.3.0)
bullet (5.5.1)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.10.0)
capistrano (3.7.2)
capistrano (3.8.0)
airbrussh (>= 1.0.0)
capistrano-harrow
i18n
rake (>= 10.0.0)
sshkit (>= 1.9.0)
capistrano-bundler (1.2.0)
capistrano (~> 3.1)
sshkit (~> 1.2)
capistrano-harrow (0.5.3)
capistrano-rails (1.2.2)
capistrano-faster-assets (1.0.2)
capistrano (>= 3.1)
capistrano-rails (1.2.3)
capistrano (~> 3.1)
capistrano-bundler (~> 1.1)
capistrano-rbenv (2.1.0)
@@ -98,25 +99,25 @@ GEM
sshkit (~> 1.3)
capistrano-yarn (2.0.2)
capistrano (~> 3.0)
capybara (2.13.0)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
chunky_png (1.3.8)
climate_control (0.1.0)
cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0)
coderay (1.1.1)
coffee-rails (4.1.1)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.1.x)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.10.0)
colorize (0.8.1)
concurrent-ruby (1.0.4)
concurrent-ruby (1.0.5)
connection_pool (2.2.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
debug_inspector (0.0.2)
devise (4.2.0)
devise (4.2.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.1)
@@ -128,16 +129,16 @@ GEM
devise (~> 4.0)
railties
rotp (~> 2.0)
diff-lcs (1.2.5)
diff-lcs (1.3)
docile (1.1.5)
domain_name (0.5.20161129)
domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.0)
doorkeeper (4.2.5)
railties (>= 4.2)
dotenv (2.1.1)
dotenv-rails (2.1.1)
dotenv (= 2.1.1)
railties (>= 4.0, < 5.1)
dotenv (2.2.0)
dotenv-rails (2.2.0)
dotenv (= 2.2.0)
railties (>= 3.2, < 5.1)
easy_translate (0.5.0)
json
thread
@@ -145,12 +146,14 @@ GEM
encryptor (3.0.0)
erubis (2.7.0)
execjs (2.7.0)
fabrication (2.15.2)
fabrication (2.16.1)
faker (1.7.3)
i18n (~> 0.5)
fast_blank (1.0.0)
font-awesome-rails (4.6.3.1)
font-awesome-rails (4.7.0.1)
railties (>= 3.2, < 5.1)
fuubar (2.1.1)
rspec (~> 3.0)
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
globalid (0.3.7)
activesupport (>= 4.1.0)
@@ -158,20 +161,20 @@ GEM
addressable (~> 2.4)
http (~> 2.0)
nokogiri (~> 1.6)
hamlit (2.7.2)
temple (~> 0.7.6)
hamlit (2.8.1)
temple (>= 0.8.0)
thor
tilt
hamlit-rails (0.1.0)
hamlit-rails (0.2.0)
actionpack (>= 4.0.1)
activesupport (>= 4.0.1)
hamlit (>= 1.2.0)
railties (>= 4.0.1)
hashdiff (0.3.0)
hashdiff (0.3.2)
highline (1.7.8)
hiredis (0.6.1)
htmlentities (4.3.4)
http (2.1.0)
http (2.2.1)
addressable (~> 2.3)
http-cookie (~> 1.0)
http-form_data (~> 1.0.1)
@@ -179,11 +182,12 @@ GEM
http-cookie (1.0.3)
domain_name (~> 0.5)
http-form_data (1.0.1)
http_accept_language (2.1.0)
http_parser.rb (0.6.0)
httplog (0.3.2)
httplog (0.99.2)
colorize
i18n (0.7.0)
i18n-tasks (0.9.6)
i18n (0.8.1)
i18n-tasks (0.9.13)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
easy_translate (>= 0.5.0)
@@ -191,22 +195,31 @@ GEM
highline (>= 1.7.3)
i18n
parser (>= 2.2.3.0)
term-ansicolor (>= 1.3.2)
rainbow (~> 2.2)
terminal-table (>= 1.5.1)
jbuilder (2.6.0)
activesupport (>= 3.0.0, < 5.1)
multi_json (~> 1.2)
jmespath (1.3.1)
jquery-rails (4.1.1)
jquery-rails (4.3.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.3)
json (2.0.3)
kaminari (1.0.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.0.1)
kaminari-activerecord (= 1.0.1)
kaminari-core (= 1.0.1)
kaminari-actionview (1.0.1)
actionview
kaminari-core (= 1.0.1)
kaminari-activerecord (1.0.1)
activerecord
kaminari-core (= 1.0.1)
kaminari-core (1.0.1)
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.4.1)
launchy (~> 2.2)
letter_opener_web (1.3.0)
letter_opener_web (1.3.1)
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
@@ -220,25 +233,31 @@ GEM
mail (2.6.4)
mime-types (>= 1.16, < 4)
method_source (0.8.2)
microformats2 (2.1.0)
activesupport
json
nokogiri
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mimemagic (0.3.2)
mini_portile2 (2.1.0)
minitest (5.10.1)
multi_json (1.12.1)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (4.0.1)
nio4r (1.2.1)
nokogiri (1.7.0.1)
net-ssh (4.1.0)
nio4r (2.0.0)
nokogiri (1.7.1)
mini_portile2 (~> 2.1.0)
oj (2.17.3)
oj (2.18.5)
openssl (2.0.3)
orm_adapter (0.5.0)
ostatus2 (1.0.2)
ostatus2 (1.1.0)
addressable (~> 2.4)
http (~> 2.0)
nokogiri (~> 1.6)
openssl (~> 2.0)
ox (2.4.11)
paperclip (5.1.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
@@ -248,52 +267,56 @@ GEM
paperclip-av-transcoder (0.6.4)
av (~> 0.9.0)
paperclip (>= 2.5.2)
parser (2.3.1.2)
parser (2.4.0.0)
ast (~> 2.2)
pg (0.18.4)
pg_search (1.0.6)
activerecord (>= 3.1)
activesupport (>= 3.1)
arel
pghero (1.6.2)
pg (0.20.0)
pghero (1.6.4)
activerecord
pkg-config (1.1.7)
powerpack (0.1.1)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry-rails (0.3.4)
pry (>= 0.9.10)
public_suffix (2.0.4)
puma (3.6.0)
pry-rails (0.3.6)
pry (>= 0.10.4)
public_suffix (2.0.5)
puma (3.8.2)
rabl (0.13.1)
activesupport (>= 2.3.14)
rack (2.0.1)
rack-attack (5.0.1)
rack
rack-cors (0.4.0)
rack-cors (0.4.1)
rack-protection (1.5.3)
rack
rack-test (0.6.3)
rack (>= 1.0)
rack-timeout (0.4.2)
rails (5.0.1)
actioncable (= 5.0.1)
actionmailer (= 5.0.1)
actionpack (= 5.0.1)
actionview (= 5.0.1)
activejob (= 5.0.1)
activemodel (= 5.0.1)
activerecord (= 5.0.1)
activesupport (= 5.0.1)
rails (5.0.2)
actioncable (= 5.0.2)
actionmailer (= 5.0.2)
actionpack (= 5.0.2)
actionview (= 5.0.2)
activejob (= 5.0.2)
activemodel (= 5.0.2)
activerecord (= 5.0.2)
activesupport (= 5.0.2)
bundler (>= 1.3.0, < 2.0)
railties (= 5.0.1)
railties (= 5.0.2)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.1)
actionpack (~> 5.x)
actionview (~> 5.x)
activesupport (~> 5.x)
rails-dom-testing (2.0.2)
activesupport (>= 4.2.0, < 6.0)
nokogiri (~> 1.6)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
rails-i18n (5.0.3)
i18n (~> 0.7)
railties (~> 5.0)
rails-settings-cached (0.6.5)
rails (>= 4.2.0)
rails_12factor (0.0.3)
@@ -301,50 +324,43 @@ GEM
rails_stdout_logging
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (5.0.1)
actionpack (= 5.0.1)
activesupport (= 5.0.1)
railties (5.0.2)
actionpack (= 5.0.2)
activesupport (= 5.0.2)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
rainbow (2.2.1)
rake (12.0.0)
rdoc (4.2.2)
json (~> 1.4)
react-rails (1.8.2)
react-rails (1.11.0)
babel-transpiler (>= 0.7.0)
coffee-script-source (~> 1.8)
connection_pool
execjs
railties (>= 3.2)
tilt
redis (3.3.2)
redis-actionpack (5.0.0)
actionpack (>= 4.0.0, < 6)
redis-rack (~> 2.0.0.pre)
redis-store (~> 1.2.0.pre)
redis-activesupport (5.0.1)
redis (3.3.3)
redis-actionpack (5.0.1)
actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 1.4.0)
redis-activesupport (5.0.2)
activesupport (>= 3, < 6)
redis-store (~> 1.2.0)
redis-rack (2.0.0)
rack (~> 2.0)
redis-store (~> 1.2.0)
redis-rails (5.0.1)
redis-actionpack (~> 5.0.0)
redis-activesupport (~> 5.0.0)
redis-store (~> 1.2.0)
redis-store (1.2.0)
redis-store (~> 1.3.0)
redis-rack (2.0.1)
rack (>= 2.0, < 3)
redis-store (>= 1.2, < 1.4)
redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
redis-store (1.3.0)
redis (>= 2.2)
responders (2.3.0)
railties (>= 4.2.0, < 5.1)
rotp (2.1.2)
rqrcode (0.10.1)
chunky_png (~> 1.0)
rspec (3.5.0)
rspec-core (~> 3.5.0)
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
rspec-core (3.5.2)
rspec-core (3.5.4)
rspec-support (~> 3.5.0)
rspec-expectations (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
@@ -352,7 +368,7 @@ GEM
rspec-mocks (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
rspec-rails (3.5.1)
rspec-rails (3.5.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
@@ -360,40 +376,40 @@ GEM
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
rspec-support (~> 3.5.0)
rspec-sidekiq (2.2.0)
rspec (~> 3.0, >= 3.0.0)
rspec-sidekiq (3.0.0)
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.5.0)
rubocop (0.42.0)
parser (>= 2.3.1.1, < 3.0)
rubocop (0.48.1)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-oembed (0.10.1)
ruby-oembed (0.12.0)
ruby-progressbar (1.8.1)
safe_yaml (1.0.4)
sass (3.4.22)
sass (3.4.23)
sass-rails (5.0.6)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
sdoc (0.4.1)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
sidekiq (4.2.7)
sidekiq (4.2.10)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
redis (~> 3.2, >= 3.2.1)
simple-navigation (4.0.3)
sidekiq-unique-jobs (5.0.0)
sidekiq (>= 4.0)
thor
simple-navigation (4.0.5)
activesupport (>= 2.3.2)
simple_form (3.2.1)
simple_form (3.4.0)
actionpack (> 4, < 5.1)
activemodel (> 4, < 5.1)
simplecov (0.12.0)
simplecov (0.14.1)
docile (~> 1.1.0)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
@@ -406,39 +422,42 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sshkit (1.11.5)
sshkit (1.13.1)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
statsd-instrument (2.1.2)
temple (0.7.7)
term-ansicolor (1.4.0)
tins (~> 1.0)
terminal-table (1.7.0)
unicode-display_width (~> 1.1)
temple (0.8.0)
terminal-table (1.7.3)
unicode-display_width (~> 1.1.1)
thor (0.19.4)
thread (0.2.2)
thread_safe (0.3.5)
tilt (2.0.5)
tins (1.12.0)
tzinfo (1.2.2)
thread_safe (0.3.6)
tilt (2.0.7)
twitter-text (1.14.5)
unf (~> 0.1.0)
tzinfo (1.2.3)
thread_safe (~> 0.1)
uglifier (3.0.1)
tzinfo-data (1.2017.2)
tzinfo (>= 1.0.0)
uglifier (3.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
unicode-display_width (1.1.0)
unf_ext (0.0.7.3)
unicode-display_width (1.1.3)
uniform_notifier (1.10.0)
warden (1.2.6)
warden (1.2.7)
rack (>= 1.0)
webmock (2.1.0)
webmock (2.3.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
websocket-driver (0.6.4)
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
will_paginate (3.1.0)
whatlanguage (1.0.6)
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS
ruby
@@ -453,16 +472,18 @@ DEPENDENCIES
binding_of_caller
browserify-rails
bullet
capistrano
capistrano (= 3.8.0)
capistrano-faster-assets (~> 1.0)
capistrano-rails
capistrano-rbenv
capistrano-yarn
coffee-rails (~> 4.1.0)
capybara
devise
devise-two-factor
doorkeeper
dotenv-rails
fabrication
faker
fast_blank
font-awesome-rails
fuubar
@@ -471,29 +492,34 @@ DEPENDENCIES
hiredis
htmlentities
http
http_accept_language
httplog
i18n-tasks (~> 0.9.6)
jbuilder (~> 2.0)
jquery-rails
kaminari
letter_opener
letter_opener_web
link_header
lograge
microformats2
nokogiri
oj
ostatus2
ostatus2 (~> 1.1)
ox
paperclip (~> 5.1)
paperclip-av-transcoder
pg
pg_search
pghero
pkg-config
pry-rails
puma
rabl
rack-attack
rack-cors
rack-timeout
rails (~> 5.0.1.0)
rails (~> 5.0.2)
rails-controller-testing
rails-i18n
rails-settings-cached
rails_12factor
react-rails
@@ -505,18 +531,21 @@ DEPENDENCIES
rubocop
ruby-oembed
sass-rails (~> 5.0)
sdoc (~> 0.4.0)
sidekiq
sidekiq-unique-jobs
simple-navigation
simple_form
simplecov
sprockets-rails
statsd-instrument
twitter-text
tzinfo-data
uglifier (>= 1.3.0)
webmock
will_paginate
whatlanguage
RUBY VERSION
ruby 2.3.1p112
ruby 2.4.1p111
BUNDLED WITH
1.14.3
1.14.6

6
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1,6 @@
[Issue text goes here].
* * * *
- [ ] I searched or browsed the repos other issues to ensure this is not a duplicate.
- [ ] This bug happens on a [tagged release](https://github.com/tootsuite/mastodon/releases) and not on `master` (If you're a user, don't worry about this).

View File

@@ -1,2 +1,2 @@
web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq -q default -q mailers -q push
worker: bundle exec sidekiq -q default -q push -q pull -q mailers

View File

@@ -7,7 +7,7 @@ Mastodon
[travis]: https://travis-ci.org/tootsuite/mastodon
[code_climate]: https://codeclimate.com/github/tootsuite/mastodon
Mastodon is a free, open-source social network server. A decentralized alternative 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.
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.
@@ -17,7 +17,7 @@ Click on the screenshot to watch a demo of the UI:
[youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU
Focus of the project on a clean REST API and a good user interface. Ruby on Rails is used for the back-end, while React.js and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided.
The project focus is a clean REST API and a good user interface. Ruby on Rails is used for the back-end, while React.js and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided.
If you would like, you can [support the development of this project on Patreon][patreon]. Alternatively, you can donate to this BTC address: `17j2g7vpgHhLuXhN4bueZFCvdxxieyRVWd`
@@ -25,11 +25,11 @@ If you would like, you can [support the development of this project on Patreon][
## Resources
- [List of Mastodon instances](docs/Using-Mastodon/List-of-Mastodon-instances.md)
- [List of Mastodon instances](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md)
- [Use this tool to find Twitter friends on Mastodon](https://mastodon-bridge.herokuapp.com)
- [API overview](docs/Using-the-API/API.md)
- [Frequently Asked Questions](docs/Using-Mastodon/FAQ.md)
- [List of apps](docs/Using-Mastodon/Apps.md)
- [API overview](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md)
- [Frequently Asked Questions](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md)
- [List of apps](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md)
## Features
@@ -48,6 +48,14 @@ 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
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
@@ -65,23 +73,55 @@ Consult the example configuration file, `.env.production.sample` for the full li
## Running with Docker and Docker-Compose
The project now includes a `Dockerfile` and a `docker-compose.yml`. You need to turn `.env.production.sample` into `.env.production` with all the variables set before you can:
[![](https://images.microbadger.com/badges/version/gargron/mastodon.svg)](https://microbadger.com/images/gargron/mastodon "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/gargron/mastodon.svg)](https://microbadger.com/images/gargron/mastodon "Get your own image badge on microbadger.com")
The project now includes a `Dockerfile` and a `docker-compose.yml` file (which requires at least docker-compose version `1.10.0`).
Review the settings in `docker-compose.yml`. Note that it is not default to store the postgresql database and redis databases in a persistent storage location,
so you may need or want to adjust the settings there.
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
And finally
docker-compose up -d
docker-compose run --rm web rake secret
As usual, the first thing you would need to do would be to run migrations:
Do this once for each of those keys, and copy the result into the `.env.production` file in the appropriate field.
Then you should run the `db:migrate` command to create the database, or migrate it from an older release:
docker-compose run --rm web rails db:migrate
And since the instance running in the container will be running in production mode, you need to pre-compile assets:
Then, you will also need to precompile the assets:
docker-compose run --rm web rails assets:precompile
The container has two volumes, for the assets and for user uploads. The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up.
before you can launch the docker image with:
docker-compose up
If you wish to run this as a daemon process instead of monitoring it on console, use instead:
docker-compose up -d
Then you may login to your new Mastodon instance by browsing to http://localhost:3000/
Following that, make sure that you read the [production guide](docs/Running-Mastodon/Production-guide.md). You are probably going to want to understand how
to configure Nginx to make your Mastodon instance available to the rest of the world.
The container has two volumes, for the assets and for user uploads, and optionally two more, for the postgresql and redis databases.
The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up.
**Note**: The `--rm` option for docker-compose will remove the container that is created to run a one-off command after it completes. As data is stored in volumes it is not affected by that container clean-up.
@@ -101,37 +141,37 @@ Running any of these tasks via docker-compose would look like this:
This approach makes updating to the latest version a real breeze.
git pull
To pull down the updates, re-run
docker-compose build
And finally,
docker-compose up -d
Which will re-create the updated containers, leaving databases and data as is. Depending on what files have been updated, you might need to re-run migrations and asset compilation.
1. `git pull` to download updates from the repository
2. `docker-compose build` to compile the Docker image out of the changed source files
3. (optional) `docker-compose run --rm web rails db:migrate` to perform database migrations. Does nothing if your database is up to date
4. (optional) `docker-compose run --rm web rails assets:precompile` to compile new JS and CSS assets
5. `docker-compose up -d` to re-create (restart) containers and pick up the changes
## Deployment without Docker
Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](docs/Running-Mastodon/Production-guide.md) for examples, configuration and instructions.
Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md) for examples, configuration and instructions.
## Deployment on Scalingo
[![Deploy on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/tootsuite/mastodon#master)
[You can view a guide for deployment on Scalingo here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Scalingo-guide.md)
## Deployment on Heroku (experimental)
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
Mastodon can theoretically run indefinitely on a free [Heroku](https://heroku.com) app. [You can view a guide for deployment on Heroku here.](docs/Running-Mastodon/Heroku-guide.md)
Mastodon can run on [Heroku](https://heroku.com), but it gets expensive and impractical due to how Heroku prices resource usage. [You can view a guide for deployment on Heroku here](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Heroku-guide.md), but you have been warned.
## Development with Vagrant
A quick way to get a development environment up and running is with Vagrant. You will need recent versions of [Vagrant](https://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) installed.
[You can find the guide for setting up a Vagrant development environment here.](docs/Running-Mastodon/Vagrant-guide.md)
[You can find the guide for setting up a Vagrant development environment here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Vagrant-guide.md)
## Contributing
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository. This section may be updated with more details in the future.
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository. [Here are the guidelines for code contributions](CONTRIBUTING.md)
**IRC channel**: #mastodon on irc.freenode.net

26
Vagrantfile vendored
View File

@@ -43,15 +43,15 @@ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
export PATH="$HOME/.rbenv/bin::$PATH"
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
echo "Compiling Ruby 2.3.1: warning, this takes a while!!!"
rbenv install 2.3.1
rbenv global 2.3.1
cd /vagrant
echo "Compiling Ruby $(cat .ruby-version): warning, this takes a while!!!"
rbenv install $(cat .ruby-version)
rbenv global $(cat .ruby-version)
# Configure database
sudo -u postgres createuser -U postgres vagrant -s
sudo -u postgres createdb -U postgres mastodon_development
@@ -84,6 +84,16 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.provider :virtualbox do |vb|
vb.name = "mastodon"
vb.customize ["modifyvm", :id, "--memory", "1024"]
# Disable VirtualBox DNS proxy to skip long-delay IPv6 resolutions.
# https://github.com/mitchellh/vagrant/issues/1172
vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"]
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"]
# Use "virtio" network interfaces for better performance.
vb.customize ["modifyvm", :id, "--nictype1", "virtio"]
vb.customize ["modifyvm", :id, "--nictype2", "virtio"]
end
config.vm.hostname = "mastodon.dev"
@@ -91,12 +101,14 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# This uses the vagrant-hostsupdater plugin, and lets you
# access the development site at http://mastodon.dev.
# To install:
# $ vagrant plugin install hostsupdater
# $ vagrant plugin install vagrant-hostsupdater
if defined?(VagrantPlugins::HostsUpdater)
config.vm.network :private_network, ip: "192.168.42.42"
config.vm.network :private_network, ip: "192.168.42.42", nictype: "virtio"
config.hostsupdater.remove_on_suspend = false
end
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'vers=3', 'tcp']
# Otherwise, you can access the site at http://localhost:3000
config.vm.network :forwarded_port, guest: 80, host: 3000

View File

@@ -26,6 +26,10 @@
"description": "The secret key base",
"generator": "secret"
},
"OTP_SECRET": {
"description": "One-time password secret",
"generator": "secret"
},
"SINGLE_USER_MODE": {
"description": "Should the instance run in single user mode? (Disable registrations, redirect to front page)",
"value": "false",
@@ -71,6 +75,22 @@
"SMTP_DOMAIN": {
"description": "Domain for SMTP server. Will default to instance domain if blank.",
"required": false
},
"SMTP_FROM_ADDRESS": {
"description": "Address to send emails from",
"required": false
},
"SMTP_AUTH_METHOD": {
"description": "Authentication method to use with SMTP server. Default is 'plain'.",
"required": false
},
"SMTP_OPENSSL_VERIFY_MODE": {
"description": "SMTP server certificate verification mode. Defaults is 'peer'.",
"required": false
},
"SMTP_ENABLE_STARTTLS_AUTO": {
"description": "Enable STARTTLS if SMTP server supports it? Default is true.",
"required": false
}
},
"buildpacks": [
@@ -88,4 +108,4 @@
"heroku-postgresql",
"heroku-redis"
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 KiB

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -0,0 +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>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -21,6 +21,14 @@ export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST';
export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS';
export const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL';
export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST';
export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS';
export const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL';
export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST';
export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS';
export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL';
export const ACCOUNT_TIMELINE_FETCH_REQUEST = 'ACCOUNT_TIMELINE_FETCH_REQUEST';
export const ACCOUNT_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_TIMELINE_FETCH_SUCCESS';
export const ACCOUNT_TIMELINE_FETCH_FAIL = 'ACCOUNT_TIMELINE_FETCH_FAIL';
@@ -67,11 +75,16 @@ export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL';
export function fetchAccount(id) {
return (dispatch, getState) => {
dispatch(fetchRelationships([id]));
if (getState().getIn(['accounts', id], null) !== null) {
return;
}
dispatch(fetchAccountRequest(id));
api(getState).get(`/api/v1/accounts/${id}`).then(response => {
dispatch(fetchAccountSuccess(response.data));
dispatch(fetchRelationships([id]));
}).catch(error => {
dispatch(fetchAccountFail(id, error));
});
@@ -138,7 +151,8 @@ export function fetchAccountFail(id, error) {
return {
type: ACCOUNT_FETCH_FAIL,
id,
error
error,
skipAlert: true
};
};
@@ -231,7 +245,8 @@ export function fetchAccountTimelineFail(id, error, skipLoading) {
type: ACCOUNT_TIMELINE_FETCH_FAIL,
id,
error,
skipLoading
skipLoading,
skipAlert: error.response.status === 404
};
};
@@ -326,6 +341,76 @@ export function unblockAccountFail(error) {
};
};
export function muteAccount(id) {
return (dispatch, getState) => {
dispatch(muteAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/mute`).then(response => {
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
dispatch(muteAccountSuccess(response.data, getState().get('statuses')));
}).catch(error => {
dispatch(muteAccountFail(id, error));
});
};
};
export function unmuteAccount(id) {
return (dispatch, getState) => {
dispatch(unmuteAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => {
dispatch(unmuteAccountSuccess(response.data));
}).catch(error => {
dispatch(unmuteAccountFail(id, error));
});
};
};
export function muteAccountRequest(id) {
return {
type: ACCOUNT_MUTE_REQUEST,
id
};
};
export function muteAccountSuccess(relationship, statuses) {
return {
type: ACCOUNT_MUTE_SUCCESS,
relationship,
statuses
};
};
export function muteAccountFail(error) {
return {
type: ACCOUNT_MUTE_FAIL,
error
};
};
export function unmuteAccountRequest(id) {
return {
type: ACCOUNT_UNMUTE_REQUEST,
id
};
};
export function unmuteAccountSuccess(relationship) {
return {
type: ACCOUNT_UNMUTE_SUCCESS,
relationship
};
};
export function unmuteAccountFail(error) {
return {
type: ACCOUNT_UNMUTE_FAIL,
error
};
};
export function fetchFollowers(id) {
return (dispatch, getState) => {
dispatch(fetchFollowersRequest(id));
@@ -494,15 +579,18 @@ export function expandFollowingFail(id, error) {
};
};
export function fetchRelationships(account_ids) {
export function fetchRelationships(accountIds) {
return (dispatch, getState) => {
if (account_ids.length === 0) {
const loadedRelationships = getState().get('relationships');
const newAccountIds = accountIds.filter(id => loadedRelationships.get(id, null) === null);
if (newAccountIds.length === 0) {
return;
}
dispatch(fetchRelationshipsRequest(account_ids));
dispatch(fetchRelationshipsRequest(newAccountIds));
api(getState).get(`/api/v1/accounts/relationships?${account_ids.map(id => `id[]=${id}`).join('&')}`).then(response => {
api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
dispatch(fetchRelationshipsSuccess(response.data));
}).catch(error => {
dispatch(fetchRelationshipsFail(error));

View File

@@ -6,6 +6,10 @@ export const STATUS_CARD_FETCH_FAIL = 'STATUS_CARD_FETCH_FAIL';
export function fetchStatusCard(id) {
return (dispatch, getState) => {
if (getState().getIn(['cards', id], null) !== null) {
return;
}
dispatch(fetchStatusCardRequest(id));
api(getState).get(`/api/v1/statuses/${id}/card`).then(response => {
@@ -42,6 +46,7 @@ export function fetchStatusCardFail(id, error) {
type: STATUS_CARD_FETCH_FAIL,
id,
error,
skipLoading: true
skipLoading: true,
skipAlert: true
};
};

View File

@@ -1,7 +1,9 @@
import api from '../api'
import api from '../api';
import { updateTimeline } from './timelines';
import * as emojione from 'emojione';
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
@@ -28,6 +30,8 @@ export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT';
export function changeCompose(text) {
return {
type: COMPOSE_CHANGE,
@@ -70,22 +74,27 @@ export function mentionCompose(account, router) {
export function submitCompose() {
return function (dispatch, getState) {
dispatch(submitComposeRequest());
api(getState).post('/api/v1/statuses', {
status: getState().getIn(['compose', 'text'], ''),
status: emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], '')),
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
sensitive: getState().getIn(['compose', 'sensitive']),
spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
visibility: getState().getIn(['compose', 'private']) ? 'private' : (getState().getIn(['compose', 'unlisted']) ? 'unlisted' : 'public')
visibility: getState().getIn(['compose', 'privacy'])
}).then(function (response) {
dispatch(submitComposeSuccess({ ...response.data }));
// To make the app more responsive, immediately get the status into the columns
dispatch(updateTimeline('home', { ...response.data }));
if (response.data.in_reply_to_id === null && !getState().getIn(['compose', 'private']) && !getState().getIn(['compose', 'unlisted'])) {
dispatch(updateTimeline('public', { ...response.data }));
if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
if (getState().getIn(['timelines', 'community', 'loaded'])) {
dispatch(updateTimeline('community', { ...response.data }));
}
if (getState().getIn(['timelines', 'public', 'loaded'])) {
dispatch(updateTimeline('public', { ...response.data }));
}
}
}).catch(function (error) {
dispatch(submitComposeFail(error));
@@ -115,6 +124,10 @@ export function submitComposeFail(error) {
export function uploadCompose(files) {
return function (dispatch, getState) {
if (getState().getIn(['compose', 'media_attachments']).size > 3) {
return;
}
dispatch(uploadComposeRequest());
let data = new FormData();
@@ -134,7 +147,8 @@ export function uploadCompose(files) {
export function uploadComposeRequest() {
return {
type: COMPOSE_UPLOAD_REQUEST
type: COMPOSE_UPLOAD_REQUEST,
skipLoading: true
};
};
@@ -149,14 +163,16 @@ export function uploadComposeProgress(loaded, total) {
export function uploadComposeSuccess(media) {
return {
type: COMPOSE_UPLOAD_SUCCESS,
media: media
media: media,
skipLoading: true
};
};
export function uploadComposeFail(error) {
return {
type: COMPOSE_UPLOAD_FAIL,
error: error
error: error,
skipLoading: true
};
};
@@ -220,17 +236,15 @@ export function unmountCompose() {
};
};
export function changeComposeSensitivity(checked) {
export function changeComposeSensitivity() {
return {
type: COMPOSE_SENSITIVITY_CHANGE,
checked
};
};
export function changeComposeSpoilerness(checked) {
export function changeComposeSpoilerness() {
return {
type: COMPOSE_SPOILERNESS_CHANGE,
checked
type: COMPOSE_SPOILERNESS_CHANGE
};
};
@@ -241,16 +255,17 @@ export function changeComposeSpoilerText(text) {
};
};
export function changeComposeVisibility(checked) {
export function changeComposeVisibility(value) {
return {
type: COMPOSE_VISIBILITY_CHANGE,
checked
value
};
};
export function changeComposeListability(checked) {
export function insertEmojiCompose(position, emoji) {
return {
type: COMPOSE_LISTABILITY_CHANGE,
checked
type: COMPOSE_EMOJI_INSERT,
position,
emoji
};
};

View File

@@ -1,14 +1,11 @@
export const MEDIA_OPEN = 'MEDIA_OPEN';
export const MODAL_OPEN = 'MODAL_OPEN';
export const MODAL_CLOSE = 'MODAL_CLOSE';
export const MODAL_INDEX_DECREASE = 'MODAL_INDEX_DECREASE';
export const MODAL_INDEX_INCREASE = 'MODAL_INDEX_INCREASE';
export function openMedia(media, index) {
export function openModal(type, props) {
return {
type: MEDIA_OPEN,
media,
index
type: MODAL_OPEN,
modalType: type,
modalProps: props
};
};
@@ -17,15 +14,3 @@ export function closeModal() {
type: MODAL_CLOSE
};
};
export function decreaseIndexInModal() {
return {
type: MODAL_INDEX_DECREASE
};
};
export function increaseIndexInModal() {
return {
type: MODAL_INDEX_INCREASE
};
};

View File

@@ -0,0 +1,82 @@
import api, { getLinks } from '../api'
import { fetchRelationships } from './accounts';
export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST';
export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS';
export const MUTES_FETCH_FAIL = 'MUTES_FETCH_FAIL';
export const MUTES_EXPAND_REQUEST = 'MUTES_EXPAND_REQUEST';
export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS';
export const MUTES_EXPAND_FAIL = 'MUTES_EXPAND_FAIL';
export function fetchMutes() {
return (dispatch, getState) => {
dispatch(fetchMutesRequest());
api(getState).get('/api/v1/mutes').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(fetchMutesSuccess(response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => dispatch(fetchMutesFail(error)));
};
};
export function fetchMutesRequest() {
return {
type: MUTES_FETCH_REQUEST
};
};
export function fetchMutesSuccess(accounts, next) {
return {
type: MUTES_FETCH_SUCCESS,
accounts,
next
};
};
export function fetchMutesFail(error) {
return {
type: MUTES_FETCH_FAIL,
error
};
};
export function expandMutes() {
return (dispatch, getState) => {
const url = getState().getIn(['user_lists', 'mutes', 'next']);
if (url === null) {
return;
}
dispatch(expandMutesRequest());
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandMutesSuccess(response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => dispatch(expandMutesFail(error)));
};
};
export function expandMutesRequest() {
return {
type: MUTES_EXPAND_REQUEST
};
};
export function expandMutesSuccess(accounts, next) {
return {
type: MUTES_EXPAND_SUCCESS,
accounts,
next
};
};
export function expandMutesFail(error) {
return {
type: MUTES_EXPAND_FAIL,
error
};
};

View File

@@ -14,6 +14,9 @@ export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL';
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
const fetchRelatedRelationships = (dispatch, notifications) => {
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
@@ -47,6 +50,8 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
};
};
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
export function refreshNotifications() {
return (dispatch, getState) => {
dispatch(refreshNotificationsRequest());
@@ -58,6 +63,8 @@ export function refreshNotifications() {
params.since_id = ids.first().get('id');
}
params.exclude_types = excludeTypesFromSettings(getState());
api(getState).get('/api/v1/notifications', { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
@@ -102,11 +109,11 @@ export function expandNotifications() {
dispatch(expandNotificationsRequest());
api(getState).get(url, {
params: {
limit: 5
}
}).then(response => {
const params = {};
params.exclude_types = excludeTypesFromSettings(getState());
api(getState).get(url, params).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
@@ -139,3 +146,20 @@ export function expandNotificationsFail(error) {
error
};
};
export function clearNotifications() {
return (dispatch, getState) => {
dispatch({
type: NOTIFICATIONS_CLEAR
});
api(getState).post('/api/v1/notifications/clear');
};
};
export function scrollTopNotifications(top) {
return {
type: NOTIFICATIONS_SCROLL_TOP,
top
};
};

View File

@@ -0,0 +1,14 @@
import { openModal } from './modal';
import { changeSetting, saveSettings } from './settings';
export function showOnboardingOnce() {
return (dispatch, getState) => {
const alreadySeen = getState().getIn(['settings', 'onboarded']);
if (!alreadySeen) {
dispatch(openModal('ONBOARDING'));
dispatch(changeSetting(['onboarded'], true));
dispatch(saveSettings());
}
};
};

View File

@@ -0,0 +1,72 @@
import api from '../api';
export const REPORT_INIT = 'REPORT_INIT';
export const REPORT_CANCEL = 'REPORT_CANCEL';
export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST';
export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS';
export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL';
export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
export function initReport(account, status) {
return {
type: REPORT_INIT,
account,
status
};
};
export function cancelReport() {
return {
type: REPORT_CANCEL
};
};
export function toggleStatusReport(statusId, checked) {
return {
type: REPORT_STATUS_TOGGLE,
statusId,
checked,
};
};
export function submitReport() {
return (dispatch, getState) => {
dispatch(submitReportRequest());
api(getState).post('/api/v1/reports', {
account_id: getState().getIn(['reports', 'new', 'account_id']),
status_ids: getState().getIn(['reports', 'new', 'status_ids']),
comment: getState().getIn(['reports', 'new', 'comment'])
}).then(response => dispatch(submitReportSuccess(response.data))).catch(error => dispatch(submitReportFail(error)));
};
};
export function submitReportRequest() {
return {
type: REPORT_SUBMIT_REQUEST
};
};
export function submitReportSuccess(report) {
return {
type: REPORT_SUBMIT_SUCCESS,
report
};
};
export function submitReportFail(error) {
return {
type: REPORT_SUBMIT_FAIL,
error
};
};
export function changeReportComment(comment) {
return {
type: REPORT_COMMENT_CHANGE,
comment
};
};

View File

@@ -1,9 +1,12 @@
import api from '../api'
export const SEARCH_CHANGE = 'SEARCH_CHANGE';
export const SEARCH_SUGGESTIONS_CLEAR = 'SEARCH_SUGGESTIONS_CLEAR';
export const SEARCH_SUGGESTIONS_READY = 'SEARCH_SUGGESTIONS_READY';
export const SEARCH_RESET = 'SEARCH_RESET';
export const SEARCH_CHANGE = 'SEARCH_CHANGE';
export const SEARCH_CLEAR = 'SEARCH_CLEAR';
export const SEARCH_SHOW = 'SEARCH_SHOW';
export const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST';
export const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS';
export const SEARCH_FETCH_FAIL = 'SEARCH_FETCH_FAIL';
export function changeSearch(value) {
return {
@@ -12,40 +15,59 @@ export function changeSearch(value) {
};
};
export function clearSearchSuggestions() {
export function clearSearch() {
return {
type: SEARCH_SUGGESTIONS_CLEAR
type: SEARCH_CLEAR
};
};
export function readySearchSuggestions(value, accounts) {
return {
type: SEARCH_SUGGESTIONS_READY,
value,
accounts
};
};
export function fetchSearchSuggestions(value) {
export function submitSearch() {
return (dispatch, getState) => {
if (getState().getIn(['search', 'loaded_value']) === value) {
const value = getState().getIn(['search', 'value']);
if (value.length === 0) {
return;
}
api(getState).get('/api/v1/accounts/search', {
dispatch(fetchSearchRequest());
api(getState).get('/api/v1/search', {
params: {
q: value,
resolve: true,
limit: 4
resolve: true
}
}).then(response => {
dispatch(readySearchSuggestions(value, response.data));
dispatch(fetchSearchSuccess(response.data));
}).catch(error => {
dispatch(fetchSearchFail(error));
});
};
};
export function resetSearch() {
export function fetchSearchRequest() {
return {
type: SEARCH_RESET
type: SEARCH_FETCH_REQUEST
};
};
export function fetchSearchSuccess(results) {
return {
type: SEARCH_FETCH_SUCCESS,
results,
accounts: results.accounts,
statuses: results.statuses
};
};
export function fetchSearchFail(error) {
return {
type: SEARCH_FETCH_FAIL,
error
};
};
export function showSearch() {
return {
type: SEARCH_SHOW
};
};

View File

@@ -27,12 +27,17 @@ export function fetchStatus(id) {
return (dispatch, getState) => {
const skipLoading = getState().getIn(['statuses', id], null) !== null;
dispatch(fetchContext(id));
dispatch(fetchStatusCard(id));
if (skipLoading) {
return;
}
dispatch(fetchStatusRequest(id, skipLoading));
api(getState).get(`/api/v1/statuses/${id}`).then(response => {
dispatch(fetchStatusSuccess(response.data, skipLoading));
dispatch(fetchContext(id));
dispatch(fetchStatusCard(id));
}).catch(error => {
dispatch(fetchStatusFail(id, error, skipLoading));
});
@@ -52,7 +57,8 @@ export function fetchStatusFail(id, error, skipLoading) {
type: STATUS_FETCH_FAIL,
id,
error,
skipLoading
skipLoading,
skipAlert: true
};
};
@@ -97,7 +103,12 @@ export function fetchContext(id) {
api(getState).get(`/api/v1/statuses/${id}/context`).then(response => {
dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants));
}).catch(error => {
if (error.response.status === 404) {
dispatch(deleteFromTimelines(id));
}
dispatch(fetchContextFail(id, error));
});
};
@@ -124,6 +135,7 @@ export function fetchContextFail(id, error) {
return {
type: CONTEXT_FETCH_FAIL,
id,
error
error,
skipAlert: true
};
};

View File

@@ -1,4 +1,4 @@
import api from '../api'
import api, { getLinks } from '../api'
import Immutable from 'immutable';
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
@@ -14,12 +14,16 @@ export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL';
export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
export function refreshTimelineSuccess(timeline, statuses, skipLoading) {
export const TIMELINE_CONNECT = 'TIMELINE_CONNECT';
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) {
return {
type: TIMELINE_REFRESH_SUCCESS,
timeline,
statuses,
skipLoading
skipLoading,
next
};
};
@@ -69,25 +73,27 @@ export function refreshTimeline(timeline, id = null) {
const ids = getState().getIn(['timelines', timeline, 'items'], Immutable.List());
const newestId = ids.size > 0 ? ids.first() : null;
let params = getState().getIn(['timelines', timeline, 'params'], {});
const path = getState().getIn(['timelines', timeline, 'path'])(id);
let params = '';
let path = timeline;
let skipLoading = false;
if (newestId !== null && getState().getIn(['timelines', timeline, 'loaded']) && (id === null || getState().getIn(['timelines', timeline, 'id']) === id)) {
params = `?since_id=${newestId}`;
skipLoading = true;
}
if (id === null && getState().getIn(['timelines', timeline, 'online'])) {
// Skip refreshing when timeline is live anyway
return;
}
if (id) {
path = `${path}/${id}`
params = { ...params, since_id: newestId };
skipLoading = true;
}
dispatch(refreshTimelineRequest(timeline, id, skipLoading));
api(getState).get(`/api/v1/timelines/${path}${params}`).then(function (response) {
dispatch(refreshTimelineSuccess(timeline, response.data, skipLoading));
}).catch(function (error) {
api(getState).get(path, { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(refreshTimelineSuccess(timeline, response.data, skipLoading, next ? next.uri : null));
}).catch(error => {
dispatch(refreshTimelineFail(timeline, error, skipLoading));
});
};
@@ -102,50 +108,50 @@ export function refreshTimelineFail(timeline, error, skipLoading) {
};
};
export function expandTimeline(timeline, id = null) {
export function expandTimeline(timeline) {
return (dispatch, getState) => {
const lastId = getState().getIn(['timelines', timeline, 'items'], Immutable.List()).last();
if (!lastId || getState().getIn(['timelines', timeline, 'isLoading'])) {
// If timeline is empty, don't try to load older posts since there are none
// Also if already loading
if (getState().getIn(['timelines', timeline, 'isLoading'])) {
return;
}
dispatch(expandTimelineRequest(timeline, id));
let path = timeline;
if (id) {
path = `${path}/${id}`
if (getState().getIn(['timelines', timeline, 'items']).size === 0) {
return;
}
api(getState).get(`/api/v1/timelines/${path}`, {
const path = getState().getIn(['timelines', timeline, 'path'])(getState().getIn(['timelines', timeline, 'id']));
const params = getState().getIn(['timelines', timeline, 'params'], {});
const lastId = getState().getIn(['timelines', timeline, 'items']).last();
dispatch(expandTimelineRequest(timeline));
api(getState).get(path, {
params: {
limit: 10,
max_id: lastId
...params,
max_id: lastId,
limit: 10
}
}).then(response => {
dispatch(expandTimelineSuccess(timeline, response.data));
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandTimelineSuccess(timeline, response.data, next ? next.uri : null));
}).catch(error => {
dispatch(expandTimelineFail(timeline, error));
});
};
};
export function expandTimelineRequest(timeline, id) {
export function expandTimelineRequest(timeline) {
return {
type: TIMELINE_EXPAND_REQUEST,
timeline,
id
timeline
};
};
export function expandTimelineSuccess(timeline, statuses) {
export function expandTimelineSuccess(timeline, statuses, next) {
return {
type: TIMELINE_EXPAND_SUCCESS,
timeline,
statuses
statuses,
next
};
};
@@ -164,3 +170,17 @@ export function scrollTopTimeline(timeline, top) {
top
};
};
export function connectTimeline(timeline) {
return {
type: TIMELINE_CONNECT,
timeline
};
};
export function disconnectTimeline(timeline) {
return {
type: TIMELINE_DISCONNECT,
timeline
};
};

View File

@@ -1,5 +1,5 @@
import axios from 'axios';
import LinkHeader from 'http-link-header';
import LinkHeader from './link_header';
export const getLinks = response => {
const value = response.headers.link;

View File

@@ -10,29 +10,10 @@ 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' }
unblock: { id: 'account.unblock', defaultMessage: 'Unblock' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute' }
});
const outerStyle = {
padding: '10px',
borderBottom: '1px solid #363c4b'
};
const itemStyle = {
flex: '1 1 auto',
display: 'block',
color: '#9baec8',
overflow: 'hidden',
textDecoration: 'none',
fontSize: '14px'
};
const noteStyle = {
paddingTop: '5px',
fontSize: '12px',
color: '#616b86'
};
const buttonsStyle = {
padding: '10px',
height: '18px'
@@ -45,16 +26,10 @@ const Account = React.createClass({
me: React.PropTypes.number.isRequired,
onFollow: React.PropTypes.func.isRequired,
onBlock: React.PropTypes.func.isRequired,
withNote: React.PropTypes.bool,
onMute: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
},
getDefaultProps () {
return {
withNote: false
};
},
mixins: [PureRenderMixin],
handleFollow () {
@@ -65,38 +40,41 @@ const Account = React.createClass({
this.props.onBlock(this.props.account);
},
handleMute () {
this.props.onMute(this.props.account);
},
render () {
const { account, me, withNote, intl } = this.props;
const { account, me, intl } = this.props;
if (!account) {
return <div />;
}
let note, buttons;
if (account.get('note').length > 0 && withNote) {
note = <div style={noteStyle}>{account.get('note')}</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)} onClick={this.handleBlock} />;
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 style={outerStyle}>
<div className='account'>
<div style={{ display: 'flex' }}>
<Permalink key={account.get('id')} style={itemStyle} className='account__display-name' href={account.get('url')} to={`/accounts/${account.get('id')}`}>
<div style={{ float: 'left', marginLeft: '12px', marginRight: '10px' }}><Avatar src={account.get('avatar')} size={36} /></div>
<Permalink key={account.get('id')} className='account__display-name' href={account.get('url')} to={`/accounts/${account.get('id')}`}>
<div style={{ float: 'left', marginLeft: '12px', marginRight: '10px' }}><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={36} /></div>
<DisplayName account={account} />
</Permalink>
@@ -104,8 +82,6 @@ const Account = React.createClass({
{buttons}
</div>
</div>
{note}
</div>
);
}

View File

@@ -1,5 +1,6 @@
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { isRtl } from '../rtl';
const textAtCursorMatchesToken = (str, caretPosition) => {
let word;
@@ -32,20 +33,18 @@ const AutosuggestTextarea = React.createClass({
value: React.PropTypes.string,
suggestions: ImmutablePropTypes.list,
disabled: React.PropTypes.bool,
fileDropDate: React.PropTypes.instanceOf(Date),
placeholder: React.PropTypes.string,
onSuggestionSelected: React.PropTypes.func.isRequired,
onSuggestionsClearRequested: React.PropTypes.func.isRequired,
onSuggestionsFetchRequested: React.PropTypes.func.isRequired,
onChange: React.PropTypes.func.isRequired,
onKeyUp: React.PropTypes.func,
onKeyDown: React.PropTypes.func
onKeyDown: React.PropTypes.func,
onPaste: React.PropTypes.func.isRequired,
},
getInitialState () {
return {
isFileDragging: false,
fileDraggingDate: undefined,
suggestionsHidden: false,
selectedSuggestion: 0,
lastToken: null,
@@ -137,45 +136,28 @@ const AutosuggestTextarea = React.createClass({
if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
this.setState({ suggestionsHidden: false });
}
const fileDropDate = nextProps.fileDropDate;
const { isFileDragging, fileDraggingDate } = this.state;
/*
* We can't detect drop events, because they might not be on the textarea (the app allows dropping anywhere in the
* window). Instead, on-drop, we notify this textarea to stop its hover effect by passing in a prop with the
* drop-date.
*/
if (isFileDragging && fileDraggingDate && fileDropDate // if dragging when props updated, and dates aren't undefined
&& fileDropDate > fileDraggingDate) { // and if the drop date is now greater than when we started dragging
// then we should stop dragging
this.setState({
isFileDragging: false
});
}
},
setTextarea (c) {
this.textarea = c;
},
onDragEnter () {
this.setState({
isFileDragging: true,
fileDraggingDate: new Date()
})
},
onDragExit () {
this.setState({
isFileDragging: false
})
onPaste (e) {
if (e.clipboardData && e.clipboardData.files.length === 1) {
this.props.onPaste(e.clipboardData.files)
e.preventDefault();
}
},
render () {
const { value, suggestions, fileDropDate, disabled, placeholder, onKeyUp } = this.props;
const { isFileDragging, suggestionsHidden, selectedSuggestion } = this.state;
const className = isFileDragging ? 'autosuggest-textarea__textarea file-drop' : 'autosuggest-textarea__textarea';
const { value, suggestions, disabled, placeholder, onKeyUp } = this.props;
const { suggestionsHidden, selectedSuggestion } = this.state;
const className = 'autosuggest-textarea__textarea';
const style = { direction: 'ltr' };
if (isRtl(value)) {
style.direction = 'rtl';
}
return (
<div className='autosuggest-textarea'>
@@ -190,13 +172,18 @@ const AutosuggestTextarea = React.createClass({
onKeyDown={this.onKeyDown}
onKeyUp={onKeyUp}
onBlur={this.onBlur}
onDragEnter={this.onDragEnter}
onDragExit={this.onDragExit}
onPaste={this.onPaste}
style={style}
/>
<div style={{ display: (suggestions.size > 0 && !suggestionsHidden) ? 'block' : 'none' }} className='autosuggest-textarea__suggestions'>
{suggestions.map((suggestion, i) => (
<div key={suggestion} className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`} onClick={this.onSuggestionClick.bind(this, suggestion)}>
<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>
))}

View File

@@ -1,103 +1,18 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
// From: http://stackoverflow.com/a/18320662
const resample = (canvas, width, height, resize_canvas) => {
let width_source = canvas.width;
let height_source = canvas.height;
width = Math.round(width);
height = Math.round(height);
let ratio_w = width_source / width;
let ratio_h = height_source / height;
let ratio_w_half = Math.ceil(ratio_w / 2);
let ratio_h_half = Math.ceil(ratio_h / 2);
let ctx = canvas.getContext("2d");
let img = ctx.getImageData(0, 0, width_source, height_source);
let img2 = ctx.createImageData(width, height);
let data = img.data;
let data2 = img2.data;
for (let j = 0; j < height; j++) {
for (let i = 0; i < width; i++) {
let x2 = (i + j * width) * 4;
let weight = 0;
let weights = 0;
let weights_alpha = 0;
let gx_r = 0;
let gx_g = 0;
let gx_b = 0;
let gx_a = 0;
let center_y = (j + 0.5) * ratio_h;
let yy_start = Math.floor(j * ratio_h);
let yy_stop = Math.ceil((j + 1) * ratio_h);
for (let yy = yy_start; yy < yy_stop; yy++) {
let dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
let center_x = (i + 0.5) * ratio_w;
let w0 = dy * dy; //pre-calc part of w
let xx_start = Math.floor(i * ratio_w);
let xx_stop = Math.ceil((i + 1) * ratio_w);
for (let xx = xx_start; xx < xx_stop; xx++) {
let dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
let w = Math.sqrt(w0 + dx * dx);
if (w >= 1) {
// pixel too far
continue;
}
// hermite filter
weight = 2 * w * w * w - 3 * w * w + 1;
let pos_x = 4 * (xx + yy * width_source);
// alpha
gx_a += weight * data[pos_x + 3];
weights_alpha += weight;
// colors
if (data[pos_x + 3] < 255)
weight = weight * data[pos_x + 3] / 250;
gx_r += weight * data[pos_x];
gx_g += weight * data[pos_x + 1];
gx_b += weight * data[pos_x + 2];
weights += weight;
}
}
data2[x2] = gx_r / weights;
data2[x2 + 1] = gx_g / weights;
data2[x2 + 2] = gx_b / weights;
data2[x2 + 3] = gx_a / weights_alpha;
}
}
// clear and resize canvas
if (resize_canvas === true) {
canvas.width = width;
canvas.height = height;
} else {
ctx.clearRect(0, 0, width_source, height_source);
}
// draw
ctx.putImageData(img2, 0, 0);
};
const Avatar = React.createClass({
propTypes: {
src: React.PropTypes.string.isRequired,
staticSrc: React.PropTypes.string,
size: React.PropTypes.number.isRequired,
style: React.PropTypes.object,
animated: React.PropTypes.bool
animate: React.PropTypes.bool
},
getDefaultProps () {
return {
animated: true
animate: false
};
},
@@ -117,38 +32,30 @@ const Avatar = React.createClass({
this.setState({ hovering: false });
},
handleLoad () {
this.canvas.width = this.image.naturalWidth;
this.canvas.height = this.image.naturalHeight;
this.canvas.getContext('2d').drawImage(this.image, 0, 0);
resample(this.canvas, this.props.size * window.devicePixelRatio, this.props.size * window.devicePixelRatio, true);
},
setImageRef (c) {
this.image = c;
},
setCanvasRef (c) {
this.canvas = c;
},
render () {
const { src, size, staticSrc, animate } = this.props;
const { hovering } = this.state;
if (this.props.animated) {
return (
<div style={{ ...this.props.style, width: `${this.props.size}px`, height: `${this.props.size}px` }}>
<img src={this.props.src} width={this.props.size} height={this.props.size} alt='' style={{ borderRadius: '4px' }} />
</div>
);
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 onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} style={{ ...this.props.style, width: `${this.props.size}px`, height: `${this.props.size}px`, position: 'relative' }}>
<img ref={this.setImageRef} onLoad={this.handleLoad} src={this.props.src} width={this.props.size} height={this.props.size} alt='' style={{ position: 'absolute', top: '0', left: '0', opacity: hovering ? '1' : '0', borderRadius: '4px' }} />
<canvas ref={this.setCanvasRef} style={{ borderRadius: '4px', width: this.props.size, height: this.props.size, opacity: hovering ? '0' : '1' }} />
</div>
<div
className='avatar'
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
style={style}
/>
);
}

View File

@@ -3,12 +3,14 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
const Button = React.createClass({
propTypes: {
text: React.PropTypes.string,
text: React.PropTypes.node,
onClick: React.PropTypes.func,
disabled: React.PropTypes.bool,
block: React.PropTypes.bool,
secondary: React.PropTypes.bool,
size: React.PropTypes.number,
style: React.PropTypes.object,
children: React.PropTypes.node
},
getDefaultProps () {
@@ -34,11 +36,9 @@ const Button = React.createClass({
boxSizing: 'border-box',
textAlign: 'center',
border: '10px none',
color: '#fff',
fontSize: '14px',
fontWeight: '500',
letterSpacing: '0',
textTransform: 'uppercase',
padding: `0 ${this.props.size / 2.25}px`,
height: `${this.props.size}px`,
cursor: 'pointer',

View File

@@ -0,0 +1,19 @@
import { Motion, spring } from 'react-motion';
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: React.PropTypes.number.isRequired,
isVisible: React.PropTypes.bool.isRequired,
children: React.PropTypes.node.isRequired
};
export default Collapsable;

View File

@@ -1,15 +1,6 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import { FormattedMessage } from 'react-intl';
const outerStyle = {
padding: '15px',
fontSize: '16px',
background: '#2f3441',
flex: '0 0 auto',
cursor: 'pointer',
color: '#2b90d9'
};
const iconStyle = {
display: 'inline-block',
marginRight: '5px'
@@ -24,12 +15,13 @@ const ColumnBackButton = React.createClass({
mixins: [PureRenderMixin],
handleClick () {
this.context.router.goBack();
if (window.history && window.history.length === 1) this.context.router.push("/");
else this.context.router.goBack();
},
render () {
return (
<div onClick={this.handleClick} style={outerStyle} className='column-back-button'>
<div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button'>
<i className='fa fa-fw fa-chevron-left' style={iconStyle} />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</div>

View File

@@ -7,10 +7,8 @@ const outerStyle = {
top: '-48px',
padding: '15px',
fontSize: '16px',
background: '#2f3441',
flex: '0 0 auto',
cursor: 'pointer',
color: '#2b90d9'
cursor: 'pointer'
};
const iconStyle = {
@@ -33,7 +31,7 @@ const ColumnBackButtonSlim = React.createClass({
render () {
return (
<div style={{ position: 'relative' }}>
<div style={outerStyle} onClick={this.handleClick} className='column-back-button'>
<div role='button' tabIndex='0' style={outerStyle} onClick={this.handleClick} className='column-back-button'>
<i className='fa fa-fw fa-chevron-left' style={iconStyle} />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</div>

View File

@@ -7,13 +7,15 @@ const iconStyle = {
position: 'absolute',
right: '0',
top: '-48px',
cursor: 'pointer'
cursor: 'pointer',
zIndex: '3'
};
const ColumnCollapsable = React.createClass({
propTypes: {
icon: React.PropTypes.string.isRequired,
title: React.PropTypes.string,
fullHeight: React.PropTypes.number.isRequired,
children: React.PropTypes.node,
onCollapse: React.PropTypes.func
@@ -38,12 +40,15 @@ const ColumnCollapsable = React.createClass({
},
render () {
const { icon, fullHeight, children } = this.props;
const { icon, title, fullHeight, children } = this.props;
const { collapsed } = this.state;
const collapsedClassName = collapsed ? 'collapsable-collapsed' : 'collapsable';
return (
<div style={{ position: 'relative' }}>
<div style={{...iconStyle, color: collapsed ? '#9baec8' : '#fff', background: collapsed ? '#2f3441' : '#373b4a' }} onClick={this.handleToggleCollapsed}><i className={`fa fa-${icon}`} /></div>
<div role='button' tabIndex='0' title={`${title}`} style={{...iconStyle }} 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 }) =>

View File

@@ -1,6 +1,6 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import escapeTextContentForBrowser from 'react/lib/escapeTextContentForBrowser';
import escapeTextContentForBrowser from 'escape-html';
import emojify from '../emoji';
const DisplayName = React.createClass({

View File

@@ -1,32 +1,72 @@
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
import PureRenderMixin from 'react-addons-pure-render-mixin';
const DropdownMenu = ({ icon, items, size, direction }) => {
const directionClass = (direction == "left") ? "dropdown__left" : "dropdown__right";
const DropdownMenu = React.createClass({
return (
<Dropdown>
<DropdownTrigger className='icon-button' style={{ fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }}>
<i className={`fa fa-fw fa-${icon}`} style={{ verticalAlign: 'middle' }} />
</DropdownTrigger>
propTypes: {
icon: React.PropTypes.string.isRequired,
items: React.PropTypes.array.isRequired,
size: React.PropTypes.number.isRequired,
direction: React.PropTypes.string
},
<DropdownContent className={directionClass} style={{ lineHeight: '18px', textAlign: 'left' }}>
<ul>
{items.map(({ text, action, href = '#' }, i) => <li key={i}><a href={href} target='_blank' rel='noopener' onClick={e => {
if (typeof action === 'function') {
e.preventDefault();
action();
}
}}>{text}</a></li>)}
</ul>
</DropdownContent>
</Dropdown>
);
};
getDefaultProps () {
return {
direction: 'left'
};
},
DropdownMenu.propTypes = {
icon: React.PropTypes.string.isRequired,
items: React.PropTypes.array.isRequired,
size: React.PropTypes.number.isRequired
};
mixins: [PureRenderMixin],
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={i} className='dropdown__sep' />;
}
const { text, action, href = '#' } = item;
return (
<li key={i}>
<a href={href} target='_blank' rel='noopener' onClick={this.handleClick.bind(this, i)}>
{text}
</a>
</li>
);
},
render () {
const { icon, items, size, direction } = 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` }}>
<i className={`fa fa-fw fa-${icon}`} style={{ verticalAlign: 'middle' }} />
</DropdownTrigger>
<DropdownContent className={directionClass} style={{ lineHeight: '18px', textAlign: 'left' }}>
<ul>
{items.map(this.renderItem)}
</ul>
</DropdownContent>
</Dropdown>
);
}
});
export default DropdownMenu;

View File

@@ -0,0 +1,49 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
const ExtendedVideoPlayer = React.createClass({
propTypes: {
src: React.PropTypes.string.isRequired,
time: React.PropTypes.number,
controls: React.PropTypes.bool.isRequired,
muted: React.PropTypes.bool.isRequired
},
mixins: [PureRenderMixin],
handleLoadedData () {
if (this.props.time) {
this.video.currentTime = this.props.time;
}
},
componentDidMount () {
this.video.addEventListener('loadeddata', this.handleLoadedData);
},
componentWillUnmount () {
this.video.removeEventListener('loadeddata', this.handleLoadedData);
},
setRef (c) {
this.video = c;
},
render () {
return (
<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>
);
},
});
export default ExtendedVideoPlayer;

View File

@@ -12,7 +12,9 @@ const IconButton = React.createClass({
style: React.PropTypes.object,
activeStyle: React.PropTypes.object,
disabled: React.PropTypes.bool,
animate: React.PropTypes.bool
inverted: React.PropTypes.bool,
animate: React.PropTypes.bool,
overlay: React.PropTypes.bool
},
getDefaultProps () {
@@ -20,7 +22,8 @@ const IconButton = React.createClass({
size: 18,
active: false,
disabled: false,
animate: false
animate: false,
overlay: false
};
},
@@ -30,19 +33,15 @@ const IconButton = React.createClass({
e.preventDefault();
if (!this.props.disabled) {
this.props.onClick();
this.props.onClick(e);
}
},
render () {
let style = {
display: 'inline-block',
border: 'none',
padding: '0',
background: 'transparent',
fontSize: `${this.props.size}px`,
width: `${this.props.size * 1.28571429}px`,
height: `${this.props.size}px`,
height: `${this.props.size * 1.28571429}px`,
lineHeight: `${this.props.size}px`,
...this.props.style
};
@@ -51,13 +50,31 @@ const IconButton = React.createClass({
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');
}
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={`icon-button ${this.props.active ? 'active' : ''} ${this.props.disabled ? 'disabled' : ''}`}
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' />

View File

@@ -1,82 +0,0 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import IconButton from './icon_button';
import { Motion, spring } from 'react-motion';
import { injectIntl } from 'react-intl';
const overlayStyle = {
position: 'fixed',
top: '0',
left: '0',
width: '100%',
height: '100%',
background: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
justifyContent: 'center',
alignContent: 'center',
flexDirection: 'row',
zIndex: '9999'
};
const dialogStyle = {
color: '#282c37',
boxShadow: '0 0 30px rgba(0, 0, 0, 0.8)',
margin: 'auto',
position: 'relative'
};
const closeStyle = {
position: 'absolute',
top: '4px',
right: '4px'
};
const Lightbox = React.createClass({
propTypes: {
isVisible: React.PropTypes.bool,
onOverlayClicked: React.PropTypes.func,
onCloseClicked: React.PropTypes.func,
intl: React.PropTypes.object.isRequired,
children: React.PropTypes.node
},
mixins: [PureRenderMixin],
componentDidMount () {
this._listener = e => {
if (this.props.isVisible && e.key === 'Escape') {
this.props.onCloseClicked();
}
};
window.addEventListener('keyup', this._listener);
},
componentWillUnmount () {
window.removeEventListener('keyup', this._listener);
},
stopPropagation (e) {
e.stopPropagation();
},
render () {
const { intl, isVisible, onOverlayClicked, onCloseClicked, children } = this.props;
return (
<Motion defaultStyle={{ backgroundOpacity: 0, opacity: 0, y: -400 }} style={{ backgroundOpacity: spring(isVisible ? 50 : 0), opacity: isVisible ? spring(200) : 0, y: spring(isVisible ? 0 : -400, { stiffness: 150, damping: 12 }) }}>
{({ backgroundOpacity, opacity, y }) =>
<div className='lightbox' style={{...overlayStyle, background: `rgba(0, 0, 0, ${backgroundOpacity / 100})`, display: Math.floor(backgroundOpacity) === 0 ? 'none' : 'flex'}} onClick={onOverlayClicked}>
<div style={{...dialogStyle, transform: `translateY(${y}px)`, opacity: opacity / 100 }} onClick={this.stopPropagation}>
<IconButton title={intl.formatMessage({ id: 'lightbox.close', defaultMessage: 'Close' })} icon='times' onClick={onCloseClicked} size={16} style={closeStyle} />
{children}
</div>
</div>
}
</Motion>
);
}
});
export default injectIntl(Lightbox);

View File

@@ -1,15 +1,7 @@
import { FormattedMessage } from 'react-intl';
const loadMoreStyle = {
display: 'block',
color: '#616b86',
textAlign: 'center',
padding: '15px',
textDecoration: 'none'
};
const LoadMore = ({ onClick }) => (
<a href='#' className='load-more' onClick={onClick} style={loadMoreStyle}>
<a href="#" className='load-more' role='button' onClick={onClick}>
<FormattedMessage id='status.load_more' defaultMessage='Load more' />
</a>
);

View File

@@ -4,12 +4,11 @@ const style = {
textAlign: 'center',
fontSize: '16px',
fontWeight: '500',
color: '#616b86',
paddingTop: '120px'
};
const LoadingIndicator = () => (
<div style={style}>
<div className='loading-indicator' style={style}>
<FormattedMessage id='loading_indicator.label' defaultMessage='Loading...' />
</div>
);

View File

@@ -2,6 +2,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PureRenderMixin from 'react-addons-pure-render-mixin';
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' }
@@ -16,8 +17,6 @@ const outerStyle = {
};
const spoilerStyle = {
background: '#000',
color: '#fff',
textAlign: 'center',
height: '100%',
cursor: 'pointer',
@@ -40,11 +39,146 @@ const spoilerSubSpanStyle = {
const spoilerButtonStyle = {
position: 'absolute',
top: '6px',
left: '8px',
top: '4px',
left: '4px',
zIndex: '100'
};
const itemStyle = {
boxSizing: 'border-box',
position: 'relative',
float: 'left',
border: 'none',
display: 'block'
};
const thumbStyle = {
display: 'block',
width: '100%',
height: '100%',
textDecoration: 'none',
backgroundSize: 'cover',
cursor: 'zoom-in'
};
const gifvThumbStyle = {
position: 'relative',
zIndex: '1',
width: '100%',
height: '100%',
objectFit: 'cover',
top: '50%',
transform: 'translateY(-50%)',
cursor: 'zoom-in'
};
const Item = React.createClass({
propTypes: {
attachment: ImmutablePropTypes.map.isRequired,
index: React.PropTypes.number.isRequired,
size: React.PropTypes.number.isRequired,
onClick: React.PropTypes.func.isRequired
},
mixins: [PureRenderMixin],
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
href={attachment.get('remote_url') ? attachment.get('remote_url') : attachment.get('url')}
onClick={this.handleClick}
target='_blank'
style={{ background: `url(${attachment.get('preview_url')}) no-repeat center`, ...thumbStyle }}
/>
);
} else if (attachment.get('type') === 'gifv') {
thumbnail = (
<video
src={attachment.get('url')}
onClick={this.handleClick}
autoPlay={!isIOS()}
loop={true}
muted={true}
style={gifvThumbStyle}
/>
);
}
return (
<div key={attachment.get('id')} style={{ ...itemStyle, left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
{thumbnail}
</div>
);
}
});
const MediaGallery = React.createClass({
getInitialState () {
@@ -63,17 +197,12 @@ const MediaGallery = React.createClass({
mixins: [PureRenderMixin],
handleClick (index, e) {
if (e.button === 0) {
e.preventDefault();
this.props.onOpenMedia(this.props.media, index);
}
e.stopPropagation();
handleOpen (e) {
this.setState({ visible: !this.state.visible });
},
handleOpen () {
this.setState({ visible: !this.state.visible });
handleClick (index) {
this.props.onOpenMedia(this.props.media, index);
},
render () {
@@ -82,87 +211,31 @@ const MediaGallery = React.createClass({
let children;
if (!this.state.visible) {
let warning;
if (sensitive) {
children = (
<div style={spoilerStyle} onClick={this.handleOpen}>
<span style={spoilerSpanStyle}><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
</div>
);
warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
} else {
children = (
<div style={spoilerStyle} onClick={this.handleOpen}>
<span style={spoilerSpanStyle}><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
</div>
);
warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
}
children = (
<div role='button' tabIndex='0' style={spoilerStyle} className='media-spoiler' onClick={this.handleOpen}>
<span style={spoilerSpanStyle}>{warning}</span>
<span style={spoilerSubSpanStyle}><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) => {
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 && i > 0)) {
height = 50;
}
if (size === 2) {
if (i === 0) {
right = '2px';
} else {
left = '2px';
}
} else if (size === 3) {
if (i === 0) {
right = '2px';
} else if (i > 0) {
left = '2px';
}
if (i === 1) {
bottom = '2px';
} else if (i > 1) {
top = '2px';
}
} else if (size === 4) {
if (i === 0 || i === 2) {
right = '2px';
}
if (i === 1 || i === 3) {
left = '2px';
}
if (i < 2) {
bottom = '2px';
} else {
top = '2px';
}
}
return (
<div key={attachment.get('id')} style={{ boxSizing: 'border-box', position: 'relative', left: left, top: top, right: right, bottom: bottom, float: 'left', border: 'none', display: 'block', width: `${width}%`, height: `${height}%` }}>
<a href={attachment.get('remote_url') ? attachment.get('remote_url') : attachment.get('url')} onClick={this.handleClick.bind(this, i)} target='_blank' style={{ display: 'block', width: '100%', height: '100%', background: `url(${attachment.get('preview_url')}) no-repeat center`, textDecoration: 'none', backgroundSize: 'cover', cursor: 'zoom-in' }} />
</div>
);
});
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
}
return (
<div style={{ ...outerStyle, height: `${this.props.height}px` }}>
<div style={spoilerButtonStyle} >
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleOpen} />
<div style={{ ...spoilerButtonStyle, 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>
);

View File

@@ -1,15 +1,7 @@
import { FormattedMessage } from 'react-intl';
const style = {
textAlign: 'center',
fontSize: '16px',
fontWeight: '500',
color: '#616b86',
paddingTop: '120px'
};
const MissingIndicator = () => (
<div style={style}>
<div className='missing-indicator'>
<FormattedMessage id='missing_indicator.label' defaultMessage='Not found' />
</div>
);

View File

@@ -6,7 +6,8 @@ const Permalink = React.createClass({
propTypes: {
href: React.PropTypes.string.isRequired,
to: React.PropTypes.string.isRequired
to: React.PropTypes.string.isRequired,
children: React.PropTypes.node
},
handleClick (e) {

View File

@@ -9,16 +9,7 @@ import StatusContent from './status_content';
import StatusActionBar from './status_action_bar';
import { FormattedMessage } from 'react-intl';
import emojify from '../emoji';
import escapeTextContentForBrowser from 'react/lib/escapeTextContentForBrowser';
const outerStyle = {
padding: '8px 10px',
paddingLeft: '68px',
position: 'relative',
minHeight: '48px',
borderBottom: '1px solid #363c4b',
cursor: 'default'
};
import escapeTextContentForBrowser from 'escape-html';
const Status = React.createClass({
@@ -34,8 +25,10 @@ const Status = React.createClass({
onReblog: React.PropTypes.func,
onDelete: React.PropTypes.func,
onOpenMedia: React.PropTypes.func,
onOpenVideo: React.PropTypes.func,
onBlock: React.PropTypes.func,
me: React.PropTypes.number,
boostModal: React.PropTypes.bool,
muted: React.PropTypes.bool
},
@@ -55,7 +48,7 @@ const Status = React.createClass({
render () {
let media = '';
const { status, now, ...other } = this.props;
const { status, ...other } = this.props;
if (status === null) {
return <div />;
@@ -72,9 +65,9 @@ const Status = React.createClass({
return (
<div style={{ cursor: 'default' }}>
<div style={{ marginLeft: '68px', color: '#616b86', padding: '8px 0', paddingBottom: '2px', fontSize: '14px', position: 'relative' }}>
<div className='status__prepend'>
<div style={{ position: 'absolute', 'left': '-26px'}}><i className='fa fa-fw fa-retweet' /></div>
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} reblogged' values={{ name: <a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong style={{ color: '#616b86'}} dangerouslySetInnerHTML={displayNameHTML} /></a> }} />
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} reblogged' 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')} />
@@ -84,22 +77,22 @@ const Status = React.createClass({
if (status.get('media_attachments').size > 0 && !this.props.muted) {
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} />;
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} />;
}
}
return (
<div className={this.props.muted ? 'muted' : ''} style={outerStyle}>
<div className={this.props.muted ? 'status muted' : 'status'}>
<div style={{ fontSize: '15px' }}>
<div style={{ float: 'right', fontSize: '14px' }}>
<a href={status.get('url')} className='status__relative-time' style={{ color: '#616b86' }} target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} now={now} /></a>
<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' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', color: '#616b86' }}>
<a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px' }}>
<div className='status__avatar' style={{ position: 'absolute', left: '10px', top: '10px', width: '48px', height: '48px' }}>
<Avatar src={status.getIn(['account', 'avatar'])} size={48} />
<Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} />
</div>
<DisplayName account={status.get('account')} />

View File

@@ -6,12 +6,14 @@ import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
mention: { id: 'status.mention', defaultMessage: 'Mention' },
block: { id: 'account.block', defaultMessage: 'Block' },
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' },
reblog: { id: 'status.reblog', defaultMessage: 'Reblog' },
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
open: { id: 'status.open', defaultMessage: 'Expand' }
open: { id: 'status.open', defaultMessage: 'Expand this status' },
report: { id: 'status.report', defaultMessage: 'Report @{name}' }
});
const StatusActionBar = React.createClass({
@@ -27,7 +29,11 @@ const StatusActionBar = React.createClass({
onReblog: React.PropTypes.func,
onDelete: React.PropTypes.func,
onMention: React.PropTypes.func,
onBlock: React.PropTypes.func
onMute: React.PropTypes.func,
onBlock: React.PropTypes.func,
onReport: React.PropTypes.func,
me: React.PropTypes.number.isRequired,
intl: React.PropTypes.object.isRequired
},
mixins: [PureRenderMixin],
@@ -40,8 +46,8 @@ const StatusActionBar = React.createClass({
this.props.onFavourite(this.props.status);
},
handleReblogClick () {
this.props.onReblog(this.props.status);
handleReblogClick (e) {
this.props.onReblog(this.props.status, e);
},
handleDeleteClick () {
@@ -52,6 +58,10 @@ const StatusActionBar = React.createClass({
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'));
},
@@ -60,23 +70,36 @@ const StatusActionBar = React.createClass({
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;
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), action: this.handleMentionClick });
menu.push({ text: intl.formatMessage(messages.block), action: this.handleBlockClick });
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';
return (
<div style={{ marginTop: '10px', overflow: 'hidden' }}>
<div style={{ float: 'left', marginRight: '18px'}}><IconButton title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReplyClick} /></div>
<div style={{ float: 'left', marginRight: '18px'}}><IconButton disabled={status.get('visibility') === 'private'} active={status.get('reblogged')} title={intl.formatMessage(messages.reblog)} icon={status.get('visibility') === 'private' ? 'lock' : 'retweet'} onClick={this.handleReblogClick} /></div>
<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 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>
<div style={{ width: '18px', height: '18px', float: 'left' }}>

View File

@@ -1,21 +1,11 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import escapeTextContentForBrowser from 'react/lib/escapeTextContentForBrowser';
import escapeTextContentForBrowser from 'escape-html';
import emojify from '../emoji';
import { isRtl } from '../rtl';
import { FormattedMessage } from 'react-intl';
import Permalink from './permalink';
const spoilerStyle = {
display: 'inline-block',
borderRadius: '2px',
color: '#363c4b',
fontWeight: '500',
fontSize: '11px',
padding: '0px 6px',
textTransform: 'uppercase',
lineHeight: 'inherit'
};
const StatusContent = React.createClass({
contextTypes: {
@@ -42,10 +32,11 @@ const StatusContent = React.createClass({
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') || link.href === item.get('remote_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) {
@@ -53,6 +44,7 @@ const StatusContent = React.createClass({
} else {
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener');
link.setAttribute('title', link.href);
}
}
},
@@ -92,7 +84,8 @@ const StatusContent = React.createClass({
this.startXY = null;
},
handleSpoilerClick () {
handleSpoilerClick (e) {
e.preventDefault();
this.setState({ hidden: !this.state.hidden });
},
@@ -102,6 +95,11 @@ const StatusContent = React.createClass({
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 = '';
@@ -121,21 +119,29 @@ const StatusContent = React.createClass({
return (
<div className='status__content' style={{ cursor: 'pointer' }} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
<p style={{ marginBottom: hidden && status.get('mentions').size === 0 ? '0px' : '' }} >
<span dangerouslySetInnerHTML={spoilerContent} /> <a className='status__content__spoiler-link' style={spoilerStyle} onClick={this.handleSpoilerClick}>{toggleText}</a>
<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' }} dangerouslySetInnerHTML={content} />
<div style={{ display: hidden ? 'none' : 'block', ...directionStyle }} dangerouslySetInnerHTML={content} />
</div>
);
} else if (this.props.onClick) {
return (
<div
className='status__content'
style={{ cursor: 'pointer', ...directionStyle }}
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
dangerouslySetInnerHTML={content}
/>
);
} else {
return (
<div
className='status__content'
style={{ cursor: 'pointer' }}
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
style={{ ...directionStyle }}
dangerouslySetInnerHTML={content}
/>
);

View File

@@ -14,7 +14,10 @@ const StatusList = React.createClass({
onScroll: React.PropTypes.func,
trackScroll: React.PropTypes.bool,
isLoading: React.PropTypes.bool,
prepend: React.PropTypes.node
isUnread: React.PropTypes.bool,
hasMore: React.PropTypes.bool,
prepend: React.PropTypes.node,
emptyMessage: React.PropTypes.node
},
getDefaultProps () {
@@ -71,27 +74,43 @@ const StatusList = React.createClass({
},
render () {
const { statusIds, onScrollToBottom, trackScroll, isLoading, prepend } = this.props;
const { statusIds, onScrollToBottom, trackScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props;
let loadMore = '';
let loadMore = '';
let scrollableArea = '';
let unread = '';
if (!isLoading && statusIds.size > 0) {
if (!isLoading && statusIds.size > 0 && hasMore) {
loadMore = <LoadMore onClick={this.handleLoadMore} />;
}
const scrollableArea = (
<div className='scrollable' ref={this.setRef}>
<div>
{prepend}
if (isUnread) {
unread = <div className='status-list__unread-indicator' />;
}
{statusIds.map((statusId) => {
return <StatusContainer key={statusId} id={statusId} />;
})}
if (isLoading || statusIds.size > 0 || !emptyMessage) {
scrollableArea = (
<div className='scrollable' ref={this.setRef}>
{unread}
{loadMore}
<div>
{prepend}
{statusIds.map((statusId) => {
return <StatusContainer key={statusId} id={statusId} />;
})}
{loadMore}
</div>
</div>
</div>
);
);
} else {
scrollableArea = (
<div className='empty-column-indicator' ref={this.setRef}>
{emptyMessage}
</div>
);
}
if (trackScroll) {
return (

View File

@@ -2,10 +2,13 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PureRenderMixin from 'react-addons-pure-render-mixin';
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' }
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' }
});
const videoStyle = {
@@ -20,16 +23,16 @@ const videoStyle = {
const muteStyle = {
position: 'absolute',
top: '10px',
right: '10px',
top: '4px',
right: '4px',
color: 'white',
textShadow: "0px 1px 1px black, 1px 0px 1px black",
opacity: '0.8',
zIndex: '5'
};
const spoilerStyle = {
const coverStyle = {
marginTop: '8px',
background: '#000',
color: '#fff',
textAlign: 'center',
height: '100%',
cursor: 'pointer',
@@ -53,8 +56,19 @@ const spoilerSubSpanStyle = {
const spoilerButtonStyle = {
position: 'absolute',
top: '6px',
left: '8px',
top: '4px',
left: '4px',
color: 'white',
textShadow: "0px 1px 1px black, 1px 0px 1px black",
zIndex: '100'
};
const expandButtonStyle = {
position: 'absolute',
bottom: '4px',
right: '4px',
color: 'white',
textShadow: "0px 1px 1px black, 1px 0px 1px black",
zIndex: '100'
};
@@ -63,12 +77,15 @@ const VideoPlayer = React.createClass({
media: ImmutablePropTypes.map.isRequired,
width: React.PropTypes.number,
height: React.PropTypes.number,
sensitive: React.PropTypes.bool
sensitive: React.PropTypes.bool,
intl: React.PropTypes.object.isRequired,
autoplay: React.PropTypes.bool,
onOpenVideo: React.PropTypes.func.isRequired
},
getDefaultProps () {
return {
width: 196,
width: 239,
height: 110
};
},
@@ -77,7 +94,9 @@ const VideoPlayer = React.createClass({
return {
visible: !this.props.sensitive,
preview: true,
muted: true
muted: true,
hasAudio: true,
videoError: false
};
},
@@ -110,19 +129,81 @@ const VideoPlayer = React.createClass({
});
},
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 } = this.props;
const { media, intl, width, height, sensitive, autoplay } = this.props;
let spoilerButton = (
<div style={spoilerButtonStyle} >
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} />
<div style={{...spoilerButtonStyle, 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 style={expandButtonStyle} >
<IconButton overlay title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} />
</div>
);
let muteButton = '';
if (this.state.hasAudio) {
muteButton = (
<div style={muteStyle}>
<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 style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} onClick={this.handleVisibility}>
<div role='button' tabIndex='0' style={{...coverStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
{spoilerButton}
<span style={spoilerSpanStyle}><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
@@ -130,7 +211,7 @@ const VideoPlayer = React.createClass({
);
} else {
return (
<div style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} onClick={this.handleOpen}>
<div role='button' tabIndex='0' style={{...coverStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
{spoilerButton}
<span style={spoilerSpanStyle}><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
@@ -139,20 +220,29 @@ const VideoPlayer = React.createClass({
}
}
if (this.state.preview) {
if (this.state.preview && !autoplay) {
return (
<div style={{ cursor: 'pointer', position: 'relative', marginTop: '8px', width: `${width}px`, height: `${height}px`, background: `url(${media.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }} onClick={this.handleOpen}>
<div role='button' tabIndex='0' style={{ cursor: 'pointer', position: 'relative', marginTop: '8px', width: `${width}px`, height: `${height}px`, background: `url(${media.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }} onClick={this.handleOpen}>
{spoilerButton}
<div style={{ position: 'absolute', top: '50%', left: '50%', fontSize: '36px', transform: 'translate(-50%, -50%)', padding: '5px', borderRadius: '100px', color: 'rgba(255, 255, 255, 0.8)' }}><i className='fa fa-play' /></div>
</div>
);
}
if (this.state.videoError) {
return (
<div style={{...coverStyle, width: `${width}px`, height: `${height}px` }} className='video-error-cover' >
<span style={spoilerSpanStyle}><FormattedMessage id='video_player.video_error' defaultMessage='Video could not be played' /></span>
</div>
);
}
return (
<div style={{ cursor: 'default', marginTop: '8px', overflow: 'hidden', width: `${width}px`, height: `${height}px`, boxSizing: 'border-box', background: '#000', position: 'relative' }}>
{spoilerButton}
<div style={muteStyle}><IconButton title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} /></div>
<video src={media.get('url')} autoPlay='true' loop={true} muted={this.state.muted} style={videoStyle} onClick={this.handleVideoClick} />
{muteButton}
{expandButton}
<video role='button' tabIndex='0' ref={this.setRef} src={media.get('url')} autoPlay={!isIOS()} loop={true} muted={this.state.muted} style={videoStyle} onClick={this.handleVideoClick} />
</div>
);
}

View File

@@ -5,7 +5,9 @@ import {
followAccount,
unfollowAccount,
blockAccount,
unblockAccount
unblockAccount,
muteAccount,
unmuteAccount,
} from '../actions/accounts';
const makeMapStateToProps = () => {
@@ -34,6 +36,14 @@ const mapDispatchToProps = (dispatch) => ({
} else {
dispatch(blockAccount(account.get('id')));
}
},
onMute (account) {
if (account.getIn(['relationship', 'muting'])) {
dispatch(unmuteAccount(account.get('id')));
} else {
dispatch(muteAccount(account.get('id')));
}
}
});

View File

@@ -4,9 +4,12 @@ import {
refreshTimelineSuccess,
updateTimeline,
deleteFromTimelines,
refreshTimeline
refreshTimeline,
connectTimeline,
disconnectTimeline
} from '../actions/timelines';
import { updateNotifications } from '../actions/notifications';
import { showOnboardingOnce } from '../actions/onboarding';
import { updateNotifications, refreshNotifications } from '../actions/notifications';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import {
applyRouterMiddleware,
@@ -21,6 +24,7 @@ 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';
@@ -34,27 +38,55 @@ 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 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 fi from 'react-intl/locale-data/fi';
import fr from 'react-intl/locale-data/fr';
import pt from 'react-intl/locale-data/pt';
import hu from 'react-intl/locale-data/hu';
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 { localeData as zh_hk } from '../locales/zh-hk';
import getMessagesForLocale from '../locales';
import { hydrateStore } from '../actions/store';
import createStream from '../stream';
const store = configureStore();
store.dispatch(hydrateStore(window.INITIAL_STATE));
const initialState = JSON.parse(document.getElementById("initial-state").textContent);
store.dispatch(hydrateStore(initialState));
const browserHistory = useRouterHistory(createBrowserHistory)({
basename: '/web'
});
addLocaleData([...en, ...de, ...es, ...fr, ...pt, ...hu, ...uk]);
addLocaleData([
...en,
...de,
...eo,
...es,
...fi,
...fr,
...hu,
...ja,
...pt,
...nl,
...no,
...ru,
...uk,
...zh,
...zh_hk,
...bg,
]);
const Mastodon = React.createClass({
@@ -64,9 +96,18 @@ const Mastodon = React.createClass({
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(accessToken, 'user', {
this.subscription = createStream(streamingAPIBaseURL, accessToken, 'user', {
connected () {
store.dispatch(connectTimeline('home'));
},
disconnected () {
store.dispatch(disconnectTimeline('home'));
},
received (data) {
switch(data.event) {
@@ -80,6 +121,12 @@ const Mastodon = React.createClass({
store.dispatch(updateNotifications(JSON.parse(data.payload), getMessagesForLocale(locale), locale));
break;
}
},
reconnected () {
store.dispatch(connectTimeline('home'));
store.dispatch(refreshTimeline('home'));
store.dispatch(refreshNotifications());
}
});
@@ -88,6 +135,8 @@ const Mastodon = React.createClass({
if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
Notification.requestPermission();
}
store.dispatch(showOnboardingOnce());
},
componentWillUnmount () {
@@ -110,6 +159,7 @@ const Mastodon = React.createClass({
<Route path='getting-started' component={GettingStarted} />
<Route path='timelines/home' component={HomeTimeline} />
<Route path='timelines/public' component={PublicTimeline} />
<Route path='timelines/public/local' component={CommunityTimeline} />
<Route path='timelines/tag/:id' component={HashtagTimeline} />
<Route path='notifications' component={Notifications} />
@@ -126,6 +176,8 @@ const Mastodon = React.createClass({
<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>

View File

@@ -11,51 +11,23 @@ import {
unreblog,
unfavourite
} from '../actions/interactions';
import { blockAccount } from '../actions/accounts';
import {
blockAccount,
muteAccount
} from '../actions/accounts';
import { deleteStatus } from '../actions/statuses';
import { openMedia } from '../actions/modal';
import { initReport } from '../actions/reports';
import { openModal } from '../actions/modal';
import { createSelector } from 'reselect'
import { isMobile } from '../is_mobile'
const mapStateToProps = (state, props) => ({
statusBase: state.getIn(['statuses', props.id]),
me: state.getIn(['meta', 'me'])
});
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const makeMapStateToPropsInner = () => {
const getStatus = (() => {
return createSelector(
[
(_, base) => base,
(state, base) => (base ? state.getIn(['accounts', base.get('account')]) : null),
(state, base) => (base ? state.getIn(['statuses', base.get('reblog')], null) : null)
],
(base, account, reblog) => (base ? base.set('account', account).set('reblog', reblog) : null)
);
})();
const mapStateToProps = (state, { statusBase }) => ({
status: getStatus(state, statusBase)
});
return mapStateToProps;
};
const makeMapStateToPropsLast = () => {
const getStatus = (() => {
return createSelector(
[
(_, status) => status,
(state, status) => (status ? state.getIn(['accounts', status.getIn(['reblog', 'account'])], null) : null)
],
(status, reblogAccount) => (status && status.get('reblog') ? status.setIn(['reblog', 'account'], reblogAccount) : status)
);
})();
const mapStateToProps = (state, { status }) => ({
status: getStatus(state, status)
const mapStateToProps = (state, props) => ({
status: getStatus(state, props.id),
me: state.getIn(['meta', 'me']),
boostModal: state.getIn(['meta', 'boost_modal'])
});
return mapStateToProps;
@@ -67,11 +39,19 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(replyCompose(status, router));
},
onReblog (status) {
onModalReblog (status) {
dispatch(reblog(status));
},
onReblog (status, e) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
} else {
dispatch(reblog(status));
if (e.shiftKey || !this.boostModal) {
this.onModalReblog(status);
} else {
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
}
}
},
@@ -92,17 +72,25 @@ const mapDispatchToProps = (dispatch) => ({
},
onOpenMedia (media, index) {
dispatch(openMedia(media, index));
dispatch(openModal('MEDIA', { media, index }));
},
onOpenVideo (media, time) {
dispatch(openModal('VIDEO', { media, time }));
},
onBlock (account) {
dispatch(blockAccount(account.get('id')));
}
},
onReport (status) {
dispatch(initReport(status.get('account'), status));
},
onMute (account) {
dispatch(muteAccount(account.get('id')));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(
connect(makeMapStateToPropsInner)(
connect(makeMapStateToPropsLast)(Status)
)
);
export default connect(makeMapStateToProps, mapDispatchToProps)(Status);

View File

@@ -1,9 +1,35 @@
import emojione from 'emojione';
emojione.imageType = 'png';
emojione.sprites = false;
emojione.imagePathPNG = '/emoji/';
const toImage = str => shortnameToImage(unicodeToImage(str));
const unicodeToImage = str => {
const mappedUnicode = emojione.mapUnicodeToShort();
return str.replace(emojione.regUnicode, unicodeChar => {
if (typeof unicodeChar === 'undefined' || unicodeChar === '' || !(unicodeChar in emojione.jsEscapeMap)) {
return unicodeChar;
}
const unicode = emojione.jsEscapeMap[unicodeChar];
const short = mappedUnicode[unicode];
const filename = emojione.emojioneList[short].fname;
const alt = emojione.convert(unicode.toUpperCase());
return `<img draggable="false" class="emojione" alt="${alt}" src="/emoji/${filename}.svg" />`;
});
};
const shortnameToImage = str => str.replace(emojione.regShortNames, shortname => {
if (typeof shortname === 'undefined' || shortname === '' || !(shortname in emojione.emojioneList)) {
return shortname;
}
const unicode = emojione.emojioneList[shortname].unicode[emojione.emojioneList[shortname].unicode.length - 1];
const alt = emojione.convert(unicode.toUpperCase());
return `<img draggable="false" class="emojione" alt="${alt}" src="/emoji/${unicode}.svg" />`;
});
export default function emojify(text) {
return emojione.toImage(text);
return toImage(text);
};

View File

@@ -5,24 +5,18 @@ import { Link } from 'react-router';
import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
const messages = defineMessages({
mention: { id: 'account.mention', defaultMessage: 'Mention' },
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
block: { id: 'account.block', defaultMessage: 'Block' },
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' },
block: { id: 'account.block', defaultMessage: 'Block' }
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' }
});
const outerStyle = {
borderTop: '1px solid #363c4b',
borderBottom: '1px solid #363c4b',
lineHeight: '36px',
overflow: 'hidden',
flex: '0 0 auto',
display: 'flex'
};
const outerDropdownStyle = {
padding: '10px',
flex: '1 1 auto'
@@ -41,7 +35,10 @@ const ActionBar = React.createClass({
me: React.PropTypes.number.isRequired,
onFollow: React.PropTypes.func,
onBlock: React.PropTypes.func.isRequired,
onMention: React.PropTypes.func.isRequired
onMention: React.PropTypes.func.isRequired,
onReport: React.PropTypes.func.isRequired,
onMute: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
},
mixins: [PureRenderMixin],
@@ -50,39 +47,53 @@ const ActionBar = React.createClass({
const { account, me, intl } = this.props;
let menu = [];
let extraInfo = '';
menu.push({ text: intl.formatMessage(messages.mention), action: this.props.onMention });
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', 'blocking'])) {
menu.push({ text: intl.formatMessage(messages.unblock), action: this.props.onBlock });
} else if (account.getIn(['relationship', 'following'])) {
menu.push({ text: intl.formatMessage(messages.block), action: this.props.onBlock });
} else {
menu.push({ text: intl.formatMessage(messages.block), action: this.props.onBlock });
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 style={outerStyle}>
<div className='account__action-bar'>
<div style={outerDropdownStyle}>
<DropdownMenu items={menu} icon='bars' size={24} direction="right" />
</div>
<div style={outerLinksStyle}>
<Link to={`/accounts/${account.get('id')}`} style={{ textDecoration: 'none', overflow: 'hidden', width: '80px', borderLeft: '1px solid #363c4b', padding: '10px', paddingRight: '5px' }}>
<span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}><FormattedMessage id='account.posts' defaultMessage='Posts' /></span>
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}><FormattedNumber value={account.get('statuses_count')} /></span>
<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 to={`/accounts/${account.get('id')}/following`} style={{ textDecoration: 'none', overflow: 'hidden', width: '80px', borderLeft: '1px solid #363c4b', padding: '10px 5px' }}>
<span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}><FormattedMessage id='account.follows' defaultMessage='Follows' /></span>
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}><FormattedNumber value={account.get('following_count')} /></span>
<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 to={`/accounts/${account.get('id')}/followers`} style={{ textDecoration: 'none', overflow: 'hidden', width: '80px', padding: '10px 5px', borderLeft: '1px solid #363c4b' }}>
<span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}><FormattedMessage id='account.followers' defaultMessage='Followers' /></span>
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}><FormattedNumber value={account.get('followers_count')} /></span>
<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>

View File

@@ -1,9 +1,10 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
import emojify from '../../../emoji';
import escapeTextContentForBrowser from 'react/lib/escapeTextContentForBrowser';
import escapeTextContentForBrowser from 'escape-html';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from '../../../components/icon_button';
import { Motion, spring } from 'react-motion';
const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@@ -11,12 +12,63 @@ const messages = defineMessages({
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }
});
const Avatar = React.createClass({
propTypes: {
account: ImmutablePropTypes.map.isRequired
},
getInitialState () {
return {
isHovered: false
};
},
mixins: [PureRenderMixin],
handleMouseOver () {
if (this.state.isHovered) return;
this.setState({ isHovered: true });
},
handleMouseOut () {
if (!this.state.isHovered) return;
this.setState({ isHovered: false });
},
render () {
const { account } = 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={{ display: 'block', width: '90px', height: '90px', margin: '0 auto', marginBottom: '10px', borderRadius: `${radius}px`, overflow: 'hidden' }}
onMouseOver={this.handleMouseOver}
onMouseOut={this.handleMouseOut}
onFocus={this.handleMouseOver}
onBlur={this.handleMouseOut}>
<img src={account.get('avatar')} alt={account.get('acct')} style={{ display: 'block', width: '90px', height: '90px' }} />
</a>
}
</Motion>
);
}
});
const Header = React.createClass({
propTypes: {
account: ImmutablePropTypes.map.isRequired,
account: ImmutablePropTypes.map,
me: React.PropTypes.number.isRequired,
onFollow: React.PropTypes.func.isRequired
onFollow: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
},
mixins: [PureRenderMixin],
@@ -24,6 +76,10 @@ const Header = React.createClass({
render () {
const { account, me, intl } = this.props;
if (!account) {
return null;
}
let displayName = account.get('display_name');
let info = '';
let actionBtn = '';
@@ -34,7 +90,7 @@ const Header = React.createClass({
}
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
info = <span style={{ position: 'absolute', top: '10px', right: '10px', opacity: '0.7', display: 'inline-block', verticalAlign: 'top', background: 'rgba(0, 0, 0, 0.4)', color: '#fff', textTransform: 'uppercase', fontSize: '11px', fontWeight: '500', padding: '4px', borderRadius: '4px' }}><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>
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')) {
@@ -44,7 +100,7 @@ const Header = React.createClass({
<IconButton size={26} disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} />
</div>
);
} else {
} 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} />
@@ -61,18 +117,13 @@ const Header = React.createClass({
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
return (
<div className='account__header' style={{ flex: '0 0 auto', background: '#2f3441', textAlign: 'center', backgroundImage: `url(${account.get('header')})`, backgroundSize: 'cover', backgroundPosition: 'center', position: 'relative' }}>
<div style={{ background: 'rgba(47, 52, 65, 0.9)', padding: '20px 10px' }}>
<a href={account.get('url')} target='_blank' rel='noopener' style={{ display: 'block', color: 'inherit', textDecoration: 'none' }}>
<div className='account__header__avatar' style={{ width: '90px', margin: '0 auto', marginBottom: '10px' }}>
<img src={account.get('avatar')} alt='' style={{ display: 'block', width: '90px', height: '90px', borderRadius: '90px' }} />
</div>
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
<div style={{ padding: '20px 10px' }}>
<Avatar account={account} />
<span style={{ display: 'inline-block', color: '#fff', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }} className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} />
</a>
<span style={{ fontSize: '14px', fontWeight: '400', display: 'block', color: '#489fde', marginBottom: '10px' }}>@{account.get('acct')} {lockedIcon}</span>
<div style={{ color: '#d9e1e8', fontSize: '14px' }} className='account__header__content' dangerouslySetInnerHTML={content} />
<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}

View File

@@ -2,6 +2,7 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
import InnerHeader from '../../account/components/header';
import ActionBar from '../../account/components/action_bar';
import MissingIndicator from '../../../components/missing_indicator';
const Header = React.createClass({
contextTypes: {
@@ -9,11 +10,13 @@ const Header = React.createClass({
},
propTypes: {
account: ImmutablePropTypes.map.isRequired,
account: ImmutablePropTypes.map,
me: React.PropTypes.number.isRequired,
onFollow: React.PropTypes.func.isRequired,
onBlock: React.PropTypes.func.isRequired,
onMention: React.PropTypes.func.isRequired
onMention: React.PropTypes.func.isRequired,
onReport: React.PropTypes.func.isRequired,
onMute: React.PropTypes.func.isRequired
},
mixins: [PureRenderMixin],
@@ -30,11 +33,20 @@ const Header = React.createClass({
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) {
return null;
if (account === null) {
return <MissingIndicator />;
}
return (
@@ -50,6 +62,8 @@ const Header = React.createClass({
me={me}
onBlock={this.handleBlock}
onMention={this.handleMention}
onReport={this.handleReport}
onMute={this.handleMute}
/>
</div>
);

View File

@@ -5,9 +5,12 @@ import {
followAccount,
unfollowAccount,
blockAccount,
unblockAccount
unblockAccount,
muteAccount,
unmuteAccount
} from '../../../actions/accounts';
import { mentionCompose } from '../../../actions/compose';
import { initReport } from '../../../actions/reports';
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
@@ -39,6 +42,18 @@ const mapDispatchToProps = dispatch => ({
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(muteAccount(account.get('id')));
}
}
});

View File

@@ -16,6 +16,7 @@ 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'])
});
@@ -26,6 +27,7 @@ const AccountTimeline = React.createClass({
dispatch: React.PropTypes.func.isRequired,
statusIds: ImmutablePropTypes.list,
isLoading: React.PropTypes.bool,
hasMore: React.PropTypes.bool,
me: React.PropTypes.number.isRequired
},
@@ -48,7 +50,7 @@ const AccountTimeline = React.createClass({
},
render () {
const { statusIds, isLoading, me } = this.props;
const { statusIds, isLoading, hasMore, me } = this.props;
if (!statusIds && isLoading) {
return (
@@ -66,6 +68,7 @@ const AccountTimeline = React.createClass({
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
statusIds={statusIds}
isLoading={isLoading}
hasMore={hasMore}
me={me}
onScrollToBottom={this.handleScrollToBottom}
/>

View File

@@ -0,0 +1,97 @@
import { connect } from 'react-redux';
import PureRenderMixin from 'react-addons-pure-render-mixin';
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;
const CommunityTimeline = React.createClass({
propTypes: {
dispatch: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired,
streamingAPIBaseURL: React.PropTypes.string.isRequired,
accessToken: React.PropTypes.string.isRequired,
hasUnread: React.PropTypes.bool
},
mixins: [PureRenderMixin],
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 type='community' emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} />
</Column>
);
},
});
export default connect(mapStateToProps)(injectIntl(CommunityTimeline));

View File

@@ -1,11 +1,16 @@
import Avatar from '../../../components/avatar';
import DisplayName from '../../../components/display_name';
import ImmutablePropTypes from 'react-immutable-proptypes';
const AutosuggestAccount = ({ account }) => (
<div style={{ overflow: 'hidden' }}>
<div style={{ float: 'left', marginRight: '5px' }}><Avatar src={account.get('avatar')} size={18} /></div>
<div style={{ overflow: 'hidden' }} className='autosuggest-account'>
<div style={{ float: 'left', marginRight: '5px' }}><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;

View File

@@ -0,0 +1,15 @@
import { FormattedMessage } from 'react-intl';
import DisplayName from '../../../components/display_name';
import ImmutablePropTypes from 'react-immutable-proptypes';
const AutosuggestStatus = ({ status }) => (
<div style={{ overflow: 'hidden' }} 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;

View File

@@ -10,7 +10,7 @@ const CharacterCounter = React.createClass({
mixins: [PureRenderMixin],
render () {
const diff = this.props.max - this.props.text.length;
const diff = this.props.max - this.props.text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length;
return (
<span style={{ fontSize: '16px', cursor: 'default' }}>

View File

@@ -2,20 +2,24 @@ import CharacterCounter from './character_counter';
import Button from '../../../components/button';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ReplyIndicator from './reply_indicator';
import UploadButton from './upload_button';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import AutosuggestAccountContainer from '../../compose/containers/autosuggest_account_container';
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 { Motion, spring } from 'react-motion';
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';
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: 'Publish' }
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' }
});
const ComposeForm = React.createClass({
@@ -25,28 +29,24 @@ const ComposeForm = React.createClass({
text: React.PropTypes.string.isRequired,
suggestion_token: React.PropTypes.string,
suggestions: ImmutablePropTypes.list,
sensitive: React.PropTypes.bool,
spoiler: React.PropTypes.bool,
privacy: React.PropTypes.string,
spoiler_text: React.PropTypes.string,
unlisted: React.PropTypes.bool,
private: React.PropTypes.bool,
fileDropDate: React.PropTypes.instanceOf(Date),
focusDate: React.PropTypes.instanceOf(Date),
preselectDate: React.PropTypes.instanceOf(Date),
is_submitting: React.PropTypes.bool,
is_uploading: React.PropTypes.bool,
in_reply_to: ImmutablePropTypes.map,
media_count: React.PropTypes.number,
me: React.PropTypes.number,
needsPrivacyWarning: React.PropTypes.bool,
mentionedDomains: React.PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired,
onSubmit: React.PropTypes.func.isRequired,
onCancelReply: React.PropTypes.func.isRequired,
onClearSuggestions: React.PropTypes.func.isRequired,
onFetchSuggestions: React.PropTypes.func.isRequired,
onSuggestionSelected: React.PropTypes.func.isRequired,
onChangeSensitivity: React.PropTypes.func.isRequired,
onChangeSpoilerness: React.PropTypes.func.isRequired,
onChangeSpoilerText: React.PropTypes.func.isRequired,
onChangeVisibility: React.PropTypes.func.isRequired,
onChangeListability: React.PropTypes.func.isRequired,
onPaste: React.PropTypes.func.isRequired,
onPickEmoji: React.PropTypes.func.isRequired
},
mixins: [PureRenderMixin],
@@ -75,37 +75,43 @@ const ComposeForm = React.createClass({
},
onSuggestionSelected (tokenStart, token, value) {
this._restoreCaret = null;
this.props.onSuggestionSelected(tokenStart, token, value);
},
handleChangeSensitivity (e) {
this.props.onChangeSensitivity(e.target.checked);
},
handleChangeSpoilerness (e) {
this.props.onChangeSpoilerness(e.target.checked);
this.props.onChangeSpoilerText('');
},
handleChangeSpoilerText (e) {
this.props.onChangeSpoilerText(e.target.value);
},
handleChangeVisibility (e) {
this.props.onChangeVisibility(e.target.checked);
},
handleChangeListability (e) {
this.props.onChangeListability(e.target.checked);
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) {
if ((prevProps.in_reply_to === null && this.props.in_reply_to !== null) || (prevProps.in_reply_to !== null && this.props.in_reply_to !== null && prevProps.in_reply_to.get('id') !== this.props.in_reply_to.get('id'))) {
// If replying to zero or one users, places the cursor at the end of the textbox.
// If replying to more than one user, selects any usernames past the first;
// this provides a convenient shortcut to drop everyone else from the conversation.
const selectionStart = this.props.text.search(/\s/) + 1;
const selectionEnd = this.props.text.length;
// 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();
@@ -116,76 +122,85 @@ const ComposeForm = React.createClass({
this.autosuggestTextarea = c;
},
render () {
const { intl } = this.props;
let replyArea = '';
const disabled = this.props.is_submitting || this.props.is_uploading;
handleEmojiPick (data) {
const position = this.autosuggestTextarea.textarea.selectionStart;
this._restoreCaret = position + data.shortname.length + 1;
this.props.onPickEmoji(position, data);
},
if (this.props.in_reply_to) {
replyArea = <ReplyIndicator status={this.props.in_reply_to} onCancel={this.props.onCancelReply} />;
render () {
const { intl, needsPrivacyWarning, mentionedDomains, onPaste } = this.props;
const disabled = this.props.is_submitting;
let publishText = '';
let privacyWarning = '';
let reply_to_other = false;
if (needsPrivacyWarning) {
privacyWarning = (
<div className='compose-form__warning'>
<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}} to not leak your status?'
values={{ domains: <strong>{mentionedDomains.join(', ')}</strong>, domainsCount: mentionedDomains.length }}
/>
</div>
);
}
let reply_to_other = !!this.props.in_reply_to && (this.props.in_reply_to.getIn(['account', 'id']) !== this.props.me);
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
publishText = <span><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
} else {
publishText = intl.formatMessage(messages.publish) + (this.props.privacy !== 'unlisted' ? '!' : '');
}
return (
<div style={{ padding: '10px' }}>
<Motion defaultStyle={{ opacity: !this.props.spoiler ? 0 : 100, height: !this.props.spoiler ? 50 : 0 }} style={{ opacity: spring(!this.props.spoiler ? 0 : 100), height: spring(!this.props.spoiler ? 0 : 50) }}>
{({ opacity, height }) =>
<div className="spoiler-input" style={{ height: `${height}px`, overflow: 'hidden', opacity: opacity / 100 }}>
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} type="text" className="spoiler-input__input" />
</div>
}
</Motion>
<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" />
</div>
</Collapsable>
{replyArea}
{privacyWarning}
<AutosuggestTextarea
ref={this.setAutosuggestTextarea}
placeholder={intl.formatMessage(messages.placeholder)}
disabled={disabled}
fileDropDate={this.props.fileDropDate}
value={this.props.text}
onChange={this.handleChange}
suggestions={this.props.suggestions}
onKeyDown={this.handleKeyDown}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
/>
<ReplyIndicatorContainer />
<div style={{ marginTop: '10px', overflow: 'hidden' }}>
<div style={{ float: 'right' }}><Button text={intl.formatMessage(messages.publish)} onClick={this.handleSubmit} disabled={disabled} /></div>
<div style={{ float: 'right', marginRight: '16px', lineHeight: '36px' }}><CharacterCounter max={500} text={[this.props.spoiler_text, this.props.text].join('')} /></div>
<UploadButtonContainer style={{ paddingTop: '4px' }} />
<div style={{ position: 'relative' }}>
<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>
<label style={{ display: 'block', lineHeight: '24px', verticalAlign: 'middle', marginTop: '10px', borderTop: '1px solid #282c37', paddingTop: '10px' }}>
<Toggle checked={this.props.spoiler} onChange={this.handleChangeSpoilerness} />
<span style={{ display: 'inline-block', verticalAlign: 'top', marginLeft: '8px', color: '#9baec8' }}><FormattedMessage id='compose_form.spoiler' defaultMessage='Hide text behind warning' /></span>
</label>
<div className='compose-form__modifiers'>
<UploadFormContainer />
</div>
<label style={{ display: 'block', lineHeight: '24px', verticalAlign: 'middle', borderTop: '1px solid #282c37', paddingTop: '10px' }}>
<Toggle checked={this.props.private} onChange={this.handleChangeVisibility} />
<span style={{ display: 'inline-block', verticalAlign: 'middle', marginBottom: '14px', marginLeft: '8px', color: '#9baec8' }}><FormattedMessage id='compose_form.private' defaultMessage='Mark as private' /></span>
</label>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div className='compose-form__buttons'>
<UploadButtonContainer />
<PrivacyDropdownContainer />
<SensitiveButtonContainer />
<SpoilerButtonContainer />
</div>
<Motion defaultStyle={{ opacity: (this.props.private || reply_to_other) ? 0 : 100, height: (this.props.private || reply_to_other) ? 39.5 : 0 }} style={{ opacity: spring((this.props.private || reply_to_other) ? 0 : 100), height: spring((this.props.private || reply_to_other) ? 0 : 39.5) }}>
{({ opacity, height }) =>
<label style={{ display: 'block', lineHeight: '24px', verticalAlign: 'middle', height: `${height}px`, overflow: 'hidden', opacity: opacity / 100 }}>
<Toggle checked={this.props.unlisted} onChange={this.handleChangeListability} />
<span style={{ display: 'inline-block', verticalAlign: 'middle', marginBottom: '14px', marginLeft: '8px', color: '#9baec8' }}><FormattedMessage id='compose_form.unlisted' defaultMessage='Do not display in public timeline' /></span>
</label>
}
</Motion>
<Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(this.props.media_count === 0 ? 0 : 100), height: spring(this.props.media_count === 0 ? 0 : 39.5) }}>
{({ opacity, height }) =>
<label style={{ display: 'block', lineHeight: '24px', verticalAlign: 'middle', height: `${height}px`, overflow: 'hidden', opacity: opacity / 100 }}>
<Toggle checked={this.props.sensitive} onChange={this.handleChangeSensitivity} />
<span style={{ display: 'inline-block', verticalAlign: 'middle', marginBottom: '14px', marginLeft: '8px', color: '#9baec8' }}><FormattedMessage id='compose_form.sensitive' defaultMessage='Mark media as sensitive' /></span>
</label>
}
</Motion>
<div style={{ display: 'flex' }}>
<div style={{ paddingTop: '10px', marginRight: '16px', lineHeight: '36px' }}><CharacterCounter max={500} text={[this.props.spoiler_text, this.props.text].join('')} /></div>
<div style={{ paddingTop: '10px' }}><Button text={publishText} onClick={this.handleSubmit} disabled={disabled} /></div>
</div>
</div>
</div>
);
}

View File

@@ -1,75 +0,0 @@
import { Link } from 'react-router';
import { injectIntl, defineMessages } from 'react-intl';
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Public timeline' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }
});
const outerStyle = {
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'column',
overflowY: 'hidden'
};
const innerStyle = {
boxSizing: 'border-box',
padding: '0',
display: 'flex',
flexDirection: 'column',
overflowY: 'auto',
flexGrow: '1'
};
const tabStyle = {
display: 'block',
flex: '1 1 auto',
padding: '15px',
paddingBottom: '13px',
color: '#9baec8',
textDecoration: 'none',
textAlign: 'center',
fontSize: '16px',
borderBottom: '2px solid transparent'
};
const tabActiveStyle = {
color: '#2b90d9',
borderBottom: '2px solid #2b90d9'
};
const Drawer = ({ children, withHeader, intl }) => {
let header = '';
if (withHeader) {
header = (
<div className='drawer__header'>
<Link title={intl.formatMessage(messages.start)} style={tabStyle} to='/getting-started'><i className='fa fa-fw fa-asterisk' /></Link>
<Link title={intl.formatMessage(messages.public)} style={tabStyle} to='/timelines/public'><i className='fa fa-fw fa-globe' /></Link>
<a title={intl.formatMessage(messages.preferences)} style={tabStyle} href='/settings/preferences'><i className='fa fa-fw fa-cog' /></a>
<a title={intl.formatMessage(messages.logout)} style={tabStyle} href='/auth/sign_out' data-method='delete'><i className='fa fa-fw fa-sign-out' /></a>
</div>
);
}
return (
<div className='drawer' style={outerStyle}>
{header}
<div className='drawer__inner' style={innerStyle}>
{children}
</div>
</div>
);
};
Drawer.propTypes = {
withHeader: React.PropTypes.bool,
children: React.PropTypes.node,
intl: React.PropTypes.object
};
export default injectIntl(Drawer);

View File

@@ -0,0 +1,58 @@
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
import EmojiPicker from 'emojione-picker';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }
});
const settings = {
imageType: 'png',
sprites: false,
imagePathPNG: '/emoji/'
};
const style = {
position: 'absolute',
right: '5px',
top: '5px'
};
const EmojiPickerDropdown = React.createClass({
propTypes: {
intl: React.PropTypes.object.isRequired,
onPickEmoji: React.PropTypes.func.isRequired
},
mixins: [PureRenderMixin],
setRef (c) {
this.dropdown = c;
},
handleChange (data) {
this.dropdown.hide();
this.props.onPickEmoji(data);
},
render () {
const { intl } = this.props;
return (
<Dropdown ref={this.setRef} style={style}>
<DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)} style={{ fontSize: `24px`, width: `24px`, lineHeight: `24px`, display: 'block', marginLeft: '2px' }}>
<img draggable="false" className="emojione" alt="🙂" src="/emoji/1f602.svg" />
</DropdownTrigger>
<DropdownContent className='dropdown__left light'>
<EmojiPicker emojione={settings} onChange={this.handleChange} search={true} />
</DropdownContent>
</Dropdown>
);
}
});
export default injectIntl(EmojiPickerDropdown);

View File

@@ -16,11 +16,11 @@ const NavigationBar = React.createClass({
render () {
return (
<div style={{ padding: '10px', display: 'flex', flexShrink: '0', cursor: 'default' }}>
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`} style={{ textDecoration: 'none' }}><Avatar src={this.props.account.get('avatar')} size={40} /></Permalink>
<div className='navigation-bar'>
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`} style={{ textDecoration: 'none' }}><Avatar src={this.props.account.get('avatar')} animate size={40} /></Permalink>
<div style={{ flex: '1 1 auto', marginLeft: '8px', color: '#9baec8' }}>
<strong style={{ fontWeight: '500', display: 'block', color: '#fff' }}>{this.props.account.get('acct')}</strong>
<div style={{ flex: '1 1 auto', marginLeft: '8px' }}>
<strong style={{ fontWeight: '500', display: 'block' }}>{this.props.account.get('acct')}</strong>
<a href='/settings/profile' style={{ color: 'inherit', textDecoration: 'none' }}><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
</div>
</div>

View File

@@ -0,0 +1,101 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
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: 'Private' },
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 = {
lineHeight: '27px',
height: null
};
const PrivacyDropdown = React.createClass({
propTypes: {
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
},
getInitialState () {
return {
open: false
};
},
mixins: [PureRenderMixin],
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 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>
);
}
});
export default injectIntl(PrivacyDropdown);

View File

@@ -17,8 +17,9 @@ const ReplyIndicator = React.createClass({
},
propTypes: {
status: ImmutablePropTypes.map.isRequired,
onCancel: React.PropTypes.func.isRequired
status: ImmutablePropTypes.map,
onCancel: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
},
mixins: [PureRenderMixin],
@@ -35,17 +36,22 @@ const ReplyIndicator = React.createClass({
},
render () {
const { intl } = this.props;
const content = { __html: emojify(this.props.status.get('content')) };
const { status, intl } = this.props;
if (!status) {
return null;
}
const content = { __html: emojify(status.get('content')) };
return (
<div style={{ background: '#9baec8', padding: '10px' }}>
<div className='reply-indicator'>
<div style={{ overflow: 'hidden', marginBottom: '5px' }}>
<div style={{ float: 'right', lineHeight: '24px' }}><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div>
<a href={this.props.status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', color: '#282c37', textDecoration: 'none', overflow: 'hidden', lineHeight: '24px' }}>
<div style={{ float: 'left', marginRight: '5px' }}><Avatar size={24} src={this.props.status.getIn(['account', 'avatar'])} /></div>
<DisplayName account={this.props.status.get('account')} />
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', textDecoration: 'none', overflow: 'hidden', lineHeight: '24px' }}>
<div style={{ float: 'left', marginRight: '5px' }}><Avatar size={24} src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} /></div>
<DisplayName account={status.get('account')} />
</a>
</div>

View File

@@ -1,132 +1,72 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Autosuggest from 'react-autosuggest';
import AutosuggestAccountContainer from '../containers/autosuggest_account_container';
import { debounce } from 'react-decoration';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }
});
const getSuggestionValue = suggestion => suggestion.value;
const renderSuggestion = suggestion => {
if (suggestion.type === 'account') {
return <AutosuggestAccountContainer id={suggestion.id} />;
} else {
return <span>#{suggestion.id}</span>
}
};
const renderSectionTitle = section => (
<strong><FormattedMessage id={`search.${section.title}`} defaultMessage={section.title} /></strong>
);
const getSectionSuggestions = section => section.items;
const outerStyle = {
padding: '10px',
lineHeight: '20px',
position: 'relative'
};
const inputStyle = {
boxSizing: 'border-box',
display: 'block',
width: '100%',
border: 'none',
padding: '10px',
paddingRight: '30px',
fontFamily: 'inherit',
background: '#282c37',
color: '#9baec8',
fontSize: '14px',
margin: '0'
};
const iconStyle = {
position: 'absolute',
top: '18px',
right: '20px',
color: '#9baec8',
fontSize: '18px',
pointerEvents: 'none'
};
const Search = React.createClass({
contextTypes: {
router: React.PropTypes.object
},
propTypes: {
suggestions: React.PropTypes.array.isRequired,
value: React.PropTypes.string.isRequired,
submitted: React.PropTypes.bool,
onChange: React.PropTypes.func.isRequired,
onSubmit: React.PropTypes.func.isRequired,
onClear: React.PropTypes.func.isRequired,
onFetch: React.PropTypes.func.isRequired,
onReset: React.PropTypes.func.isRequired
onShow: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
},
mixins: [PureRenderMixin],
onChange (_, { newValue }) {
if (typeof newValue !== 'string') {
return;
}
this.props.onChange(newValue);
handleChange (e) {
this.props.onChange(e.target.value);
},
onSuggestionsClearRequested () {
handleClear (e) {
e.preventDefault();
this.props.onClear();
},
@debounce(500)
onSuggestionsFetchRequested ({ value }) {
value = value.replace('#', '');
this.props.onFetch(value.trim());
},
onSuggestionSelected (_, { suggestion }) {
if (suggestion.type === 'account') {
this.context.router.push(`/accounts/${suggestion.id}`);
} else {
this.context.router.push(`/timelines/tag/${suggestion.id}`);
handleKeyDown (e) {
if (e.key === 'Enter') {
e.preventDefault();
this.props.onSubmit();
}
},
noop () {
},
handleFocus () {
this.props.onShow();
},
render () {
const inputProps = {
placeholder: this.props.intl.formatMessage(messages.placeholder),
value: this.props.value,
onChange: this.onChange,
style: inputStyle
};
const { intl, value, submitted } = this.props;
const hasValue = value.length > 0 || submitted;
return (
<div style={outerStyle}>
<Autosuggest
multiSection={true}
suggestions={this.props.suggestions}
focusFirstSuggestion={true}
focusInputOnSuggestionClick={false}
alwaysRenderSuggestions={false}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
renderSectionTitle={renderSectionTitle}
getSectionSuggestions={getSectionSuggestions}
inputProps={inputProps}
<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 style={iconStyle}><i className='fa fa-search' /></div>
<div role='button' tabIndex='0' className='search__icon' onClick={hasValue ? this.handleClear : this.noop}>
<i className={`fa fa-search ${hasValue ? '' : 'active'}`} />
<i aria-label="Clear search" className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} />
</div>
</div>
);
},
}
});

View File

@@ -0,0 +1,68 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
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';
const SearchResults = React.createClass({
propTypes: {
results: ImmutablePropTypes.map.isRequired
},
mixins: [PureRenderMixin],
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} {count, plural, one {result} other {results}}' values={{ count }} />
</div>
{accounts}
{statuses}
{hashtags}
</div>
);
}
});
export default SearchResults;

View File

@@ -0,0 +1,31 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
const TextIconButton = React.createClass({
propTypes: {
label: React.PropTypes.string.isRequired,
title: React.PropTypes.string,
active: React.PropTypes.bool,
onClick: React.PropTypes.func.isRequired
},
mixins: [PureRenderMixin],
handleClick (e) {
e.preventDefault();
this.props.onClick();
},
render () {
const { label, title, active } = this.props;
return (
<button title={title} aria-label={title} className={`text-icon-button ${active ? 'active' : ''}`} onClick={this.handleClick}>
{label}
</button>
);
}
});
export default TextIconButton;

View File

@@ -6,6 +6,11 @@ const messages = defineMessages({
upload: { id: 'upload_button.label', defaultMessage: 'Add media' }
});
const iconStyle = {
lineHeight: '27px',
height: null
};
const UploadButton = React.createClass({
propTypes: {
@@ -37,7 +42,7 @@ const UploadButton = React.createClass({
return (
<div style={this.props.style}>
<IconButton icon='photo' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} size={24} />
<IconButton icon='camera' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} style={iconStyle} size={18} inverted />
<input key={resetFileKey} ref={this.setRef} type='file' multiple={false} onChange={this.handleChange} disabled={disabled} style={{ display: 'none' }} />
</div>
);

View File

@@ -2,6 +2,8 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
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' }
@@ -11,7 +13,6 @@ const UploadForm = React.createClass({
propTypes: {
media: ImmutablePropTypes.list.isRequired,
is_uploading: React.PropTypes.bool,
onRemoveFile: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
},
@@ -21,21 +22,22 @@ const UploadForm = React.createClass({
render () {
const { intl, media } = this.props;
if (!media.size) {
return null;
}
const uploads = media.map(attachment => (
<div key={attachment.get('id')} style={{ borderRadius: '4px', marginBottom: '10px' }} className='transparent-background'>
<div style={{ width: '100%', height: '100px', borderRadius: '4px', background: `url(${attachment.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }}>
<IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} />
</div>
const uploads = media.map(attachment =>
<div key={attachment.get('id')} style={{ margin: '5px', flex: '1 1 0' }}>
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
{({ scale }) =>
<div style={{ transform: `translateZ(0) scale(${scale})`, width: '100%', height: '100px', borderRadius: '4px', background: `url(${attachment.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }}>
<IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} />
</div>
}
</Motion>
</div>
));
);
return (
<div style={{ marginBottom: '20px', padding: '10px', overflow: 'hidden', flexShrink: '0' }}>
{uploads}
<div style={{ overflow: 'hidden' }}>
<UploadProgressContainer />
<div style={{ display: 'flex', padding: '5px' }}>{uploads}</div>
</div>
);
}

View File

@@ -0,0 +1,44 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import { Motion, spring } from 'react-motion';
import { FormattedMessage } from 'react-intl';
const UploadProgress = React.createClass({
propTypes: {
active: React.PropTypes.bool,
progress: React.PropTypes.number
},
mixins: [PureRenderMixin],
render () {
const { active, progress } = this.props;
if (!active) {
return null;
}
return (
<div className='upload-progress'>
<div>
<i className='fa fa-upload' />
</div>
<div style={{ flex: '1 1 auto' }}>
<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>
);
}
});
export default UploadProgress;

View File

@@ -0,0 +1,15 @@
import { connect } from 'react-redux';
import AutosuggestStatus from '../components/autosuggest_status';
import { makeGetStatus } from '../../../selectors';
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const mapStateToProps = (state, { id }) => ({
status: getStatus(state, id)
});
return mapStateToProps;
};
export default connect(makeMapStateToProps)(AutosuggestStatus);

View File

@@ -1,91 +1,78 @@
import { connect } from 'react-redux';
import ComposeForm from '../components/compose_form';
import { uploadCompose } from '../../../actions/compose';
import { createSelector } from 'reselect';
import {
changeCompose,
submitCompose,
cancelReplyCompose,
clearComposeSuggestions,
fetchComposeSuggestions,
selectComposeSuggestion,
changeComposeSensitivity,
changeComposeSpoilerness,
changeComposeSpoilerText,
changeComposeVisibility,
changeComposeListability
insertEmojiCompose
} from '../../../actions/compose';
import { makeGetStatus } from '../../../selectors';
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const getMentionedUsernames = createSelector(state => state.getIn(['compose', 'text']), text => text.match(/(?:^|[^\/\w])@([a-z0-9_]+@[a-z0-9\.\-]+)/ig));
const mapStateToProps = function (state, props) {
return {
text: state.getIn(['compose', 'text']),
suggestion_token: state.getIn(['compose', 'suggestion_token']),
suggestions: state.getIn(['compose', 'suggestions']),
sensitive: state.getIn(['compose', 'sensitive']),
spoiler: state.getIn(['compose', 'spoiler']),
spoiler_text: state.getIn(['compose', 'spoiler_text']),
unlisted: state.getIn(['compose', 'unlisted']),
private: state.getIn(['compose', 'private']),
fileDropDate: state.getIn(['compose', 'fileDropDate']),
is_submitting: state.getIn(['compose', 'is_submitting']),
is_uploading: state.getIn(['compose', 'is_uploading']),
in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to'])),
media_count: state.getIn(['compose', 'media_attachments']).size,
me: state.getIn(['compose', 'me'])
};
};
const getMentionedDomains = createSelector(getMentionedUsernames, mentionedUsernamesWithDomains => {
return mentionedUsernamesWithDomains !== null ? [...new Set(mentionedUsernamesWithDomains.map(item => item.split('@')[2]))] : [];
});
return mapStateToProps;
};
const mapStateToProps = (state, props) => {
const mentionedUsernames = getMentionedUsernames(state);
const mentionedUsernamesWithDomains = getMentionedDomains(state);
const mapDispatchToProps = function (dispatch) {
return {
onChange (text) {
dispatch(changeCompose(text));
},
onSubmit () {
dispatch(submitCompose());
},
onCancelReply () {
dispatch(cancelReplyCompose());
},
onClearSuggestions () {
dispatch(clearComposeSuggestions());
},
onFetchSuggestions (token) {
dispatch(fetchComposeSuggestions(token));
},
onSuggestionSelected (position, token, accountId) {
dispatch(selectComposeSuggestion(position, token, accountId));
},
onChangeSensitivity (checked) {
dispatch(changeComposeSensitivity(checked));
},
onChangeSpoilerness (checked) {
dispatch(changeComposeSpoilerness(checked));
},
onChangeSpoilerText (checked) {
dispatch(changeComposeSpoilerText(checked));
},
onChangeVisibility (checked) {
dispatch(changeComposeVisibility(checked));
},
onChangeListability (checked) {
dispatch(changeComposeListability(checked));
}
}
text: state.getIn(['compose', 'text']),
suggestion_token: state.getIn(['compose', 'suggestion_token']),
suggestions: state.getIn(['compose', 'suggestions']),
spoiler: state.getIn(['compose', 'spoiler']),
spoiler_text: state.getIn(['compose', 'spoiler_text']),
privacy: state.getIn(['compose', 'privacy']),
focusDate: state.getIn(['compose', 'focusDate']),
preselectDate: state.getIn(['compose', 'preselectDate']),
is_submitting: state.getIn(['compose', 'is_submitting']),
is_uploading: state.getIn(['compose', 'is_uploading']),
me: state.getIn(['compose', 'me']),
needsPrivacyWarning: (state.getIn(['compose', 'privacy']) === 'private' || state.getIn(['compose', 'privacy']) === 'direct') && mentionedUsernames !== null,
mentionedDomains: mentionedUsernamesWithDomains
};
};
export default connect(makeMapStateToProps, mapDispatchToProps)(ComposeForm);
const mapDispatchToProps = (dispatch) => ({
onChange (text) {
dispatch(changeCompose(text));
},
onSubmit () {
dispatch(submitCompose());
},
onClearSuggestions () {
dispatch(clearComposeSuggestions());
},
onFetchSuggestions (token) {
dispatch(fetchComposeSuggestions(token));
},
onSuggestionSelected (position, token, accountId) {
dispatch(selectComposeSuggestion(position, token, accountId));
},
onChangeSpoilerText (checked) {
dispatch(changeComposeSpoilerText(checked));
},
onPaste (files) {
dispatch(uploadCompose(files));
},
onPickEmoji (position, data) {
dispatch(insertEmojiCompose(position, data));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm);

View File

@@ -0,0 +1,17 @@
import { connect } from 'react-redux';
import PrivacyDropdown from '../components/privacy_dropdown';
import { changeComposeVisibility } from '../../../actions/compose';
const mapStateToProps = state => ({
value: state.getIn(['compose', 'privacy'])
});
const mapDispatchToProps = dispatch => ({
onChange (value) {
dispatch(changeComposeVisibility(value));
}
});
export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);

View File

@@ -0,0 +1,24 @@
import { connect } from 'react-redux';
import { cancelReplyCompose } from '../../../actions/compose';
import { makeGetStatus } from '../../../selectors';
import ReplyIndicator from '../components/reply_indicator';
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const mapStateToProps = (state, props) => ({
status: getStatus(state, state.getIn(['compose', 'in_reply_to'])),
});
return mapStateToProps;
};
const mapDispatchToProps = dispatch => ({
onCancel () {
dispatch(cancelReplyCompose());
}
});
export default connect(makeMapStateToProps, mapDispatchToProps)(ReplyIndicator);

View File

@@ -1,15 +1,15 @@
import { connect } from 'react-redux';
import {
changeSearch,
clearSearchSuggestions,
fetchSearchSuggestions,
resetSearch
clearSearch,
submitSearch,
showSearch
} from '../../../actions/search';
import Search from '../components/search';
const mapStateToProps = state => ({
suggestions: state.getIn(['search', 'suggestions']),
value: state.getIn(['search', 'value'])
value: state.getIn(['search', 'value']),
submitted: state.getIn(['search', 'submitted'])
});
const mapDispatchToProps = dispatch => ({
@@ -19,15 +19,15 @@ const mapDispatchToProps = dispatch => ({
},
onClear () {
dispatch(clearSearchSuggestions());
dispatch(clearSearch());
},
onFetch (value) {
dispatch(fetchSearchSuggestions(value));
onSubmit () {
dispatch(submitSearch());
},
onReset () {
dispatch(resetSearch());
onShow () {
dispatch(showSearch());
}
});

View File

@@ -0,0 +1,8 @@
import { connect } from 'react-redux';
import SearchResults from '../components/search_results';
const mapStateToProps = state => ({
results: state.getIn(['search', 'results'])
});
export default connect(mapStateToProps)(SearchResults);

View File

@@ -0,0 +1,49 @@
import { connect } from 'react-redux';
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());
}
});
const SensitiveButton = React.createClass({
propTypes: {
visible: React.PropTypes.bool,
active: React.PropTypes.bool,
onClick: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
},
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>
);
}
});
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton));

View File

@@ -0,0 +1,24 @@
import { connect } from 'react-redux';
import TextIconButton from '../components/text_icon_button';
import { changeComposeSpoilerness } from '../../../actions/compose';
import { injectIntl, defineMessages } from 'react-intl';
const messages = defineMessages({
title: { id: 'compose_form.spoiler', defaultMessage: 'Hide text behind content warning' }
});
const mapStateToProps = (state, { intl }) => ({
label: 'CW',
title: intl.formatMessage(messages.title),
active: state.getIn(['compose', 'spoiler'])
});
const mapDispatchToProps = dispatch => ({
onClick () {
dispatch(changeComposeSpoilerness());
}
});
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(TextIconButton));

View File

@@ -0,0 +1,9 @@
import { connect } from 'react-redux';
import UploadProgress from '../components/upload_progress';
const mapStateToProps = (state, props) => ({
active: state.getIn(['compose', 'is_uploading']),
progress: state.getIn(['compose', 'progress'])
});
export default connect(mapStateToProps)(UploadProgress);

View File

@@ -1,17 +1,34 @@
import Drawer from './components/drawer';
import ComposeFormContainer from './containers/compose_form_container';
import UploadFormContainer from './containers/upload_form_container';
import NavigationContainer from './containers/navigation_container';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import SearchContainer from './containers/search_container';
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'])
});
const Compose = React.createClass({
propTypes: {
dispatch: React.PropTypes.func.isRequired,
withHeader: React.PropTypes.bool
withHeader: React.PropTypes.bool,
showSearch: React.PropTypes.bool,
intl: React.PropTypes.object.isRequired
},
mixins: [PureRenderMixin],
@@ -25,16 +42,46 @@ const Compose = React.createClass({
},
render () {
const { withHeader, showSearch, intl } = this.props;
let header = '';
if (withHeader) {
header = (
<div className='drawer__header'>
<Link title={intl.formatMessage(messages.start)} className='drawer__tab' to='/getting-started'><i className='fa fa-fw fa-asterisk' /></Link>
<Link title={intl.formatMessage(messages.community)} className='drawer__tab' to='/timelines/public/local'><i className='fa fa-fw fa-users' /></Link>
<Link title={intl.formatMessage(messages.public)} className='drawer__tab' to='/timelines/public'><i className='fa fa-fw fa-globe' /></Link>
<a title={intl.formatMessage(messages.preferences)} className='drawer__tab' href='/settings/preferences'><i className='fa fa-fw fa-cog' /></a>
<a title={intl.formatMessage(messages.logout)} className='drawer__tab' href='/auth/sign_out' data-method='delete'><i className='fa fa-fw fa-sign-out' /></a>
</div>
);
}
return (
<Drawer withHeader={this.props.withHeader}>
<div className='drawer'>
{header}
<SearchContainer />
<NavigationContainer />
<ComposeFormContainer />
<UploadFormContainer />
</Drawer>
<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>
);
}
});
export default connect()(Compose);
export default connect(mapStateToProps)(injectIntl(Compose));

View File

@@ -16,11 +16,8 @@ const outerStyle = {
};
const panelStyle = {
background: '#2f3441',
display: 'flex',
flexDirection: 'row',
borderTop: '1px solid #363c4b',
borderBottom: '1px solid #363c4b',
padding: '10px 0'
};
@@ -36,14 +33,14 @@ const AccountAuthorize = ({ intl, account, onAuthorize, onReject }) => {
<div>
<div style={outerStyle}>
<Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name' style={{ display: 'block', overflow: 'hidden', marginBottom: '15px' }}>
<div style={{ float: 'left', marginRight: '10px' }}><Avatar src={account.get('avatar')} size={48} /></div>
<div style={{ float: 'left', marginRight: '10px' }}><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={48} /></div>
<DisplayName account={account} />
</Permalink>
<div style={{ color: '#616b86', fontSize: '14px' }} className='account__header__content' dangerouslySetInnerHTML={content} />
<div style={{ fontSize: '14px' }} className='account__header__content' dangerouslySetInnerHTML={content} />
</div>
<div style={panelStyle}>
<div className='account--panel' style={panelStyle}>
<div style={btnStyle}><IconButton title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} /></div>
<div style={btnStyle}><IconButton title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} /></div>
</div>

View File

@@ -50,7 +50,7 @@ const Followers = React.createClass({
handleLoadMore (e) {
e.preventDefault();
this.props.dispatch(expandFollowing(Number(this.props.params.accountId)));
this.props.dispatch(expandFollowers(Number(this.props.params.accountId)));
},
render () {

Some files were not shown because too many files have changed in this diff Show More