merge messaging

This commit is contained in:
tykayn 2020-02-01 12:17:06 +01:00
commit 519c9f417e
166 changed files with 3234 additions and 1885 deletions

1
.gitignore vendored
View File

@ -13,6 +13,7 @@
/db/*.sqlite3-journal /db/*.sqlite3-journal
# Ignore all logfiles and tempfiles. # Ignore all logfiles and tempfiles.
.eslintcache
/log/* /log/*
!/log/.keep !/log/.keep
/tmp /tmp

View File

@ -3,6 +3,7 @@ Authors
Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon) Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon)
and provided thanks to the work of the following contributors: and provided thanks to the work of the following contributors:
* [Tykayn](https://framagit.org/tykayn) for the Bliss version
* [Gargron](https://github.com/Gargron) * [Gargron](https://github.com/Gargron)
* [ThibG](https://github.com/ThibG) * [ThibG](https://github.com/ThibG)

View File

@ -6,7 +6,8 @@ ruby '>= 2.4.0', '< 2.7.0'
gem 'pkg-config', '~> 1.4' gem 'pkg-config', '~> 1.4'
gem 'puma', '~> 4.3' gem 'puma', '~> 4.3'
gem 'rails', '~> 5.2.3' gem 'rails', '~> 5.2.4'
gem 'sprockets', '~> 3.7'
gem 'thor', '~> 0.20' gem 'thor', '~> 0.20'
gem 'hamlit-rails', '~> 0.2' gem 'hamlit-rails', '~> 0.2'
@ -122,7 +123,7 @@ group :test do
gem 'rspec-sidekiq', '~> 3.0' gem 'rspec-sidekiq', '~> 3.0'
gem 'simplecov', '~> 0.17', require: false gem 'simplecov', '~> 0.17', require: false
gem 'webmock', '~> 3.7' gem 'webmock', '~> 3.7'
gem 'parallel_tests', '~> 2.29' gem 'parallel_tests', '~> 2.30'
end end
group :development do group :development do

View File

@ -44,25 +44,25 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (5.2.3) actioncable (5.2.4)
actionpack (= 5.2.3) actionpack (= 5.2.4)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailer (5.2.3) actionmailer (5.2.4)
actionpack (= 5.2.3) actionpack (= 5.2.4)
actionview (= 5.2.3) actionview (= 5.2.4)
activejob (= 5.2.3) activejob (= 5.2.4)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (5.2.3) actionpack (5.2.4)
actionview (= 5.2.3) actionview (= 5.2.4)
activesupport (= 5.2.3) activesupport (= 5.2.4)
rack (~> 2.0) rack (~> 2.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.2.3) actionview (5.2.4)
activesupport (= 5.2.3) activesupport (= 5.2.4)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
@ -73,20 +73,20 @@ GEM
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.7) active_record_query_trace (1.7)
activejob (5.2.3) activejob (5.2.4)
activesupport (= 5.2.3) activesupport (= 5.2.4)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (5.2.3) activemodel (5.2.4)
activesupport (= 5.2.3) activesupport (= 5.2.4)
activerecord (5.2.3) activerecord (5.2.4)
activemodel (= 5.2.3) activemodel (= 5.2.4)
activesupport (= 5.2.3) activesupport (= 5.2.4)
arel (>= 9.0) arel (>= 9.0)
activestorage (5.2.3) activestorage (5.2.4)
actionpack (= 5.2.3) actionpack (= 5.2.4)
activerecord (= 5.2.3) activerecord (= 5.2.4)
marcel (~> 0.3.1) marcel (~> 0.3.1)
activesupport (5.2.3) activesupport (5.2.4)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
@ -218,7 +218,7 @@ GEM
docile (1.3.2) docile (1.3.2)
domain_name (0.5.20180417) domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.2.2) doorkeeper (5.2.3)
railties (>= 5) railties (>= 5)
dotenv (2.7.5) dotenv (2.7.5)
dotenv-rails (2.7.5) dotenv-rails (2.7.5)
@ -238,9 +238,9 @@ GEM
erubi (1.9.0) erubi (1.9.0)
et-orbi (1.1.6) et-orbi (1.1.6)
tzinfo tzinfo
excon (0.62.0) excon (0.71.0)
fabrication (2.21.0) fabrication (2.21.0)
faker (2.8.0) faker (2.8.1)
i18n (>= 1.6, < 1.8) i18n (>= 1.6, < 1.8)
faraday (0.15.4) faraday (0.15.4)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
@ -324,7 +324,7 @@ GEM
jmespath (1.4.0) jmespath (1.4.0)
json (2.2.0) json (2.2.0)
json-canonicalization (0.1.0) json-canonicalization (0.1.0)
json-ld-preloaded (3.0.4) json-ld-preloaded (3.0.6)
json-ld (~> 3.0) json-ld (~> 3.0)
multi_json (~> 1.12) multi_json (~> 1.12)
rdf (~> 3.0) rdf (~> 3.0)
@ -380,7 +380,7 @@ GEM
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
minitest (5.13.0) minitest (5.13.0)
msgpack (1.3.1) msgpack (1.3.1)
multi_json (1.13.1) multi_json (1.14.1)
multipart-post (2.1.1) multipart-post (2.1.1)
necromancer (0.5.1) necromancer (0.5.1)
net-ldap (0.16.2) net-ldap (0.16.2)
@ -424,7 +424,7 @@ GEM
av (~> 0.9.0) av (~> 0.9.0)
paperclip (>= 2.5.2) paperclip (>= 2.5.2)
parallel (1.19.1) parallel (1.19.1)
parallel_tests (2.29.2) parallel_tests (2.30.0)
parallel parallel
parser (2.6.5.0) parser (2.6.5.0)
ast (~> 2.4.0) ast (~> 2.4.0)
@ -469,18 +469,18 @@ GEM
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (5.2.3) rails (5.2.4)
actioncable (= 5.2.3) actioncable (= 5.2.4)
actionmailer (= 5.2.3) actionmailer (= 5.2.4)
actionpack (= 5.2.3) actionpack (= 5.2.4)
actionview (= 5.2.3) actionview (= 5.2.4)
activejob (= 5.2.3) activejob (= 5.2.4)
activemodel (= 5.2.3) activemodel (= 5.2.4)
activerecord (= 5.2.3) activerecord (= 5.2.4)
activestorage (= 5.2.3) activestorage (= 5.2.4)
activesupport (= 5.2.3) activesupport (= 5.2.4)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 5.2.3) railties (= 5.2.4)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.4) rails-controller-testing (1.0.4)
actionpack (>= 5.0.1.x) actionpack (>= 5.0.1.x)
@ -496,15 +496,15 @@ GEM
railties (>= 5.0, < 6) railties (>= 5.0, < 6)
rails-settings-cached (0.6.6) rails-settings-cached (0.6.6)
rails (>= 4.2.0) rails (>= 4.2.0)
railties (5.2.3) railties (5.2.4)
actionpack (= 5.2.3) actionpack (= 5.2.4)
activesupport (= 5.2.3) activesupport (= 5.2.4)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0) thor (>= 0.19.0, < 2.0)
rainbow (3.0.0) rainbow (3.0.0)
rake (13.0.1) rake (13.0.1)
rdf (3.0.12) rdf (3.0.13)
hamster (~> 3.0) hamster (~> 3.0)
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.3.3) rdf-normalize (0.3.3)
@ -615,7 +615,7 @@ GEM
sshkit (1.20.0) sshkit (1.20.0)
net-scp (>= 1.1.2) net-scp (>= 1.1.2)
net-ssh (>= 2.8.0) net-ssh (>= 2.8.0)
stackprof (0.2.13) stackprof (0.2.14)
statsd-ruby (1.4.0) statsd-ruby (1.4.0)
stoplight (2.2.0) stoplight (2.2.0)
streamio-ffmpeg (3.0.2) streamio-ffmpeg (3.0.2)
@ -667,9 +667,9 @@ GEM
webpush (0.3.8) webpush (0.3.8)
hkdf (~> 0.2) hkdf (~> 0.2)
jwt (~> 2.0) jwt (~> 2.0)
websocket-driver (0.7.0) websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3) websocket-extensions (0.1.4)
wisper (2.0.1) wisper (2.0.1)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
@ -753,7 +753,7 @@ DEPENDENCIES
paperclip (~> 6.0) paperclip (~> 6.0)
paperclip-av-transcoder (~> 0.6) paperclip-av-transcoder (~> 0.6)
parallel (~> 1.19) parallel (~> 1.19)
parallel_tests (~> 2.29) parallel_tests (~> 2.30)
parslet parslet
pg (~> 1.1) pg (~> 1.1)
pghero (~> 2.4) pghero (~> 2.4)
@ -767,7 +767,7 @@ DEPENDENCIES
pundit (~> 2.1) pundit (~> 2.1)
rack-attack (~> 6.2) rack-attack (~> 6.2)
rack-cors (~> 1.1) rack-cors (~> 1.1)
rails (~> 5.2.3) rails (~> 5.2.4)
rails-controller-testing (~> 1.0) rails-controller-testing (~> 1.0)
rails-i18n (~> 5.1) rails-i18n (~> 5.1)
rails-settings-cached (~> 0.6) rails-settings-cached (~> 0.6)
@ -789,6 +789,7 @@ DEPENDENCIES
simple-navigation (~> 4.1) simple-navigation (~> 4.1)
simple_form (~> 5.0) simple_form (~> 5.0)
simplecov (~> 0.17) simplecov (~> 0.17)
sprockets (~> 3.7)
sprockets-rails (~> 3.2) sprockets-rails (~> 3.2)
stackprof stackprof
stoplight (~> 2.2.0) stoplight (~> 2.2.0)

View File

@ -1,5 +1,23 @@
![Mastodon](https://i.imgur.com/NhZc40l.png) ![Mastodon](https://i.imgur.com/NhZc40l.png)
======== ========
# CipherBliss version of Mastodon
features:
* liks to free tools
* snow fall during the end of the yar.
## Todo
* proper responsive columning
* **instant Messaging** in the web front end.
uses the api to get accounts
```bash
GET /api/v1/accounts/[id of my account]/following
```
========
[![GitHub release](https://img.shields.io/github/release/tootsuite/mastodon.svg)][releases] [![GitHub release](https://img.shields.io/github/release/tootsuite/mastodon.svg)][releases]
[![Build Status](https://img.shields.io/circleci/project/github/tootsuite/mastodon.svg)][circleci] [![Build Status](https://img.shields.io/circleci/project/github/tootsuite/mastodon.svg)][circleci]
@ -72,7 +90,7 @@ Mastodon acts as an OAuth2 provider so 3rd party apps can use the REST and Strea
- **Ruby** 2.4+ - **Ruby** 2.4+
- **Node.js** 8+ - **Node.js** 8+
The repository includes deployment configurations for **Docker and docker-compose**, but also a few specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. The [**stand-alone** installation guide](https://docs.joinmastodon.org/administration/installation/) is available in the documentation. The repository includes deployment configurations for **Docker and docker-compose**, but also a few specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. The [**stand-alone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
A **Vagrant** configuration is included for development purposes. A **Vagrant** configuration is included for development purposes.

View File

@ -106,7 +106,7 @@ export function fetchAccount(id) {
dispatch, dispatch,
getState, getState,
db.transaction('accounts', 'read').objectStore('accounts').index('id'), db.transaction('accounts', 'read').objectStore('accounts').index('id'),
id id,
).then(() => db.close(), error => { ).then(() => db.close(), error => {
db.close(); db.close();
throw error; throw error;
@ -118,20 +118,20 @@ export function fetchAccount(id) {
dispatch(fetchAccountFail(id, error)); dispatch(fetchAccountFail(id, error));
}); });
}; };
}; }
export function fetchAccountRequest(id) { export function fetchAccountRequest(id) {
return { return {
type: ACCOUNT_FETCH_REQUEST, type: ACCOUNT_FETCH_REQUEST,
id, id,
}; };
}; }
export function fetchAccountSuccess() { export function fetchAccountSuccess() {
return { return {
type: ACCOUNT_FETCH_SUCCESS, type: ACCOUNT_FETCH_SUCCESS,
}; };
}; }
export function fetchAccountFail(id, error) { export function fetchAccountFail(id, error) {
return { return {
@ -140,7 +140,7 @@ export function fetchAccountFail(id, error) {
error, error,
skipAlert: true, skipAlert: true,
}; };
}; }
export function followAccount(id, reblogs = true) { export function followAccount(id, reblogs = true) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -155,7 +155,7 @@ export function followAccount(id, reblogs = true) {
dispatch(followAccountFail(error, locked)); dispatch(followAccountFail(error, locked));
}); });
}; };
}; }
export function unfollowAccount(id) { export function unfollowAccount(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -167,7 +167,7 @@ export function unfollowAccount(id) {
dispatch(unfollowAccountFail(error)); dispatch(unfollowAccountFail(error));
}); });
}; };
}; }
export function followAccountRequest(id, locked) { export function followAccountRequest(id, locked) {
return { return {
@ -176,7 +176,7 @@ export function followAccountRequest(id, locked) {
locked, locked,
skipLoading: true, skipLoading: true,
}; };
}; }
export function followAccountSuccess(relationship, alreadyFollowing) { export function followAccountSuccess(relationship, alreadyFollowing) {
return { return {
@ -185,7 +185,7 @@ export function followAccountSuccess(relationship, alreadyFollowing) {
alreadyFollowing, alreadyFollowing,
skipLoading: true, skipLoading: true,
}; };
}; }
export function followAccountFail(error, locked) { export function followAccountFail(error, locked) {
return { return {
@ -194,7 +194,7 @@ export function followAccountFail(error, locked) {
locked, locked,
skipLoading: true, skipLoading: true,
}; };
}; }
export function unfollowAccountRequest(id) { export function unfollowAccountRequest(id) {
return { return {
@ -202,7 +202,7 @@ export function unfollowAccountRequest(id) {
id, id,
skipLoading: true, skipLoading: true,
}; };
}; }
export function unfollowAccountSuccess(relationship, statuses) { export function unfollowAccountSuccess(relationship, statuses) {
return { return {
@ -211,7 +211,7 @@ export function unfollowAccountSuccess(relationship, statuses) {
statuses, statuses,
skipLoading: true, skipLoading: true,
}; };
}; }
export function unfollowAccountFail(error) { export function unfollowAccountFail(error) {
return { return {
@ -219,7 +219,7 @@ export function unfollowAccountFail(error) {
error, error,
skipLoading: true, skipLoading: true,
}; };
}; }
export function blockAccount(id) { export function blockAccount(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -232,7 +232,7 @@ export function blockAccount(id) {
dispatch(blockAccountFail(id, error)); dispatch(blockAccountFail(id, error));
}); });
}; };
}; }
export function unblockAccount(id) { export function unblockAccount(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -244,14 +244,14 @@ export function unblockAccount(id) {
dispatch(unblockAccountFail(id, error)); dispatch(unblockAccountFail(id, error));
}); });
}; };
}; }
export function blockAccountRequest(id) { export function blockAccountRequest(id) {
return { return {
type: ACCOUNT_BLOCK_REQUEST, type: ACCOUNT_BLOCK_REQUEST,
id, id,
}; };
}; }
export function blockAccountSuccess(relationship, statuses) { export function blockAccountSuccess(relationship, statuses) {
return { return {
@ -259,36 +259,35 @@ export function blockAccountSuccess(relationship, statuses) {
relationship, relationship,
statuses, statuses,
}; };
}; }
export function blockAccountFail(error) { export function blockAccountFail(error) {
return { return {
type: ACCOUNT_BLOCK_FAIL, type: ACCOUNT_BLOCK_FAIL,
error, error,
}; };
}; }
export function unblockAccountRequest(id) { export function unblockAccountRequest(id) {
return { return {
type: ACCOUNT_UNBLOCK_REQUEST, type: ACCOUNT_UNBLOCK_REQUEST,
id, id,
}; };
}; }
export function unblockAccountSuccess(relationship) { export function unblockAccountSuccess(relationship) {
return { return {
type: ACCOUNT_UNBLOCK_SUCCESS, type: ACCOUNT_UNBLOCK_SUCCESS,
relationship, relationship,
}; };
}; }
export function unblockAccountFail(error) { export function unblockAccountFail(error) {
return { return {
type: ACCOUNT_UNBLOCK_FAIL, type: ACCOUNT_UNBLOCK_FAIL,
error, error,
}; };
}; }
export function muteAccount(id, notifications) { export function muteAccount(id, notifications) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -301,7 +300,7 @@ export function muteAccount(id, notifications) {
dispatch(muteAccountFail(id, error)); dispatch(muteAccountFail(id, error));
}); });
}; };
}; }
export function unmuteAccount(id) { export function unmuteAccount(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -313,14 +312,14 @@ export function unmuteAccount(id) {
dispatch(unmuteAccountFail(id, error)); dispatch(unmuteAccountFail(id, error));
}); });
}; };
}; }
export function muteAccountRequest(id) { export function muteAccountRequest(id) {
return { return {
type: ACCOUNT_MUTE_REQUEST, type: ACCOUNT_MUTE_REQUEST,
id, id,
}; };
}; }
export function muteAccountSuccess(relationship, statuses) { export function muteAccountSuccess(relationship, statuses) {
return { return {
@ -328,36 +327,35 @@ export function muteAccountSuccess(relationship, statuses) {
relationship, relationship,
statuses, statuses,
}; };
}; }
export function muteAccountFail(error) { export function muteAccountFail(error) {
return { return {
type: ACCOUNT_MUTE_FAIL, type: ACCOUNT_MUTE_FAIL,
error, error,
}; };
}; }
export function unmuteAccountRequest(id) { export function unmuteAccountRequest(id) {
return { return {
type: ACCOUNT_UNMUTE_REQUEST, type: ACCOUNT_UNMUTE_REQUEST,
id, id,
}; };
}; }
export function unmuteAccountSuccess(relationship) { export function unmuteAccountSuccess(relationship) {
return { return {
type: ACCOUNT_UNMUTE_SUCCESS, type: ACCOUNT_UNMUTE_SUCCESS,
relationship, relationship,
}; };
}; }
export function unmuteAccountFail(error) { export function unmuteAccountFail(error) {
return { return {
type: ACCOUNT_UNMUTE_FAIL, type: ACCOUNT_UNMUTE_FAIL,
error, error,
}; };
}; }
export function fetchFollowers(id) { export function fetchFollowers(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -373,14 +371,14 @@ export function fetchFollowers(id) {
dispatch(fetchFollowersFail(id, error)); dispatch(fetchFollowersFail(id, error));
}); });
}; };
}; }
export function fetchFollowersRequest(id) { export function fetchFollowersRequest(id) {
return { return {
type: FOLLOWERS_FETCH_REQUEST, type: FOLLOWERS_FETCH_REQUEST,
id, id,
}; };
}; }
export function fetchFollowersSuccess(id, accounts, next) { export function fetchFollowersSuccess(id, accounts, next) {
return { return {
@ -389,7 +387,7 @@ export function fetchFollowersSuccess(id, accounts, next) {
accounts, accounts,
next, next,
}; };
}; }
export function fetchFollowersFail(id, error) { export function fetchFollowersFail(id, error) {
return { return {
@ -397,7 +395,7 @@ export function fetchFollowersFail(id, error) {
id, id,
error, error,
}; };
}; }
export function expandFollowers(id) { export function expandFollowers(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -419,14 +417,14 @@ export function expandFollowers(id) {
dispatch(expandFollowersFail(id, error)); dispatch(expandFollowersFail(id, error));
}); });
}; };
}; }
export function expandFollowersRequest(id) { export function expandFollowersRequest(id) {
return { return {
type: FOLLOWERS_EXPAND_REQUEST, type: FOLLOWERS_EXPAND_REQUEST,
id, id,
}; };
}; }
export function expandFollowersSuccess(id, accounts, next) { export function expandFollowersSuccess(id, accounts, next) {
return { return {
@ -435,7 +433,7 @@ export function expandFollowersSuccess(id, accounts, next) {
accounts, accounts,
next, next,
}; };
}; }
export function expandFollowersFail(id, error) { export function expandFollowersFail(id, error) {
return { return {
@ -443,7 +441,7 @@ export function expandFollowersFail(id, error) {
id, id,
error, error,
}; };
}; }
export function fetchFollowing(id) { export function fetchFollowing(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -459,14 +457,14 @@ export function fetchFollowing(id) {
dispatch(fetchFollowingFail(id, error)); dispatch(fetchFollowingFail(id, error));
}); });
}; };
}; }
export function fetchFollowingRequest(id) { export function fetchFollowingRequest(id) {
return { return {
type: FOLLOWING_FETCH_REQUEST, type: FOLLOWING_FETCH_REQUEST,
id, id,
}; };
}; }
export function fetchFollowingSuccess(id, accounts, next) { export function fetchFollowingSuccess(id, accounts, next) {
return { return {
@ -475,7 +473,7 @@ export function fetchFollowingSuccess(id, accounts, next) {
accounts, accounts,
next, next,
}; };
}; }
export function fetchFollowingFail(id, error) { export function fetchFollowingFail(id, error) {
return { return {
@ -483,7 +481,7 @@ export function fetchFollowingFail(id, error) {
id, id,
error, error,
}; };
}; }
export function expandFollowing(id) { export function expandFollowing(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -505,14 +503,14 @@ export function expandFollowing(id) {
dispatch(expandFollowingFail(id, error)); dispatch(expandFollowingFail(id, error));
}); });
}; };
}; }
export function expandFollowingRequest(id) { export function expandFollowingRequest(id) {
return { return {
type: FOLLOWING_EXPAND_REQUEST, type: FOLLOWING_EXPAND_REQUEST,
id, id,
}; };
}; }
export function expandFollowingSuccess(id, accounts, next) { export function expandFollowingSuccess(id, accounts, next) {
return { return {
@ -521,7 +519,7 @@ export function expandFollowingSuccess(id, accounts, next) {
accounts, accounts,
next, next,
}; };
}; }
export function expandFollowingFail(id, error) { export function expandFollowingFail(id, error) {
return { return {
@ -529,7 +527,7 @@ export function expandFollowingFail(id, error) {
id, id,
error, error,
}; };
}; }
export function fetchRelationships(accountIds) { export function fetchRelationships(accountIds) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -548,7 +546,7 @@ export function fetchRelationships(accountIds) {
dispatch(fetchRelationshipsFail(error)); dispatch(fetchRelationshipsFail(error));
}); });
}; };
}; }
export function fetchRelationshipsRequest(ids) { export function fetchRelationshipsRequest(ids) {
return { return {
@ -556,7 +554,7 @@ export function fetchRelationshipsRequest(ids) {
ids, ids,
skipLoading: true, skipLoading: true,
}; };
}; }
export function fetchRelationshipsSuccess(relationships) { export function fetchRelationshipsSuccess(relationships) {
return { return {
@ -564,7 +562,7 @@ export function fetchRelationshipsSuccess(relationships) {
relationships, relationships,
skipLoading: true, skipLoading: true,
}; };
}; }
export function fetchRelationshipsFail(error) { export function fetchRelationshipsFail(error) {
return { return {
@ -572,7 +570,7 @@ export function fetchRelationshipsFail(error) {
error, error,
skipLoading: true, skipLoading: true,
}; };
}; }
export function fetchFollowRequests() { export function fetchFollowRequests() {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -584,13 +582,13 @@ export function fetchFollowRequests() {
dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)); dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null));
}).catch(error => dispatch(fetchFollowRequestsFail(error))); }).catch(error => dispatch(fetchFollowRequestsFail(error)));
}; };
}; }
export function fetchFollowRequestsRequest() { export function fetchFollowRequestsRequest() {
return { return {
type: FOLLOW_REQUESTS_FETCH_REQUEST, type: FOLLOW_REQUESTS_FETCH_REQUEST,
}; };
}; }
export function fetchFollowRequestsSuccess(accounts, next) { export function fetchFollowRequestsSuccess(accounts, next) {
return { return {
@ -598,14 +596,14 @@ export function fetchFollowRequestsSuccess(accounts, next) {
accounts, accounts,
next, next,
}; };
}; }
export function fetchFollowRequestsFail(error) { export function fetchFollowRequestsFail(error) {
return { return {
type: FOLLOW_REQUESTS_FETCH_FAIL, type: FOLLOW_REQUESTS_FETCH_FAIL,
error, error,
}; };
}; }
export function expandFollowRequests() { export function expandFollowRequests() {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -623,13 +621,13 @@ export function expandFollowRequests() {
dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)); dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null));
}).catch(error => dispatch(expandFollowRequestsFail(error))); }).catch(error => dispatch(expandFollowRequestsFail(error)));
}; };
}; }
export function expandFollowRequestsRequest() { export function expandFollowRequestsRequest() {
return { return {
type: FOLLOW_REQUESTS_EXPAND_REQUEST, type: FOLLOW_REQUESTS_EXPAND_REQUEST,
}; };
}; }
export function expandFollowRequestsSuccess(accounts, next) { export function expandFollowRequestsSuccess(accounts, next) {
return { return {
@ -637,14 +635,14 @@ export function expandFollowRequestsSuccess(accounts, next) {
accounts, accounts,
next, next,
}; };
}; }
export function expandFollowRequestsFail(error) { export function expandFollowRequestsFail(error) {
return { return {
type: FOLLOW_REQUESTS_EXPAND_FAIL, type: FOLLOW_REQUESTS_EXPAND_FAIL,
error, error,
}; };
}; }
export function authorizeFollowRequest(id) { export function authorizeFollowRequest(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -655,21 +653,21 @@ export function authorizeFollowRequest(id) {
.then(() => dispatch(authorizeFollowRequestSuccess(id))) .then(() => dispatch(authorizeFollowRequestSuccess(id)))
.catch(error => dispatch(authorizeFollowRequestFail(id, error))); .catch(error => dispatch(authorizeFollowRequestFail(id, error)));
}; };
}; }
export function authorizeFollowRequestRequest(id) { export function authorizeFollowRequestRequest(id) {
return { return {
type: FOLLOW_REQUEST_AUTHORIZE_REQUEST, type: FOLLOW_REQUEST_AUTHORIZE_REQUEST,
id, id,
}; };
}; }
export function authorizeFollowRequestSuccess(id) { export function authorizeFollowRequestSuccess(id) {
return { return {
type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS, type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
id, id,
}; };
}; }
export function authorizeFollowRequestFail(id, error) { export function authorizeFollowRequestFail(id, error) {
return { return {
@ -677,8 +675,7 @@ export function authorizeFollowRequestFail(id, error) {
id, id,
error, error,
}; };
}; }
export function rejectFollowRequest(id) { export function rejectFollowRequest(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -689,21 +686,21 @@ export function rejectFollowRequest(id) {
.then(() => dispatch(rejectFollowRequestSuccess(id))) .then(() => dispatch(rejectFollowRequestSuccess(id)))
.catch(error => dispatch(rejectFollowRequestFail(id, error))); .catch(error => dispatch(rejectFollowRequestFail(id, error)));
}; };
}; }
export function rejectFollowRequestRequest(id) { export function rejectFollowRequestRequest(id) {
return { return {
type: FOLLOW_REQUEST_REJECT_REQUEST, type: FOLLOW_REQUEST_REJECT_REQUEST,
id, id,
}; };
}; }
export function rejectFollowRequestSuccess(id) { export function rejectFollowRequestSuccess(id) {
return { return {
type: FOLLOW_REQUEST_REJECT_SUCCESS, type: FOLLOW_REQUEST_REJECT_SUCCESS,
id, id,
}; };
}; }
export function rejectFollowRequestFail(id, error) { export function rejectFollowRequestFail(id, error) {
return { return {
@ -711,7 +708,7 @@ export function rejectFollowRequestFail(id, error) {
id, id,
error, error,
}; };
}; }
export function pinAccount(id) { export function pinAccount(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -723,7 +720,7 @@ export function pinAccount(id) {
dispatch(pinAccountFail(error)); dispatch(pinAccountFail(error));
}); });
}; };
}; }
export function unpinAccount(id) { export function unpinAccount(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -735,46 +732,46 @@ export function unpinAccount(id) {
dispatch(unpinAccountFail(error)); dispatch(unpinAccountFail(error));
}); });
}; };
}; }
export function pinAccountRequest(id) { export function pinAccountRequest(id) {
return { return {
type: ACCOUNT_PIN_REQUEST, type: ACCOUNT_PIN_REQUEST,
id, id,
}; };
}; }
export function pinAccountSuccess(relationship) { export function pinAccountSuccess(relationship) {
return { return {
type: ACCOUNT_PIN_SUCCESS, type: ACCOUNT_PIN_SUCCESS,
relationship, relationship,
}; };
}; }
export function pinAccountFail(error) { export function pinAccountFail(error) {
return { return {
type: ACCOUNT_PIN_FAIL, type: ACCOUNT_PIN_FAIL,
error, error,
}; };
}; }
export function unpinAccountRequest(id) { export function unpinAccountRequest(id) {
return { return {
type: ACCOUNT_UNPIN_REQUEST, type: ACCOUNT_UNPIN_REQUEST,
id, id,
}; };
}; }
export function unpinAccountSuccess(relationship) { export function unpinAccountSuccess(relationship) {
return { return {
type: ACCOUNT_UNPIN_SUCCESS, type: ACCOUNT_UNPIN_SUCCESS,
relationship, relationship,
}; };
}; }
export function unpinAccountFail(error) { export function unpinAccountFail(error) {
return { return {
type: ACCOUNT_UNPIN_FAIL, type: ACCOUNT_UNPIN_FAIL,
error, error,
}; };
}; }

View File

@ -7,8 +7,7 @@ import { useEmoji } from './emojis';
import resizeImage from '../utils/resize_image'; import resizeImage from '../utils/resize_image';
import { importFetchedAccounts } from './importer'; import { importFetchedAccounts } from './importer';
import { updateTimeline } from './timelines'; import { updateTimeline } from './timelines';
import { showAlertForError } from './alerts'; import { showAlert, showAlertForError } from './alerts';
import { showAlert } from './alerts';
import { defineMessages } from 'react-intl'; import { defineMessages } from 'react-intl';
let cancelFetchComposeSuggestionsAccounts, cancelFetchComposeSuggestionsTags; let cancelFetchComposeSuggestionsAccounts, cancelFetchComposeSuggestionsTags;
@ -76,7 +75,7 @@ export function changeCompose(text) {
type: COMPOSE_CHANGE, type: COMPOSE_CHANGE,
text: text, text: text,
}; };
}; }
export function replyCompose(status, routerHistory) { export function replyCompose(status, routerHistory) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -87,19 +86,19 @@ export function replyCompose(status, routerHistory) {
ensureComposeIsVisible(getState, routerHistory); ensureComposeIsVisible(getState, routerHistory);
}; };
}; }
export function cancelReplyCompose() { export function cancelReplyCompose() {
return { return {
type: COMPOSE_REPLY_CANCEL, type: COMPOSE_REPLY_CANCEL,
}; };
}; }
export function resetCompose() { export function resetCompose() {
return { return {
type: COMPOSE_RESET, type: COMPOSE_RESET,
}; };
}; }
export function mentionCompose(account, routerHistory) { export function mentionCompose(account, routerHistory) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -110,7 +109,7 @@ export function mentionCompose(account, routerHistory) {
ensureComposeIsVisible(getState, routerHistory); ensureComposeIsVisible(getState, routerHistory);
}; };
}; }
export function directCompose(account, routerHistory) { export function directCompose(account, routerHistory) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -121,7 +120,7 @@ export function directCompose(account, routerHistory) {
ensureComposeIsVisible(getState, routerHistory); ensureComposeIsVisible(getState, routerHistory);
}; };
}; }
export function submitCompose(routerHistory) { export function submitCompose(routerHistory) {
return function (dispatch, getState) { return function (dispatch, getState) {
@ -179,27 +178,27 @@ export function submitCompose(routerHistory) {
dispatch(submitComposeFail(error)); dispatch(submitComposeFail(error));
}); });
}; };
}; }
export function submitComposeRequest() { export function submitComposeRequest() {
return { return {
type: COMPOSE_SUBMIT_REQUEST, type: COMPOSE_SUBMIT_REQUEST,
}; };
}; }
export function submitComposeSuccess(status) { export function submitComposeSuccess(status) {
return { return {
type : COMPOSE_SUBMIT_SUCCESS, type : COMPOSE_SUBMIT_SUCCESS,
status: status, status: status,
}; };
}; }
export function submitComposeFail(error) { export function submitComposeFail(error) {
return { return {
type : COMPOSE_SUBMIT_FAIL, type : COMPOSE_SUBMIT_FAIL,
error: error, error: error,
}; };
}; }
export function uploadCompose(files) { export function uploadCompose(files) {
return function (dispatch, getState) { return function (dispatch, getState) {
@ -237,9 +236,10 @@ export function uploadCompose(files) {
}, },
}).then(({ data }) => dispatch(uploadComposeSuccess(data, f))); }).then(({ data }) => dispatch(uploadComposeSuccess(data, f)));
}).catch(error => dispatch(uploadComposeFail(error))); }).catch(error => dispatch(uploadComposeFail(error)));
}
}; };
}; }
};
export function changeUploadCompose(id, params) { export function changeUploadCompose(id, params) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -251,21 +251,22 @@ export function changeUploadCompose(id, params) {
dispatch(changeUploadComposeFail(id, error)); dispatch(changeUploadComposeFail(id, error));
}); });
}; };
}; }
export function changeUploadComposeRequest() { export function changeUploadComposeRequest() {
return { return {
type : COMPOSE_UPLOAD_CHANGE_REQUEST, type : COMPOSE_UPLOAD_CHANGE_REQUEST,
skipLoading: true, skipLoading: true,
}; };
}; }
export function changeUploadComposeSuccess(media) { export function changeUploadComposeSuccess(media) {
return { return {
type : COMPOSE_UPLOAD_CHANGE_SUCCESS, type : COMPOSE_UPLOAD_CHANGE_SUCCESS,
media : media, media : media,
skipLoading: true, skipLoading: true,
}; };
}; }
export function changeUploadComposeFail(error) { export function changeUploadComposeFail(error) {
return { return {
@ -273,14 +274,14 @@ export function changeUploadComposeFail(error) {
error : error, error : error,
skipLoading: true, skipLoading: true,
}; };
}; }
export function uploadComposeRequest() { export function uploadComposeRequest() {
return { return {
type : COMPOSE_UPLOAD_REQUEST, type : COMPOSE_UPLOAD_REQUEST,
skipLoading: true, skipLoading: true,
}; };
}; }
export function uploadComposeProgress(loaded, total) { export function uploadComposeProgress(loaded, total) {
return { return {
@ -288,7 +289,7 @@ export function uploadComposeProgress(loaded, total) {
loaded: loaded, loaded: loaded,
total : total, total : total,
}; };
}; }
export function uploadComposeSuccess(media, file) { export function uploadComposeSuccess(media, file) {
return { return {
@ -297,7 +298,7 @@ export function uploadComposeSuccess(media, file) {
file : file, file : file,
skipLoading: true, skipLoading: true,
}; };
}; }
export function uploadComposeFail(error) { export function uploadComposeFail(error) {
return { return {
@ -305,14 +306,14 @@ export function uploadComposeFail(error) {
error : error, error : error,
skipLoading: true, skipLoading: true,
}; };
}; }
export function undoUploadCompose(media_id) { export function undoUploadCompose(media_id) {
return { return {
type : COMPOSE_UPLOAD_UNDO, type : COMPOSE_UPLOAD_UNDO,
media_id: media_id, media_id: media_id,
}; };
}; }
export function clearComposeSuggestions() { export function clearComposeSuggestions() {
if (cancelFetchComposeSuggestionsAccounts) { if (cancelFetchComposeSuggestionsAccounts) {
@ -321,7 +322,7 @@ export function clearComposeSuggestions() {
return { return {
type: COMPOSE_SUGGESTIONS_CLEAR, type: COMPOSE_SUGGESTIONS_CLEAR,
}; };
}; }
const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => { const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
if (cancelFetchComposeSuggestionsAccounts) { if (cancelFetchComposeSuggestionsAccounts) {
@ -395,7 +396,7 @@ export function fetchComposeSuggestions(token) {
break; break;
} }
}; };
}; }
export function readyComposeSuggestionsEmojis(token, emojis) { export function readyComposeSuggestionsEmojis(token, emojis) {
return { return {
@ -403,7 +404,7 @@ export function readyComposeSuggestionsEmojis(token, emojis) {
token, token,
emojis, emojis,
}; };
}; }
export function readyComposeSuggestionsAccounts(token, accounts) { export function readyComposeSuggestionsAccounts(token, accounts) {
return { return {
@ -411,7 +412,7 @@ export function readyComposeSuggestionsAccounts(token, accounts) {
token, token,
accounts, accounts,
}; };
}; }
export const readyComposeSuggestionsTags = (token, tags) => ({ export const readyComposeSuggestionsTags = (token, tags) => ({
type: COMPOSE_SUGGESTIONS_READY, type: COMPOSE_SUGGESTIONS_READY,
@ -444,7 +445,7 @@ export function selectComposeSuggestion(position, token, suggestion, path) {
path, path,
}); });
}; };
}; }
export function updateSuggestionTags(token) { export function updateSuggestionTags(token) {
return { return {
@ -492,39 +493,39 @@ export function mountCompose() {
return { return {
type: COMPOSE_MOUNT, type: COMPOSE_MOUNT,
}; };
}; }
export function unmountCompose() { export function unmountCompose() {
return { return {
type: COMPOSE_UNMOUNT, type: COMPOSE_UNMOUNT,
}; };
}; }
export function changeComposeSensitivity() { export function changeComposeSensitivity() {
return { return {
type: COMPOSE_SENSITIVITY_CHANGE, type: COMPOSE_SENSITIVITY_CHANGE,
}; };
}; }
export function changeComposeSpoilerness() { export function changeComposeSpoilerness() {
return { return {
type: COMPOSE_SPOILERNESS_CHANGE, type: COMPOSE_SPOILERNESS_CHANGE,
}; };
}; }
export function changeComposeSpoilerText(text) { export function changeComposeSpoilerText(text) {
return { return {
type: COMPOSE_SPOILER_TEXT_CHANGE, type: COMPOSE_SPOILER_TEXT_CHANGE,
text, text,
}; };
}; }
export function changeComposeVisibility(value) { export function changeComposeVisibility(value) {
return { return {
type: COMPOSE_VISIBILITY_CHANGE, type: COMPOSE_VISIBILITY_CHANGE,
value, value,
}; };
}; }
export function insertEmojiCompose(position, emoji, needsSpace) { export function insertEmojiCompose(position, emoji, needsSpace) {
return { return {
@ -533,33 +534,33 @@ export function insertEmojiCompose(position, emoji, needsSpace) {
emoji, emoji,
needsSpace, needsSpace,
}; };
}; }
export function changeComposing(value) { export function changeComposing(value) {
return { return {
type: COMPOSE_COMPOSING_CHANGE, type: COMPOSE_COMPOSING_CHANGE,
value, value,
}; };
}; }
export function addPoll() { export function addPoll() {
return { return {
type: COMPOSE_POLL_ADD, type: COMPOSE_POLL_ADD,
}; };
}; }
export function removePoll() { export function removePoll() {
return { return {
type: COMPOSE_POLL_REMOVE, type: COMPOSE_POLL_REMOVE,
}; };
}; }
export function addPollOption(title) { export function addPollOption(title) {
return { return {
type: COMPOSE_POLL_OPTION_ADD, type: COMPOSE_POLL_OPTION_ADD,
title, title,
}; };
}; }
export function changePollOption(index, title) { export function changePollOption(index, title) {
return { return {
@ -567,14 +568,14 @@ export function changePollOption(index, title) {
index, index,
title, title,
}; };
}; }
export function removePollOption(index) { export function removePollOption(index) {
return { return {
type: COMPOSE_POLL_OPTION_REMOVE, type: COMPOSE_POLL_OPTION_REMOVE,
index, index,
}; };
}; }
export function changePollSettings(expiresIn, isMultiple) { export function changePollSettings(expiresIn, isMultiple) {
return { return {
@ -582,4 +583,4 @@ export function changePollSettings(expiresIn, isMultiple) {
expiresIn, expiresIn,
isMultiple, isMultiple,
}; };
}; }

View File

@ -1,9 +1,5 @@
import api, { getLinks } from '../api'; import api, { getLinks } from '../api';
import { import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer';
importFetchedAccounts,
importFetchedStatuses,
importFetchedStatus,
} from './importer';
export const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT'; export const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT';
export const CONVERSATIONS_UNMOUNT = 'CONVERSATIONS_UNMOUNT'; export const CONVERSATIONS_UNMOUNT = 'CONVERSATIONS_UNMOUNT';

View File

@ -3,7 +3,7 @@ import openDB from '../storage/db';
import { evictStatus } from '../storage/modifier'; import { evictStatus } from '../storage/modifier';
import { deleteFromTimelines } from './timelines'; import { deleteFromTimelines } from './timelines';
import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus } from './importer'; import { importAccount, importFetchedStatus, importFetchedStatuses, importStatus } from './importer';
import { ensureComposeIsVisible } from './compose'; import { ensureComposeIsVisible } from './compose';
export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
@ -37,7 +37,7 @@ export function fetchStatusRequest(id, skipLoading) {
id, id,
skipLoading, skipLoading,
}; };
}; }
function getFromDB(dispatch, getState, accountIndex, index, id) { function getFromDB(dispatch, getState, accountIndex, index, id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -113,14 +113,14 @@ export function fetchStatus(id) {
dispatch(fetchStatusFail(id, error, skipLoading)); dispatch(fetchStatusFail(id, error, skipLoading));
}); });
}; };
}; }
export function fetchStatusSuccess(skipLoading) { export function fetchStatusSuccess(skipLoading) {
return { return {
type: STATUS_FETCH_SUCCESS, type: STATUS_FETCH_SUCCESS,
skipLoading, skipLoading,
}; };
}; }
export function fetchStatusFail(id, error, skipLoading) { export function fetchStatusFail(id, error, skipLoading) {
return { return {
@ -130,7 +130,7 @@ export function fetchStatusFail(id, error, skipLoading) {
skipLoading, skipLoading,
skipAlert: true, skipAlert: true,
}; };
}; }
export function redraft(status, raw_text) { export function redraft(status, raw_text) {
return { return {
@ -138,7 +138,7 @@ export function redraft(status, raw_text) {
status, status,
raw_text, raw_text,
}; };
}; }
export function deleteStatus(id, routerHistory, withRedraft = false) { export function deleteStatus(id, routerHistory, withRedraft = false) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -163,21 +163,21 @@ export function deleteStatus(id, routerHistory, withRedraft = false) {
dispatch(deleteStatusFail(id, error)); dispatch(deleteStatusFail(id, error));
}); });
}; };
}; }
export function deleteStatusRequest(id) { export function deleteStatusRequest(id) {
return { return {
type: STATUS_DELETE_REQUEST, type: STATUS_DELETE_REQUEST,
id : id, id : id,
}; };
}; }
export function deleteStatusSuccess(id) { export function deleteStatusSuccess(id) {
return { return {
type: STATUS_DELETE_SUCCESS, type: STATUS_DELETE_SUCCESS,
id : id, id : id,
}; };
}; }
export function deleteStatusFail(id, error) { export function deleteStatusFail(id, error) {
return { return {
@ -185,7 +185,7 @@ export function deleteStatusFail(id, error) {
id : id, id : id,
error: error, error: error,
}; };
}; }
export function fetchContext(id) { export function fetchContext(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -203,14 +203,14 @@ export function fetchContext(id) {
dispatch(fetchContextFail(id, error)); dispatch(fetchContextFail(id, error));
}); });
}; };
}; }
export function fetchContextRequest(id) { export function fetchContextRequest(id) {
return { return {
type: CONTEXT_FETCH_REQUEST, type: CONTEXT_FETCH_REQUEST,
id, id,
}; };
}; }
export function fetchContextSuccess(id, ancestors, descendants) { export function fetchContextSuccess(id, ancestors, descendants) {
return { return {
@ -220,7 +220,7 @@ export function fetchContextSuccess(id, ancestors, descendants) {
descendants, descendants,
statuses: ancestors.concat(descendants), statuses: ancestors.concat(descendants),
}; };
}; }
export function fetchContextFail(id, error) { export function fetchContextFail(id, error) {
return { return {
@ -229,7 +229,7 @@ export function fetchContextFail(id, error) {
error, error,
skipAlert: true, skipAlert: true,
}; };
}; }
export function muteStatus(id) { export function muteStatus(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -241,21 +241,21 @@ export function muteStatus(id) {
dispatch(muteStatusFail(id, error)); dispatch(muteStatusFail(id, error));
}); });
}; };
}; }
export function muteStatusRequest(id) { export function muteStatusRequest(id) {
return { return {
type: STATUS_MUTE_REQUEST, type: STATUS_MUTE_REQUEST,
id, id,
}; };
}; }
export function muteStatusSuccess(id) { export function muteStatusSuccess(id) {
return { return {
type: STATUS_MUTE_SUCCESS, type: STATUS_MUTE_SUCCESS,
id, id,
}; };
}; }
export function muteStatusFail(id, error) { export function muteStatusFail(id, error) {
return { return {
@ -263,7 +263,7 @@ export function muteStatusFail(id, error) {
id, id,
error, error,
}; };
}; }
export function unmuteStatus(id) { export function unmuteStatus(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -275,21 +275,21 @@ export function unmuteStatus(id) {
dispatch(unmuteStatusFail(id, error)); dispatch(unmuteStatusFail(id, error));
}); });
}; };
}; }
export function unmuteStatusRequest(id) { export function unmuteStatusRequest(id) {
return { return {
type: STATUS_UNMUTE_REQUEST, type: STATUS_UNMUTE_REQUEST,
id, id,
}; };
}; }
export function unmuteStatusSuccess(id) { export function unmuteStatusSuccess(id) {
return { return {
type: STATUS_UNMUTE_SUCCESS, type: STATUS_UNMUTE_SUCCESS,
id, id,
}; };
}; }
export function unmuteStatusFail(id, error) { export function unmuteStatusFail(id, error) {
return { return {
@ -297,7 +297,7 @@ export function unmuteStatusFail(id, error) {
id, id,
error, error,
}; };
}; }
export function hideStatus(ids) { export function hideStatus(ids) {
if (!Array.isArray(ids)) { if (!Array.isArray(ids)) {
@ -308,7 +308,7 @@ export function hideStatus(ids) {
type: STATUS_HIDE, type: STATUS_HIDE,
ids, ids,
}; };
}; }
export function revealStatus(ids) { export function revealStatus(ids) {
if (!Array.isArray(ids)) { if (!Array.isArray(ids)) {
@ -319,4 +319,4 @@ export function revealStatus(ids) {
type: STATUS_REVEAL, type: STATUS_REVEAL,
ids, ids,
}; };
}; }

View File

@ -37,27 +37,27 @@ class Account extends ImmutablePureComponent {
handleFollow = () => { handleFollow = () => {
this.props.onFollow(this.props.account); this.props.onFollow(this.props.account);
} };
handleBlock = () => { handleBlock = () => {
this.props.onBlock(this.props.account); this.props.onBlock(this.props.account);
} };
handleMute = () => { handleMute = () => {
this.props.onMute(this.props.account); this.props.onMute(this.props.account);
} };
handleMuteNotifications = () => { handleMuteNotifications = () => {
this.props.onMuteNotifications(this.props.account, true); this.props.onMuteNotifications(this.props.account, true);
} };
handleUnmuteNotifications = () => { handleUnmuteNotifications = () => {
this.props.onMuteNotifications(this.props.account, false); this.props.onMuteNotifications(this.props.account, false);
} };
handleAction = () => { handleAction = () => {
this.props.onActionClick(this.props.account); this.props.onActionClick(this.props.account);
} };
render () { render () {
const { account, intl, hidden, onActionClick, actionIcon, actionTitle } = this.props; const { account, intl, hidden, onActionClick, actionIcon, actionTitle } = this.props;
@ -111,7 +111,10 @@ class Account extends ImmutablePureComponent {
<div className='account'> <div className='account'>
<div className='account__wrapper'> <div className='account__wrapper'>
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}> <Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}>
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div> <div className='account__avatar-wrapper'><Avatar
account={account}
size={55}
/></div >
<DisplayName account={account} /> <DisplayName account={account} />
</Permalink> </Permalink>

View File

@ -26,12 +26,12 @@ export default class Avatar extends React.PureComponent {
handleMouseEnter = () => { handleMouseEnter = () => {
if (this.props.animate) return; if (this.props.animate) return;
this.setState({ hovering: true }); this.setState({ hovering: true });
} };
handleMouseLeave = () => { handleMouseLeave = () => {
if (this.props.animate) return; if (this.props.animate) return;
this.setState({ hovering: false }); this.setState({ hovering: false });
} };
render() { render() {
const { account, size, animate, inline } = this.props; const { account, size, animate, inline } = this.props;

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { version, source_url } from 'mastodon/initial_state'; import { source_url, version } from 'mastodon/initial_state';
export default class ErrorBoundary extends React.PureComponent { export default class ErrorBoundary extends React.PureComponent {
@ -44,7 +44,7 @@ export default class ErrorBoundary extends React.PureComponent {
this.setState({ copied: true }); this.setState({ copied: true });
setTimeout(() => this.setState({ copied: false }), 700); setTimeout(() => this.setState({ copied: false }), 700);
} };
render() { render() {
const { hasError, copied } = this.state; const { hasError, copied } = this.state;
@ -54,11 +54,30 @@ export default class ErrorBoundary extends React.PureComponent {
} }
return ( return (
<div className='error-boundary'> <div className='error-boundary hero text-center padded content'>
<div > <div >
<p className='error-boundary__error'><FormattedMessage id='error.unexpected_crash.explanation' defaultMessage='Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.' /></p> <p className='error-boundary__error title content-heading'><FormattedMessage
<p><FormattedMessage id='error.unexpected_crash.next_steps' defaultMessage='Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.' /></p> id='error.unexpected_crash.explanation'
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied && 'copied'}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p> defaultMessage='Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.'
/></p >
<p className='content'><FormattedMessage
id='error.unexpected_crash.next_steps'
defaultMessage='Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.'
/></p >
<p className='error-boundary__footer'>Mastodon v{version} · <a
href={source_url}
rel='noopener noreferrer'
target='_blank'
><FormattedMessage
id='errors.unexpected_crash.report_issue'
defaultMessage='Report issue'
/></a > · <button
onClick={this.handleCopyStackTrace}
className={copied && 'copied'}
><FormattedMessage
id='errors.unexpected_crash.copy_stacktrace'
defaultMessage='Copy stacktrace to clipboard'
/></button ></p >
</div > </div >
</div > </div >
); );

View File

@ -38,7 +38,7 @@ export default class IconButton extends React.PureComponent {
state = { state = {
activate : false, activate : false,
deactivate: false, deactivate: false,
} };
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (!nextProps.animate) return; if (!nextProps.animate) return;
@ -56,32 +56,32 @@ export default class IconButton extends React.PureComponent {
if (!this.props.disabled) { if (!this.props.disabled) {
this.props.onClick(e); this.props.onClick(e);
} }
} };
handleKeyPress = (e) => { handleKeyPress = (e) => {
if (this.props.onKeyPress && !this.props.disabled) { if (this.props.onKeyPress && !this.props.disabled) {
this.props.onKeyPress(e); this.props.onKeyPress(e);
} }
} };
handleMouseDown = (e) => { handleMouseDown = (e) => {
if (!this.props.disabled && this.props.onMouseDown) { if (!this.props.disabled && this.props.onMouseDown) {
this.props.onMouseDown(e); this.props.onMouseDown(e);
} }
} };
handleKeyDown = (e) => { handleKeyDown = (e) => {
if (!this.props.disabled && this.props.onKeyDown) { if (!this.props.disabled && this.props.onKeyDown) {
this.props.onKeyDown(e); this.props.onKeyDown(e);
} }
} };
render() { render() {
const style = { const style = {
fontSize: `${this.props.size}px`, // fontSize: `${this.props.size}px`,
width: `${this.props.size * 1.28571429}px`, // width: `${this.props.size * 1.28571429}px`,
height: `${this.props.size * 1.28571429}px`, // height: `${this.props.size * 1.28571429}px`,
lineHeight: `${this.props.size}px`, // lineHeight: `${this.props.size}px`,
...this.props.style, ...this.props.style,
...(this.props.active ? this.props.activeStyle : {}), ...(this.props.active ? this.props.activeStyle : {}),
}; };
@ -128,7 +128,11 @@ export default class IconButton extends React.PureComponent {
tabIndex={tabIndex} tabIndex={tabIndex}
disabled={disabled} disabled={disabled}
> >
<Icon id={icon} fixedWidth aria-hidden='true' /> <Icon
id={icon}
fixedWidth
aria-hidden='true'
/>
</button > </button >
); );
} }

View File

@ -16,7 +16,7 @@ import { Audio, MediaGallery, Video } from '../features/ui/util/async-components
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import classNames from 'classnames'; import classNames from 'classnames';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import { displayMedia } from '../initial_state'; import { displayMedia, isStaff } from '../initial_state';
// We use the component (and not the container) since we do not want // We use the component (and not the container) since we do not want
// to use the progress bar to show download progress // to use the progress bar to show download progress
import Bundle from '../features/ui/components/bundle'; import Bundle from '../features/ui/components/bundle';
@ -82,11 +82,15 @@ class Status extends ImmutablePureComponent {
onMoveUp : PropTypes.func, onMoveUp : PropTypes.func,
onMoveDown : PropTypes.func, onMoveDown : PropTypes.func,
showThread : PropTypes.bool, showThread : PropTypes.bool,
threadsCompile : PropTypes.bool,
getScrollPosition : PropTypes.func, getScrollPosition : PropTypes.func,
updateScrollBottom: PropTypes.func, updateScrollBottom: PropTypes.func,
cacheMediaWidth : PropTypes.func, cacheMediaWidth : PropTypes.func,
cachedMediaWidth : PropTypes.number, cachedMediaWidth : PropTypes.number,
}; };
static defaultProps = {
threadsCompile: true,
};
// Avoid checking props that are functions (and whose equality will always // Avoid checking props that are functions (and whose equality will always
// evaluate to false. See react-immutable-pure-component for usage. // evaluate to false. See react-immutable-pure-component for usage.
@ -199,24 +203,24 @@ class Status extends ImmutablePureComponent {
}; };
renderLoadingMediaGallery() { renderLoadingMediaGallery() {
return <div return (<div
className='media-gallery' className='media-gallery'
style={{ height: '110px' }} style={{ height: '110px' }}
/>; />);
} }
renderLoadingVideoPlayer() { renderLoadingVideoPlayer() {
return <div return (<div
className='video-player' className='video-player'
style={{ height: '110px' }} style={{ height: '110px' }}
/>; />);
} }
renderLoadingAudioPlayer() { renderLoadingAudioPlayer() {
return <div return (<div
className='audio-player' className='audio-player'
style={{ height: '110px' }} style={{ height: '110px' }}
/>; />);
} }
handleOpenVideo = (media, startTime) => { handleOpenVideo = (media, startTime) => {
@ -497,20 +501,20 @@ class Status extends ImmutablePureComponent {
} }
if (otherAccounts && otherAccounts.size > 0) { if (otherAccounts && otherAccounts.size > 0) {
statusAvatar = <AvatarComposite statusAvatar = (<AvatarComposite
accounts={otherAccounts} accounts={otherAccounts}
size={48} size={55}
/>; />);
} else if (account === undefined || account === null) { } else if (account === undefined || account === null) {
statusAvatar = <Avatar statusAvatar = (<Avatar
account={status.get('account')} account={status.get('account')}
size={48} size={55}
/>; />);
} else { } else {
statusAvatar = <AvatarOverlay statusAvatar = (<AvatarOverlay
account={status.get('account')} account={status.get('account')}
friend={account} friend={account}
/>; />);
} }
return ( return (
@ -542,6 +546,12 @@ class Status extends ImmutablePureComponent {
role='presentation' role='presentation'
/> />
<div className='status__info'> <div className='status__info'>
{isStaff && (<div className='administrate-stuff pull-left'>
<i className='fa fa-gears' />
</div >
)}
<a <a
href={status.get('url')} href={status.get('url')}
className='status__relative-time' className='status__relative-time'
@ -595,10 +605,19 @@ class Status extends ImmutablePureComponent {
/> />
</button > </button >
)} )}
{/*<div className='well'>*/}
{/* {status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) &&*/}
{/* <p > oui je cause tout seul, c'est un thread</p >*/}
{/* }*/}
{/* {this.props.threadsCompile &&*/}
{/* <p > les threads sont en mode compilés</p >*/}
{/* }*/}
{/*</div >*/}
<StatusActionBar <StatusActionBar
status={status} status={status}
account={account} {...other} /> account={account} {...other}
/>
</div > </div >
</div > </div >
</HotKeys > </HotKeys >

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import Button from 'mastodon/components/button'; import Button from 'mastodon/components/button';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { autoPlayGif, me, isStaff } from 'mastodon/initial_state'; import { autoPlayGif, isStaff, me } from 'mastodon/initial_state';
import classNames from 'classnames'; import classNames from 'classnames';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import Avatar from 'mastodon/components/avatar'; import Avatar from 'mastodon/components/avatar';
@ -70,7 +70,7 @@ class Header extends ImmutablePureComponent {
openEditProfile = () => { openEditProfile = () => {
window.open('/settings/profile', '_blank'); window.open('/settings/profile', '_blank');
} };
isStatusesPageActive = (match, location) => { isStatusesPageActive = (match, location) => {
if (!match) { if (!match) {
@ -78,7 +78,7 @@ class Header extends ImmutablePureComponent {
} }
return !location.pathname.match(/\/(followers|following)\/?$/); return !location.pathname.match(/\/(followers|following)\/?$/);
} };
_updateEmojis () { _updateEmojis () {
const node = this.node; const node = this.node;
@ -111,15 +111,15 @@ class Header extends ImmutablePureComponent {
handleEmojiMouseEnter = ({ target }) => { handleEmojiMouseEnter = ({ target }) => {
target.src = target.getAttribute('data-original'); target.src = target.getAttribute('data-original');
} };
handleEmojiMouseLeave = ({ target }) => { handleEmojiMouseLeave = ({ target }) => {
target.src = target.getAttribute('data-static'); target.src = target.getAttribute('data-static');
} };
setRef = (c) => { setRef = (c) => {
this.node = c; this.node = c;
} };
render () { render () {
const { account, intl, domain, identity_proofs } = this.props; const { account, intl, domain, identity_proofs } = this.props;
@ -263,7 +263,10 @@ class Header extends ImmutablePureComponent {
<div className='account__header__bar'> <div className='account__header__bar'>
<div className='account__header__tabs'> <div className='account__header__tabs'>
<a className='avatar' href={account.get('url')} rel='noopener noreferrer' target='_blank'> <a className='avatar' href={account.get('url')} rel='noopener noreferrer' target='_blank'>
<Avatar account={account} size={90} /> <Avatar
account={account}
size={120}
/>
</a> </a>
<div className='spacer' /> <div className='spacer' />

View File

@ -22,6 +22,7 @@ const mapStateToProps = (state, { params: { accountId }, withReplies = false })
return { return {
isAccount : !!state.getIn(['accounts', accountId]), isAccount : !!state.getIn(['accounts', accountId]),
account : state.getIn(['accounts', accountId]),
statusIds : state.getIn(['timelines', `account:${path}`, 'items'], emptyList), statusIds : state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList), featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList),
isLoading : state.getIn(['timelines', `account:${path}`, 'isLoading']), isLoading : state.getIn(['timelines', `account:${path}`, 'isLoading']),
@ -74,8 +75,11 @@ class AccountTimeline extends ImmutablePureComponent {
} }
handleLoadMore = maxId => { handleLoadMore = maxId => {
this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies })); this.props.dispatch(expandAccountTimeline(this.props.params.accountId, {
} maxId,
withReplies: this.props.withReplies,
}));
};
render() { render() {
const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, isAccount, multiColumn } = this.props; const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, isAccount, multiColumn } = this.props;
@ -97,7 +101,13 @@ class AccountTimeline extends ImmutablePureComponent {
); );
} }
const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />; const emptyMessage = blockedBy ? <FormattedMessage
id='empty_column.account_unavailable'
defaultMessage='Profile unavailable'
/> : <FormattedMessage
id='empty_column.account_timeline'
defaultMessage='No toots here!'
/>;
return ( return (
<Column > <Column >

View File

@ -46,6 +46,7 @@ class ComposeForm extends ImmutablePureComponent {
spoilerText : PropTypes.string, spoilerText : PropTypes.string,
focusDate : PropTypes.instanceOf(Date), focusDate : PropTypes.instanceOf(Date),
caretPosition : PropTypes.number, caretPosition : PropTypes.number,
maxTootCharsLimit : PropTypes.number,
preselectDate : PropTypes.instanceOf(Date), preselectDate : PropTypes.instanceOf(Date),
isSubmitting : PropTypes.bool, isSubmitting : PropTypes.bool,
isChangingUpload : PropTypes.bool, isChangingUpload : PropTypes.bool,
@ -65,6 +66,7 @@ class ComposeForm extends ImmutablePureComponent {
static defaultProps = { static defaultProps = {
showSearch : false, showSearch : false,
maxTootCharsLimit: 7777,
}; };
handleChange = (e) => { handleChange = (e) => {
@ -88,7 +90,7 @@ class ComposeForm extends ImmutablePureComponent {
const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props; const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
const fulltext = [this.props.spoilerText, countableText(this.props.text)].join(''); const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('');
if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > 7777 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) { if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > this.props.maxTootCharsLimit || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
return; return;
} }
@ -181,7 +183,7 @@ class ComposeForm extends ImmutablePureComponent {
const { intl, onPaste, showSearch, anyMedia } = this.props; const { intl, onPaste, showSearch, anyMedia } = this.props;
const disabled = this.props.isSubmitting; const disabled = this.props.isSubmitting;
const text = [this.props.spoilerText, countableText(this.props.text)].join(''); const text = [this.props.spoilerText, countableText(this.props.text)].join('');
const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > 7777 || (text.length !== 0 && text.trim().length === 0 && !anyMedia); const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > this.props.maxTootCharsLimit || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
let publishText = ''; let publishText = '';
if (this.props.privacy === 'private' || this.props.privacy === 'direct') { if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
@ -197,7 +199,10 @@ class ComposeForm extends ImmutablePureComponent {
<ReplyIndicatorContainer /> <ReplyIndicatorContainer />
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`} ref={this.setRef}> <div
className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}
ref={this.setRef}
>
<AutosuggestInput <AutosuggestInput
placeholder={intl.formatMessage(messages.spoiler_placeholder)} placeholder={intl.formatMessage(messages.spoiler_placeholder)}
value={this.props.spoilerText} value={this.props.spoilerText}
@ -244,13 +249,18 @@ class ComposeForm extends ImmutablePureComponent {
<PrivacyDropdownContainer /> <PrivacyDropdownContainer />
<SpoilerButtonContainer /> <SpoilerButtonContainer />
</div > </div >
<div className='character-counter__wrapper'><CharacterCounter max={7777} text={text}/></div> <div className='character-counter__wrapper'><CharacterCounter
max={this.props.maxTootCharsLimit}
text={text}
/></div >
</div > </div >
<div className='compose-form__publish'> <div className='compose-form__publish'>
<div className='compose-form__publish-button-wrapper'><Button <div className='compose-form__publish-button-wrapper'><Button
text={publishText} onClick={this.handleSubmit} text={publishText}
disabled={disabledButton} block onClick={this.handleSubmit}
disabled={disabledButton}
block
/></div > /></div >
</div > </div >

View File

@ -26,7 +26,7 @@ export default class NavigationBar extends ImmutablePureComponent {
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span > <span style={{ display: 'none' }}>{this.props.account.get('acct')}</span >
<Avatar <Avatar
account={this.props.account} account={this.props.account}
size={48} size={55}
/> />
</Permalink > </Permalink >

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import IconButton from 'mastodon/components/icon_button'; import IconButton from 'mastodon/components/icon_button';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import AutosuggestInput from 'mastodon/components/autosuggest_input'; import AutosuggestInput from 'mastodon/components/autosuggest_input';
@ -56,19 +56,19 @@ class Option extends React.PureComponent {
if (e.key === 'Enter' || e.key === ' ') { if (e.key === 'Enter' || e.key === ' ') {
this.handleToggleMultiple(e); this.handleToggleMultiple(e);
} }
} };
onSuggestionsClearRequested = () => { onSuggestionsClearRequested = () => {
this.props.onClearSuggestions(); this.props.onClearSuggestions();
} };
onSuggestionsFetchRequested = (token) => { onSuggestionsFetchRequested = (token) => {
this.props.onFetchSuggestions(token); this.props.onFetchSuggestions(token);
} };
onSuggestionSelected = (tokenStart, token, value) => { onSuggestionSelected = (tokenStart, token, value) => {
this.props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', this.props.index]); this.props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', this.props.index]);
} };
render () { render () {
const { isPollMultiple, title, index, intl } = this.props; const { isPollMultiple, title, index, intl } = this.props;
@ -82,8 +82,8 @@ class Option extends React.PureComponent {
onKeyPress={this.handleCheckboxKeypress} onKeyPress={this.handleCheckboxKeypress}
role='button' role='button'
tabIndex='0' tabIndex='0'
title={intl.formatMessage(isPollMultiple ? messages.switchToMultiple : messages.switchToSingle)} title={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)}
aria-label={intl.formatMessage(isPollMultiple ? messages.switchToMultiple : messages.switchToSingle)} aria-label={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)}
/> />
<AutosuggestInput <AutosuggestInput

View File

@ -2,12 +2,12 @@ import { connect } from 'react-redux';
import ComposeForm from '../components/compose_form'; import ComposeForm from '../components/compose_form';
import { import {
changeCompose, changeCompose,
submitCompose, changeComposeSpoilerText,
clearComposeSuggestions, clearComposeSuggestions,
fetchComposeSuggestions, fetchComposeSuggestions,
selectComposeSuggestion,
changeComposeSpoilerText,
insertEmojiCompose, insertEmojiCompose,
selectComposeSuggestion,
submitCompose,
uploadCompose, uploadCompose,
} from '../../../actions/compose'; } from '../../../actions/compose';

View File

@ -150,7 +150,7 @@ class Conversation extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete }); menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete });
const names = accounts.map(a => <Permalink const names = accounts.map(a => (<Permalink
to={`/accounts/${a.get('id')}`} to={`/accounts/${a.get('id')}`}
href={a.get('url')} href={a.get('url')}
key={a.get('id')} key={a.get('id')}
@ -160,7 +160,7 @@ class Conversation extends ImmutablePureComponent {
className='display-name__html' className='display-name__html'
dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }}
/></bdi > /></bdi >
</Permalink >).reduce((prev, cur) => [prev, ', ', cur]); </Permalink >)).reduce((prev, cur) => [prev, ', ', cur]);
const handlers = { const handlers = {
reply : this.handleReply, reply : this.handleReply,
@ -179,7 +179,7 @@ class Conversation extends ImmutablePureComponent {
<div className='conversation__avatar'> <div className='conversation__avatar'>
<AvatarComposite <AvatarComposite
accounts={accounts} accounts={accounts}
size={48} size={55}
/> />
</div > </div >
@ -221,23 +221,24 @@ class Conversation extends ImmutablePureComponent {
)} )}
<div className='status__action-bar'> <div className='status__action-bar'>
<IconButton
className='status__action-bar-button'
title={intl.formatMessage(messages.reply)}
icon='reply'
onClick={this.handleReply}
/>
<div className='status__action-bar-dropdown'> <div className='status__action-bar-dropdown'>
<DropdownMenuContainer <DropdownMenuContainer
status={lastStatus} status={lastStatus}
items={menu} items={menu}
icon='ellipsis-h' icon='ellipsis-h'
size={18}
direction='right' direction='right'
style={{ width: '15em' }}
size={18}
title={intl.formatMessage(messages.more)} title={intl.formatMessage(messages.more)}
/> />
</div > </div >
<IconButton
className='status__action-bar-button conversation_reply'
title={intl.formatMessage(messages.reply)}
icon='reply'
size={40}
onClick={this.handleReply}
/>
</div > </div >
</div > </div >
</div > </div >

View File

@ -9,10 +9,10 @@ import DisplayName from 'mastodon/components/display_name';
import Permalink from 'mastodon/components/permalink'; import Permalink from 'mastodon/components/permalink';
import RelativeTimestamp from 'mastodon/components/relative_timestamp'; import RelativeTimestamp from 'mastodon/components/relative_timestamp';
import IconButton from 'mastodon/components/icon_button'; import IconButton from 'mastodon/components/icon_button';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state'; import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
import { shortNumberFormat } from 'mastodon/utils/numbers'; import { shortNumberFormat } from 'mastodon/utils/numbers';
import { followAccount, unfollowAccount, blockAccount, unblockAccount, unmuteAccount } from 'mastodon/actions/accounts'; import { blockAccount, followAccount, unblockAccount, unfollowAccount, unmuteAccount } from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import { initMuteModal } from 'mastodon/actions/mutes'; import { initMuteModal } from 'mastodon/actions/mutes';
@ -113,27 +113,27 @@ class AccountCard extends ImmutablePureComponent {
handleEmojiMouseEnter = ({ target }) => { handleEmojiMouseEnter = ({ target }) => {
target.src = target.getAttribute('data-original'); target.src = target.getAttribute('data-original');
} };
handleEmojiMouseLeave = ({ target }) => { handleEmojiMouseLeave = ({ target }) => {
target.src = target.getAttribute('data-static'); target.src = target.getAttribute('data-static');
} };
handleFollow = () => { handleFollow = () => {
this.props.onFollow(this.props.account); this.props.onFollow(this.props.account);
} };
handleBlock = () => { handleBlock = () => {
this.props.onBlock(this.props.account); this.props.onBlock(this.props.account);
} };
handleMute = () => { handleMute = () => {
this.props.onMute(this.props.account); this.props.onMute(this.props.account);
} };
setRef = (c) => { setRef = (c) => {
this.node = c; this.node = c;
} };
render () { render () {
const { account, intl } = this.props; const { account, intl } = this.props;
@ -165,7 +165,10 @@ class AccountCard extends ImmutablePureComponent {
<div className='directory__card__bar'> <div className='directory__card__bar'>
<Permalink className='directory__card__bar__name' href={account.get('url')} to={`/accounts/${account.get('id')}`}> <Permalink className='directory__card__bar__name' href={account.get('url')} to={`/accounts/${account.get('id')}`}>
<Avatar account={account} size={48} /> <Avatar
account={account}
size={55}
/>
<DisplayName account={account} /> <DisplayName account={account} />
</Permalink> </Permalink>

View File

@ -31,7 +31,10 @@ class AccountAuthorize extends ImmutablePureComponent {
<div className='account-authorize__wrapper'> <div className='account-authorize__wrapper'>
<div className='account-authorize'> <div className='account-authorize'>
<Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name'> <Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name'>
<div className='account-authorize__avatar'><Avatar account={account} size={48} /></div> <div className='account-authorize__avatar'><Avatar
account={account}
size={55}
/></div >
<DisplayName account={account} /> <DisplayName account={account} />
</Permalink> </Permalink>

View File

@ -5,11 +5,7 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import LoadingIndicator from '../../components/loading_indicator'; import LoadingIndicator from '../../components/loading_indicator';
import { import { expandFollowing, fetchAccount, fetchFollowing } from '../../actions/accounts';
fetchAccount,
fetchFollowing,
expandFollowing,
} from '../../actions/accounts';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
@ -76,7 +72,13 @@ class Following extends ImmutablePureComponent {
); );
} }
const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />; const emptyMessage = blockedBy ? (<FormattedMessage
id='empty_column.account_unavailable'
defaultMessage='Profile unavailable'
/>) : (<FormattedMessage
id='account.follows.empty'
defaultMessage="This user doesn't follow anyone yet."
/>);
return ( return (
<Column > <Column >
@ -87,13 +89,20 @@ class Following extends ImmutablePureComponent {
hasMore={hasMore} hasMore={hasMore}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
shouldUpdateScroll={shouldUpdateScroll} shouldUpdateScroll={shouldUpdateScroll}
prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />} prepend={<HeaderContainer
accountId={this.props.params.accountId}
hideTabs
/>}
alwaysPrepend alwaysPrepend
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn} bindToDocument={!multiColumn}
> >
{blockedBy ? [] : accountIds.map(id => {blockedBy ? [] : accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} /> (<AccountContainer
key={id}
id={id}
withNote={false}
/>),
)} )}
</ScrollableList > </ScrollableList >
</Column > </Column >

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@ -54,12 +54,12 @@ class Notification extends ImmutablePureComponent {
handleMoveUp = () => { handleMoveUp = () => {
const { notification, onMoveUp } = this.props; const { notification, onMoveUp } = this.props;
onMoveUp(notification.get('id')); onMoveUp(notification.get('id'));
} };
handleMoveDown = () => { handleMoveDown = () => {
const { notification, onMoveDown } = this.props; const { notification, onMoveDown } = this.props;
onMoveDown(notification.get('id')); onMoveDown(notification.get('id'));
} };
handleOpen = () => { handleOpen = () => {
const { notification } = this.props; const { notification } = this.props;
@ -69,34 +69,34 @@ class Notification extends ImmutablePureComponent {
} else { } else {
this.handleOpenProfile(); this.handleOpenProfile();
} }
} };
handleOpenProfile = () => { handleOpenProfile = () => {
const { notification } = this.props; const { notification } = this.props;
this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`); this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`);
} };
handleMention = e => { handleMention = e => {
e.preventDefault(); e.preventDefault();
const { notification, onMention } = this.props; const { notification, onMention } = this.props;
onMention(notification.get('account'), this.context.router.history); onMention(notification.get('account'), this.context.router.history);
} };
handleHotkeyFavourite = () => { handleHotkeyFavourite = () => {
const { status } = this.props; const { status } = this.props;
if (status) this.props.onFavourite(status); if (status) this.props.onFavourite(status);
} };
handleHotkeyBoost = e => { handleHotkeyBoost = e => {
const { status } = this.props; const { status } = this.props;
if (status) this.props.onReblog(status, e); if (status) this.props.onReblog(status, e);
} };
handleHotkeyToggleHidden = () => { handleHotkeyToggleHidden = () => {
const { status } = this.props; const { status } = this.props;
if (status) this.props.onToggleHidden(status); if (status) this.props.onToggleHidden(status);
} };
getHandlers() { getHandlers() {
return { return {
@ -117,18 +117,37 @@ class Notification extends ImmutablePureComponent {
return ( return (
<HotKeys handlers={this.getHandlers()}> <HotKeys handlers={this.getHandlers()}>
<div className='notification notification-follow focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.follow, { name: account.get('acct') }), notification.get('created_at'))}> <div
<div className='notification__message'> className='notification notification-follow focusable'
<div className='notification__favourite-icon-wrapper'> tabIndex='0'
<Icon id='user-plus' fixedWidth /> aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.follow, { name: account.get('acct') }), notification.get('created_at'))}
</div> >
<span title={notification.get('created_at')}> <span title={notification.get('created_at')}>
<FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} /> <span className='media'>
<span className='media-left'>
<AccountContainer
id={account.get('id')}
hidden={this.props.hidden}
/>
</span >
<span className='media-center'>
<FormattedMessage
id='notification.follow'
defaultMessage='{name} followed you'
values={{ name: link }}
/>
</span >
<span className='media-right'>
<Icon
id='user-plus'
fixedWidth
/>
</span >
</span >
</span > </span >
</div>
<AccountContainer id={account.get('id')} hidden={this.props.hidden} />
</div > </div >
</HotKeys > </HotKeys >
); );
@ -139,18 +158,36 @@ class Notification extends ImmutablePureComponent {
return ( return (
<HotKeys handlers={this.getHandlers()}> <HotKeys handlers={this.getHandlers()}>
<div className='notification notification-follow-request focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow_request', defaultMessage: '{name} has requested to follow you' }, { name: account.get('acct') }), notification.get('created_at'))}> <div
className='notification notification-follow-request focusable'
tabIndex='0'
aria-label={notificationForScreenReader(intl, intl.formatMessage({
id : 'notification.follow_request',
defaultMessage: '{name} has requested to follow you',
}, { name: account.get('acct') }), notification.get('created_at'))}
>
<div className='notification__message'> <div className='notification__message'>
<div className='notification__favourite-icon-wrapper'> <div className='notification__favourite-icon-wrapper'>
<Icon id='user' fixedWidth /> <Icon
id='user'
fixedWidth
/>
</div > </div >
<span title={notification.get('created_at')}> <span title={notification.get('created_at')}>
<FormattedMessage id='notification.follow_request' defaultMessage='{name} has requested to follow you' values={{ name: link }} /> <FormattedMessage
id='notification.follow_request'
defaultMessage='{name} has requested to follow you'
values={{ name: link }}
/>
</span > </span >
</div > </div >
<FollowRequestContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} /> <FollowRequestContainer
id={account.get('id')}
withNote={false}
hidden={this.props.hidden}
/>
</div > </div >
</HotKeys > </HotKeys >
); );
@ -178,14 +215,26 @@ class Notification extends ImmutablePureComponent {
return ( return (
<HotKeys handlers={this.getHandlers()}> <HotKeys handlers={this.getHandlers()}>
<div className='notification notification-favourite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.favourite, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> <div
className='notification notification-favourite focusable'
tabIndex='0'
aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.favourite, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}
>
<div className='notification__message'> <div className='notification__message'>
<div className='notification__favourite-icon-wrapper'> <div className='notification__favourite-icon-wrapper'>
<Icon id='star' className='star-icon' fixedWidth /> <Icon
id='star'
className='star-icon'
fixedWidth
/>
</div > </div >
<span title={notification.get('created_at')}> <span title={notification.get('created_at')}>
<FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} /> <FormattedMessage
id='notification.favourite'
defaultMessage='{name} favourited your status'
values={{ name: link }}
/>
</span > </span >
</div > </div >
@ -210,14 +259,25 @@ class Notification extends ImmutablePureComponent {
return ( return (
<HotKeys handlers={this.getHandlers()}> <HotKeys handlers={this.getHandlers()}>
<div className='notification notification-reblog focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> <div
className='notification notification-reblog focusable'
tabIndex='0'
aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}
>
<div className='notification__message'> <div className='notification__message'>
<div className='notification__favourite-icon-wrapper'> <div className='notification__favourite-icon-wrapper'>
<Icon id='retweet' fixedWidth /> <Icon
id='retweet'
fixedWidth
/>
</div > </div >
<span title={notification.get('created_at')}> <span title={notification.get('created_at')}>
<FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} /> <FormattedMessage
id='notification.reblog'
defaultMessage='{name} boosted your status'
values={{ name: link }}
/>
</span > </span >
</div > </div >
@ -244,17 +304,30 @@ class Notification extends ImmutablePureComponent {
return ( return (
<HotKeys handlers={this.getHandlers()}> <HotKeys handlers={this.getHandlers()}>
<div className='notification notification-poll focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}> <div
className='notification notification-poll focusable'
tabIndex='0'
aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}
>
<div className='notification__message'> <div className='notification__message'>
<div className='notification__favourite-icon-wrapper'> <div className='notification__favourite-icon-wrapper'>
<Icon id='tasks' fixedWidth /> <Icon
id='tasks'
fixedWidth
/>
</div > </div >
<span title={notification.get('created_at')}> <span title={notification.get('created_at')}>
{ownPoll ? ( {ownPoll ? (
<FormattedMessage id='notification.own_poll' defaultMessage='Your poll has ended' /> <FormattedMessage
id='notification.own_poll'
defaultMessage='Your poll has ended'
/>
) : ( ) : (
<FormattedMessage id='notification.poll' defaultMessage='A poll you have voted in has ended' /> <FormattedMessage
id='notification.poll'
defaultMessage='A poll you have voted in has ended'
/>
)} )}
</span > </span >
</div > </div >
@ -279,7 +352,13 @@ class Notification extends ImmutablePureComponent {
const { notification } = this.props; const { notification } = this.props;
const account = notification.get('account'); const account = notification.get('account');
const displayNameHtml = { __html: account.get('display_name_html') }; const displayNameHtml = { __html: account.get('display_name_html') };
const link = <bdi><Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>; const link = (<bdi ><Permalink
className='notification__display-name'
href={account.get('url')}
title={account.get('acct')}
to={`/accounts/${account.get('id')}`}
dangerouslySetInnerHTML={displayNameHtml}
/></bdi >);
switch (notification.get('type')) { switch (notification.get('type')) {
case 'follow': case 'follow':

View File

@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../components/loading_indicator'; import LoadingIndicator from '../../components/loading_indicator';
import { fetchReblogs } from '../../actions/interactions'; import { fetchReblogs } from '../../actions/interactions';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
@ -47,7 +47,7 @@ class Reblogs extends ImmutablePureComponent {
handleRefresh = () => { handleRefresh = () => {
this.props.dispatch(fetchReblogs(this.props.params.statusId)); this.props.dispatch(fetchReblogs(this.props.params.statusId));
} };
render() { render() {
const { intl, shouldUpdateScroll, accountIds, multiColumn } = this.props; const { intl, shouldUpdateScroll, accountIds, multiColumn } = this.props;
@ -60,7 +60,10 @@ class Reblogs extends ImmutablePureComponent {
); );
} }
const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has boosted this toot yet. When someone does, they will show up here.' />; const emptyMessage = (<FormattedMessage
id='status.reblogs.empty'
defaultMessage='No one has boosted this toot yet. When someone does, they will show up here.'
/>);
return ( return (
<Column bindToDocument={!multiColumn}> <Column bindToDocument={!multiColumn}>
@ -68,7 +71,12 @@ class Reblogs extends ImmutablePureComponent {
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
extraButton={( extraButton={(
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button> <button
className='column-header__button'
title={intl.formatMessage(messages.refresh)}
aria-label={intl.formatMessage(messages.refresh)}
onClick={this.handleRefresh}
><Icon id='refresh' /></button >
)} )}
/> />
@ -79,7 +87,11 @@ class Reblogs extends ImmutablePureComponent {
bindToDocument={!multiColumn} bindToDocument={!multiColumn}
> >
{accountIds.map(id => {accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} /> (<AccountContainer
key={id}
id={id}
withNote={false}
/>),
)} )}
</ScrollableList > </ScrollableList >
</Column > </Column >

View File

@ -45,15 +45,15 @@ export default class DetailedStatus extends ImmutablePureComponent {
} }
e.stopPropagation(); e.stopPropagation();
} };
handleOpenVideo = (media, startTime) => { handleOpenVideo = (media, startTime) => {
this.props.onOpenVideo(media, startTime); this.props.onOpenVideo(media, startTime);
} };
handleExpandedToggle = () => { handleExpandedToggle = () => {
this.props.onToggleHidden(this.props.status); this.props.onToggleHidden(this.props.status);
} };
_measureHeight (heightJustChanged) { _measureHeight (heightJustChanged) {
if (this.props.measureHeight && this.node) { if (this.props.measureHeight && this.node) {
@ -68,7 +68,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
setRef = c => { setRef = c => {
this.node = c; this.node = c;
this._measureHeight(); this._measureHeight();
} };
componentDidUpdate (prevProps, prevState) { componentDidUpdate (prevProps, prevState) {
this._measureHeight(prevState.height !== this.state.height); this._measureHeight(prevState.height !== this.state.height);
@ -86,7 +86,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
} }
window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
} };
render () { render () {
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status; const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
@ -211,8 +211,14 @@ export default class DetailedStatus extends ImmutablePureComponent {
<div style={outerStyle}> <div style={outerStyle}>
<div ref={this.setRef} className={classNames('detailed-status', { compact })}> <div ref={this.setRef} className={classNames('detailed-status', { compact })}>
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'> <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div> <div className='detailed-status__display-avatar'><Avatar
<DisplayName account={status.get('account')} localDomain={this.props.domain} /> account={status.get('account')}
size={55}
/></div >
<DisplayName
account={status.get('account')}
localDomain={this.props.domain}
/>
</a> </a>
<StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} /> <StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />

View File

@ -551,8 +551,9 @@ class Status extends ImmutablePureComponent {
className={classNames('scrollable', { fullscreen })} className={classNames('scrollable', { fullscreen })}
ref={this.setRef} ref={this.setRef}
> >
{/*<h2 className='debug'>ancestors:</h2 >*/}
{ancestors} {ancestors}
{/*<h2 className='debug'>common:</h2 >*/}
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
<div <div
className={classNames('focusable', 'detailed-status__wrapper')} className={classNames('focusable', 'detailed-status__wrapper')}
@ -591,7 +592,7 @@ class Status extends ImmutablePureComponent {
/> />
</div > </div >
</HotKeys > </HotKeys >
{/*<h2 className='debug'>Descendants:</h2 >*/}
{descendants} {descendants}
</div > </div >
</ScrollContainer > </ScrollContainer >

View File

@ -26,8 +26,21 @@ export default class ActionsModal extends ImmutablePureComponent {
return ( return (
<li key={`${text}-${i}`}> <li key={`${text}-${i}`}>
<a href={href} target='_blank' rel='noopener noreferrer' onClick={this.props.onClick} data-index={i} className={classNames({ active })}> <a
{icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' inverted />} href={href}
target='_blank'
rel='noopener noreferrer'
onClick={this.props.onClick}
data-index={i}
className={classNames({ active })}
>
{icon && <IconButton
title={text}
icon={icon}
role='presentation'
tabIndex='-1'
inverted
/>}
<div > <div >
<div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div > <div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div >
<div >{meta}</div > <div >{meta}</div >
@ -35,7 +48,7 @@ export default class ActionsModal extends ImmutablePureComponent {
</a > </a >
</li > </li >
); );
} };
render () { render () {
const status = this.props.status && ( const status = this.props.status && (
@ -49,7 +62,10 @@ export default class ActionsModal extends ImmutablePureComponent {
<a href={this.props.status.getIn(['account', 'url'])} className='status__display-name'> <a href={this.props.status.getIn(['account', 'url'])} className='status__display-name'>
<div className='status__avatar'> <div className='status__avatar'>
<Avatar account={this.props.status.get('account')} size={48} /> <Avatar
account={this.props.status.get('account')}
size={55}
/>
</div> </div>
<DisplayName account={this.props.status.get('account')} /> <DisplayName account={this.props.status.get('account')} />

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import Button from '../../../components/button'; import Button from '../../../components/button';
import StatusContent from '../../../components/status_content'; import StatusContent from '../../../components/status_content';
import Avatar from '../../../components/avatar'; import Avatar from '../../../components/avatar';
@ -37,7 +37,7 @@ class BoostModal extends ImmutablePureComponent {
handleReblog = () => { handleReblog = () => {
this.props.onReblog(this.props.status); this.props.onReblog(this.props.status);
this.props.onClose(); this.props.onClose();
} };
handleAccountClick = (e) => { handleAccountClick = (e) => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
@ -45,11 +45,11 @@ class BoostModal extends ImmutablePureComponent {
this.props.onClose(); this.props.onClose();
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
} }
} };
setRef = (c) => { setRef = (c) => {
this.button = c; this.button = c;
} };
render () { render () {
const { status, intl } = this.props; const { status, intl } = this.props;
@ -66,7 +66,10 @@ class BoostModal extends ImmutablePureComponent {
<a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'> <a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'>
<div className='status__avatar'> <div className='status__avatar'>
<Avatar account={status.get('account')} size={48} /> <Avatar
account={status.get('account')}
size={55}
/>
</div> </div>
<DisplayName account={status.get('account')} /> <DisplayName account={status.get('account')} />

View File

@ -31,6 +31,8 @@ import NavigationPanel from './navigation_panel';
import detectPassiveEvents from 'detect-passive-events'; import detectPassiveEvents from 'detect-passive-events';
import { scrollRight } from '../../../scroll'; import { scrollRight } from '../../../scroll';
import LinkFooter from './link_footer';
import InstantMessaging from './messaging/instantMessaging';
const componentMap = { const componentMap = {
'COMPOSE' : Compose, 'COMPOSE' : Compose,
@ -157,10 +159,10 @@ class ColumnsArea extends ImmutablePureComponent {
const view = (index === columnIndex) ? const view = (index === columnIndex) ?
React.cloneElement(this.props.children) : React.cloneElement(this.props.children) :
<ColumnLoading (<ColumnLoading
title={title} title={title}
icon={icon} icon={icon}
/>; />);
return ( return (
<div <div
@ -187,12 +189,12 @@ class ColumnsArea extends ImmutablePureComponent {
const columnIndex = getIndex(this.context.router.history.location.pathname); const columnIndex = getIndex(this.context.router.history.location.pathname);
if (singleColumn) { if (singleColumn) {
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : (<Link
key='floating-action-button' key='floating-action-button'
to='/statuses/new' to='/statuses/new'
className='floating-action-button' className='floating-action-button'
aria-label={intl.formatMessage(messages.publish)} aria-label={intl.formatMessage(messages.publish)}
><Icon id='pencil' /></Link >; ><Icon id='pencil' /></Link >);
const content = columnIndex !== -1 ? ( const content = columnIndex !== -1 ? (
<ReactSwipeableViews <ReactSwipeableViews
@ -217,10 +219,13 @@ class ColumnsArea extends ImmutablePureComponent {
return ( return (
<div className='columns-area__panels'> <div className='columns-area__panels'>
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'> <div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
<div className='columns-area__panels__pane__inner'> <div className='columns-area__panels__pane__inner'>
<ComposePanel /> <ComposePanel />
</div > </div >
</div > </div >
<div className='columns-area__panels__main'> <div className='columns-area__panels__main'>
@ -233,8 +238,12 @@ class ColumnsArea extends ImmutablePureComponent {
<NavigationPanel /> <NavigationPanel />
</div > </div >
</div > </div >
<LinkFooter withHotkeys />
{floatingActionButton} {floatingActionButton}
<div className='hidden_nope'>
<InstantMessaging />
</div >
</div > </div >
); );
} }
@ -255,17 +264,20 @@ class ColumnsArea extends ImmutablePureComponent {
loading={this.renderLoading(column.get('id'))} loading={this.renderLoading(column.get('id'))}
error={this.renderError} error={this.renderError}
> >
{SpecificComponent => <SpecificComponent {SpecificComponent => (<SpecificComponent
columnId={column.get('uuid')} columnId={column.get('uuid')}
params={params} params={params}
multiColumn {...other} />} multiColumn {...other}
/>)}
</BundleContainer > </BundleContainer >
); );
})} })}
{React.Children.map(children, child => React.cloneElement(child, { multiColumn: true }))} {React.Children.map(children, child => React.cloneElement(child, { multiColumn: true }))}
</div > </div >
); )
;
} }
} }

View File

@ -2,14 +2,12 @@ import React from 'react';
import SearchContainer from 'mastodon/features/compose/containers/search_container'; import SearchContainer from 'mastodon/features/compose/containers/search_container';
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container'; import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
import NavigationContainer from 'mastodon/features/compose/containers/navigation_container'; import NavigationContainer from 'mastodon/features/compose/containers/navigation_container';
import LinkFooter from './link_footer';
const ComposePanel = () => ( const ComposePanel = () => (
<div className='compose-panel'> <div className='compose-panel'>
<SearchContainer openInRoute /> <SearchContainer openInRoute />
<NavigationContainer /> <NavigationContainer />
<ComposeFormContainer singleColumn /> <ComposeFormContainer singleColumn />
<LinkFooter withHotkeys />
</div > </div >
); );

View File

@ -2,10 +2,11 @@ import { connect } from 'react-redux';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom'; import { Link, NavLink } from 'react-router-dom';
import { invitesEnabled, repository, source_url, version } from 'mastodon/initial_state'; import { invitesEnabled, repository, source_url, version } from 'mastodon/initial_state';
import { logOut } from 'mastodon/utils/log_out'; import { logOut } from 'mastodon/utils/log_out';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import { isStaff } from '../../../initial_state';
const messages = defineMessages({ const messages = defineMessages({
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' }, logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
@ -22,15 +23,27 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}, },
}); });
// const displaythemetoggler = true;
export default @injectIntl export default @injectIntl
@connect(null, mapDispatchToProps) @connect(null, mapDispatchToProps)
class LinkFooter extends React.PureComponent { class LinkFooter extends React.PureComponent {
static propTypes = { static propTypes = {
enableChristmasSnow : PropTypes.bool,
minimumWeekToShowSnow: PropTypes.number,
snowActive : PropTypes.bool,
withHotkeys : PropTypes.bool, withHotkeys : PropTypes.bool,
snow : PropTypes.func,
themeIsDark : PropTypes.bool,
theme : PropTypes.string,
onLogout : PropTypes.func.isRequired, onLogout : PropTypes.func.isRequired,
intl : PropTypes.object.isRequired, intl : PropTypes.object.isRequired,
}; };
static defaultProps = {
enableChristmasSnow : true,
themeIsDark : true,
minimumWeekToShowSnow: 48,
};
handleLogoutClick = e => { handleLogoutClick = e => {
e.preventDefault(); e.preventDefault();
@ -41,35 +54,177 @@ class LinkFooter extends React.PureComponent {
return false; return false;
}; };
constructor(props) {
super(props);
Date.prototype.getWeek = function () {
var onejan = new Date(this.getFullYear(), 0, 1);
return Math.ceil((((this - onejan) / 86400000) + onejan.getDay() + 1) / 7);
};
var weekNumber = (new Date()).getWeek();
// display snow during the last two weeks of the year
const shouldWeDisplaySnow = (weekNumber > props.minimumWeekToShowSnow) && props.enableChristmasSnow;
this.state = {
enableChristmasSnow: shouldWeDisplaySnow,
theme : props.theme,
};
// make snow effect
if (shouldWeDisplaySnow) {
import('../../../utils/snowstorm-min')
.then((snowstorm) => {
Window.snowstorm = snowstorm.default;
this.state.snow = Window.snowstorm;
// snowstorm.start();
this.state.snowActive = true;
})
.catch((err) => console.error(err));
}
}
toggleSnow = () => {
if (this.state.snow) {
if (this.state.snowActive) {
this.state.snow.stop();
this.state.enableChristmasSnow = false;
} else {
this.state.snow.start();
this.state.enableChristmasSnow = true;
}
}
};
changeTheme(newTheme) {
console.log('change theme en ', newTheme);
}
render() { render() {
const HashTagNavlinks = ['Mastoart', 'OpenStreetMaps', 'Ironèmes', 'vélo'];
const { withHotkeys } = this.props; const { withHotkeys } = this.props;
var snowClasses = this.props.enableChristmasSnow ? 'snow-button active' : 'snow-button ';
const navToTags = HashTagNavlinks.map(element => {
return (
<li
className='tag-element btn-small btn'
key={element}
>
<NavLink
exact
activeClassName='active'
to={'/timelines/tag/' + element}
title='Mastoart'
>
#{element}
</NavLink >
</li >
);
});
return ( return (
<div className='getting-started__footer'>
<ul > <div className='links-started__footer desktop-only'>
<li >
<a href='https://liberapay.com/cipherbliss'>Supportez Cipherbliss</a > <div className='extras'>
</li >
<li > {/*<button className='mod-theme btn btn-block btn-small btn-primary pull-left'>*/}
{/* {this.themeIsDark ? (*/}
{/* <span*/}
{/* onClick={this.setState('theme', 'light')}*/}
{/* title='set light'*/}
{/* >*/}
{/* <i className='fa fa-pencil-o' /> to light*/}
{/* </span >*/}
{/* ) : (*/}
{/* <span*/}
{/* onClick={*/}
{/* this.changeTheme*/}
{/* }*/}
{/* title='set dark'*/}
{/* >*/}
{/* <i className='fa fa-pencil' /> to dark*/}
{/* </span >*/}
{/* )}*/}
{/*</button >*/}
{this.state.enableChristmasSnow && (
<div
onClick={this.toggleSnow}
className='christmas-snow'
>
<div className={snowClasses}>
<i
className='icon fa fa-snowflake-o'
aria-hidden='true'
/>
</div >
<div > Joyeuses fêtes!</div >
</div >
)}
{isStaff && (
<span className='staff-actions'>
<NavLink
exact
activeClassName='active'
to={'/tk-example/'}
title='tk example link'
>
example link
</NavLink >
<a
className='btn-warning'
href='/admin/tags?pending_review=1'
>
<i className='fa fa-fire' />
Trending hashtags
</a >
<a
className='btn-warning'
href='/admin/accounts'
>
<i className='fa fa-users' />
Comptes
</a >
</span >
)}
<br />
<div className='external-utilities'>
<a href='https://mastodon.cipherbliss.com/@tykayn'> <a href='https://mastodon.cipherbliss.com/@tykayn'>
<i className='fa fa-paper-plane' /> <i className='fa fa-paper-plane' />
contactez nous</a > contactez nous
</li > </a >
<li > <a href='https://liberapay.com/cipherbliss'><i className='fa fa-coffee' /> Supportez
Cipherbliss</a >
<a href='/admin/tags?pending_review=1'> <a href='https://peertube.cipherbliss.com'> <i className='fa fa-play ' /> Videos</a >
<i className='fa fa-fire' /> <a href='https://framadate.org/'> <i className='fa fa-calendar' /> FramaDate</a >
Trending hashtags</a > <a href='https://framapad.org/'> <i className='fa fa-file-text' /> Pad</a >
<a href='https://framagit.org/tykayn/mastodon'> <i className='fa fa-gitlab' /> Source</a >
<hr /> <hr />
</li > <div className='suggested-tags'>
{invitesEnabled && <li ><a <ul >
{navToTags}
</ul >
</div >
</div >
</div >
<ul >
{invitesEnabled && (
<li >
<a
href='/invites' href='/invites'
target='_blank' target='_blank'
><FormattedMessage >
<FormattedMessage
id='getting_started.invite' id='getting_started.invite'
defaultMessage='Invite people' defaultMessage='Invite people'
/> ·</a > /> ·</a >
</li >} </li >
)}
{withHotkeys && <li ><Link to='/keyboard-shortcuts'> {withHotkeys && <li ><Link to='/keyboard-shortcuts'>
<FormattedMessage <FormattedMessage
id='navigation_bar.keyboard_shortcuts' id='navigation_bar.keyboard_shortcuts'

View File

@ -1,147 +0,0 @@
import ImmutablePureComponent from 'react-immutable-pure-component';
export default class Messaging extends ImmutablePureComponent {
// static propTypes = {
// following : ImmutablePropTypes.list,
// conversations: ImmutablePropTypes.list,
// newMessage : ImmutablePropTypes.string,
// };
// openConversationWith(account) {
// let conversationFound = account;
// if conversation exist, focus on it
// if (conversationFound) {
//
// } else {
//
// }
// else, create conversation and focus on it
// };
submitCompose() {
};
constructor() {
super();
// this.props.newMessage = 'meh';
// this.props.conversations = [
// {
// withAccount: '@machin',
// messages : [],
// opened : true,
// },
// {
// withAccount: '@chuck',
// messages : [],
// opened : false,
// },
// ];
// this.props.following = [
// { username: 'wulfila', handle: '@wulfila' },
// { username: 'machin', handle: '@machin' },
// { username: 'chuck norris', handle: '@chuck' },
// ];
}
render() {
// const contactlist = null;
return (
<div>
messagerie todo
</div>
);
// const contactlist = this.props.following.foreEach(elem => (
// <li className='user-item'>
// <div
// className='username'
// onClick={this.openConversationWith(elem.username)}
// >
// Machin
// </div >
// <div className='last-active'>3 min</div >
// </li >
// ));
// return (
// <div className='messaging-container'>
// <div className='messaging-box'>
// <div className='title'>
// <i
// role='img'
// className='fa fa-envelope column-header__icon fa-fw'
// />
// Messaging box
// </div >
// <div className='user-list column-header'>
// <h2 className='title'>User list</h2 >
// <ul >
// {contactlist}
// </ul >
// </div >
//
// </div >
// <div className='conversations_list'>
// <ul >
// <li className='conversations_item has-new-message'>
// <div className='title'>
// <i
// role='img'
// className='fa fa-envelope column-header__icon fa-fw'
// />
// Un Gens
// <span className='new-message-counter'>
// (3)</span >
// <button className='btn-small'>
// <i
// role='img'
// className='fa fa-caret-down column-header__icon fa-fw'
// />
// </button >
// </div >
// <div className='conversation_stream'>
// <div className='message theirs'>
// <p >oh hello there! 😋 </p >
// <div className='arrow-down' />
// </div >
// <div className='message mine'>
// <p >General Emoji</p >
// <div className='arrow-down' />
// </div >
// <div className='message theirs'>
// <p >we just achieved comedy</p >
// <div className='arrow-down' />
// </div >
// </div >
// <div className='conversation_input'>
// <form
// action='#'
// // onSubmit={this.submitCompose()}
// >
// {/*value={this.newMessage.toString()}*/}
// <textarea
// name='messager'
// id=''
// cols='30'
// rows='10'
// className='messager-textarea'
// placeholder='allez dis nous tout'
//
// />
// <input
// type='submit'
// name='submit'
// value='Send'
// />
// </form >
// </div >
// </li >
// </ul >
// </div >
// </div >
// );
}
};

View File

@ -0,0 +1,65 @@
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import React from 'react';
import Permalink from '../../../../components/permalink';
export default class Contact extends ImmutablePureComponent {
static propTypes = {
account: PropTypes.object,
};
static defaultProps = {};
constructor(props) {
super(props);
this.state = {
account: this.props.account,
};
}
openConversationWithAccount = (accountId) => {
dispatchEvent({ type: 'openConversation', target: accountId });
};
render() {
const account = this.props.account;
return (
<div
className='contact media'
onClick={this.openConversationWithAccount}
>
<div className='name media-left'>
<Permalink
key={account.id}
className='account__display-name'
title={account.acct}
href={account.url}
to={`/accounts/${account.id}`}
>
<div className='avatar image is-32x32'>
<img
className='is-rounded'
src={this.props.account.avatar}
alt='avatar'
/>
</div >
</Permalink >
</div >
<div className='media-content'>
<div className='username'>
{this.props.account.username}
</div >
</div >
<div className='media-right'>
<div className='last-seen'>
<i className='fa fa-clock-o' />
</div >
</div >
</div >
);
}
}

View File

@ -0,0 +1,142 @@
import React from 'react';
import PropTypes from 'prop-types';
import accounts, { me } from '../../../../initial_state';
import api from '../../../../api';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { mockContactList } from './mocks/mockContactList';
import Contact from './Contact';
import classNames from 'classnames';
export default class ContactsList extends ImmutablePureComponent {
static propTypes = {
myAccount : PropTypes.array,
showList : PropTypes.bool,
contactList : PropTypes.array,
conversationList: PropTypes.array,
following_count : PropTypes.number,
};
static defaultProps = {
showList : true,
myAccount : null,
userID : me,
following_count : 0,
contactList : mockContactList,
conversationList: mockContactList,
};
constructor(props) {
super(props);
this.state = {
following_count : accounts.accounts[me].following_count,
showList : props.showList,
myAccount : accounts.accounts[me],
contactList : mockContactList,
conversationList: mockContactList,
};
// this.fetchContacts(1);
}
submitCompose() {
console.log('submit message');
}
toggleList = () => {
this.setState((state) => {
return {
showList: !state.showList,
};
});
};
/**
* find followed accounts
* @param AccountID
* @returns {Promise<AxiosResponse<T> | void>}
*/
fetchContacts = (AccountID = me) => {
return api(this.getState()).get('/api/v1/accounts/' + AccountID + '/following').then(resp => {
console.log('resp', resp);
}).catch(err => console.error('err', err));
};
render() {
let renderedList = (
<span >no contacts</span >
);
if (this.props.contactList) {
renderedList = this.props.contactList.map(account => {
return (
<li
className='contact-item'
key={account.id}
>
<Contact account={account} />
</li >
);
});
}
const showListClass = (this.state.showList ? 'active' : 'inactive');
const classList = 'btn btn-primary toggle-list ' + showListClass;
return (
<div className='messaging-container'>
<link
rel='stylesheet'
type='text/css'
media='screen'
href='https://cdn.conversejs.org/6.0.0/dist/converse.min.css'
/>
{/*<script*/}
{/* src='https://cdn.conversejs.org/6.0.0/dist/converse.min.js'*/}
{/* charSet='utf-8'*/}
{/*></script >*/}
{/*<script >*/}
{/* converse.initialize({*/}
{/* bosh_service_url : 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes*/}
{/* show_controlbox_by_default: true*/}
{/*});*/}
{/*</script >*/}
<div
className={classNames('messaging-box ', {
'active' : this.state.showList,
'inactive': !this.state.showList,
})}
>
<div className='card-title '>
<i
role='img'
className='fa fa-comment column-header__icon fa-fw'
/>
{this.state.following_count} contacts
<button
className={classList}
onClick={this.toggleList}
>
{this.state.showList && (
<i className='fa fa-caret-up' />
)}
{!this.state.showList && (
<i className='fa fa-caret-left' />
)}
</button >
</div >
<div className='user-list'>
{this.state.showList && (
<div className='contact-list-container'>
<ul className='contact-list'>
{renderedList}
</ul >
</div >
)}
</div >
</div >
</div >
);
};
}

View File

@ -0,0 +1,116 @@
import React from 'react';
import PropTypes from 'prop-types';
import ConversationStream from './conversation-stream';
/**
* a conversation between the current logged in user and one recipient
*/
export default class ConversationItem extends React.PureComponent {
static propTypes = {
messages : PropTypes.array, // our and their message sorted chronologically
recipient : PropTypes.any, // account of the person we talk to, not current logged in account
newMessages: PropTypes.number,
displayed : PropTypes.bool,
};
static defaultProps = {
newMessages: 0,
displayed : true,
};
following = [];
constructor(props) {
super(props);
console.log('props', props);
this.state = {
composeMessage: '',
displayed : this.props.displayed,
newMessages : this.props.newMessages,
isFocused : false,
};
}
submitCompose = (e) => {
e.preventDefault();
console.log('submit');
};
toggleVisibility = () => {
this.setState({ 'displayed': !this.state.displayed });
};
toggleFocused = () => {
this.setState({ 'isFocused': !this.state.isFocused });
};
handleChange = (e) => {
e.preventDefault();
console.log('e', e);
};
render() {
const hasNewClass = this.state.newMessages ? 'has-new-message' : 'nothing-new';
const isVisible = this.state.displayed ? 'displayed' : 'hidden';
const isFocused = this.state.isFocused ? 'isFocused' : 'not-focused';
const list = (
<li className={'conversation-item ' + hasNewClass + ' ' + isVisible + ' ' + isFocused}>
<div className='top-title'>
<i
role='img'
className='fa fa-comment-o column-header__icon fa-fw'
/>
<span className='username'>
{this.props.recipient.username}
</span >
{/*<Contact account={this.props.recipient} />*/}
{this.props.newMessages > 0 && (
<span className='new-message-counter'>
({this.props.newMessages})
</span >
)}
<button
className='btn btn-small'
onClick={this.toggleVisibility}
>
<i
role='img'
className='fa fa-caret-down column-header__icon fa-fw'
/>
</button >
</div >
<ConversationStream messages={this.props.messages} />
<div className='conversation_input'>
<form
action='#'
onSubmit={this.submitCompose}
>
<textarea
name='messager'
id=''
cols='15'
rows='3'
className='messager-textarea'
placeholder='allez dis nous tout'
onFocusCapture={this.toggleFocused}
onBlurCapture={this.toggleFocused}
onChange={this.handleChange}
/>
<input
type='submit'
name='submit'
value='Send'
/>
</form >
</div >
</li >
);
return (
<ul >
{list}
</ul >
);
}
}

View File

@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
export default class ConversationStream extends React.PureComponent {
static propTypes = {
messages: PropTypes.array,
};
render() {
let messagesLists = (
<div className='no_messages'>
no messages
</div >
);
if (this.props.messages) {
messagesLists = this.props.messages.map(message => {
return (
<li
className={'message ' + message.who}
key={message.id}
>
<p >{message.text}</p >
<div className='arrow-down' />
</li >
);
});
}
return (
<div className='conversation-stream'>
<div className='messages'>
{messagesLists}
</div >
</div >
);
}
}

View File

@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
import ConversationItem from './conversation-item';
import { mockRecipient, mockRecipient2 } from './mocks/mockConversation';
export default class ConversationStack extends React.Component {
static propTypes = {
conversations: PropTypes.array,
};
static defaultProps = {
conversations: [mockRecipient, mockRecipient2],
};
openConversationWith(account) {
console.log('openConversationWith name', account.username);
}
render() {
let list = this.props.conversations.map(recipient => {
console.log('recipient', recipient);
recipient = recipient[0];
return (
<li
className='conversation-item-wrapper'
key={'wrapper' + recipient.id}
>
<ConversationItem
recipient={recipient}
messages={recipient.messages}
key={'ConversationItem_' + recipient.id}
onClick={this.openConversationWith}
/>
</li >
);
},
)
;
return (
<ul className='stack conversations_list'>
{list}
</ul >
);
};
}

View File

@ -0,0 +1,60 @@
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ContactsList from './contacts-list';
import ConversationStack from './conversationStack';
const mapStateToProps = (state, props) => ({
isAccount : !!state.getIn(['accounts', props.params.accountId]),
accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
hasMore : !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']),
blockedBy : state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
});
// @connect(mapStateToProps)
/**
* main component for IM, gathers contact list and list of conversations
*/
export default class InstantMessaging extends ImmutablePureComponent {
static propTypes = {
// params : PropTypes.object.isRequired,
dispatch : PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds : ImmutablePropTypes.list,
hasMore : PropTypes.bool,
blockedBy : PropTypes.bool,
isAccount : PropTypes.bool,
multiColumn : PropTypes.bool,
};
// static defaultProps = {
// threadsCompile: true,
// };
// openConversationWith(account) {
// let conversationFound = account;
// if conversation exist, focus on it
// if (conversationFound) {
//
// } else {
//
// }
// else, create conversation and focus on it
// };
// submitCompose() {
//
// };
render() {
return (
<div className='main-instant-messaging'>
<ContactsList />
<ConversationStack />
</div >
);
}
};

View File

@ -0,0 +1,69 @@
export const mockContactList = [{
'key' : '2',
'id' : '2',
'username' : 'demoguy',
'acct' : 'demoguy',
'display_name' : '',
'locked' : false,
'bot' : false,
'discoverable' : null,
'group' : false,
'created_at' : '2019-12-18T11:02:26.494Z',
'note' : '<p></p>',
'url' : 'http://localhost:3000/@demoguy',
'avatar' : 'http://localhost:3000/avatars/original/missing.png',
'avatar_static' : 'http://localhost:3000/avatars/original/missing.png',
'header' : 'http://localhost:3000/headers/original/missing.png',
'header_static' : 'http://localhost:3000/headers/original/missing.png',
'followers_count' : 1,
'following_count' : 1,
'statuses_count' : 8,
'last_status_at' : '2019-12-23T15:20:31.575Z',
'emojis' : [],
'fields' : [],
'display_name_html': 'demoguy',
'note_emojified' : '<p></p>',
}, {
'key' : '1',
'id' : '1',
'username' : 'admin',
'acct' : 'admin',
'display_name' : '',
'locked' : true,
'bot' : false,
'discoverable' : true,
'group' : false,
'created_at' : '2019-12-10T13:19:44.106Z',
'note' : '<p></p>',
'url' : 'http://localhost:3000/@admin',
'avatar' : 'http://localhost:3000/system/accounts/avatars/000/000/001/original/a1497a4af5fd8616.png?1576254102',
'avatar_static' : 'http://localhost:3000/system/accounts/avatars/000/000/001/original/a1497a4af5fd8616.png?1576254102',
'header' : 'http://localhost:3000/system/accounts/headers/000/000/001/original/f13ebf964b09ed26.png?1576254102',
'header_static' : 'http://localhost:3000/system/accounts/headers/000/000/001/original/f13ebf964b09ed26.png?1576254102',
'followers_count' : 4,
'following_count' : 3,
'statuses_count' : 31,
'last_status_at' : '2020-01-04T15:13:32.824Z',
'emojis' : [],
'fields' : [
{
'name' : 'sssss',
'value' : 'muuuuu',
'verified_at' : null,
'name_emojified' : 'sssss',
'value_emojified': 'muuuuu',
'value_plain' : 'muuuuu',
},
],
'display_name_html': 'admin',
'note_emojified' : '<p></p>',
}];
export const mockContactListShort = [{
'key' : '2',
'id' : '2',
'username': 'demoguy',
}, {
'key' : '1',
'id' : '1',
'username': 'admin',
}];

View File

@ -0,0 +1,33 @@
export const mockMessages = [
{ id: 0, text: 'oh hello there! 😋 ', who: 'theirs' },
{ id: 1, text: 'General Emoji', who: 'ours' },
{ id: 2, text: 'we just achieved comedy', who: 'theirs' },
]
;
export const mockMessages2 = [
{ id: 0, text: 'oh oh oh ', who: 'theirs' },
{ id: 1, text: 'General Emoji', who: 'ours' },
{ id: 2, text: 'DANGER!!', who: 'theirs' },
{ id: 3, text: 'JUST KIDDING WILL ROBINSON.', who: 'theirs' },
]
;
export const mockRecipient = [
{
id : 3,
username : 'chuck norris',
newMessages: 5,
messages : mockMessages,
},
]
;
export const mockRecipient2 = [
{
id : 4,
username : 'the Bo Botte',
newMessages: 0,
messages : mockMessages2,
},
]
;

View File

@ -8,11 +8,9 @@ import FollowRequestsNavLink from './follow_requests_nav_link';
import ListPanel from './list_panel'; import ListPanel from './list_panel';
import TrendsContainer from 'mastodon/features/getting_started/containers/trends_container'; import TrendsContainer from 'mastodon/features/getting_started/containers/trends_container';
const showMessaging = true;
const NavigationPanel = () => ( const NavigationPanel = () => (
<div className='navigation-panel'> <div className='navigation-panel'>
<div className='small-texts timelines'>
<NavLink <NavLink
className='column-link column-link--transparent' className='column-link column-link--transparent'
to='/timelines/home' to='/timelines/home'
@ -26,18 +24,6 @@ const NavigationPanel = () => (
id='tabs_bar.home' id='tabs_bar.home'
defaultMessage='Home' defaultMessage='Home'
/></NavLink > /></NavLink >
<NavLink
className='column-link column-link--transparent'
to='/notifications'
data-preview-title-id='column.notifications'
data-preview-icon='bell'
><NotificationsCounterIcon
className='column-link__icon'
/><FormattedMessage
id='tabs_bar.notifications'
defaultMessage='Notifications'
/></NavLink >
<FollowRequestsNavLink />
<NavLink <NavLink
className='column-link column-link--transparent' className='column-link column-link--transparent'
to='/timelines/public/local' to='/timelines/public/local'
@ -51,6 +37,7 @@ const NavigationPanel = () => (
id='tabs_bar.local_timeline' id='tabs_bar.local_timeline'
defaultMessage='Local' defaultMessage='Local'
/></NavLink > /></NavLink >
<NavLink <NavLink
className='column-link column-link--transparent' className='column-link column-link--transparent'
exact exact
@ -65,6 +52,20 @@ const NavigationPanel = () => (
id='tabs_bar.federated_timeline' id='tabs_bar.federated_timeline'
defaultMessage='Federated' defaultMessage='Federated'
/></NavLink > /></NavLink >
</div >
<div className='spacer'></div >
<NavLink
className='column-link column-link--transparent'
to='/notifications'
data-preview-title-id='column.notifications'
data-preview-icon='bell'
><NotificationsCounterIcon
className='column-link__icon'
/><FormattedMessage
id='tabs_bar.notifications'
defaultMessage='Notifications'
/></NavLink >
<NavLink <NavLink
className='column-link column-link--transparent' className='column-link column-link--transparent'
to='/timelines/direct' to='/timelines/direct'
@ -76,6 +77,10 @@ const NavigationPanel = () => (
id='navigation_bar.direct' id='navigation_bar.direct'
defaultMessage='Direct messages' defaultMessage='Direct messages'
/></NavLink > /></NavLink >
<FollowRequestsNavLink />
<div className='spacer'></div >
<NavLink <NavLink
className='column-link column-link--transparent' className='column-link column-link--transparent'
to='/favourites' to='/favourites'
@ -151,8 +156,6 @@ const NavigationPanel = () => (
{showTrends && <div className='flex-spacer' />} {showTrends && <div className='flex-spacer' />}
{showTrends && <TrendsContainer />} {showTrends && <TrendsContainer />}
{/*{showMessaging && <Messaging />}*/}
{/*<Messaging />*/}
</div > </div >
); );

View File

@ -8,12 +8,74 @@ import Icon from 'mastodon/components/icon';
import NotificationsCounterIcon from './notifications_counter_icon'; import NotificationsCounterIcon from './notifications_counter_icon';
export const links = [ export const links = [
<NavLink className='tabs-bar__link' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>, <NavLink
<NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>, className='tabs-bar__link'
<NavLink className='tabs-bar__link' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>, to='/timelines/home'
<NavLink className='tabs-bar__link' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>, data-preview-title-id='column.home'
<NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><Icon id='search' fixedWidth /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>, data-preview-icon='home'
<NavLink className='tabs-bar__link' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>, ><Icon
id='home'
fixedWidth
/><FormattedMessage
id='tabs_bar.home'
defaultMessage='Home'
/></NavLink >,
<NavLink
className='tabs-bar__link'
to='/notifications'
data-preview-title-id='column.notifications'
data-preview-icon='bell'
><NotificationsCounterIcon /><FormattedMessage
id='tabs_bar.notifications'
defaultMessage='Notifications'
/></NavLink >,
<NavLink
className='tabs-bar__link'
to='/timelines/public/local'
data-preview-title-id='column.community'
data-preview-icon='users'
><Icon
id='users'
fixedWidth
/><FormattedMessage
id='tabs_bar.local_timeline'
defaultMessage='Local'
/></NavLink >,
<NavLink
className='tabs-bar__link'
exact
to='/timelines/public'
data-preview-title-id='column.public'
data-preview-icon='globe'
><Icon
id='globe'
fixedWidth
/><FormattedMessage
id='tabs_bar.federated_timeline'
defaultMessage='Federated'
/></NavLink >,
<NavLink
className='tabs-bar__link optional'
to='/search'
data-preview-title-id='tabs_bar.search'
data-preview-icon='bell'
><Icon
id='search'
fixedWidth
/><FormattedMessage
id='tabs_bar.search'
defaultMessage='Search'
/></NavLink >,
<NavLink
className='tabs-bar__link'
style={{ flexGrow: '0', flexBasis: '30px' }}
to='/getting-started'
data-preview-title-id='getting_started.heading'
data-preview-icon='bars'
><Icon
id='bars'
fixedWidth
/></NavLink >,
]; ];
export function getIndex(path) { export function getIndex(path) {
@ -31,11 +93,11 @@ class TabsBar extends React.PureComponent {
static propTypes = { static propTypes = {
intl : PropTypes.object.isRequired, intl : PropTypes.object.isRequired,
history: PropTypes.object.isRequired, history: PropTypes.object.isRequired,
} };
setRef = ref => { setRef = ref => {
this.node = ref; this.node = ref;
} };
handleClick = (e) => { handleClick = (e) => {
// Only apply optimization for touch devices, which we assume are slower // Only apply optimization for touch devices, which we assume are slower
@ -50,7 +112,6 @@ class TabsBar extends React.PureComponent {
const nextTab = tabs.find(tab => tab.contains(e.target)); const nextTab = tabs.find(tab => tab.contains(e.target));
const { props: { to } } = links[Array(...this.node.childNodes).indexOf(nextTab)]; const { props: { to } } = links[Array(...this.node.childNodes).indexOf(nextTab)];
if (currentTab !== nextTab) { if (currentTab !== nextTab) {
if (currentTab) { if (currentTab) {
currentTab.classList.remove('active'); currentTab.classList.remove('active');
@ -67,18 +128,27 @@ class TabsBar extends React.PureComponent {
}); });
} }
} };
render() { render() {
const { intl: { formatMessage } } = this.props; const { intl: { formatMessage } } = this.props;
return ( return (
<div className='tabs-bar__wrapper'> <div className='tabs-bar__wrapper'>
<nav className='tabs-bar' ref={this.setRef}>
{links.map(link => React.cloneElement(link, { key: link.props.to, onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))} <nav
className='tabs-bar'
ref={this.setRef}
>
{links.map(link => React.cloneElement(link, {
key : link.props.to,
onClick : this.handleClick,
'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }),
}))}
</nav > </nav >
<div id='tabs-bar__portal' /> <div id='tabs-bar__portal' />
</div > </div >
); );
} }

View File

@ -10,54 +10,54 @@ import LoadingBarContainer from './containers/loading_bar_container';
import ModalContainer from './containers/modal_container'; import ModalContainer from './containers/modal_container';
import { isMobile } from '../../is_mobile'; import { isMobile } from '../../is_mobile';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { uploadCompose, resetCompose } from '../../actions/compose'; import { resetCompose, uploadCompose } from '../../actions/compose';
import { expandHomeTimeline } from '../../actions/timelines'; import { expandHomeTimeline } from '../../actions/timelines';
import { expandNotifications } from '../../actions/notifications'; import { expandNotifications } from '../../actions/notifications';
import { fetchFilters } from '../../actions/filters'; import { fetchFilters } from '../../actions/filters';
import { clearHeight } from '../../actions/height_cache'; import { clearHeight } from '../../actions/height_cache';
import { focusApp, unfocusApp } from 'mastodon/actions/app'; import { focusApp, unfocusApp } from 'mastodon/actions/app';
import { submitMarkers } from 'mastodon/actions/markers'; import { submitMarkers } from 'mastodon/actions/markers';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; import { WrappedRoute, WrappedSwitch } from './util/react_router_helpers';
import UploadArea from './components/upload_area'; import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container'; import ColumnsAreaContainer from './containers/columns_area_container';
import DocumentTitle from './components/document_title'; import DocumentTitle from './components/document_title';
import { import {
Compose,
Status,
GettingStarted,
KeyboardShortcuts,
PublicTimeline,
CommunityTimeline,
AccountTimeline,
AccountGallery, AccountGallery,
HomeTimeline, AccountTimeline,
Blocks,
BookmarkedStatuses,
CommunityTimeline,
Compose,
Directory,
DirectTimeline,
DomainBlocks,
FavouritedStatuses,
Favourites,
Followers, Followers,
Following, Following,
Reblogs,
Favourites,
DirectTimeline,
HashtagTimeline,
Notifications,
FollowRequests, FollowRequests,
GenericNotFound, GenericNotFound,
FavouritedStatuses, GettingStarted,
BookmarkedStatuses, HashtagTimeline,
ListTimeline, HomeTimeline,
Blocks, KeyboardShortcuts,
DomainBlocks,
Mutes,
PinnedStatuses,
Lists, Lists,
ListTimeline,
Mutes,
Notifications,
PinnedStatuses,
PublicTimeline,
Reblogs,
Search, Search,
Directory, Status,
} from './util/async-components'; } from './util/async-components';
import { me, forceSingleColumn } from '../../initial_state'; import { forceSingleColumn, me } from '../../initial_state';
import { previewState as previewMediaState } from './components/media_modal'; import { previewState as previewMediaState } from './components/media_modal';
import { previewState as previewVideoState } from './components/video_modal'; import { previewState as previewVideoState } from './components/video_modal';
// Dummy import, to make sure that <Status /> ends up in the application bundle. // Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles. // Without this it ends up in ~8 very commonly used bundles.
import '../../components/status'; import '../../components/status';
import InstantMessaging from './components/messaging/instantMessaging';
const messages = defineMessages({ const messages = defineMessages({
beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' }, beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' },
@ -114,6 +114,12 @@ class SwitchingColumnsArea extends React.PureComponent {
state = { state = {
mobile: isMobile(window.innerWidth), mobile: isMobile(window.innerWidth),
}; };
handleLayoutChange = debounce(() => {
// The cached heights are no longer accurate, invalidate
this.props.onLayoutChange();
}, 500, {
trailing: true,
});
componentWillMount() { componentWillMount() {
window.addEventListener('resize', this.handleResize, { passive: true }); window.addEventListener('resize', this.handleResize, { passive: true });
@ -146,13 +152,6 @@ class SwitchingColumnsArea extends React.PureComponent {
return location.state !== previewMediaState && location.state !== previewVideoState; return location.state !== previewMediaState && location.state !== previewVideoState;
} }
handleLayoutChange = debounce(() => {
// The cached heights are no longer accurate, invalidate
this.props.onLayoutChange();
}, 500, {
trailing: true,
})
handleResize = () => { handleResize = () => {
const mobile = isMobile(window.innerWidth); const mobile = isMobile(window.innerWidth);
@ -163,59 +162,217 @@ class SwitchingColumnsArea extends React.PureComponent {
} else { } else {
this.handleLayoutChange(); this.handleLayoutChange();
} }
} };
setRef = c => { setRef = c => {
if (c) { if (c) {
this.node = c.getWrappedInstance(); this.node = c.getWrappedInstance();
} }
} };
render() { render() {
const { children } = this.props; const { children } = this.props;
const { mobile } = this.state; const { mobile } = this.state;
const singleColumn = forceSingleColumn || mobile; const singleColumn = forceSingleColumn || mobile;
const redirect = singleColumn ? <Redirect from='/' to='/timelines/home' exact /> : <Redirect from='/' to='/getting-started' exact />; const redirect = singleColumn ? (<Redirect
from='/'
to='/timelines/home'
exact
/>) : (<Redirect
from='/'
to='/getting-started'
exact
/>);
return ( return (
<ColumnsAreaContainer ref={this.setRef} singleColumn={singleColumn}> <ColumnsAreaContainer
ref={this.setRef}
singleColumn={singleColumn}
>
<WrappedSwitch > <WrappedSwitch >
{redirect} {redirect}
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} /> <WrappedRoute
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} /> path='/tk-example'
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> component={InstantMessaging}
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> content={children}
<WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> />
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> path='/getting-started'
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> component={GettingStarted}
content={children}
/>
<WrappedRoute
path='/keyboard-shortcuts'
component={KeyboardShortcuts}
content={children}
/>
<WrappedRoute
path='/timelines/home'
component={HomeTimeline}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/timelines/public'
exact
component={PublicTimeline}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/timelines/public/local'
exact
component={CommunityTimeline}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/timelines/direct'
component={DirectTimeline}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/timelines/tag/:id'
component={HashtagTimeline}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/timelines/list/:id'
component={ListTimeline}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute path='/notifications' component={Notifications} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> path='/notifications'
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} /> component={Notifications}
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/favourites'
component={FavouritedStatuses}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/bookmarks'
component={BookmarkedStatuses}
content={children}
/>
<WrappedRoute
path='/pinned'
component={PinnedStatuses}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute path='/search' component={Search} content={children} /> <WrappedRoute
<WrappedRoute path='/directory' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> path='/search'
component={Search}
content={children}
/>
<WrappedRoute
path='/directory'
component={Directory}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute path='/statuses/new' component={Compose} content={children} /> <WrappedRoute
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> path='/statuses/new'
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> component={Compose}
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> content={children}
/>
<WrappedRoute
path='/statuses/:statusId'
exact
component={Status}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/statuses/:statusId/reblogs'
component={Reblogs}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/statuses/:statusId/favourites'
component={Favourites}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }} /> path='/accounts/:accountId'
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> exact
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> component={AccountTimeline}
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/accounts/:accountId/with_replies'
component={AccountTimeline}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }}
/>
<WrappedRoute
path='/accounts/:accountId/followers'
component={Followers}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/accounts/:accountId/following'
component={Following}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/accounts/:accountId/media'
component={AccountGallery}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute
<WrappedRoute path='/blocks' component={Blocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> path='/follow_requests'
<WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> component={FollowRequests}
<WrappedRoute path='/mutes' component={Mutes} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> content={children}
<WrappedRoute path='/lists' component={Lists} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/blocks'
component={Blocks}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/domain_blocks'
component={DomainBlocks}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/mutes'
component={Mutes}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute
path='/lists'
component={Lists}
content={children}
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
/>
<WrappedRoute component={GenericNotFound} content={children} /> <WrappedRoute
component={GenericNotFound}
content={children}
/>
</WrappedSwitch > </WrappedSwitch >
</ColumnsAreaContainer > </ColumnsAreaContainer >
); );
@ -259,20 +416,20 @@ class UI extends React.PureComponent {
// but we set user-friendly message for other browsers, e.g. Edge. // but we set user-friendly message for other browsers, e.g. Edge.
e.returnValue = intl.formatMessage(messages.beforeUnload); e.returnValue = intl.formatMessage(messages.beforeUnload);
} }
} };
handleWindowFocus = () => { handleWindowFocus = () => {
this.props.dispatch(focusApp()); this.props.dispatch(focusApp());
} };
handleWindowBlur = () => { handleWindowBlur = () => {
this.props.dispatch(unfocusApp()); this.props.dispatch(unfocusApp());
} };
handleLayoutChange = () => { handleLayoutChange = () => {
// The cached heights are no longer accurate, invalidate // The cached heights are no longer accurate, invalidate
this.props.dispatch(clearHeight()); this.props.dispatch(clearHeight());
} };
handleDragEnter = (e) => { handleDragEnter = (e) => {
e.preventDefault(); e.preventDefault();
@ -288,7 +445,7 @@ class UI extends React.PureComponent {
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore) { if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore) {
this.setState({ draggingOver: true }); this.setState({ draggingOver: true });
} }
} };
handleDragOver = (e) => { handleDragOver = (e) => {
if (this.dataTransferIsText(e.dataTransfer)) return false; if (this.dataTransferIsText(e.dataTransfer)) return false;
@ -303,7 +460,7 @@ class UI extends React.PureComponent {
} }
return false; return false;
} };
handleDrop = (e) => { handleDrop = (e) => {
if (this.dataTransferIsText(e.dataTransfer)) return; if (this.dataTransferIsText(e.dataTransfer)) return;
@ -316,7 +473,7 @@ class UI extends React.PureComponent {
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore) { if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore) {
this.props.dispatch(uploadCompose(e.dataTransfer.files)); this.props.dispatch(uploadCompose(e.dataTransfer.files));
} }
} };
handleDragLeave = (e) => { handleDragLeave = (e) => {
e.preventDefault(); e.preventDefault();
@ -329,15 +486,15 @@ class UI extends React.PureComponent {
} }
this.setState({ draggingOver: false }); this.setState({ draggingOver: false });
} };
dataTransferIsText = (dataTransfer) => { dataTransferIsText = (dataTransfer) => {
return (dataTransfer && Array.from(dataTransfer.types).filter((type) => type === 'text/plain').length === 1); return (dataTransfer && Array.from(dataTransfer.types).filter((type) => type === 'text/plain').length === 1);
} };
closeUploadModal = () => { closeUploadModal = () => {
this.setState({ draggingOver: false }); this.setState({ draggingOver: false });
} };
handleServiceWorkerPostMessage = ({ data }) => { handleServiceWorkerPostMessage = ({ data }) => {
if (data.type === 'navigate') { if (data.type === 'navigate') {
@ -345,7 +502,7 @@ class UI extends React.PureComponent {
} else { } else {
console.warn('Unknown message type:', data.type); console.warn('Unknown message type:', data.type);
} }
} };
componentWillMount() { componentWillMount() {
window.addEventListener('focus', this.handleWindowFocus, false); window.addEventListener('focus', this.handleWindowFocus, false);
@ -392,7 +549,7 @@ class UI extends React.PureComponent {
setRef = c => { setRef = c => {
this.node = c; this.node = c;
} };
handleHotkeyNew = e => { handleHotkeyNew = e => {
e.preventDefault(); e.preventDefault();
@ -402,7 +559,7 @@ class UI extends React.PureComponent {
if (element) { if (element) {
element.focus(); element.focus();
} }
} };
handleHotkeySearch = e => { handleHotkeySearch = e => {
e.preventDefault(); e.preventDefault();
@ -412,12 +569,12 @@ class UI extends React.PureComponent {
if (element) { if (element) {
element.focus(); element.focus();
} }
} };
handleHotkeyForceNew = e => { handleHotkeyForceNew = e => {
this.handleHotkeyNew(e); this.handleHotkeyNew(e);
this.props.dispatch(resetCompose()); this.props.dispatch(resetCompose());
} };
handleHotkeyFocusColumn = e => { handleHotkeyFocusColumn = e => {
const index = (e.key * 1) + 1; // First child is drawer, skip that const index = (e.key * 1) + 1; // First child is drawer, skip that
@ -435,7 +592,7 @@ class UI extends React.PureComponent {
status.focus(); status.focus();
} }
} }
} };
handleHotkeyBack = () => { handleHotkeyBack = () => {
if (window.history && window.history.length === 1) { if (window.history && window.history.length === 1) {
@ -443,11 +600,11 @@ class UI extends React.PureComponent {
} else { } else {
this.context.router.history.goBack(); this.context.router.history.goBack();
} }
} };
setHotkeysRef = c => { setHotkeysRef = c => {
this.hotkeys = c; this.hotkeys = c;
} };
handleHotkeyToggleHelp = () => { handleHotkeyToggleHelp = () => {
if (this.props.location.pathname === '/keyboard-shortcuts') { if (this.props.location.pathname === '/keyboard-shortcuts') {
@ -455,55 +612,55 @@ class UI extends React.PureComponent {
} else { } else {
this.context.router.history.push('/keyboard-shortcuts'); this.context.router.history.push('/keyboard-shortcuts');
} }
} };
handleHotkeyGoToHome = () => { handleHotkeyGoToHome = () => {
this.context.router.history.push('/timelines/home'); this.context.router.history.push('/timelines/home');
} };
handleHotkeyGoToNotifications = () => { handleHotkeyGoToNotifications = () => {
this.context.router.history.push('/notifications'); this.context.router.history.push('/notifications');
} };
handleHotkeyGoToLocal = () => { handleHotkeyGoToLocal = () => {
this.context.router.history.push('/timelines/public/local'); this.context.router.history.push('/timelines/public/local');
} };
handleHotkeyGoToFederated = () => { handleHotkeyGoToFederated = () => {
this.context.router.history.push('/timelines/public'); this.context.router.history.push('/timelines/public');
} };
handleHotkeyGoToDirect = () => { handleHotkeyGoToDirect = () => {
this.context.router.history.push('/timelines/direct'); this.context.router.history.push('/timelines/direct');
} };
handleHotkeyGoToStart = () => { handleHotkeyGoToStart = () => {
this.context.router.history.push('/getting-started'); this.context.router.history.push('/getting-started');
} };
handleHotkeyGoToFavourites = () => { handleHotkeyGoToFavourites = () => {
this.context.router.history.push('/favourites'); this.context.router.history.push('/favourites');
} };
handleHotkeyGoToPinned = () => { handleHotkeyGoToPinned = () => {
this.context.router.history.push('/pinned'); this.context.router.history.push('/pinned');
} };
handleHotkeyGoToProfile = () => { handleHotkeyGoToProfile = () => {
this.context.router.history.push(`/accounts/${me}`); this.context.router.history.push(`/accounts/${me}`);
} };
handleHotkeyGoToBlocked = () => { handleHotkeyGoToBlocked = () => {
this.context.router.history.push('/blocks'); this.context.router.history.push('/blocks');
} };
handleHotkeyGoToMuted = () => { handleHotkeyGoToMuted = () => {
this.context.router.history.push('/mutes'); this.context.router.history.push('/mutes');
} };
handleHotkeyGoToRequests = () => { handleHotkeyGoToRequests = () => {
this.context.router.history.push('/follow_requests'); this.context.router.history.push('/follow_requests');
} };
render() { render() {
const { draggingOver } = this.state; const { draggingOver } = this.state;
@ -531,16 +688,32 @@ class UI extends React.PureComponent {
}; };
return ( return (
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused> <HotKeys
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}> keyMap={keyMap}
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange}> handlers={handlers}
ref={this.setHotkeysRef}
attach={window}
focused
>
<div
className={classNames('ui', { 'is-composing': isComposing })}
ref={this.setRef}
style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}
>
<SwitchingColumnsArea
location={location}
onLayoutChange={this.handleLayoutChange}
>
{children} {children}
</SwitchingColumnsArea > </SwitchingColumnsArea >
<NotificationsContainer /> <NotificationsContainer />
<LoadingBarContainer className='loading-bar' /> <LoadingBarContainer className='loading-bar' />
<ModalContainer /> <ModalContainer />
<UploadArea active={draggingOver} onClose={this.closeUploadModal} /> <UploadArea
active={draggingOver}
onClose={this.closeUploadModal}
/>
<DocumentTitle /> <DocumentTitle />
</div > </div >
</HotKeys > </HotKeys >

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Switch, Route } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import ColumnLoading from '../components/column_loading'; import ColumnLoading from '../components/column_loading';
import BundleColumnError from '../components/bundle_column_error'; import BundleColumnError from '../components/bundle_column_error';
@ -46,19 +46,26 @@ export class WrappedRoute extends React.Component {
const { component, content, multiColumn, componentParams } = this.props; const { component, content, multiColumn, componentParams } = this.props;
return ( return (
<BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}> <BundleContainer
{Component => <Component params={match.params} multiColumn={multiColumn} {...componentParams}>{content}</Component>} fetchComponent={component}
loading={this.renderLoading}
error={this.renderError}
>
{Component => (<Component
params={match.params}
multiColumn={multiColumn} {...componentParams}
>{content}</Component >)}
</BundleContainer > </BundleContainer >
); );
} };
renderLoading = () => { renderLoading = () => {
return <ColumnLoading />; return <ColumnLoading />;
} };
renderError = (props) => { renderError = (props) => {
return <BundleColumnError {...props} />; return <BundleColumnError {...props} />;
} };
render() { render() {
const { component: Component, content, ...rest } = this.props; const { component: Component, content, ...rest } = this.props;

View File

@ -17,7 +17,7 @@ class ReducedMotion extends React.Component {
defaultStyle: PropTypes.object, defaultStyle: PropTypes.object,
style : PropTypes.object, style : PropTypes.object,
children : PropTypes.func, children : PropTypes.func,
} };
render() { render() {
@ -33,7 +33,10 @@ class ReducedMotion extends React.Component {
}); });
return ( return (
<Motion style={style} defaultStyle={defaultStyle}> <Motion
style={style}
defaultStyle={defaultStyle}
>
{children} {children}
</Motion > </Motion >
); );

View File

@ -25,5 +25,5 @@ export const usePendingItems = getMeta('use_pending_items');
export const showTrends = getMeta('trends'); export const showTrends = getMeta('trends');
export const title = getMeta('title'); export const title = getMeta('title');
export const cropImages = getMeta('crop_images'); export const cropImages = getMeta('crop_images');
console.log('initialState', initialState);
export default initialState; export default initialState;

View File

@ -5,7 +5,7 @@
"account.block_domain": "Tout masquer venant de {domain}", "account.block_domain": "Tout masquer venant de {domain}",
"account.blocked": "Bloqué·e", "account.blocked": "Bloqué·e",
"account.cancel_follow_request": "Annuler la demande de suivi", "account.cancel_follow_request": "Annuler la demande de suivi",
"account.direct": "Envoyer un message direct à @{name}", "account.direct": "Envoyer un message privé à @{name}",
"account.domain_blocked": "Domaine caché", "account.domain_blocked": "Domaine caché",
"account.edit_profile": "Modifier le profil", "account.edit_profile": "Modifier le profil",
"account.endorse": "Recommander sur le profil", "account.endorse": "Recommander sur le profil",
@ -52,6 +52,7 @@
"bundle_modal_error.retry": "Réessayer", "bundle_modal_error.retry": "Réessayer",
"column.bookmarks": "Marque pages", "column.bookmarks": "Marque pages",
"column.blocks": "Comptes bloqués", "column.blocks": "Comptes bloqués",
"column.bookmarks": "Bookmarks",
"column.community": "Fil public local", "column.community": "Fil public local",
"column.direct": "Messages privés", "column.direct": "Messages privés",
"column.directory": "Parcourir les profils", "column.directory": "Parcourir les profils",
@ -139,8 +140,9 @@
"empty_column.account_timeline": "Aucun pouet ici !", "empty_column.account_timeline": "Aucun pouet ici !",
"empty_column.account_unavailable": "Profil non disponible", "empty_column.account_unavailable": "Profil non disponible",
"empty_column.blocks": "Vous navez bloqué aucun·e utilisateur·rice pour le moment.", "empty_column.blocks": "Vous navez bloqué aucun·e utilisateur·rice pour le moment.",
"empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
"empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir!", "empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir!",
"empty_column.direct": "Vous navez pas encore de messages directs. Lorsque vous en enverrez ou recevrez un, il saffichera ici.", "empty_column.direct": "Vous navez pas encore de messages privés. Lorsque vous en enverrez ou recevrez un, il saffichera ici.",
"empty_column.domain_blocks": "Il ny a aucun domaine caché pour le moment.", "empty_column.domain_blocks": "Il ny a aucun domaine caché pour le moment.",
"empty_column.favourited_statuses": "Vous navez aucun pouet favoris pour le moment. Lorsque vous en mettrez un en favori, il apparaîtra ici.", "empty_column.favourited_statuses": "Vous navez aucun pouet favoris pour le moment. Lorsque vous en mettrez un en favori, il apparaîtra ici.",
"empty_column.favourites": "Personne na encore mis ce pouet en favori. Lorsque quelquun le fera, il apparaîtra ici.", "empty_column.favourites": "Personne na encore mis ce pouet en favori. Lorsque quelquun le fera, il apparaîtra ici.",
@ -164,7 +166,7 @@
"getting_started.documentation": "Documentation", "getting_started.documentation": "Documentation",
"getting_started.heading": "Pour commencer", "getting_started.heading": "Pour commencer",
"getting_started.invite": "Inviter des gens", "getting_started.invite": "Inviter des gens",
"getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer ou faire des rapports de bogues via {github} sur GitHub.", "getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer ou faire des rapports de bogues via {forge} sur GitHub.",
"getting_started.security": "Sécurité", "getting_started.security": "Sécurité",
"getting_started.terms": "Conditions dutilisation", "getting_started.terms": "Conditions dutilisation",
"hashtag.column_header.tag_mode.all": "et {additional}", "hashtag.column_header.tag_mode.all": "et {additional}",
@ -205,7 +207,7 @@
"keyboard_shortcuts.column": "pour focaliser un statut dans lune des colonnes", "keyboard_shortcuts.column": "pour focaliser un statut dans lune des colonnes",
"keyboard_shortcuts.compose": "pour focaliser la zone de rédaction", "keyboard_shortcuts.compose": "pour focaliser la zone de rédaction",
"keyboard_shortcuts.description": "Description", "keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.direct": "pour ouvrir la colonne des messages directs", "keyboard_shortcuts.direct": "pour ouvrir la colonne des messages privés",
"keyboard_shortcuts.down": "pour descendre dans la liste", "keyboard_shortcuts.down": "pour descendre dans la liste",
"keyboard_shortcuts.enter": "pour ouvrir le statut", "keyboard_shortcuts.enter": "pour ouvrir le statut",
"keyboard_shortcuts.favourite": "pour ajouter aux favoris", "keyboard_shortcuts.favourite": "pour ajouter aux favoris",
@ -253,9 +255,10 @@
"mute_modal.hide_notifications": "Masquer les notifications de cette personne?", "mute_modal.hide_notifications": "Masquer les notifications de cette personne?",
"navigation_bar.apps": "Applications mobiles", "navigation_bar.apps": "Applications mobiles",
"navigation_bar.blocks": "Comptes bloqués", "navigation_bar.blocks": "Comptes bloqués",
"navigation_bar.bookmarks": "Bookmarks",
"navigation_bar.community_timeline": "Fil public local", "navigation_bar.community_timeline": "Fil public local",
"navigation_bar.compose": "Rédiger un nouveau pouet", "navigation_bar.compose": "Rédiger un nouveau pouet",
"navigation_bar.direct": "Messages directs", "navigation_bar.direct": "Messages privés",
"navigation_bar.discover": "Découvrir", "navigation_bar.discover": "Découvrir",
"navigation_bar.domain_blocks": "Domaines cachés", "navigation_bar.domain_blocks": "Domaines cachés",
"navigation_bar.edit_profile": "Modifier le profil", "navigation_bar.edit_profile": "Modifier le profil",
@ -352,7 +355,7 @@
"status.copy": "Copier le lien vers le pouet", "status.copy": "Copier le lien vers le pouet",
"status.delete": "Effacer", "status.delete": "Effacer",
"status.detailed_status": "Vue détaillée de la conversation", "status.detailed_status": "Vue détaillée de la conversation",
"status.direct": "Envoyer un message direct à @{name}", "status.direct": "Envoyer un message privé à @{name}",
"status.embed": "Intégrer", "status.embed": "Intégrer",
"status.favourite": "Ajouter aux favoris", "status.favourite": "Ajouter aux favoris",
"status.filtered": "Filtré", "status.filtered": "Filtré",
@ -371,6 +374,7 @@
"status.reblogged_by": "{name} a partagé:", "status.reblogged_by": "{name} a partagé:",
"status.reblogs.empty": "Personne na encore partagé ce pouet. Lorsque quelquun le fera, il apparaîtra ici.", "status.reblogs.empty": "Personne na encore partagé ce pouet. Lorsque quelquun le fera, il apparaîtra ici.",
"status.redraft": "Effacer et ré-écrire", "status.redraft": "Effacer et ré-écrire",
"status.remove_bookmark": "Enlever le marque-page",
"status.reply": "Répondre", "status.reply": "Répondre",
"status.replyAll": "Répondre au fil", "status.replyAll": "Répondre au fil",
"status.report": "Signaler @{name}", "status.report": "Signaler @{name}",
@ -386,9 +390,9 @@
"status.unpin": "Retirer du profil", "status.unpin": "Retirer du profil",
"suggestions.dismiss": "Rejeter la suggestion", "suggestions.dismiss": "Rejeter la suggestion",
"suggestions.header": "Vous pourriez être intéressé·e par…", "suggestions.header": "Vous pourriez être intéressé·e par…",
"tabs_bar.federated_timeline": "Fil public global", "tabs_bar.federated_timeline": "Global",
"tabs_bar.home": "Accueil", "tabs_bar.home": "Accueil",
"tabs_bar.local_timeline": "Fil public local", "tabs_bar.local_timeline": "Local",
"tabs_bar.notifications": "Notifications", "tabs_bar.notifications": "Notifications",
"tabs_bar.search": "Chercher", "tabs_bar.search": "Chercher",
"time_remaining.days": "{number, plural, one {# day} other {# days}} restants", "time_remaining.days": "{number, plural, one {# day} other {# days}} restants",

View File

@ -1,5 +1,5 @@
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer'; import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer';
import { Map as ImmutableMap, fromJS } from 'immutable'; import { fromJS, Map as ImmutableMap } from 'immutable';
const initialState = ImmutableMap(); const initialState = ImmutableMap();

View File

@ -1,21 +1,16 @@
import { import {
REBLOG_REQUEST,
REBLOG_FAIL,
FAVOURITE_REQUEST,
FAVOURITE_FAIL,
UNFAVOURITE_SUCCESS,
BOOKMARK_REQUEST,
BOOKMARK_FAIL, BOOKMARK_FAIL,
BOOKMARK_REQUEST,
FAVOURITE_FAIL,
FAVOURITE_REQUEST,
REBLOG_FAIL,
REBLOG_REQUEST,
UNFAVOURITE_SUCCESS,
} from '../actions/interactions'; } from '../actions/interactions';
import { import { STATUS_HIDE, STATUS_MUTE_SUCCESS, STATUS_REVEAL, STATUS_UNMUTE_SUCCESS } from '../actions/statuses';
STATUS_MUTE_SUCCESS,
STATUS_UNMUTE_SUCCESS,
STATUS_REVEAL,
STATUS_HIDE,
} from '../actions/statuses';
import { TIMELINE_DELETE } from '../actions/timelines'; import { TIMELINE_DELETE } from '../actions/timelines';
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
import { Map as ImmutableMap, fromJS } from 'immutable'; import { fromJS, Map as ImmutableMap } from 'immutable';
const importStatus = (state, status) => state.set(status.id, fromJS(status)); const importStatus = (state, status) => state.set(status.id, fromJS(status));

View File

@ -0,0 +1,29 @@
/** @license
DHTML Snowstorm! JavaScript-based snow for web pages
Making it snow on the internets since 2003. You're welcome.
-----------------------------------------------------------
Version 1.44.20131208 (Previous rev: 1.44.20131125)
Copyright (c) 2007, Scott Schiller. All rights reserved.
Code provided under the BSD License
http://schillmania.com/projects/snowstorm/license.txt
*/
var snowStorm=function(g,f){function k(a,d){isNaN(d)&&(d=0);return Math.random()*a+d}function x(){g.setTimeout(function(){a.start(!0)},20);a.events.remove(m?f:g,"mousemove",x)}function y(){(!a.excludeMobile||!D)&&x();a.events.remove(g,"load",y)}this.excludeMobile=this.autoStart=!0;this.flakesMax=128;this.flakesMaxActive=64;this.animationInterval=33;this.useGPU=!0;this.className=null;this.excludeMobile=!0;this.flakeBottom=null;this.followMouse=!0;this.snowColor="#fff";this.snowCharacter="&bull;";this.snowStick=
!0;this.targetElement=null;this.useMeltEffect=!0;this.usePixelPosition=this.usePositionFixed=this.useTwinkleEffect=!1;this.freezeOnBlur=!0;this.flakeRightOffset=this.flakeLeftOffset=0;this.flakeHeight=this.flakeWidth=8;this.vMaxX=5;this.vMaxY=4;this.zIndex=0;var a=this,q,m=navigator.userAgent.match(/msie/i),E=navigator.userAgent.match(/msie 6/i),D=navigator.userAgent.match(/mobile|opera m(ob|in)/i),r=m&&"BackCompat"===f.compatMode||E,h=null,n=null,l=null,p=null,s=null,z=null,A=null,v=1,t=!1,w=!1,
u;a:{try{f.createElement("div").style.opacity="0.5"}catch(F){u=!1;break a}u=!0}var B=!1,C=f.createDocumentFragment();q=function(){function c(b){g.setTimeout(b,1E3/(a.animationInterval||20))}function d(a){return void 0!==h.style[a]?a:null}var e,b=g.requestAnimationFrame||g.webkitRequestAnimationFrame||g.mozRequestAnimationFrame||g.oRequestAnimationFrame||g.msRequestAnimationFrame||c;e=b?function(){return b.apply(g,arguments)}:null;var h;h=f.createElement("div");e={transform:{ie:d("-ms-transform"),
moz:d("MozTransform"),opera:d("OTransform"),webkit:d("webkitTransform"),w3:d("transform"),prop:null},getAnimationFrame:e};e.transform.prop=e.transform.w3||e.transform.moz||e.transform.webkit||e.transform.ie||e.transform.opera;h=null;return e}();this.timer=null;this.flakes=[];this.active=this.disabled=!1;this.meltFrameCount=20;this.meltFrames=[];this.setXY=function(c,d,e){if(!c)return!1;a.usePixelPosition||w?(c.style.left=d-a.flakeWidth+"px",c.style.top=e-a.flakeHeight+"px"):r?(c.style.right=100-100*
(d/h)+"%",c.style.top=Math.min(e,s-a.flakeHeight)+"px"):a.flakeBottom?(c.style.right=100-100*(d/h)+"%",c.style.top=Math.min(e,s-a.flakeHeight)+"px"):(c.style.right=100-100*(d/h)+"%",c.style.bottom=100-100*(e/l)+"%")};this.events=function(){function a(c){c=b.call(c);var d=c.length;e?(c[1]="on"+c[1],3<d&&c.pop()):3===d&&c.push(!1);return c}function d(a,b){var c=a.shift(),d=[f[b]];if(e)c[d](a[0],a[1]);else c[d].apply(c,a)}var e=!g.addEventListener&&g.attachEvent,b=Array.prototype.slice,f={add:e?"attachEvent":
"addEventListener",remove:e?"detachEvent":"removeEventListener"};return{add:function(){d(a(arguments),"add")},remove:function(){d(a(arguments),"remove")}}}();this.randomizeWind=function(){var c;c=k(a.vMaxX,0.2);z=1===parseInt(k(2),10)?-1*c:c;A=k(a.vMaxY,0.2);if(this.flakes)for(c=0;c<this.flakes.length;c++)this.flakes[c].active&&this.flakes[c].setVelocities()};this.scrollHandler=function(){var c;p=a.flakeBottom?0:parseInt(g.scrollY||f.documentElement.scrollTop||(r?f.body.scrollTop:0),10);isNaN(p)&&
(p=0);if(!t&&!a.flakeBottom&&a.flakes)for(c=0;c<a.flakes.length;c++)0===a.flakes[c].active&&a.flakes[c].stick()};this.resizeHandler=function(){g.innerWidth||g.innerHeight?(h=g.innerWidth-16-a.flakeRightOffset,l=a.flakeBottom||g.innerHeight):(h=(f.documentElement.clientWidth||f.body.clientWidth||f.body.scrollWidth)-(!m?8:0)-a.flakeRightOffset,l=a.flakeBottom||f.documentElement.clientHeight||f.body.clientHeight||f.body.scrollHeight);s=f.body.offsetHeight;n=parseInt(h/2,10)};this.resizeHandlerAlt=function(){h=
a.targetElement.offsetWidth-a.flakeRightOffset;l=a.flakeBottom||a.targetElement.offsetHeight;n=parseInt(h/2,10);s=f.body.offsetHeight};this.freeze=function(){if(a.disabled)return!1;a.disabled=1;a.timer=null};this.resume=function(){if(a.disabled)a.disabled=0;else return!1;a.timerInit()};this.toggleSnow=function(){a.flakes.length?(a.active=!a.active,a.active?(a.show(),a.resume()):(a.stop(),a.freeze())):a.start()};this.stop=function(){var c;this.freeze();for(c=0;c<this.flakes.length;c++)this.flakes[c].o.style.display=
"none";a.events.remove(g,"scroll",a.scrollHandler);a.events.remove(g,"resize",a.resizeHandler);a.freezeOnBlur&&(m?(a.events.remove(f,"focusout",a.freeze),a.events.remove(f,"focusin",a.resume)):(a.events.remove(g,"blur",a.freeze),a.events.remove(g,"focus",a.resume)))};this.show=function(){var a;for(a=0;a<this.flakes.length;a++)this.flakes[a].o.style.display="block"};this.SnowFlake=function(c,d,e){var b=this;this.type=c;this.x=d||parseInt(k(h-20),10);this.y=!isNaN(e)?e:-k(l)-12;this.vY=this.vX=null;
this.vAmpTypes=[1,1.2,1.4,1.6,1.8];this.vAmp=this.vAmpTypes[this.type]||1;this.melting=!1;this.meltFrameCount=a.meltFrameCount;this.meltFrames=a.meltFrames;this.twinkleFrame=this.meltFrame=0;this.active=1;this.fontSize=10+10*(this.type/5);this.o=f.createElement("div");this.o.innerHTML=a.snowCharacter;a.className&&this.o.setAttribute("class",a.className);this.o.style.color=a.snowColor;this.o.style.position=t?"fixed":"absolute";a.useGPU&&q.transform.prop&&(this.o.style[q.transform.prop]="translate3d(0px, 0px, 0px)");
this.o.style.width=a.flakeWidth+"px";this.o.style.height=a.flakeHeight+"px";this.o.style.fontFamily="arial,verdana";this.o.style.cursor="default";this.o.style.overflow="hidden";this.o.style.fontWeight="normal";this.o.style.zIndex=a.zIndex;C.appendChild(this.o);this.refresh=function(){if(isNaN(b.x)||isNaN(b.y))return!1;a.setXY(b.o,b.x,b.y)};this.stick=function(){r||a.targetElement!==f.documentElement&&a.targetElement!==f.body?b.o.style.top=l+p-a.flakeHeight+"px":a.flakeBottom?b.o.style.top=a.flakeBottom+
"px":(b.o.style.display="none",b.o.style.bottom="0%",b.o.style.position="fixed",b.o.style.display="block")};this.vCheck=function(){0<=b.vX&&0.2>b.vX?b.vX=0.2:0>b.vX&&-0.2<b.vX&&(b.vX=-0.2);0<=b.vY&&0.2>b.vY&&(b.vY=0.2)};this.move=function(){var c=b.vX*v;b.x+=c;b.y+=b.vY*b.vAmp;b.x>=h||h-b.x<a.flakeWidth?b.x=0:0>c&&b.x-a.flakeLeftOffset<-a.flakeWidth&&(b.x=h-a.flakeWidth-1);b.refresh();l+p-b.y+a.flakeHeight<a.flakeHeight?(b.active=0,a.snowStick?b.stick():b.recycle()):(a.useMeltEffect&&(b.active&&3>
b.type&&!b.melting&&0.998<Math.random())&&(b.melting=!0,b.melt()),a.useTwinkleEffect&&(0>b.twinkleFrame?0.97<Math.random()&&(b.twinkleFrame=parseInt(8*Math.random(),10)):(b.twinkleFrame--,u?b.o.style.opacity=b.twinkleFrame&&0===b.twinkleFrame%2?0:1:b.o.style.visibility=b.twinkleFrame&&0===b.twinkleFrame%2?"hidden":"visible")))};this.animate=function(){b.move()};this.setVelocities=function(){b.vX=z+k(0.12*a.vMaxX,0.1);b.vY=A+k(0.12*a.vMaxY,0.1)};this.setOpacity=function(a,b){if(!u)return!1;a.style.opacity=
b};this.melt=function(){!a.useMeltEffect||!b.melting?b.recycle():b.meltFrame<b.meltFrameCount?(b.setOpacity(b.o,b.meltFrames[b.meltFrame]),b.o.style.fontSize=b.fontSize-b.fontSize*(b.meltFrame/b.meltFrameCount)+"px",b.o.style.lineHeight=a.flakeHeight+2+0.75*a.flakeHeight*(b.meltFrame/b.meltFrameCount)+"px",b.meltFrame++):b.recycle()};this.recycle=function(){b.o.style.display="none";b.o.style.position=t?"fixed":"absolute";b.o.style.bottom="auto";b.setVelocities();b.vCheck();b.meltFrame=0;b.melting=
!1;b.setOpacity(b.o,1);b.o.style.padding="0px";b.o.style.margin="0px";b.o.style.fontSize=b.fontSize+"px";b.o.style.lineHeight=a.flakeHeight+2+"px";b.o.style.textAlign="center";b.o.style.verticalAlign="baseline";b.x=parseInt(k(h-a.flakeWidth-20),10);b.y=parseInt(-1*k(l),10)-a.flakeHeight;b.refresh();b.o.style.display="block";b.active=1};this.recycle();this.refresh()};this.snow=function(){var c=0,d=null,e,d=0;for(e=a.flakes.length;d<e;d++)1===a.flakes[d].active&&(a.flakes[d].move(),c++),a.flakes[d].melting&&
a.flakes[d].melt();c<a.flakesMaxActive&&(d=a.flakes[parseInt(k(a.flakes.length),10)],0===d.active&&(d.melting=!0));a.timer&&q.getAnimationFrame(a.snow)};this.mouseMove=function(c){if(!a.followMouse)return!0;c=parseInt(c.clientX,10);c<n?v=-2+2*(c/n):(c-=n,v=2*(c/n))};this.createSnow=function(c,d){var e;for(e=0;e<c;e++)if(a.flakes[a.flakes.length]=new a.SnowFlake(parseInt(k(6),10)),d||e>a.flakesMaxActive)a.flakes[a.flakes.length-1].active=-1;a.targetElement.appendChild(C)};this.timerInit=function(){a.timer=
!0;a.snow()};this.init=function(){var c;for(c=0;c<a.meltFrameCount;c++)a.meltFrames.push(1-c/a.meltFrameCount);a.randomizeWind();a.createSnow(a.flakesMax);a.events.add(g,"resize",a.resizeHandler);a.events.add(g,"scroll",a.scrollHandler);a.freezeOnBlur&&(m?(a.events.add(f,"focusout",a.freeze),a.events.add(f,"focusin",a.resume)):(a.events.add(g,"blur",a.freeze),a.events.add(g,"focus",a.resume)));a.resizeHandler();a.scrollHandler();a.followMouse&&a.events.add(m?f:g,"mousemove",a.mouseMove);a.animationInterval=
Math.max(20,a.animationInterval);a.timerInit()};this.start=function(c){if(B){if(c)return!0}else B=!0;if("string"===typeof a.targetElement&&(c=a.targetElement,a.targetElement=f.getElementById(c),!a.targetElement))throw Error('Snowstorm: Unable to get targetElement "'+c+'"');a.targetElement||(a.targetElement=f.body||f.documentElement);a.targetElement!==f.documentElement&&a.targetElement!==f.body&&(a.resizeHandler=a.resizeHandlerAlt,a.usePixelPosition=!0);a.resizeHandler();a.usePositionFixed=a.usePositionFixed&&
!r&&!a.flakeBottom;if(g.getComputedStyle)try{w="relative"===g.getComputedStyle(a.targetElement,null).getPropertyValue("position")}catch(d){w=!1}t=a.usePositionFixed;h&&(l&&!a.disabled)&&(a.init(),a.active=!0)};a.autoStart&&a.events.add(g,"load",y,!1);return this}(window, document);

View File

@ -45,7 +45,25 @@ const onDomainBlockSeverityChange = (target) => {
delegate(document, '#domain_block_severity', 'change', ({ target }) => onDomainBlockSeverityChange(target)); delegate(document, '#domain_block_severity', 'change', ({ target }) => onDomainBlockSeverityChange(target));
const onEnableBootstrapTimelineAccountsChange = (target) => {
const bootstrapTimelineAccountsField = document.querySelector('#form_admin_settings_bootstrap_timeline_accounts');
if (bootstrapTimelineAccountsField) {
bootstrapTimelineAccountsField.disabled = !target.checked;
if (target.checked) {
bootstrapTimelineAccountsField.parentElement.classList.remove('disabled');
} else {
bootstrapTimelineAccountsField.parentElement.classList.add('disabled');
}
}
};
delegate(document, '#form_admin_settings_enable_bootstrap_timeline_accounts', 'change', ({ target }) => onEnableBootstrapTimelineAccountsChange(target));
ready(() => { ready(() => {
const input = document.getElementById('domain_block_severity'); const domainBlockSeverityInput = document.getElementById('domain_block_severity');
if (input) onDomainBlockSeverityChange(input); if (domainBlockSeverityInput) onDomainBlockSeverityChange(domainBlockSeverityInput);
const enableBootstrapTimelineAccounts = document.getElementById('form_admin_settings_enable_bootstrap_timeline_accounts');
if (enableBootstrapTimelineAccounts) onEnableBootstrapTimelineAccountsChange(enableBootstrapTimelineAccounts);
}); });

View File

@ -1,3 +1,6 @@
@import '~bulma/sass/utilities/_all';
@import '~bulma/sass/grid/columns';
@import 'bliss/variables'; @import 'bliss/variables';
@import 'bliss/mixins'; @import 'bliss/mixins';
@import 'bliss/variables'; @import 'bliss/variables';

View File

@ -72,3 +72,13 @@
color: $inverted-text-color; color: $inverted-text-color;
} }
} }
.hidden {
display: none;
&:before {
content: "hidden div here";
background: yellow;
padding: 1em;
}
}

View File

@ -1,7 +1,3 @@
$maximum-width: 1235px;
$fluid-breakpoint: $maximum-width + 20px;
$column-breakpoint: 700px;
$small-breakpoint: 960px;
.container { .container {
box-sizing: border-box; box-sizing: border-box;
@ -622,7 +618,7 @@ $small-breakpoint: 960px;
} }
} }
@media screen and (max-width: 675px) { @media screen and (max-width: $xs-breakpoint) {
.header-wrapper { .header-wrapper {
padding-top: 0; padding-top: 0;
@ -649,7 +645,7 @@ $small-breakpoint: 960px;
.landing { .landing {
margin-bottom: 100px; margin-bottom: 100px;
@media screen and (max-width: 738px) { @media screen and (max-width: $xs-top-breakpoint) {
margin-bottom: 0; margin-bottom: 0;
} }
@ -715,9 +711,9 @@ $small-breakpoint: 960px;
} }
.account__avatar { .account__avatar {
width: 44px; width: $avatar-side;
height: 44px; height: $avatar-side;
background-size: 44px 44px; background-size: $avatar-side $avatar-side;
} }
} }
@ -752,13 +748,13 @@ $small-breakpoint: 960px;
} }
&__grid { &__grid {
max-width: 960px; max-width: $small-breakpoint;
margin: 0 auto; margin: 0 auto;
display: grid; display: grid;
grid-template-columns: minmax(0, 50%) minmax(0, 50%); grid-template-columns: minmax(0, 50%) minmax(0, 50%);
grid-gap: 30px; grid-gap: 30px;
@media screen and (max-width: 738px) { @media screen and (max-width: $xs-top-breakpoint) {
grid-template-columns: minmax(0, 100%); grid-template-columns: minmax(0, 100%);
grid-gap: 10px; grid-gap: 10px;

View File

@ -13,6 +13,4 @@ $black-emojis: '8ball' 'ant' 'back' 'black_circle' 'black_heart' 'black_large_sq
} }
} }
.fa {
margin-right: 1ch;
}

View File

@ -384,6 +384,22 @@ hr.spacer {
max-width: 100%; max-width: 100%;
} }
input {
background: auto;
}
hr,
.select select,
.textarea,
.input {
background-color: transparent;
border-color: transparent;
&:hover {
border: transparent;
}
}
.batch-form-box { .batch-form-box {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@ -160,3 +160,35 @@ button {
height: 100%; height: 100%;
} }
} }
.media-gallery {
margin-left: 0;
}
.debug, .well {
padding: 0.5rem;
border: solid 1px greenyellow;
background: yellow;
color: #222;
}
.spacer {
display: block;
padding: 1em;
}
.small-texts {
.timelines {
.column-link {
}
span {
display: none;
}
}
}

View File

@ -406,6 +406,10 @@
} }
} }
.autosuggest-textarea__textarea {
margin-bottom: 5px;
}
.autosuggest-textarea__textarea, .autosuggest-textarea__textarea,
.spoiler-input__input { .spoiler-input__input {
display: block; display: block;
@ -2193,27 +2197,16 @@ a.account__display-name {
} }
.notification { .notification {
&__message { background-color: $classic-base-color !important;
margin-left: 48px + 15px * 2; padding: 1em 0;
padding-top: 15px; color: $ui-highlight-color;
} border-left: $ui-highlight-color 5px solid;
&__favourite-icon-wrapper {
left: -32px;
}
.status {
padding-top: 8px;
}
.account { .account {
padding: 0;
padding-top: 8px; padding-top: 8px;
} }
.account__avatar-wrapper {
margin-left: 17px;
margin-right: 15px;
}
} }
} }
} }
@ -2261,6 +2254,7 @@ a.account__display-name {
.columns-area__panels__pane--compositional { .columns-area__panels__pane--compositional {
display: none; display: none;
} }
} }
@media screen and (min-width: 600px + (285px * 1) + (10px * 1)) { @media screen and (min-width: 600px + (285px * 1) + (10px * 1)) {
@ -2315,6 +2309,7 @@ a.account__display-name {
flex-direction: column; flex-direction: column;
height: calc(100% - 10px); height: calc(100% - 10px);
overflow-y: hidden; overflow-y: hidden;
z-index: 1;
.navigation-bar { .navigation-bar {
padding-top: 20px; padding-top: 20px;
@ -2659,6 +2654,28 @@ a.account__display-name {
padding: 15px; padding: 15px;
text-decoration: none; text-decoration: none;
.timelines & {
display: inline-block;
width: 20%;
display: inline-block !important;
float: left;
text-align: center;
span {
opacity: 0;
transition: opacity ease-in 1s;
}
&:hover {
span {
opacity: 1;
transition: opacity ease-in 0.2s;
}
}
}
&:hover, &:hover,
&:focus, &:focus,
&:active { &:active {

View File

@ -136,7 +136,23 @@
} }
} }
.getting-started__footer { .links-started__footer {
display: block;
position: fixed;
bottom: 0;
left: 1em;
width: 300px;
z-index: 10;
text-align: right;
color: $ui-primary-color;
hr {
border-color: $ui-primary-color;
}
.fa {
margin-right: 1ch;
}
ul { ul {
list-style-type: none; list-style-type: none;
@ -150,4 +166,36 @@
i { i {
margin: 0.5ch; margin: 0.5ch;
} }
a {
margin-left: 2ch;
color: $ui-primary-color;
padding: 3px;
border-radius: 3px;
&:hover {
color: white;
background: $ui-highlight-color;
} }
}
@media all and(min-width: $maximum-width) {
& {
width: 25vw;
}
}
@media all and(max-width: $maximum-width) {
& {
width: 22vw;
height: 50vh;
overflow-y: auto;
padding-right: 1em;
}
}
@media all and(max-width: $small-breakpoint) {
& {
display: none;
}
}
}

View File

@ -7,16 +7,20 @@ table{
background: $ui-highlight-color; background: $ui-highlight-color;
} }
} }
td, th { td, th {
padding: 1rem; padding: 1rem;
} }
a { a {
@extend .text-btn @extend .text-btn
} }
} }
.table-responsive { .table-responsive {
width: 100%; width: 100%;
} }
.table-striped { .table-striped {
margin: 1rem 0; margin: 1rem 0;
@ -27,9 +31,11 @@ table{
} }
} }
} }
.group-form { .group-form {
} }
code { code {
font-family: $font-monospace, monospace; font-family: $font-monospace, monospace;
font-weight: 400; font-weight: 400;
@ -114,6 +120,7 @@ code {
position: absolute; position: absolute;
margin: 0 4px; margin: 0 4px;
margin-top: -2px; margin-top: -2px;
background: $ui-highlight-color;
} }
} }
} }
@ -957,3 +964,8 @@ code {
flex-direction: row; flex-direction: row;
} }
} }
.compose-form__publish-button-wrapper {
width: 100%;
display: block;
}

View File

@ -1,84 +1 @@
$messagingBoxWidth: 15em; @import "../mastodon/messaging/main";
$messagingBoxHeight: 20em;
.fixed-box {
border: solid 1px white;
padding: 1em;
position: fixed;
bottom: 0;
}
.messaging-box {
@extend .fixed-box;
right: 1em;
width: $messagingBoxWidth;
background: $ui-base-color;
}
.conversations_list {
@extend .fixed-box;
width: $messagingBoxWidth;
right: $messagingBoxWidth + 5em;
background: $ui-secondary-color;
}
.conversation-item {
&.has-new-message {
background: $ui-highlight-color;
color: $classic-primary-color;
}
}
.conversation_created-at {
margin-right: 1em;
}
.conversation_stream {
padding-top: 1em;
height: $messagingBoxHeight;
overflow: auto;
background: $ui-secondary-color;
.message {
-webkit-border-radius: 0.5rem;
-moz-border-radius: 0.5rem;
border-radius: 0.5rem;
margin-bottom: 0.5em;
padding: 0.5em 1em;
width: 80%;
}
.mine {
text-align: right;
background: $classic-primary-color;
float: right;
.arrow-down {
border-top-color: $classic-primary-color;
left: 1em;
}
}
.theirs {
text-align: left;
background: $ui-highlight-color;
float: left;
.arrow-down {
border-top-color: $ui-highlight-color;
right: 1em;
}
}
.arrow-down {
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 20px solid transparent;
border-top: 20px solid $classic-primary-color;
position: relative;
bottom: -1em;
}
}

View File

@ -1,10 +1,10 @@
// Commonly used web colors // Commonly used web colors
$black: #000000; // Black $black: #111; // Black
$white: #ffffff; // White $white: #fff; // White
$success-green: #79bd9a !default; // Padua $success-green: #6bbd77 !default; // Padua
$error-red: #df405a !default; // Cerise $error-red: #d4839b !default; // Cerise
$warning-red: #ff5050 !default; // Sunset Orange $warning-red: #528dc8 !default; // Sunset Orange
$gold-star: #ca8f04 !default; // Dark Goldenrod $gold-star: #98c6ff !default; // Dark Goldenrod
// Values from the classic Mastodon UI // Values from the classic Mastodon UI
$classic-base-color: #282c37; // Midnight Express $classic-base-color: #282c37; // Midnight Express
@ -48,7 +48,15 @@ $media-modal-media-max-width: 100%;
$media-modal-media-max-height: 80%; $media-modal-media-max-height: 80%;
$no-gap-breakpoint: 415px; $no-gap-breakpoint: 415px;
$maximum-width: 1235px;
$fluid-breakpoint: $maximum-width + 20px;
$column-breakpoint: 700px;
$small-breakpoint: 960px;
$xs-top-breakpoint: 738px;
$xs-breakpoint: 675px;
// pictures
$avatar-side: 55px;
$font-sans-serif: 'mastodon-font-sans-serif' !default; $font-sans-serif: 'mastodon-font-sans-serif' !default;
$font-display: 'mastodon-font-display' !default; $font-display: 'mastodon-font-display' !default;
$font-monospace: 'mastodon-font-monospace' !default; $font-monospace: 'mastodon-font-monospace' !default;

View File

@ -95,9 +95,9 @@
} }
.account__avatar { .account__avatar {
width: 44px; width: $avatar-side;
height: 44px; height: $avatar-side;
background-size: 44px 44px; background-size: $avatar-side $avatar-side;
} }
} }
} }
@ -537,3 +537,32 @@ $fluid-breakpoint: $maximum-width + 20px;
} }
} }
} }
// christmas snow
.round-button {
cursor: pointer;
margin: 0.5em;
width: 2em;
height: 2em;
display: inline-block;
}
.snow-button {
-webkit-border-radius: 100%;
-moz-border-radius: 100%;
border-radius: 100%;
background: $ui-secondary-color;
@extend .round-button;
.icon {
margin: 0.5em;
}
&:hover {
background: $ui-primary-color;
}
&.active {
background: $ui-highlight-color;
}
}

View File

@ -22,3 +22,4 @@ $action-button-color: #8d9ac2;
$inverted-text-color: $black !default; $inverted-text-color: $black !default;
$lighter-text-color: darken($ui-base-color, 6%) !default; $lighter-text-color: darken($ui-base-color, 6%) !default;
$light-text-color: darken($ui-primary-color, 40%) !default; $light-text-color: darken($ui-primary-color, 40%) !default;
$avatar-side: 55px;

View File

@ -1,30 +1,26 @@
@import 'large_center'; //@import 'large_center';
// custom sheet made on https://www.cipherbliss.com //// custom sheet made on https://www.cipherbliss.com
// Commonly used web colors //// Commonly used web colors
$black: #fff; // Black //$black: #fff; // Black
$white: #000; // White //$white: #000; // White
$success-green: #6bbd77 !default; // Padua //$success-green: #6bbd77 !default; // Padua
$error-red: #d4839b !default; // Cerise //$error-red: #d4839b !default; // Cerise
$warning-red: #528dc8 !default; // Sunset Orange //$warning-red: #528dc8 !default; // Sunset Orange
$gold-star: #00ec84 !default; // Dark Goldenrod //$gold-star: #00ec84 !default; // Dark Goldenrod
//
// User Interface Colors //// User Interface Colors
$ui-base-color: #313644; // Midnight Express //$ui-base-color: #313644; // Midnight Express
$ui-base-lighter-color: #bdd2d6; //$ui-base-lighter-color: #bdd2d6;
$ui-primary-color: #a1ccff; // Echo Blue //$ui-primary-color: #a1ccff; // Echo Blue
$ui-secondary-color: #7fc0ff; // Pattens Blue //$ui-secondary-color: #7fc0ff; // Pattens Blue
$ui-highlight-color: #00a7d1; // Summer Sky //$ui-highlight-color: #00a7d1; // Summer Sky
//$avatar-side: 55px;
// Variables for components //// Variables for components
$media-modal-media-max-width: 100%; //$media-modal-media-max-width: 100%;
// put margins on top and bottom of image to avoid the screen covered by image. //// put margins on top and bottom of image to avoid the screen covered by image.
$media-modal-media-max-height: 80%; //$media-modal-media-max-height: 80%;
// fix //// fix
//
.media-gallery { //// then we import the rest of the world
margin-left: 0; //@import 'application';
} //@import 'bliss/messaging';
// then we import the rest of the world
@import 'application';
@import 'bliss/messaging';

View File

@ -751,9 +751,9 @@ $small-breakpoint: 960px;
} }
.account__avatar { .account__avatar {
width: 44px; width: $avatar-side;
height: 44px; height: $avatar-side;
background-size: 44px 44px; background-size: $avatar-side $avatar-side;
} }
} }

View File

@ -181,18 +181,39 @@ $content-width: 840px;
padding-top: 30px; padding-top: 30px;
} }
&-heading {
display: flex;
padding-bottom: 40px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
margin-bottom: 40px;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
&-actions {
display: inline-flex;
& > * {
margin-left: 5px;
}
}
@media screen and (max-width: $no-columns-breakpoint) {
border-bottom: 0;
padding-bottom: 0;
}
}
h2 { h2 {
color: $secondary-text-color; color: $secondary-text-color;
font-size: 24px; font-size: 24px;
line-height: 28px; line-height: 28px;
font-weight: 400; font-weight: 400;
padding-bottom: 40px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
margin-bottom: 40px;
@media screen and (max-width: $no-columns-breakpoint) { @media screen and (max-width: $no-columns-breakpoint) {
border-bottom: 0;
padding-bottom: 0;
font-weight: 700; font-weight: 700;
} }
} }

View File

@ -229,3 +229,7 @@ button {
} }
} }
} }
.padded {
padding: 1em;
}

View File

@ -949,8 +949,12 @@
} }
@keyframes fade { @keyframes fade {
0% { opacity: 0; } 0% {
100% { opacity: 1; } opacity: 0;
}
100% {
opacity: 1;
}
} }
opacity: 1; opacity: 1;
@ -2333,6 +2337,9 @@ a.account__display-name {
.columns-area__panels__pane--navigational { .columns-area__panels__pane--navigational {
display: none; display: none;
} }
.getting-started__footer {
width: 250px;
}
} }
@media screen and (min-width: 600px + (285px * 2) + (10px * 2)) { @media screen and (min-width: 600px + (285px * 2) + (10px * 2)) {
@ -2512,7 +2519,6 @@ a.account__display-name {
overflow-x: hidden; overflow-x: hidden;
flex: 1 1 auto; flex: 1 1 auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
will-change: transform; // improves perf in mobile Chrome
&.optionally-scrollable { &.optionally-scrollable {
overflow-y: auto; overflow-y: auto;
@ -3465,9 +3471,15 @@ a.status-card.compact:hover {
} }
@keyframes loader-label { @keyframes loader-label {
0% { opacity: 0.25; } 0% {
30% { opacity: 1; } opacity: 0.25;
100% { opacity: 0.25; } }
30% {
opacity: 1;
}
100% {
opacity: 0.25;
}
} }
.video-error-cover { .video-error-cover {
@ -5161,6 +5173,7 @@ a.status-card.compact:hover {
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
} }
/* End Media Gallery */ /* End Media Gallery */
.detailed, .detailed,
@ -5832,9 +5845,15 @@ noscript {
} }
@keyframes flicker { @keyframes flicker {
0% { opacity: 1; } 0% {
30% { opacity: 0.75; } opacity: 1;
100% { opacity: 1; } }
30% {
opacity: 0.75;
}
100% {
opacity: 1;
}
} }
@media screen and (max-width: 630px) and (max-height: 400px) { @media screen and (max-width: 630px) and (max-height: 400px) {

View File

@ -21,7 +21,7 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
svg { > svg {
fill: $primary-text-color; fill: $primary-text-color;
height: 42px; height: 42px;
margin-right: 10px; margin-right: 10px;

View File

@ -0,0 +1,3 @@
$messagingBoxWidth: 20em;
$messagingBoxHeight: 100%;
$conversationBoxHeight: 20em;

View File

@ -0,0 +1,16 @@
.contact-list-container {
overflow-x: auto;
}
.contact-list {
display: block;
padding: 1em;
min-height: 5em;
list-style-type: none;
color: white;
.contact {
margin-bottom: 0.5rem;
}
}

View File

@ -0,0 +1,16 @@
.fixed-box {
border: solid 1px white;
padding: 1em;
position: fixed;
bottom: 0;
}
.airmail-border {
border: 0.25em solid transparent;
border-image: 4 repeating-linear-gradient(-45deg, red 0, red 1em, white 0, white 2em,
#58a 0, #58a 3em, white 0, white 4em);
border-right-width: 0;
border-bottom-width: 0;
border-top-width: 0;
}

View File

@ -0,0 +1,6 @@
@import "config";
@import "helpers";
@import "messaging";
@import "contacts";
@import "messaging-conversation";
@import "messaging-stream";

View File

@ -0,0 +1,87 @@
.main-instant-messaging {
.conversation {
.conversation__content {
padding-right: 0;
}
.conversation_reply,
.icon-button {
&:hover {
color: $ui-highlight-color;
background: mix($ui-base-color, $ui-secondary-color);
}
}
.conversation_reply,
.icon-button,
.status__action-bar-dropdown {
display: inline-block;
float: right;
width: 18em;
height: 3.2em;
text-align: center;
}
}
.conversations_list {
@extend .fixed-box;
right: $messagingBoxWidth + 2em;
bottom: 0;
width: 100%;
padding: 0.5em;
overflow-x: auto;
border: 0;
}
.conversation-item {
float: right;
width: 20em;
margin-left: 2em;
padding: 1em;
border-radius: 15px;
border: 3px solid $classic-primary-color;
-moz-border-radius-bottomleft: 0;
-moz-border-radius-bottomright: 0;
background: $classic-base-color;
&.has-new-message {
background: $ui-highlight-color;
color: $classic-primary-color;
}
&.is-focused {
border-color: $ui-base-color;
}
.username {
font-weight: bold;
}
.conversation-stream,
.conversation_input {
height: 100%;
display: block;
}
&.hidden {
.conversation-stream,
.conversation_input {
display: none;
}
}
.btn {
background: $classic-base-color;
color: $ui-base-color;
float: right;
padding: 0 1em;
margin-left: 1em;
border: 0;
}
}
}

View File

@ -0,0 +1,55 @@
.conversation-stream {
padding-top: 1em;
height: 5em;
overflow: auto;
width: 100%;
.message {
clear: both;
}
.messages >
.message {
-webkit-border-radius: 0.5rem;
-moz-border-radius: 0.5rem;
border-radius: 0.5rem;
margin-bottom: 0.5em;
padding: 0.5em 1em;
display: block;
}
.ours {
text-align: right;
background: $light-text-color;
float: right;
.arrow-down {
border-top-color: $light-text-color;
left: 100%;
}
}
.theirs {
text-align: left;
background: $ui-highlight-color;
float: left;
.arrow-down {
border-top-color: $ui-highlight-color;
right: 1em;
}
}
.arrow-down {
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 20px solid transparent;
border-top: 20px solid $classic-primary-color;
position: relative;
bottom: -1em;
}
}

View File

@ -0,0 +1,54 @@
.status-direct,
.item-list .conversation {
@extend .airmail-border;
}
.main-instant-messaging {
.column-header__icon {
color: white;
}
}
.messaging-box {
@extend .fixed-box;
right: 1em;
width: $messagingBoxWidth;
background: $ui-base-color;
height: 4em;
color: white;
z-index: 10;
&.active {
height: $messagingBoxHeight;
}
.messager-textarea {
width: 100%;
}
.title {
color: white;
}
}
.toggle-list {
float: right;
margin-left: 1em;
background: $ui-primary-color;
border: 0;
padding: .5em;
width: 2em;
&.active {
background: $highlight-text-color;
}
}
.conversation_created-at {
margin-right: 1em;
}

View File

@ -76,3 +76,5 @@ $no-gap-breakpoint: 415px;
$font-sans-serif: 'mastodon-font-sans-serif' !default; $font-sans-serif: 'mastodon-font-sans-serif' !default;
$font-display: 'mastodon-font-display' !default; $font-display: 'mastodon-font-display' !default;
$font-monospace: 'mastodon-font-monospace' !default; $font-monospace: 'mastodon-font-monospace' !default;
$avatar-side: 55px;

View File

@ -372,8 +372,8 @@
.account__avatar { .account__avatar {
flex: 0 0 auto; flex: 0 0 auto;
width: 36px; width: $avatar-side;
height: 36px; height: $avatar-side;
border-radius: 50%; border-radius: 50%;
position: relative; position: relative;
margin-left: -10px; margin-left: -10px;

View File

@ -5,7 +5,7 @@ class ActivityPub::Activity
include Redisable include Redisable
SUPPORTED_TYPES = %w(Note Question).freeze SUPPORTED_TYPES = %w(Note Question).freeze
CONVERTED_TYPES = %w(Image Audio Video Article Page).freeze CONVERTED_TYPES = %w(Image Audio Video Article Page Event).freeze
def initialize(json, account, **options) def initialize(json, account, **options)
@json = json @json = json

View File

@ -157,7 +157,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
return if tag['name'].blank? return if tag['name'].blank?
Tag.find_or_create_by_names(tag['name']) do |hashtag| Tag.find_or_create_by_names(tag['name']) do |hashtag|
@tags << hashtag unless @tags.include?(hashtag) @tags << hashtag unless @tags.include?(hashtag) || !hashtag.valid?
end end
rescue ActiveRecord::RecordInvalid rescue ActiveRecord::RecordInvalid
nil nil
@ -167,7 +167,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
return if tag['href'].blank? return if tag['href'].blank?
account = account_from_uri(tag['href']) account = account_from_uri(tag['href'])
account = ::FetchRemoteAccountService.new.call(tag['href']) if account.nil? account = ActivityPub::FetchRemoteAccountService.new.call(tag['href']) if account.nil?
return if account.nil? return if account.nil?

View File

@ -7,7 +7,7 @@
# user_id :bigint(8) # user_id :bigint(8)
# dump_file_name :string # dump_file_name :string
# dump_content_type :string # dump_content_type :string
# dump_file_size :integer # dump_file_size :bigint
# dump_updated_at :datetime # dump_updated_at :datetime
# processed :boolean default(FALSE), not null # processed :boolean default(FALSE), not null
# created_at :datetime not null # created_at :datetime not null

View File

@ -16,6 +16,7 @@ class Form::AdminSettings
open_deletion open_deletion
timeline_preview timeline_preview
show_staff_badge show_staff_badge
enable_bootstrap_timeline_accounts
bootstrap_timeline_accounts bootstrap_timeline_accounts
theme theme
min_invite_role min_invite_role
@ -40,6 +41,7 @@ class Form::AdminSettings
open_deletion open_deletion
timeline_preview timeline_preview
show_staff_badge show_staff_badge
enable_bootstrap_timeline_accounts
activity_api_enabled activity_api_enabled
peers_api_enabled peers_api_enabled
show_known_fediverse_at_about_page show_known_fediverse_at_about_page

View File

@ -40,7 +40,7 @@ class Form::CustomEmojiBatch
if category_id.present? if category_id.present?
CustomEmojiCategory.find(category_id) CustomEmojiCategory.find(category_id)
elsif category_name.present? elsif category_name.present?
CustomEmojiCategory.create!(name: category_name) CustomEmojiCategory.find_or_create_by!(name: category_name)
end end
end end

View File

@ -117,7 +117,7 @@ class Tag < ApplicationRecord
class << self class << self
def find_or_create_by_names(name_or_names) def find_or_create_by_names(name_or_names)
Array(name_or_names).map(&method(:normalize)).uniq { |str| str.mb_chars.downcase.to_s }.map do |normalized_name| Array(name_or_names).map(&method(:normalize)).uniq { |str| str.mb_chars.downcase.to_s }.map do |normalized_name|
tag = matching_name(normalized_name).first || create!(name: normalized_name) tag = matching_name(normalized_name).first || create(name: normalized_name)
yield tag if block_given? yield tag if block_given?

View File

@ -3,6 +3,8 @@
require 'rubygems/package' require 'rubygems/package'
class BackupService < BaseService class BackupService < BaseService
include Payloadable
attr_reader :account, :backup, :collection attr_reader :account, :backup, :collection
def call(backup) def call(backup)
@ -20,7 +22,7 @@ class BackupService < BaseService
account.statuses.with_includes.reorder(nil).find_in_batches do |statuses| account.statuses.with_includes.reorder(nil).find_in_batches do |statuses|
statuses.each do |status| statuses.each do |status|
item = serialize(status, ActivityPub::ActivitySerializer) item = serialize_payload(status, ActivityPub::ActivitySerializer, signer: @account)
item.delete(:'@context') item.delete(:'@context')
unless item[:type] == 'Announce' || item[:object][:attachment].blank? unless item[:type] == 'Announce' || item[:object][:attachment].blank?

View File

@ -5,7 +5,7 @@ class BootstrapTimelineService < BaseService
@source_account = source_account @source_account = source_account
autofollow_inviter! autofollow_inviter!
autofollow_bootstrap_timeline_accounts! autofollow_bootstrap_timeline_accounts! if Setting.enable_bootstrap_timeline_accounts
end end
private private

View File

@ -45,7 +45,7 @@ class FetchLinkCardService < BaseService
def html def html
return @html if defined?(@html) return @html if defined?(@html)
Request.new(:get, @url).perform do |res| Request.new(:get, @url).add_headers('Accept' => 'text/html').perform do |res|
if res.code == 200 && res.mime_type == 'text/html' if res.code == 200 && res.mime_type == 'text/html'
@html = res.body_with_limit @html = res.body_with_limit
@html_charset = res.charset @html_charset = res.charset

View File

@ -93,7 +93,7 @@ class FetchOEmbedService
def html def html
return @html if defined?(@html) return @html if defined?(@html)
@html = @options[:html] || Request.new(:get, @url).perform do |res| @html = @options[:html] || Request.new(:get, @url).add_headers('Accept' => 'text/html').perform do |res|
res.code != 200 || res.mime_type != 'text/html' ? nil : res.body_with_limit res.code != 200 || res.mime_type != 'text/html' ? nil : res.body_with_limit
end end
end end

View File

@ -1,17 +0,0 @@
# frozen_string_literal: true
class FetchRemoteAccountService < BaseService
def call(url, prefetched_body = nil, protocol = :ostatus)
if prefetched_body.nil?
resource_url, resource_options, protocol = FetchResourceService.new.call(url)
else
resource_url = url
resource_options = { prefetched_body: prefetched_body }
end
case protocol
when :activitypub
ActivityPub::FetchRemoteAccountService.new.call(resource_url, **resource_options)
end
end
end

View File

@ -1,17 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
class FetchRemoteStatusService < BaseService class FetchRemoteStatusService < BaseService
def call(url, prefetched_body = nil, protocol = :ostatus) def call(url, prefetched_body = nil)
if prefetched_body.nil? if prefetched_body.nil?
resource_url, resource_options, protocol = FetchResourceService.new.call(url) resource_url, resource_options = FetchResourceService.new.call(url)
else else
resource_url = url resource_url = url
resource_options = { prefetched_body: prefetched_body } resource_options = { prefetched_body: prefetched_body }
end end
case protocol ActivityPub::FetchRemoteStatusService.new.call(resource_url, **resource_options) unless resource_url.nil?
when :activitypub
ActivityPub::FetchRemoteStatusService.new.call(resource_url, **resource_options)
end
end end
end end

View File

@ -33,7 +33,7 @@ class FetchResourceService < BaseService
body = response.body_with_limit body = response.body_with_limit
json = body_to_json(body) json = body_to_json(body)
[json['id'], { prefetched_body: body, id: true }, :activitypub] if supported_context?(json) && (equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) || expected_type?(json)) [json['id'], { prefetched_body: body, id: true }] if supported_context?(json) && (equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) || expected_type?(json))
elsif !terminal elsif !terminal
link_header = response['Link'] && parse_link_header(response) link_header = response['Link'] && parse_link_header(response)

View File

@ -19,9 +19,9 @@ class ResolveURLService < BaseService
def process_url def process_url
if equals_or_includes_any?(type, ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) if equals_or_includes_any?(type, ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES)
FetchRemoteAccountService.new.call(resource_url, body, protocol) ActivityPub::FetchRemoteAccountService.new.call(resource_url, prefetched_body: body)
elsif equals_or_includes_any?(type, ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES) elsif equals_or_includes_any?(type, ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
status = FetchRemoteStatusService.new.call(resource_url, body, protocol) status = FetchRemoteStatusService.new.call(resource_url, body)
authorize_with @on_behalf_of, status, :show? unless status.nil? authorize_with @on_behalf_of, status, :show? unless status.nil?
status status
elsif fetched_resource.nil? && @on_behalf_of.present? elsif fetched_resource.nil? && @on_behalf_of.present?
@ -45,12 +45,8 @@ class ResolveURLService < BaseService
fetched_resource.second[:prefetched_body] fetched_resource.second[:prefetched_body]
end end
def protocol
fetched_resource.third
end
def type def type
return json_data['type'] if protocol == :activitypub json_data['type']
end end
def json_data def json_data

View File

@ -4,21 +4,12 @@
- content_for :page_title do - content_for :page_title do
= t('admin.reports.report', id: @report.id) = t('admin.reports.report', id: @report.id)
%div{ style: 'overflow: hidden; margin-bottom: 20px' } - content_for :page_heading_actions do
- if @report.unresolved? - if @report.unresolved?
%div{ style: 'float: right' }
- if @report.target_account.local?
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button'
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive'
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@report.target_account_id, type: 'silence', report_id: @report.id), class: 'button button--destructive'
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@report.target_account_id, type: 'suspend', report_id: @report.id), class: 'button button--destructive'
%div{ style: 'float: left' }
= link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), method: :post, class: 'button' = link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), method: :post, class: 'button'
- else - else
= link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button' = link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button'
%hr.spacer
.table-wrapper .table-wrapper
%table.table.inline-table %table.table.inline-table
%tbody %tbody
@ -77,6 +68,17 @@
%hr.spacer %hr.spacer
%div{ style: 'overflow: hidden; margin-bottom: 20px; clear: both' }
- if @report.unresolved?
%div{ style: 'float: right' }
- if @report.target_account.local?
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button'
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive'
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@report.target_account_id, type: 'silence', report_id: @report.id), class: 'button button--destructive'
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@report.target_account_id, type: 'suspend', report_id: @report.id), class: 'button button--destructive'
%hr.spacer
.speech-bubble .speech-bubble
.speech-bubble__bubble= simple_format(@report.comment.presence || t('admin.reports.comment.none')) .speech-bubble__bubble= simple_format(@report.comment.presence || t('admin.reports.comment.none'))
.speech-bubble__owner .speech-bubble__owner

View File

@ -1,3 +1,6 @@
- content_for :header_tags do
= javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
- content_for :page_title do - content_for :page_title do
= t('admin.settings.title') = t('admin.settings.title')
@ -38,7 +41,9 @@
%hr.spacer/ %hr.spacer/
.fields-group .fields-group
= f.input :bootstrap_timeline_accounts, wrapper: :with_block_label, label: t('admin.settings.bootstrap_timeline_accounts.title'), hint: t('admin.settings.bootstrap_timeline_accounts.desc_html') = f.input :enable_bootstrap_timeline_accounts, as: :boolean, wrapper: :with_label, label: t('admin.settings.enable_bootstrap_timeline_accounts.title')
.fields-group
= f.input :bootstrap_timeline_accounts, wrapper: :with_block_label, label: t('admin.settings.bootstrap_timeline_accounts.title'), hint: t('admin.settings.bootstrap_timeline_accounts.desc_html'), disabled: !Setting.enable_bootstrap_timeline_accounts
%hr.spacer/ %hr.spacer/

View File

@ -21,8 +21,13 @@
.content-wrapper .content-wrapper
.content .content
.content-heading
%h2= yield :page_title %h2= yield :page_title
- if :page_heading_actions
.content-heading-actions
= yield :page_heading_actions
= render 'application/flashes' = render 'application/flashes'
= yield = yield

View File

@ -9,11 +9,11 @@
%td= number_to_human_size @export.total_storage %td= number_to_human_size @export.total_storage
%td %td
%tr %tr
%th= t('accounts.posts', count: @export.total_statuses) %th= t('accounts.posts_tab_heading')
%td= number_with_delimiter @export.total_statuses %td= number_with_delimiter @export.total_statuses
%td %td
%tr %tr
%th= t('exports.follows') %th= t('admin.accounts.follows')
%td= number_with_delimiter @export.total_follows %td= number_with_delimiter @export.total_follows
%td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv) %td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv)
%tr %tr
@ -21,7 +21,7 @@
%td= number_with_delimiter @export.total_lists %td= number_with_delimiter @export.total_lists
%td= table_link_to 'download', t('exports.csv'), settings_exports_lists_path(format: :csv) %td= table_link_to 'download', t('exports.csv'), settings_exports_lists_path(format: :csv)
%tr %tr
%th= t('accounts.followers', count: @export.total_followers) %th= t('admin.accounts.followers')
%td= number_with_delimiter @export.total_followers %td= number_with_delimiter @export.total_followers
%td %td
%tr %tr

View File

@ -21,7 +21,7 @@
%hr.spacer/ %hr.spacer/
.fields-group .fields-group
= f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked') = f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked'), recommended: true
.fields-group .fields-group
= f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot') = f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
@ -32,18 +32,20 @@
%hr.spacer/ %hr.spacer/
.fields-row .custom-label-container
.fields-row__column.fields-group.fields-row__column-6 .fields-group.custom_labels_container
.input.with_block_label .with_block_label
%label= t('simple_form.labels.defaults.fields') %label= t('simple_form.labels.defaults.fields')
%span.hint= t('simple_form.hints.defaults.fields') %span.hint= t('simple_form.hints.defaults.fields')
= f.simple_fields_for :fields do |fields_f| = f.simple_fields_for :fields do |fields_f|
.row .columns
.column.tag-side
= fields_f.input :name, placeholder: t('simple_form.labels.account.fields.name'), input_html: { maxlength: 255 } = fields_f.input :name, placeholder: t('simple_form.labels.account.fields.name'), input_html: { maxlength: 255 }
.column.value-side
= fields_f.input :value, placeholder: t('simple_form.labels.account.fields.value'), input_html: { maxlength: 255 } = fields_f.input :value, placeholder: t('simple_form.labels.account.fields.value'), input_html: { maxlength: 255 }
.fields-row__column.fields-group.fields-row__column-6 .fields-group.verification-container
%h6= t('verification.verification') %h6= t('verification.verification')
%p.hint= t('verification.explanation_html') %p.hint= t('verification.explanation_html')

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