diff options
Diffstat (limited to 'app/assets/javascripts')
190 files changed, 0 insertions, 14857 deletions
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js deleted file mode 100644 index 441282825..000000000 --- a/app/assets/javascripts/application.js +++ /dev/null @@ -1,15 +0,0 @@ -// This is a manifest file that'll be compiled into application.js, which will include all the files -// listed below. -// -// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. -// -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// compiled file. -// -// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details -// about supported directives. -// -//= require jquery2 -//= require jquery_ujs -//= require components diff --git a/app/assets/javascripts/application_public.js b/app/assets/javascripts/application_public.js deleted file mode 100644 index 7df4891e7..000000000 --- a/app/assets/javascripts/application_public.js +++ /dev/null @@ -1,9 +0,0 @@ -//= require jquery2 -//= require jquery_ujs -//= require extras -//= require best_in_place -//= require local_time - -$(function () { - $(".best_in_place").best_in_place(); -}); diff --git a/app/assets/javascripts/components.js b/app/assets/javascripts/components.js deleted file mode 100644 index 1604d5198..000000000 --- a/app/assets/javascripts/components.js +++ /dev/null @@ -1,15 +0,0 @@ -//= require_self -//= require react_ujs - -window.React = require('react'); -window.ReactDOM = require('react-dom'); -window.Perf = require('react-addons-perf'); - -if (!window.Intl) { - require('intl'); - require('intl/locale-data/jsonp/en.js'); -} - -//= require_tree ./components - -window.Mastodon = require('./components/containers/mastodon'); diff --git a/app/assets/javascripts/components/.gitkeep b/app/assets/javascripts/components/.gitkeep deleted file mode 100644 index e69de29bb..000000000 --- a/app/assets/javascripts/components/.gitkeep +++ /dev/null diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx deleted file mode 100644 index eac5c78bb..000000000 --- a/app/assets/javascripts/components/actions/accounts.jsx +++ /dev/null @@ -1,762 +0,0 @@ -import api, { getLinks } from '../api' -import Immutable from 'immutable'; - -export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; -export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; -export const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL'; - -export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST'; -export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS'; -export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL'; - -export const ACCOUNT_UNFOLLOW_REQUEST = 'ACCOUNT_UNFOLLOW_REQUEST'; -export const ACCOUNT_UNFOLLOW_SUCCESS = 'ACCOUNT_UNFOLLOW_SUCCESS'; -export const ACCOUNT_UNFOLLOW_FAIL = 'ACCOUNT_UNFOLLOW_FAIL'; - -export const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST'; -export const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS'; -export const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL'; - -export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST'; -export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS'; -export const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL'; - -export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST'; -export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS'; -export const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL'; - -export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST'; -export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS'; -export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL'; - -export const ACCOUNT_TIMELINE_FETCH_REQUEST = 'ACCOUNT_TIMELINE_FETCH_REQUEST'; -export const ACCOUNT_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_TIMELINE_FETCH_SUCCESS'; -export const ACCOUNT_TIMELINE_FETCH_FAIL = 'ACCOUNT_TIMELINE_FETCH_FAIL'; - -export const ACCOUNT_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_TIMELINE_EXPAND_REQUEST'; -export const ACCOUNT_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_TIMELINE_EXPAND_SUCCESS'; -export const ACCOUNT_TIMELINE_EXPAND_FAIL = 'ACCOUNT_TIMELINE_EXPAND_FAIL'; - -export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST'; -export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS'; -export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL'; - -export const FOLLOWERS_EXPAND_REQUEST = 'FOLLOWERS_EXPAND_REQUEST'; -export const FOLLOWERS_EXPAND_SUCCESS = 'FOLLOWERS_EXPAND_SUCCESS'; -export const FOLLOWERS_EXPAND_FAIL = 'FOLLOWERS_EXPAND_FAIL'; - -export const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST'; -export const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS'; -export const FOLLOWING_FETCH_FAIL = 'FOLLOWING_FETCH_FAIL'; - -export const FOLLOWING_EXPAND_REQUEST = 'FOLLOWING_EXPAND_REQUEST'; -export const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS'; -export const FOLLOWING_EXPAND_FAIL = 'FOLLOWING_EXPAND_FAIL'; - -export const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST'; -export const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS'; -export const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL'; - -export const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST'; -export const FOLLOW_REQUESTS_FETCH_SUCCESS = 'FOLLOW_REQUESTS_FETCH_SUCCESS'; -export const FOLLOW_REQUESTS_FETCH_FAIL = 'FOLLOW_REQUESTS_FETCH_FAIL'; - -export const FOLLOW_REQUESTS_EXPAND_REQUEST = 'FOLLOW_REQUESTS_EXPAND_REQUEST'; -export const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS'; -export const FOLLOW_REQUESTS_EXPAND_FAIL = 'FOLLOW_REQUESTS_EXPAND_FAIL'; - -export const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST'; -export const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS'; -export const FOLLOW_REQUEST_AUTHORIZE_FAIL = 'FOLLOW_REQUEST_AUTHORIZE_FAIL'; - -export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST'; -export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS'; -export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL'; - -export function fetchAccount(id) { - return (dispatch, getState) => { - dispatch(fetchRelationships([id])); - - if (getState().getIn(['accounts', id], null) !== null) { - return; - } - - dispatch(fetchAccountRequest(id)); - - api(getState).get(`/api/v1/accounts/${id}`).then(response => { - dispatch(fetchAccountSuccess(response.data)); - }).catch(error => { - dispatch(fetchAccountFail(id, error)); - }); - }; -}; - -export function fetchAccountTimeline(id, replace = false) { - return (dispatch, getState) => { - const ids = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()); - const newestId = ids.size > 0 ? ids.first() : null; - - let params = ''; - let skipLoading = false; - - if (newestId !== null && !replace) { - params = `?since_id=${newestId}`; - skipLoading = true; - } - - dispatch(fetchAccountTimelineRequest(id, skipLoading)); - - api(getState).get(`/api/v1/accounts/${id}/statuses${params}`).then(response => { - dispatch(fetchAccountTimelineSuccess(id, response.data, replace, skipLoading)); - }).catch(error => { - dispatch(fetchAccountTimelineFail(id, error, skipLoading)); - }); - }; -}; - -export function expandAccountTimeline(id) { - return (dispatch, getState) => { - const lastId = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()).last(); - - dispatch(expandAccountTimelineRequest(id)); - - api(getState).get(`/api/v1/accounts/${id}/statuses`, { - params: { - limit: 10, - max_id: lastId - } - }).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(expandAccountTimelineSuccess(id, response.data, next)); - }).catch(error => { - dispatch(expandAccountTimelineFail(id, error)); - }); - }; -}; - -export function fetchAccountRequest(id) { - return { - type: ACCOUNT_FETCH_REQUEST, - id - }; -}; - -export function fetchAccountSuccess(account) { - return { - type: ACCOUNT_FETCH_SUCCESS, - account - }; -}; - -export function fetchAccountFail(id, error) { - return { - type: ACCOUNT_FETCH_FAIL, - id, - error, - skipAlert: true - }; -}; - -export function followAccount(id) { - return (dispatch, getState) => { - dispatch(followAccountRequest(id)); - - api(getState).post(`/api/v1/accounts/${id}/follow`).then(response => { - dispatch(followAccountSuccess(response.data)); - }).catch(error => { - dispatch(followAccountFail(error)); - }); - }; -}; - -export function unfollowAccount(id) { - return (dispatch, getState) => { - dispatch(unfollowAccountRequest(id)); - - api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => { - dispatch(unfollowAccountSuccess(response.data)); - }).catch(error => { - dispatch(unfollowAccountFail(error)); - }); - } -}; - -export function followAccountRequest(id) { - return { - type: ACCOUNT_FOLLOW_REQUEST, - id - }; -}; - -export function followAccountSuccess(relationship) { - return { - type: ACCOUNT_FOLLOW_SUCCESS, - relationship - }; -}; - -export function followAccountFail(error) { - return { - type: ACCOUNT_FOLLOW_FAIL, - error - }; -}; - -export function unfollowAccountRequest(id) { - return { - type: ACCOUNT_UNFOLLOW_REQUEST, - id - }; -}; - -export function unfollowAccountSuccess(relationship) { - return { - type: ACCOUNT_UNFOLLOW_SUCCESS, - relationship - }; -}; - -export function unfollowAccountFail(error) { - return { - type: ACCOUNT_UNFOLLOW_FAIL, - error - }; -}; - -export function fetchAccountTimelineRequest(id, skipLoading) { - return { - type: ACCOUNT_TIMELINE_FETCH_REQUEST, - id, - skipLoading - }; -}; - -export function fetchAccountTimelineSuccess(id, statuses, replace, skipLoading) { - return { - type: ACCOUNT_TIMELINE_FETCH_SUCCESS, - id, - statuses, - replace, - skipLoading - }; -}; - -export function fetchAccountTimelineFail(id, error, skipLoading) { - return { - type: ACCOUNT_TIMELINE_FETCH_FAIL, - id, - error, - skipLoading, - skipAlert: error.response.status === 404 - }; -}; - -export function expandAccountTimelineRequest(id) { - return { - type: ACCOUNT_TIMELINE_EXPAND_REQUEST, - id - }; -}; - -export function expandAccountTimelineSuccess(id, statuses, next) { - return { - type: ACCOUNT_TIMELINE_EXPAND_SUCCESS, - id, - statuses, - next - }; -}; - -export function expandAccountTimelineFail(id, error) { - return { - type: ACCOUNT_TIMELINE_EXPAND_FAIL, - id, - error - }; -}; - -export function blockAccount(id) { - return (dispatch, getState) => { - dispatch(blockAccountRequest(id)); - - api(getState).post(`/api/v1/accounts/${id}/block`).then(response => { - // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers - dispatch(blockAccountSuccess(response.data, getState().get('statuses'))); - }).catch(error => { - dispatch(blockAccountFail(id, error)); - }); - }; -}; - -export function unblockAccount(id) { - return (dispatch, getState) => { - dispatch(unblockAccountRequest(id)); - - api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => { - dispatch(unblockAccountSuccess(response.data)); - }).catch(error => { - dispatch(unblockAccountFail(id, error)); - }); - }; -}; - -export function blockAccountRequest(id) { - return { - type: ACCOUNT_BLOCK_REQUEST, - id - }; -}; - -export function blockAccountSuccess(relationship, statuses) { - return { - type: ACCOUNT_BLOCK_SUCCESS, - relationship, - statuses - }; -}; - -export function blockAccountFail(error) { - return { - type: ACCOUNT_BLOCK_FAIL, - error - }; -}; - -export function unblockAccountRequest(id) { - return { - type: ACCOUNT_UNBLOCK_REQUEST, - id - }; -}; - -export function unblockAccountSuccess(relationship) { - return { - type: ACCOUNT_UNBLOCK_SUCCESS, - relationship - }; -}; - -export function unblockAccountFail(error) { - return { - type: ACCOUNT_UNBLOCK_FAIL, - error - }; -}; - - -export function muteAccount(id) { - return (dispatch, getState) => { - dispatch(muteAccountRequest(id)); - - api(getState).post(`/api/v1/accounts/${id}/mute`).then(response => { - // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers - dispatch(muteAccountSuccess(response.data, getState().get('statuses'))); - }).catch(error => { - dispatch(muteAccountFail(id, error)); - }); - }; -}; - -export function unmuteAccount(id) { - return (dispatch, getState) => { - dispatch(unmuteAccountRequest(id)); - - api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => { - dispatch(unmuteAccountSuccess(response.data)); - }).catch(error => { - dispatch(unmuteAccountFail(id, error)); - }); - }; -}; - -export function muteAccountRequest(id) { - return { - type: ACCOUNT_MUTE_REQUEST, - id - }; -}; - -export function muteAccountSuccess(relationship, statuses) { - return { - type: ACCOUNT_MUTE_SUCCESS, - relationship, - statuses - }; -}; - -export function muteAccountFail(error) { - return { - type: ACCOUNT_MUTE_FAIL, - error - }; -}; - -export function unmuteAccountRequest(id) { - return { - type: ACCOUNT_UNMUTE_REQUEST, - id - }; -}; - -export function unmuteAccountSuccess(relationship) { - return { - type: ACCOUNT_UNMUTE_SUCCESS, - relationship - }; -}; - -export function unmuteAccountFail(error) { - return { - type: ACCOUNT_UNMUTE_FAIL, - error - }; -}; - - -export function fetchFollowers(id) { - return (dispatch, getState) => { - dispatch(fetchFollowersRequest(id)); - - api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(fetchFollowersSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => { - dispatch(fetchFollowersFail(id, error)); - }); - }; -}; - -export function fetchFollowersRequest(id) { - return { - type: FOLLOWERS_FETCH_REQUEST, - id - }; -}; - -export function fetchFollowersSuccess(id, accounts, next) { - return { - type: FOLLOWERS_FETCH_SUCCESS, - id, - accounts, - next - }; -}; - -export function fetchFollowersFail(id, error) { - return { - type: FOLLOWERS_FETCH_FAIL, - id, - error - }; -}; - -export function expandFollowers(id) { - return (dispatch, getState) => { - const url = getState().getIn(['user_lists', 'followers', id, 'next']); - - if (url === null) { - return; - } - - dispatch(expandFollowersRequest(id)); - - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(expandFollowersSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => { - dispatch(expandFollowersFail(id, error)); - }); - }; -}; - -export function expandFollowersRequest(id) { - return { - type: FOLLOWERS_EXPAND_REQUEST, - id - }; -}; - -export function expandFollowersSuccess(id, accounts, next) { - return { - type: FOLLOWERS_EXPAND_SUCCESS, - id, - accounts, - next - }; -}; - -export function expandFollowersFail(id, error) { - return { - type: FOLLOWERS_EXPAND_FAIL, - id, - error - }; -}; - -export function fetchFollowing(id) { - return (dispatch, getState) => { - dispatch(fetchFollowingRequest(id)); - - api(getState).get(`/api/v1/accounts/${id}/following`).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(fetchFollowingSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => { - dispatch(fetchFollowingFail(id, error)); - }); - }; -}; - -export function fetchFollowingRequest(id) { - return { - type: FOLLOWING_FETCH_REQUEST, - id - }; -}; - -export function fetchFollowingSuccess(id, accounts, next) { - return { - type: FOLLOWING_FETCH_SUCCESS, - id, - accounts, - next - }; -}; - -export function fetchFollowingFail(id, error) { - return { - type: FOLLOWING_FETCH_FAIL, - id, - error - }; -}; - -export function expandFollowing(id) { - return (dispatch, getState) => { - const url = getState().getIn(['user_lists', 'following', id, 'next']); - - if (url === null) { - return; - } - - dispatch(expandFollowingRequest(id)); - - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(expandFollowingSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => { - dispatch(expandFollowingFail(id, error)); - }); - }; -}; - -export function expandFollowingRequest(id) { - return { - type: FOLLOWING_EXPAND_REQUEST, - id - }; -}; - -export function expandFollowingSuccess(id, accounts, next) { - return { - type: FOLLOWING_EXPAND_SUCCESS, - id, - accounts, - next - }; -}; - -export function expandFollowingFail(id, error) { - return { - type: FOLLOWING_EXPAND_FAIL, - id, - error - }; -}; - -export function fetchRelationships(accountIds) { - return (dispatch, getState) => { - const loadedRelationships = getState().get('relationships'); - const newAccountIds = accountIds.filter(id => loadedRelationships.get(id, null) === null); - - if (newAccountIds.length === 0) { - return; - } - - dispatch(fetchRelationshipsRequest(newAccountIds)); - - api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => { - dispatch(fetchRelationshipsSuccess(response.data)); - }).catch(error => { - dispatch(fetchRelationshipsFail(error)); - }); - }; -}; - -export function fetchRelationshipsRequest(ids) { - return { - type: RELATIONSHIPS_FETCH_REQUEST, - ids, - skipLoading: true - }; -}; - -export function fetchRelationshipsSuccess(relationships) { - return { - type: RELATIONSHIPS_FETCH_SUCCESS, - relationships, - skipLoading: true - }; -}; - -export function fetchRelationshipsFail(error) { - return { - type: RELATIONSHIPS_FETCH_FAIL, - error, - skipLoading: true - }; -}; - -export function fetchFollowRequests() { - return (dispatch, getState) => { - dispatch(fetchFollowRequestsRequest()); - - api(getState).get('/api/v1/follow_requests').then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)) - }).catch(error => dispatch(fetchFollowRequestsFail(error))); - }; -}; - -export function fetchFollowRequestsRequest() { - return { - type: FOLLOW_REQUESTS_FETCH_REQUEST - }; -}; - -export function fetchFollowRequestsSuccess(accounts, next) { - return { - type: FOLLOW_REQUESTS_FETCH_SUCCESS, - accounts, - next - }; -}; - -export function fetchFollowRequestsFail(error) { - return { - type: FOLLOW_REQUESTS_FETCH_FAIL, - error - }; -}; - -export function expandFollowRequests() { - return (dispatch, getState) => { - const url = getState().getIn(['user_lists', 'follow_requests', 'next']); - - if (url === null) { - return; - } - - dispatch(expandFollowRequestsRequest()); - - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)) - }).catch(error => dispatch(expandFollowRequestsFail(error))); - }; -}; - -export function expandFollowRequestsRequest() { - return { - type: FOLLOW_REQUESTS_EXPAND_REQUEST - }; -}; - -export function expandFollowRequestsSuccess(accounts, next) { - return { - type: FOLLOW_REQUESTS_EXPAND_SUCCESS, - accounts, - next - }; -}; - -export function expandFollowRequestsFail(error) { - return { - type: FOLLOW_REQUESTS_EXPAND_FAIL, - error - }; -}; - -export function authorizeFollowRequest(id) { - return (dispatch, getState) => { - dispatch(authorizeFollowRequestRequest(id)); - - api(getState) - .post(`/api/v1/follow_requests/${id}/authorize`) - .then(response => dispatch(authorizeFollowRequestSuccess(id))) - .catch(error => dispatch(authorizeFollowRequestFail(id, error))); - }; -}; - -export function authorizeFollowRequestRequest(id) { - return { - type: FOLLOW_REQUEST_AUTHORIZE_REQUEST, - id - }; -}; - -export function authorizeFollowRequestSuccess(id) { - return { - type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS, - id - }; -}; - -export function authorizeFollowRequestFail(id, error) { - return { - type: FOLLOW_REQUEST_AUTHORIZE_FAIL, - id, - error - }; -}; - - -export function rejectFollowRequest(id) { - return (dispatch, getState) => { - dispatch(rejectFollowRequestRequest(id)); - - api(getState) - .post(`/api/v1/follow_requests/${id}/reject`) - .then(response => dispatch(rejectFollowRequestSuccess(id))) - .catch(error => dispatch(rejectFollowRequestFail(id, error))); - }; -}; - -export function rejectFollowRequestRequest(id) { - return { - type: FOLLOW_REQUEST_REJECT_REQUEST, - id - }; -}; - -export function rejectFollowRequestSuccess(id) { - return { - type: FOLLOW_REQUEST_REJECT_SUCCESS, - id - }; -}; - -export function rejectFollowRequestFail(id, error) { - return { - type: FOLLOW_REQUEST_REJECT_FAIL, - id, - error - }; -}; diff --git a/app/assets/javascripts/components/actions/alerts.jsx b/app/assets/javascripts/components/actions/alerts.jsx deleted file mode 100644 index 086e0727e..000000000 --- a/app/assets/javascripts/components/actions/alerts.jsx +++ /dev/null @@ -1,24 +0,0 @@ -export const ALERT_SHOW = 'ALERT_SHOW'; -export const ALERT_DISMISS = 'ALERT_DISMISS'; -export const ALERT_CLEAR = 'ALERT_CLEAR'; - -export function dismissAlert(alert) { - return { - type: ALERT_DISMISS, - alert - }; -}; - -export function clearAlert() { - return { - type: ALERT_CLEAR - }; -}; - -export function showAlert(title, message) { - return { - type: ALERT_SHOW, - title, - message - }; -}; diff --git a/app/assets/javascripts/components/actions/blocks.jsx b/app/assets/javascripts/components/actions/blocks.jsx deleted file mode 100644 index 79e316497..000000000 --- a/app/assets/javascripts/components/actions/blocks.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import api, { getLinks } from '../api' -import { fetchRelationships } from './accounts'; - -export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST'; -export const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS'; -export const BLOCKS_FETCH_FAIL = 'BLOCKS_FETCH_FAIL'; - -export const BLOCKS_EXPAND_REQUEST = 'BLOCKS_EXPAND_REQUEST'; -export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS'; -export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL'; - -export function fetchBlocks() { - return (dispatch, getState) => { - dispatch(fetchBlocksRequest()); - - api(getState).get('/api/v1/blocks').then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => dispatch(fetchBlocksFail(error))); - }; -}; - -export function fetchBlocksRequest() { - return { - type: BLOCKS_FETCH_REQUEST - }; -}; - -export function fetchBlocksSuccess(accounts, next) { - return { - type: BLOCKS_FETCH_SUCCESS, - accounts, - next - }; -}; - -export function fetchBlocksFail(error) { - return { - type: BLOCKS_FETCH_FAIL, - error - }; -}; - -export function expandBlocks() { - return (dispatch, getState) => { - const url = getState().getIn(['user_lists', 'blocks', 'next']); - - if (url === null) { - return; - } - - dispatch(expandBlocksRequest()); - - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(expandBlocksSuccess(response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => dispatch(expandBlocksFail(error))); - }; -}; - -export function expandBlocksRequest() { - return { - type: BLOCKS_EXPAND_REQUEST - }; -}; - -export function expandBlocksSuccess(accounts, next) { - return { - type: BLOCKS_EXPAND_SUCCESS, - accounts, - next - }; -}; - -export function expandBlocksFail(error) { - return { - type: BLOCKS_EXPAND_FAIL, - error - }; -}; diff --git a/app/assets/javascripts/components/actions/cards.jsx b/app/assets/javascripts/components/actions/cards.jsx deleted file mode 100644 index 805be9709..000000000 --- a/app/assets/javascripts/components/actions/cards.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import api from '../api'; - -export const STATUS_CARD_FETCH_REQUEST = 'STATUS_CARD_FETCH_REQUEST'; -export const STATUS_CARD_FETCH_SUCCESS = 'STATUS_CARD_FETCH_SUCCESS'; -export const STATUS_CARD_FETCH_FAIL = 'STATUS_CARD_FETCH_FAIL'; - -export function fetchStatusCard(id) { - return (dispatch, getState) => { - if (getState().getIn(['cards', id], null) !== null) { - return; - } - - dispatch(fetchStatusCardRequest(id)); - - api(getState).get(`/api/v1/statuses/${id}/card`).then(response => { - if (!response.data.url) { - return; - } - - dispatch(fetchStatusCardSuccess(id, response.data)); - }).catch(error => { - dispatch(fetchStatusCardFail(id, error)); - }); - }; -}; - -export function fetchStatusCardRequest(id) { - return { - type: STATUS_CARD_FETCH_REQUEST, - id, - skipLoading: true - }; -}; - -export function fetchStatusCardSuccess(id, card) { - return { - type: STATUS_CARD_FETCH_SUCCESS, - id, - card, - skipLoading: true - }; -}; - -export function fetchStatusCardFail(id, error) { - return { - type: STATUS_CARD_FETCH_FAIL, - id, - error, - skipLoading: true, - skipAlert: true - }; -}; diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx deleted file mode 100644 index d7ff6ea63..000000000 --- a/app/assets/javascripts/components/actions/compose.jsx +++ /dev/null @@ -1,279 +0,0 @@ -import api from '../api'; - -import { updateTimeline } from './timelines'; - -import * as emojione from 'emojione'; - -export const COMPOSE_CHANGE = 'COMPOSE_CHANGE'; -export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST'; -export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS'; -export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL'; -export const COMPOSE_REPLY = 'COMPOSE_REPLY'; -export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'; -export const COMPOSE_MENTION = 'COMPOSE_MENTION'; -export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'; -export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS'; -export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL'; -export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS'; -export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO'; - -export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR'; -export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY'; -export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT'; - -export const COMPOSE_MOUNT = 'COMPOSE_MOUNT'; -export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT'; - -export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE'; -export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; -export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; -export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; -export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE'; - -export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT'; - -export function changeCompose(text) { - return { - type: COMPOSE_CHANGE, - text: text - }; -}; - -export function replyCompose(status, router) { - return (dispatch, getState) => { - dispatch({ - type: COMPOSE_REPLY, - status: status - }); - - if (!getState().getIn(['compose', 'mounted'])) { - router.push('/statuses/new'); - } - }; -}; - -export function cancelReplyCompose() { - return { - type: COMPOSE_REPLY_CANCEL - }; -}; - -export function mentionCompose(account, router) { - return (dispatch, getState) => { - dispatch({ - type: COMPOSE_MENTION, - account: account - }); - - if (!getState().getIn(['compose', 'mounted'])) { - router.push('/statuses/new'); - } - }; -}; - -export function submitCompose() { - return function (dispatch, getState) { - const status = emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], '')); - if (!status || !status.length) { - return; - } - dispatch(submitComposeRequest()); - api(getState).post('/api/v1/statuses', { - status, - in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), - media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')), - sensitive: getState().getIn(['compose', 'sensitive']), - spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''), - visibility: getState().getIn(['compose', 'privacy']) - }, { - headers: { - 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']) - } - }).then(function (response) { - dispatch(submitComposeSuccess({ ...response.data })); - - // To make the app more responsive, immediately get the status into the columns - dispatch(updateTimeline('home', { ...response.data })); - - if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { - if (getState().getIn(['timelines', 'community', 'loaded'])) { - dispatch(updateTimeline('community', { ...response.data })); - } - - if (getState().getIn(['timelines', 'public', 'loaded'])) { - dispatch(updateTimeline('public', { ...response.data })); - } - } - }).catch(function (error) { - dispatch(submitComposeFail(error)); - }); - }; -}; - -export function submitComposeRequest() { - return { - type: COMPOSE_SUBMIT_REQUEST - }; -}; - -export function submitComposeSuccess(status) { - return { - type: COMPOSE_SUBMIT_SUCCESS, - status: status - }; -}; - -export function submitComposeFail(error) { - return { - type: COMPOSE_SUBMIT_FAIL, - error: error - }; -}; - -export function uploadCompose(files) { - return function (dispatch, getState) { - if (getState().getIn(['compose', 'media_attachments']).size > 3) { - return; - } - - dispatch(uploadComposeRequest()); - - let data = new FormData(); - data.append('file', files[0]); - - api(getState).post('/api/v1/media', data, { - onUploadProgress: function (e) { - dispatch(uploadComposeProgress(e.loaded, e.total)); - } - }).then(function (response) { - dispatch(uploadComposeSuccess(response.data)); - }).catch(function (error) { - dispatch(uploadComposeFail(error)); - }); - }; -}; - -export function uploadComposeRequest() { - return { - type: COMPOSE_UPLOAD_REQUEST, - skipLoading: true - }; -}; - -export function uploadComposeProgress(loaded, total) { - return { - type: COMPOSE_UPLOAD_PROGRESS, - loaded: loaded, - total: total - }; -}; - -export function uploadComposeSuccess(media) { - return { - type: COMPOSE_UPLOAD_SUCCESS, - media: media, - skipLoading: true - }; -}; - -export function uploadComposeFail(error) { - return { - type: COMPOSE_UPLOAD_FAIL, - error: error, - skipLoading: true - }; -}; - -export function undoUploadCompose(media_id) { - return { - type: COMPOSE_UPLOAD_UNDO, - media_id: media_id - }; -}; - -export function clearComposeSuggestions() { - return { - type: COMPOSE_SUGGESTIONS_CLEAR - }; -}; - -export function fetchComposeSuggestions(token) { - return (dispatch, getState) => { - api(getState).get('/api/v1/accounts/search', { - params: { - q: token, - resolve: false, - limit: 4 - } - }).then(response => { - dispatch(readyComposeSuggestions(token, response.data)); - }); - }; -}; - -export function readyComposeSuggestions(token, accounts) { - return { - type: COMPOSE_SUGGESTIONS_READY, - token, - accounts - }; -}; - -export function selectComposeSuggestion(position, token, accountId) { - return (dispatch, getState) => { - const completion = getState().getIn(['accounts', accountId, 'acct']); - - dispatch({ - type: COMPOSE_SUGGESTION_SELECT, - position, - token, - completion - }); - }; -}; - -export function mountCompose() { - return { - type: COMPOSE_MOUNT - }; -}; - -export function unmountCompose() { - return { - type: COMPOSE_UNMOUNT - }; -}; - -export function changeComposeSensitivity() { - return { - type: COMPOSE_SENSITIVITY_CHANGE, - }; -}; - -export function changeComposeSpoilerness() { - return { - type: COMPOSE_SPOILERNESS_CHANGE - }; -}; - -export function changeComposeSpoilerText(text) { - return { - type: COMPOSE_SPOILER_TEXT_CHANGE, - text - }; -}; - -export function changeComposeVisibility(value) { - return { - type: COMPOSE_VISIBILITY_CHANGE, - value - }; -}; - -export function insertEmojiCompose(position, emoji) { - return { - type: COMPOSE_EMOJI_INSERT, - position, - emoji - }; -}; diff --git a/app/assets/javascripts/components/actions/favourites.jsx b/app/assets/javascripts/components/actions/favourites.jsx deleted file mode 100644 index a25c1ae1c..000000000 --- a/app/assets/javascripts/components/actions/favourites.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import api, { getLinks } from '../api' - -export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; -export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS'; -export const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL'; - -export const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_REQUEST'; -export const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS'; -export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FAIL'; - -export function fetchFavouritedStatuses() { - return (dispatch, getState) => { - dispatch(fetchFavouritedStatusesRequest()); - - api(getState).get('/api/v1/favourites').then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null)); - }).catch(error => { - dispatch(fetchFavouritedStatusesFail(error)); - }); - }; -}; - -export function fetchFavouritedStatusesRequest() { - return { - type: FAVOURITED_STATUSES_FETCH_REQUEST - }; -}; - -export function fetchFavouritedStatusesSuccess(statuses, next) { - return { - type: FAVOURITED_STATUSES_FETCH_SUCCESS, - statuses, - next - }; -}; - -export function fetchFavouritedStatusesFail(error) { - return { - type: FAVOURITED_STATUSES_FETCH_FAIL, - error - }; -}; - -export function expandFavouritedStatuses() { - return (dispatch, getState) => { - const url = getState().getIn(['status_lists', 'favourites', 'next'], null); - - if (url === null) { - return; - } - - dispatch(expandFavouritedStatusesRequest()); - - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null)); - }).catch(error => { - dispatch(expandFavouritedStatusesFail(error)); - }); - }; -}; - -export function expandFavouritedStatusesRequest() { - return { - type: FAVOURITED_STATUSES_EXPAND_REQUEST - }; -}; - -export function expandFavouritedStatusesSuccess(statuses, next) { - return { - type: FAVOURITED_STATUSES_EXPAND_SUCCESS, - statuses, - next - }; -}; - -export function expandFavouritedStatusesFail(error) { - return { - type: FAVOURITED_STATUSES_EXPAND_FAIL, - error - }; -}; diff --git a/app/assets/javascripts/components/actions/interactions.jsx b/app/assets/javascripts/components/actions/interactions.jsx deleted file mode 100644 index 45f4508f6..000000000 --- a/app/assets/javascripts/components/actions/interactions.jsx +++ /dev/null @@ -1,235 +0,0 @@ -import api from '../api' - -export const REBLOG_REQUEST = 'REBLOG_REQUEST'; -export const REBLOG_SUCCESS = 'REBLOG_SUCCESS'; -export const REBLOG_FAIL = 'REBLOG_FAIL'; - -export const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST'; -export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS'; -export const FAVOURITE_FAIL = 'FAVOURITE_FAIL'; - -export const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST'; -export const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS'; -export const UNREBLOG_FAIL = 'UNREBLOG_FAIL'; - -export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST'; -export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS'; -export const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL'; - -export const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST'; -export const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS'; -export const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL'; - -export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST'; -export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS'; -export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL'; - -export function reblog(status) { - return function (dispatch, getState) { - dispatch(reblogRequest(status)); - - api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then(function (response) { - // The reblog API method returns a new status wrapped around the original. In this case we are only - // interested in how the original is modified, hence passing it skipping the wrapper - dispatch(reblogSuccess(status, response.data.reblog)); - }).catch(function (error) { - dispatch(reblogFail(status, error)); - }); - }; -}; - -export function unreblog(status) { - return (dispatch, getState) => { - dispatch(unreblogRequest(status)); - - api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => { - dispatch(unreblogSuccess(status, response.data)); - }).catch(error => { - dispatch(unreblogFail(status, error)); - }); - }; -}; - -export function reblogRequest(status) { - return { - type: REBLOG_REQUEST, - status: status - }; -}; - -export function reblogSuccess(status, response) { - return { - type: REBLOG_SUCCESS, - status: status, - response: response - }; -}; - -export function reblogFail(status, error) { - return { - type: REBLOG_FAIL, - status: status, - error: error - }; -}; - -export function unreblogRequest(status) { - return { - type: UNREBLOG_REQUEST, - status: status - }; -}; - -export function unreblogSuccess(status, response) { - return { - type: UNREBLOG_SUCCESS, - status: status, - response: response - }; -}; - -export function unreblogFail(status, error) { - return { - type: UNREBLOG_FAIL, - status: status, - error: error - }; -}; - -export function favourite(status) { - return function (dispatch, getState) { - dispatch(favouriteRequest(status)); - - api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) { - dispatch(favouriteSuccess(status, response.data)); - }).catch(function (error) { - dispatch(favouriteFail(status, error)); - }); - }; -}; - -export function unfavourite(status) { - return (dispatch, getState) => { - dispatch(unfavouriteRequest(status)); - - api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => { - dispatch(unfavouriteSuccess(status, response.data)); - }).catch(error => { - dispatch(unfavouriteFail(status, error)); - }); - }; -}; - -export function favouriteRequest(status) { - return { - type: FAVOURITE_REQUEST, - status: status - }; -}; - -export function favouriteSuccess(status, response) { - return { - type: FAVOURITE_SUCCESS, - status: status, - response: response - }; -}; - -export function favouriteFail(status, error) { - return { - type: FAVOURITE_FAIL, - status: status, - error: error - }; -}; - -export function unfavouriteRequest(status) { - return { - type: UNFAVOURITE_REQUEST, - status: status - }; -}; - -export function unfavouriteSuccess(status, response) { - return { - type: UNFAVOURITE_SUCCESS, - status: status, - response: response - }; -}; - -export function unfavouriteFail(status, error) { - return { - type: UNFAVOURITE_FAIL, - status: status, - error: error - }; -}; - -export function fetchReblogs(id) { - return (dispatch, getState) => { - dispatch(fetchReblogsRequest(id)); - - api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => { - dispatch(fetchReblogsSuccess(id, response.data)); - }).catch(error => { - dispatch(fetchReblogsFail(id, error)); - }); - }; -}; - -export function fetchReblogsRequest(id) { - return { - type: REBLOGS_FETCH_REQUEST, - id - }; -}; - -export function fetchReblogsSuccess(id, accounts) { - return { - type: REBLOGS_FETCH_SUCCESS, - id, - accounts - }; -}; - -export function fetchReblogsFail(id, error) { - return { - type: REBLOGS_FETCH_FAIL, - error - }; -}; - -export function fetchFavourites(id) { - return (dispatch, getState) => { - dispatch(fetchFavouritesRequest(id)); - - api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => { - dispatch(fetchFavouritesSuccess(id, response.data)); - }).catch(error => { - dispatch(fetchFavouritesFail(id, error)); - }); - }; -}; - -export function fetchFavouritesRequest(id) { - return { - type: FAVOURITES_FETCH_REQUEST, - id - }; -}; - -export function fetchFavouritesSuccess(id, accounts) { - return { - type: FAVOURITES_FETCH_SUCCESS, - id, - accounts - }; -}; - -export function fetchFavouritesFail(id, error) { - return { - type: FAVOURITES_FETCH_FAIL, - error - }; -}; diff --git a/app/assets/javascripts/components/actions/modal.jsx b/app/assets/javascripts/components/actions/modal.jsx deleted file mode 100644 index 615cd6bfe..000000000 --- a/app/assets/javascripts/components/actions/modal.jsx +++ /dev/null @@ -1,16 +0,0 @@ -export const MODAL_OPEN = 'MODAL_OPEN'; -export const MODAL_CLOSE = 'MODAL_CLOSE'; - -export function openModal(type, props) { - return { - type: MODAL_OPEN, - modalType: type, - modalProps: props - }; -}; - -export function closeModal() { - return { - type: MODAL_CLOSE - }; -}; diff --git a/app/assets/javascripts/components/actions/mutes.jsx b/app/assets/javascripts/components/actions/mutes.jsx deleted file mode 100644 index 824821594..000000000 --- a/app/assets/javascripts/components/actions/mutes.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import api, { getLinks } from '../api' -import { fetchRelationships } from './accounts'; - -export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST'; -export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS'; -export const MUTES_FETCH_FAIL = 'MUTES_FETCH_FAIL'; - -export const MUTES_EXPAND_REQUEST = 'MUTES_EXPAND_REQUEST'; -export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS'; -export const MUTES_EXPAND_FAIL = 'MUTES_EXPAND_FAIL'; - -export function fetchMutes() { - return (dispatch, getState) => { - dispatch(fetchMutesRequest()); - - api(getState).get('/api/v1/mutes').then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(fetchMutesSuccess(response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => dispatch(fetchMutesFail(error))); - }; -}; - -export function fetchMutesRequest() { - return { - type: MUTES_FETCH_REQUEST - }; -}; - -export function fetchMutesSuccess(accounts, next) { - return { - type: MUTES_FETCH_SUCCESS, - accounts, - next - }; -}; - -export function fetchMutesFail(error) { - return { - type: MUTES_FETCH_FAIL, - error - }; -}; - -export function expandMutes() { - return (dispatch, getState) => { - const url = getState().getIn(['user_lists', 'mutes', 'next']); - - if (url === null) { - return; - } - - dispatch(expandMutesRequest()); - - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(expandMutesSuccess(response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => dispatch(expandMutesFail(error))); - }; -}; - -export function expandMutesRequest() { - return { - type: MUTES_EXPAND_REQUEST - }; -}; - -export function expandMutesSuccess(accounts, next) { - return { - type: MUTES_EXPAND_SUCCESS, - accounts, - next - }; -}; - -export function expandMutesFail(error) { - return { - type: MUTES_EXPAND_FAIL, - error - }; -}; diff --git a/app/assets/javascripts/components/actions/notifications.jsx b/app/assets/javascripts/components/actions/notifications.jsx deleted file mode 100644 index b09ca0854..000000000 --- a/app/assets/javascripts/components/actions/notifications.jsx +++ /dev/null @@ -1,165 +0,0 @@ -import api, { getLinks } from '../api' -import Immutable from 'immutable'; -import IntlMessageFormat from 'intl-messageformat'; - -import { fetchRelationships } from './accounts'; - -export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; - -export const NOTIFICATIONS_REFRESH_REQUEST = 'NOTIFICATIONS_REFRESH_REQUEST'; -export const NOTIFICATIONS_REFRESH_SUCCESS = 'NOTIFICATIONS_REFRESH_SUCCESS'; -export const NOTIFICATIONS_REFRESH_FAIL = 'NOTIFICATIONS_REFRESH_FAIL'; - -export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; -export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; -export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; - -export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR'; -export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; - -const fetchRelatedRelationships = (dispatch, notifications) => { - const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id); - - if (accountIds > 0) { - dispatch(fetchRelationships(accountIds)); - } -}; - -export function updateNotifications(notification, intlMessages, intlLocale) { - return (dispatch, getState) => { - const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); - const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); - - dispatch({ - type: NOTIFICATIONS_UPDATE, - notification, - account: notification.account, - status: notification.status, - meta: playSound ? { sound: 'boop' } : undefined - }); - - fetchRelatedRelationships(dispatch, [notification]); - - // Desktop notifications - if (typeof window.Notification !== 'undefined' && showAlert) { - const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }); - const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : $('<p>').html(notification.status ? notification.status.content : '').text(); - - new Notification(title, { body, icon: notification.account.avatar, tag: notification.id }); - } - }; -}; - -const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); - -export function refreshNotifications() { - return (dispatch, getState) => { - dispatch(refreshNotificationsRequest()); - - const params = {}; - const ids = getState().getIn(['notifications', 'items']); - - if (ids.size > 0) { - params.since_id = ids.first().get('id'); - } - - params.exclude_types = excludeTypesFromSettings(getState()); - - api(getState).get('/api/v1/notifications', { params }).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(refreshNotificationsSuccess(response.data, next ? next.uri : null)); - fetchRelatedRelationships(dispatch, response.data); - }).catch(error => { - dispatch(refreshNotificationsFail(error)); - }); - }; -}; - -export function refreshNotificationsRequest() { - return { - type: NOTIFICATIONS_REFRESH_REQUEST - }; -}; - -export function refreshNotificationsSuccess(notifications, next) { - return { - type: NOTIFICATIONS_REFRESH_SUCCESS, - notifications, - accounts: notifications.map(item => item.account), - statuses: notifications.map(item => item.status).filter(status => !!status), - next - }; -}; - -export function refreshNotificationsFail(error) { - return { - type: NOTIFICATIONS_REFRESH_FAIL, - error - }; -}; - -export function expandNotifications() { - return (dispatch, getState) => { - const url = getState().getIn(['notifications', 'next'], null); - - if (url === null || getState().getIn(['notifications', 'isLoading'])) { - return; - } - - dispatch(expandNotificationsRequest()); - - const params = {}; - - params.exclude_types = excludeTypesFromSettings(getState()); - - api(getState).get(url, params).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null)); - fetchRelatedRelationships(dispatch, response.data); - }).catch(error => { - dispatch(expandNotificationsFail(error)); - }); - }; -}; - -export function expandNotificationsRequest() { - return { - type: NOTIFICATIONS_EXPAND_REQUEST - }; -}; - -export function expandNotificationsSuccess(notifications, next) { - return { - type: NOTIFICATIONS_EXPAND_SUCCESS, - notifications, - accounts: notifications.map(item => item.account), - statuses: notifications.map(item => item.status).filter(status => !!status), - next - }; -}; - -export function expandNotificationsFail(error) { - return { - type: NOTIFICATIONS_EXPAND_FAIL, - error - }; -}; - -export function clearNotifications() { - return (dispatch, getState) => { - dispatch({ - type: NOTIFICATIONS_CLEAR - }); - - api(getState).post('/api/v1/notifications/clear'); - }; -}; - -export function scrollTopNotifications(top) { - return { - type: NOTIFICATIONS_SCROLL_TOP, - top - }; -}; diff --git a/app/assets/javascripts/components/actions/onboarding.jsx b/app/assets/javascripts/components/actions/onboarding.jsx deleted file mode 100644 index a161c50ef..000000000 --- a/app/assets/javascripts/components/actions/onboarding.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import { openModal } from './modal'; -import { changeSetting, saveSettings } from './settings'; - -export function showOnboardingOnce() { - return (dispatch, getState) => { - const alreadySeen = getState().getIn(['settings', 'onboarded']); - - if (!alreadySeen) { - dispatch(openModal('ONBOARDING')); - dispatch(changeSetting(['onboarded'], true)); - dispatch(saveSettings()); - } - }; -}; diff --git a/app/assets/javascripts/components/actions/reports.jsx b/app/assets/javascripts/components/actions/reports.jsx deleted file mode 100644 index 094670d62..000000000 --- a/app/assets/javascripts/components/actions/reports.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import api from '../api'; - -export const REPORT_INIT = 'REPORT_INIT'; -export const REPORT_CANCEL = 'REPORT_CANCEL'; - -export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST'; -export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS'; -export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL'; - -export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE'; -export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE'; - -export function initReport(account, status) { - return { - type: REPORT_INIT, - account, - status - }; -}; - -export function cancelReport() { - return { - type: REPORT_CANCEL - }; -}; - -export function toggleStatusReport(statusId, checked) { - return { - type: REPORT_STATUS_TOGGLE, - statusId, - checked, - }; -}; - -export function submitReport() { - return (dispatch, getState) => { - dispatch(submitReportRequest()); - - api(getState).post('/api/v1/reports', { - account_id: getState().getIn(['reports', 'new', 'account_id']), - status_ids: getState().getIn(['reports', 'new', 'status_ids']), - comment: getState().getIn(['reports', 'new', 'comment']) - }).then(response => dispatch(submitReportSuccess(response.data))).catch(error => dispatch(submitReportFail(error))); - }; -}; - -export function submitReportRequest() { - return { - type: REPORT_SUBMIT_REQUEST - }; -}; - -export function submitReportSuccess(report) { - return { - type: REPORT_SUBMIT_SUCCESS, - report - }; -}; - -export function submitReportFail(error) { - return { - type: REPORT_SUBMIT_FAIL, - error - }; -}; - -export function changeReportComment(comment) { - return { - type: REPORT_COMMENT_CHANGE, - comment - }; -}; diff --git a/app/assets/javascripts/components/actions/search.jsx b/app/assets/javascripts/components/actions/search.jsx deleted file mode 100644 index df3ae0db1..000000000 --- a/app/assets/javascripts/components/actions/search.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import api from '../api' - -export const SEARCH_CHANGE = 'SEARCH_CHANGE'; -export const SEARCH_CLEAR = 'SEARCH_CLEAR'; -export const SEARCH_SHOW = 'SEARCH_SHOW'; - -export const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST'; -export const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS'; -export const SEARCH_FETCH_FAIL = 'SEARCH_FETCH_FAIL'; - -export function changeSearch(value) { - return { - type: SEARCH_CHANGE, - value - }; -}; - -export function clearSearch() { - return { - type: SEARCH_CLEAR - }; -}; - -export function submitSearch() { - return (dispatch, getState) => { - const value = getState().getIn(['search', 'value']); - - if (value.length === 0) { - return; - } - - dispatch(fetchSearchRequest()); - - api(getState).get('/api/v1/search', { - params: { - q: value, - resolve: true - } - }).then(response => { - dispatch(fetchSearchSuccess(response.data)); - }).catch(error => { - dispatch(fetchSearchFail(error)); - }); - }; -}; - -export function fetchSearchRequest() { - return { - type: SEARCH_FETCH_REQUEST - }; -}; - -export function fetchSearchSuccess(results) { - return { - type: SEARCH_FETCH_SUCCESS, - results, - accounts: results.accounts, - statuses: results.statuses - }; -}; - -export function fetchSearchFail(error) { - return { - type: SEARCH_FETCH_FAIL, - error - }; -}; - -export function showSearch() { - return { - type: SEARCH_SHOW - }; -}; diff --git a/app/assets/javascripts/components/actions/settings.jsx b/app/assets/javascripts/components/actions/settings.jsx deleted file mode 100644 index c754b30ca..000000000 --- a/app/assets/javascripts/components/actions/settings.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import axios from 'axios'; - -export const SETTING_CHANGE = 'SETTING_CHANGE'; - -export function changeSetting(key, value) { - return { - type: SETTING_CHANGE, - key, - value - }; -}; - -export function saveSettings() { - return (_, getState) => { - axios.put('/api/web/settings', { - data: getState().get('settings').toJS() - }); - }; -}; diff --git a/app/assets/javascripts/components/actions/statuses.jsx b/app/assets/javascripts/components/actions/statuses.jsx deleted file mode 100644 index 19df2c36c..000000000 --- a/app/assets/javascripts/components/actions/statuses.jsx +++ /dev/null @@ -1,141 +0,0 @@ -import api from '../api'; - -import { deleteFromTimelines } from './timelines'; -import { fetchStatusCard } from './cards'; - -export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; -export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'; -export const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL'; - -export const STATUS_DELETE_REQUEST = 'STATUS_DELETE_REQUEST'; -export const STATUS_DELETE_SUCCESS = 'STATUS_DELETE_SUCCESS'; -export const STATUS_DELETE_FAIL = 'STATUS_DELETE_FAIL'; - -export const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST'; -export const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS'; -export const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL'; - -export function fetchStatusRequest(id, skipLoading) { - return { - type: STATUS_FETCH_REQUEST, - id, - skipLoading - }; -}; - -export function fetchStatus(id) { - return (dispatch, getState) => { - const skipLoading = getState().getIn(['statuses', id], null) !== null; - - dispatch(fetchContext(id)); - dispatch(fetchStatusCard(id)); - - if (skipLoading) { - return; - } - - dispatch(fetchStatusRequest(id, skipLoading)); - - api(getState).get(`/api/v1/statuses/${id}`).then(response => { - dispatch(fetchStatusSuccess(response.data, skipLoading)); - }).catch(error => { - dispatch(fetchStatusFail(id, error, skipLoading)); - }); - }; -}; - -export function fetchStatusSuccess(status, skipLoading) { - return { - type: STATUS_FETCH_SUCCESS, - status, - skipLoading - }; -}; - -export function fetchStatusFail(id, error, skipLoading) { - return { - type: STATUS_FETCH_FAIL, - id, - error, - skipLoading, - skipAlert: true - }; -}; - -export function deleteStatus(id) { - return (dispatch, getState) => { - dispatch(deleteStatusRequest(id)); - - api(getState).delete(`/api/v1/statuses/${id}`).then(response => { - dispatch(deleteStatusSuccess(id)); - dispatch(deleteFromTimelines(id)); - }).catch(error => { - dispatch(deleteStatusFail(id, error)); - }); - }; -}; - -export function deleteStatusRequest(id) { - return { - type: STATUS_DELETE_REQUEST, - id: id - }; -}; - -export function deleteStatusSuccess(id) { - return { - type: STATUS_DELETE_SUCCESS, - id: id - }; -}; - -export function deleteStatusFail(id, error) { - return { - type: STATUS_DELETE_FAIL, - id: id, - error: error - }; -}; - -export function fetchContext(id) { - return (dispatch, getState) => { - dispatch(fetchContextRequest(id)); - - api(getState).get(`/api/v1/statuses/${id}/context`).then(response => { - dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants)); - - }).catch(error => { - if (error.response.status === 404) { - dispatch(deleteFromTimelines(id)); - } - - dispatch(fetchContextFail(id, error)); - }); - }; -}; - -export function fetchContextRequest(id) { - return { - type: CONTEXT_FETCH_REQUEST, - id - }; -}; - -export function fetchContextSuccess(id, ancestors, descendants) { - return { - type: CONTEXT_FETCH_SUCCESS, - id, - ancestors, - descendants, - statuses: ancestors.concat(descendants) - }; -}; - -export function fetchContextFail(id, error) { - return { - type: CONTEXT_FETCH_FAIL, - id, - error, - skipAlert: true - }; -}; diff --git a/app/assets/javascripts/components/actions/store.jsx b/app/assets/javascripts/components/actions/store.jsx deleted file mode 100644 index 3bba99549..000000000 --- a/app/assets/javascripts/components/actions/store.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import Immutable from 'immutable'; - -export const STORE_HYDRATE = 'STORE_HYDRATE'; - -const convertState = rawState => - Immutable.fromJS(rawState, (k, v) => - Immutable.Iterable.isIndexed(v) ? v.toList() : v.toMap().mapKeys(x => - Number.isNaN(x * 1) ? x : x * 1)); - -export function hydrateStore(rawState) { - const state = convertState(rawState); - - return { - type: STORE_HYDRATE, - state - }; -}; diff --git a/app/assets/javascripts/components/actions/timelines.jsx b/app/assets/javascripts/components/actions/timelines.jsx deleted file mode 100644 index 6cd1f04b3..000000000 --- a/app/assets/javascripts/components/actions/timelines.jsx +++ /dev/null @@ -1,186 +0,0 @@ -import api, { getLinks } from '../api' -import Immutable from 'immutable'; - -export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; -export const TIMELINE_DELETE = 'TIMELINE_DELETE'; - -export const TIMELINE_REFRESH_REQUEST = 'TIMELINE_REFRESH_REQUEST'; -export const TIMELINE_REFRESH_SUCCESS = 'TIMELINE_REFRESH_SUCCESS'; -export const TIMELINE_REFRESH_FAIL = 'TIMELINE_REFRESH_FAIL'; - -export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'; -export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS'; -export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'; - -export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; - -export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; -export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; - -export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) { - return { - type: TIMELINE_REFRESH_SUCCESS, - timeline, - statuses, - skipLoading, - next - }; -}; - -export function updateTimeline(timeline, status) { - return (dispatch, getState) => { - const references = status.reblog ? getState().get('statuses').filter((item, itemId) => (itemId === status.reblog.id || item.get('reblog') === status.reblog.id)).map((_, itemId) => itemId) : []; - - dispatch({ - type: TIMELINE_UPDATE, - timeline, - status, - references - }); - }; -}; - -export function deleteFromTimelines(id) { - return (dispatch, getState) => { - const accountId = getState().getIn(['statuses', id, 'account']); - const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]); - const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); - - dispatch({ - type: TIMELINE_DELETE, - id, - accountId, - references, - reblogOf - }); - }; -}; - -export function refreshTimelineRequest(timeline, id, skipLoading) { - return { - type: TIMELINE_REFRESH_REQUEST, - timeline, - id, - skipLoading - }; -}; - -export function refreshTimeline(timeline, id = null) { - return function (dispatch, getState) { - if (getState().getIn(['timelines', timeline, 'isLoading'])) { - return; - } - - const ids = getState().getIn(['timelines', timeline, 'items'], Immutable.List()); - const newestId = ids.size > 0 ? ids.first() : null; - let params = getState().getIn(['timelines', timeline, 'params'], {}); - const path = getState().getIn(['timelines', timeline, 'path'])(id); - - let skipLoading = false; - - if (newestId !== null && getState().getIn(['timelines', timeline, 'loaded']) && (id === null || getState().getIn(['timelines', timeline, 'id']) === id)) { - if (id === null && getState().getIn(['timelines', timeline, 'online'])) { - // Skip refreshing when timeline is live anyway - return; - } - - params = { ...params, since_id: newestId }; - skipLoading = true; - } - - dispatch(refreshTimelineRequest(timeline, id, skipLoading)); - - api(getState).get(path, { params }).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(refreshTimelineSuccess(timeline, response.data, skipLoading, next ? next.uri : null)); - }).catch(error => { - dispatch(refreshTimelineFail(timeline, error, skipLoading)); - }); - }; -}; - -export function refreshTimelineFail(timeline, error, skipLoading) { - return { - type: TIMELINE_REFRESH_FAIL, - timeline, - error, - skipLoading - }; -}; - -export function expandTimeline(timeline) { - return (dispatch, getState) => { - if (getState().getIn(['timelines', timeline, 'isLoading'])) { - return; - } - - if (getState().getIn(['timelines', timeline, 'items']).size === 0) { - return; - } - - const path = getState().getIn(['timelines', timeline, 'path'])(getState().getIn(['timelines', timeline, 'id'])); - const params = getState().getIn(['timelines', timeline, 'params'], {}); - const lastId = getState().getIn(['timelines', timeline, 'items']).last(); - - dispatch(expandTimelineRequest(timeline)); - - api(getState).get(path, { - params: { - ...params, - max_id: lastId, - limit: 10 - } - }).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(expandTimelineSuccess(timeline, response.data, next ? next.uri : null)); - }).catch(error => { - dispatch(expandTimelineFail(timeline, error)); - }); - }; -}; - -export function expandTimelineRequest(timeline) { - return { - type: TIMELINE_EXPAND_REQUEST, - timeline - }; -}; - -export function expandTimelineSuccess(timeline, statuses, next) { - return { - type: TIMELINE_EXPAND_SUCCESS, - timeline, - statuses, - next - }; -}; - -export function expandTimelineFail(timeline, error) { - return { - type: TIMELINE_EXPAND_FAIL, - timeline, - error - }; -}; - -export function scrollTopTimeline(timeline, top) { - return { - type: TIMELINE_SCROLL_TOP, - timeline, - top - }; -}; - -export function connectTimeline(timeline) { - return { - type: TIMELINE_CONNECT, - timeline - }; -}; - -export function disconnectTimeline(timeline) { - return { - type: TIMELINE_DISCONNECT, - timeline - }; -}; diff --git a/app/assets/javascripts/components/api.jsx b/app/assets/javascripts/components/api.jsx deleted file mode 100644 index 185729ce0..000000000 --- a/app/assets/javascripts/components/api.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import axios from 'axios'; -import LinkHeader from './link_header'; - -export const getLinks = response => { - const value = response.headers.link; - - if (!value) { - return { refs: [] }; - } - - return LinkHeader.parse(value); -}; - -export default getState => axios.create({ - headers: { - 'Authorization': `Bearer ${getState().getIn(['meta', 'access_token'], '')}` - }, - - transformResponse: [function (data) { - try { - return JSON.parse(data); - } catch(Exception) { - return data; - } - }] -}); diff --git a/app/assets/javascripts/components/components/account.jsx b/app/assets/javascripts/components/components/account.jsx deleted file mode 100644 index 81439bd25..000000000 --- a/app/assets/javascripts/components/components/account.jsx +++ /dev/null @@ -1,91 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import Avatar from './avatar'; -import DisplayName from './display_name'; -import Permalink from './permalink'; -import IconButton from './icon_button'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - follow: { id: 'account.follow', defaultMessage: 'Follow' }, - unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, - requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, - unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, - unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' } -}); - -class Account extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleFollow = this.handleFollow.bind(this); - this.handleBlock = this.handleBlock.bind(this); - this.handleMute = this.handleMute.bind(this); - } - - handleFollow () { - this.props.onFollow(this.props.account); - } - - handleBlock () { - this.props.onBlock(this.props.account); - } - - handleMute () { - this.props.onMute(this.props.account); - } - - render () { - const { account, me, intl } = this.props; - - if (!account) { - return <div />; - } - - let buttons; - - if (account.get('id') !== me && account.get('relationship', null) !== null) { - const following = account.getIn(['relationship', 'following']); - const requested = account.getIn(['relationship', 'requested']); - const blocking = account.getIn(['relationship', 'blocking']); - const muting = account.getIn(['relationship', 'muting']); - - if (requested) { - buttons = <IconButton disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} /> - } else if (blocking) { - buttons = <IconButton active={true} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />; - } else if (muting) { - buttons = <IconButton active={true} icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />; - } else { - buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />; - } - } - - return ( - <div className='account'> - <div className='account__wrapper'> - <Permalink key={account.get('id')} className='account__display-name' href={account.get('url')} to={`/accounts/${account.get('id')}`}> - <div className='account__avatar-wrapper'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={36} /></div> - <DisplayName account={account} /> - </Permalink> - - <div className='account__relationship'> - {buttons} - </div> - </div> - </div> - ); - } - -} - -Account.propTypes = { - account: ImmutablePropTypes.map.isRequired, - me: PropTypes.number.isRequired, - onFollow: PropTypes.func.isRequired, - onBlock: PropTypes.func.isRequired, - onMute: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -} - -export default injectIntl(Account); diff --git a/app/assets/javascripts/components/components/attachment_list.jsx b/app/assets/javascripts/components/components/attachment_list.jsx deleted file mode 100644 index 54841fa51..000000000 --- a/app/assets/javascripts/components/components/attachment_list.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; - -const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; - -class AttachmentList extends React.PureComponent { - - render () { - const { media } = this.props; - - return ( - <div className='attachment-list'> - <div className='attachment-list__icon'> - <i className='fa fa-link' /> - </div> - - <ul className='attachment-list__list'> - {media.map(attachment => - <li key={attachment.get('id')}> - <a href={attachment.get('remote_url')} target='_blank' rel='noopener'>{filename(attachment.get('remote_url'))}</a> - </li> - )} - </ul> - </div> - ); - } -} - -AttachmentList.propTypes = { - media: ImmutablePropTypes.list.isRequired -}; - -export default AttachmentList; diff --git a/app/assets/javascripts/components/components/autosuggest_textarea.jsx b/app/assets/javascripts/components/components/autosuggest_textarea.jsx deleted file mode 100644 index 9a4d5b7e3..000000000 --- a/app/assets/javascripts/components/components/autosuggest_textarea.jsx +++ /dev/null @@ -1,211 +0,0 @@ -import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import { isRtl } from '../rtl'; - -const textAtCursorMatchesToken = (str, caretPosition) => { - let word; - - let left = str.slice(0, caretPosition).search(/\S+$/); - let right = str.slice(caretPosition).search(/\s/); - - if (right < 0) { - word = str.slice(left); - } else { - word = str.slice(left, right + caretPosition); - } - - if (!word || word.trim().length < 2 || word[0] !== '@') { - return [null, null]; - } - - word = word.trim().toLowerCase().slice(1); - - if (word.length > 0) { - return [left + 1, word]; - } else { - return [null, null]; - } -}; - -class AutosuggestTextarea extends React.Component { - - constructor (props, context) { - super(props, context); - this.state = { - suggestionsHidden: false, - selectedSuggestion: 0, - lastToken: null, - tokenStart: 0 - }; - this.onChange = this.onChange.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); - this.onBlur = this.onBlur.bind(this); - this.onSuggestionClick = this.onSuggestionClick.bind(this); - this.setTextarea = this.setTextarea.bind(this); - this.onPaste = this.onPaste.bind(this); - } - - onChange (e) { - const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart); - - if (token !== null && this.state.lastToken !== token) { - this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart }); - this.props.onSuggestionsFetchRequested(token); - } else if (token === null) { - this.setState({ lastToken: null }); - this.props.onSuggestionsClearRequested(); - } - - // auto-resize textarea - e.target.style.height = `${e.target.scrollHeight}px`; - - this.props.onChange(e); - } - - onKeyDown (e) { - const { suggestions, disabled } = this.props; - const { selectedSuggestion, suggestionsHidden } = this.state; - - if (disabled) { - e.preventDefault(); - return; - } - - switch(e.key) { - case 'Escape': - if (!suggestionsHidden) { - e.preventDefault(); - this.setState({ suggestionsHidden: true }); - } - - break; - case 'ArrowDown': - if (suggestions.size > 0 && !suggestionsHidden) { - e.preventDefault(); - this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) }); - } - - break; - case 'ArrowUp': - if (suggestions.size > 0 && !suggestionsHidden) { - e.preventDefault(); - this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) }); - } - - break; - case 'Enter': - case 'Tab': - // Select suggestion - if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) { - e.preventDefault(); - e.stopPropagation(); - this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion)); - } - - break; - } - - if (e.defaultPrevented || !this.props.onKeyDown) { - return; - } - - this.props.onKeyDown(e); - } - - onBlur () { - // If we hide the suggestions immediately, then this will prevent the - // onClick for the suggestions themselves from firing. - // Setting a short window for that to take place before hiding the - // suggestions ensures that can't happen. - setTimeout(() => { - this.setState({ suggestionsHidden: true }); - }, 100); - } - - onSuggestionClick (suggestion, e) { - e.preventDefault(); - this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion); - this.textarea.focus(); - } - - componentWillReceiveProps (nextProps) { - if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) { - this.setState({ suggestionsHidden: false }); - } - } - - setTextarea (c) { - this.textarea = c; - } - - onPaste (e) { - if (e.clipboardData && e.clipboardData.files.length === 1) { - this.props.onPaste(e.clipboardData.files) - e.preventDefault(); - } - } - - reset () { - this.textarea.style.height = 'auto'; - } - - render () { - const { value, suggestions, disabled, placeholder, onKeyUp } = this.props; - const { suggestionsHidden, selectedSuggestion } = this.state; - const style = { direction: 'ltr' }; - - if (isRtl(value)) { - style.direction = 'rtl'; - } - - return ( - <div className='autosuggest-textarea'> - <textarea - ref={this.setTextarea} - className='autosuggest-textarea__textarea' - disabled={disabled} - placeholder={placeholder} - autoFocus={true} - value={value} - onChange={this.onChange} - onKeyDown={this.onKeyDown} - onKeyUp={onKeyUp} - onBlur={this.onBlur} - onPaste={this.onPaste} - style={style} - /> - - <div style={{ display: (suggestions.size > 0 && !suggestionsHidden) ? 'block' : 'none' }} className='autosuggest-textarea__suggestions'> - {suggestions.map((suggestion, i) => ( - <div - role='button' - tabIndex='0' - key={suggestion} - className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`} - onClick={this.onSuggestionClick.bind(this, suggestion)}> - <AutosuggestAccountContainer id={suggestion} /> - </div> - ))} - </div> - </div> - ); - } - -}; - -AutosuggestTextarea.propTypes = { - value: PropTypes.string, - suggestions: ImmutablePropTypes.list, - disabled: PropTypes.bool, - placeholder: PropTypes.string, - onSuggestionSelected: PropTypes.func.isRequired, - onSuggestionsClearRequested: PropTypes.func.isRequired, - onSuggestionsFetchRequested: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - onKeyUp: PropTypes.func, - onKeyDown: PropTypes.func, - onPaste: PropTypes.func.isRequired, -}; - -export default AutosuggestTextarea; diff --git a/app/assets/javascripts/components/components/avatar.jsx b/app/assets/javascripts/components/components/avatar.jsx deleted file mode 100644 index d1a00ac39..000000000 --- a/app/assets/javascripts/components/components/avatar.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import PropTypes from 'prop-types'; - -class Avatar extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - hovering: false - }; - this.handleMouseEnter = this.handleMouseEnter.bind(this); - this.handleMouseLeave = this.handleMouseLeave.bind(this); - } - - handleMouseEnter () { - this.setState({ hovering: true }); - } - - handleMouseLeave () { - this.setState({ hovering: false }); - } - - render () { - const { src, size, staticSrc, animate } = this.props; - const { hovering } = this.state; - - const style = { - ...this.props.style, - width: `${size}px`, - height: `${size}px`, - backgroundSize: `${size}px ${size}px` - }; - - if (hovering || animate) { - style.backgroundImage = `url(${src})`; - } else { - style.backgroundImage = `url(${staticSrc})`; - } - - return ( - <div - className='account__avatar' - onMouseEnter={this.handleMouseEnter} - onMouseLeave={this.handleMouseLeave} - style={style} - /> - ); - } - -} - -Avatar.propTypes = { - src: PropTypes.string.isRequired, - staticSrc: PropTypes.string, - size: PropTypes.number.isRequired, - style: PropTypes.object, - animate: PropTypes.bool -}; - -Avatar.defaultProps = { - animate: false -}; - -export default Avatar; diff --git a/app/assets/javascripts/components/components/button.jsx b/app/assets/javascripts/components/components/button.jsx deleted file mode 100644 index 00d80b1fd..000000000 --- a/app/assets/javascripts/components/components/button.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import PropTypes from 'prop-types'; - -class Button extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - } - - handleClick (e) { - if (!this.props.disabled) { - this.props.onClick(); - } - } - - render () { - const style = { - display: this.props.block ? 'block' : 'inline-block', - width: this.props.block ? '100%' : 'auto', - padding: `0 ${this.props.size / 2.25}px`, - height: `${this.props.size}px`, - lineHeight: `${this.props.size}px` - }; - - return ( - <button className={`button ${this.props.secondary ? 'button-secondary' : ''}`} disabled={this.props.disabled} onClick={this.handleClick} style={{ ...style, ...this.props.style }}> - {this.props.text || this.props.children} - </button> - ); - } - -} - -Button.propTypes = { - text: PropTypes.node, - onClick: PropTypes.func, - disabled: PropTypes.bool, - block: PropTypes.bool, - secondary: PropTypes.bool, - size: PropTypes.number, - style: PropTypes.object, - children: PropTypes.node -}; - -Button.defaultProps = { - size: 36 -}; - -export default Button; diff --git a/app/assets/javascripts/components/components/collapsable.jsx b/app/assets/javascripts/components/components/collapsable.jsx deleted file mode 100644 index 0810768f0..000000000 --- a/app/assets/javascripts/components/components/collapsable.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Motion, spring } from 'react-motion'; -import PropTypes from 'prop-types'; - -const Collapsable = ({ fullHeight, isVisible, children }) => ( - <Motion defaultStyle={{ opacity: !isVisible ? 0 : 100, height: isVisible ? fullHeight : 0 }} style={{ opacity: spring(!isVisible ? 0 : 100), height: spring(!isVisible ? 0 : fullHeight) }}> - {({ opacity, height }) => - <div style={{ height: `${height}px`, overflow: 'hidden', opacity: opacity / 100, display: Math.floor(opacity) === 0 ? 'none' : 'block' }}> - {children} - </div> - } - </Motion> -); - -Collapsable.propTypes = { - fullHeight: PropTypes.number.isRequired, - isVisible: PropTypes.bool.isRequired, - children: PropTypes.node.isRequired -}; - -export default Collapsable; diff --git a/app/assets/javascripts/components/components/column_back_button.jsx b/app/assets/javascripts/components/components/column_back_button.jsx deleted file mode 100644 index 70110f0aa..000000000 --- a/app/assets/javascripts/components/components/column_back_button.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import { FormattedMessage } from 'react-intl'; -import PropTypes from 'prop-types'; - -class ColumnBackButton extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - } - - handleClick () { - if (window.history && window.history.length === 1) this.context.router.push("/"); - else this.context.router.goBack(); - } - - render () { - return ( - <div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button'> - <i className='fa fa-fw fa-chevron-left column-back-button__icon'/> - <FormattedMessage id='column_back_button.label' defaultMessage='Back' /> - </div> - ); - } - -}; - -ColumnBackButton.contextTypes = { - router: PropTypes.object -}; - -export default ColumnBackButton; diff --git a/app/assets/javascripts/components/components/column_back_button_slim.jsx b/app/assets/javascripts/components/components/column_back_button_slim.jsx deleted file mode 100644 index 719690097..000000000 --- a/app/assets/javascripts/components/components/column_back_button_slim.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import { FormattedMessage } from 'react-intl'; -import PropTypes from 'prop-types'; - -class ColumnBackButtonSlim extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - } - - handleClick () { - this.context.router.push('/'); - } - - render () { - return ( - <div className='column-back-button--slim'> - <div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button column-back-button--slim-button'> - <i className='fa fa-fw fa-chevron-left column-back-button__icon' /> - <FormattedMessage id='column_back_button.label' defaultMessage='Back' /> - </div> - </div> - ); - } -} - -ColumnBackButtonSlim.contextTypes = { - router: PropTypes.object -}; - -export default ColumnBackButtonSlim; diff --git a/app/assets/javascripts/components/components/column_collapsable.jsx b/app/assets/javascripts/components/components/column_collapsable.jsx deleted file mode 100644 index 2cb440862..000000000 --- a/app/assets/javascripts/components/components/column_collapsable.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Motion, spring } from 'react-motion'; -import PropTypes from 'prop-types'; - -class ColumnCollapsable extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - collapsed: true - }; - - this.handleToggleCollapsed = this.handleToggleCollapsed.bind(this); - } - - handleToggleCollapsed () { - const currentState = this.state.collapsed; - - this.setState({ collapsed: !currentState }); - - if (!currentState && this.props.onCollapse) { - this.props.onCollapse(); - } - } - - render () { - const { icon, title, fullHeight, children } = this.props; - const { collapsed } = this.state; - const collapsedClassName = collapsed ? 'collapsable-collapsed' : 'collapsable'; - - return ( - <div className='column-collapsable'> - <div role='button' tabIndex='0' title={`${title}`} className={`column-icon ${collapsedClassName}`} onClick={this.handleToggleCollapsed}> - <i className={`fa fa-${icon}`} /> - </div> - - <Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : fullHeight, collapsed ? undefined : { stiffness: 150, damping: 9 }) }}> - {({ opacity, height }) => - <div style={{ overflow: height === fullHeight ? 'auto' : 'hidden', height: `${height}px`, opacity: opacity / 100, maxHeight: '70vh' }}> - {children} - </div> - } - </Motion> - </div> - ); - } -} - -ColumnCollapsable.propTypes = { - icon: PropTypes.string.isRequired, - title: PropTypes.string, - fullHeight: PropTypes.number.isRequired, - children: PropTypes.node, - onCollapse: PropTypes.func -}; - -export default ColumnCollapsable; diff --git a/app/assets/javascripts/components/components/display_name.jsx b/app/assets/javascripts/components/components/display_name.jsx deleted file mode 100644 index d7257e092..000000000 --- a/app/assets/javascripts/components/components/display_name.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import escapeTextContentForBrowser from 'escape-html'; -import emojify from '../emoji'; - -class DisplayName extends React.PureComponent { - - render () { - const displayName = this.props.account.get('display_name').length === 0 ? this.props.account.get('username') : this.props.account.get('display_name'); - const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; - - return ( - <span className='display-name'> - <strong className='display-name__html' dangerouslySetInnerHTML={displayNameHTML} /> <span className='display-name__account'>@{this.props.account.get('acct')}</span> - </span> - ); - } - -}; - -DisplayName.propTypes = { - account: ImmutablePropTypes.map.isRequired -} - -export default DisplayName; diff --git a/app/assets/javascripts/components/components/dropdown_menu.jsx b/app/assets/javascripts/components/components/dropdown_menu.jsx deleted file mode 100644 index f5ee27a11..000000000 --- a/app/assets/javascripts/components/components/dropdown_menu.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown'; -import PropTypes from 'prop-types'; - -class DropdownMenu extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - direction: 'left' - }; - this.setRef = this.setRef.bind(this); - this.renderItem = this.renderItem.bind(this); - } - - setRef (c) { - this.dropdown = c; - } - - handleClick (i, e) { - const { action } = this.props.items[i]; - - if (typeof action === 'function') { - e.preventDefault(); - action(); - this.dropdown.hide(); - } - } - - renderItem (item, i) { - if (item === null) { - return <li key={ 'sep' + i } className='dropdown__sep' />; - } - - const { text, action, href = '#' } = item; - - return ( - <li className='dropdown__content-list-item' key={ text + i }> - <a href={href} target='_blank' rel='noopener' onClick={this.handleClick.bind(this, i)} className='dropdown__content-list-link'> - {text} - </a> - </li> - ); - } - - render () { - const { icon, items, size, direction, ariaLabel } = this.props; - const directionClass = (direction === "left") ? "dropdown__left" : "dropdown__right"; - - return ( - <Dropdown ref={this.setRef}> - <DropdownTrigger className='icon-button' style={{ fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }} aria-label={ariaLabel}> - <i className={ `fa fa-fw fa-${icon} dropdown__icon` } aria-hidden={true} /> - </DropdownTrigger> - - <DropdownContent className={directionClass}> - <ul className='dropdown__content-list'> - {items.map(this.renderItem)} - </ul> - </DropdownContent> - </Dropdown> - ); - } - -} - -DropdownMenu.propTypes = { - icon: PropTypes.string.isRequired, - items: PropTypes.array.isRequired, - size: PropTypes.number.isRequired, - direction: PropTypes.string, - ariaLabel: PropTypes.string -}; - -DropdownMenu.defaultProps = { - ariaLabel: "Menu" -}; - -export default DropdownMenu; diff --git a/app/assets/javascripts/components/components/extended_video_player.jsx b/app/assets/javascripts/components/components/extended_video_player.jsx deleted file mode 100644 index bbbe09da8..000000000 --- a/app/assets/javascripts/components/components/extended_video_player.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import PropTypes from 'prop-types'; - -class ExtendedVideoPlayer extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleLoadedData = this.handleLoadedData.bind(this); - this.setRef = this.setRef.bind(this); - } - - handleLoadedData () { - if (this.props.time) { - this.video.currentTime = this.props.time; - } - } - - componentDidMount () { - this.video.addEventListener('loadeddata', this.handleLoadedData); - } - - componentWillUnmount () { - this.video.removeEventListener('loadeddata', this.handleLoadedData); - } - - setRef (c) { - this.video = c; - } - - render () { - return ( - <div className='extended-video-player'> - <video - ref={this.setRef} - src={this.props.src} - autoPlay - muted={this.props.muted} - controls={this.props.controls} - loop={!this.props.controls} - /> - </div> - ); - } - -} - -ExtendedVideoPlayer.propTypes = { - src: PropTypes.string.isRequired, - time: PropTypes.number, - controls: PropTypes.bool.isRequired, - muted: PropTypes.bool.isRequired -}; - -export default ExtendedVideoPlayer; diff --git a/app/assets/javascripts/components/components/icon_button.jsx b/app/assets/javascripts/components/components/icon_button.jsx deleted file mode 100644 index 67c6513fd..000000000 --- a/app/assets/javascripts/components/components/icon_button.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Motion, spring } from 'react-motion'; -import PropTypes from 'prop-types'; - -class IconButton extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - } - - handleClick (e) { - e.preventDefault(); - - if (!this.props.disabled) { - this.props.onClick(e); - } - } - - render () { - let style = { - fontSize: `${this.props.size}px`, - width: `${this.props.size * 1.28571429}px`, - height: `${this.props.size * 1.28571429}px`, - lineHeight: `${this.props.size}px`, - ...this.props.style - }; - - if (this.props.active) { - style = { ...style, ...this.props.activeStyle }; - } - - const classes = ['icon-button']; - - if (this.props.active) { - classes.push('active'); - } - - if (this.props.disabled) { - classes.push('disabled'); - } - - if (this.props.inverted) { - classes.push('inverted'); - } - - if (this.props.overlay) { - classes.push('overlayed'); - } - - if (this.props.className) { - classes.push(this.props.className) - } - - return ( - <Motion defaultStyle={{ rotate: this.props.active ? -360 : 0 }} style={{ rotate: this.props.animate ? spring(this.props.active ? -360 : 0, { stiffness: 120, damping: 7 }) : 0 }}> - {({ rotate }) => - <button - aria-label={this.props.title} - title={this.props.title} - className={classes.join(' ')} - onClick={this.handleClick} - style={style}> - <i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' /> - </button> - } - </Motion> - ); - } - -} - -IconButton.propTypes = { - className: PropTypes.string, - title: PropTypes.string.isRequired, - icon: PropTypes.string.isRequired, - onClick: PropTypes.func, - size: PropTypes.number, - active: PropTypes.bool, - style: PropTypes.object, - activeStyle: PropTypes.object, - disabled: PropTypes.bool, - inverted: PropTypes.bool, - animate: PropTypes.bool, - overlay: PropTypes.bool -}; - -IconButton.defaultProps = { - size: 18, - active: false, - disabled: false, - animate: false, - overlay: false -}; - -export default IconButton; diff --git a/app/assets/javascripts/components/components/load_more.jsx b/app/assets/javascripts/components/components/load_more.jsx deleted file mode 100644 index f59ff1103..000000000 --- a/app/assets/javascripts/components/components/load_more.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import { FormattedMessage } from 'react-intl'; -import PropTypes from 'prop-types'; - -const LoadMore = ({ onClick }) => ( - <a href="#" className='load-more' role='button' onClick={onClick}> - <FormattedMessage id='status.load_more' defaultMessage='Load more' /> - </a> -); - -LoadMore.propTypes = { - onClick: PropTypes.func -}; - -export default LoadMore; diff --git a/app/assets/javascripts/components/components/loading_indicator.jsx b/app/assets/javascripts/components/components/loading_indicator.jsx deleted file mode 100644 index 61e0a0f15..000000000 --- a/app/assets/javascripts/components/components/loading_indicator.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import { FormattedMessage } from 'react-intl'; - -const LoadingIndicator = () => ( - <div className='loading-indicator'> - <FormattedMessage id='loading_indicator.label' defaultMessage='Loading...' /> - </div> -); - -export default LoadingIndicator; diff --git a/app/assets/javascripts/components/components/media_gallery.jsx b/app/assets/javascripts/components/components/media_gallery.jsx deleted file mode 100644 index 58e134f50..000000000 --- a/app/assets/javascripts/components/components/media_gallery.jsx +++ /dev/null @@ -1,195 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import IconButton from './icon_button'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { isIOS } from '../is_mobile'; - -const messages = defineMessages({ - toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' } -}); - -class Item extends React.PureComponent { - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - } - - handleClick (e) { - const { index, onClick } = this.props; - - if (e.button === 0) { - e.preventDefault(); - onClick(index); - } - - e.stopPropagation(); - } - - render () { - const { attachment, index, size } = this.props; - - let width = 50; - let height = 100; - let top = 'auto'; - let left = 'auto'; - let bottom = 'auto'; - let right = 'auto'; - - if (size === 1) { - width = 100; - } - - if (size === 4 || (size === 3 && index > 0)) { - height = 50; - } - - if (size === 2) { - if (index === 0) { - right = '2px'; - } else { - left = '2px'; - } - } else if (size === 3) { - if (index === 0) { - right = '2px'; - } else if (index > 0) { - left = '2px'; - } - - if (index === 1) { - bottom = '2px'; - } else if (index > 1) { - top = '2px'; - } - } else if (size === 4) { - if (index === 0 || index === 2) { - right = '2px'; - } - - if (index === 1 || index === 3) { - left = '2px'; - } - - if (index < 2) { - bottom = '2px'; - } else { - top = '2px'; - } - } - - let thumbnail = ''; - - if (attachment.get('type') === 'image') { - thumbnail = ( - <a - className='media-gallery__item-thumbnail' - href={attachment.get('remote_url') || attachment.get('url')} - onClick={this.handleClick} - target='_blank' - style={{ backgroundImage: `url(${attachment.get('preview_url')})` }} - /> - ); - } else if (attachment.get('type') === 'gifv') { - const autoPlay = !isIOS() && this.props.autoPlayGif; - - thumbnail = ( - <div className={`media-gallery__gifv ${autoPlay ? 'autoplay' : ''}`}> - <video - className='media-gallery__item-gifv-thumbnail' - role='application' - src={attachment.get('url')} - onClick={this.handleClick} - autoPlay={autoPlay} - loop={true} - muted={true} - /> - - <span className='media-gallery__gifv__label'>GIF</span> - </div> - ); - } - - return ( - <div className='media-gallery__item' key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}> - {thumbnail} - </div> - ); - } - -} - -Item.propTypes = { - attachment: ImmutablePropTypes.map.isRequired, - index: PropTypes.number.isRequired, - size: PropTypes.number.isRequired, - onClick: PropTypes.func.isRequired, - autoPlayGif: PropTypes.bool.isRequired -}; - -class MediaGallery extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - visible: !props.sensitive - }; - this.handleOpen = this.handleOpen.bind(this); - this.handleClick = this.handleClick.bind(this); - } - - handleOpen (e) { - this.setState({ visible: !this.state.visible }); - } - - handleClick (index) { - this.props.onOpenMedia(this.props.media, index); - } - - render () { - const { media, intl, sensitive } = this.props; - - let children; - - if (!this.state.visible) { - let warning; - - if (sensitive) { - warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />; - } else { - warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />; - } - - children = ( - <div role='button' tabIndex='0' className='media-spoiler' onClick={this.handleOpen}> - <span className='media-spoiler__warning'>{warning}</span> - <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> - </div> - ); - } else { - const size = media.take(4).size; - children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} autoPlayGif={this.props.autoPlayGif} index={i} size={size} />); - } - - return ( - <div className='media-gallery' style={{ height: `${this.props.height}px` }}> - <div className='spoiler-button' style={{ display: !this.state.visible ? 'none' : 'block' }}> - <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} /> - </div> - - {children} - </div> - ); - } - -} - -MediaGallery.propTypes = { - sensitive: PropTypes.bool, - media: ImmutablePropTypes.list.isRequired, - height: PropTypes.number.isRequired, - onOpenMedia: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - autoPlayGif: PropTypes.bool.isRequired -}; - -export default injectIntl(MediaGallery); diff --git a/app/assets/javascripts/components/components/missing_indicator.jsx b/app/assets/javascripts/components/components/missing_indicator.jsx deleted file mode 100644 index 75129ae14..000000000 --- a/app/assets/javascripts/components/components/missing_indicator.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import { FormattedMessage } from 'react-intl'; - -const MissingIndicator = () => ( - <div className='missing-indicator'> - <FormattedMessage id='missing_indicator.label' defaultMessage='Not found' /> - </div> -); - -export default MissingIndicator; diff --git a/app/assets/javascripts/components/components/permalink.jsx b/app/assets/javascripts/components/components/permalink.jsx deleted file mode 100644 index ccbe4944f..000000000 --- a/app/assets/javascripts/components/components/permalink.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import PropTypes from 'prop-types'; - -class Permalink extends React.Component { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - } - - handleClick (e) { - if (e.button === 0) { - e.preventDefault(); - this.context.router.push(this.props.to); - } - } - - render () { - const { href, children, className, ...other } = this.props; - - return <a href={href} onClick={this.handleClick} {...other} className={'permalink ' + className}>{children}</a>; - } - -} - -Permalink.contextTypes = { - router: PropTypes.object -}; - -Permalink.propTypes = { - className: PropTypes.string, - href: PropTypes.string.isRequired, - to: PropTypes.string.isRequired, - children: PropTypes.node -}; - -export default Permalink; diff --git a/app/assets/javascripts/components/components/relative_timestamp.jsx b/app/assets/javascripts/components/components/relative_timestamp.jsx deleted file mode 100644 index 9ab472e2c..000000000 --- a/app/assets/javascripts/components/components/relative_timestamp.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import { injectIntl, FormattedRelative } from 'react-intl'; -import PropTypes from 'prop-types'; - -const RelativeTimestamp = ({ intl, timestamp }) => { - const date = new Date(timestamp); - - return ( - <time dateTime={timestamp} title={intl.formatDate(date, { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' })}> - <FormattedRelative value={date} /> - </time> - ); -}; - -RelativeTimestamp.propTypes = { - intl: PropTypes.object.isRequired, - timestamp: PropTypes.string.isRequired -}; - -export default injectIntl(RelativeTimestamp); diff --git a/app/assets/javascripts/components/components/status.jsx b/app/assets/javascripts/components/components/status.jsx deleted file mode 100644 index 193231837..000000000 --- a/app/assets/javascripts/components/components/status.jsx +++ /dev/null @@ -1,121 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import Avatar from './avatar'; -import RelativeTimestamp from './relative_timestamp'; -import DisplayName from './display_name'; -import MediaGallery from './media_gallery'; -import VideoPlayer from './video_player'; -import AttachmentList from './attachment_list'; -import StatusContent from './status_content'; -import StatusActionBar from './status_action_bar'; -import { FormattedMessage } from 'react-intl'; -import emojify from '../emoji'; -import escapeTextContentForBrowser from 'escape-html'; - -class Status extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - this.handleAccountClick = this.handleAccountClick.bind(this); - } - - handleClick () { - const { status } = this.props; - this.context.router.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`); - } - - handleAccountClick (id, e) { - if (e.button === 0) { - e.preventDefault(); - this.context.router.push(`/accounts/${id}`); - } - } - - render () { - let media = ''; - const { status, ...other } = this.props; - - if (status === null) { - return <div />; - } - - if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { - let displayName = status.getIn(['account', 'display_name']); - - if (displayName.length === 0) { - displayName = status.getIn(['account', 'username']); - } - - const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; - - return ( - <div className='status__wrapper'> - <div className='status__prepend'> - <div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div> - <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={displayNameHTML} /></a> }} /> - </div> - - <Status {...other} wrapped={true} status={status.get('reblog')} /> - </div> - ); - } - - if (status.get('media_attachments').size > 0 && !this.props.muted) { - if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { - - } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { - media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />; - } else { - media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />; - } - } - - return ( - <div className={this.props.muted ? 'status muted' : 'status'}> - <div className='status__info'> - <div className='status__info-time'> - <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a> - </div> - - <a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name'> - <div className='status__avatar'> - <Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} /> - </div> - - <DisplayName account={status.get('account')} /> - </a> - </div> - - <StatusContent status={status} onClick={this.handleClick} /> - - {media} - - <StatusActionBar {...this.props} /> - </div> - ); - } - -} - -Status.contextTypes = { - router: PropTypes.object -}; - -Status.propTypes = { - status: ImmutablePropTypes.map, - wrapped: PropTypes.bool, - onReply: PropTypes.func, - onFavourite: PropTypes.func, - onReblog: PropTypes.func, - onDelete: PropTypes.func, - onOpenMedia: PropTypes.func, - onOpenVideo: PropTypes.func, - onBlock: PropTypes.func, - me: PropTypes.number, - boostModal: PropTypes.bool, - autoPlayGif: PropTypes.bool, - muted: PropTypes.bool -}; - -export default Status; diff --git a/app/assets/javascripts/components/components/status_action_bar.jsx b/app/assets/javascripts/components/components/status_action_bar.jsx deleted file mode 100644 index 29938f23e..000000000 --- a/app/assets/javascripts/components/components/status_action_bar.jsx +++ /dev/null @@ -1,137 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import IconButton from './icon_button'; -import DropdownMenu from './dropdown_menu'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - delete: { id: 'status.delete', defaultMessage: 'Delete' }, - mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, - mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, - block: { id: 'account.block', defaultMessage: 'Block @{name}' }, - reply: { id: 'status.reply', defaultMessage: 'Reply' }, - replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' }, - reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, - cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, - favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, - open: { id: 'status.open', defaultMessage: 'Expand this status' }, - report: { id: 'status.report', defaultMessage: 'Report @{name}' } -}); - -class StatusActionBar extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleReplyClick = this.handleReplyClick.bind(this); - this.handleFavouriteClick = this.handleFavouriteClick.bind(this); - this.handleReblogClick = this.handleReblogClick.bind(this); - this.handleDeleteClick = this.handleDeleteClick.bind(this); - this.handleMentionClick = this.handleMentionClick.bind(this); - this.handleMuteClick = this.handleMuteClick.bind(this); - this.handleBlockClick = this.handleBlockClick.bind(this); - this.handleOpen = this.handleOpen.bind(this); - this.handleReport = this.handleReport.bind(this); - } - - handleReplyClick () { - this.props.onReply(this.props.status, this.context.router); - } - - handleFavouriteClick () { - this.props.onFavourite(this.props.status); - } - - handleReblogClick (e) { - this.props.onReblog(this.props.status, e); - } - - handleDeleteClick () { - this.props.onDelete(this.props.status); - } - - handleMentionClick () { - this.props.onMention(this.props.status.get('account'), this.context.router); - } - - handleMuteClick () { - this.props.onMute(this.props.status.get('account')); - } - - handleBlockClick () { - this.props.onBlock(this.props.status.get('account')); - } - - handleOpen () { - this.context.router.push(`/statuses/${this.props.status.get('id')}`); - } - - handleReport () { - this.props.onReport(this.props.status); - this.context.router.push('/report'); - } - - render () { - const { status, me, intl } = this.props; - const reblog_disabled = status.get('visibility') === 'private' || status.get('visibility') === 'direct'; - let menu = []; - - menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen }); - menu.push(null); - - if (status.getIn(['account', 'id']) === me) { - menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); - } else { - menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); - menu.push(null); - menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); - menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick }); - menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport }); - } - - let reblogIcon = 'retweet'; - if (status.get('visibility') === 'direct') reblogIcon = 'envelope'; - else if (status.get('visibility') === 'private') reblogIcon = 'lock'; - let reply_icon; - let reply_title; - if (status.get('in_reply_to_id', null) === null) { - reply_icon = "reply"; - reply_title = intl.formatMessage(messages.reply); - } else { - reply_icon = "reply-all"; - reply_title = intl.formatMessage(messages.replyAll); - } - - return ( - <div className='status__action-bar'> - <div className='status__action-bar-button-wrapper'><IconButton title={reply_title} icon={reply_icon} onClick={this.handleReplyClick} /></div> - <div className='status__action-bar-button-wrapper'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div> - <div className='status__action-bar-button-wrapper'><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} className='star-icon' /></div> - - <div className='status__action-bar-dropdown'> - <DropdownMenu items={menu} icon='ellipsis-h' size={18} direction="right" ariaLabel="More"/> - </div> - </div> - ); - } - -} - -StatusActionBar.contextTypes = { - router: PropTypes.object -}; - -StatusActionBar.propTypes = { - status: ImmutablePropTypes.map.isRequired, - onReply: PropTypes.func, - onFavourite: PropTypes.func, - onReblog: PropTypes.func, - onDelete: PropTypes.func, - onMention: PropTypes.func, - onMute: PropTypes.func, - onBlock: PropTypes.func, - onReport: PropTypes.func, - me: PropTypes.number.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(StatusActionBar); diff --git a/app/assets/javascripts/components/components/status_content.jsx b/app/assets/javascripts/components/components/status_content.jsx deleted file mode 100644 index 714c00951..000000000 --- a/app/assets/javascripts/components/components/status_content.jsx +++ /dev/null @@ -1,157 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import escapeTextContentForBrowser from 'escape-html'; -import PropTypes from 'prop-types'; -import emojify from '../emoji'; -import { isRtl } from '../rtl'; -import { FormattedMessage } from 'react-intl'; -import Permalink from './permalink'; - -class StatusContent extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - hidden: true - }; - this.onMentionClick = this.onMentionClick.bind(this); - this.onHashtagClick = this.onHashtagClick.bind(this); - this.handleMouseDown = this.handleMouseDown.bind(this) - this.handleMouseUp = this.handleMouseUp.bind(this); - this.handleSpoilerClick = this.handleSpoilerClick.bind(this); - }; - - componentDidMount () { - const node = ReactDOM.findDOMNode(this); - const links = node.querySelectorAll('a'); - - for (var i = 0; i < links.length; ++i) { - let link = links[i]; - let mention = this.props.status.get('mentions').find(item => link.href === item.get('url')); - let media = this.props.status.get('media_attachments').find(item => link.href === item.get('text_url') || (item.get('remote_url').length > 0 && link.href === item.get('remote_url'))); - - if (mention) { - link.addEventListener('click', this.onMentionClick.bind(this, mention), false); - link.setAttribute('title', mention.get('acct')); - } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { - link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); - } else if (media) { - link.innerHTML = '<i class="fa fa-fw fa-photo"></i>'; - } else { - link.setAttribute('target', '_blank'); - link.setAttribute('rel', 'noopener'); - link.setAttribute('title', link.href); - } - } - } - - onMentionClick (mention, e) { - if (e.button === 0) { - e.preventDefault(); - this.context.router.push(`/accounts/${mention.get('id')}`); - } - } - - onHashtagClick (hashtag, e) { - hashtag = hashtag.replace(/^#/, '').toLowerCase(); - - if (e.button === 0) { - e.preventDefault(); - this.context.router.push(`/timelines/tag/${hashtag}`); - } - } - - handleMouseDown (e) { - this.startXY = [e.clientX, e.clientY]; - } - - handleMouseUp (e) { - const [ startX, startY ] = this.startXY; - const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)]; - - if (e.target.localName === 'a' || (e.target.parentNode && e.target.parentNode.localName === 'a')) { - return; - } - - if (deltaX + deltaY < 5 && e.button === 0) { - this.props.onClick(); - } - - this.startXY = null; - } - - handleSpoilerClick (e) { - e.preventDefault(); - this.setState({ hidden: !this.state.hidden }); - } - - render () { - const { status } = this.props; - const { hidden } = this.state; - - const content = { __html: emojify(status.get('content')) }; - const spoilerContent = { __html: emojify(escapeTextContentForBrowser(status.get('spoiler_text', ''))) }; - const directionStyle = { direction: 'ltr' }; - - if (isRtl(status.get('content'))) { - directionStyle.direction = 'rtl'; - } - - if (status.get('spoiler_text').length > 0) { - let mentionsPlaceholder = ''; - - const mentionLinks = status.get('mentions').map(item => ( - <Permalink to={`/accounts/${item.get('id')}`} href={item.get('url')} key={item.get('id')} className='mention'> - @<span>{item.get('username')}</span> - </Permalink> - )).reduce((aggregate, item) => [...aggregate, item, ' '], []) - - const toggleText = hidden ? <FormattedMessage id='status.show_more' defaultMessage='Show more' /> : <FormattedMessage id='status.show_less' defaultMessage='Show less' />; - - if (hidden) { - mentionsPlaceholder = <div>{mentionLinks}</div>; - } - - return ( - <div className='status__content' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}> - <p style={{ marginBottom: hidden && status.get('mentions').size === 0 ? '0px' : '' }} > - <span dangerouslySetInnerHTML={spoilerContent} /> <a tabIndex='0' className='status__content__spoiler-link' role='button' onClick={this.handleSpoilerClick}>{toggleText}</a> - </p> - - {mentionsPlaceholder} - - <div style={{ display: hidden ? 'none' : 'block', ...directionStyle }} dangerouslySetInnerHTML={content} /> - </div> - ); - } else if (this.props.onClick) { - return ( - <div - className='status__content' - style={{ ...directionStyle }} - onMouseDown={this.handleMouseDown} - onMouseUp={this.handleMouseUp} - dangerouslySetInnerHTML={content} - /> - ); - } else { - return ( - <div - className='status__content status__content--no-action' - style={{ ...directionStyle }} - dangerouslySetInnerHTML={content} - /> - ); - } - } - -} - -StatusContent.contextTypes = { - router: PropTypes.object -}; - -StatusContent.propTypes = { - status: ImmutablePropTypes.map.isRequired, - onClick: PropTypes.func -}; - -export default StatusContent; diff --git a/app/assets/javascripts/components/components/status_list.jsx b/app/assets/javascripts/components/components/status_list.jsx deleted file mode 100644 index 517c8fe5d..000000000 --- a/app/assets/javascripts/components/components/status_list.jsx +++ /dev/null @@ -1,128 +0,0 @@ -import Status from './status'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ScrollContainer } from 'react-router-scroll'; -import PropTypes from 'prop-types'; -import StatusContainer from '../containers/status_container'; -import LoadMore from './load_more'; - -class StatusList extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleScroll = this.handleScroll.bind(this); - this.setRef = this.setRef.bind(this); - this.handleLoadMore = this.handleLoadMore.bind(this); - } - - handleScroll (e) { - const { scrollTop, scrollHeight, clientHeight } = e.target; - const offset = scrollHeight - scrollTop - clientHeight; - this._oldScrollPosition = scrollHeight - scrollTop; - - if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) { - this.props.onScrollToBottom(); - } else if (scrollTop < 100 && this.props.onScrollToTop) { - this.props.onScrollToTop(); - } else if (this.props.onScroll) { - this.props.onScroll(); - } - } - - componentDidMount () { - this.attachScrollListener(); - } - - componentDidUpdate (prevProps) { - if (this.node.scrollTop > 0 && (prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition)) { - this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition; - } - } - - componentWillUnmount () { - this.detachScrollListener(); - } - - attachScrollListener () { - this.node.addEventListener('scroll', this.handleScroll); - } - - detachScrollListener () { - this.node.removeEventListener('scroll', this.handleScroll); - } - - setRef (c) { - this.node = c; - } - - handleLoadMore (e) { - e.preventDefault(); - this.props.onScrollToBottom(); - } - - render () { - const { statusIds, onScrollToBottom, scrollKey, shouldUpdateScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props; - - let loadMore = ''; - let scrollableArea = ''; - let unread = ''; - - if (!isLoading && statusIds.size > 0 && hasMore) { - loadMore = <LoadMore onClick={this.handleLoadMore} />; - } - - if (isUnread) { - unread = <div className='status-list__unread-indicator' />; - } - - if (isLoading || statusIds.size > 0 || !emptyMessage) { - scrollableArea = ( - <div className='scrollable' ref={this.setRef}> - {unread} - - <div className='status-list'> - {prepend} - - {statusIds.map((statusId) => { - return <StatusContainer key={statusId} id={statusId} />; - })} - - {loadMore} - </div> - </div> - ); - } else { - scrollableArea = ( - <div className='empty-column-indicator' ref={this.setRef}> - {emptyMessage} - </div> - ); - } - - return ( - <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}> - {scrollableArea} - </ScrollContainer> - ); - } - -} - -StatusList.propTypes = { - scrollKey: PropTypes.string.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - onScrollToBottom: PropTypes.func, - onScrollToTop: PropTypes.func, - onScroll: PropTypes.func, - shouldUpdateScroll: PropTypes.func, - isLoading: PropTypes.bool, - isUnread: PropTypes.bool, - hasMore: PropTypes.bool, - prepend: PropTypes.node, - emptyMessage: PropTypes.node -}; - -StatusList.defaultProps = { - trackScroll: true -}; - -export default StatusList; diff --git a/app/assets/javascripts/components/components/video_player.jsx b/app/assets/javascripts/components/components/video_player.jsx deleted file mode 100644 index 09c8ed875..000000000 --- a/app/assets/javascripts/components/components/video_player.jsx +++ /dev/null @@ -1,198 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import IconButton from './icon_button'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { isIOS } from '../is_mobile'; - -const messages = defineMessages({ - toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' }, - toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' }, - expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' }, - expand_video: { id: 'video_player.video_error', defaultMessage: 'Video could not be played' } -}); - -class VideoPlayer extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - visible: !this.props.sensitive, - preview: true, - muted: true, - hasAudio: true, - videoError: false - }; - - this.handleClick = this.handleClick.bind(this); - this.handleVideoClick = this.handleVideoClick.bind(this); - this.handleOpen = this.handleOpen.bind(this); - this.handleVisibility = this.handleVisibility.bind(this); - this.handleExpand = this.handleExpand.bind(this); - this.setRef = this.setRef.bind(this); - this.handleLoadedData = this.handleLoadedData.bind(this); - this.handleVideoError = this.handleVideoError.bind(this); - } - - handleClick () { - this.setState({ muted: !this.state.muted }); - } - - handleVideoClick (e) { - e.stopPropagation(); - - const node = ReactDOM.findDOMNode(this).querySelector('video'); - - if (node.paused) { - node.play(); - } else { - node.pause(); - } - } - - handleOpen () { - this.setState({ preview: !this.state.preview }); - } - - handleVisibility () { - this.setState({ - visible: !this.state.visible, - preview: true - }); - } - - handleExpand () { - this.video.pause(); - this.props.onOpenVideo(this.props.media, this.video.currentTime); - } - - setRef (c) { - this.video = c; - } - - handleLoadedData () { - if (('WebkitAppearance' in document.documentElement.style && this.video.audioTracks.length === 0) || this.video.mozHasAudio === false) { - this.setState({ hasAudio: false }); - } - } - - handleVideoError () { - this.setState({ videoError: true }); - } - - componentDidMount () { - if (!this.video) { - return; - } - - this.video.addEventListener('loadeddata', this.handleLoadedData); - this.video.addEventListener('error', this.handleVideoError); - } - - componentDidUpdate () { - if (!this.video) { - return; - } - - this.video.addEventListener('loadeddata', this.handleLoadedData); - this.video.addEventListener('error', this.handleVideoError); - } - - componentWillUnmount () { - if (!this.video) { - return; - } - - this.video.removeEventListener('loadeddata', this.handleLoadedData); - this.video.removeEventListener('error', this.handleVideoError); - } - - render () { - const { media, intl, width, height, sensitive, autoplay } = this.props; - - let spoilerButton = ( - <div className='status__video-player-spoiler' style={{ display: !this.state.visible ? 'none' : 'block' }} > - <IconButton overlay title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} /> - </div> - ); - - let expandButton = ( - <div className='status__video-player-expand'> - <IconButton overlay title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} /> - </div> - ); - - let muteButton = ''; - - if (this.state.hasAudio) { - muteButton = ( - <div className='status__video-player-mute'> - <IconButton overlay title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} /> - </div> - ); - } - - if (!this.state.visible) { - if (sensitive) { - return ( - <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}> - {spoilerButton} - <span className='media-spoiler__warning'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span> - <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> - </div> - ); - } else { - return ( - <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}> - {spoilerButton} - <span className='media-spoiler__warning'><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span> - <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> - </div> - ); - } - } - - if (this.state.preview && !autoplay) { - return ( - <div role='button' tabIndex='0' className='media-spoiler-video' style={{ width: `${width}px`, height: `${height}px`, background: `url(${media.get('preview_url')}) no-repeat center` }} onClick={this.handleOpen}> - {spoilerButton} - <div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div> - </div> - ); - } - - if (this.state.videoError) { - return ( - <div style={{ width: `${width}px`, height: `${height}px` }} className='video-error-cover' > - <span className='media-spoiler__warning'><FormattedMessage id='video_player.video_error' defaultMessage='Video could not be played' /></span> - </div> - ); - } - - return ( - <div className='status__video-player' style={{ width: `${width}px`, height: `${height}px` }}> - {spoilerButton} - {muteButton} - {expandButton} - <video className='status__video-player-video' role='button' tabIndex='0' ref={this.setRef} src={media.get('url')} autoPlay={!isIOS()} loop={true} muted={this.state.muted} onClick={this.handleVideoClick} /> - </div> - ); - } - -} - -VideoPlayer.propTypes = { - media: ImmutablePropTypes.map.isRequired, - width: PropTypes.number, - height: PropTypes.number, - sensitive: PropTypes.bool, - intl: PropTypes.object.isRequired, - autoplay: PropTypes.bool, - onOpenVideo: PropTypes.func.isRequired -}; - -VideoPlayer.defaultProps = { - width: 239, - height: 110 -}; - -export default injectIntl(VideoPlayer); diff --git a/app/assets/javascripts/components/containers/account_container.jsx b/app/assets/javascripts/components/containers/account_container.jsx deleted file mode 100644 index 3c30be715..000000000 --- a/app/assets/javascripts/components/containers/account_container.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import { connect } from 'react-redux'; -import { makeGetAccount } from '../selectors'; -import Account from '../components/account'; -import { - followAccount, - unfollowAccount, - blockAccount, - unblockAccount, - muteAccount, - unmuteAccount, -} from '../actions/accounts'; - -const makeMapStateToProps = () => { - const getAccount = makeGetAccount(); - - const mapStateToProps = (state, props) => ({ - account: getAccount(state, props.id), - me: state.getIn(['meta', 'me']) - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = (dispatch) => ({ - onFollow (account) { - if (account.getIn(['relationship', 'following'])) { - dispatch(unfollowAccount(account.get('id'))); - } else { - dispatch(followAccount(account.get('id'))); - } - }, - - onBlock (account) { - if (account.getIn(['relationship', 'blocking'])) { - dispatch(unblockAccount(account.get('id'))); - } else { - dispatch(blockAccount(account.get('id'))); - } - }, - - onMute (account) { - if (account.getIn(['relationship', 'muting'])) { - dispatch(unmuteAccount(account.get('id'))); - } else { - dispatch(muteAccount(account.get('id'))); - } - } -}); - -export default connect(makeMapStateToProps, mapDispatchToProps)(Account); diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx deleted file mode 100644 index 3f58b257a..000000000 --- a/app/assets/javascripts/components/containers/mastodon.jsx +++ /dev/null @@ -1,320 +0,0 @@ -import { Provider } from 'react-redux'; -import PropTypes from 'prop-types'; -import configureStore from '../store/configureStore'; -import { - refreshTimelineSuccess, - updateTimeline, - deleteFromTimelines, - refreshTimeline, - connectTimeline, - disconnectTimeline -} from '../actions/timelines'; -import { showOnboardingOnce } from '../actions/onboarding'; -import { updateNotifications, refreshNotifications } from '../actions/notifications'; -import createBrowserHistory from 'history/lib/createBrowserHistory'; -import { - applyRouterMiddleware, - useRouterHistory, - Router, - Route, - IndexRedirect, - IndexRoute -} from 'react-router'; -import { useScroll } from 'react-router-scroll'; -import UI from '../features/ui'; -import Status from '../features/status'; -import GettingStarted from '../features/getting_started'; -import PublicTimeline from '../features/public_timeline'; -import CommunityTimeline from '../features/community_timeline'; -import AccountTimeline from '../features/account_timeline'; -import HomeTimeline from '../features/home_timeline'; -import Compose from '../features/compose'; -import Followers from '../features/followers'; -import Following from '../features/following'; -import Reblogs from '../features/reblogs'; -import Favourites from '../features/favourites'; -import HashtagTimeline from '../features/hashtag_timeline'; -import Notifications from '../features/notifications'; -import FollowRequests from '../features/follow_requests'; -import GenericNotFound from '../features/generic_not_found'; -import FavouritedStatuses from '../features/favourited_statuses'; -import Blocks from '../features/blocks'; -import Mutes from '../features/mutes'; -import Report from '../features/report'; -import { IntlProvider, addLocaleData } from 'react-intl'; -import ar from 'react-intl/locale-data/ar'; -import en from 'react-intl/locale-data/en'; -import de from 'react-intl/locale-data/de'; -import eo from 'react-intl/locale-data/eo'; -import es from 'react-intl/locale-data/es'; -import fa from 'react-intl/locale-data/fa'; -import fi from 'react-intl/locale-data/fi'; -import fr from 'react-intl/locale-data/fr'; -import he from 'react-intl/locale-data/he'; -import hu from 'react-intl/locale-data/hu'; -import it from 'react-intl/locale-data/it'; -import ja from 'react-intl/locale-data/ja'; -import pt from 'react-intl/locale-data/pt'; -import nl from 'react-intl/locale-data/nl'; -import no from 'react-intl/locale-data/no'; -import ru from 'react-intl/locale-data/ru'; -import uk from 'react-intl/locale-data/uk'; -import zh from 'react-intl/locale-data/zh'; -import bg from 'react-intl/locale-data/bg'; -import id from 'react-intl/locale-data/id'; -import { localeData as zh_hk } from '../locales/zh-hk'; -import { localeData as zh_cn } from '../locales/zh-cn'; -import pt_br from '../locales/pt-br'; -import getMessagesForLocale from '../locales'; -import { hydrateStore } from '../actions/store'; -import createStream from '../stream'; - -const store = configureStore(); -const initialState = JSON.parse(document.getElementById("initial-state").textContent); -store.dispatch(hydrateStore(initialState)); - -const browserHistory = useRouterHistory(createBrowserHistory)({ - basename: '/web' -}); - -addLocaleData([ - ...en, - ...ar, - ...de, - ...eo, - ...es, - ...fa, - ...fi, - ...fr, - ...he, - ...hu, - ...it, - ...ja, - ...pt, - ...pt_br, - ...nl, - ...no, - ...ru, - ...uk, - ...zh, - ...zh_hk, - ...zh_cn, - ...bg, - ...id, -]); - -const getTopWhenReplacing = (previous, { location }) => location && location.action === 'REPLACE' && [0, 0]; - -const hiddenColumnContainerStyle = { - position: 'absolute', - left: '0', - top: '0', - visibility: 'hidden' -}; - -class Container extends React.PureComponent { - - constructor(props) { - super(props); - - this.state = { - renderedPersistents: [], - unrenderedPersistents: [], - }; - } - - componentWillMount () { - this.unlistenHistory = null; - - this.setState(() => { - return { - mountImpersistent: false, - renderedPersistents: [], - unrenderedPersistents: [ - {pathname: '/timelines/home', component: HomeTimeline}, - {pathname: '/timelines/public', component: PublicTimeline}, - {pathname: '/timelines/public/local', component: CommunityTimeline}, - - {pathname: '/notifications', component: Notifications}, - {pathname: '/favourites', component: FavouritedStatuses} - ], - }; - }, () => { - if (this.unlistenHistory) { - return; - } - - this.unlistenHistory = browserHistory.listen(location => { - const pathname = location.pathname.replace(/\/$/, '').toLowerCase(); - - this.setState(oldState => { - let persistentMatched = false; - - const newState = { - renderedPersistents: oldState.renderedPersistents.map(persistent => { - const givenMatched = persistent.pathname === pathname; - - if (givenMatched) { - persistentMatched = true; - } - - return { - hidden: !givenMatched, - pathname: persistent.pathname, - component: persistent.component - }; - }), - }; - - if (!persistentMatched) { - newState.unrenderedPersistents = []; - - oldState.unrenderedPersistents.forEach(persistent => { - if (persistent.pathname === pathname) { - persistentMatched = true; - - newState.renderedPersistents.push({ - hidden: false, - pathname: persistent.pathname, - component: persistent.component - }); - } else { - newState.unrenderedPersistents.push(persistent); - } - }); - } - - newState.mountImpersistent = !persistentMatched; - - return newState; - }); - }); - }); - } - - componentWillUnmount () { - if (this.unlistenHistory) { - this.unlistenHistory(); - } - - this.unlistenHistory = "done"; - } - - render () { - // Hide some components rather than unmounting them to allow to show again - // quickly and keep the view state such as the scrolled offset. - const persistentsView = this.state.renderedPersistents.map((persistent) => - <div aria-hidden={persistent.hidden} key={persistent.pathname} className='mastodon-column-container' style={persistent.hidden ? hiddenColumnContainerStyle : null}> - <persistent.component shouldUpdateScroll={persistent.hidden ? Function.prototype : getTopWhenReplacing} /> - </div> - ); - - return ( - <UI> - {this.state.mountImpersistent && this.props.children} - {persistentsView} - </UI> - ); - } -} - -Container.propTypes = { - children: PropTypes.node, -}; - -class Mastodon extends React.Component { - - componentDidMount() { - const { locale } = this.props; - const streamingAPIBaseURL = store.getState().getIn(['meta', 'streaming_api_base_url']); - const accessToken = store.getState().getIn(['meta', 'access_token']); - - this.subscription = createStream(streamingAPIBaseURL, accessToken, 'user', { - - connected () { - store.dispatch(connectTimeline('home')); - }, - - disconnected () { - store.dispatch(disconnectTimeline('home')); - }, - - received (data) { - switch(data.event) { - case 'update': - store.dispatch(updateTimeline('home', JSON.parse(data.payload))); - break; - case 'delete': - store.dispatch(deleteFromTimelines(data.payload)); - break; - case 'notification': - store.dispatch(updateNotifications(JSON.parse(data.payload), getMessagesForLocale(locale), locale)); - break; - } - }, - - reconnected () { - store.dispatch(connectTimeline('home')); - store.dispatch(refreshTimeline('home')); - store.dispatch(refreshNotifications()); - } - - }); - - // Desktop notifications - if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') { - Notification.requestPermission(); - } - - store.dispatch(showOnboardingOnce()); - } - - componentWillUnmount () { - if (typeof this.subscription !== 'undefined') { - this.subscription.close(); - this.subscription = null; - } - } - - render () { - const { locale } = this.props; - - return ( - <IntlProvider locale={locale} messages={getMessagesForLocale(locale)}> - <Provider store={store}> - <Router history={browserHistory} render={applyRouterMiddleware(useScroll())}> - <Route path='/' component={Container}> - <IndexRedirect to="/getting-started" /> - - <Route path='getting-started' component={GettingStarted} /> - <Route path='timelines/tag/:id' component={HashtagTimeline} /> - - <Route path='statuses/new' component={Compose} /> - <Route path='statuses/:statusId' component={Status} /> - <Route path='statuses/:statusId/reblogs' component={Reblogs} /> - <Route path='statuses/:statusId/favourites' component={Favourites} /> - - <Route path='accounts/:accountId' component={AccountTimeline} /> - <Route path='accounts/:accountId/followers' component={Followers} /> - <Route path='accounts/:accountId/following' component={Following} /> - - <Route path='follow_requests' component={FollowRequests} /> - <Route path='blocks' component={Blocks} /> - <Route path='mutes' component={Mutes} /> - <Route path='report' component={Report} /> - - <Route path='*' component={GenericNotFound} /> - </Route> - </Router> - </Provider> - </IntlProvider> - ); - } - -} - -Mastodon.propTypes = { - locale: PropTypes.string.isRequired -}; - -export default Mastodon; diff --git a/app/assets/javascripts/components/containers/status_container.jsx b/app/assets/javascripts/components/containers/status_container.jsx deleted file mode 100644 index ae83d36c9..000000000 --- a/app/assets/javascripts/components/containers/status_container.jsx +++ /dev/null @@ -1,117 +0,0 @@ -import { connect } from 'react-redux'; -import Status from '../components/status'; -import { makeGetStatus } from '../selectors'; -import { - replyCompose, - mentionCompose -} from '../actions/compose'; -import { - reblog, - favourite, - unreblog, - unfavourite -} from '../actions/interactions'; -import { - blockAccount, - muteAccount -} from '../actions/accounts'; -import { deleteStatus } from '../actions/statuses'; -import { initReport } from '../actions/reports'; -import { openModal } from '../actions/modal'; -import { createSelector } from 'reselect' -import { isMobile } from '../is_mobile' -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -const messages = defineMessages({ - deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, - deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, - blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, - muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' }, -}); - -const makeMapStateToProps = () => { - const getStatus = makeGetStatus(); - - const mapStateToProps = (state, props) => ({ - status: getStatus(state, props.id), - me: state.getIn(['meta', 'me']), - boostModal: state.getIn(['meta', 'boost_modal']), - autoPlayGif: state.getIn(['meta', 'auto_play_gif']) - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = (dispatch, { intl }) => ({ - - onReply (status, router) { - dispatch(replyCompose(status, router)); - }, - - onModalReblog (status) { - dispatch(reblog(status)); - }, - - onReblog (status, e) { - if (status.get('reblogged')) { - dispatch(unreblog(status)); - } else { - if (e.shiftKey || !this.boostModal) { - this.onModalReblog(status); - } else { - dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); - } - } - }, - - onFavourite (status) { - if (status.get('favourited')) { - dispatch(unfavourite(status)); - } else { - dispatch(favourite(status)); - } - }, - - onDelete (status) { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'))) - })); - }, - - onMention (account, router) { - dispatch(mentionCompose(account, router)); - }, - - onOpenMedia (media, index) { - dispatch(openModal('MEDIA', { media, index })); - }, - - onOpenVideo (media, time) { - dispatch(openModal('VIDEO', { media, time })); - }, - - onBlock (account) { - dispatch(openModal('CONFIRM', { - message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - confirm: intl.formatMessage(messages.blockConfirm), - onConfirm: () => dispatch(blockAccount(account.get('id'))) - })); - }, - - onReport (status) { - dispatch(initReport(status.get('account'), status)); - }, - - onMute (account) { - dispatch(openModal('CONFIRM', { - message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - confirm: intl.formatMessage(messages.muteConfirm), - onConfirm: () => dispatch(muteAccount(account.get('id'))) - })); - }, - -}); - -export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status)); diff --git a/app/assets/javascripts/components/emoji.jsx b/app/assets/javascripts/components/emoji.jsx deleted file mode 100644 index eee657b86..000000000 --- a/app/assets/javascripts/components/emoji.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import emojione from 'emojione'; - -const toImage = str => shortnameToImage(unicodeToImage(str)); - -const unicodeToImage = str => { - const mappedUnicode = emojione.mapUnicodeToShort(); - - return str.replace(emojione.regUnicode, unicodeChar => { - if (typeof unicodeChar === 'undefined' || unicodeChar === '' || !(unicodeChar in emojione.jsEscapeMap)) { - return unicodeChar; - } - - const unicode = emojione.jsEscapeMap[unicodeChar]; - const short = mappedUnicode[unicode]; - const filename = emojione.emojioneList[short].fname; - const alt = emojione.convert(unicode.toUpperCase()); - - return `<img draggable="false" class="emojione" alt="${alt}" src="/emoji/${filename}.svg" />`; - }); -}; - -const shortnameToImage = str => str.replace(emojione.regShortNames, shortname => { - if (typeof shortname === 'undefined' || shortname === '' || !(shortname in emojione.emojioneList)) { - return shortname; - } - - const unicode = emojione.emojioneList[shortname].unicode[emojione.emojioneList[shortname].unicode.length - 1]; - const alt = emojione.convert(unicode.toUpperCase()); - - return `<img draggable="false" class="emojione" alt="${alt}" src="/emoji/${unicode}.svg" />`; -}); - -export default function emojify(text) { - return toImage(text); -}; diff --git a/app/assets/javascripts/components/features/account/components/action_bar.jsx b/app/assets/javascripts/components/features/account/components/action_bar.jsx deleted file mode 100644 index 772ea3a38..000000000 --- a/app/assets/javascripts/components/features/account/components/action_bar.jsx +++ /dev/null @@ -1,92 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import DropdownMenu from '../../../components/dropdown_menu'; -import { Link } from 'react-router'; -import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl'; - -const messages = defineMessages({ - mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, - edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, - unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, - unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, - unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, - block: { id: 'account.block', defaultMessage: 'Block @{name}' }, - mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, - follow: { id: 'account.follow', defaultMessage: 'Follow' }, - report: { id: 'account.report', defaultMessage: 'Report @{name}' }, - disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' } -}); - -class ActionBar extends React.PureComponent { - - render () { - const { account, me, intl } = this.props; - - let menu = []; - let extraInfo = ''; - - menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); - menu.push(null); - - if (account.get('id') === me) { - menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }); - } else { - if (account.getIn(['relationship', 'muting'])) { - menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute }); - } else { - menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute }); - } - - if (account.getIn(['relationship', 'blocking'])) { - menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock }); - } else { - menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock }); - } - - menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport }); - } - - if (account.get('acct') !== account.get('username')) { - extraInfo = <abbr title={intl.formatMessage(messages.disclaimer)}>*</abbr>; - } - - return ( - <div className='account__action-bar'> - <div className='account__action-bar-dropdown'> - <DropdownMenu items={menu} icon='bars' size={24} direction="right" /> - </div> - - <div className='account__action-bar-links'> - <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}`}> - <span><FormattedMessage id='account.posts' defaultMessage='Posts' /></span> - <strong><FormattedNumber value={account.get('statuses_count')} /> {extraInfo}</strong> - </Link> - - <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`}> - <span><FormattedMessage id='account.follows' defaultMessage='Follows' /></span> - <strong><FormattedNumber value={account.get('following_count')} /> {extraInfo}</strong> - </Link> - - <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`}> - <span><FormattedMessage id='account.followers' defaultMessage='Followers' /></span> - <strong><FormattedNumber value={account.get('followers_count')} /> {extraInfo}</strong> - </Link> - </div> - </div> - ); - } - -} - -ActionBar.propTypes = { - account: ImmutablePropTypes.map.isRequired, - me: PropTypes.number.isRequired, - onFollow: PropTypes.func, - onBlock: PropTypes.func.isRequired, - onMention: PropTypes.func.isRequired, - onReport: PropTypes.func.isRequired, - onMute: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(ActionBar); diff --git a/app/assets/javascripts/components/features/account/components/header.jsx b/app/assets/javascripts/components/features/account/components/header.jsx deleted file mode 100644 index 958a5206b..000000000 --- a/app/assets/javascripts/components/features/account/components/header.jsx +++ /dev/null @@ -1,148 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import emojify from '../../../emoji'; -import escapeTextContentForBrowser from 'escape-html'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import IconButton from '../../../components/icon_button'; -import { Motion, spring } from 'react-motion'; -import { connect } from 'react-redux'; - -const messages = defineMessages({ - unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, - follow: { id: 'account.follow', defaultMessage: 'Follow' }, - requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' } -}); - -const makeMapStateToProps = () => { - const mapStateToProps = (state, props) => ({ - autoPlayGif: state.getIn(['meta', 'auto_play_gif']) - }); - - return mapStateToProps; -}; - -class Avatar extends React.PureComponent { - - constructor (props, context) { - super(props, context); - - this.state = { - isHovered: false - }; - - this.handleMouseOver = this.handleMouseOver.bind(this); - this.handleMouseOut = this.handleMouseOut.bind(this); - } - - handleMouseOver () { - if (this.state.isHovered) return; - this.setState({ isHovered: true }); - } - - handleMouseOut () { - if (!this.state.isHovered) return; - this.setState({ isHovered: false }); - } - - render () { - const { account, autoPlayGif } = this.props; - const { isHovered } = this.state; - - return ( - <Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}> - {({ radius }) => - <a - href={account.get('url')} - className='account__header__avatar' - target='_blank' - rel='noopener' - style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }} - onMouseOver={this.handleMouseOver} - onMouseOut={this.handleMouseOut} - onFocus={this.handleMouseOver} - onBlur={this.handleMouseOut} - /> - } - </Motion> - ); - } - -} - -Avatar.propTypes = { - account: ImmutablePropTypes.map.isRequired, - autoPlayGif: PropTypes.bool.isRequired -}; - -class Header extends React.Component { - - render () { - const { account, me, intl } = this.props; - - if (!account) { - return null; - } - - let displayName = account.get('display_name'); - let info = ''; - let actionBtn = ''; - let lockedIcon = ''; - - if (displayName.length === 0) { - displayName = account.get('username'); - } - - if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) { - info = <span className='account--follows-info' style={{ position: 'absolute', top: '10px', right: '10px', opacity: '0.7', display: 'inline-block', verticalAlign: 'top', background: 'rgba(0, 0, 0, 0.4)', textTransform: 'uppercase', fontSize: '11px', fontWeight: '500', padding: '4px', borderRadius: '4px' }}><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span> - } - - if (me !== account.get('id')) { - if (account.getIn(['relationship', 'requested'])) { - actionBtn = ( - <div style={{ position: 'absolute', top: '10px', left: '20px' }}> - <IconButton size={26} disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} /> - </div> - ); - } else if (!account.getIn(['relationship', 'blocking'])) { - actionBtn = ( - <div style={{ position: 'absolute', top: '10px', left: '20px' }}> - <IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} /> - </div> - ); - } - } - - if (account.get('locked')) { - lockedIcon = <i className='fa fa-lock' />; - } - - const content = { __html: emojify(account.get('note')) }; - const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; - - return ( - <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}> - <div style={{ padding: '20px 10px' }}> - <Avatar account={account} autoPlayGif={this.props.autoPlayGif} /> - - <span style={{ display: 'inline-block', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }} className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} /> - <span className='account__header__username' style={{ fontSize: '14px', fontWeight: '400', display: 'block', marginBottom: '10px' }}>@{account.get('acct')} {lockedIcon}</span> - <div style={{ fontSize: '14px' }} className='account__header__content' dangerouslySetInnerHTML={content} /> - - {info} - {actionBtn} - </div> - </div> - ); - } - -} - -Header.propTypes = { - account: ImmutablePropTypes.map, - me: PropTypes.number.isRequired, - onFollow: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - autoPlayGif: PropTypes.bool.isRequired -}; - -export default connect(makeMapStateToProps)(injectIntl(Header)); diff --git a/app/assets/javascripts/components/features/account_timeline/components/header.jsx b/app/assets/javascripts/components/features/account_timeline/components/header.jsx deleted file mode 100644 index fd66c13e0..000000000 --- a/app/assets/javascripts/components/features/account_timeline/components/header.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import InnerHeader from '../../account/components/header'; -import ActionBar from '../../account/components/action_bar'; -import MissingIndicator from '../../../components/missing_indicator'; - -class Header extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleFollow = this.handleFollow.bind(this); - this.handleBlock = this.handleBlock.bind(this); - this.handleMention = this.handleMention.bind(this); - this.handleReport = this.handleReport.bind(this); - this.handleMute = this.handleMute.bind(this); - } - - handleFollow () { - this.props.onFollow(this.props.account); - } - - handleBlock () { - this.props.onBlock(this.props.account); - } - - handleMention () { - this.props.onMention(this.props.account, this.context.router); - } - - handleReport () { - this.props.onReport(this.props.account); - this.context.router.push('/report'); - } - - handleMute() { - this.props.onMute(this.props.account); - } - - render () { - const { account, me } = this.props; - - if (account === null) { - return <MissingIndicator />; - } - - return ( - <div className='account-timeline__header'> - <InnerHeader - account={account} - me={me} - onFollow={this.handleFollow} - /> - - <ActionBar - account={account} - me={me} - onBlock={this.handleBlock} - onMention={this.handleMention} - onReport={this.handleReport} - onMute={this.handleMute} - /> - </div> - ); - } -} - -Header.propTypes = { - account: ImmutablePropTypes.map, - me: PropTypes.number.isRequired, - onFollow: PropTypes.func.isRequired, - onBlock: PropTypes.func.isRequired, - onMention: PropTypes.func.isRequired, - onReport: PropTypes.func.isRequired, - onMute: PropTypes.func.isRequired -}; - -Header.contextTypes = { - router: PropTypes.object -}; - -export default Header; diff --git a/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx b/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx deleted file mode 100644 index f924e7f5e..000000000 --- a/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import { connect } from 'react-redux'; -import { makeGetAccount } from '../../../selectors'; -import Header from '../components/header'; -import { - followAccount, - unfollowAccount, - blockAccount, - unblockAccount, - muteAccount, - unmuteAccount -} from '../../../actions/accounts'; -import { mentionCompose } from '../../../actions/compose'; -import { initReport } from '../../../actions/reports'; -import { openModal } from '../../../actions/modal'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -const messages = defineMessages({ - blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, - muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' } -}); - -const makeMapStateToProps = () => { - const getAccount = makeGetAccount(); - - const mapStateToProps = (state, { accountId }) => ({ - account: getAccount(state, Number(accountId)), - me: state.getIn(['meta', 'me']) - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = (dispatch, { intl }) => ({ - onFollow (account) { - if (account.getIn(['relationship', 'following'])) { - dispatch(unfollowAccount(account.get('id'))); - } else { - dispatch(followAccount(account.get('id'))); - } - }, - - onBlock (account) { - if (account.getIn(['relationship', 'blocking'])) { - dispatch(unblockAccount(account.get('id'))); - } else { - dispatch(openModal('CONFIRM', { - message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - confirm: intl.formatMessage(messages.blockConfirm), - onConfirm: () => dispatch(blockAccount(account.get('id'))) - })); - } - }, - - onMention (account, router) { - dispatch(mentionCompose(account, router)); - }, - - onReport (account) { - dispatch(initReport(account)); - }, - - onMute (account) { - if (account.getIn(['relationship', 'muting'])) { - dispatch(unmuteAccount(account.get('id'))); - } else { - dispatch(openModal('CONFIRM', { - message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - confirm: intl.formatMessage(messages.muteConfirm), - onConfirm: () => dispatch(muteAccount(account.get('id'))) - })); - } - } -}); - -export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header)); diff --git a/app/assets/javascripts/components/features/account_timeline/index.jsx b/app/assets/javascripts/components/features/account_timeline/index.jsx deleted file mode 100644 index a06de3d21..000000000 --- a/app/assets/javascripts/components/features/account_timeline/index.jsx +++ /dev/null @@ -1,87 +0,0 @@ -import { connect } from 'react-redux'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import { - fetchAccount, - fetchAccountTimeline, - expandAccountTimeline -} from '../../actions/accounts'; -import StatusList from '../../components/status_list'; -import LoadingIndicator from '../../components/loading_indicator'; -import Column from '../ui/components/column'; -import HeaderContainer from './containers/header_container'; -import ColumnBackButton from '../../components/column_back_button'; -import Immutable from 'immutable'; - -const mapStateToProps = (state, props) => ({ - statusIds: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'items'], Immutable.List()), - isLoading: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'isLoading']), - hasMore: !!state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'next']), - me: state.getIn(['meta', 'me']) -}); - -class AccountTimeline extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleScrollToBottom = this.handleScrollToBottom.bind(this); - } - - componentWillMount () { - this.props.dispatch(fetchAccount(Number(this.props.params.accountId))); - this.props.dispatch(fetchAccountTimeline(Number(this.props.params.accountId))); - } - - componentWillReceiveProps(nextProps) { - if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(Number(nextProps.params.accountId))); - this.props.dispatch(fetchAccountTimeline(Number(nextProps.params.accountId))); - } - } - - handleScrollToBottom () { - if (!this.props.isLoading && this.props.hasMore) { - this.props.dispatch(expandAccountTimeline(Number(this.props.params.accountId))); - } - } - - render () { - const { statusIds, isLoading, hasMore, me } = this.props; - - if (!statusIds && isLoading) { - return ( - <Column> - <LoadingIndicator /> - </Column> - ); - } - - return ( - <Column> - <ColumnBackButton /> - - <StatusList - prepend={<HeaderContainer accountId={this.props.params.accountId} />} - scrollKey='account_timeline' - statusIds={statusIds} - isLoading={isLoading} - hasMore={hasMore} - me={me} - onScrollToBottom={this.handleScrollToBottom} - /> - </Column> - ); - } - -} - -AccountTimeline.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list, - isLoading: PropTypes.bool, - hasMore: PropTypes.bool, - me: PropTypes.number.isRequired -}; - -export default connect(mapStateToProps)(AccountTimeline); diff --git a/app/assets/javascripts/components/features/blocks/index.jsx b/app/assets/javascripts/components/features/blocks/index.jsx deleted file mode 100644 index 8b973ebb1..000000000 --- a/app/assets/javascripts/components/features/blocks/index.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import { connect } from 'react-redux'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import LoadingIndicator from '../../components/loading_indicator'; -import { ScrollContainer } from 'react-router-scroll'; -import Column from '../ui/components/column'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; -import AccountContainer from '../../containers/account_container'; -import { fetchBlocks, expandBlocks } from '../../actions/blocks'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - heading: { id: 'column.blocks', defaultMessage: 'Blocked users' } -}); - -const mapStateToProps = state => ({ - accountIds: state.getIn(['user_lists', 'blocks', 'items']) -}); - -class Blocks extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleScroll = this.handleScroll.bind(this); - } - - componentWillMount () { - this.props.dispatch(fetchBlocks()); - } - - handleScroll (e) { - const { scrollTop, scrollHeight, clientHeight } = e.target; - - if (scrollTop === scrollHeight - clientHeight) { - this.props.dispatch(expandBlocks()); - } - } - - render () { - const { intl, accountIds } = this.props; - - if (!accountIds) { - return ( - <Column> - <LoadingIndicator /> - </Column> - ); - } - - return ( - <Column icon='ban' heading={intl.formatMessage(messages.heading)}> - <ColumnBackButtonSlim /> - <ScrollContainer scrollKey='blocks'> - <div className='scrollable' onScroll={this.handleScroll}> - {accountIds.map(id => - <AccountContainer key={id} id={id} /> - )} - </div> - </ScrollContainer> - </Column> - ); - } -} - -Blocks.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list, - intl: PropTypes.object.isRequired -}; - -export default connect(mapStateToProps)(injectIntl(Blocks)); diff --git a/app/assets/javascripts/components/features/community_timeline/index.jsx b/app/assets/javascripts/components/features/community_timeline/index.jsx deleted file mode 100644 index 3877888ba..000000000 --- a/app/assets/javascripts/components/features/community_timeline/index.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import StatusListContainer from '../ui/containers/status_list_container'; -import Column from '../ui/components/column'; -import { - refreshTimeline, - updateTimeline, - deleteFromTimelines, - connectTimeline, - disconnectTimeline -} from '../../actions/timelines'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; -import createStream from '../../stream'; - -const messages = defineMessages({ - title: { id: 'column.community', defaultMessage: 'Local timeline' } -}); - -const mapStateToProps = state => ({ - hasUnread: state.getIn(['timelines', 'community', 'unread']) > 0, - streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']), - accessToken: state.getIn(['meta', 'access_token']) -}); - -let subscription; - -class CommunityTimeline extends React.PureComponent { - - componentDidMount () { - const { dispatch, streamingAPIBaseURL, accessToken } = this.props; - - dispatch(refreshTimeline('community')); - - if (typeof subscription !== 'undefined') { - return; - } - - subscription = createStream(streamingAPIBaseURL, accessToken, 'public:local', { - - connected () { - dispatch(connectTimeline('community')); - }, - - reconnected () { - dispatch(connectTimeline('community')); - }, - - disconnected () { - dispatch(disconnectTimeline('community')); - }, - - received (data) { - switch(data.event) { - case 'update': - dispatch(updateTimeline('community', JSON.parse(data.payload))); - break; - case 'delete': - dispatch(deleteFromTimelines(data.payload)); - break; - } - } - - }); - } - - componentWillUnmount () { - // if (typeof subscription !== 'undefined') { - // subscription.close(); - // subscription = null; - // } - } - - render () { - const { intl, hasUnread } = this.props; - - return ( - <Column icon='users' active={hasUnread} heading={intl.formatMessage(messages.title)}> - <ColumnBackButtonSlim /> - <StatusListContainer {...this.props} scrollKey='community_timeline' type='community' emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} /> - </Column> - ); - } - -} - -CommunityTimeline.propTypes = { - dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - streamingAPIBaseURL: PropTypes.string.isRequired, - accessToken: PropTypes.string.isRequired, - hasUnread: PropTypes.bool -}; - -export default connect(mapStateToProps)(injectIntl(CommunityTimeline)); diff --git a/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx b/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx deleted file mode 100644 index bf6a15e5d..000000000 --- a/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import Avatar from '../../../components/avatar'; -import DisplayName from '../../../components/display_name'; -import ImmutablePropTypes from 'react-immutable-proptypes'; - -const AutosuggestAccount = ({ account }) => ( - <div className='autosuggest-account'> - <div className='autosuggest-account-icon'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={18} /></div> - <DisplayName account={account} /> - </div> -); - -AutosuggestAccount.propTypes = { - account: ImmutablePropTypes.map.isRequired -}; - -export default AutosuggestAccount; diff --git a/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx b/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx deleted file mode 100644 index 275b3d5a6..000000000 --- a/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import { FormattedMessage } from 'react-intl'; -import DisplayName from '../../../components/display_name'; -import ImmutablePropTypes from 'react-immutable-proptypes'; - -const AutosuggestStatus = ({ status }) => ( - <div className='autosuggest-status'> - <FormattedMessage id='search.status_by' defaultMessage='Status by {name}' values={{ name: <strong>@{status.getIn(['account', 'acct'])}</strong> }} /> - </div> -); - -AutosuggestStatus.propTypes = { - status: ImmutablePropTypes.map.isRequired -}; - -export default AutosuggestStatus; diff --git a/app/assets/javascripts/components/features/compose/components/character_counter.jsx b/app/assets/javascripts/components/features/compose/components/character_counter.jsx deleted file mode 100644 index 08d2ac4d1..000000000 --- a/app/assets/javascripts/components/features/compose/components/character_counter.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import PropTypes from 'prop-types'; -import { length } from 'stringz'; - -class CharacterCounter extends React.PureComponent { - - checkRemainingText (diff) { - if (diff < 0) { - return <span className='character-counter character-counter--over'>{diff}</span>; - } - return <span className='character-counter'>{diff}</span>; - } - - render () { - const diff = this.props.max - length(this.props.text); - - return this.checkRemainingText(diff); - } - -} - -CharacterCounter.propTypes = { - text: PropTypes.string.isRequired, - max: PropTypes.number.isRequired -} - -export default CharacterCounter; diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx deleted file mode 100644 index 6bc811160..000000000 --- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx +++ /dev/null @@ -1,209 +0,0 @@ -import CharacterCounter from './character_counter'; -import Button from '../../../components/button'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import ReplyIndicatorContainer from '../containers/reply_indicator_container'; -import AutosuggestTextarea from '../../../components/autosuggest_textarea'; -import { debounce } from 'react-decoration'; -import UploadButtonContainer from '../containers/upload_button_container'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Toggle from 'react-toggle'; -import Collapsable from '../../../components/collapsable'; -import SpoilerButtonContainer from '../containers/spoiler_button_container'; -import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; -import SensitiveButtonContainer from '../containers/sensitive_button_container'; -import EmojiPickerDropdown from './emoji_picker_dropdown'; -import UploadFormContainer from '../containers/upload_form_container'; -import TextIconButton from './text_icon_button'; -import WarningContainer from '../containers/warning_container'; - -const messages = defineMessages({ - placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, - spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Content warning' }, - publish: { id: 'compose_form.publish', defaultMessage: 'Toot' } -}); - -class ComposeForm extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleChange = this.handleChange.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this); - this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this); - this.onSuggestionSelected = this.onSuggestionSelected.bind(this); - this.handleChangeSpoilerText = this.handleChangeSpoilerText.bind(this); - this.setAutosuggestTextarea = this.setAutosuggestTextarea.bind(this); - this.handleEmojiPick = this.handleEmojiPick.bind(this); - } - - handleChange (e) { - this.props.onChange(e.target.value); - } - - handleKeyDown (e) { - if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { - this.handleSubmit(); - } - } - - handleSubmit () { - this.autosuggestTextarea.reset(); - this.props.onSubmit(); - } - - onSuggestionsClearRequested () { - this.props.onClearSuggestions(); - } - - @debounce(500) - onSuggestionsFetchRequested (token) { - this.props.onFetchSuggestions(token); - } - - onSuggestionSelected (tokenStart, token, value) { - this._restoreCaret = null; - this.props.onSuggestionSelected(tokenStart, token, value); - } - - handleChangeSpoilerText (e) { - this.props.onChangeSpoilerText(e.target.value); - } - - componentWillReceiveProps (nextProps) { - // If this is the update where we've finished uploading, - // save the last caret position so we can restore it below! - if (!nextProps.is_uploading && this.props.is_uploading) { - this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart; - } - } - - componentDidUpdate (prevProps) { - // This statement does several things: - // - If we're beginning a reply, and, - // - Replying to zero or one users, places the cursor at the end of the textbox. - // - Replying to more than one user, selects any usernames past the first; - // this provides a convenient shortcut to drop everyone else from the conversation. - // - If we've just finished uploading an image, and have a saved caret position, - // restores the cursor to that position after the text changes! - if (this.props.focusDate !== prevProps.focusDate || (prevProps.is_uploading && !this.props.is_uploading && typeof this._restoreCaret === 'number')) { - let selectionEnd, selectionStart; - - if (this.props.preselectDate !== prevProps.preselectDate) { - selectionEnd = this.props.text.length; - selectionStart = this.props.text.search(/\s/) + 1; - } else if (typeof this._restoreCaret === 'number') { - selectionStart = this._restoreCaret; - selectionEnd = this._restoreCaret; - } else { - selectionEnd = this.props.text.length; - selectionStart = selectionEnd; - } - - this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd); - this.autosuggestTextarea.textarea.focus(); - } - } - - setAutosuggestTextarea (c) { - this.autosuggestTextarea = c; - } - - handleEmojiPick (data) { - const position = this.autosuggestTextarea.textarea.selectionStart; - this._restoreCaret = position + data.shortname.length + 1; - this.props.onPickEmoji(position, data); - } - - render () { - const { intl, onPaste } = this.props; - const disabled = this.props.is_submitting; - const text = [this.props.spoiler_text, this.props.text].join(''); - - let publishText = ''; - let reply_to_other = false; - - if (this.props.privacy === 'private' || this.props.privacy === 'direct') { - publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>; - } else { - publishText = intl.formatMessage(messages.publish) + (this.props.privacy !== 'unlisted' ? '!' : ''); - } - - return ( - <div className='compose-form'> - <Collapsable isVisible={this.props.spoiler} fullHeight={50}> - <div className="spoiler-input"> - <input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type="text" className="spoiler-input__input" id='cw-spoiler-input'/> - </div> - </Collapsable> - - <WarningContainer /> - - <ReplyIndicatorContainer /> - - <div className='compose-form__autosuggest-wrapper'> - <AutosuggestTextarea - ref={this.setAutosuggestTextarea} - placeholder={intl.formatMessage(messages.placeholder)} - disabled={disabled} - value={this.props.text} - onChange={this.handleChange} - suggestions={this.props.suggestions} - onKeyDown={this.handleKeyDown} - onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} - onSuggestionsClearRequested={this.onSuggestionsClearRequested} - onSuggestionSelected={this.onSuggestionSelected} - onPaste={onPaste} - /> - - <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} /> - </div> - - <div className='compose-form__modifiers'> - <UploadFormContainer /> - </div> - - <div className='compose-form__buttons-wrapper'> - <div className='compose-form__buttons'> - <UploadButtonContainer /> - <PrivacyDropdownContainer /> - <SensitiveButtonContainer /> - <SpoilerButtonContainer /> - </div> - - <div className='compose-form__publish'> - <div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div> - <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length > 500 || (text.length !==0 && text.trim().length === 0)} block /></div> - </div> - </div> - </div> - ); - } - -} - -ComposeForm.propTypes = { - intl: PropTypes.object.isRequired, - text: PropTypes.string.isRequired, - suggestion_token: PropTypes.string, - suggestions: ImmutablePropTypes.list, - spoiler: PropTypes.bool, - privacy: PropTypes.string, - spoiler_text: PropTypes.string, - focusDate: PropTypes.instanceOf(Date), - preselectDate: PropTypes.instanceOf(Date), - is_submitting: PropTypes.bool, - is_uploading: PropTypes.bool, - me: PropTypes.number, - onChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - onClearSuggestions: PropTypes.func.isRequired, - onFetchSuggestions: PropTypes.func.isRequired, - onSuggestionSelected: PropTypes.func.isRequired, - onChangeSpoilerText: PropTypes.func.isRequired, - onPaste: PropTypes.func.isRequired, - onPickEmoji: PropTypes.func.isRequired -}; - -export default injectIntl(ComposeForm); diff --git a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx deleted file mode 100644 index bc22b074d..000000000 --- a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx +++ /dev/null @@ -1,114 +0,0 @@ -import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown'; -import EmojiPicker from 'emojione-picker'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, - emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' }, - people: { id: 'emoji_button.people', defaultMessage: 'People' }, - nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' }, - food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' }, - activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' }, - travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' }, - objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' }, - symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' }, - flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' } -}); - -const settings = { - imageType: 'png', - sprites: false, - imagePathPNG: '/emoji/' -}; - -const dropdownStyle = { - position: 'absolute', - right: '5px', - top: '5px' -}; - -const dropdownTriggerStyle = { - display: 'block', - fontSize: '24px', - lineHeight: '24px', - marginLeft: '2px', - width: '24px' -} - -class EmojiPickerDropdown extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.setRef = this.setRef.bind(this); - this.handleChange = this.handleChange.bind(this); - } - - setRef (c) { - this.dropdown = c; - } - - handleChange (data) { - this.dropdown.hide(); - this.props.onPickEmoji(data); - } - - render () { - const { intl } = this.props; - - const categories = { - people: { - title: intl.formatMessage(messages.people), - emoji: 'smile', - }, - nature: { - title: intl.formatMessage(messages.nature), - emoji: 'hamster', - }, - food: { - title: intl.formatMessage(messages.food), - emoji: 'pizza', - }, - activity: { - title: intl.formatMessage(messages.activity), - emoji: 'soccer', - }, - travel: { - title: intl.formatMessage(messages.travel), - emoji: 'earth_americas', - }, - objects: { - title: intl.formatMessage(messages.objects), - emoji: 'bulb', - }, - symbols: { - title: intl.formatMessage(messages.symbols), - emoji: 'clock9', - }, - flags: { - title: intl.formatMessage(messages.flags), - emoji: 'flag_gb', - } - } - - return ( - <Dropdown ref={this.setRef} style={dropdownStyle}> - <DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)} style={dropdownTriggerStyle}> - <img draggable="false" className="emojione" alt="🙂" src="/emoji/1f602.svg" /> - </DropdownTrigger> - - <DropdownContent className='dropdown__left'> - <EmojiPicker emojione={settings} onChange={this.handleChange} searchPlaceholder={intl.formatMessage(messages.emoji_search)} categories={categories} search={true} /> - </DropdownContent> - </Dropdown> - ); - } - -} - -EmojiPickerDropdown.propTypes = { - intl: PropTypes.object.isRequired, - onPickEmoji: PropTypes.func.isRequired -}; - -export default injectIntl(EmojiPickerDropdown); diff --git a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx b/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx deleted file mode 100644 index aae0592c6..000000000 --- a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Avatar from '../../../components/avatar'; -import IconButton from '../../../components/icon_button'; -import DisplayName from '../../../components/display_name'; -import Permalink from '../../../components/permalink'; -import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router'; - -class NavigationBar extends React.PureComponent { - - render () { - return ( - <div className='navigation-bar'> - <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}><Avatar src={this.props.account.get('avatar')} animate size={40} /></Permalink> - - <div className='navigation-bar__profile'> - <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}> - <strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong> - </Permalink> - <a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a> - </div> - </div> - ); - } - -} - -NavigationBar.propTypes = { - account: ImmutablePropTypes.map.isRequired -}; - -export default NavigationBar; diff --git a/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx deleted file mode 100644 index 82b3454c6..000000000 --- a/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import PropTypes from 'prop-types'; -import { injectIntl, defineMessages } from 'react-intl'; -import IconButton from '../../../components/icon_button'; - -const messages = defineMessages({ - public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, - public_long: { id: 'privacy.public.long', defaultMessage: 'Post to public timelines' }, - unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, - unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' }, - private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, - private_long: { id: 'privacy.private.long', defaultMessage: 'Post to followers only' }, - direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, - direct_long: { id: 'privacy.direct.long', defaultMessage: 'Post to mentioned users only' }, - change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' } -}); - -const iconStyle = { - height: null, - lineHeight: '27px' -} - -class PrivacyDropdown extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - open: false - }; - this.handleToggle = this.handleToggle.bind(this); - this.handleClick = this.handleClick.bind(this); - this.onGlobalClick = this.onGlobalClick.bind(this); - this.setRef = this.setRef.bind(this); - } - - handleToggle () { - this.setState({ open: !this.state.open }); - } - - handleClick (value, e) { - e.preventDefault(); - this.setState({ open: false }); - this.props.onChange(value); - } - - onGlobalClick (e) { - if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) { - this.setState({ open: false }); - } - } - - componentDidMount () { - window.addEventListener('click', this.onGlobalClick); - window.addEventListener('touchstart', this.onGlobalClick); - } - - componentWillUnmount () { - window.removeEventListener('click', this.onGlobalClick); - window.removeEventListener('touchstart', this.onGlobalClick); - } - - setRef (c) { - this.node = c; - } - - render () { - const { value, onChange, intl } = this.props; - const { open } = this.state; - - const options = [ - { icon: 'globe', value: 'public', shortText: intl.formatMessage(messages.public_short), longText: intl.formatMessage(messages.public_long) }, - { icon: 'unlock-alt', value: 'unlisted', shortText: intl.formatMessage(messages.unlisted_short), longText: intl.formatMessage(messages.unlisted_long) }, - { icon: 'lock', value: 'private', shortText: intl.formatMessage(messages.private_short), longText: intl.formatMessage(messages.private_long) }, - { icon: 'envelope', value: 'direct', shortText: intl.formatMessage(messages.direct_short), longText: intl.formatMessage(messages.direct_long) } - ]; - - const valueOption = options.find(item => item.value === value); - - return ( - <div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}> - <div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} active={open} inverted onClick={this.handleToggle} style={iconStyle}/></div> - <div className='privacy-dropdown__dropdown'> - {options.map(item => - <div role='button' tabIndex='0' key={item.value} onClick={this.handleClick.bind(this, item.value)} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}> - <div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div> - <div className='privacy-dropdown__option__content'> - <strong>{item.shortText}</strong> - {item.longText} - </div> - </div> - )} - </div> - </div> - ); - } - -} - -PrivacyDropdown.propTypes = { - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(PrivacyDropdown); diff --git a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx b/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx deleted file mode 100644 index 442ed5a35..000000000 --- a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import Avatar from '../../../components/avatar'; -import IconButton from '../../../components/icon_button'; -import DisplayName from '../../../components/display_name'; -import emojify from '../../../emoji'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' } -}); - -class ReplyIndicator extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - this.handleAccountClick = this.handleAccountClick.bind(this); - } - - handleClick () { - this.props.onCancel(); - } - - handleAccountClick (e) { - if (e.button === 0) { - e.preventDefault(); - this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); - } - } - - render () { - const { status, intl } = this.props; - - if (!status) { - return null; - } - - const content = { __html: emojify(status.get('content')) }; - - return ( - <div className='reply-indicator'> - <div className='reply-indicator__header'> - <div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div> - - <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name'> - <div className='reply-indicator__display-avatar'><Avatar size={24} src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} /></div> - <DisplayName account={status.get('account')} /> - </a> - </div> - - <div className='reply-indicator__content' dangerouslySetInnerHTML={content} /> - </div> - ); - } - -} - -ReplyIndicator.contextTypes = { - router: PropTypes.object -}; - -ReplyIndicator.propTypes = { - status: ImmutablePropTypes.map, - onCancel: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(ReplyIndicator); diff --git a/app/assets/javascripts/components/features/compose/components/search.jsx b/app/assets/javascripts/components/features/compose/components/search.jsx deleted file mode 100644 index f62248a33..000000000 --- a/app/assets/javascripts/components/features/compose/components/search.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -const messages = defineMessages({ - placeholder: { id: 'search.placeholder', defaultMessage: 'Search' } -}); - -class Search extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleChange = this.handleChange.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleFocus = this.handleFocus.bind(this); - this.handleClear = this.handleClear.bind(this); - } - - handleChange (e) { - this.props.onChange(e.target.value); - } - - handleClear (e) { - e.preventDefault(); - - if (this.props.value.length > 0 || this.props.submitted) { - this.props.onClear(); - } - } - - handleKeyDown (e) { - if (e.key === 'Enter') { - e.preventDefault(); - this.props.onSubmit(); - } - } - - noop () { - - } - - handleFocus () { - this.props.onShow(); - } - - render () { - const { intl, value, submitted } = this.props; - const hasValue = value.length > 0 || submitted; - - return ( - <div className='search'> - <input - className='search__input' - type='text' - placeholder={intl.formatMessage(messages.placeholder)} - value={value} - onChange={this.handleChange} - onKeyUp={this.handleKeyDown} - onFocus={this.handleFocus} - /> - - <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}> - <i className={`fa fa-search ${hasValue ? '' : 'active'}`} /> - <i aria-label={intl.formatMessage(messages.placeholder)} className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} /> - </div> - </div> - ); - } - -} - -Search.propTypes = { - value: PropTypes.string.isRequired, - submitted: PropTypes.bool, - onChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - onClear: PropTypes.func.isRequired, - onShow: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(Search); diff --git a/app/assets/javascripts/components/features/compose/components/search_results.jsx b/app/assets/javascripts/components/features/compose/components/search_results.jsx deleted file mode 100644 index 00bfd1786..000000000 --- a/app/assets/javascripts/components/features/compose/components/search_results.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import AccountContainer from '../../../containers/account_container'; -import StatusContainer from '../../../containers/status_container'; -import { Link } from 'react-router'; - -class SearchResults extends React.PureComponent { - - render () { - const { results } = this.props; - - let accounts, statuses, hashtags; - let count = 0; - - if (results.get('accounts') && results.get('accounts').size > 0) { - count += results.get('accounts').size; - accounts = ( - <div className='search-results__section'> - {results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)} - </div> - ); - } - - if (results.get('statuses') && results.get('statuses').size > 0) { - count += results.get('statuses').size; - statuses = ( - <div className='search-results__section'> - {results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)} - </div> - ); - } - - if (results.get('hashtags') && results.get('hashtags').size > 0) { - count += results.get('hashtags').size; - hashtags = ( - <div className='search-results__section'> - {results.get('hashtags').map(hashtag => - <Link className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}> - #{hashtag} - </Link> - )} - </div> - ); - } - - return ( - <div className='search-results'> - <div className='search-results__header'> - <FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} /> - </div> - - {accounts} - {statuses} - {hashtags} - </div> - ); - } - -} - -SearchResults.propTypes = { - results: ImmutablePropTypes.map.isRequired -}; - -export default SearchResults; diff --git a/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx b/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx deleted file mode 100644 index 4252596c2..000000000 --- a/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from 'prop-types'; - -class TextIconButton extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - } - - handleClick (e) { - e.preventDefault(); - this.props.onClick(); - } - - render () { - const { label, title, active, ariaControls } = this.props; - - return ( - <button title={title} aria-label={title} className={`text-icon-button ${active ? 'active' : ''}`} aria-expanded={active} onClick={this.handleClick} aria-controls={ariaControls}> - {label} - </button> - ); - } - -} - -TextIconButton.propTypes = { - label: PropTypes.string.isRequired, - title: PropTypes.string, - active: PropTypes.bool, - onClick: PropTypes.func.isRequired, - ariaControls: PropTypes.string -}; - -export default TextIconButton; diff --git a/app/assets/javascripts/components/features/compose/components/upload_button.jsx b/app/assets/javascripts/components/features/compose/components/upload_button.jsx deleted file mode 100644 index 9b2de0332..000000000 --- a/app/assets/javascripts/components/features/compose/components/upload_button.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import IconButton from '../../../components/icon_button'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - upload: { id: 'upload_button.label', defaultMessage: 'Add media' } -}); - - -const iconStyle = { - height: null, - lineHeight: '27px' -} - -class UploadButton extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleChange = this.handleChange.bind(this); - this.handleClick = this.handleClick.bind(this); - this.setRef = this.setRef.bind(this); - } - - handleChange (e) { - if (e.target.files.length > 0) { - this.props.onSelectFile(e.target.files); - } - } - - handleClick () { - this.fileElement.click(); - } - - setRef (c) { - this.fileElement = c; - } - - render () { - - const { intl, resetFileKey, disabled } = this.props; - - return ( - <div className='compose-form__upload-button'> - <IconButton icon='camera' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle}/> - <input key={resetFileKey} ref={this.setRef} type='file' multiple={false} onChange={this.handleChange} disabled={disabled} style={{ display: 'none' }} /> - </div> - ); - } - -} - -UploadButton.propTypes = { - disabled: PropTypes.bool, - onSelectFile: PropTypes.func.isRequired, - style: PropTypes.object, - resetFileKey: PropTypes.number, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(UploadButton); diff --git a/app/assets/javascripts/components/features/compose/components/upload_form.jsx b/app/assets/javascripts/components/features/compose/components/upload_form.jsx deleted file mode 100644 index a2fb7cfe0..000000000 --- a/app/assets/javascripts/components/features/compose/components/upload_form.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import IconButton from '../../../components/icon_button'; -import { defineMessages, injectIntl } from 'react-intl'; -import UploadProgressContainer from '../containers/upload_progress_container'; -import { Motion, spring } from 'react-motion'; - -const messages = defineMessages({ - undo: { id: 'upload_form.undo', defaultMessage: 'Undo' } -}); - -class UploadForm extends React.PureComponent { - - render () { - const { intl, media } = this.props; - - const uploads = media.map(attachment => - <div className='compose-form__upload' key={attachment.get('id')}> - <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}> - {({ scale }) => - <div className='compose-form__upload-thumbnail' style={{ transform: `translateZ(0) scale(${scale})`, backgroundImage: `url(${attachment.get('preview_url')})` }}> - <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} /> - </div> - } - </Motion> - </div> - ); - - return ( - <div className='compose-form__upload-wrapper'> - <UploadProgressContainer /> - <div className='compose-form__uploads-wrapper'>{uploads}</div> - </div> - ); - } - -} - -UploadForm.propTypes = { - media: ImmutablePropTypes.list.isRequired, - onRemoveFile: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(UploadForm); diff --git a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx b/app/assets/javascripts/components/features/compose/components/upload_progress.jsx deleted file mode 100644 index 8f03bb76a..000000000 --- a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import PropTypes from 'prop-types'; -import { Motion, spring } from 'react-motion'; -import { FormattedMessage } from 'react-intl'; - -class UploadProgress extends React.PureComponent { - - render () { - const { active, progress } = this.props; - - if (!active) { - return null; - } - - return ( - <div className='upload-progress'> - <div className='upload-progress__icon'> - <i className='fa fa-upload' /> - </div> - - <div className='upload-progress__message'> - <FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' /> - - <div className='upload-progress__backdrop'> - <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}> - {({ width }) => - <div className='upload-progress__tracker' style={{ width: `${width}%` }} /> - } - </Motion> - </div> - </div> - </div> - ); - } - -} - -UploadProgress.propTypes = { - active: PropTypes.bool, - progress: PropTypes.number -}; - -export default UploadProgress; diff --git a/app/assets/javascripts/components/features/compose/components/warning.jsx b/app/assets/javascripts/components/features/compose/components/warning.jsx deleted file mode 100644 index ff1989755..000000000 --- a/app/assets/javascripts/components/features/compose/components/warning.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types'; - -class Warning extends React.PureComponent { - - constructor (props) { - super(props); - } - - render () { - const { message } = this.props; - - return ( - <div className='compose-form__warning'> - {message} - </div> - ); - } - -} - -Warning.propTypes = { - message: PropTypes.node.isRequired -}; - -export default Warning; diff --git a/app/assets/javascripts/components/features/compose/containers/autosuggest_account_container.jsx b/app/assets/javascripts/components/features/compose/containers/autosuggest_account_container.jsx deleted file mode 100644 index de76a364d..000000000 --- a/app/assets/javascripts/components/features/compose/containers/autosuggest_account_container.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux'; -import AutosuggestAccount from '../components/autosuggest_account'; -import { makeGetAccount } from '../../../selectors'; - -const makeMapStateToProps = () => { - const getAccount = makeGetAccount(); - - const mapStateToProps = (state, { id }) => ({ - account: getAccount(state, id) - }); - - return mapStateToProps; -}; - -export default connect(makeMapStateToProps)(AutosuggestAccount); diff --git a/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx b/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx deleted file mode 100644 index ef46eb09c..000000000 --- a/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux'; -import AutosuggestStatus from '../components/autosuggest_status'; -import { makeGetStatus } from '../../../selectors'; - -const makeMapStateToProps = () => { - const getStatus = makeGetStatus(); - - const mapStateToProps = (state, { id }) => ({ - status: getStatus(state, id) - }); - - return mapStateToProps; -}; - -export default connect(makeMapStateToProps)(AutosuggestStatus); diff --git a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx deleted file mode 100644 index 892183b83..000000000 --- a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { connect } from 'react-redux'; -import ComposeForm from '../components/compose_form'; -import { uploadCompose } from '../../../actions/compose'; -import { - changeCompose, - submitCompose, - clearComposeSuggestions, - fetchComposeSuggestions, - selectComposeSuggestion, - changeComposeSpoilerText, - insertEmojiCompose -} from '../../../actions/compose'; - -const mapStateToProps = state => ({ - text: state.getIn(['compose', 'text']), - suggestion_token: state.getIn(['compose', 'suggestion_token']), - suggestions: state.getIn(['compose', 'suggestions']), - spoiler: state.getIn(['compose', 'spoiler']), - spoiler_text: state.getIn(['compose', 'spoiler_text']), - privacy: state.getIn(['compose', 'privacy']), - focusDate: state.getIn(['compose', 'focusDate']), - preselectDate: state.getIn(['compose', 'preselectDate']), - is_submitting: state.getIn(['compose', 'is_submitting']), - is_uploading: state.getIn(['compose', 'is_uploading']), - me: state.getIn(['compose', 'me']) -}); - -const mapDispatchToProps = (dispatch) => ({ - - onChange (text) { - dispatch(changeCompose(text)); - }, - - onSubmit () { - dispatch(submitCompose()); - }, - - onClearSuggestions () { - dispatch(clearComposeSuggestions()); - }, - - onFetchSuggestions (token) { - dispatch(fetchComposeSuggestions(token)); - }, - - onSuggestionSelected (position, token, accountId) { - dispatch(selectComposeSuggestion(position, token, accountId)); - }, - - onChangeSpoilerText (checked) { - dispatch(changeComposeSpoilerText(checked)); - }, - - onPaste (files) { - dispatch(uploadCompose(files)); - }, - - onPickEmoji (position, data) { - dispatch(insertEmojiCompose(position, data)); - }, - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm); diff --git a/app/assets/javascripts/components/features/compose/containers/navigation_container.jsx b/app/assets/javascripts/components/features/compose/containers/navigation_container.jsx deleted file mode 100644 index 0006608da..000000000 --- a/app/assets/javascripts/components/features/compose/containers/navigation_container.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import { connect } from 'react-redux'; -import NavigationBar from '../components/navigation_bar'; - -const mapStateToProps = (state, props) => { - return { - account: state.getIn(['accounts', state.getIn(['meta', 'me'])]) - }; -}; - -export default connect(mapStateToProps)(NavigationBar); diff --git a/app/assets/javascripts/components/features/compose/containers/privacy_dropdown_container.jsx b/app/assets/javascripts/components/features/compose/containers/privacy_dropdown_container.jsx deleted file mode 100644 index 1eee8f84c..000000000 --- a/app/assets/javascripts/components/features/compose/containers/privacy_dropdown_container.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { connect } from 'react-redux'; -import PrivacyDropdown from '../components/privacy_dropdown'; -import { changeComposeVisibility } from '../../../actions/compose'; - -const mapStateToProps = state => ({ - value: state.getIn(['compose', 'privacy']) -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (value) { - dispatch(changeComposeVisibility(value)); - } - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown); diff --git a/app/assets/javascripts/components/features/compose/containers/reply_indicator_container.jsx b/app/assets/javascripts/components/features/compose/containers/reply_indicator_container.jsx deleted file mode 100644 index 39b48f3b6..000000000 --- a/app/assets/javascripts/components/features/compose/containers/reply_indicator_container.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { connect } from 'react-redux'; -import { cancelReplyCompose } from '../../../actions/compose'; -import { makeGetStatus } from '../../../selectors'; -import ReplyIndicator from '../components/reply_indicator'; - -const makeMapStateToProps = () => { - const getStatus = makeGetStatus(); - - const mapStateToProps = (state, props) => ({ - status: getStatus(state, state.getIn(['compose', 'in_reply_to'])), - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = dispatch => ({ - - onCancel () { - dispatch(cancelReplyCompose()); - } - -}); - -export default connect(makeMapStateToProps, mapDispatchToProps)(ReplyIndicator); diff --git a/app/assets/javascripts/components/features/compose/containers/search_container.jsx b/app/assets/javascripts/components/features/compose/containers/search_container.jsx deleted file mode 100644 index 906c0c28c..000000000 --- a/app/assets/javascripts/components/features/compose/containers/search_container.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import { connect } from 'react-redux'; -import { - changeSearch, - clearSearch, - submitSearch, - showSearch -} from '../../../actions/search'; -import Search from '../components/search'; - -const mapStateToProps = state => ({ - value: state.getIn(['search', 'value']), - submitted: state.getIn(['search', 'submitted']) -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (value) { - dispatch(changeSearch(value)); - }, - - onClear () { - dispatch(clearSearch()); - }, - - onSubmit () { - dispatch(submitSearch()); - }, - - onShow () { - dispatch(showSearch()); - } - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Search); diff --git a/app/assets/javascripts/components/features/compose/containers/search_results_container.jsx b/app/assets/javascripts/components/features/compose/containers/search_results_container.jsx deleted file mode 100644 index e5911fd38..000000000 --- a/app/assets/javascripts/components/features/compose/containers/search_results_container.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import { connect } from 'react-redux'; -import SearchResults from '../components/search_results'; - -const mapStateToProps = state => ({ - results: state.getIn(['search', 'results']) -}); - -export default connect(mapStateToProps)(SearchResults); diff --git a/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx deleted file mode 100644 index c83598a7d..000000000 --- a/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import TextIconButton from '../components/text_icon_button'; -import { changeComposeSensitivity } from '../../../actions/compose'; -import { Motion, spring } from 'react-motion'; -import { injectIntl, defineMessages } from 'react-intl'; - -const messages = defineMessages({ - title: { id: 'compose_form.sensitive', defaultMessage: 'Mark media as sensitive' } -}); - -const mapStateToProps = state => ({ - visible: state.getIn(['compose', 'media_attachments']).size > 0, - active: state.getIn(['compose', 'sensitive']) -}); - -const mapDispatchToProps = dispatch => ({ - - onClick () { - dispatch(changeComposeSensitivity()); - } - -}); - -class SensitiveButton extends React.PureComponent { - - render () { - const { visible, active, onClick, intl } = this.props; - - return ( - <Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(visible ? 1 : 0.87, { stiffness: 200, damping: 3 }) }}> - {({ scale }) => - <div style={{ display: visible ? 'block' : 'none', transform: `translateZ(0) scale(${scale})` }}> - <TextIconButton onClick={onClick} label='NSFW' title={intl.formatMessage(messages.title)} active={active} /> - </div> - } - </Motion> - ); - } - -} - -SensitiveButton.propTypes = { - visible: PropTypes.bool, - active: PropTypes.bool, - onClick: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton)); diff --git a/app/assets/javascripts/components/features/compose/containers/spoiler_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/spoiler_button_container.jsx deleted file mode 100644 index b1c80fe19..000000000 --- a/app/assets/javascripts/components/features/compose/containers/spoiler_button_container.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { connect } from 'react-redux'; -import TextIconButton from '../components/text_icon_button'; -import { changeComposeSpoilerness } from '../../../actions/compose'; -import { injectIntl, defineMessages } from 'react-intl'; - -const messages = defineMessages({ - title: { id: 'compose_form.spoiler', defaultMessage: 'Hide text behind warning' } -}); - -const mapStateToProps = (state, { intl }) => ({ - label: 'CW', - title: intl.formatMessage(messages.title), - active: state.getIn(['compose', 'spoiler']), - ariaControls: 'cw-spoiler-input' -}); - -const mapDispatchToProps = dispatch => ({ - - onClick () { - dispatch(changeComposeSpoilerness()); - } - -}); - -export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(TextIconButton)); diff --git a/app/assets/javascripts/components/features/compose/containers/upload_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/upload_button_container.jsx deleted file mode 100644 index 78e5312f5..000000000 --- a/app/assets/javascripts/components/features/compose/containers/upload_button_container.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import { connect } from 'react-redux'; -import UploadButton from '../components/upload_button'; -import { uploadCompose } from '../../../actions/compose'; - -const mapStateToProps = state => ({ - disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')), - resetFileKey: state.getIn(['compose', 'resetFileKey']) -}); - -const mapDispatchToProps = dispatch => ({ - - onSelectFile (files) { - dispatch(uploadCompose(files)); - } - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(UploadButton); diff --git a/app/assets/javascripts/components/features/compose/containers/upload_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/upload_form_container.jsx deleted file mode 100644 index a6a202e17..000000000 --- a/app/assets/javascripts/components/features/compose/containers/upload_form_container.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { connect } from 'react-redux'; -import UploadForm from '../components/upload_form'; -import { undoUploadCompose } from '../../../actions/compose'; - -const mapStateToProps = (state, props) => ({ - media: state.getIn(['compose', 'media_attachments']), -}); - -const mapDispatchToProps = dispatch => ({ - - onRemoveFile (media_id) { - dispatch(undoUploadCompose(media_id)); - } - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(UploadForm); diff --git a/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx b/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx deleted file mode 100644 index b0f1d4d19..000000000 --- a/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import { connect } from 'react-redux'; -import UploadProgress from '../components/upload_progress'; - -const mapStateToProps = (state, props) => ({ - active: state.getIn(['compose', 'is_uploading']), - progress: state.getIn(['compose', 'progress']) -}); - -export default connect(mapStateToProps)(UploadProgress); diff --git a/app/assets/javascripts/components/features/compose/containers/warning_container.jsx b/app/assets/javascripts/components/features/compose/containers/warning_container.jsx deleted file mode 100644 index cd744ed82..000000000 --- a/app/assets/javascripts/components/features/compose/containers/warning_container.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import { connect } from 'react-redux'; -import Warning from '../components/warning'; -import { createSelector } from 'reselect'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; - -const getMentionedUsernames = createSelector(state => state.getIn(['compose', 'text']), text => text.match(/(?:^|[^\/\w])@([a-z0-9_]+@[a-z0-9\.\-]+)/ig)); - -const getMentionedDomains = createSelector(getMentionedUsernames, mentionedUsernamesWithDomains => { - return mentionedUsernamesWithDomains !== null ? [...new Set(mentionedUsernamesWithDomains.map(item => item.split('@')[2]))] : []; -}); - -const mapStateToProps = state => { - const mentionedUsernames = getMentionedUsernames(state); - const mentionedUsernamesWithDomains = getMentionedDomains(state); - - return { - needsLeakWarning: (state.getIn(['compose', 'privacy']) === 'private' || state.getIn(['compose', 'privacy']) === 'direct') && mentionedUsernames !== null, - mentionedDomains: mentionedUsernamesWithDomains, - needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', state.getIn(['meta', 'me']), 'locked']) - }; -}; - -const WarningWrapper = ({ needsLeakWarning, needsLockWarning, mentionedDomains }) => { - if (needsLockWarning) { - return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <a href='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></a> }} />} />; - } else if (needsLeakWarning) { - return ( - <Warning - message={<FormattedMessage - id='compose_form.privacy_disclaimer' - defaultMessage='Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.' - values={{ domains: <strong>{mentionedDomains.join(', ')}</strong>, domainsCount: mentionedDomains.length }} - />} - /> - ); - } - - return null; -}; - -WarningWrapper.propTypes = { - needsLeakWarning: PropTypes.bool, - needsLockWarning: PropTypes.bool, - mentionedDomains: PropTypes.array.isRequired, -}; - -export default connect(mapStateToProps)(WarningWrapper); diff --git a/app/assets/javascripts/components/features/compose/index.jsx b/app/assets/javascripts/components/features/compose/index.jsx deleted file mode 100644 index ae1b52ca0..000000000 --- a/app/assets/javascripts/components/features/compose/index.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import ComposeFormContainer from './containers/compose_form_container'; -import UploadFormContainer from './containers/upload_form_container'; -import NavigationContainer from './containers/navigation_container'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { mountCompose, unmountCompose } from '../../actions/compose'; -import { Link } from 'react-router'; -import { injectIntl, defineMessages } from 'react-intl'; -import SearchContainer from './containers/search_container'; -import { Motion, spring } from 'react-motion'; -import SearchResultsContainer from './containers/search_results_container'; - -const messages = defineMessages({ - start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, - public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, - community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, - preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, - logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' } -}); - -const mapStateToProps = state => ({ - showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) -}); - -class Compose extends React.PureComponent { - - componentDidMount () { - this.props.dispatch(mountCompose()); - } - - componentWillUnmount () { - this.props.dispatch(unmountCompose()); - } - - render () { - const { withHeader, showSearch, intl } = this.props; - - let header = ''; - - if (withHeader) { - header = ( - <div className='drawer__header'> - <Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)}><i role="img" aria-label={intl.formatMessage(messages.start)} className='fa fa-fw fa-asterisk' /></Link> - <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role="img" aria-label={intl.formatMessage(messages.community)} className='fa fa-fw fa-users' /></Link> - <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role="img" aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link> - <a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)}><i role="img" aria-label={intl.formatMessage(messages.preferences)} className='fa fa-fw fa-cog' /></a> - <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)}><i role="img" aria-label={intl.formatMessage(messages.logout)} className='fa fa-fw fa-sign-out' /></a> - </div> - ); - } - - return ( - <div className='drawer'> - {header} - - <SearchContainer /> - - <div className='drawer__pager'> - <div className='drawer__inner'> - <NavigationContainer /> - <ComposeFormContainer /> - </div> - - <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}> - {({ x }) => - <div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}> - <SearchResultsContainer /> - </div> - } - </Motion> - </div> - </div> - ); - } - -} - -Compose.propTypes = { - dispatch: PropTypes.func.isRequired, - withHeader: PropTypes.bool, - showSearch: PropTypes.bool, - intl: PropTypes.object.isRequired -}; - -export default connect(mapStateToProps)(injectIntl(Compose)); diff --git a/app/assets/javascripts/components/features/favourited_statuses/index.jsx b/app/assets/javascripts/components/features/favourited_statuses/index.jsx deleted file mode 100644 index bc45ace51..000000000 --- a/app/assets/javascripts/components/features/favourited_statuses/index.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import LoadingIndicator from '../../components/loading_indicator'; -import { fetchFavouritedStatuses, expandFavouritedStatuses } from '../../actions/favourites'; -import Column from '../ui/components/column'; -import StatusList from '../../components/status_list'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - heading: { id: 'column.favourites', defaultMessage: 'Favourites' } -}); - -const mapStateToProps = state => ({ - statusIds: state.getIn(['status_lists', 'favourites', 'items']), - loaded: state.getIn(['status_lists', 'favourites', 'loaded']), - me: state.getIn(['meta', 'me']) -}); - -class Favourites extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleScrollToBottom = this.handleScrollToBottom.bind(this); - } - - componentWillMount () { - this.props.dispatch(fetchFavouritedStatuses()); - } - - handleScrollToBottom () { - this.props.dispatch(expandFavouritedStatuses()); - } - - render () { - const { statusIds, loaded, intl, me } = this.props; - - if (!loaded) { - return ( - <Column> - <LoadingIndicator /> - </Column> - ); - } - - return ( - <Column icon='star' heading={intl.formatMessage(messages.heading)}> - <ColumnBackButtonSlim /> - <StatusList {...this.props} onScrollToBottom={this.handleScrollToBottom} /> - </Column> - ); - } - -} - -Favourites.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - loaded: PropTypes.bool, - intl: PropTypes.object.isRequired, - me: PropTypes.number.isRequired -}; - -export default connect(mapStateToProps)(injectIntl(Favourites)); diff --git a/app/assets/javascripts/components/features/favourites/index.jsx b/app/assets/javascripts/components/features/favourites/index.jsx deleted file mode 100644 index bd6cf8a90..000000000 --- a/app/assets/javascripts/components/features/favourites/index.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import LoadingIndicator from '../../components/loading_indicator'; -import { fetchFavourites } from '../../actions/interactions'; -import { ScrollContainer } from 'react-router-scroll'; -import AccountContainer from '../../containers/account_container'; -import Column from '../ui/components/column'; -import ColumnBackButton from '../../components/column_back_button'; - -const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'favourited_by', Number(props.params.statusId)]) -}); - -class Favourites extends React.PureComponent { - - componentWillMount () { - this.props.dispatch(fetchFavourites(Number(this.props.params.statusId))); - } - - componentWillReceiveProps(nextProps) { - if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this.props.dispatch(fetchFavourites(Number(nextProps.params.statusId))); - } - } - - render () { - const { accountIds } = this.props; - - if (!accountIds) { - return ( - <Column> - <LoadingIndicator /> - </Column> - ); - } - - return ( - <Column> - <ColumnBackButton /> - - <ScrollContainer scrollKey='favourites'> - <div className='scrollable'> - {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} - </div> - </ScrollContainer> - </Column> - ); - } - -} - -Favourites.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list -}; - -export default connect(mapStateToProps)(Favourites); diff --git a/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx b/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx deleted file mode 100644 index d35a54c12..000000000 --- a/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Permalink from '../../../components/permalink'; -import Avatar from '../../../components/avatar'; -import DisplayName from '../../../components/display_name'; -import emojify from '../../../emoji'; -import IconButton from '../../../components/icon_button'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' }, - reject: { id: 'follow_request.reject', defaultMessage: 'Reject' } -}); - -const AccountAuthorize = ({ intl, account, onAuthorize, onReject }) => { - const content = { __html: emojify(account.get('note')) }; - - return ( - <div className='account-authorize__wrapper'> - <div className='account-authorize'> - <Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name'> - <div className='account-authorize__avatar'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={48} /></div> - <DisplayName account={account} /> - </Permalink> - - <div className='account__header__content' dangerouslySetInnerHTML={content} /> - </div> - - <div className='account--panel'> - <div className='account--panel__button'><IconButton title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} /></div> - <div className='account--panel__button'><IconButton title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} /></div> - </div> - </div> - ) -}; - -AccountAuthorize.propTypes = { - account: ImmutablePropTypes.map.isRequired, - onAuthorize: PropTypes.func.isRequired, - onReject: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(AccountAuthorize); diff --git a/app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx b/app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx deleted file mode 100644 index da1e5eaa1..000000000 --- a/app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import { connect } from 'react-redux'; -import { makeGetAccount } from '../../../selectors'; -import AccountAuthorize from '../components/account_authorize'; -import { authorizeFollowRequest, rejectFollowRequest } from '../../../actions/accounts'; - -const makeMapStateToProps = () => { - const getAccount = makeGetAccount(); - - const mapStateToProps = (state, props) => ({ - account: getAccount(state, props.id) - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = (dispatch, { id }) => ({ - onAuthorize (account) { - dispatch(authorizeFollowRequest(id)); - }, - - onReject (account) { - dispatch(rejectFollowRequest(id)); - } -}); - -export default connect(makeMapStateToProps, mapDispatchToProps)(AccountAuthorize); diff --git a/app/assets/javascripts/components/features/follow_requests/index.jsx b/app/assets/javascripts/components/features/follow_requests/index.jsx deleted file mode 100644 index 3dc709654..000000000 --- a/app/assets/javascripts/components/features/follow_requests/index.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import LoadingIndicator from '../../components/loading_indicator'; -import { ScrollContainer } from 'react-router-scroll'; -import Column from '../ui/components/column'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; -import AccountAuthorizeContainer from './containers/account_authorize_container'; -import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' } -}); - -const mapStateToProps = state => ({ - accountIds: state.getIn(['user_lists', 'follow_requests', 'items']) -}); - -class FollowRequests extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleScroll = this.handleScroll.bind(this); - } - - componentWillMount () { - this.props.dispatch(fetchFollowRequests()); - } - - handleScroll (e) { - const { scrollTop, scrollHeight, clientHeight } = e.target; - - if (scrollTop === scrollHeight - clientHeight) { - this.props.dispatch(expandFollowRequests()); - } - } - - render () { - const { intl, accountIds } = this.props; - - if (!accountIds) { - return ( - <Column> - <LoadingIndicator /> - </Column> - ); - } - - return ( - <Column icon='users' heading={intl.formatMessage(messages.heading)}> - <ColumnBackButtonSlim /> - <ScrollContainer scrollKey='follow_requests'> - <div className='scrollable' onScroll={this.handleScroll}> - {accountIds.map(id => - <AccountAuthorizeContainer key={id} id={id} /> - )} - </div> - </ScrollContainer> - </Column> - ); - } -} - -FollowRequests.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list, - intl: PropTypes.object.isRequired -}; - -export default connect(mapStateToProps)(injectIntl(FollowRequests)); diff --git a/app/assets/javascripts/components/features/followers/index.jsx b/app/assets/javascripts/components/features/followers/index.jsx deleted file mode 100644 index 2b1e3719e..000000000 --- a/app/assets/javascripts/components/features/followers/index.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import LoadingIndicator from '../../components/loading_indicator'; -import { - fetchAccount, - fetchFollowers, - expandFollowers -} from '../../actions/accounts'; -import { ScrollContainer } from 'react-router-scroll'; -import AccountContainer from '../../containers/account_container'; -import Column from '../ui/components/column'; -import HeaderContainer from '../account_timeline/containers/header_container'; -import LoadMore from '../../components/load_more'; -import ColumnBackButton from '../../components/column_back_button'; - -const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'followers', Number(props.params.accountId), 'items']) -}); - -class Followers extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleScroll = this.handleScroll.bind(this); - this.handleLoadMore = this.handleLoadMore.bind(this); - } - - componentWillMount () { - this.props.dispatch(fetchAccount(Number(this.props.params.accountId))); - this.props.dispatch(fetchFollowers(Number(this.props.params.accountId))); - } - - componentWillReceiveProps(nextProps) { - if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(Number(nextProps.params.accountId))); - this.props.dispatch(fetchFollowers(Number(nextProps.params.accountId))); - } - } - - handleScroll (e) { - const { scrollTop, scrollHeight, clientHeight } = e.target; - - if (scrollTop === scrollHeight - clientHeight) { - this.props.dispatch(expandFollowers(Number(this.props.params.accountId))); - } - } - - handleLoadMore (e) { - e.preventDefault(); - this.props.dispatch(expandFollowers(Number(this.props.params.accountId))); - } - - render () { - const { accountIds } = this.props; - - if (!accountIds) { - return ( - <Column> - <LoadingIndicator /> - </Column> - ); - } - - return ( - <Column> - <ColumnBackButton /> - - <ScrollContainer scrollKey='followers'> - <div className='scrollable' onScroll={this.handleScroll}> - <div className='followers'> - <HeaderContainer accountId={this.props.params.accountId} /> - {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} - <LoadMore onClick={this.handleLoadMore} /> - </div> - </div> - </ScrollContainer> - </Column> - ); - } - -} - -Followers.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list -}; - -export default connect(mapStateToProps)(Followers); diff --git a/app/assets/javascripts/components/features/following/index.jsx b/app/assets/javascripts/components/features/following/index.jsx deleted file mode 100644 index 30b320917..000000000 --- a/app/assets/javascripts/components/features/following/index.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import LoadingIndicator from '../../components/loading_indicator'; -import { - fetchAccount, - fetchFollowing, - expandFollowing -} from '../../actions/accounts'; -import { ScrollContainer } from 'react-router-scroll'; -import AccountContainer from '../../containers/account_container'; -import Column from '../ui/components/column'; -import HeaderContainer from '../account_timeline/containers/header_container'; -import LoadMore from '../../components/load_more'; -import ColumnBackButton from '../../components/column_back_button'; - -const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'following', Number(props.params.accountId), 'items']) -}); - -class Following extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleScroll = this.handleScroll.bind(this); - this.handleLoadMore = this.handleLoadMore.bind(this); - } - - componentWillMount () { - this.props.dispatch(fetchAccount(Number(this.props.params.accountId))); - this.props.dispatch(fetchFollowing(Number(this.props.params.accountId))); - } - - componentWillReceiveProps(nextProps) { - if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(Number(nextProps.params.accountId))); - this.props.dispatch(fetchFollowing(Number(nextProps.params.accountId))); - } - } - - handleScroll (e) { - const { scrollTop, scrollHeight, clientHeight } = e.target; - - if (scrollTop === scrollHeight - clientHeight) { - this.props.dispatch(expandFollowing(Number(this.props.params.accountId))); - } - } - - handleLoadMore (e) { - e.preventDefault(); - this.props.dispatch(expandFollowing(Number(this.props.params.accountId))); - } - - render () { - const { accountIds } = this.props; - - if (!accountIds) { - return ( - <Column> - <LoadingIndicator /> - </Column> - ); - } - - return ( - <Column> - <ColumnBackButton /> - - <ScrollContainer scrollKey='following'> - <div className='scrollable' onScroll={this.handleScroll}> - <div className='following'> - <HeaderContainer accountId={this.props.params.accountId} /> - {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} - <LoadMore onClick={this.handleLoadMore} /> - </div> - </div> - </ScrollContainer> - </Column> - ); - } - -} - -Following.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list -}; - -export default connect(mapStateToProps)(Following); diff --git a/app/assets/javascripts/components/features/generic_not_found/index.jsx b/app/assets/javascripts/components/features/generic_not_found/index.jsx deleted file mode 100644 index a7afe29b0..000000000 --- a/app/assets/javascripts/components/features/generic_not_found/index.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import Column from '../ui/components/column'; -import MissingIndicator from '../../components/missing_indicator'; - -const GenericNotFound = () => ( - <Column> - <MissingIndicator /> - </Column> -); - -export default GenericNotFound; diff --git a/app/assets/javascripts/components/features/getting_started/index.jsx b/app/assets/javascripts/components/features/getting_started/index.jsx deleted file mode 100644 index bd4920c94..000000000 --- a/app/assets/javascripts/components/features/getting_started/index.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import Column from '../ui/components/column'; -import ColumnLink from '../ui/components/column_link'; -import ColumnSubheading from '../ui/components/column_subheading'; -import { Link } from 'react-router'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; - -const messages = defineMessages({ - heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, - public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, - navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation'}, - settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings'}, - community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, - preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, - follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, - sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, - favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, - blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, - mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, - info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' } -}); - -const mapStateToProps = state => ({ - me: state.getIn(['accounts', state.getIn(['meta', 'me'])]) -}); - -const GettingStarted = ({ intl, me }) => { - let followRequests = ''; - - if (me.get('locked')) { - followRequests = <ColumnLink icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />; - } - - return ( - <Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile={true}> - <div className='getting-started__wrapper'> - <ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)}/> - <ColumnLink icon='users' hideOnMobile={true} text={intl.formatMessage(messages.community_timeline)} to='/timelines/public/local' /> - <ColumnLink icon='globe' hideOnMobile={true} text={intl.formatMessage(messages.public_timeline)} to='/timelines/public' /> - <ColumnLink icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' /> - {followRequests} - <ColumnLink icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' /> - <ColumnLink icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' /> - <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)}/> - <ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' /> - <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' /> - <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' /> - </div> - - <div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}> - <div className='static-content getting-started'> - <p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p> - </div> - </div> - </Column> - ); -}; - -GettingStarted.propTypes = { - intl: PropTypes.object.isRequired, - me: ImmutablePropTypes.map.isRequired -}; - -export default connect(mapStateToProps)(injectIntl(GettingStarted)); diff --git a/app/assets/javascripts/components/features/hashtag_timeline/index.jsx b/app/assets/javascripts/components/features/hashtag_timeline/index.jsx deleted file mode 100644 index 0575e9214..000000000 --- a/app/assets/javascripts/components/features/hashtag_timeline/index.jsx +++ /dev/null @@ -1,89 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import StatusListContainer from '../ui/containers/status_list_container'; -import Column from '../ui/components/column'; -import { - refreshTimeline, - updateTimeline, - deleteFromTimelines -} from '../../actions/timelines'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; -import { FormattedMessage } from 'react-intl'; -import createStream from '../../stream'; - -const mapStateToProps = state => ({ - hasUnread: state.getIn(['timelines', 'tag', 'unread']) > 0, - streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']), - accessToken: state.getIn(['meta', 'access_token']) -}); - -class HashtagTimeline extends React.PureComponent { - - _subscribe (dispatch, id) { - const { streamingAPIBaseURL, accessToken } = this.props; - - this.subscription = createStream(streamingAPIBaseURL, accessToken, `hashtag&tag=${id}`, { - - received (data) { - switch(data.event) { - case 'update': - dispatch(updateTimeline('tag', JSON.parse(data.payload))); - break; - case 'delete': - dispatch(deleteFromTimelines(data.payload)); - break; - } - } - - }); - } - - _unsubscribe () { - if (typeof this.subscription !== 'undefined') { - this.subscription.close(); - this.subscription = null; - } - } - - componentDidMount () { - const { dispatch } = this.props; - const { id } = this.props.params; - - dispatch(refreshTimeline('tag', id)); - this._subscribe(dispatch, id); - } - - componentWillReceiveProps (nextProps) { - if (nextProps.params.id !== this.props.params.id) { - this.props.dispatch(refreshTimeline('tag', nextProps.params.id)); - this._unsubscribe(); - this._subscribe(this.props.dispatch, nextProps.params.id); - } - } - - componentWillUnmount () { - this._unsubscribe(); - } - - render () { - const { id, hasUnread } = this.props.params; - - return ( - <Column icon='hashtag' active={hasUnread} heading={id}> - <ColumnBackButtonSlim /> - <StatusListContainer scrollKey='hashtag_timeline' type='tag' id={id} emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} /> - </Column> - ); - } - -} - -HashtagTimeline.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - streamingAPIBaseURL: PropTypes.string.isRequired, - accessToken: PropTypes.string.isRequired, - hasUnread: PropTypes.bool -}; - -export default connect(mapStateToProps)(HashtagTimeline); diff --git a/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx b/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx deleted file mode 100644 index 81a1a0e5b..000000000 --- a/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ColumnCollapsable from '../../../components/column_collapsable'; -import SettingToggle from '../../notifications/components/setting_toggle'; -import SettingText from './setting_text'; - -const messages = defineMessages({ - filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' }, - settings: { id: 'home.settings', defaultMessage: 'Column settings' } -}); - -class ColumnSettings extends React.PureComponent { - - render () { - const { settings, onChange, onSave, intl } = this.props; - - return ( - <ColumnCollapsable icon='sliders' title={intl.formatMessage(messages.settings)} fullHeight={209} onCollapse={onSave}> - <div className='column-settings__outer'> - <span className='column-settings__section'><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span> - - <div className='column-settings__row'> - <SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_reblogs' defaultMessage='Show boosts' />} /> - </div> - - <div className='column-settings__row'> - <SettingToggle settings={settings} settingKey={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_replies' defaultMessage='Show replies' />} /> - </div> - - <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span> - - <div className='column-settings__row'> - <SettingText settings={settings} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} /> - </div> - </div> - </ColumnCollapsable> - ); - } - -} - -ColumnSettings.propTypes = { - settings: ImmutablePropTypes.map.isRequired, - onChange: PropTypes.func.isRequired, - onSave: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -} - -export default injectIntl(ColumnSettings); diff --git a/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx b/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx deleted file mode 100644 index 90b4aeb94..000000000 --- a/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; - -class SettingText extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleChange = this.handleChange.bind(this); - } - - handleChange (e) { - this.props.onChange(this.props.settingKey, e.target.value) - } - - render () { - const { settings, settingKey, label } = this.props; - - return ( - <input - className='setting-text' - value={settings.getIn(settingKey)} - onChange={this.handleChange} - placeholder={label} - /> - ); - } - -} - -SettingText.propTypes = { - settings: ImmutablePropTypes.map.isRequired, - settingKey: PropTypes.array.isRequired, - label: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired -}; - -export default SettingText; diff --git a/app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx b/app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx deleted file mode 100644 index 3b3ce19bc..000000000 --- a/app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux'; -import ColumnSettings from '../components/column_settings'; -import { changeSetting, saveSettings } from '../../../actions/settings'; - -const mapStateToProps = state => ({ - settings: state.getIn(['settings', 'home']) -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (key, checked) { - dispatch(changeSetting(['home', ...key], checked)); - }, - - onSave () { - dispatch(saveSettings()); - } - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); diff --git a/app/assets/javascripts/components/features/home_timeline/index.jsx b/app/assets/javascripts/components/features/home_timeline/index.jsx deleted file mode 100644 index 52b94690d..000000000 --- a/app/assets/javascripts/components/features/home_timeline/index.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import StatusListContainer from '../ui/containers/status_list_container'; -import Column from '../ui/components/column'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ColumnSettingsContainer from './containers/column_settings_container'; -import { Link } from 'react-router'; - -const messages = defineMessages({ - title: { id: 'column.home', defaultMessage: 'Home' } -}); - -const mapStateToProps = state => ({ - hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0 -}); - -class HomeTimeline extends React.PureComponent { - - render () { - const { intl, hasUnread } = this.props; - - return ( - <Column icon='home' active={hasUnread} heading={intl.formatMessage(messages.title)}> - <ColumnSettingsContainer /> - <StatusListContainer {...this.props} scrollKey='home_timeline' type='home' emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage="You aren't following anyone yet. Visit {public} or use search to get started and meet other users." values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} /> - </Column> - ); - } - -} - -HomeTimeline.propTypes = { - intl: PropTypes.object.isRequired, - hasUnread: PropTypes.bool -}; - -export default connect(mapStateToProps)(injectIntl(HomeTimeline)); diff --git a/app/assets/javascripts/components/features/mutes/index.jsx b/app/assets/javascripts/components/features/mutes/index.jsx deleted file mode 100644 index 0310fa7f2..000000000 --- a/app/assets/javascripts/components/features/mutes/index.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import LoadingIndicator from '../../components/loading_indicator'; -import { ScrollContainer } from 'react-router-scroll'; -import Column from '../ui/components/column'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; -import AccountContainer from '../../containers/account_container'; -import { fetchMutes, expandMutes } from '../../actions/mutes'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - heading: { id: 'column.mutes', defaultMessage: 'Muted users' } -}); - -const mapStateToProps = state => ({ - accountIds: state.getIn(['user_lists', 'mutes', 'items']) -}); - -class Mutes extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleScroll = this.handleScroll.bind(this); - } - - componentWillMount () { - this.props.dispatch(fetchMutes()); - } - - handleScroll (e) { - const { scrollTop, scrollHeight, clientHeight } = e.target; - - if (scrollTop === scrollHeight - clientHeight) { - this.props.dispatch(expandMutes()); - } - } - - render () { - const { intl, accountIds } = this.props; - - if (!accountIds) { - return ( - <Column> - <LoadingIndicator /> - </Column> - ); - } - - return ( - <Column icon='volume-off' heading={intl.formatMessage(messages.heading)}> - <ColumnBackButtonSlim /> - <ScrollContainer scrollKey='mutes'> - <div className='scrollable mutes' onScroll={this.handleScroll}> - {accountIds.map(id => - <AccountContainer key={id} id={id} /> - )} - </div> - </ScrollContainer> - </Column> - ); - } - -} - -Mutes.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list, - intl: PropTypes.object.isRequired -}; - -export default connect(mapStateToProps)(injectIntl(Mutes)); diff --git a/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx b/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx deleted file mode 100644 index 206b05f91..000000000 --- a/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - clear: { id: 'notifications.clear', defaultMessage: 'Clear notifications' } -}); - -class ClearColumnButton extends React.Component { - - render () { - const { intl } = this.props; - - return ( - <div role='button' title={intl.formatMessage(messages.clear)} className='column-icon column-icon-clear' tabIndex='0' onClick={this.props.onClick}> - <i className='fa fa-eraser' /> - </div> - ); - } -} - -ClearColumnButton.propTypes = { - onClick: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(ClearColumnButton); diff --git a/app/assets/javascripts/components/features/notifications/components/column_settings.jsx b/app/assets/javascripts/components/features/notifications/components/column_settings.jsx deleted file mode 100644 index 30063010c..000000000 --- a/app/assets/javascripts/components/features/notifications/components/column_settings.jsx +++ /dev/null @@ -1,70 +0,0 @@ -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ColumnCollapsable from '../../../components/column_collapsable'; -import SettingToggle from './setting_toggle'; - -const messages = defineMessages({ - settings: { id: 'notifications.settings', defaultMessage: 'Column settings' } -}); - -class ColumnSettings extends React.PureComponent { - - render () { - const { settings, intl, onChange, onSave } = this.props; - - const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />; - const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />; - const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />; - - return ( - <ColumnCollapsable icon='sliders' title={intl.formatMessage(messages.settings)} fullHeight={616} onCollapse={onSave}> - <div className='column-settings__outer'> - <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span> - - <div className='column-settings__row'> - <SettingToggle settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} /> - <SettingToggle settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} /> - <SettingToggle settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} /> - </div> - - <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span> - - <div className='column-settings__row'> - <SettingToggle settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} /> - <SettingToggle settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} /> - <SettingToggle settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} /> - </div> - - <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span> - - <div className='column-settings__row'> - <SettingToggle settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} /> - <SettingToggle settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} /> - <SettingToggle settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} /> - </div> - - <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span> - - <div className='column-settings__row'> - <SettingToggle settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} /> - <SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} /> - <SettingToggle settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} /> - </div> - </div> - </ColumnCollapsable> - ); - } - -} - -ColumnSettings.propTypes = { - settings: ImmutablePropTypes.map.isRequired, - onChange: PropTypes.func.isRequired, - onSave: PropTypes.func.isRequired, - intl: PropTypes.shape({ - formatMessage: PropTypes.func.isRequired - }).isRequired -}; - -export default injectIntl(ColumnSettings); diff --git a/app/assets/javascripts/components/features/notifications/components/notification.jsx b/app/assets/javascripts/components/features/notifications/components/notification.jsx deleted file mode 100644 index 34dd76bb7..000000000 --- a/app/assets/javascripts/components/features/notifications/components/notification.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import StatusContainer from '../../../containers/status_container'; -import AccountContainer from '../../../containers/account_container'; -import { FormattedMessage } from 'react-intl'; -import Permalink from '../../../components/permalink'; -import emojify from '../../../emoji'; -import escapeTextContentForBrowser from 'escape-html'; - -class Notification extends React.PureComponent { - - renderFollow (account, link) { - return ( - <div className='notification notification-follow'> - <div className='notification__message'> - <div className='notification__favourite-icon-wrapper'> - <i className='fa fa-fw fa-user-plus' /> - </div> - - <FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} /> - </div> - - <AccountContainer id={account.get('id')} withNote={false} /> - </div> - ); - } - - renderMention (notification) { - return <StatusContainer id={notification.get('status')} />; - } - - renderFavourite (notification, link) { - return ( - <div className='notification notification-favourite'> - <div className='notification__message'> - <div className='notification__favourite-icon-wrapper'> - <i className='fa fa-fw fa-star star-icon'/> - </div> - - <FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} /> - </div> - - <StatusContainer id={notification.get('status')} muted={true} /> - </div> - ); - } - - renderReblog (notification, link) { - return ( - <div className='notification notification-reblog'> - <div className='notification__message'> - <div className='notification__favourite-icon-wrapper'> - <i className='fa fa-fw fa-retweet' /> - </div> - - <FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} /> - </div> - - <StatusContainer id={notification.get('status')} muted={true} /> - </div> - ); - } - - render () { // eslint-disable-line consistent-return - const { notification } = this.props; - const account = notification.get('account'); - const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username'); - const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; - const link = <Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHTML} />; - - switch(notification.get('type')) { - case 'follow': - return this.renderFollow(account, link); - case 'mention': - return this.renderMention(notification); - case 'favourite': - return this.renderFavourite(notification, link); - case 'reblog': - return this.renderReblog(notification, link); - } - } - -} - -Notification.propTypes = { - notification: ImmutablePropTypes.map.isRequired -}; - -export default Notification; diff --git a/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx b/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx deleted file mode 100644 index e9bca5928..000000000 --- a/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Toggle from 'react-toggle'; - -const SettingToggle = ({ settings, settingKey, label, onChange, htmlFor = '' }) => ( - <label htmlFor={htmlFor} className='setting-toggle__label'> - <Toggle checked={settings.getIn(settingKey)} onChange={(e) => onChange(settingKey, e.target.checked)} /> - <span className='setting-toggle'>{label}</span> - </label> -); - -SettingToggle.propTypes = { - settings: ImmutablePropTypes.map.isRequired, - settingKey: PropTypes.array.isRequired, - label: PropTypes.node.isRequired, - onChange: PropTypes.func.isRequired, - htmlFor: PropTypes.string -}; - -export default SettingToggle; diff --git a/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx b/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx deleted file mode 100644 index bc24c75e0..000000000 --- a/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux'; -import ColumnSettings from '../components/column_settings'; -import { changeSetting, saveSettings } from '../../../actions/settings'; - -const mapStateToProps = state => ({ - settings: state.getIn(['settings', 'notifications']) -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (key, checked) { - dispatch(changeSetting(['notifications', ...key], checked)); - }, - - onSave () { - dispatch(saveSettings()); - } - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); diff --git a/app/assets/javascripts/components/features/notifications/containers/notification_container.jsx b/app/assets/javascripts/components/features/notifications/containers/notification_container.jsx deleted file mode 100644 index 4ca1b1b7b..000000000 --- a/app/assets/javascripts/components/features/notifications/containers/notification_container.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux'; -import { makeGetNotification } from '../../../selectors'; -import Notification from '../components/notification'; - -const makeMapStateToProps = () => { - const getNotification = makeGetNotification(); - - const mapStateToProps = (state, props) => ({ - notification: getNotification(state, props.notification, props.accountId) - }); - - return mapStateToProps; -}; - -export default connect(makeMapStateToProps)(Notification); diff --git a/app/assets/javascripts/components/features/notifications/index.jsx b/app/assets/javascripts/components/features/notifications/index.jsx deleted file mode 100644 index da3ce2f62..000000000 --- a/app/assets/javascripts/components/features/notifications/index.jsx +++ /dev/null @@ -1,142 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Column from '../ui/components/column'; -import { expandNotifications, clearNotifications, scrollTopNotifications } from '../../actions/notifications'; -import NotificationContainer from './containers/notification_container'; -import { ScrollContainer } from 'react-router-scroll'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ColumnSettingsContainer from './containers/column_settings_container'; -import { createSelector } from 'reselect'; -import Immutable from 'immutable'; -import LoadMore from '../../components/load_more'; -import ClearColumnButton from './components/clear_column_button'; -import { openModal } from '../../actions/modal'; - -const messages = defineMessages({ - title: { id: 'column.notifications', defaultMessage: 'Notifications' }, - clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' }, - clearConfirm: { id: 'notifications.clear', defaultMessage: 'Clear notifications' } -}); - -const getNotifications = createSelector([ - state => Immutable.List(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()), - state => state.getIn(['notifications', 'items']) -], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type')))); - -const mapStateToProps = state => ({ - notifications: getNotifications(state), - isLoading: state.getIn(['notifications', 'isLoading'], true), - isUnread: state.getIn(['notifications', 'unread']) > 0 -}); - -class Notifications extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleScroll = this.handleScroll.bind(this); - this.handleLoadMore = this.handleLoadMore.bind(this); - this.handleClear = this.handleClear.bind(this); - this.setRef = this.setRef.bind(this); - } - - handleScroll (e) { - const { scrollTop, scrollHeight, clientHeight } = e.target; - const offset = scrollHeight - scrollTop - clientHeight; - this._oldScrollPosition = scrollHeight - scrollTop; - - if (250 > offset && !this.props.isLoading) { - this.props.dispatch(expandNotifications()); - } else if (scrollTop < 100) { - this.props.dispatch(scrollTopNotifications(true)); - } else { - this.props.dispatch(scrollTopNotifications(false)); - } - } - - componentDidUpdate (prevProps) { - if (this.node.scrollTop > 0 && (prevProps.notifications.size < this.props.notifications.size && prevProps.notifications.first() !== this.props.notifications.first() && !!this._oldScrollPosition)) { - this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition; - } - } - - handleLoadMore (e) { - e.preventDefault(); - this.props.dispatch(expandNotifications()); - } - - handleClear () { - const { dispatch, intl } = this.props; - - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.clearMessage), - confirm: intl.formatMessage(messages.clearConfirm), - onConfirm: () => dispatch(clearNotifications()) - })); - } - - setRef (c) { - this.node = c; - } - - render () { - const { intl, notifications, shouldUpdateScroll, isLoading, isUnread } = this.props; - - let loadMore = ''; - let scrollableArea = ''; - let unread = ''; - - if (!isLoading && notifications.size > 0) { - loadMore = <LoadMore onClick={this.handleLoadMore} />; - } - - if (isUnread) { - unread = <div className='notifications__unread-indicator' />; - } - - if (isLoading || notifications.size > 0) { - scrollableArea = ( - <div className='scrollable' onScroll={this.handleScroll} ref={this.setRef}> - {unread} - - <div> - {notifications.map(item => <NotificationContainer key={item.get('id')} notification={item} accountId={item.get('account')} />)} - {loadMore} - </div> - </div> - ); - } else { - scrollableArea = ( - <div className='empty-column-indicator' ref={this.setRef}> - <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." /> - </div> - ); - } - - return ( - <Column icon='bell' active={isUnread} heading={intl.formatMessage(messages.title)}> - <ColumnSettingsContainer /> - <ClearColumnButton onClick={this.handleClear} /> - <ScrollContainer scrollKey='notifications' shouldUpdateScroll={shouldUpdateScroll}> - {scrollableArea} - </ScrollContainer> - </Column> - ); - } - -} - -Notifications.propTypes = { - notifications: ImmutablePropTypes.list.isRequired, - dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, - intl: PropTypes.object.isRequired, - isLoading: PropTypes.bool, - isUnread: PropTypes.bool -}; - -Notifications.defaultProps = { - trackScroll: true -}; - -export default connect(mapStateToProps)(injectIntl(Notifications)); diff --git a/app/assets/javascripts/components/features/public_timeline/index.jsx b/app/assets/javascripts/components/features/public_timeline/index.jsx deleted file mode 100644 index 53be13686..000000000 --- a/app/assets/javascripts/components/features/public_timeline/index.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import StatusListContainer from '../ui/containers/status_list_container'; -import Column from '../ui/components/column'; -import { - refreshTimeline, - updateTimeline, - deleteFromTimelines, - connectTimeline, - disconnectTimeline -} from '../../actions/timelines'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; -import createStream from '../../stream'; - -const messages = defineMessages({ - title: { id: 'column.public', defaultMessage: 'Federated timeline' } -}); - -const mapStateToProps = state => ({ - hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0, - streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']), - accessToken: state.getIn(['meta', 'access_token']) -}); - -let subscription; - -class PublicTimeline extends React.PureComponent { - - componentDidMount () { - const { dispatch, streamingAPIBaseURL, accessToken } = this.props; - - dispatch(refreshTimeline('public')); - - if (typeof subscription !== 'undefined') { - return; - } - - subscription = createStream(streamingAPIBaseURL, accessToken, 'public', { - - connected () { - dispatch(connectTimeline('public')); - }, - - reconnected () { - dispatch(connectTimeline('public')); - }, - - disconnected () { - dispatch(disconnectTimeline('public')); - }, - - received (data) { - switch(data.event) { - case 'update': - dispatch(updateTimeline('public', JSON.parse(data.payload))); - break; - case 'delete': - dispatch(deleteFromTimelines(data.payload)); - break; - } - } - - }); - } - - componentWillUnmount () { - // if (typeof subscription !== 'undefined') { - // subscription.close(); - // subscription = null; - // } - } - - render () { - const { intl, hasUnread } = this.props; - - return ( - <Column icon='globe' active={hasUnread} heading={intl.formatMessage(messages.title)}> - <ColumnBackButtonSlim /> - <StatusListContainer {...this.props} type='public' scrollKey='public_timeline' emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />} /> - </Column> - ); - } - -} - -PublicTimeline.propTypes = { - dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - streamingAPIBaseURL: PropTypes.string.isRequired, - accessToken: PropTypes.string.isRequired, - hasUnread: PropTypes.bool -}; - -export default connect(mapStateToProps)(injectIntl(PublicTimeline)); diff --git a/app/assets/javascripts/components/features/reblogs/index.jsx b/app/assets/javascripts/components/features/reblogs/index.jsx deleted file mode 100644 index 5e5671422..000000000 --- a/app/assets/javascripts/components/features/reblogs/index.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import LoadingIndicator from '../../components/loading_indicator'; -import { fetchReblogs } from '../../actions/interactions'; -import { ScrollContainer } from 'react-router-scroll'; -import AccountContainer from '../../containers/account_container'; -import Column from '../ui/components/column'; -import ColumnBackButton from '../../components/column_back_button'; - -const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'reblogged_by', Number(props.params.statusId)]) -}); - -class Reblogs extends React.PureComponent { - - componentWillMount () { - this.props.dispatch(fetchReblogs(Number(this.props.params.statusId))); - } - - componentWillReceiveProps(nextProps) { - if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this.props.dispatch(fetchReblogs(Number(nextProps.params.statusId))); - } - } - - render () { - const { accountIds } = this.props; - - if (!accountIds) { - return ( - <Column> - <LoadingIndicator /> - </Column> - ); - } - - return ( - <Column> - <ColumnBackButton /> - - <ScrollContainer scrollKey='reblogs'> - <div className='scrollable reblogs'> - {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} - </div> - </ScrollContainer> - </Column> - ); - } - -} - -Reblogs.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list -}; - -export default connect(mapStateToProps)(Reblogs); diff --git a/app/assets/javascripts/components/features/report/components/status_check_box.jsx b/app/assets/javascripts/components/features/report/components/status_check_box.jsx deleted file mode 100644 index bc866616a..000000000 --- a/app/assets/javascripts/components/features/report/components/status_check_box.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import emojify from '../../../emoji'; -import Toggle from 'react-toggle'; - -class StatusCheckBox extends React.PureComponent { - - render () { - const { status, checked, onToggle, disabled } = this.props; - const content = { __html: emojify(status.get('content')) }; - - if (status.get('reblog')) { - return null; - } - - return ( - <div className='status-check-box'> - <div - className='status__content' - dangerouslySetInnerHTML={content} - /> - - <div className='status-check-box-toggle'> - <Toggle checked={checked} onChange={onToggle} disabled={disabled} /> - </div> - </div> - ); - } - -} - -StatusCheckBox.propTypes = { - status: ImmutablePropTypes.map.isRequired, - checked: PropTypes.bool, - onToggle: PropTypes.func.isRequired, - disabled: PropTypes.bool -}; - -export default StatusCheckBox; diff --git a/app/assets/javascripts/components/features/report/containers/status_check_box_container.jsx b/app/assets/javascripts/components/features/report/containers/status_check_box_container.jsx deleted file mode 100644 index 67ce9d9f3..000000000 --- a/app/assets/javascripts/components/features/report/containers/status_check_box_container.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import { connect } from 'react-redux'; -import StatusCheckBox from '../components/status_check_box'; -import { toggleStatusReport } from '../../../actions/reports'; -import Immutable from 'immutable'; - -const mapStateToProps = (state, { id }) => ({ - status: state.getIn(['statuses', id]), - checked: state.getIn(['reports', 'new', 'status_ids'], Immutable.Set()).includes(id) -}); - -const mapDispatchToProps = (dispatch, { id }) => ({ - - onToggle (e) { - dispatch(toggleStatusReport(id, e.target.checked)); - } - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(StatusCheckBox); diff --git a/app/assets/javascripts/components/features/report/index.jsx b/app/assets/javascripts/components/features/report/index.jsx deleted file mode 100644 index 6e3cfcb2a..000000000 --- a/app/assets/javascripts/components/features/report/index.jsx +++ /dev/null @@ -1,130 +0,0 @@ -import { connect } from 'react-redux'; -import { cancelReport, changeReportComment, submitReport } from '../../actions/reports'; -import { fetchAccountTimeline } from '../../actions/accounts'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Column from '../ui/components/column'; -import Button from '../../components/button'; -import { makeGetAccount } from '../../selectors'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; -import StatusCheckBox from './containers/status_check_box_container'; -import Immutable from 'immutable'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; - -const messages = defineMessages({ - heading: { id: 'report.heading', defaultMessage: 'New report' }, - placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' }, - submit: { id: 'report.submit', defaultMessage: 'Submit' } -}); - -const makeMapStateToProps = () => { - const getAccount = makeGetAccount(); - - const mapStateToProps = state => { - const accountId = state.getIn(['reports', 'new', 'account_id']); - - return { - isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']), - account: getAccount(state, accountId), - comment: state.getIn(['reports', 'new', 'comment']), - statusIds: Immutable.OrderedSet(state.getIn(['timelines', 'accounts_timelines', accountId, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])) - }; - }; - - return mapStateToProps; -}; - -class Report extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleCommentChange = this.handleCommentChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - } - - componentWillMount () { - if (!this.props.account) { - this.context.router.replace('/'); - } - } - - componentDidMount () { - if (!this.props.account) { - return; - } - - this.props.dispatch(fetchAccountTimeline(this.props.account.get('id'))); - } - - componentWillReceiveProps (nextProps) { - if (this.props.account !== nextProps.account && nextProps.account) { - this.props.dispatch(fetchAccountTimeline(nextProps.account.get('id'))); - } - } - - handleCommentChange (e) { - this.props.dispatch(changeReportComment(e.target.value)); - } - - handleSubmit () { - this.props.dispatch(submitReport()); - this.context.router.replace('/'); - } - - render () { - const { account, comment, intl, statusIds, isSubmitting } = this.props; - - if (!account) { - return null; - } - - return ( - <Column heading={intl.formatMessage(messages.heading)} icon='flag'> - <ColumnBackButtonSlim /> - - <div className='report scrollable'> - <div className='report__target'> - <FormattedMessage id='report.target' defaultMessage='Reporting' /> - <strong>{account.get('acct')}</strong> - </div> - - <div className='scrollable report__statuses'> - <div> - {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)} - </div> - </div> - - <div className='report__textarea-wrapper'> - <textarea - className='report__textarea' - placeholder={intl.formatMessage(messages.placeholder)} - value={comment} - onChange={this.handleCommentChange} - disabled={isSubmitting} - /> - - <div className='report__submit'> - <div className='report__submit-button'><Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} /></div> - </div> - </div> - </div> - </Column> - ); - } - -} - -Report.contextTypes = { - router: PropTypes.object -}; - -Report.propTypes = { - isSubmitting: PropTypes.bool, - account: ImmutablePropTypes.map, - statusIds: ImmutablePropTypes.orderedSet.isRequired, - comment: PropTypes.string.isRequired, - dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default connect(makeMapStateToProps)(injectIntl(Report)); diff --git a/app/assets/javascripts/components/features/status/components/action_bar.jsx b/app/assets/javascripts/components/features/status/components/action_bar.jsx deleted file mode 100644 index 1e0b3f74d..000000000 --- a/app/assets/javascripts/components/features/status/components/action_bar.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import PropTypes from 'prop-types'; -import IconButton from '../../../components/icon_button'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import DropdownMenu from '../../../components/dropdown_menu'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - delete: { id: 'status.delete', defaultMessage: 'Delete' }, - mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, - reply: { id: 'status.reply', defaultMessage: 'Reply' }, - reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, - cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, - favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, - report: { id: 'status.report', defaultMessage: 'Report @{name}' } -}); - -class ActionBar extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleReplyClick = this.handleReplyClick.bind(this); - this.handleReblogClick = this.handleReblogClick.bind(this); - this.handleFavouriteClick = this.handleFavouriteClick.bind(this); - this.handleDeleteClick = this.handleDeleteClick.bind(this); - this.handleMentionClick = this.handleMentionClick.bind(this); - this.handleReport = this.handleReport.bind(this); - } - - handleReplyClick () { - this.props.onReply(this.props.status); - } - - handleReblogClick (e) { - this.props.onReblog(this.props.status, e); - } - - handleFavouriteClick () { - this.props.onFavourite(this.props.status); - } - - handleDeleteClick () { - this.props.onDelete(this.props.status); - } - - handleMentionClick () { - this.props.onMention(this.props.status.get('account'), this.context.router); - } - - handleReport () { - this.props.onReport(this.props.status); - this.context.router.push('/report'); - } - - render () { - const { status, me, intl } = this.props; - - let menu = []; - - if (me === status.getIn(['account', 'id'])) { - menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); - } else { - menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); - menu.push(null); - menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport }); - } - - let reblogIcon = 'retweet'; - if (status.get('visibility') === 'direct') reblogIcon = 'envelope'; - else if (status.get('visibility') === 'private') reblogIcon = 'lock'; - - let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private'); - - return ( - <div className='detailed-status__action-bar'> - <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_id', null) === null ? 'reply' : 'reply-all'} onClick={this.handleReplyClick} /></div> - <div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div> - <div className='detailed-status__button'><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div> - <div className='detailed-status__button'><DropdownMenu size={18} icon='ellipsis-h' items={menu} direction="left" ariaLabel="More" /></div> - </div> - ); - } - -} - -ActionBar.contextTypes = { - router: PropTypes.object -}; - -ActionBar.propTypes = { - status: ImmutablePropTypes.map.isRequired, - onReply: PropTypes.func.isRequired, - onReblog: PropTypes.func.isRequired, - onFavourite: PropTypes.func.isRequired, - onDelete: PropTypes.func.isRequired, - onMention: PropTypes.func.isRequired, - onReport: PropTypes.func, - me: PropTypes.number.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(ActionBar); diff --git a/app/assets/javascripts/components/features/status/components/card.jsx b/app/assets/javascripts/components/features/status/components/card.jsx deleted file mode 100644 index a5ce7f08a..000000000 --- a/app/assets/javascripts/components/features/status/components/card.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; - -const hostStyle = { - display: 'block', - marginTop: '5px', - fontSize: '13px' -}; - -const getHostname = url => { - const parser = document.createElement('a'); - parser.href = url; - return parser.hostname; -}; - -class Card extends React.PureComponent { - - renderLink () { - const { card } = this.props; - - let image = ''; - let provider = card.get('provider_name'); - - if (card.get('image')) { - image = ( - <div className='status-card__image'> - <img src={card.get('image')} alt={card.get('title')} className='status-card__image-image' /> - </div> - ); - } - - if (provider.length < 1) { - provider = getHostname(card.get('url')) - } - - return ( - <a href={card.get('url')} className='status-card' target='_blank' rel='noopener'> - {image} - - <div className='status-card__content'> - <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong> - <p className='status-card__description'>{(card.get('description') || '').substring(0, 50)}</p> - <span className='status-card__host' style={hostStyle}>{provider}</span> - </div> - </a> - ); - } - - renderPhoto () { - const { card } = this.props; - - return ( - <a href={card.get('url')} className='status-card-photo' target='_blank' rel='noopener'> - <img src={card.get('url')} alt={card.get('title')} width={card.get('width')} height={card.get('height')} /> - </a> - ); - } - - renderVideo () { - const { card } = this.props; - const content = { __html: card.get('html') }; - - return ( - <div - className='status-card-video' - dangerouslySetInnerHTML={content} - /> - ); - } - - render () { - const { card } = this.props; - - if (card === null) { - return null; - } - - switch(card.get('type')) { - case 'link': - return this.renderLink(); - case 'photo': - return this.renderPhoto(); - case 'video': - return this.renderVideo(); - case 'rich': - default: - return null; - } - } -} - -Card.propTypes = { - card: ImmutablePropTypes.map -}; - -export default Card; diff --git a/app/assets/javascripts/components/features/status/components/detailed_status.jsx b/app/assets/javascripts/components/features/status/components/detailed_status.jsx deleted file mode 100644 index 0e2a7c17e..000000000 --- a/app/assets/javascripts/components/features/status/components/detailed_status.jsx +++ /dev/null @@ -1,94 +0,0 @@ -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Avatar from '../../../components/avatar'; -import DisplayName from '../../../components/display_name'; -import StatusContent from '../../../components/status_content'; -import MediaGallery from '../../../components/media_gallery'; -import VideoPlayer from '../../../components/video_player'; -import AttachmentList from '../../../components/attachment_list'; -import { Link } from 'react-router'; -import { FormattedDate, FormattedNumber } from 'react-intl'; -import CardContainer from '../containers/card_container'; - -class DetailedStatus extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleAccountClick = this.handleAccountClick.bind(this); - } - - handleAccountClick (e) { - if (e.button === 0) { - e.preventDefault(); - this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); - } - - e.stopPropagation(); - } - - render () { - const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; - - let media = ''; - let applicationLink = ''; - - if (status.get('media_attachments').size > 0) { - if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { - media = <AttachmentList media={status.get('media_attachments')} />; - } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { - media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} onOpenVideo={this.props.onOpenVideo} autoplay />; - } else { - media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />; - } - } else if (status.get('spoiler_text').length === 0) { - media = <CardContainer statusId={status.get('id')} />; - } - - if (status.get('application')) { - applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>; - } - - return ( - <div className='detailed-status'> - <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'> - <div className='detailed-status__display-avatar'><Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} /></div> - <DisplayName account={status.get('account')} /> - </a> - - <StatusContent status={status} /> - - {media} - - <div className='detailed-status__meta'> - <a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'> - <FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' /> - </a>{applicationLink} · <Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'> - <i className='fa fa-retweet' /> - <span className='detailed-status__reblogs'> - <FormattedNumber value={status.get('reblogs_count')} /> - </span> - </Link> · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'> - <i className='fa fa-star' /> - <span className='detailed-status__favorites'> - <FormattedNumber value={status.get('favourites_count')} /> - </span> - </Link> - </div> - </div> - ); - } - -} - -DetailedStatus.contextTypes = { - router: PropTypes.object -}; - -DetailedStatus.propTypes = { - status: ImmutablePropTypes.map.isRequired, - onOpenMedia: PropTypes.func.isRequired, - onOpenVideo: PropTypes.func.isRequired, - autoPlayGif: PropTypes.bool, -}; - -export default DetailedStatus; diff --git a/app/assets/javascripts/components/features/status/containers/card_container.jsx b/app/assets/javascripts/components/features/status/containers/card_container.jsx deleted file mode 100644 index 5c8bfeec2..000000000 --- a/app/assets/javascripts/components/features/status/containers/card_container.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import { connect } from 'react-redux'; -import Card from '../components/card'; - -const mapStateToProps = (state, { statusId }) => ({ - card: state.getIn(['cards', statusId], null) -}); - -export default connect(mapStateToProps)(Card); diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx deleted file mode 100644 index 595df251c..000000000 --- a/app/assets/javascripts/components/features/status/index.jsx +++ /dev/null @@ -1,197 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { fetchStatus } from '../../actions/statuses'; -import Immutable from 'immutable'; -import EmbeddedStatus from '../../components/status'; -import MissingIndicator from '../../components/missing_indicator'; -import DetailedStatus from './components/detailed_status'; -import ActionBar from './components/action_bar'; -import Column from '../ui/components/column'; -import { - favourite, - unfavourite, - reblog, - unreblog -} from '../../actions/interactions'; -import { - replyCompose, - mentionCompose -} from '../../actions/compose'; -import { deleteStatus } from '../../actions/statuses'; -import { initReport } from '../../actions/reports'; -import { - makeGetStatus, - getStatusAncestors, - getStatusDescendants -} from '../../selectors'; -import { ScrollContainer } from 'react-router-scroll'; -import ColumnBackButton from '../../components/column_back_button'; -import StatusContainer from '../../containers/status_container'; -import { openModal } from '../../actions/modal'; -import { isMobile } from '../../is_mobile' -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -const messages = defineMessages({ - deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, - deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' } -}); - -const makeMapStateToProps = () => { - const getStatus = makeGetStatus(); - - const mapStateToProps = (state, props) => ({ - status: getStatus(state, Number(props.params.statusId)), - ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]), - descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]), - me: state.getIn(['meta', 'me']), - boostModal: state.getIn(['meta', 'boost_modal']), - autoPlayGif: state.getIn(['meta', 'auto_play_gif']) - }); - - return mapStateToProps; -}; - -class Status extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleFavouriteClick = this.handleFavouriteClick.bind(this); - this.handleReplyClick = this.handleReplyClick.bind(this); - this.handleModalReblog = this.handleModalReblog.bind(this); - this.handleReblogClick = this.handleReblogClick.bind(this); - this.handleDeleteClick = this.handleDeleteClick.bind(this); - this.handleMentionClick = this.handleMentionClick.bind(this); - this.handleOpenMedia = this.handleOpenMedia.bind(this); - this.handleOpenVideo = this.handleOpenVideo.bind(this); - this.handleReport = this.handleReport.bind(this); - } - - componentWillMount () { - this.props.dispatch(fetchStatus(Number(this.props.params.statusId))); - } - - componentWillReceiveProps (nextProps) { - if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this.props.dispatch(fetchStatus(Number(nextProps.params.statusId))); - } - } - - handleFavouriteClick (status) { - if (status.get('favourited')) { - this.props.dispatch(unfavourite(status)); - } else { - this.props.dispatch(favourite(status)); - } - } - - handleReplyClick (status) { - this.props.dispatch(replyCompose(status, this.context.router)); - } - - handleModalReblog (status) { - this.props.dispatch(reblog(status)); - } - - handleReblogClick (status, e) { - if (status.get('reblogged')) { - this.props.dispatch(unreblog(status)); - } else { - if (e.shiftKey || !this.props.boostModal) { - this.handleModalReblog(status); - } else { - this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog })); - } - } - } - - handleDeleteClick (status) { - const { dispatch, intl } = this.props; - - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'))) - })); - } - - handleMentionClick (account, router) { - this.props.dispatch(mentionCompose(account, router)); - } - - handleOpenMedia (media, index) { - this.props.dispatch(openModal('MEDIA', { media, index })); - } - - handleOpenVideo (media, time) { - this.props.dispatch(openModal('VIDEO', { media, time })); - } - - handleReport (status) { - this.props.dispatch(initReport(status.get('account'), status)); - } - - renderChildren (list) { - return list.map(id => <StatusContainer key={id} id={id} />); - } - - render () { - let ancestors, descendants; - const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props; - - if (status === null) { - return ( - <Column> - <ColumnBackButton /> - <MissingIndicator /> - </Column> - ); - } - - const account = status.get('account'); - - if (ancestorsIds && ancestorsIds.size > 0) { - ancestors = <div>{this.renderChildren(ancestorsIds)}</div>; - } - - if (descendantsIds && descendantsIds.size > 0) { - descendants = <div>{this.renderChildren(descendantsIds)}</div>; - } - - return ( - <Column> - <ColumnBackButton /> - - <ScrollContainer scrollKey='thread'> - <div className='scrollable detailed-status__wrapper'> - {ancestors} - - <DetailedStatus status={status} autoPlayGif={autoPlayGif} me={me} onOpenVideo={this.handleOpenVideo} onOpenMedia={this.handleOpenMedia} /> - <ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} onReport={this.handleReport} /> - - {descendants} - </div> - </ScrollContainer> - </Column> - ); - } - -} - -Status.contextTypes = { - router: PropTypes.object -}; - -Status.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - status: ImmutablePropTypes.map, - ancestorsIds: ImmutablePropTypes.list, - descendantsIds: ImmutablePropTypes.list, - me: PropTypes.number, - boostModal: PropTypes.bool, - autoPlayGif: PropTypes.bool, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(connect(makeMapStateToProps)(Status)); diff --git a/app/assets/javascripts/components/features/ui/components/boost_modal.jsx b/app/assets/javascripts/components/features/ui/components/boost_modal.jsx deleted file mode 100644 index 3bd82ceee..000000000 --- a/app/assets/javascripts/components/features/ui/components/boost_modal.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import IconButton from '../../../components/icon_button'; -import Button from '../../../components/button'; -import StatusContent from '../../../components/status_content'; -import Avatar from '../../../components/avatar'; -import RelativeTimestamp from '../../../components/relative_timestamp'; -import DisplayName from '../../../components/display_name'; - -const messages = defineMessages({ - reblog: { id: 'status.reblog', defaultMessage: 'Boost' } -}); - -class BoostModal extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleReblog = this.handleReblog.bind(this); - this.handleAccountClick = this.handleAccountClick.bind(this); - } - - handleReblog() { - this.props.onReblog(this.props.status); - this.props.onClose(); - } - - handleAccountClick (e) { - if (e.button === 0) { - e.preventDefault(); - this.props.onClose(); - this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); - } - } - - render () { - const { status, intl, onClose } = this.props; - - return ( - <div className='modal-root__modal boost-modal'> - <div className='boost-modal__container'> - <div className='status light'> - <div className='boost-modal__status-header'> - <div className='boost-modal__status-time'> - <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a> - </div> - - <a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'> - <div className='status__avatar'> - <Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} /> - </div> - - <DisplayName account={status.get('account')} /> - </a> - </div> - - <StatusContent status={status} /> - </div> - </div> - - <div className='boost-modal__action-bar'> - <div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <i className='fa fa-retweet' /></span> }} /></div> - <Button text={intl.formatMessage(messages.reblog)} onClick={this.handleReblog} /> - </div> - </div> - ); - } - -} - -BoostModal.contextTypes = { - router: PropTypes.object -}; - -BoostModal.propTypes = { - status: ImmutablePropTypes.map.isRequired, - onReblog: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(BoostModal); diff --git a/app/assets/javascripts/components/features/ui/components/column.jsx b/app/assets/javascripts/components/features/ui/components/column.jsx deleted file mode 100644 index aa09d0fd2..000000000 --- a/app/assets/javascripts/components/features/ui/components/column.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import ColumnHeader from './column_header'; -import PropTypes from 'prop-types'; - -const easingOutQuint = (x, t, b, c, d) => c*((t=t/d-1)*t*t*t*t + 1) + b; - -const scrollTop = (node) => { - const startTime = Date.now(); - const offset = node.scrollTop; - const targetY = -offset; - const duration = 1000; - let interrupt = false; - - const step = () => { - const elapsed = Date.now() - startTime; - const percentage = elapsed / duration; - - if (percentage > 1 || interrupt) { - return; - } - - node.scrollTop = easingOutQuint(0, elapsed, offset, targetY, duration); - requestAnimationFrame(step); - }; - - step(); - - return () => { - interrupt = true; - }; -}; - -class Column extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleHeaderClick = this.handleHeaderClick.bind(this); - this.handleWheel = this.handleWheel.bind(this); - } - - handleHeaderClick () { - const scrollable = ReactDOM.findDOMNode(this).querySelector('.scrollable'); - if (!scrollable) { - return; - } - this._interruptScrollAnimation = scrollTop(scrollable); - } - - handleWheel () { - if (typeof this._interruptScrollAnimation !== 'undefined') { - this._interruptScrollAnimation(); - } - } - - render () { - const { heading, icon, children, active, hideHeadingOnMobile } = this.props; - - let columnHeaderId = null - let header = ''; - - if (heading) { - columnHeaderId = heading.replace(/ /g, '-') - header = <ColumnHeader icon={icon} active={active} type={heading} onClick={this.handleHeaderClick} hideOnMobile={hideHeadingOnMobile} columnHeaderId={columnHeaderId}/>; - } - return ( - <div role='region' aria-labelledby={columnHeaderId} className='column' onWheel={this.handleWheel}> - {header} - {children} - </div> - ); - } - -} - -Column.propTypes = { - heading: PropTypes.string, - icon: PropTypes.string, - children: PropTypes.node, - active: PropTypes.bool, - hideHeadingOnMobile: PropTypes.bool -}; - -export default Column; diff --git a/app/assets/javascripts/components/features/ui/components/column_header.jsx b/app/assets/javascripts/components/features/ui/components/column_header.jsx deleted file mode 100644 index 7ccd72e0b..000000000 --- a/app/assets/javascripts/components/features/ui/components/column_header.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import PropTypes from 'prop-types' - -class ColumnHeader extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - } - - handleClick () { - this.props.onClick(); - } - - render () { - const { type, active, hideOnMobile, columnHeaderId } = this.props; - - let icon = ''; - - if (this.props.icon) { - icon = <i className={`fa fa-fw fa-${this.props.icon} column-header__icon`} />; - } - - return ( - <div role='button heading' tabIndex='0' className={`column-header ${active ? 'active' : ''} ${hideOnMobile ? 'hidden-on-mobile' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}> - {icon} - {type} - </div> - ); - } - -} - -ColumnHeader.propTypes = { - icon: PropTypes.string, - type: PropTypes.string, - active: PropTypes.bool, - onClick: PropTypes.func, - hideOnMobile: PropTypes.bool, - columnHeaderId: PropTypes.string -}; - -export default ColumnHeader; diff --git a/app/assets/javascripts/components/features/ui/components/column_link.jsx b/app/assets/javascripts/components/features/ui/components/column_link.jsx deleted file mode 100644 index 820e4246a..000000000 --- a/app/assets/javascripts/components/features/ui/components/column_link.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from 'prop-types'; -import { Link } from 'react-router'; - -const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => { - if (href) { - return ( - <a href={href} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}> - <i className={`fa fa-fw fa-${icon} column-link__icon`} /> - {text} - </a> - ); - } else { - return ( - <Link to={to} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`}> - <i className={`fa fa-fw fa-${icon} column-link__icon`} /> - {text} - </Link> - ); - } -}; - -ColumnLink.propTypes = { - icon: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - to: PropTypes.string, - href: PropTypes.string, - method: PropTypes.string, - hideOnMobile: PropTypes.bool -}; - -export default ColumnLink; diff --git a/app/assets/javascripts/components/features/ui/components/column_subheading.jsx b/app/assets/javascripts/components/features/ui/components/column_subheading.jsx deleted file mode 100644 index 061c8be6c..000000000 --- a/app/assets/javascripts/components/features/ui/components/column_subheading.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import PropTypes from 'prop-types'; - -const ColumnSubheading = ({ text }) => { - return ( - <div className='column-subheading'> - {text} - </div> - ); - }; - -ColumnSubheading.propTypes = { - text: PropTypes.string.isRequired, -}; - -export default ColumnSubheading; diff --git a/app/assets/javascripts/components/features/ui/components/columns_area.jsx b/app/assets/javascripts/components/features/ui/components/columns_area.jsx deleted file mode 100644 index 360a759ae..000000000 --- a/app/assets/javascripts/components/features/ui/components/columns_area.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import PropTypes from 'prop-types'; - -class ColumnsArea extends React.PureComponent { - - render () { - return ( - <div className='columns-area'> - {this.props.children} - </div> - ); - } - -} - -ColumnsArea.propTypes = { - children: PropTypes.node -}; - -export default ColumnsArea; diff --git a/app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx b/app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx deleted file mode 100644 index 914c12f82..000000000 --- a/app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Button from '../../../components/button'; - -class ConfirmationModal extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - this.handleCancel = this.handleCancel.bind(this); - } - - handleClick () { - this.props.onClose(); - this.props.onConfirm(); - } - - handleCancel (e) { - e.preventDefault(); - this.props.onClose(); - } - - render () { - const { intl, message, confirm, onConfirm, onClose } = this.props; - - return ( - <div className='modal-root__modal confirmation-modal'> - <div className='confirmation-modal__container'> - {message} - </div> - - <div className='confirmation-modal__action-bar'> - <div><a href='#' onClick={this.handleCancel}><FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' /></a></div> - <Button text={confirm} onClick={this.handleClick} /> - </div> - </div> - ); - } - -} - -ConfirmationModal.propTypes = { - message: PropTypes.node.isRequired, - confirm: PropTypes.string.isRequired, - onClose: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(ConfirmationModal); diff --git a/app/assets/javascripts/components/features/ui/components/media_modal.jsx b/app/assets/javascripts/components/features/ui/components/media_modal.jsx deleted file mode 100644 index 02a577500..000000000 --- a/app/assets/javascripts/components/features/ui/components/media_modal.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import LoadingIndicator from '../../../components/loading_indicator'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import ExtendedVideoPlayer from '../../../components/extended_video_player'; -import ImageLoader from 'react-imageloader'; -import { defineMessages, injectIntl } from 'react-intl'; -import IconButton from '../../../components/icon_button'; - -const messages = defineMessages({ - close: { id: 'lightbox.close', defaultMessage: 'Close' } -}); - -class MediaModal extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - index: null - }; - this.handleNextClick = this.handleNextClick.bind(this); - this.handlePrevClick = this.handlePrevClick.bind(this); - this.handleKeyUp = this.handleKeyUp.bind(this); - } - - handleNextClick () { - this.setState({ index: (this.getIndex() + 1) % this.props.media.size}); - } - - handlePrevClick () { - this.setState({ index: (this.getIndex() - 1) % this.props.media.size}); - } - - handleKeyUp (e) { - switch(e.key) { - case 'ArrowLeft': - this.handlePrevClick(); - break; - case 'ArrowRight': - this.handleNextClick(); - break; - } - } - - componentDidMount () { - window.addEventListener('keyup', this.handleKeyUp, false); - } - - componentWillUnmount () { - window.removeEventListener('keyup', this.handleKeyUp); - } - - getIndex () { - return this.state.index !== null ? this.state.index : this.props.index; - } - - render () { - const { media, intl, onClose } = this.props; - - const index = this.getIndex(); - const attachment = media.get(index); - const url = attachment.get('url'); - - let leftNav, rightNav, content; - - leftNav = rightNav = content = ''; - - if (media.size > 1) { - leftNav = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>; - rightNav = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--right' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>; - } - - if (attachment.get('type') === 'image') { - content = <ImageLoader src={url} imgProps={{ style: { display: 'block' } }} />; - } else if (attachment.get('type') === 'gifv') { - content = <ExtendedVideoPlayer src={url} muted={true} controls={false} />; - } - - return ( - <div className='modal-root__modal media-modal'> - {leftNav} - - <div className='media-modal__content'> - <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} /> - {content} - </div> - - {rightNav} - </div> - ); - } - -} - -MediaModal.propTypes = { - media: ImmutablePropTypes.list.isRequired, - index: PropTypes.number.isRequired, - onClose: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(MediaModal); diff --git a/app/assets/javascripts/components/features/ui/components/modal_root.jsx b/app/assets/javascripts/components/features/ui/components/modal_root.jsx deleted file mode 100644 index 23057715c..000000000 --- a/app/assets/javascripts/components/features/ui/components/modal_root.jsx +++ /dev/null @@ -1,92 +0,0 @@ -import PropTypes from 'prop-types'; -import MediaModal from './media_modal'; -import OnboardingModal from './onboarding_modal'; -import VideoModal from './video_modal'; -import BoostModal from './boost_modal'; -import ConfirmationModal from './confirmation_modal'; -import { TransitionMotion, spring } from 'react-motion'; - -const MODAL_COMPONENTS = { - 'MEDIA': MediaModal, - 'ONBOARDING': OnboardingModal, - 'VIDEO': VideoModal, - 'BOOST': BoostModal, - 'CONFIRM': ConfirmationModal -}; - -class ModalRoot extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleKeyUp = this.handleKeyUp.bind(this); - } - - handleKeyUp (e) { - if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) - && !!this.props.type) { - this.props.onClose(); - } - } - - componentDidMount () { - window.addEventListener('keyup', this.handleKeyUp, false); - } - - componentWillUnmount () { - window.removeEventListener('keyup', this.handleKeyUp); - } - - willEnter () { - return { opacity: 0, scale: 0.98 }; - } - - willLeave () { - return { opacity: spring(0), scale: spring(0.98) }; - } - - render () { - const { type, props, onClose } = this.props; - const items = []; - - if (!!type) { - items.push({ - key: type, - data: { type, props }, - style: { opacity: spring(1), scale: spring(1, { stiffness: 120, damping: 14 }) } - }); - } - - return ( - <TransitionMotion - styles={items} - willEnter={this.willEnter} - willLeave={this.willLeave}> - {interpolatedStyles => - <div className='modal-root'> - {interpolatedStyles.map(({ key, data: { type, props }, style }) => { - const SpecificComponent = MODAL_COMPONENTS[type]; - - return ( - <div key={key}> - <div role='presentation' className='modal-root__overlay' style={{ opacity: style.opacity }} onClick={onClose} /> - <div className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}> - <SpecificComponent {...props} onClose={onClose} /> - </div> - </div> - ); - })} - </div> - } - </TransitionMotion> - ); - } - -} - -ModalRoot.propTypes = { - type: PropTypes.string, - props: PropTypes.object, - onClose: PropTypes.func.isRequired -}; - -export default ModalRoot; diff --git a/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx b/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx deleted file mode 100644 index 4c2c55f93..000000000 --- a/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx +++ /dev/null @@ -1,263 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Permalink from '../../../components/permalink'; -import { TransitionMotion, spring } from 'react-motion'; -import ComposeForm from '../../compose/components/compose_form'; -import Search from '../../compose/components/search'; -import NavigationBar from '../../compose/components/navigation_bar'; -import ColumnHeader from './column_header'; -import Immutable from 'immutable'; - -const messages = defineMessages({ - home_title: { id: 'column.home', defaultMessage: 'Home' }, - notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' }, - local_title: { id: 'column.community', defaultMessage: 'Local timeline' }, - federated_title: { id: 'column.public', defaultMessage: 'Federated timeline' } -}); - -const PageOne = ({ acct, domain }) => ( - <div className='onboarding-modal__page onboarding-modal__page-one'> - <div style={{ flex: '0 0 auto' }}> - <div className='onboarding-modal__page-one__elephant-friend' /> - </div> - - <div> - <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to Mastodon!' /></h1> - <p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' /></p> - <p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>{acct}@{domain}</strong> }}/></p> - </div> - </div> -); - -PageOne.propTypes = { - acct: PropTypes.string.isRequired, - domain: PropTypes.string.isRequired -}; - -const PageTwo = ({ me }) => ( - <div className='onboarding-modal__page onboarding-modal__page-two'> - <div className='figure non-interactive'> - <div className='pseudo-drawer'> - <NavigationBar account={me} /> - </div> - <ComposeForm - text='Awoo! #introductions' - suggestions={Immutable.List()} - mentionedDomains={[]} - spoiler={false} - onChange={() => {}} - onSubmit={() => {}} - onPaste={() => {}} - onPickEmoji={() => {}} - onChangeSpoilerText={() => {}} - onClearSuggestions={() => {}} - onFetchSuggestions={() => {}} - onSuggestionSelected={() => {}} - /> - </div> - - <p><FormattedMessage id='onboarding.page_two.compose' defaultMessage='Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.' /></p> - </div> -); - -PageTwo.propTypes = { - me: ImmutablePropTypes.map.isRequired, -}; - -const PageThree = ({ me, domain }) => ( - <div className='onboarding-modal__page onboarding-modal__page-three'> - <div className='figure non-interactive'> - <Search - value='' - onChange={() => {}} - onSubmit={() => {}} - onClear={() => {}} - onShow={() => {}} - /> - - <div className='pseudo-drawer'> - <NavigationBar account={me} /> - </div> - </div> - - <p><FormattedMessage id='onboarding.page_three.search' defaultMessage='Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.' values={{ illustration: <Permalink to='/timelines/tag/illustration' href='/tags/illustration'>#illustration</Permalink>, introductions: <Permalink to='/timelines/tag/introductions' href='/tags/introductions'>#introductions</Permalink> }}/></p> - <p><FormattedMessage id='onboarding.page_three.profile' defaultMessage='Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.' /></p> - </div> -); - -PageThree.propTypes = { - me: ImmutablePropTypes.map.isRequired, - domain: PropTypes.string.isRequired -}; - -const PageFour = ({ domain, intl }) => ( - <div className='onboarding-modal__page onboarding-modal__page-four'> - <div className='onboarding-modal__page-four__columns'> - <div className='row'> - <div> - <div className='figure non-interactive'><ColumnHeader icon='home' type={intl.formatMessage(messages.home_title)} /></div> - <p><FormattedMessage id='onboarding.page_four.home' defaultMessage='The home timeline shows posts from people you follow.'/></p> - </div> - - <div> - <div className='figure non-interactive'><ColumnHeader icon='bell' type={intl.formatMessage(messages.notifications_title)} /></div> - <p><FormattedMessage id='onboarding.page_four.notifications' defaultMessage='The notifications column shows when someone interacts with you.' /></p> - </div> - </div> - - <div className='row'> - <div> - <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='users' type={intl.formatMessage(messages.local_title)} /></div> - </div> - - <div> - <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='globe' type={intl.formatMessage(messages.federated_title)} /></div> - </div> - </div> - - <p><FormattedMessage id='onboarding.page_five.public_timelines' defaultMessage='The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.' values={{ domain }} /></p> - </div> - </div> -); - -PageFour.propTypes = { - domain: PropTypes.string.isRequired, - intl: PropTypes.object.isRequired -}; - -const PageSix = ({ admin, domain }) => { - let adminSection = ''; - - if (admin) { - adminSection = ( - <p> - <FormattedMessage id='onboarding.page_six.admin' defaultMessage="Your instance's admin is {admin}." values={{ admin: <Permalink href={admin.get('url')} to={`/accounts/${admin.get('id')}`}>@{admin.get('acct')}</Permalink> }} /> - <br /> - <FormattedMessage id='onboarding.page_six.read_guidelines' defaultMessage="Please read {domain}'s {guidelines}!" values={{domain, guidelines: <a href='/about/more' target='_blank'><FormattedMessage id='onboarding.page_six.guidelines' defaultMessage='community guidelines' /></a> }}/> - </p> - ); - } - - return ( - <div className='onboarding-modal__page onboarding-modal__page-six'> - <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1> - {adminSection} - <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p> - <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p> - <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p> - </div> - ); -}; - -PageSix.propTypes = { - admin: ImmutablePropTypes.map, - domain: PropTypes.string.isRequired -}; - -const mapStateToProps = state => ({ - me: state.getIn(['accounts', state.getIn(['meta', 'me'])]), - admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]), - domain: state.getIn(['meta', 'domain']) -}); - -class OnboardingModal extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - currentIndex: 0 - }; - this.handleSkip = this.handleSkip.bind(this); - this.handleDot = this.handleDot.bind(this); - this.handleNext = this.handleNext.bind(this); - } - - handleSkip (e) { - e.preventDefault(); - this.props.onClose(); - } - - handleDot (i, e) { - e.preventDefault(); - this.setState({ currentIndex: i }); - } - - handleNext (maxNum, e) { - e.preventDefault(); - - if (this.state.currentIndex < maxNum - 1) { - this.setState({ currentIndex: this.state.currentIndex + 1 }); - } else { - this.props.onClose(); - } - } - - render () { - const { me, admin, domain, intl } = this.props; - - const pages = [ - <PageOne acct={me.get('acct')} domain={domain} />, - <PageTwo me={me} />, - <PageThree me={me} domain={domain} />, - <PageFour domain={domain} intl={intl} />, - <PageSix admin={admin} domain={domain} /> - ]; - - const { currentIndex } = this.state; - const hasMore = currentIndex < pages.length - 1; - - let nextOrDoneBtn; - - if(hasMore) { - nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__next'><FormattedMessage id='onboarding.next' defaultMessage='Next' /></a>; - } else { - nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__done'><FormattedMessage id='onboarding.done' defaultMessage='Done' /></a>; - } - - const styles = pages.map((page, i) => ({ - key: `page-${i}`, - style: { opacity: spring(i === currentIndex ? 1 : 0) } - })); - - return ( - <div className='modal-root__modal onboarding-modal'> - <TransitionMotion styles={styles}> - {interpolatedStyles => - <div className='onboarding-modal__pager'> - {pages.map((page, i) => - <div key={`page-${i}`} style={{ opacity: interpolatedStyles[i].style.opacity, pointerEvents: i === currentIndex ? 'auto' : 'none' }}>{page}</div> - )} - </div> - } - </TransitionMotion> - - <div className='onboarding-modal__paginator'> - <div> - <a href='#' className='onboarding-modal__skip' onClick={this.handleSkip}><FormattedMessage id='onboarding.skip' defaultMessage='Skip' /></a> - </div> - - <div className='onboarding-modal__dots'> - {pages.map((_, i) => <div key={i} onClick={this.handleDot.bind(null, i)} className={`onboarding-modal__dot ${i === currentIndex ? 'active' : ''}`} />)} - </div> - - <div> - {nextOrDoneBtn} - </div> - </div> - </div> - ); - } - -} - -OnboardingModal.propTypes = { - onClose: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - me: ImmutablePropTypes.map.isRequired, - domain: PropTypes.string.isRequired, - admin: ImmutablePropTypes.map -} - -export default connect(mapStateToProps)(injectIntl(OnboardingModal)); diff --git a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx deleted file mode 100644 index 316b4bf7d..000000000 --- a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Link } from 'react-router'; -import { FormattedMessage } from 'react-intl'; - -class TabsBar extends React.Component { - - render () { - return ( - <div className='tabs-bar'> - <Link className='tabs-bar__link primary' activeClassName='active' to='/statuses/new'><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></Link> - <Link className='tabs-bar__link primary' activeClassName='active' to='/timelines/home'><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></Link> - <Link className='tabs-bar__link primary' activeClassName='active' to='/notifications'><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></Link> - - <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public/local'><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></Link> - <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public'><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></Link> - - <Link className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started'><i className='fa fa-fw fa-asterisk' /></Link> - </div> - ); - } - -} - -export default TabsBar; diff --git a/app/assets/javascripts/components/features/ui/components/upload_area.jsx b/app/assets/javascripts/components/features/ui/components/upload_area.jsx deleted file mode 100644 index 3a933398b..000000000 --- a/app/assets/javascripts/components/features/ui/components/upload_area.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import PropTypes from 'prop-types'; -import { Motion, spring } from 'react-motion'; -import { FormattedMessage } from 'react-intl'; - -class UploadArea extends React.PureComponent { - - constructor (props, context) { - super(props, context); - - this.handleKeyUp = this.handleKeyUp.bind(this); - } - - handleKeyUp (e) { - e.preventDefault(); - e.stopPropagation(); - - const keyCode = e.keyCode - if (this.props.active) { - switch(keyCode) { - case 27: - this.props.onClose(); - break; - } - } - } - - componentDidMount () { - window.addEventListener('keyup', this.handleKeyUp, false); - } - - componentWillUnmount () { - window.removeEventListener('keyup', this.handleKeyUp); - } - - render () { - const { active } = this.props; - - return ( - <Motion defaultStyle={{ backgroundOpacity: 0, backgroundScale: 0.95 }} style={{ backgroundOpacity: spring(active ? 1 : 0, { stiffness: 150, damping: 15 }), backgroundScale: spring(active ? 1 : 0.95, { stiffness: 200, damping: 3 }) }}> - {({ backgroundOpacity, backgroundScale }) => - <div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}> - <div className='upload-area__drop'> - <div className='upload-area__background' style={{ transform: `translateZ(0) scale(${backgroundScale})` }} /> - <div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div> - </div> - </div> - } - </Motion> - ); - } - -} - -UploadArea.propTypes = { - active: PropTypes.bool, - onClose: PropTypes.func -}; - -export default UploadArea; diff --git a/app/assets/javascripts/components/features/ui/components/video_modal.jsx b/app/assets/javascripts/components/features/ui/components/video_modal.jsx deleted file mode 100644 index d98b42882..000000000 --- a/app/assets/javascripts/components/features/ui/components/video_modal.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import LoadingIndicator from '../../../components/loading_indicator'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import ExtendedVideoPlayer from '../../../components/extended_video_player'; -import { defineMessages, injectIntl } from 'react-intl'; -import IconButton from '../../../components/icon_button'; - -const messages = defineMessages({ - close: { id: 'lightbox.close', defaultMessage: 'Close' } -}); - -class VideoModal extends React.PureComponent { - - render () { - const { media, intl, time, onClose } = this.props; - - const url = media.get('url'); - - return ( - <div className='modal-root__modal media-modal'> - <div> - <div className='media-modal__close'><IconButton title={intl.formatMessage(messages.close)} icon='times' overlay onClick={onClose} /></div> - <ExtendedVideoPlayer src={url} muted={false} controls={true} time={time} /> - </div> - </div> - ); - } - -} - -VideoModal.propTypes = { - media: ImmutablePropTypes.map.isRequired, - time: PropTypes.number, - onClose: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(VideoModal); diff --git a/app/assets/javascripts/components/features/ui/containers/loading_bar_container.jsx b/app/assets/javascripts/components/features/ui/containers/loading_bar_container.jsx deleted file mode 100644 index 6c4e73e38..000000000 --- a/app/assets/javascripts/components/features/ui/containers/loading_bar_container.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import { connect } from 'react-redux'; -import LoadingBar from 'react-redux-loading-bar'; - -const mapStateToProps = (state) => ({ - loading: state.get('loadingBar') -}); - -export default connect(mapStateToProps)(LoadingBar.WrappedComponent); diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx deleted file mode 100644 index 26d77818c..000000000 --- a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import { connect } from 'react-redux'; -import { closeModal } from '../../../actions/modal'; -import ModalRoot from '../components/modal_root'; - -const mapStateToProps = state => ({ - type: state.get('modal').modalType, - props: state.get('modal').modalProps -}); - -const mapDispatchToProps = dispatch => ({ - onClose () { - dispatch(closeModal()); - }, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(ModalRoot); diff --git a/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx b/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx deleted file mode 100644 index 529ebf6c8..000000000 --- a/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux'; -import { NotificationStack } from 'react-notification'; -import { - dismissAlert, - clearAlerts -} from '../../../actions/alerts'; -import { getAlerts } from '../../../selectors'; - -const mapStateToProps = (state, props) => ({ - notifications: getAlerts(state) -}); - -const mapDispatchToProps = (dispatch) => { - return { - onDismiss: alert => { - dispatch(dismissAlert(alert)); - } - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(NotificationStack); diff --git a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx deleted file mode 100644 index 1599000b5..000000000 --- a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import { connect } from 'react-redux'; -import StatusList from '../../../components/status_list'; -import { expandTimeline, scrollTopTimeline } from '../../../actions/timelines'; -import Immutable from 'immutable'; -import { createSelector } from 'reselect'; -import { debounce } from 'react-decoration'; - -const makeGetStatusIds = () => createSelector([ - (state, { type }) => state.getIn(['settings', type], Immutable.Map()), - (state, { type }) => state.getIn(['timelines', type, 'items'], Immutable.List()), - (state) => state.get('statuses'), - (state) => state.getIn(['meta', 'me']) -], (columnSettings, statusIds, statuses, me) => statusIds.filter(id => { - const statusForId = statuses.get(id); - let showStatus = true; - - if (columnSettings.getIn(['shows', 'reblog']) === false) { - showStatus = showStatus && statusForId.get('reblog') === null; - } - - if (columnSettings.getIn(['shows', 'reply']) === false) { - showStatus = showStatus && (statusForId.get('in_reply_to_id') === null || statusForId.get('in_reply_to_account_id') === me); - } - - if (columnSettings.getIn(['regex', 'body'], '').trim().length > 0) { - try { - if (showStatus) { - const regex = new RegExp(columnSettings.getIn(['regex', 'body']).trim(), 'i'); - showStatus = !regex.test(statusForId.get('reblog') ? statuses.getIn([statusForId.get('reblog'), 'unescaped_content']) : statusForId.get('unescaped_content')); - } - } catch(e) { - // Bad regex, don't affect filters - } - } - - return showStatus; -})); - -const makeMapStateToProps = () => { - const getStatusIds = makeGetStatusIds(); - - const mapStateToProps = (state, props) => ({ - scrollKey: props.scrollKey, - shouldUpdateScroll: props.shouldUpdateScroll, - statusIds: getStatusIds(state, props), - isLoading: state.getIn(['timelines', props.type, 'isLoading'], true), - isUnread: state.getIn(['timelines', props.type, 'unread']) > 0, - hasMore: !!state.getIn(['timelines', props.type, 'next']) - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = (dispatch, { type, id }) => ({ - - @debounce(300, true) - onScrollToBottom () { - dispatch(scrollTopTimeline(type, false)); - dispatch(expandTimeline(type, id)); - }, - - @debounce(100) - onScrollToTop () { - dispatch(scrollTopTimeline(type, true)); - }, - - @debounce(100) - onScroll () { - dispatch(scrollTopTimeline(type, false)); - } - -}); - -export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList); diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx deleted file mode 100644 index b402639ce..000000000 --- a/app/assets/javascripts/components/features/ui/index.jsx +++ /dev/null @@ -1,166 +0,0 @@ -import ColumnsArea from './components/columns_area'; -import NotificationsContainer from './containers/notifications_container'; -import PropTypes from 'prop-types'; -import LoadingBarContainer from './containers/loading_bar_container'; -import HomeTimeline from '../home_timeline'; -import Compose from '../compose'; -import TabsBar from './components/tabs_bar'; -import ModalContainer from './containers/modal_container'; -import Notifications from '../notifications'; -import { connect } from 'react-redux'; -import { isMobile } from '../../is_mobile'; -import { debounce } from 'react-decoration'; -import { uploadCompose } from '../../actions/compose'; -import { refreshTimeline } from '../../actions/timelines'; -import { refreshNotifications } from '../../actions/notifications'; -import UploadArea from './components/upload_area'; - -class UI extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - width: window.innerWidth, - draggingOver: false - }; - this.handleResize = this.handleResize.bind(this); - this.handleDragEnter = this.handleDragEnter.bind(this); - this.handleDragOver = this.handleDragOver.bind(this); - this.handleDrop = this.handleDrop.bind(this); - this.handleDragLeave = this.handleDragLeave.bind(this); - this.handleDragEnd = this.handleDragLeave.bind(this) - this.closeUploadModal = this.closeUploadModal.bind(this) - this.setRef = this.setRef.bind(this); - } - - @debounce(500) - handleResize () { - this.setState({ width: window.innerWidth }); - } - - handleDragEnter (e) { - e.preventDefault(); - - if (!this.dragTargets) { - this.dragTargets = []; - } - - if (this.dragTargets.indexOf(e.target) === -1) { - this.dragTargets.push(e.target); - } - - if (e.dataTransfer && e.dataTransfer.types.includes('Files')) { - this.setState({ draggingOver: true }); - } - } - - handleDragOver (e) { - e.preventDefault(); - e.stopPropagation(); - - try { - e.dataTransfer.dropEffect = 'copy'; - } catch (err) { - - } - - return false; - } - - handleDrop (e) { - e.preventDefault(); - - this.setState({ draggingOver: false }); - - if (e.dataTransfer && e.dataTransfer.files.length === 1) { - this.props.dispatch(uploadCompose(e.dataTransfer.files)); - } - } - - handleDragLeave (e) { - e.preventDefault(); - e.stopPropagation(); - - this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el)); - - if (this.dragTargets.length > 0) { - return; - } - - this.setState({ draggingOver: false }); - } - - closeUploadModal() { - this.setState({ draggingOver: false }); - } - - componentWillMount () { - window.addEventListener('resize', this.handleResize, { passive: true }); - document.addEventListener('dragenter', this.handleDragEnter, false); - document.addEventListener('dragover', this.handleDragOver, false); - document.addEventListener('drop', this.handleDrop, false); - document.addEventListener('dragleave', this.handleDragLeave, false); - document.addEventListener('dragend', this.handleDragEnd, false); - - this.props.dispatch(refreshTimeline('home')); - this.props.dispatch(refreshNotifications()); - } - - componentWillUnmount () { - window.removeEventListener('resize', this.handleResize); - document.removeEventListener('dragenter', this.handleDragEnter); - document.removeEventListener('dragover', this.handleDragOver); - document.removeEventListener('drop', this.handleDrop); - document.removeEventListener('dragleave', this.handleDragLeave); - document.removeEventListener('dragend', this.handleDragEnd); - } - - setRef (c) { - this.node = c; - } - - render () { - const { width, draggingOver } = this.state; - const { children } = this.props; - - let mountedColumns; - - if (isMobile(width)) { - mountedColumns = ( - <ColumnsArea> - {children} - </ColumnsArea> - ); - } else { - mountedColumns = ( - <ColumnsArea> - <Compose withHeader={true} /> - <HomeTimeline shouldUpdateScroll={() => false} /> - <Notifications shouldUpdateScroll={() => false} /> - <div style={{display: 'flex', flex: '1 1 auto', position: 'relative'}}>{children}</div> - </ColumnsArea> - ); - } - - return ( - <div className='ui' ref={this.setRef}> - <TabsBar /> - - {mountedColumns} - - <NotificationsContainer /> - <LoadingBarContainer className="loading-bar" /> - <ModalContainer /> - <UploadArea active={draggingOver} onClose={this.closeUploadModal} /> - </div> - ); - } - -} - -UI.propTypes = { - dispatch: PropTypes.func.isRequired, - children: PropTypes.node -}; - -export default connect()(UI); diff --git a/app/assets/javascripts/components/is_mobile.jsx b/app/assets/javascripts/components/is_mobile.jsx deleted file mode 100644 index 992e63727..000000000 --- a/app/assets/javascripts/components/is_mobile.jsx +++ /dev/null @@ -1,11 +0,0 @@ -const LAYOUT_BREAKPOINT = 1024; - -export function isMobile(width) { - return width <= LAYOUT_BREAKPOINT; -}; - -const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; - -export function isIOS() { - return iOS; -}; diff --git a/app/assets/javascripts/components/link_header.jsx b/app/assets/javascripts/components/link_header.jsx deleted file mode 100644 index b872dc24a..000000000 --- a/app/assets/javascripts/components/link_header.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import Link from 'http-link-header'; -import querystring from 'querystring'; - -Link.parseAttrs = (link, parts) => { - let match = null - let attr = '' - let value = '' - let attrs = '' - - let uriAttrs = /<(.*)>;\s*(.*)/gi.exec(parts) - - if(uriAttrs) { - attrs = uriAttrs[2] - link = Link.parseParams(link, uriAttrs[1]) - } - - while(match = Link.attrPattern.exec(attrs)) { // eslint-disable-line no-cond-assign - attr = match[1].toLowerCase() - value = match[4] || match[3] || match[2] - - if( /\*$/.test(attr)) { - Link.setAttr(link, attr, Link.parseExtendedValue(value)) - } else if(/%/.test(value)) { - Link.setAttr(link, attr, querystring.decode(value)) - } else { - Link.setAttr(link, attr, value) - } - } - - return link -}; - -export default Link; diff --git a/app/assets/javascripts/components/locales/ar.jsx b/app/assets/javascripts/components/locales/ar.jsx deleted file mode 100644 index 06a83b609..000000000 --- a/app/assets/javascripts/components/locales/ar.jsx +++ /dev/null @@ -1,131 +0,0 @@ -/** - * ملاحظة للمساهمين و المساهمات : - * لجعل مهمة المساهمين الآخرين أسهل، رجاءا تذكر : - * 1. إضافة سلسلة جديدة هنا؛ و - * 2. لإزالة السلاسل القديمة التي لم تعد هناك حاجة إليها. و - * 3. لفرز السلاسل تبعا للأبجدية - * شكر! - */ -const ar = { - "account.block": "حظر @{name}", - "account.disclaimer": "هذا المستخدم من مثيل خادم آخر. قد يكون هذا الرقم أكبر.", - "account.edit_profile": "تعديل الملف الشخصي", - "account.follow": "إتبع", - "account.followers": "المتابعون", - "account.follows": "يتبع", - "account.follows_you": "يتابعك", - "account.mention": "أُذكُر @{name}", - "account.mute": "أكتم @{name}", - "account.posts": "المشاركات", - "account.report": "أبلغ عن @{name}", - "account.requested": "في انتظار الموافقة", - "account.unblock": "إلغاء الحظر عن @{name}", - "account.unfollow": "إلغاء المتابعة", - "account.unmute": "إلغاء الكتم عن @{name}", - "boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة", - "column.blocks": "الحسابات المحجوبة", - "column.community": "الخيط العام المحلي", - "column.favourites": "المفضلة", - "column.follow_requests": "طلبات المتابعة", - "column.home": "الرئيسية", - "column.mutes": "الحسابات المكتومة", - "column.notifications": "الإشعارات", - "column.public": "الخيط العام الموحد", - "column_back_button.label": "العودة", - "column_subheading.navigation": "التصفح", - "column_subheading.settings": "الإعدادات", - "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.", - "compose_form.lock_disclaimer.lock": "مقفل", - "compose_form.placeholder": "فيمَ تفكّر؟", - "compose_form.publish": "بوّق !", - "compose_form.sensitive": "ضع علامة على الوسيط باعتباره حسّاس", - "compose_form.spoiler": "أخفِ النص واعرض تحذيرا", - "compose_form.spoiler_placeholder": "تنبيه عن المحتوى", - "confirmation_modal.cancel": "إلغاء", - "confirmations.block.confirm": "حجب", - "confirmations.block.message": "هل أنت متأكد أنك تريد حجب {name} ؟", - "confirmations.delete.confirm": "حذف", - "confirmations.delete.message": "هل أنت متأكد أنك تريد حذف هذا المنشور ؟", - "confirmations.mute.confirm": "أكتم", - "confirmations.mute.message": "هل أنت متأكد أنك تريد كتم {name} ؟", - "emoji_button.activity": "الأنشطة", - "emoji_button.flags": "الأعلام", - "emoji_button.food": "الطعام والشراب", - "emoji_button.label": "أدرج إيموجي", - "emoji_button.nature": "الطبيعة", - "emoji_button.objects": "أشياء", - "emoji_button.people": "الناس", - "emoji_button.search": "ابحث...", - "emoji_button.symbols": "رموز", - "emoji_button.travel": "أماكن و أسفار", - "empty_column.community": "الخط الزمني المحلي فارغ. اكتب شيئا ما للعامة كبداية.", - "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.", - "empty_column.home.public_timeline": "الخيط العام", - "empty_column.home": "إنك لا تتبع بعد أي شخص إلى حد الآن. زر {public} أو استخدام حقل البحث لكي تبدأ على التعرف على مستخدمين آخرين.", - "empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.", - "empty_column.public": "لا يوجد شيء هنا ! قم بتحرير شيء ما بشكل عام، أو اتبع مستخدمين آخرين في الخوادم المثيلة الأخرى لملء خيط المحادثات العام.", - "follow_request.authorize": "ترخيص", - "follow_request.reject": "رفض", - "getting_started.apps": "عدة تطبيقات مختلفة متوفرة", - "getting_started.heading": "إستعدّ للبدء", - "getting_started.about_addressing": "يمكنك متابعة الأشخاص إذا كنت تعرف اسم المستخدم الخاص بهم والنطاق الذي هم عليه عن طريق إدخال عنوان شبيه بالبريد الإلكتروني في الحقل المخصص للبحث.", - "getting_started.about_shortcuts": "إذا كان المستخدم المستهدف في نفس النطاق الذي تستخدمه، فإسم المستخدم وحده يكفي. وتنطبق نفس القاعدة على ذكر الأشخاص في المنشورات و التبويقات.", - "getting_started.open_source_notice": "ماستدون برنامج مفتوح المصدر. يمكنك المساهمة، أو الإبلاغ عن تقارير الأخطاء، على GitHub {github}. {apps}.", - "home.column_settings.advanced": "متقدمة", - "home.column_settings.basic": "أساسية", - "home.column_settings.filter_regex": "تصفية حسب التعبيرات العادية", - "home.column_settings.show_reblogs": "عرض الترقيات", - "home.column_settings.show_replies": "عرض الردود", - "home.settings": "إعدادات العمود", - "lightbox.close": "إغلاق", - "loading_indicator.label": "تحميل ...", - "media_gallery.toggle_visible": "Toggle visibility", - "missing_indicator.label": "تعذر العثور عليه", - "navigation_bar.blocks": "الحسابات المحجوبة", - "navigation_bar.community_timeline": "الخيط العام المحلي", - "navigation_bar.edit_profile": "تعديل الملف الشخصي", - "navigation_bar.preferences": "التفضيلات", - "navigation_bar.community_timeline": "الخيط العام المحلي", - "navigation_bar.public_timeline": "الخيط العام الموحد", - "navigation_bar.logout": "خروج", - "reply_indicator.cancel": "إلغاء", - "search.placeholder": "ابحث", - "search.account": "حساب", - "search.hashtag": "وسم", - "status.mention": "أذكُر @{name}", - "status.delete": "إحذف", - "status.reply": "ردّ", - "status.reblog": "رَقِّي", - "status.favourite": "أضف إلى المفضلة", - "status.reblogged_by": "{name} رقى", - "status.sensitive_warning": "محتوى حساس", - "status.sensitive_toggle": "اضغط للعرض", - "status.show_more": "أظهر المزيد", - "status.show_less": "إعرض أقلّ", - "status.open": "وسع هذه المشاركة", - "status.report": "إبلِغ عن @{name}", - "tabs_bar.compose": "تحرير", - "tabs_bar.home": "الرئيسية", - "tabs_bar.mentions": "الإشارات", - "tabs_bar.public": "الخيط العام الموحد", - "tabs_bar.notifications": "الإخطارات", - "upload_button.label": "إضافة وسائط", - "upload_form.undo": "إلغاء", - "upload_progress.label": "يرفع...", - "notification.follow": "{name} يتبعك", - "notification.favourite": "{name} أعجب بمنشورك", - "notification.reblog": "{name} قام بترقية تبويقك", - "notification.mention": "{name} ذكرك", - "notifications.column_settings.alert": "إشعارات سطح المكتب", - "notifications.column_settings.show": "إعرِضها في عمود", - "notifications.column_settings.follow": "متابعُون جُدُد :", - "notifications.column_settings.favourite": "المُفَضَّلة :", - "notifications.column_settings.mention": "الإشارات :", - "notifications.column_settings.reblog": "الترقيّات:", - "video_player.toggle_sound": "تبديل الصوت", - "video_player.toggle_visible": "إظهار / إخفاء الفيديو", - "video_player.expand": "وسّع الفيديو", - "video_player.video_error": "تعذر تشغيل الفيديو", -}; - -export default ar; diff --git a/app/assets/javascripts/components/locales/bg.jsx b/app/assets/javascripts/components/locales/bg.jsx deleted file mode 100644 index a194cdbdd..000000000 --- a/app/assets/javascripts/components/locales/bg.jsx +++ /dev/null @@ -1,68 +0,0 @@ -const bg = { - "column_back_button.label": "Назад", - "lightbox.close": "Затвори", - "loading_indicator.label": "Зареждане...", - "status.mention": "Споменаване", - "status.delete": "Изтриване", - "status.reply": "Отговор", - "status.reblog": "Споделяне", - "status.favourite": "Предпочитани", - "status.reblogged_by": "{name} сподели", - "status.sensitive_warning": "Деликатно съдържание", - "status.sensitive_toggle": "Покажи", - "video_player.toggle_sound": "Звук", - "account.mention": "Споменаване", - "account.edit_profile": "Редактирай профила си", - "account.unblock": "Не блокирай", - "account.unfollow": "Не следвай", - "account.block": "Блокирай", - "account.follow": "Последвай", - "account.posts": "Публикации", - "account.follows": "Следвам", - "account.followers": "Последователи", - "account.follows_you": "Твой последовател", - "account.requested": "В очакване на одобрение", - "getting_started.heading": "Първи стъпки", - "getting_started.about_addressing": "Можеш да последваш потребител, ако знаеш потребителското му име и домейна, на който се намира, като в полето за търсене ги въведеш по този начин: име@домейн", - "getting_started.about_shortcuts": "Ако с търсения потребител се намирате на един и същ домейн, достатъчно е да въведеш само името. Същото важи и за споменаване на хора в публикации.", - "getting_started.about_developer": "Можеш да потърсиш разработчика на този проект като: Gargron@mastodon.social", - "getting_started.open_source_notice": "Mastodon е софтуер с отворен код. Можеш да помогнеш или да докладваш за проблеми в Github: {github}.", - "column.home": "Начало", - "column.mentions": "Споменавания", - "column.public": "Публичен канал", - "column.notifications": "Известия", - "tabs_bar.compose": "Съставяне", - "tabs_bar.home": "Начало", - "tabs_bar.mentions": "Споменавания", - "tabs_bar.public": "Публичен канал", - "tabs_bar.notifications": "Известия", - "compose_form.placeholder": "Какво си мислиш?", - "compose_form.publish": "Раздумай", - "compose_form.sensitive": "Отбележи съдържанието като деликатно", - "compose_form.spoiler": "Скрий текста зад предупреждение", - "compose_form.private": "Отбележи като поверително", - "compose_form.privacy_disclaimer": "Поверителни публикации ще бъдат изпратени до споменатите потребители на {domains}. Доверяваш ли се на {domainsCount, plural, one {that server} other {those servers}}, че няма да издаде твоята публикация?", - "compose_form.unlisted": "Не показвай в публичния канал", - "navigation_bar.edit_profile": "Редактирай профил", - "navigation_bar.preferences": "Предпочитания", - "navigation_bar.public_timeline": "Публичен канал", - "navigation_bar.logout": "Излизане", - "reply_indicator.cancel": "Отказ", - "search.placeholder": "Търсене", - "search.account": "Акаунт", - "search.hashtag": "Хаштаг", - "upload_button.label": "Добави медия", - "upload_form.undo": "Отмяна", - "notification.follow": "{name} те последва", - "notification.favourite": "{name} хареса твоята публикация", - "notification.reblog": "{name} сподели твоята публикация", - "notification.mention": "{name} те спомена", - "notifications.column_settings.alert": "Десктоп известия", - "notifications.column_settings.show": "Покажи в колона", - "notifications.column_settings.follow": "Нови последователи:", - "notifications.column_settings.favourite": "Предпочитани:", - "notifications.column_settings.mention": "Споменавания:", - "notifications.column_settings.reblog": "Споделяния:", -}; - -export default bg; diff --git a/app/assets/javascripts/components/locales/de.jsx b/app/assets/javascripts/components/locales/de.jsx deleted file mode 100644 index 8720a2d36..000000000 --- a/app/assets/javascripts/components/locales/de.jsx +++ /dev/null @@ -1,126 +0,0 @@ -const de = { - "account.block": "@{name} blocken", - "account.disclaimer": "Dieser Benutzer ist von einer anderen Instanz. Diese Zahl könnte größer sein.", - "account.edit_profile": "Profil bearbeiten", - "account.follow": "Folgen", - "account.followers": "Folgende", - "account.follows": "Folgt", - "account.follows_you": "Folgt dir", - "account.mention": "@{name} erwähnen", - "account.mute": "@{name} stummschalten", - "account.posts": "Beiträge", - "account.report": "@{name} melden", - "account.requested": "Warte auf Erlaubnis", - "account.unblock": "@{name} entblocken", - "account.unfollow": "Entfolgen", - "account.unmute": "@{name} nicht mehr stummschalten", - "boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen", - "column_back_button.label": "Zurück", - "column.blocks": "Blockierte Benutzer", - "column.community": "Lokale Zeitleiste", - "column.favourites": "Favoriten", - "column.follow_requests": "Folgeanfragen", - "column.home": "Startseite", - "column.mutes": "Stummgeschaltete Benutzer", - "column.notifications": "Mitteilungen", - "column.public": "Gesamtes bekanntes Netz", - "compose_form.placeholder": "Worüber möchtest du schreiben?", - "compose_form.privacy_disclaimer": "Dein privater Status wird an die genannten Benutzer auf den Domains {domains} zugestellt. Vertraust du {domainsCount, plural, one {diesem Server} other {diesen Servern}}? Private Beiträge funktionieren nur auf Mastodon-Instanzen. Wenn {domains} {domainsCount, plural, one {keine Mastodon-Instanz ist} other {keine Mastodon-Instanzen sind}}, wird es dort kein Anzeichen geben, dass dein Beitrag privat ist und er könnte geteilt oder anderweitig für unerwünschte Empfänger sichtbar gemacht werden.", - "compose_form.publish": "Tröt", - "compose_form.sensitive": "Medien als heikel markieren", - "compose_form.spoiler_placeholder": "Inhaltswarnung", - "compose_form.spoiler": "Text hinter Warnung verbergen", - "emoji_button.label": "Emoji einfügen", - "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe etwas öffentlich, um den Ball ins Rollen zu bringen!", - "empty_column.hashtag": "Es gibt noch nichts unter diesem Hashtag.", - "empty_column.home.public_timeline": "die öffentliche Zeitleiste", - "empty_column.home": "Du folgst noch niemandem. Besuche {public} oder benutze die Suche, um zu starten oder andere Benutzer anzutreffen.", - "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um die Konversation zu starten.", - "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Benutzern von anderen Instanzen, um es aufzufüllen.", - "follow_request.authorize": "Erlauben", - "follow_request.reject": "Ablehnen", - "getting_started.apps": "Es sind verschiedene Apps verfügbar", - "getting_started.heading": "Erste Schritte", - "getting_started.open_source_notice": "Mastodon ist quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.", - "home.column_settings.advanced": "Fortgeschritten", - "home.column_settings.basic": "Einfach", - "home.column_settings.filter_regex": "Filter durch reguläre Ausdrücke", - "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen", - "home.column_settings.show_replies": "Antworten anzeigen", - "home.settings": "Spalteneinstellungen", - "lightbox.close": "Schließen", - "loading_indicator.label": "Lade…", - "media_gallery.toggle_visible": "Sichtbarkeit einstellen", - "missing_indicator.label": "Nicht gefunden", - "navigation_bar.blocks": "Blockierte Benutzer", - "navigation_bar.community_timeline": "Lokale Zeitleiste", - "navigation_bar.edit_profile": "Profil bearbeiten", - "navigation_bar.favourites": "Favoriten", - "navigation_bar.follow_requests": "Folgeanfragen", - "navigation_bar.info": "Erweiterte Informationen", - "navigation_bar.logout": "Abmelden", - "navigation_bar.mutes": "Stummgeschaltete Benutzer", - "navigation_bar.preferences": "Einstellungen", - "navigation_bar.public_timeline": "Föderierte Zeitleiste", - "notification.favourite": "{name} favorisierte deinen Status", - "notification.follow": "{name} folgt dir", - "notification.mention": "{name} erwähnte dich", - "notification.reblog": "{name} teilte deinen Status", - "notifications.clear_confirmation": "Bist du sicher, dass du alle Mitteilungen beseitigen willst?", - "notifications.clear": "Mitteilungen beseitigen", - "notifications.column_settings.alert": "Desktop-Benachrichtigungen", - "notifications.column_settings.favourite": "Favorisierungen:", - "notifications.column_settings.follow": "Neue Folgende:", - "notifications.column_settings.mention": "Erwähnungen:", - "notifications.column_settings.reblog": "Geteilte Beiträge:", - "notifications.column_settings.show": "In der Spalte anzeigen", - "notifications.column_settings.sound": "Ton abspielen", - "notifications.settings": "Spalteneinstellungen", - "privacy.change": "Privatsphäre des Status anpassen", - "privacy.direct.long": "Beitrag nur an erwähnte Benutzer", - "privacy.direct.short": "Direkt", - "privacy.private.long": "Beitrag nur an Folgende", - "privacy.private.short": "Privat", - "privacy.public.long": "Beitrag an öffentliche Zeitleisten", - "privacy.public.short": "Öffentlich", - "privacy.unlisted.long": "Nicht in öffentlichen Zeitleisten anzeigen", - "privacy.unlisted.short": "Nicht gelistet", - "reply_indicator.cancel": "Abbrechen", - "report.heading": "Neue Meldung", - "report.placeholder": "Zusätzliche Kommentare", - "report.submit": "Absenden", - "report.target": "Melden", - "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}", - "search.placeholder": "Suche", - "search.status_by": "Status von {name}", - "status.delete": "Löschen", - "status.favourite": "Favorisieren", - "status.load_more": "Weitere laden", - "status.media_hidden": "Medien versteckt", - "status.mention": "Erwähnen", - "status.open": "Öffnen", - "status.reblog": "Teilen", - "status.reblogged_by": "{name} teilte", - "status.reply": "Antworten", - "status.replyAll": "Auf Thread antworten", - "status.report": "@{name} melden", - "status.sensitive_toggle": "Klicke, um sie zu sehen", - "status.sensitive_warning": "Heikle Inhalte", - "status.show_less": "Weniger anzeigen", - "status.show_more": "Mehr anzeigen", - "tabs_bar.compose": "Schreiben", - "tabs_bar.federated_timeline": "Föderation", - "tabs_bar.home": "Home", - "tabs_bar.local_timeline": "Lokal", - "tabs_bar.notifications": "Mitteilungen", - "upload_area.title": "Hereinziehen zum Hochladen", - "upload_button.label": "Mediendatei hinzufügen", - "upload_form.undo": "Entfernen", - "upload_progress.label": "Lade hoch…", - "video_player.toggle_sound": "Ton umschalten", - "video_player.toggle_visible": "Sichtbarkeit umschalten", - "video_player.expand": "Videoanzeige vergrößern", - "video_player.video_error": "Video konnte nicht abgespielt werden", -}; - -export default de; diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx deleted file mode 100644 index afe714cac..000000000 --- a/app/assets/javascripts/components/locales/en.jsx +++ /dev/null @@ -1,177 +0,0 @@ -/** - * Note for Contributors: - * This file (en.jsx) serve as a template for other languages. - * To make other contributors' life easier, please REMEMBER: - * 1. to add your new string here; and - * 2. to remove old strings that are no longer needed; and - * 3. to sort the strings by the key. - * 4. To rename the `en` const name and export default name to match your locale. - * Thanks! - */ -const en = { - "account.block": "Block @{name}", - "account.disclaimer": "This user is from another instance. This number may be larger.", - "account.edit_profile": "Edit profile", - "account.follow": "Follow", - "account.followers": "Followers", - "account.follows": "Follows", - "account.follows_you": "Follows you", - "account.mention": "Mention @{name}", - "account.mute": "Mute @{name}", - "account.posts": "Posts", - "account.report": "Report @{name}", - "account.requested": "Awaiting approval", - "account.unblock": "Unblock @{name}", - "account.unfollow": "Unfollow", - "account.unmute": "Unmute @{name}", - "boost_modal.combo": "You can press {combo} to skip this next time", - "column.blocks": "Blocked users", - "column.community": "Local timeline", - "column.favourites": "Favourites", - "column.follow_requests": "Follow requests", - "column.home": "Home", - "column.mutes": "Muted users", - "column.notifications": "Notifications", - "column.public": "Federated timeline", - "column_back_button.label": "Back", - "column_subheading.navigation": "Navigation", - "column_subheading.settings": "Settings", - "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", - "compose_form.lock_disclaimer.lock": "locked", - "compose_form.placeholder": "What is on your mind?", - "compose_form.privacy_disclaimer": "Your post will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is not a public post, and it may be boosted or otherwise made visible to unintended recipients.", - "compose_form.publish": "Toot", - "compose_form.sensitive": "Mark media as sensitive", - "compose_form.spoiler": "Hide text behind warning", - "compose_form.spoiler_placeholder": "Content warning", - "confirmation_modal.cancel": "Cancel", - "confirmations.block.confirm": "Block", - "confirmations.block.message": "Are you sure you want to block {name}?", - "confirmations.delete.confirm": "Delete", - "confirmations.delete.message": "Are you sure you want to delete this status?", - "confirmations.mute.confirm": "Mute", - "confirmations.mute.message": "Are you sure you want to mute {name}?", - "emoji_button.activity": "Activity", - "emoji_button.flags": "Flags", - "emoji_button.food": "Food & Drink", - "emoji_button.label": "Insert emoji", - "emoji_button.nature": "Nature", - "emoji_button.objects": "Objects", - "emoji_button.people": "People", - "emoji_button.search": "Search...", - "emoji_button.symbols": "Symbols", - "emoji_button.travel": "Travel & Places", - "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", - "empty_column.hashtag": "There is nothing in this hashtag yet.", - "empty_column.home.public_timeline": "the public timeline", - "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.", - "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", - "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", - "follow_request.authorize": "Authorize", - "follow_request.reject": "Reject", - "getting_started.apps": "Various apps are available", - "getting_started.heading": "Getting started", - "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.", - "home.column_settings.advanced": "Advanced", - "home.column_settings.basic": "Basic", - "home.column_settings.filter_regex": "Filter out by regular expressions", - "home.column_settings.show_reblogs": "Show boosts", - "home.column_settings.show_replies": "Show replies", - "home.settings": "Column settings", - "lightbox.close": "Close", - "loading_indicator.label": "Loading...", - "media_gallery.toggle_visible": "Toggle visibility", - "missing_indicator.label": "Not found", - "navigation_bar.blocks": "Blocked users", - "navigation_bar.community_timeline": "Local timeline", - "navigation_bar.edit_profile": "Edit profile", - "navigation_bar.favourites": "Favourites", - "navigation_bar.follow_requests": "Follow requests", - "navigation_bar.info": "Extended information", - "navigation_bar.logout": "Logout", - "navigation_bar.mutes": "Muted users", - "navigation_bar.preferences": "Preferences", - "navigation_bar.public_timeline": "Federated timeline", - "notification.favourite": "{name} favourited your status", - "notification.follow": "{name} followed you", - "notification.mention": "{name} mentioned you", - "notification.reblog": "{name} boosted your status", - "notifications.clear": "Clear notifications", - "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", - "notifications.column_settings.alert": "Desktop notifications", - "notifications.column_settings.favourite": "Favourites:", - "notifications.column_settings.follow": "New followers:", - "notifications.column_settings.mention": "Mentions:", - "notifications.column_settings.reblog": "Boosts:", - "notifications.column_settings.show": "Show in column", - "notifications.column_settings.sound": "Play sound", - "notifications.settings": "Column settings", - "onboarding.done": "Done", - "onboarding.next": "Next", - "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.", - "onboarding.page_four.home": "The home timeline shows posts from people you follow.", - "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.", - "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", - "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}", - "onboarding.page_one.welcome": "Welcome to Mastodon!", - "onboarding.page_six.admin": "Your instance's admin is {admin}.", - "onboarding.page_six.almost_done": "Almost done...", - "onboarding.page_six.appetoot": "Bon Appetoot!", - "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.", - "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", - "onboarding.page_six.guidelines": "community guidelines", - "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!", - "onboarding.page_six.various_app": "mobile apps", - "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.", - "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.", - "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.", - "onboarding.skip": "Skip", - "privacy.change": "Adjust status privacy", - "privacy.direct.long": "Post to mentioned users only", - "privacy.direct.short": "Direct", - "privacy.private.long": "Post to followers only", - "privacy.private.short": "Followers-only", - "privacy.public.long": "Post to public timelines", - "privacy.public.short": "Public", - "privacy.unlisted.long": "Do not post to public timelines", - "privacy.unlisted.short": "Unlisted", - "reply_indicator.cancel": "Cancel", - "report.heading": "New report", - "report.placeholder": "Additional comments", - "report.submit": "Submit", - "report.target": "Reporting", - "search.placeholder": "Search", - "search.status_by": "Status by {name}", - "search_results.total": "{count, number} {count, plural, one {result} other {results}}", - "status.cannot_reblog": "This post cannot be boosted", - "status.delete": "Delete", - "status.favourite": "Favourite", - "status.load_more": "Load more", - "status.media_hidden": "Media hidden", - "status.mention": "Mention @{name}", - "status.open": "Expand this status", - "status.reblog": "Boost", - "status.reblogged_by": "{name} boosted", - "status.reply": "Reply", - "status.replyAll": "Reply to thread", - "status.report": "Report @{name}", - "status.sensitive_toggle": "Click to view", - "status.sensitive_warning": "Sensitive content", - "status.show_less": "Show less", - "status.show_more": "Show more", - "tabs_bar.compose": "Compose", - "tabs_bar.federated_timeline": "Federated", - "tabs_bar.home": "Home", - "tabs_bar.local_timeline": "Local", - "tabs_bar.notifications": "Notifications", - "upload_area.title": "Drag & drop to upload", - "upload_button.label": "Add media", - "upload_form.undo": "Undo", - "upload_progress.label": "Uploading...", - "video_player.expand": "Expand video", - "video_player.toggle_sound": "Toggle sound", - "video_player.toggle_visible": "Toggle visibility", - "video_player.video_error": "Video could not be played", -}; - -export default en; diff --git a/app/assets/javascripts/components/locales/eo.jsx b/app/assets/javascripts/components/locales/eo.jsx deleted file mode 100644 index 8c118b31f..000000000 --- a/app/assets/javascripts/components/locales/eo.jsx +++ /dev/null @@ -1,68 +0,0 @@ -const eo = { - "column_back_button.label": "Reveni", - "lightbox.close": "Fermi", - "loading_indicator.label": "Ŝarĝanta...", - "status.mention": "Mencii @{name}", - "status.delete": "Forigi", - "status.reply": "Respondi", - "status.reblog": "Diskonigi", - "status.favourite": "Favori", - "status.reblogged_by": "{name} diskonigita", - "status.sensitive_warning": "Tikla enhavo", - "status.sensitive_toggle": "Alklaki por vidi", - "video_player.toggle_sound": "Aktivigi sonojn", - "account.mention": "Mencii @{name}", - "account.edit_profile": "Redakti la profilon", - "account.unblock": "Malbloki @{name}", - "account.unfollow": "Malsekvi", - "account.block": "Bloki @{name}", - "account.follow": "Sekvi", - "account.posts": "Mesaĝoj", - "account.follows": "Sekvatoj", - "account.followers": "Sekvantoj", - "account.follows_you": "Sekvas vin", - "account.requested": "Atendas aprobon", - "getting_started.heading": "Por komenci", - "getting_started.about_addressing": "Vi povas sekvi homojn se vi konas la uzantnomon kaj domajnon tajpinte retpoŝtecan adreson en la serĉilon.", - "getting_started.about_shortcuts": "Se la celita uzanto troviĝas en la sama domajno de vi, uzi nur la uzantnomon sufiĉos. La sama regulo validas por mencii aliajn uzantojn en mesaĝo.", - "getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en github je {github}. {apps}.", - "column.home": "Hejmo", - "column.community": "Loka tempolinio", - "column.public": "Fratara tempolinio", - "column.notifications": "Sciigoj", - "tabs_bar.compose": "Ekskribi", - "tabs_bar.home": "Hejmo", - "tabs_bar.mentions": "Sciigoj", - "tabs_bar.public": "Fratara tempolinio", - "tabs_bar.notifications": "Sciigoj", - "compose_form.placeholder": "Pri kio vi pensas?", - "compose_form.publish": "Hup", - "compose_form.sensitive": "Marki ke la enhavo estas tikla", - "compose_form.spoiler": "Kaŝi la tekston malantaŭ averto", - "compose_form.private": "Marki ke la enhavo estas privata", - "compose_form.privacy_disclaimer": "Via privata mesaĝo estos sendita nur al menciitaj uzantoj en {domains}. Ĉu vi fidas {domainsCount, plural, one {tiun servilon} other {tiujn servilojn}}? Mesaĝa privateco funkcias nur en aperaĵoj de Mastodon. Se {domains} {domainsCount, plural, one {ne estas aperaĵo de Mastodon} other {ne estas aperaĵoj de Mastodon}}, estos neniu indiko ke via mesaĝo estas privata, kaj ĝi povus esti diskonigita aŭ videbligita al necelitaj ricevantoj.", - "compose_form.unlisted": "Ne afiŝi en publikaj tempolinioj", - "navigation_bar.edit_profile": "Redakti la profilon", - "navigation_bar.preferences": "Preferoj", - "navigation_bar.community_timeline": "Loka tempolinio", - "navigation_bar.public_timeline": "Fratara tempolinio", - "navigation_bar.logout": "Elsaluti", - "reply_indicator.cancel": "Rezigni", - "search.placeholder": "Serĉi", - "search.account": "Konto", - "search.hashtag": "Kradvorto", - "upload_button.label": "Aldoni enhavaĵon", - "upload_form.undo": "Malfari", - "notification.follow": "{name} sekvis vin", - "notification.favourite": "{name} favoris vian mesaĝon", - "notification.reblog": "{name} diskonigis vian mesaĝon", - "notification.mention": "{name} menciis vin", - "notifications.column_settings.alert": "Retumilaj atentigoj", - "notifications.column_settings.show": "Montri en kolono", - "notifications.column_settings.follow": "Novaj sekvantoj:", - "notifications.column_settings.favourite": "Favoroj:", - "notifications.column_settings.mention": "Mencioj:", - "notifications.column_settings.reblog": "Diskonigoj:", -}; - -export default eo; diff --git a/app/assets/javascripts/components/locales/es.jsx b/app/assets/javascripts/components/locales/es.jsx deleted file mode 100644 index bbd6e07c1..000000000 --- a/app/assets/javascripts/components/locales/es.jsx +++ /dev/null @@ -1,93 +0,0 @@ -const es = { - "column_back_button.label": "Atrás", - "lightbox.close": "Cerrar", - "loading_indicator.label": "Cargando...", - "status.mention": "Mencionar", - "status.delete": "Borrar", - "status.reply": "Responder", - "status.reblog": "Retoot", - "status.favourite": "Favorito", - "status.reblogged_by": "Retooteado por {name}", - "status.sensitive_warning": "Contenido sensible", - "status.sensitive_toggle": "Click para ver", - "status.show_more": "Mostrar más", - "status.show_less": "Mostrar menos", - "status.open": "Expandir estado", - "status.report": "Reportar", - "video_player.toggle_sound": "Act/Desac. sonido", - "account.mention": "Mencionar", - "account.edit_profile": "Editar perfil", - "account.unblock": "Desbloquear", - "account.unfollow": "Dejar de seguir", - "account.mute": "Silenciar", - "account.block": "Bloquear", - "account.follow": "Seguir", - "account.posts": "Publicaciones", - "account.follows": "Seguir", - "account.followers": "Seguidores", - "account.follows_you": "Te sigue", - "account.requested": "Esperando aprobación", - "getting_started.heading": "Primeros pasos", - "getting_started.about_addressing": "Puedes seguir a gente si conoces su nombre de usuario y el dominio en el que están registrados, introduciendo algo similar a una dirección de correo electrónico en el formulario en la parte superior de la barra lateral.", - "getting_started.about_shortcuts": "Si el usuario que buscas está en el mismo dominio que tú, simplemente funcionará introduciendo el nombre de usuario. La misma regla se aplica para mencionar a usuarios.", - "getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}. {apps}.", - "column.home": "Inicio", - "column.community": "Historia local", - "column.public": "Historia federada", - "column.notifications": "Notificaciones", - "column.blocks": "Usuarios bloqueados", - "column.favourites": "Favoritos", - "column.follow_requests": "Solicitudes para seguirte", - "column.mutes": "Usuarios silenciados", - "tabs_bar.compose": "Redactar", - "tabs_bar.home": "Inicio", - "tabs_bar.mentions": "Menciones", - "tabs_bar.public": "Público", - "tabs_bar.notifications": "Notificaciones", - "compose_form.placeholder": "¿En qué estás pensando?", - "compose_form.publish": "Tootear", - "compose_form.sensitive": "Marcar contenido como sensible", - "compose_form.spoiler": "Ocultar texto tras advertencia", - "compose_form.spoiler_placeholder": "Advertencia de contenido", - "composer_form.private": "Marcar como privado", - "composer_form.privacy_disclaimer": "Tu estado se mostrará a los usuarios mencionados en {domains}. Tu estado podrá ser visto en otras instancias, quizás no quieras que tu estado sea visto por otros usuarios.", - "compose_form.unlisted": "No mostrar en la historia federada", - "navigation_bar.edit_profile": "Editar perfil", - "navigation_bar.preferences": "Preferencias", - "navigation_bar.community_timeline": "Historia local", - "navigation_bar.public_timeline": "Historia federada", - "navigation_bar.favourites": "Favoritos", - "navigation_bar.blocks": "Usuarios bloqueados", - "navigation_bar.info": "Información adicional", - "navigation_bar.logout": "Cerrar sesión", - "navigation_bar.follow_requests": "Solicitudes para seguirte", - "navigation_bar.mutes": "Usuarios silenciados", - "reply_indicator.cancel": "Cancelar", - "search.placeholder": "Buscar", - "search.account": "Cuenta", - "search.hashtag": "Etiqueta", - "upload_button.label": "Subir multimedia", - "upload_form.undo": "Deshacer", - "notification.follow": "{name} te empezó a seguir", - "notification.favourite": "{name} marcó tu estado como favorito", - "notification.reblog": "{name} ha retooteado tu estado", - "notification.mention": "{name} te ha mencionado", - "notifications.column_settings.alert": "Notificaciones de escritorio", - "notifications.column_settings.show": "Mostrar en columna", - "notifications.column_settings.follow": "Nuevos seguidores:", - "notifications.column_settings.favourite": "Favoritos:", - "notifications.column_settings.mention": "Menciones:", - "notifications.column_settings.reblog": "Retoots:", - "emoji_button.label": "Insertar emoji", - "privacy.public.short": "Público", - "privacy.public.long": "Mostrar en la historia federada", - "privacy.unlisted.short": "Sin federar", - "privacy.unlisted.long": "No mostrar en la historia federada", - "privacy.private.short": "Privado", - "privacy.private.long": "Sólo mostrar a seguidores", - "privacy.direct.short": "Directo", - "privacy.direct.long": "Sólo mostrar a los usuarios mencionados", - "privacy.change": "Ajustar privacidad" -}; - -export default es; diff --git a/app/assets/javascripts/components/locales/fa.jsx b/app/assets/javascripts/components/locales/fa.jsx deleted file mode 100644 index 40a750618..000000000 --- a/app/assets/javascripts/components/locales/fa.jsx +++ /dev/null @@ -1,136 +0,0 @@ -const fa = { - "account.block": "@{name} را مسدود کن", - "account.disclaimer": "این کاربر عضو سرور متفاوتی است. شاید عدد واقعی بیشتر از این باشد.", - "account.edit_profile": "ویرایش نمایه", - "account.follow": "پی بگیرید", - "account.followers": "پیگیران", - "account.follows_you": "پیگیر شماست", - "account.follows": "پی میگیرد", - "account.mention": "نامبردن از @{name}", - "account.mute": "بیصدا کردن @{name}", - "account.posts": "نوشتهها", - "account.report": "گزارش @{name}", - "account.requested": "در انتظار پذیرش", - "account.unblock": "رفع انسداد @{name}", - "account.unfollow": "پایان پیگیری", - "account.unmute": "باصدا کردن @{name}", - "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید", - "column_back_button.label": "بازگشت", - "column.blocks": "کاربران مسدودشده", - "column.community": "نوشتههای محلی", - "column.favourites": "پسندیدهها", - "column.follow_requests": "درخواستهای پیگیری", - "column.home": "خانه", - "column.mutes": "کاربران بیصداشده", - "column.notifications": "اعلانها", - "column.public": "نوشتههای همهجا", - "compose_form.placeholder": "تازه چه خبر؟", - "compose_form.privacy_disclaimer": "نوشتهٔ خصوصی شما به کاربران نامبردهشده در {domains} فرستاده میشود. آیا به {domainsCount, plural, one {آن سرور} other {آن سرورها}} اعتماد دارید؟ تنظیمات حریم خصوصی نوشتهها تنها در سرورهای ماستدون کار میکند. اگر {domains} {domainsCount, plural, one {یک سرور ماستدون نباشد} other {سرورهای ماستدون نباشند}}، اشارهای به خصوصیبودن نوشتهٔ شما نخواهد شد و شاید نوشتهٔ شما همرسان شود یا برای کاربرانی که نمیخواهید نمایش یابد.", - "compose_form.publish": "بوق", - "compose_form.sensitive": "تصاویر حساس هستند", - "compose_form.spoiler_placeholder": "هشدار محتوا", - "compose_form.spoiler": "نوشته را پشت هشدار پنهان کنید", - "emoji_button.label": "افزودن شکلک", - "emoji_button.search": "جستجو...", - "emoji_button.people": "مردم", - "emoji_button.nature": "طبیعت", - "emoji_button.food": "غذا و نوشیدنی", - "emoji_button.activity": "فعالیت", - "emoji_button.travel": "سفر و مکان", - "emoji_button.objects": "اشیا", - "emoji_button.symbols": "نمادها", - "emoji_button.flags": "پرچمها", - "empty_column.community": "فهرست نوشتههای محلی خالی است. چیزی بنویسید تا چرخش بچرخد!", - "empty_column.hashtag": "هنوز هیچ چیزی با این هشتگ نیست.", - "empty_column.home.public_timeline": "فهرست نوشتههای همهجا", - "empty_column.home": "شما هنوز پیگیر کسی نیستید. {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.", - "empty_column.notifications": "هنوز هیچ اعلانی ندارید. به نوشتههای دیگران واکنش نشان دهید تا گفتگو آغاز شود.", - "empty_column.public": "اینجا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران دیگر را پی بگیرید تا اینجا پر شود", - "follow_request.authorize": "اجازه دهید", - "follow_request.reject": "اجازه ندهید", - "getting_started.apps": "اپهای گوناگونی در دسترساند", - "getting_started.heading": "آغاز کنید", - "getting_started.open_source_notice": "ماستدون یک نرمافزار آزاد است. میتوانید در ساخت آن مشارکت کنید یا مشکلاتش را در {github} گزارش دهید. {apps}.", - "home.column_settings.advanced": "پیشرفته", - "home.column_settings.basic": "اصلی", - "home.column_settings.filter_regex": "با عبارتهای باقاعده فیلتر کنید", - "home.column_settings.show_reblogs": "نمایش بازبوقها", - "home.column_settings.show_replies": "نمایش پاسخها", - "home.settings": "تنظیمات ستون", - "lightbox.close": "بستن", - "loading_indicator.label": "بارگیری...", - "media_gallery.toggle_visible": "تغییر پیدایی", - "missing_indicator.label": "پیدا نشد", - "navigation_bar.blocks": "کاربران مسدودشده", - "navigation_bar.community_timeline": "نوشتههای محلی", - "navigation_bar.edit_profile": "ویرایش نمایه", - "navigation_bar.favourites": "پسندیدهها", - "navigation_bar.follow_requests": "درخواستهای پیگیری", - "navigation_bar.info": "اطلاعات تکمیلی", - "navigation_bar.logout": "خروج", - "navigation_bar.mutes": "کاربران بیصداشده", - "navigation_bar.preferences": "ترجیحات", - "navigation_bar.public_timeline": "نوشتههای همهجا", - "notification.favourite": "{name} نوشتهٔ شما را پسندید", - "notification.follow": "{name} پیگیر شما شد", - "notification.mention": "{name} از شما نام برد", - "notification.reblog": "{name} نوشتهٔ شما را بازبوقید", - "notifications.clear_confirmation": "واقعاً میخواهید همهٔ اعلانهایتان را برای همیشه پاک کنید؟", - "notifications.clear": "پاککردن اعلانها", - "notifications.column_settings.alert": "اعلان در کامپیوتر", - "notifications.column_settings.favourite": "پسندیدهها:", - "notifications.column_settings.follow": "پیگیران تازه:", - "notifications.column_settings.mention": "نامبردنها:", - "notifications.column_settings.reblog": "بازبوقها:", - "notifications.column_settings.show": "در ستون نشان بده", - "notifications.column_settings.sound": "صدا را پخش کن", - "notifications.settings": "تنظیمات ستون", - "privacy.change": "تنظیم حریم خصوصی نوشتهها", - "privacy.direct.long": "تنها به کاربران نامبردهشده نشان بده", - "privacy.direct.short": "مستقیم", - "privacy.private.long": "تنها به پیگیران نشان بده", - "privacy.private.short": "خصوصی", - "privacy.public.long": "در فهرست نوشتههای عمومی نشان بده", - "privacy.public.short": "عمومی", - "privacy.unlisted.long": "در فهرست نوشتههای همهجا نشان نده", - "privacy.unlisted.short": "فهرستنشده", - "reply_indicator.cancel": "لغو", - "report.heading": "گزارش تازه", - "report.placeholder": "توضیح اضافه", - "report.submit": "بفرست", - "report.target": "گزارشدادن", - "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}", - "search.placeholder": "جستجو", - "search.status_by": "نوشتهٔ {name}", - "status.delete": "پاککردن", - "status.favourite": "پسندیدن", - "status.load_more": "بیشتر نشان بده", - "status.media_hidden": "تصویر پنهان شده", - "status.mention": "از @{name} نام ببرید", - "status.open": "این نوشته را باز کن", - "status.reblog": "بوق", - "status.cannot_reblog": "این نوشته را نمیشود بازبوقید", - "status.reblogged_by": "{name} بازبوقید", - "status.reply": "پاسخ", - "status.replyAll": "به نوشته پاسخ دهید", - "status.report": "@{name} را گزارش دهید", - "status.sensitive_toggle": "برای دیدن کلیک کنید", - "status.sensitive_warning": "محتوای حساس", - "status.show_less": "نهفتن", - "status.show_more": "نمایش", - "tabs_bar.compose": "بنویسید", - "tabs_bar.federated_timeline": "همگانی", - "tabs_bar.home": "خانه", - "tabs_bar.local_timeline": "محلی", - "tabs_bar.notifications": "اعلانها", - "upload_area.title": "برای بارگذاری به اینجا بکشید", - "upload_button.label": "افزودن تصویر", - "upload_form.undo": "واگردانی", - "upload_progress.label": "بارگذاری...", - "video_player.toggle_sound": "تغییر صداداری", - "video_player.toggle_visible": "تغییر پیدایی", - "video_player.expand": "بازکردن ویدیو", - "video_player.video_error": "ویدیو نمیتواند پخش شود", -}; - -export default fa; diff --git a/app/assets/javascripts/components/locales/fi.jsx b/app/assets/javascripts/components/locales/fi.jsx deleted file mode 100644 index b3ae4bc56..000000000 --- a/app/assets/javascripts/components/locales/fi.jsx +++ /dev/null @@ -1,68 +0,0 @@ -const fi = { - "column_back_button.label": "Takaisin", - "lightbox.close": "Sulje", - "loading_indicator.label": "Ladataan...", - "status.mention": "Mainitse @{name}", - "status.delete": "Poista", - "status.reply": "Vastaa", - "status.reblog": "Buustaa", - "status.favourite": "Tykkää", - "status.reblogged_by": "{name} buustasi", - "status.sensitive_warning": "Arkaluontoista sisältöä", - "status.sensitive_toggle": "Klikkaa nähdäksesi", - "video_player.toggle_sound": "Äänet päälle/pois", - "account.mention": "Mainitse @{name}", - "account.edit_profile": "Muokkaa", - "account.unblock": "Salli @{name}", - "account.unfollow": "Lopeta seuraaminen", - "account.block": "Estä @{name}", - "account.follow": "Seuraa", - "account.posts": "Postit", - "account.follows": "Seuraa", - "account.followers": "Seuraajia", - "account.follows_you": "Seuraa sinua", - "account.requested": "Odottaa hyväksyntää", - "getting_started.heading": "Aloitus", - "getting_started.about_addressing": "Voit seurata ihmisiä jos tiedät heidän käyttäjänimensä ja domainin missä he ovat syöttämällä e-mail-esque osoitteen Etsi kenttään.", - "getting_started.about_shortcuts": "Jos etsimäsi henkilö on samassa domainissa kuin sinä, pelkkä käyttäjänimi kelpaa. Sama pätee kun mainitset ihmisiä statuksessasi", - "getting_started.open_source_notice": "Mastodon Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}. {apps}.", - "column.home": "Koti", - "column.community": "Paikallinen aikajana", - "column.public": "Yleinen aikajana", - "column.notifications": "Ilmoitukset", - "tabs_bar.compose": "Luo", - "tabs_bar.home": "Koti", - "tabs_bar.mentions": "Maininnat", - "tabs_bar.public": "Yleinen aikajana", - "tabs_bar.notifications": "Ilmoitukset", - "compose_form.placeholder": "Mitä sinulla on mielessä?", - "compose_form.publish": "Toot", - "compose_form.sensitive": "Merkitse media herkäksi", - "compose_form.spoiler": "Piiloita teksti varoituksen taakse", - "compose_form.private": "Merkitse yksityiseksi", - "compose_form.privacy_disclaimer": "Sinun yksityinen status toimitetaan mainitsemallesi käyttäjille domaineissa {domains}. Luotatko {domainsCount, plural, one {tähän palvelimeen} other {näihin palvelimiin}}? Postauksen yksityisyys toimii van Mastodon palvelimilla. Jos {domains} {domainsCount, plural, one {ei ole Mastodon palvelin} other {eivät ole Mastodon palvelin}}, viestiin ei tule Yksityinen-merkintää, ja sitä voidaan boostata tai muuten tehdä näkyväksi muille vastaanottajille.", - "compose_form.unlisted": "Älä näytä yleisillä aikajanoilla", - "navigation_bar.edit_profile": "Muokkaa profiilia", - "navigation_bar.preferences": "Ominaisuudet", - "navigation_bar.community_timeline": "Paikallinen aikajana", - "navigation_bar.public_timeline": "Yleinen aikajana", - "navigation_bar.logout": "Kirjaudu ulos", - "reply_indicator.cancel": "Peruuta", - "search.placeholder": "Hae", - "search.account": "Tili", - "search.hashtag": "Hashtag", - "upload_button.label": "Lisää mediaa", - "upload_form.undo": "Peru", - "notification.follow": "{name} seurasi sinua", - "notification.favourite": "{name} tykkäsi statuksestasi", - "notification.reblog": "{name} buustasi statustasi", - "notification.mention": "{name} mainitsi sinut", - "notifications.column_settings.alert": "Työpöytä ilmoitukset", - "notifications.column_settings.show": "Näytä sarakkeessa", - "notifications.column_settings.follow": "Uusia seuraajia:", - "notifications.column_settings.favourite": "Tykkäyksiä:", - "notifications.column_settings.mention": "Mainintoja:", - "notifications.column_settings.reblog": "Buusteja:", -}; - -export default fi; diff --git a/app/assets/javascripts/components/locales/fr.jsx b/app/assets/javascripts/components/locales/fr.jsx deleted file mode 100644 index de13284e5..000000000 --- a/app/assets/javascripts/components/locales/fr.jsx +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Note aux contributeurs⋅trices: - * Pour rendre plus simple la vie des autres personnes - * apportant leur contribution, merci de penser aux choses suivantes : - * 1. Ajoutez les nouvelles chaînes traduites par ordre alphabétique - * 2. Pensez à supprimer les chaînes inutilisées - * Merci ! - */ -const fr = { - "account.block": "Bloquer", - "account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.", - "account.edit_profile": "Modifier le profil", - "account.followers": "Abonné⋅e⋅s", - "account.follows": "Abonnements", - "account.follow": "Suivre", - "account.follows_you": "Vous suit", - "account.mention": "Mentionner", - "account.mute": "Masquer", - "account.posts": "Statuts", - "account.report": "Signaler", - "account.requested": "Invitation envoyée", - "account.unblock": "Débloquer", - "account.unfollow": "Ne plus suivre", - "account.unmute": "Ne plus masquer", - "column_back_button.label": "Retour", - "column.blocks": "Comptes bloqués", - "column.community": "Fil public local", - "column.favourites": "Favoris", - "column.follow_requests": "Demandes de suivi", - "column.home": "Accueil", - "column.notifications": "Notifications", - "column.public": "Fil public global", - "compose_form.placeholder": "Qu’avez-vous en tête ?", - "compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodon. Si {domains} {domainsCount, plural, one {n’est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n’y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d’une autre manière à d’autres personnes imprévues.", - "compose_form.private": "Rendre privé", - "compose_form.publish": "Pouet", - "compose_form.sensitive": "Marquer le média comme délicat", - "compose_form.spoiler": "Masquer le texte derrière un avertissement", - "compose_form.spoiler_placeholder": "Avertissement", - "compose_form.unlisted": "Ne pas afficher dans les fils publics", - "emoji_button.label": "Insérer un emoji", - "empty_column.community": "Le fil public local est vide. Écrivez-donc quelque chose pour le remplir !", - "empty_column.hashtag": "Il n’y a encore aucun contenu relatif à ce hashtag", - "empty_column.home.public_timeline": "le fil public", - "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateurs⋅trices.", - "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateurs⋅trices pour débuter la conversation.", - "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateurs⋅trices d’autres instances pour remplir le fil public.", - "follow_request.authorize": "Autoriser", - "follow_request.reject": "Rejeter", - "getting_started.about_addressing": "Vous pouvez suivre les statuts de quelqu’un en entrant dans le champ de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière d’une adresse courriel.", - "getting_started.about_developer": "Pour suivre le développeur de ce projet, c’est Gargron@mastodon.social", - "getting_started.about_shortcuts": "Si cette personne utilise la même instance que vous, l’identifiant suffit. C’est le même principe pour mentionner quelqu’un dans vos statuts.", - "getting_started.heading": "Pour commencer", - "getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {github} sur GitHub.", - "home.column_settings.advanced": "Avancé", - "home.column_settings.basic": "Basique", - "home.column_settings.filter_regex": "Filtrer avec une expression rationnelle", - "home.column_settings.show_reblogs": "Afficher les partages", - "home.column_settings.show_replies": "Afficher les réponses", - "home.settings": "Paramètres de la colonne", - "lightbox.close": "Fermer", - "loading_indicator.label": "Chargement…", - "media_gallery.toggle_visible": "Modifier la visibilité", - "missing_indicator.label": "Non trouvé", - "navigation_bar.blocks": "Comptes bloqués", - "navigation_bar.community_timeline": "Fil public local", - "navigation_bar.edit_profile": "Modifier le profil", - "navigation_bar.favourites": "Favoris", - "navigation_bar.follow_requests": "Demandes de suivi", - "navigation_bar.info": "Plus d’informations", - "navigation_bar.logout": "Déconnexion", - "navigation_bar.mutes": "Comptes silencés", - "navigation_bar.preferences": "Préférences", - "navigation_bar.public_timeline": "Fil public global", - "notification.favourite": "{name} a ajouté à ses favoris :", - "notification.follow": "{name} vous suit.", - "notification.mention": "{name} vous a mentionné⋅e :", - "notification.reblog": "{name} a partagé votre statut :", - "notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?", - "notifications.clear": "Nettoyer", - "notifications.column_settings.alert": "Notifications locales", - "notifications.column_settings.favourite": "Favoris :", - "notifications.column_settings.follow": "Nouveaux abonné⋅e⋅s :", - "notifications.column_settings.mention": "Mentions :", - "notifications.column_settings.reblog": "Partages :", - "notifications.column_settings.show": "Afficher dans la colonne", - "notifications.column_settings.sound": "Émettre un son", - "notifications.settings": "Paramètres de la colonne", - "onboarding.next": "Suivant", - "onboarding.page_five.public_timelines": "Le fil public global affiche les posts de tou⋅te⋅s les utilisateurs⋅trices suivi⋅es par les membres de {domain}. Le fil public local est identique mais se limite aux utilisateurs⋅trices de {domain}.", - "onboarding.page_four.home": "L’Accueil affiche les posts de tou⋅te⋅s les utilisateurs⋅trices que vous suivez", - "onboarding.page_four.notifications": "Les Notifications vous informent lorsque quelqu’un interagit avec vous", - "onboarding.page_one.federation": "Mastodon est un réseau social qui appartient à tou⋅te⋅s.", - "onboarding.page_one.handle": "Vous êtes sur {domain}, une des nombreuses instances indépendantes de Mastodon. Votre nom d’utilisateur⋅trice complet est {handle}", - "onboarding.page_one.welcome": "Bienvenue sur Mastodon !", - "onboarding.page_six.admin": "L’administrateur⋅trice de votre instance est {admin}", - "onboarding.page_six.almost_done": "Nous y sommes presque…", - "onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres. Et maintenant… Bon Appetoot!", - "onboarding.page_six.github": "Mastodon est un logiciel libre, gratuit et open-source. Vous pouvez rapporter des bogues, suggérer des fonctionnalités, ou contribuer à son développement sur {github}.", - "onboarding.page_six.guidelines": "règles de la communauté", - "onboarding.page_six.read_guidelines": "S’il vous plaît, n’oubliez pas de lire les {guidelines} !", - "onboarding.page_six.various_app": "applications mobiles", - "onboarding.page_three.profile": "Modifiez votre profil pour changer votre avatar, votre description ainsi que votre nom. Vous y trouverez également d’autres préférences.", - "onboarding.page_three.search": "Utilisez la barre de recherche pour trouver des utilisateurs⋅trices et regarder des hashtags tels que {illustration} et {introductions}. Pour trouver quelqu’un qui n’est pas sur cette instance, utilisez son nom d’utilisateur⋅trice complet.", - "onboarding.page_two.compose": "Écrivez depuis la colonne de composition. Vous pouvez ajouter des images, changer les réglages de confidentialité, et ajouter des avertissements de contenu (Content Warning) grâce aux icônes en dessous.", - "onboarding.skip": "Passer", - "privacy.change": "Ajuster la confidentialité du message", - "privacy.direct.long": "N’afficher que pour les personnes mentionnées", - "privacy.direct.short": "Direct", - "privacy.private.long": "N’afficher que pour vos abonné⋅e⋅s", - "privacy.private.short": "Privé", - "privacy.public.long": "Afficher dans les fils publics", - "privacy.public.short": "Public", - "privacy.unlisted.long": "Ne pas afficher dans les fils publics", - "privacy.unlisted.short": "Non-listé", - "reply_indicator.cancel": "Annuler", - "report.heading": "Nouveau signalement", - "report.placeholder": "Commentaires additionnels", - "report.submit": "Envoyer", - "report.target": "Signalement", - "search.account": "Compte", - "search.hashtag": "Mot-clé", - "search.placeholder": "Rechercher", - "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}", - "search.status_by": "Statuts de {name}", - "status.delete": "Effacer", - "status.favourite": "Ajouter aux favoris", - "status.load_more": "Charger plus", - "status.media_hidden": "Média caché", - "status.mention": "Mentionner", - "status.open": "Déplier ce statut", - "status.reblogged_by": "{name} a partagé :", - "status.reblog": "Partager", - "status.reply": "Répondre", - "status.report": "Signaler @{name}", - "status.sensitive_toggle": "Cliquer pour dévoiler", - "status.sensitive_warning": "Contenu délicat", - "status.show_less": "Replier", - "status.show_more": "Déplier", - "tabs_bar.compose": "Composer", - "tabs_bar.federated_timeline": "Fil public global", - "tabs_bar.home": "Accueil", - "tabs_bar.local_timeline": "Fil public local", - "tabs_bar.mentions": "Mentions", - "tabs_bar.notifications": "Notifications", - "tabs_bar.public": "Fil public global", - "upload_area.title": "Glissez et déposez pour envoyer", - "upload_button.label": "Joindre un média", - "upload_form.undo": "Annuler", - "upload_progress.label": "Envoi en cours…", - "video_player.toggle_sound": "Mettre/Couper le son", - "video_player.toggle_visible": "Afficher/Cacher la vidéo", -}; - -export default fr; diff --git a/app/assets/javascripts/components/locales/he.jsx b/app/assets/javascripts/components/locales/he.jsx deleted file mode 100644 index 0fcb3d33e..000000000 --- a/app/assets/javascripts/components/locales/he.jsx +++ /dev/null @@ -1,177 +0,0 @@ -/** - * הערה לתורמים: - * קובץ זה (he.jsx)מבוסס על en.jsx ויש לעדכנו מפעם לפעם כשיוצאות גרסאות חדשות. - * אנא הקלו על התורמים העתידיים: - * 1. הוסיפו לכאן מחרוזות חדשות - * 2. הסירו מחרוזות ישנות שכבר לא בשימוש בגרסא האנגלית - * 3. מיינו את השורות לפי סדר ABC כמו בקובץ המקורי. - * 4. ובבקשה כבדו את סגנון התרגום שהנחלנו כאן, או תאמו איתנו אם ישנם שינויים יסודיים - * תודה! - */ -const he = { - "account.block": "חסימת @{name}", - "account.disclaimer": "‏משתמש זה מגיע מקהילה אחרת. המספר הזה עשוי להיות גדול יותר.", - "account.edit_profile": "עריכת פרופיל", - "account.follow": "מעקב", - "account.followers": "עוקבים", - "account.follows_you": "במעקב אחריך", - "account.follows": "נעקבים", - "account.mention": "אזכור של @{name}", - "account.mute": "להשתיק את @{name}", - "account.posts": "הודעות", - "account.report": "לדווח על @{name}", - "account.requested": "בהמתנה לאישור", - "account.unblock": "הסרת חסימה מעל @{name}", - "account.unfollow": "הפסקת מעקב", - "account.unmute": "הפסקת השתקת @{name}", - "boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה", - "column.blocks": "חסימות", - "column.community": "פיד מקומי", - "column.favourites": "חיבובים", - "column.follow_requests": "בקשות מעקב", - "column.home": "בבית", - "column.mutes": "השתקות", - "column.notifications": "התראות", - "column.public": "בפרהסיה", - "column_back_button.label": "אחורה", - "column_subheading.navigation": "ניווט", - "column_subheading.settings": "אפשרויות", - "compose_form.lock_disclaimer": "חשבונך אינו {locked}. כל אחד יוכל לעקוב אחריך כדי לקרוא את הודעותיך המיועדות לעוקבים בלבד.", - "compose_form.lock_disclaimer.lock": "נעול", - "compose_form.placeholder": "‏מה עובר לך בראש?", - "compose_form.privacy_disclaimer": "‏הודעתך הפרטית תשלח למשתמשים על {domains}. האם ניתן לסמוך על {domainsCount, plural, one {שרת זה} other {שרתים אלו}}? פרטיות ההודעה קיימת רק על שרתי מסטודון. אם {domains} {domainsCount, plural, one {הוא לא שרת מסטודון} other {הם לא שרתי מסטודון}}, לא יהיה שום סימן שההודעה פרטית, והוא עשוי להיות מקודם או להחשף למשתמשים שלא ברשימת היעד.", - "compose_form.publish": "‏לחצרץ", - "compose_form.sensitive": "סימון תוכן כרגיש", - "compose_form.spoiler": "הסתרה מאחורי אזהרת תוכן", - "compose_form.spoiler_placeholder": "אזהרת תוכן", - "confirmation_modal.cancel": "ביטול", - "confirmations.block.confirm": "לחסום", - "confirmations.block.message": "לחסום את {name}?", - "confirmations.delete.confirm": "למחוק", - "confirmations.delete.message": "למחוק את ההודעה?", - "confirmations.mute.confirm": "להשתיק", - "confirmations.mute.message": "להשתיק את {name}?", - "emoji_button.activity": "פעילות", - "emoji_button.flags": "דגלים", - "emoji_button.food": "אוכל ושתיה", - "emoji_button.label": "הוספת אמוג'י", - "emoji_button.nature": "טבע", - "emoji_button.objects": "חפצים", - "emoji_button.people": "אנשים", - "emoji_button.search": "‏חיפוש...", - "emoji_button.symbols": "סמלים", - "emoji_button.travel": "טיולים ואתרים", - "empty_column.community": "‏טור הסביבה ריק. יש לפרסם משהו כדי שדברים יתרחילו להתגלגל!", - "empty_column.hashtag": "‏אין כלום בהאשתג הזה עדיין.", - "empty_column.home.public_timeline": "בפרהסיה", - "empty_column.home": "‏אף אחד לא במעקב עדיין. אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר חצוצרנים אחרים.", - "empty_column.notifications": "‏אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב!", - "empty_column.public": "‏אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות.", - "follow_request.authorize": "קבלה", - "follow_request.reject": "דחיה", - "getting_started.apps": "קיים מבחר יישומונים לניידים", - "getting_started.heading": "על ההתחלה", - "getting_started.open_source_notice": "מסטודון היא תוכנה חופשית (בקוד פתוח). ניתן לתרום או לדווח על בעיות בגיטהאב: {github}. {apps}.", - "home.column_settings.advanced": "למתקדמים", - "home.column_settings.basic": "למתחילים", - "home.column_settings.filter_regex": "‏סינון באמצעות ביטויים רגולריים (regular expressions)", - "home.column_settings.show_reblogs": "הצגת הדהודים", - "home.column_settings.show_replies": "הצגת תגובות", - "home.settings": "הגדרות טור", - "lightbox.close": "סגירה", - "loading_indicator.label": "טוען...", - "media_gallery.toggle_visible": "נראה\\בלתי נראה", - "missing_indicator.label": "לא נמצא", - "navigation_bar.blocks": "חסימות", - "navigation_bar.community_timeline": "פיד מקומי", - "navigation_bar.edit_profile": "עריכת פרופיל", - "navigation_bar.favourites": "חיבובים", - "navigation_bar.follow_requests": "בקשות מעקב", - "navigation_bar.info": "מידע נוסף", - "navigation_bar.logout": "יציאה", - "navigation_bar.mutes": "השתקות", - "navigation_bar.preferences": "העדפות", - "navigation_bar.public_timeline": "בפרהסיה", - "notification.favourite": "חצרוצך חובב על ידי {name}", - "notification.follow": "{name} במעקב אחרייך", - "notification.mention": 'אוזכרת ע"י {name}', - "notification.reblog": "חצרוצך הודהד על ידי {name}", - "notifications.clear": "הסרת התראות", - "notifications.clear_confirmation": "‏להסיר את כל ההתראות? בטוח?", - "notifications.column_settings.alert": "התראות לשולחן העבודה", - "notifications.column_settings.favourite": "מחובבים:", - "notifications.column_settings.follow": "עוקבים חדשים:", - "notifications.column_settings.mention": "‏פניות:", - "notifications.column_settings.reblog": "‏הדהודים:", - "notifications.column_settings.show": "הצגה בטור", - "notifications.column_settings.sound": "שמע מופעל", - "notifications.settings": "הגדרות טור", - "onboarding.done": "יציאה", - "onboarding.next": "הלאה", - "onboarding.page_five.public_timelines": "ציר הזמן המקומי מראה הודעות פומביות מכל באי קהילת {domain}. ציר הזמן העולמי מראה הודעות פומביות מאת כי מי שבאי קהילת {domain} עוקבים אחריו. אלו צירי הזמן הפומביים, דרך נהדרת לגלות אנשים חדשים.", - "onboarding.page_four.home": "ציר זמן הבית מראה הודעות מהנעקבים שלך.", - "onboarding.page_four.notifications": "טור ההתראות מראה כשמישהו מתייחס להודעות שלך.", - "onboarding.page_one.federation": "מסטודון היא רשת של שרתים עצמאיים מצורפים ביחד לכדי רשת חברתית אחת גדולה. אנחנו מכנים את השרתים האלו: קהילות", - "onboarding.page_one.handle": "אתם בקהילה {domain}, ולכן מזהה המשתמש המלא שלכם הוא {handle}", - "onboarding.page_one.welcome": "ברוכים הבאים למסטודון!", - "onboarding.page_six.admin": "הקהילה מנוהלת בידי {admin}.", - "onboarding.page_six.almost_done": "כמעט סיימנו...", - "onboarding.page_six.appetoot": "בתותאבון!", - "onboarding.page_six.apps_available": "קיימים {apps} זמינים עבור אנדרואיד, אייפון ופלטפורמות נוספות.", - "onboarding.page_six.github": "מסטודון הוא תוכנה חופשית. ניתן לדווח על באגים, לבקש יכולות, או לתרום לקוד באתר {github}.", - "onboarding.page_six.guidelines": "חוקי הקהילה", - "onboarding.page_six.read_guidelines": "‏נא לקרוא את {guidelines} של {domain}!", - "onboarding.page_six.various_app": "יישומונים ניידים", - "onboarding.page_three.profile": "ץתחת 'עריכת פרופיל' ניתן להחליף את תמונת הפרופיל שלך, תיאור קצר, והשם המוצג. שם גם ניתן למצוא אפשרויות והעדפות נוספות.", - "onboarding.page_three.search": "בחלונית החיפוש ניתן לחפש אנשים והאשתגים, כמו למשל {illustration} או {introductions}. כדי למצוא מישהו שלא על האינסטנס המקומי, יש להשתמש בכינוי המשתמש המלא.", - "onboarding.page_two.compose": "הודעות כותבים מטור הכתיבה. ניתן לנעלות תמונות, לשנות הגדרות פרטיות, ולהוסיף אזהרות תוכן בעזרת האייקונים שמתחת.", - "onboarding.skip": "לדלג", - "privacy.change": "שינוי פרטיות ההודעה", - "privacy.direct.long": "הצג רק למי שהודעה זו פונה אליו", - "privacy.direct.short": "הודעה ישירה", - "privacy.private.long": "הצג לעוקבים מקומיים בלבד", - "privacy.private.short": "לעוקבים בלבד", - "privacy.public.long": "פרסם בפומבי", - "privacy.public.short": "פומבי", - "privacy.unlisted.long": "לא יופיע בפידים הציבוריים המשותפים", - "privacy.unlisted.short": "לא לפיד הכללי", - "reply_indicator.cancel": "ביטול", - "report.heading": "דווח חדש", - "report.placeholder": "הערות נוספות", - "report.submit": "שליחה", - "report.target": "דיווח", - "search.placeholder": "חיפוש", - "search.status_by": "הודעה מאת {name}", - "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}", - "status.cannot_reblog": "לא ניתן להדהד הודעה זו", - "status.delete": "מחיקה", - "status.favourite": "חיבוב", - "status.load_more": "עוד", - "status.media_hidden": "מדיה מוסתרת", - "status.mention": "פניה אל @{name}", - "status.open": "הרחבת הודעה", - "status.reblog": "הדהוד", - "status.reblogged_by": "הודהד על ידי {name}", - "status.reply": "תגובה", - "status.replyAll": "תגובה לכולם", - "status.report": "דיווח על @{name}", - "status.sensitive_warning": "תוכן רגיש", - "status.sensitive_toggle": "לחצו כדי לראות", - "status.show_less": "הראה פחות", - "status.show_more": "הראה יותר", - "tabs_bar.compose": "חיבור", - "tabs_bar.federated_timeline": "בפדרציה", - "tabs_bar.home": "בבית", - "tabs_bar.local_timeline": "פיד מקומי", - "tabs_bar.notifications": "התראות", - "upload_area.title": "ניתן להעלות על ידי Drag & drop", - "upload_button.label": "הוספת מדיה", - "upload_form.undo": "ביטול", - "upload_progress.label": "עולה...", - "video_player.expand": "הרחבת וידאו", - "video_player.toggle_sound": "הפעלת\\ביטול שמע", - "video_player.toggle_visible": "הפעלת\\ביטול תצוגה", - "video_player.video_error": "לא ניתן לנגן וידאו", -}; - -export default he; diff --git a/app/assets/javascripts/components/locales/hr.jsx b/app/assets/javascripts/components/locales/hr.jsx deleted file mode 100644 index 0ca3ef73e..000000000 --- a/app/assets/javascripts/components/locales/hr.jsx +++ /dev/null @@ -1,121 +0,0 @@ -const hr = { - "account.block": "Blokiraj @{name}", - "account.disclaimer": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.", - "account.edit_profile": "Uredi profil", - "account.follow": "Slijedi", - "account.followers": "Sljedbenici", - "account.follows_you": "te slijedi", - "account.follows": "Slijedi", - "account.mention": "Spomeni @{name}", - "account.mute": "Utišaj @{name}", - "account.posts": "Postovi", - "account.report": "Prijavi @{name}", - "account.requested": "Čeka pristanak", - "account.unblock": "Deblokiraj @{name}", - "account.unfollow": "Prestani slijediti", - "account.unmute": "Poništi utišavanje @{name}", - "boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put", - "column_back_button.label": "Natrag", - "column.blocks": "Blokirani korisnici", - "column.community": "Lokalni timeline", - "column.favourites": "Favoriti", - "column.follow_requests": "Zahtjevi za slijeđenje", - "column.home": "Dom", - "column.notifications": "Notifikacije", - "column.public": "Federalni timeline", - "compose_form.placeholder": "Što ti je na umu?", - "compose_form.privacy_disclaimer": "Tvoj privatni status će biti dostavljen spomenutim korisnicima na {domains}. Vjeruješ li {domainsCount, plural, one {that server} drugim {those servers}}? Privatnost postova radi samo na Mastodon instancama. Ako {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, neće biti indikacije da je tvoj post privatan, i mogao bit biti podignut ili biti učinjen vidljivim na drugi način neželjenim primateljima.", - "compose_form.publish": "Toot", - "compose_form.sensitive": "Označi media sadržaj kao osjetljiv", - "compose_form.spoiler_placeholder": "Upozorenje o sadržaju", - "compose_form.spoiler": "Sakrij text iza upozorenja", - "emoji_button.label": "Umetni smajlije", - "empty_column.community": "Lokalni timeline je prazan. Napiši nešto javno kako bi pokrenuo stvari!", - "empty_column.hashtag": "Još ne postoji ništa s ovim hashtagom.", - "empty_column.home.public_timeline": "javni timeline", - "empty_column.home": "Još ne slijediš nikoga. Posjeti {public} ili koristi tražilicu kako bi počeo i upoznao druge korisnike.", - "empty_column.notifications": "Još nemaš notifikacija. Komuniciraj sa drugima kako bi započeo razgovor.", - "empty_column.public": "Ovdje nema ništa! Napiši nešto javno, ili ručno slijedi korisnike sa drugih instanci kako bi popunio", - "follow_request.authorize": "Authoriziraj", - "follow_request.reject": "Odbij", - "getting_started.apps": "Dostupne su razne aplikacije", - "getting_started.heading": "Počnimo", - "getting_started.open_source_notice": "Mastodon je softver otvorenog koda. Možeš pridonijeti ili prijaviti probleme na GitHubu {github}. {apps}.", - "home.column_settings.advanced": "Napredno", - "home.column_settings.basic": "Osnovno", - "home.column_settings.filter_regex": "Filtriraj s regularnim izrazima", - "home.column_settings.show_reblogs": "Pokaži boosts", - "home.column_settings.show_replies": "Pokaži odgovore", - "home.settings": "Postavke Stupca", - "lightbox.close": "Zatvori", - "loading_indicator.label": "Učitavam...", - "media_gallery.toggle_visible": "Preklopi vidljivost", - "missing_indicator.label": "Nije nađen", - "navigation_bar.blocks": "Blokirani korisnici", - "navigation_bar.community_timeline": "Lokalni timeline", - "navigation_bar.edit_profile": "Uredi profil", - "navigation_bar.favourites": "Favoriti", - "navigation_bar.follow_requests": "Zahtjevi za sljeđenje", - "navigation_bar.info": "Proširena informacija", - "navigation_bar.logout": "Odjavi se", - "navigation_bar.preferences": "Postavke", - "navigation_bar.public_timeline": "Federalni timeline", - "notification.favourite": "{name} je lajkao tvoj status", - "notification.follow": "{name} te sada slijedi", - "notification.reblog": "{name} je podigao tvoj status", - "notifications.clear_confirmation": "Želiš li zaista obrisati sve svoje notifikacije?", - "notifications.clear": "Očisti notifikacije", - "notifications.column_settings.alert": "Desktop notifikacije", - "notifications.column_settings.favourite": "Favoriti:", - "notifications.column_settings.follow": "Novi sljedbenici:", - "notifications.column_settings.mention": "Spominjanja:", - "notifications.column_settings.reblog": "Boosts:", - "notifications.column_settings.show": "Prikaži u stupcu", - "notifications.column_settings.sound": "Sviraj zvuk", - "notifications.settings": "Postavke rubrike", - "privacy.change": "Podesi status privatnosti", - "privacy.direct.long": "Prikaži samo spomenutim korisnicima", - "privacy.direct.short": "Direktno", - "privacy.private.long": "Prikaži samo sljedbenicima", - "privacy.private.short": "Privatno", - "privacy.public.long": "Postaj na javne timeline", - "privacy.public.short": "Javno", - "privacy.unlisted.long": "Ne prikazuj u javnim timelineovima", - "privacy.unlisted.short": "Unlisted", - "reply_indicator.cancel": "Otkaži", - "report.heading": "Nova prijava", - "report.placeholder": "Dodatni komentari", - "report.submit": "Pošalji", - "report.target": "Prijavljivanje", - "search_results.total": "{count, number} {count, plural, one {result} other {results}}", - "search.placeholder": "Traži", - "search.status_by": "Status od {name}", - "status.delete": "Obriši", - "status.favourite": "Označi omiljenim", - "status.load_more": "Učitaj više", - "status.media_hidden": "Sakriven media sadržaj", - "status.mention": "Spomeni @{name}", - "status.open": "Proširi ovaj status", - "status.reblog": "Podigni", - "status.reblogged_by": "{name} je podigao", - "status.reply": "Odgovori", - "status.report": "Prijavi @{name}", - "status.sensitive_toggle": "Klikni da bi vidio", - "status.sensitive_warning": "Osjetljiv sadržaj", - "status.show_less": "Pokaži manje", - "status.show_more": "Pokaži više", - "tabs_bar.compose": "Sastavi", - "tabs_bar.federated_timeline": "Federalni", - "tabs_bar.home": "Dom", - "tabs_bar.local_timeline": "Lokalno", - "tabs_bar.notifications": "Notifikacije", - "upload_area.title": "Povuci & spusti kako bi uploadao", - "upload_button.label": "Dodaj media", - "upload_form.undo": "Poništi", - "upload_progress.label": "Uploadam...", - "video_player.toggle_sound": "Toggle zvuk", - "video_player.toggle_visible": "Preklopi vidljivost", - "video_player.expand": "Proširi video", -}; - -export default hr; diff --git a/app/assets/javascripts/components/locales/hu.jsx b/app/assets/javascripts/components/locales/hu.jsx deleted file mode 100644 index b68df66fd..000000000 --- a/app/assets/javascripts/components/locales/hu.jsx +++ /dev/null @@ -1,57 +0,0 @@ -const hu = { - "column_back_button.label": "Vissza", - "lightbox.close": "Bezárás", - "loading_indicator.label": "Betöltés...", - "status.mention": "Említés", - "status.delete": "Törlés", - "status.reply": "Válasz", - "status.reblog": "Reblog", - "status.favourite": "Kedvenc", - "status.reblogged_by": "{name} reblogolta", - "status.sensitive_warning": "Érzékeny tartalom", - "status.sensitive_toggle": "Katt a megtekintéshez", - "video_player.toggle_sound": "Hang kapcsolása", - "account.mention": "Említés", - "account.edit_profile": "Profil szerkesztése", - "account.unblock": "Blokkolás levétele", - "account.unfollow": "Követés abbahagyása", - "account.block": "Blokkolás", - "account.follow": "Követés", - "account.posts": "Posts", - "account.follows": "Követve", - "account.followers": "Követők", - "account.follows_you": "Követnek téged", - "getting_started.heading": "Első lépések", - "getting_started.about_addressing": "Követhetsz embereket felhasználónevük és a doménjük ismeretében, amennyiben megadod ezt az e-mail-szerű címet az oldalsáv tetején lévő rubrikában.", - "getting_started.about_shortcuts": "Ha a célzott személy azonos doménen tartózkodik, a felhasználónév elegendő. Ugyanez érvényes mikor személyeket említesz az állapotokban.", - "getting_started.about_developer": "A projekt fejlesztője követhető, mint Gargron@mastodon.social", - "column.home": "Kezdőlap", - "column.mentions": "Említések", - "column.public": "Nyilvános", - "column.notifications": "Értesítések", - "tabs_bar.compose": "Összeállítás", - "tabs_bar.home": "Kezdőlap", - "tabs_bar.mentions": "Említések", - "tabs_bar.public": "Nyilvános", - "tabs_bar.notifications": "Notifications", - "compose_form.placeholder": "Mire gondolsz?", - "compose_form.publish": "Tülk!", - "compose_form.sensitive": "Tartalom érzékenynek jelölése", - "compose_form.unlisted": "Listázatlan mód", - "navigation_bar.edit_profile": "Profil szerkesztése", - "navigation_bar.preferences": "Beállítások", - "navigation_bar.public_timeline": "Nyilvános időfolyam", - "navigation_bar.logout": "Kijelentkezés", - "reply_indicator.cancel": "Mégsem", - "search.placeholder": "Keresés", - "search.account": "Fiók", - "search.hashtag": "Hashtag", - "upload_button.label": "Média hozzáadása", - "upload_form.undo": "Mégsem", - "notification.follow": "{name} követ téged", - "notification.favourite": "{name} kedvencnek jelölte az állapotod", - "notification.reblog": "{name} reblogolta az állapotod", - "notification.mention": "{name} megemlített" -}; - -export default hu; diff --git a/app/assets/javascripts/components/locales/id.jsx b/app/assets/javascripts/components/locales/id.jsx deleted file mode 100644 index 08ea6bf15..000000000 --- a/app/assets/javascripts/components/locales/id.jsx +++ /dev/null @@ -1,167 +0,0 @@ -const id = { - "account.block": "Blokir @{name}", - "account.disclaimer": "Pengguna ini berasal dari server lain. Angka berikut mungkin lebih besar.", - "account.edit_profile": "Ubah profil", - "account.follow": "Ikuti", - "account.followers": "Pengikut", - "account.follows_you": "Mengikuti anda", - "account.follows": "Mengikuti", - "account.mention": "Balasan @{name}", - "account.mute": "Bisukan @{name}", - "account.posts": "Postingan", - "account.report": "Laporkan @{name}", - "account.requested": "Menunggu persetujuan", - "account.unblock": "Hapus blokir @{name}", - "account.unfollow": "Berhenti mengikuti", - "account.unmute": "Berhenti membisukan @{name}", - "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini", - "column.blocks": "Pengguna diblokir", - "column.community": "Linimasa Lokal", - "column.favourites": "Favorit", - "column.follow_requests": "Permintaan mengikuti", - "column.home": "Beranda", - "column.mutes": "Pengguna dibisukan", - "column.notifications": "Notifikasi", - "column.public": "Linimasa gabunggan", - "column_back_button.label": "Kembali", - "column_subheading.navigation": "Navigasi", - "column_subheading.settings": "Pengaturan", - "compose_form.lock_disclaimer": "Akun anda tidak {locked}. Semua orang dapat mengikuti anda untuk melihat postingan khusus untuk pengikut anda.", - "compose_form.lock_disclaimer.lock": "dikunci", - "compose_form.placeholder": "Apa yang ada di pikiran anda?", - "compose_form.privacy_disclaimer": "Status pribadi anda akan dikirim ke pengguna yang disebut dalam {domains}. Apa anda mempercayai {domainsCount, plural, one {server tersebut} other {server tersebut}}? Privasi postingan hanya bekerja dalam server Mastodon. Jika {domains} {domainsCount, plural, one {bukan server Mastodon} other {bukan server Mastodon}}, akan ada indikasi bahwa postingan anda adalah postingan pribadi, dan dapat di-boost atau dapat dilihat oleh orang lain.", - "compose_form.publish": "Toot", - "compose_form.sensitive": "Tandai media sensitif", - "compose_form.spoiler": "Sembunyikan teks dibalik peringatan", - "compose_form.spoiler_placeholder": "Peringatan konten", - "confirmation_modal.cancel": "Batal", - "confirmations.block.confirm": "Blokir", - "confirmations.block.message": "Apa anda yakin ingin memblokir {name}?", - "confirmations.delete.confirm": "Hapus", - "confirmations.delete.message": "Apa anda yakin akan menghapus status ini?", - "confirmations.mute.confirm": "Bisukan", - "confirmations.mute.message": "Apa anda yakin ingin membisukan {name}?", - "emoji_button.activity": "Aktivitas", - "emoji_button.flags": "Bendera", - "emoji_button.food": "Makanan & Minuman", - "emoji_button.label": "Tambahkan emoji", - "emoji_button.nature": "Alam", - "emoji_button.objects": "Benda-benda", - "emoji_button.people": "Orang", - "emoji_button.search": "Cari...", - "emoji_button.symbols": "Simbol", - "emoji_button.travel": "Tempat Wisata", - "empty_column.community": "Linimasa lokal masih kosong. Tulis sesuatu secara publik dan buat roda berputar!", - "empty_column.hashtag": "Tidak ada apapun dalam hashtag ini.", - "empty_column.home.public_timeline": "linimasa publik", - "empty_column.home": "Anda sedang tidak mengikuti siapapun. Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.", - "empty_column.notifications": "Anda tidak memiliki notifikasi apapun. Berinteraksi dengan orang lain untuk memulai percakapan.", - "empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisinya secara manual", - "follow_request.authorize": "Izinkan", - "follow_request.reject": "Tolak", - "getting_started.apps": "Tersedia dalam berbagai aplikasi", - "getting_started.heading": "Mulai", - "getting_started.open_source_notice": "Mastodon adalah perangkat lunak yang bersifat open source. Anda dapat berkontribusi atau melaporkan permasalahan/bug di Github {github}. {apps}.", - "home.column_settings.advanced": "Tingkat Lanjut", - "home.column_settings.basic": "Dasar", - "home.column_settings.filter_regex": "Penyaringan dengan Regular Expression", - "home.column_settings.show_reblogs": "Tampilkan Boost", - "home.column_settings.show_replies": "Tampilkan balasan", - "home.settings": "Pengaturan kolom", - "lightbox.close": "Tutup", - "loading_indicator.label": "Tunggu sebentar...", - "media_gallery.toggle_visible": "Tampil/Sembunyikan", - "missing_indicator.label": "Tidak ditemukan", - "navigation_bar.blocks": "Pengguna diblokir", - "navigation_bar.community_timeline": "Linimasa lokal", - "navigation_bar.edit_profile": "Ubah profil", - "navigation_bar.favourites": "Favorit", - "navigation_bar.follow_requests": "Permintaan mengikuti", - "navigation_bar.info": "Informasi selengkapnya", - "navigation_bar.logout": "Keluar", - "navigation_bar.mutes": "Pengguna dibisukan", - "navigation_bar.preferences": "Pengaturan", - "navigation_bar.public_timeline": "Linimasa gabungan", - "notification.favourite": "{name} menyukai status anda", - "notification.follow": "{name} mengikuti anda", - "notification.reblog": "{name} mem-boost status anda", - "notifications.clear": "Hapus notifikasi", - "notifications.clear_confirmation": "Apa anda yakin hendak menghapus semua notifikasi anda?", - "notifications.column_settings.alert": "Notifikasi desktop", - "notifications.column_settings.favourite": "Favorit:", - "notifications.column_settings.follow": "Pengikut baru:", - "notifications.column_settings.mention": "Balasan:", - "notifications.column_settings.reblog": "Boost:", - "notifications.column_settings.show": "Tampilkan dalam kolom", - "notifications.column_settings.sound": "Mainkan suara", - "notifications.settings": "Pengaturan kolom", - "onboarding.done": "Selesei", - "onboarding.next": "Selanjutnya", - "onboarding.page_five.public_timelines": "Linimasa lokal menampilkan semua postingan publik dari semua orang di {domain}. Linimasa gabungan menampilkan postingan publik dari semua orang yang diikuti oleh {domain}. Ini semua adalah Linimasa Publik, cara terbaik untuk bertemu orang lain.", - "onboarding.page_four.home": "Linimasa beranda menampilkan postingan dari orang-orang yang anda ikuti.", - "onboarding.page_four.notifications": "Kolom notifikasi menampilkan ketika seseorang berinteraksi dengan anda.", - "onboarding.page_one.federation": "Mastodon adalah jaringan dari beberapa server independen yang bergabung untuk membuat jejaring sosial yang besar.", - "onboarding.page_one.handle": "Ada berada dalam {domain}, jadi nama user lengkap anda adalah {handle}", - "onboarding.page_one.welcome": "Selamat datang di Mastodon!", - "onboarding.page_six.admin": "Admin serveer anda adalah {admin}.", - "onboarding.page_six.almost_done": "Hampir selesei...", - "onboarding.page_six.appetoot": "Bon Appetoot!", - "onboarding.page_six.apps_available": "Ada beberapa apl yang tersedia untuk iOS, Android, dan platform lainnya.", - "onboarding.page_six.github": "Mastodon adalah software open-source. Anda bisa melaporkan bug, meminta fitur, atau berkontribusi dengan kode di {github}.", - "onboarding.page_six.guidelines": "pedoman komunitas", - "onboarding.page_six.read_guidelines": "Silakan baca {guidelines} {domain}!", - "onboarding.page_six.various_app": "apl handphone", - "onboarding.page_three.profile": "Ubah profil anda untuk mengganti avatar, bio, dan nama pengguna anda. Disitu, anda juga bisa mengatur opsi lainnya.", - "onboarding.page_three.search": "Gunakan kolom pencarian untuk mencari orang atau melihat hashtag, seperti {illustration} dan {introductions}. Untuk mencari pengguna yang tidak berada dalam server ini, gunakan nama pengguna mereka selengkapnya.", - "onboarding.page_two.compose": "Tulis postingan melalui kolom posting. Anda dapat mengunggah gambar, mengganti pengaturan privasi, dan menambahkan peringatan konten dengan ikon-ikon dibawah ini.", - "onboarding.skip": "Lewati", - "privacy.change": "Tentukan privasi status", - "privacy.direct.long": "Kirim hanya ke pengguna yang disebut", - "privacy.direct.short": "Langsung", - "privacy.private.long": "Kirim hanya ke pengikut", - "privacy.private.short": "Pribadi", - "privacy.public.long": "Kirim ke linimasa publik", - "privacy.public.short": "Publik", - "privacy.unlisted.long": "Tidak ditampilkan di linimasa publik", - "privacy.unlisted.short": "Tak Terdaftar", - "reply_indicator.cancel": "Batal", - "report.heading": "Laporan baru", - "report.placeholder": "Komentar tambahan", - "report.submit": "Kirim", - "report.target": "Melaporkan", - "search.status_by": "Status yang dibuat oleh {name}", - "search_results.total": "{count} {count, plural, one {hasil} other {hasil}}", - "status.cannot_reblog": "Postingan ini tidak dapat di-boost", - "search.placeholder": "Pencarian", - "search.status_by": "Status oleh {name}", - "status.delete": "Hapus", - "status.favourite": "Difavoritkan", - "status.load_more": "Tampilkan semua", - "status.media_hidden": "Media disembunyikan", - "status.mention": "Balasan @{name}", - "status.open": "Tampilkan status ini", - "status.reblog": "Boost", - "status.reblogged_by": "di-boost {name}", - "status.reply": "Balas", - "status.replyAll": "Balas ke semua", - "status.report": "Laporkan @{name}", - "status.sensitive_toggle": "Klik untuk menampilkan", - "status.sensitive_warning": "Konten sensitif", - "status.show_less": "Tampilkan lebih sedikit", - "status.show_more": "Tampilkan semua", - "tabs_bar.compose": "Tulis", - "tabs_bar.federated_timeline": "Gabungan", - "tabs_bar.home": "Beranda", - "tabs_bar.local_timeline": "Lokal", - "tabs_bar.notifications": "Notifikasi", - "upload_area.title": "Seret & lepaskan untuk mengunggah", - "upload_button.label": "Tambahkan media", - "upload_form.undo": "Undo", - "upload_progress.label": "Mengunggah...", - "video_player.toggle_sound": "Suara", - "video_player.toggle_visible": "Tampilan", - "video_player.expand": "Tampilkan video", - "video_player.video_error": "Video tidak dapat diputar", -}; - -export default id; diff --git a/app/assets/javascripts/components/locales/index.jsx b/app/assets/javascripts/components/locales/index.jsx deleted file mode 100644 index 0c8472401..000000000 --- a/app/assets/javascripts/components/locales/index.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import ar from './ar'; -import en from './en'; -import de from './de'; -import es from './es'; -import fa from './fa'; -import he from './he'; -import hr from './hr'; -import hu from './hu'; -import io from './io'; -import it from './it'; -import fr from './fr'; -import nl from './nl'; -import no from './no'; -import oc from './oc'; -import pt from './pt'; -import pt_br from './pt-br'; -import uk from './uk'; -import fi from './fi'; -import eo from './eo'; -import ru from './ru'; -import ja from './ja'; -import zh_hk from './zh-hk'; -import zh_cn from './zh-cn'; -import bg from './bg'; -import id from './id'; - -const locales = { - ar, - en, - de, - es, - fa, - he, - hr, - hu, - io, - it, - fr, - nl, - no, - oc, - pt, - 'pt-BR': pt_br, - uk, - fi, - eo, - ru, - ja, - 'zh-HK': zh_hk, - 'zh-CN': zh_cn, - bg, - id, -}; - -export default function getMessagesForLocale (locale) { - return locales[locale]; -}; diff --git a/app/assets/javascripts/components/locales/io.jsx b/app/assets/javascripts/components/locales/io.jsx deleted file mode 100644 index 6715663aa..000000000 --- a/app/assets/javascripts/components/locales/io.jsx +++ /dev/null @@ -1,126 +0,0 @@ -const io = { - "account.block": "Blokusar @{name}", - "account.disclaimer": "Ca uzero esas de altra instaluro. Ca nombro forsan esas plu granda.", - "account.edit_profile": "Modifikar profilo", - "account.follow": "Sequar", - "account.followers": "Sequanti", - "account.follows_you": "Sequas tu", - "account.follows": "Sequas", - "account.mention": "Mencionar @{name}", - "account.mute": "Celar @{name}", - "account.posts": "Mesaji", - "account.report": "Denuncar @{name}", - "account.requested": "Vartante aprobo", - "account.unblock": "Desblokusar @{name}", - "account.unfollow": "Ne plus sequar", - "account.unmute": "Ne plus celar @{name}", - "boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo", - "column_back_button.label": "Retro", - "column.blocks": "Blokusita uzeri", - "column.community": "Lokala tempolineo", - "column.favourites": "Favorati", - "column.follow_requests": "Demandi di sequado", - "column.home": "Hemo", - "column.mutes": "Celita uzeri", - "column.notifications": "Savigi", - "column.public": "Federata tempolineo", - "compose_form.placeholder": "Quo esas en tua spirito?", - "compose_form.privacy_disclaimer": "Tua privata mesajo livresos a mencionata uzeri en {domains}. Ka tu fidas {domainsCount, plural, one {ta servero} other {ta serveri}}? Privateso di mesaji funcionas nur en instaluri di Mastodon. Se {domains} {domainsCount, plural, one {ne esas instaluro di Mastodon} other {ne esas instaluri di Mastodon}}, esos nula indiko, ke tua mesajo esas privata, ed ol povos repetesar od altre divenar videbla da nedezirinda recevanti.", - "compose_form.publish": "Siflar", - "compose_form.sensitive": "Markizar kontenajo kom trubliva", - "compose_form.spoiler_placeholder": "Averto di kontenajo", - "compose_form.spoiler": "Celar texto dop averto", - "emoji_button.label": "Insertar emoji", - "empty_column.community": "La lokala tempolineo esas vakua. Skribez ulo publike por iniciar la agiveso!", - "empty_column.hashtag": "Esas ankore nulo en ta gretovorto.", - "empty_column.home.public_timeline": "la publika tempolineo", - "empty_column.home": "Tu sequas ankore nulu. Vizitez {public} od uzez la serchilo por komencar e renkontrar altra uzeri.", - "empty_column.notifications": "Tu havas ankore nula savigo. Komunikez kun altri por debutar la konverso.", - "empty_column.public": "Esas nulo hike! Skribez ulo publike, o manuale sequez uzeri de altra instaluri por plenigar ol.", - "follow_request.authorize": "Yurizar", - "follow_request.reject": "Refuzar", - "getting_started.apps": "Apliki diversa esas disponebla", - "getting_started.heading": "Debuto", - "getting_started.open_source_notice": "Mastodon esas programaro kun apertita kodexo. Tu povas kontributar o signalar problemi en GitHub ye {github}. {apps}.", - "home.column_settings.advanced": "Komplexa", - "home.column_settings.basic": "Simpla", - "home.column_settings.filter_regex": "Ekfiltrar per reguloza expresuri", - "home.column_settings.show_reblogs": "Montrar repeti", - "home.column_settings.show_replies": "Montrar respondi", - "home.settings": "Aranji di la kolumno", - "lightbox.close": "Klozar", - "loading_indicator.label": "Kargante...", - "media_gallery.toggle_visible": "Chanjar videbleso", - "missing_indicator.label": "Ne trovita", - "navigation_bar.blocks": "Blokusita uzeri", - "navigation_bar.community_timeline": "Lokala tempolineo", - "navigation_bar.edit_profile": "Modifikar profilo", - "navigation_bar.favourites": "Favorati", - "navigation_bar.follow_requests": "Demandi di sequado", - "navigation_bar.info": "Detaloza informi", - "navigation_bar.logout": "Ekirar", - "navigation_bar.mutes": "Celita uzeri", - "navigation_bar.preferences": "Preferi", - "navigation_bar.public_timeline": "Federata tempolineo", - "notification.favourite": "{name} favorizis tua mesajo", - "notification.follow": "{name} sequeskis tu", - "notification.mention": "{name} mencionis tu", - "notification.reblog": "{name} repetis tua mesajo", - "notifications.clear_confirmation": "Ka tu esas certa, ke tu volas efacar omna tua savigi?", - "notifications.clear": "Efacar savigi", - "notifications.column_settings.alert": "Surtabla savigi", - "notifications.column_settings.favourite": "Favorati:", - "notifications.column_settings.follow": "Nova sequanti:", - "notifications.column_settings.mention": "Mencioni:", - "notifications.column_settings.reblog": "Repeti:", - "notifications.column_settings.show": "Montrar en kolumno", - "notifications.column_settings.sound": "Plear sono", - "notifications.settings": "Aranji di kolumno", - "privacy.change": "Aranjar privateso di mesaji", - "privacy.direct.long": "Sendar nur a mencionata uzeri", - "privacy.direct.short": "Direte", - "privacy.private.long": "Sendar nur a sequanti", - "privacy.private.short": "Private", - "privacy.public.long": "Sendar a publika tempolinei", - "privacy.public.short": "Publike", - "privacy.unlisted.long": "Ne montrar en publika tempolinei", - "privacy.unlisted.short": "Ne enlistigota", - "reply_indicator.cancel": "Nihiligar", - "report.heading": "Nova denunco", - "report.placeholder": "Plusa komenti", - "report.submit": "Sendar", - "report.target": "Denuncante", - "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}", - "search.placeholder": "Serchez", - "search.status_by": "Mesajo da {name}", - "status.delete": "Efacar", - "status.favourite": "Favorizar", - "status.load_more": "Kargar pluse", - "status.media_hidden": "Kontenajo celita", - "status.mention": "Mencionar @{name}", - "status.open": "Detaligar ca mesajo", - "status.reblog": "Repetar", - "status.reblogged_by": "{name} repetita", - "status.reply": "Respondar", - "status.replyAll": "Respondar a filo", - "status.report": "Denuncar @{name}", - "status.sensitive_toggle": "Kliktar por vidar", - "status.sensitive_warning": "Trubliva kontenajo", - "status.show_less": "Montrar mine", - "status.show_more": "Montrar plue", - "tabs_bar.compose": "Kompozar", - "tabs_bar.federated_timeline": "Federata", - "tabs_bar.home": "Hemo", - "tabs_bar.local_timeline": "Lokala", - "tabs_bar.notifications": "Savigi", - "upload_area.title": "Tranar faligar por kargar", - "upload_button.label": "Adjuntar kontenajo", - "upload_form.undo": "Desfacar", - "upload_progress.label": "Kargante...", - "video_player.toggle_sound": "Acendar sono", - "video_player.toggle_visible": "Chanjar videbleso", - "video_player.expand": "Extensar video", - "video_player.video_error": "Video ne povus pleesar", -}; - -export default io; diff --git a/app/assets/javascripts/components/locales/it.jsx b/app/assets/javascripts/components/locales/it.jsx deleted file mode 100644 index 04ff1311f..000000000 --- a/app/assets/javascripts/components/locales/it.jsx +++ /dev/null @@ -1,125 +0,0 @@ -const it = { - "account.block": "Blocca @{name}", - "account.disclaimer": "Questo utente si trova su un altro server. Questo numero potrebbe essere maggiore.", - "account.edit_profile": "Modifica profilo", - "account.follow": "Segui", - "account.followers": "Seguaci", - "account.follows_you": "Ti segue", - "account.follows": "Segue", - "account.mention": "Menziona @{name}", - "account.mute": "Silenzia @{name}", - "account.posts": "Posts", - "account.report": "Segnala @{name}", - "account.requested": "In attesa di approvazione", - "account.unblock": "Sblocca @{name}", - "account.unfollow": "Non seguire", - "account.unmute": "Non silenziare @{name}", - "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta", - "column_back_button.label": "Indietro", - "column.blocks": "Utenti bloccati", - "column.community": "Timeline locale", - "column.favourites": "Apprezzati", - "column.follow_requests": "Richieste di amicizia", - "column.home": "Home", - "column.mutes": "Utenti silenziati", - "column.notifications": "Notifiche", - "column.public": "Timeline federata", - "compose_form.placeholder": "A cosa stai pensando?", - "compose_form.privacy_disclaimer": "Il tuo status privato verrà condiviso con gli utenti menzionati su {domains}. Ti fidi di {domainsCount, plural, one {quel server} other {quei server}}? Le impostazioni sulla privacy valgono solo su server Mastodon. Se {domains} {domainsCount, plural, one {non è un server Mastodon} other {non sono server Mastodon}}, non ci saranno indicazioni sulla privacy del tuo status, e potrebbe essere condiviso o reso visibile a destinatari indesiderati.", - "compose_form.publish": "Toot", - "compose_form.sensitive": "Segnala file come sensibile", - "compose_form.spoiler_placeholder": "Content warning", - "compose_form.spoiler": "Nascondi testo con avvertimento", - "emoji_button.label": "Inserisci emoji", - "empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!", - "empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.", - "empty_column.home.public_timeline": "la timeline pubblica", - "empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.", - "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.", - "empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio.", - "follow_request.authorize": "Autorizza", - "follow_request.reject": "Rifiuta", - "getting_started.apps": "Sono disponibili diverse app", - "getting_started.heading": "Come iniziare", - "getting_started.open_source_notice": "Mastodon è un software open source. Puoi contribuire o segnalare errori su GitHub all'indirizzo {github}. {apps}.", - "home.column_settings.advanced": "Avanzato", - "home.column_settings.basic": "Semplice", - "home.column_settings.filter_regex": "Filtra con espressioni regolari", - "home.column_settings.show_reblogs": "Mostra post condivisi", - "home.column_settings.show_replies": "Mostra risposte", - "home.settings": "Impostazioni colonna", - "lightbox.close": "Chiudi", - "loading_indicator.label": "Carico...", - "media_gallery.toggle_visible": "Imposta visibilità", - "missing_indicator.label": "Non trovato", - "navigation_bar.blocks": "Utenti bloccati", - "navigation_bar.community_timeline": "Timeline locale", - "navigation_bar.edit_profile": "Modifica profilo", - "navigation_bar.favourites": "Apprezzati", - "navigation_bar.follow_requests": "Richieste di amicizia", - "navigation_bar.info": "Informazioni estese", - "navigation_bar.logout": "Logout", - "navigation_bar.mutes": "Utenti silenziati", - "navigation_bar.preferences": "Impostazioni", - "navigation_bar.public_timeline": "Timeline federata", - "notification.favourite": "{name} ha apprezzato il tuo post", - "notification.follow": "{name} ha iniziato a seguirti", - "notification.mention": "{name} ti ha menzionato", - "notification.reblog": "{name} ha condiviso il tuo post", - "notifications.clear_confirmation": "Vuoi davvero cancellare tutte le notifiche?", - "notifications.clear": "Cancella notifiche", - "notifications.column_settings.alert": "Notifiche desktop", - "notifications.column_settings.favourite": "Apprezzati:", - "notifications.column_settings.follow": "Nuovi seguaci:", - "notifications.column_settings.mention": "Menzioni:", - "notifications.column_settings.reblog": "Post condivisi:", - "notifications.column_settings.show": "Mostra in colonna", - "notifications.column_settings.sound": "Riproduci suono", - "notifications.settings": "Impostazioni colonna", - "privacy.change": "Modifica privacy post", - "privacy.direct.long": "Invia solo a utenti menzionati", - "privacy.direct.short": "Diretto", - "privacy.private.long": "Invia solo ai seguaci", - "privacy.private.short": "Privato", - "privacy.public.long": "Invia alla timeline pubblica", - "privacy.public.short": "Pubblico", - "privacy.unlisted.long": "Non mostrare sulla timeline pubblica", - "privacy.unlisted.short": "Non elencato", - "reply_indicator.cancel": "Annulla", - "report.heading": "Nuova segnalazione", - "report.placeholder": "Commenti aggiuntivi", - "report.submit": "Invia", - "report.target": "Invio la segnalazione", - "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}", - "search.placeholder": "Cerca", - "search.status_by": "Status per {name}", - "status.delete": "Elimina", - "status.favourite": "Apprezzato", - "status.load_more": "Mostra di più", - "status.media_hidden": "Allegato nascosto", - "status.mention": "Nomina @{name}", - "status.open": "Espandi questo post", - "status.reblog": "Condividi", - "status.reblogged_by": "{name} ha condiviso", - "status.reply": "Rispondi", - "status.report": "Segnala @{name}", - "status.sensitive_toggle": "Clicca per vedere", - "status.sensitive_warning": "Materiale sensibile", - "status.show_less": "Mostra meno", - "status.show_more": "Mostra di più", - "tabs_bar.compose": "Scrivi", - "tabs_bar.federated_timeline": "Federazione", - "tabs_bar.home": "Home", - "tabs_bar.local_timeline": "Locale", - "tabs_bar.notifications": "Notifiche", - "upload_area.title": "Trascina per caricare", - "upload_button.label": "Aggiungi file multimediale", - "upload_form.undo": "Annulla", - "upload_progress.label": "Sto caricando...", - "video_player.toggle_sound": "Attiva suono", - "video_player.toggle_visible": "Attiva visibilità", - "video_player.expand": "Espandi video", - "video_player.video_error": "Il video non può essere riprodotto", -}; - -export default it; \ No newline at end of file diff --git a/app/assets/javascripts/components/locales/ja.jsx b/app/assets/javascripts/components/locales/ja.jsx deleted file mode 100644 index 6a7536527..000000000 --- a/app/assets/javascripts/components/locales/ja.jsx +++ /dev/null @@ -1,167 +0,0 @@ -const ja = { - "account.block": "ブロック", - "account.disclaimer": "このユーザーは他のインスタンスに所属しているため、数字が正確で無い場合があります。", - "account.edit_profile": "プロフィールを編集", - "account.follow": "フォロー", - "account.followers": "フォロワー", - "account.follows": "フォロー", - "account.follows_you": "フォローされています", - "account.mention": "返信", - "account.mute": "ミュート", - "account.posts": "投稿", - "account.report": "通報", - "account.requested": "承認待ち", - "account.unblock": "ブロック解除", - "account.unfollow": "フォロー解除", - "account.unmute": "ミュート解除", - "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。", - "column.blocks": "ブロックしたユーザー", - "column.community": "ローカルタイムライン", - "column.favourites": "お気に入り", - "column.follow_requests": "フォローリクエスト", - "column.home": "ホーム", - "column.mutes": "ミュートしたユーザー", - "column.notifications": "通知", - "column.public": "連合タイムライン", - "column_back_button.label": "戻る", - "column_subheading.navigation": "ナビゲーション", - "column_subheading.settings": "設定", - "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。", - "compose_form.lock_disclaimer.lock": "非公開", - "compose_form.placeholder": "今なにしてる?", - "compose_form.privacy_disclaimer": "あなたの非公開トゥートは返信先ユーザーが所属する {domains} に送信されます。{domainsCount, plural, one {このサーバー} other {これらのサーバー}}は信頼できますか?投稿のプライバシー保護はMastodonサーバー内でのみ有効です。 {domains} {domainsCount, plural, one {がMastodonインスタンス} other {がMastodonインスタンス}}でない場合、あなたの投稿がプライベートなものとして扱われず、ブーストされたり予期しないユーザーに見られる可能性があります。", - "compose_form.publish": "トゥート", - "compose_form.sensitive": "メディアを閲覧注意としてマークする", - "compose_form.spoiler": "テキストを隠す", - "compose_form.spoiler_placeholder": "警告", - "confirmation_modal.cancel": "キャンセル", - "confirmations.block.confirm": "ブロック", - "confirmations.block.message": "本当に {name} をブロックしますか?", - "confirmations.delete.confirm": "削除", - "confirmations.delete.message": "本当に削除しますか?", - "confirmations.mute.confirm": "ミュート", - "confirmations.mute.message": "本当に {name} をミュートしますか?", - "emoji_button.label": "絵文字を追加", - "emoji_button.search": "検索...", - "emoji_button.people": "人々", - "emoji_button.nature": "自然", - "emoji_button.food": "食べ物", - "emoji_button.activity": "活動", - "emoji_button.travel": "旅行と場所", - "emoji_button.objects": "物", - "emoji_button.symbols": "記号", - "emoji_button.flags": "国旗", - "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!", - "empty_column.hashtag": "このハッシュタグはまだ使われていません。", - "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。", - "empty_column.home.public_timeline": "連合タイムライン", - "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", - "empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!", - "follow_request.authorize": "許可", - "follow_request.reject": "拒否", - "getting_started.apps": "さまざまなアプリで利用できます。", - "getting_started.heading": "スタート", - "getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub({github})から開発に参加したり、問題を報告したりできます。 {apps}", - "home.column_settings.advanced": "上級者向け", - "home.column_settings.basic": "シンプル", - "home.column_settings.filter_regex": "正規表現でフィルター", - "home.column_settings.show_reblogs": "ブースト表示", - "home.column_settings.show_replies": "返信表示", - "home.settings": "カラム設定", - "lightbox.close": "閉じる", - "loading_indicator.label": "読み込み中...", - "media_gallery.toggle_visible": "表示切り替え", - "missing_indicator.label": "見つかりません", - "navigation_bar.blocks": "ブロックしたユーザー", - "navigation_bar.community_timeline": "ローカルタイムライン", - "navigation_bar.edit_profile": "プロフィールを編集", - "navigation_bar.favourites": "お気に入り", - "navigation_bar.follow_requests": "フォローリクエスト", - "navigation_bar.info": "サーバー情報", - "navigation_bar.logout": "ログアウト", - "navigation_bar.mutes": "ミュートしたユーザー", - "navigation_bar.preferences": "ユーザー設定", - "navigation_bar.public_timeline": "連合タイムライン", - "notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました", - "notification.follow": "{name} さんにフォローされました", - "notification.mention": "{name} さんがあなたに返信しました", - "notification.reblog": "{name} さんがあなたのトゥートをブーストしました", - "notifications.clear": "通知を消去", - "notifications.clear_confirmation": "本当に通知を消去しますか?", - "notifications.column_settings.alert": "デスクトップ通知", - "notifications.column_settings.favourite": "お気に入り", - "notifications.column_settings.follow": "新しいフォロワー", - "notifications.column_settings.mention": "返信", - "notifications.column_settings.reblog": "ブースト", - "notifications.column_settings.show": "カラムに表示", - "notifications.column_settings.sound": "通知音を再生", - "notifications.settings": "カラム設定", - "onboarding.done": "完了", - "onboarding.next": "次へ", - "onboarding.page_one.welcome": "Mastodonへようこそ!", - "onboarding.page_one.federation": "Mastodonは誰でも参加できるSNSです。", - "onboarding.page_one.handle": "あなたは今数あるMastodonインスタンスの1つである{domain}にいます。あなたのフルハンドルは{handle}です。", - "onboarding.page_two.compose": "フォームから投稿できます。イメージや、公開範囲の設定や、表示時の警告の設定は下部のアイコンから行なえます。", - "onboarding.page_three.search": "検索バーで、{illustration}や{introductions}のように特定のハッシュタグの投稿を見たり、ユーザーを探したりできます。", - "onboarding.page_three.profile": "「プロフィールを編集」から、あなたの自己紹介や表示名を変更できます。またそこでは他の設定ができます。", - "onboarding.page_four.home": "「ホーム」タイムラインではあなたがフォローしている人の投稿を表示します。", - "onboarding.page_four.notifications": "「通知」ではあなたへの他の人からの関わりを表示します。", - "onboarding.page_five.public_timelines": "連合タイムラインでは{domain}の人がフォローしているMastodon全体での公開投稿を表示します。同じくローカルタイムラインでは{domain}のみの公開投稿を表示します。", - "onboarding.page_six.almost_done": "以上です。", - "onboarding.page_six.admin": "あなたのインスタンスの管理者は{admin}です。", - "onboarding.page_six.read_guidelines": "{guidelines}を読むことを忘れないようにしてください。", - "onboarding.page_six.guidelines": "コミュニティガイドライン", - "onboarding.page_six.github": "MastodonはOSSです。バグ報告や機能要望あるいは貢献を{github}から行なえます。", - "onboarding.page_six.apps_available": "iOS、Androidあるいは他のプラットフォームで使える{apps}があります。", - "onboarding.page_six.various_app": "様々なモバイルアプリ", - "onboarding.page_six.appetoot": "Bon Appetoot!", - "onboarding.skip": "スキップ", - "privacy.change": "投稿のプライバシーを変更", - "privacy.direct.long": "メンションしたユーザーだけに公開", - "privacy.direct.short": "ダイレクト", - "privacy.private.long": "フォロワーだけに公開", - "privacy.private.short": "非公開", - "privacy.public.long": "公開TLに投稿する", - "privacy.public.short": "公開", - "privacy.unlisted.long": "公開TLで表示しない", - "privacy.unlisted.short": "未収載", - "reply_indicator.cancel": "キャンセル", - "report.heading": "新規通報", - "report.placeholder": "コメント", - "report.submit": "通報する", - "report.target": "問題のユーザー", - "search.placeholder": "検索", - "search.status_by": "{name}からの投稿", - "search_results.total": "{count, number} 件の結果", - "status.cannot_reblog": "この投稿はブーストできません", - "status.delete": "削除", - "status.favourite": "お気に入り", - "status.load_more": "もっと見る", - "status.media_hidden": "非表示のメデイア", - "status.mention": "返信", - "status.open": "詳細を表示", - "status.reblog": "ブースト", - "status.reblogged_by": "{name} さんにブーストされました", - "status.reply": "返信", - "status.replyAll": "全員に返信", - "status.report": "通報", - "status.sensitive_toggle": "クリックして表示", - "status.sensitive_warning": "閲覧注意", - "status.show_less": "隠す", - "status.show_more": "もっと見る", - "tabs_bar.compose": "投稿", - "tabs_bar.federated_timeline": "連合", - "tabs_bar.home": "ホーム", - "tabs_bar.local_timeline": "ローカル", - "tabs_bar.notifications": "通知", - "upload_area.title": "ドラッグ&ドロップでアップロード", - "upload_button.label": "メディアを追加", - "upload_form.undo": "やり直す", - "upload_progress.label": "アップロード中…", - "video_player.expand": "動画の詳細", - "video_player.toggle_sound": "音の切り替え", - "video_player.toggle_visible": "表示切り替え", - "video_player.video_error": "動画の再生に失敗しました", -}; - -export default ja; diff --git a/app/assets/javascripts/components/locales/nl.jsx b/app/assets/javascripts/components/locales/nl.jsx deleted file mode 100644 index 388169cd5..000000000 --- a/app/assets/javascripts/components/locales/nl.jsx +++ /dev/null @@ -1,130 +0,0 @@ -const nl = { - "account.block": "Blokkeer @{name}", - "account.edit_profile": "Profiel bewerken", - "account.followers": "Volgers", - "account.follows": "Volgt", - "account.follows_you": "Volgt jou", - "account.follow": "Volgen", - "account.mention": "Vermeld @{name}", - "account.mute": "Negeer @{name}", - "account.posts": "Berichten", - "account.report": "Rapporteer @{name}", - "account.requested": "Wacht op goedkeuring", - "account.unblock": "Deblokkeer @{name}", - "account.unfollow": "Ontvolgen", - "account.unmute": "Negeer @{name} niet meer", - "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan", - "column_back_button.label": "terug", - "column.blocks": "Geblokkeerde gebruikers", - "column.community": "Lokale tijdlijn", - "column.favourites": "Favorieten", - "column.home": "Jouw tijdlijn", - "column.mutes": "Genegeerde gebruikers", - "column.notifications": "Meldingen", - "column.public": "Globale tijdlijn", - "column_subheading.navigation": "Navigatie", - "column_subheading.settings": "Instellingen", - "compose_form.placeholder": "Wat wil je kwijt?", - "compose_form.privacy_disclaimer": "Jouw privétoot wordt afgeleverd aan de vermelde gebruikers op {domains}. Vertrouw jij {domainsCount, plural, one {die server} other {die servers}}? Het privé plaatsen van toots werkt alleen op Mastodon-servers. Wanneer {domains} {domainsCount, plural, one {geen Mastodon-server is} other {geen Mastodon-servers zijn}}, dan wordt er niet aangegeven dat de toot privé is, waardoor het kan worden geboost of op een andere manier zichtbaar wordt gemaakt voor mensen waarvoor het niet was bedoeld.", - "compose_form.private": "Als privé markeren", - "compose_form.publish": "Toot", - "compose_form.sensitive": "Media als gevoelig markeren", - "compose_form.spoiler_placeholder": "Waarschuwingstekst", - "compose_form.spoiler": "Tekst achter waarschuwing verbergen", - "compose_form.unlisted": "Niet op openbare tijdlijnen tonen", - "emoji_button.activity": "Activiteiten", - "emoji_button.flags": "Vlaggen", - "emoji_button.food": "Eten en drinken", - "emoji_button.label": "Emoji toevoegen", - "emoji_button.nature": "Natuur", - "emoji_button.objects": "Voorwerpen", - "emoji_button.people": "Mensen", - "emoji_button.search": "Zoeken...", - "emoji_button.symbols": "Symbolen", - "emoji_button.travel": "Reizen en plekken", - "getting_started.about_addressing": "Je kunt mensen volgen als je hun gebruikersnaam en het domein van hun server kent. Voer hiervoor het e-mailachtige adres in het zoekveld in.", - "getting_started.about_shortcuts": "Als de gezochte gebruiker op hetzelfde domein zit als jijzelf, is invoeren van de gebruikersnaam genoeg. Dat geldt ook als je mensen in toots wilt vermelden.", - "getting_started.apps": "Er zijn meerdere apps beschikbaar", - "getting_started.heading": "Beginnen", - "getting_started.open_source_notice": "Mastodon is open-sourcesoftware. Je kunt bijdragen of problemen melden op GitHub via {github}. {apps}.", - "lightbox.close": "Sluiten", - "loading_indicator.label": "Laden…", - "navigation_bar.blocks": "Geblokkeerde gebruikers", - "navigation_bar.community_timeline": "Lokale tijdlijn", - "navigation_bar.edit_profile": "Profiel bewerken", - "navigation_bar.favourites": "Favorieten", - "navigation_bar.follow_requests": "Volgverzoeken", - "navigation_bar.info": "Uitgebreide informatie", - "navigation_bar.logout": "Afmelden", - "navigation_bar.mutes": "Genegeerde gebruikers", - "navigation_bar.preferences": "Instellingen", - "navigation_bar.public_timeline": "Globale tijdlijn", - "notification.favourite": "{name} markeerde jouw toot als favoriet", - "notification.follow": "{name} volgt jou nu", - "notification.mention": "{name} vermeldde jou", - "notification.reblog": "{name} boostte jouw toot", - "notifications.clear_confirmation": "Weet je zeker dat je al jouw meldingen wilt verwijderen?", - "notifications.clear": "Meldingen verwijderen", - "notifications.column_settings.alert": "Desktopmeldingen", - "notifications.column_settings.favourite": "Favorieten:", - "notifications.column_settings.follow": "Nieuwe volgers:", - "notifications.column_settings.mention": "Vermeldingen:", - "notifications.column_settings.reblog": "Boosts:", - "notifications.column_settings.show": "In kolom tonen", - "notifications.column_settings.sound": "Geluid afspelen", - "notifications.settings": "Kolom-instellingen", - "onboarding.next": "Volgende", - "onboarding.page_five.public_timelines": "De lokale tijdlijn toont openbare toots van iedereen op {domain}. De globale tijdlijn toont openbare toots van iedereen die door gebruikers van {domain} worden gevolgd, dus ook mensen van andere Mastodon-servers. Dit zijn de openbare tijdlijnen en vormen een uitstekende manier om nieuwe mensen te ontdekken.", - "onboarding.page_four.home": "Jouw tijdlijn laat toots zien van mensen die jij volgt.", - "onboarding.page_four.notifications": "De kolom met meldingen toont alle interacties die je met andere Mastodon-gebruikers hebt.", - "onboarding.page_one.federation": "Mastodon is een netwerk van onafhankelijke servers die samen een groot sociaal netwerk vormen.", - "onboarding.page_one.handle": "Je bevindt je nu op {domain}, dus is jouw volledige Mastodon-adres {handle}", - "onboarding.page_one.welcome": "Welkom op Mastodon!", - "onboarding.page_six.admin": "De beheerder van jouw Mastodon-server is {admin}.", - "onboarding.page_six.almost_done": "Bijna klaar...", - "onboarding.page_six.appetoot": "Veel succes!", - "onboarding.page_six.apps_available": "Er zijn {apps} beschikbaar voor iOS, Android en andere platformen.", - "onboarding.page_six.github": "Mastodon kost niets, en is open-source- en vrije software. Je kan bugs melden, nieuwe mogelijkheden aanvragen en als ontwikkelaar meewerken op {github}.", - "onboarding.page_six.guidelines": "communityrichtlijnen", - "onboarding.page_six.read_guidelines": "Vergeet niet de {guidelines} van {domain} te lezen!", - "onboarding.page_six.various_app": "mobiele apps", - "onboarding.page_three.profile": "Bewerk jouw profiel om jouw avatar, bio en weergavenaam te veranderen. Daar vind je ook andere instellingen.", - "onboarding.page_three.search": "Gebruik de zoekbalk linksboven om andere mensen op Mastodon te vinden en om te zoeken op hashtags, zoals {illustration} en {introductions}. Om iemand te vinden die niet op deze Mastodon-server zit, moet je het volledige Mastodon-adres van deze persoon invoeren.", - "onboarding.page_two.compose": "Schrijf berichten (wij noemen dit toots) in het tekstvak in de linkerkolom. Je kan met de pictogrammen daaronder afbeeldingen uploaden, privacy-instellingen veranderen en je tekst een waarschuwing meegeven.", - "onboarding.skip": "Overslaan", - "privacy.change": "Privacy toot aanpassen", - "privacy.direct.long": "Toot alleen naar vermelde gebruikers", - "privacy.direct.short": "Direct", - "privacy.private.long": "Alleen aan volgers tonen", - "privacy.private.short": "Alleen volgers", - "privacy.public.long": "Op openbare tijdlijnen tonen", - "privacy.public.short": "Openbaar", - "privacy.unlisted.long": "Niet op openbare tijdlijnen tonen", - "privacy.unlisted.short": "Minder openbaar", - "reply_indicator.cancel": "Annuleren", - "search.account": "Account", - "search.hashtag": "Hashtag", - "search.placeholder": "Zoeken", - "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}", - "status.delete": "Verwijderen", - "status.favourite": "Favoriet", - "status.mention": "@{name} vermelden", - "status.reblog": "Boost", - "status.reblogged_by": "{name} boostte", - "status.reply": "Reageren", - "status.sensitive_toggle": "Klik om te zien", - "status.sensitive_warning": "Gevoelige inhoud", - "status.show_less": "Minder tonen", - "status.show_more": "Meer tonen", - "tabs_bar.compose": "Schrijven", - "tabs_bar.home": "Jouw tijdlijn", - "tabs_bar.mentions": "Vermeldingen", - "tabs_bar.notifications": "Meldingen", - "tabs_bar.public": "Globale tijdlijn", - "upload_button.label": "Media toevoegen", - "upload_form.undo": "Ongedaan maken", - "video_player.toggle_sound": "Geluid in-/uitschakelen", - -}; - -export default nl; diff --git a/app/assets/javascripts/components/locales/no.jsx b/app/assets/javascripts/components/locales/no.jsx deleted file mode 100644 index 17a0c2099..000000000 --- a/app/assets/javascripts/components/locales/no.jsx +++ /dev/null @@ -1,130 +0,0 @@ -const no = { - "account.block": "Blokkér @{name}", - "account.disclaimer": "Denne brukeren er fra en annen instans. Dette tallet kan være høyere.", - "account.edit_profile": "Rediger profil", - "account.follow": "Følg", - "account.followers": "Følgere", - "account.follows_you": "Følger deg", - "account.follows": "Følger", - "account.mention": "Nevn @{name}", - "account.mute": "Demp @{name}", - "account.posts": "Innlegg", - "account.report": "Rapportér @{name}", - "account.requested": "Venter på godkjennelse", - "account.unblock": "Avblokker @{name}", - "account.unfollow": "Avfølg", - "account.unmute": "Avdemp @{name}", - "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang", - "column_back_button.label": "Tilbake", - "column.blocks": "Blokkerte brukere", - "column.community": "Lokal tidslinje", - "column.favourites": "Likt", - "column.follow_requests": "Følgeforespørsler", - "column.home": "Hjem", - "column.notifications": "Varslinger", - "column.public": "Felles tidslinje", - "compose_form.placeholder": "Hva har du på hjertet?", - "compose_form.privacy_disclaimer": "Din private status vil leveres til nevnte brukere på {domains}. Stoler du på {domainsCount, plural, one {den serveren} other {de serverne}}? Synlighet fungerer kun på Mastodon-instanser. Hvis {domains} {domainsCount, plural, one {ike er en Mastodon-instans} other {ikke er Mastodon-instanser}}, vil det ikke indikeres at posten din er privat, og den kan kanskje bli fremhevd eller på annen måte bli synlig for uventede mottakere.", - "compose_form.publish": "Tut", - "compose_form.sensitive": "Merk media som følsomt", - "compose_form.spoiler_placeholder": "Innholdsadvarsel", - "compose_form.spoiler": "Skjul tekst bak advarsel", - "emoji_button.label": "Sett inn emoji", - "empty_column.community": "Den lokale tidslinjen er tom. Skriv noe offentlig for å få snøballen til å rulle!", - "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.", - "empty_column.home.public_timeline": "en offentlig tidslinje", - "empty_column.home": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.", - "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.", - "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp", - "follow_request.authorize": "Autorisér", - "follow_request.reject": "Avvis", - "getting_started.apps": "Diverse apper er tilgjengelige", - "getting_started.heading": "Kom i gang", - "getting_started.open_source_notice": "Mastodon er fri programvare. Du kan bidra eller rapportere problemer på GitHub på {github}. {apps}.", - "home.column_settings.advanced": "Advansert", - "home.column_settings.basic": "Enkel", - "home.column_settings.filter_regex": "Filtrér med regulære uttrykk", - "home.column_settings.show_reblogs": "Vis fremhevinger", - "home.column_settings.show_replies": "Vis svar", - "home.settings": "Kolonneinnstillinger", - "lightbox.close": "Lukk", - "loading_indicator.label": "Laster...", - "media_gallery.toggle_visible": "Veksle synlighet", - "missing_indicator.label": "Ikke funnet", - "navigation_bar.blocks": "Blokkerte brukere", - "navigation_bar.community_timeline": "Lokal tidslinje", - "navigation_bar.edit_profile": "Rediger profil", - "navigation_bar.favourites": "Likt", - "navigation_bar.follow_requests": "Følgeforespørsler", - "navigation_bar.info": "Utvidet informasjon", - "navigation_bar.logout": "Logg ut", - "navigation_bar.preferences": "Preferanser", - "navigation_bar.public_timeline": "Felles tidslinje", - "notification.favourite": "{name} likte din status", - "notification.follow": "{name} fulgte deg", - "notification.reblog": "{name} fremhevde din status", - "notifications.clear_confirmation": "Er du sikker på at du vil fjerne alle dine varsler?", - "notifications.clear": "Fjern varsler", - "notifications.column_settings.alert": "Skrivebordsvarslinger", - "notifications.column_settings.favourite": "Likt:", - "notifications.column_settings.follow": "Nye følgere:", - "notifications.column_settings.mention": "Nevninger:", - "notifications.column_settings.reblog": "Fremhevinger:", - "notifications.column_settings.show": "Vis i kolonne", - "notifications.column_settings.sound": "Spill lyd", - "notifications.settings": "Kolonneinstillinger", - "privacy.change": "Justér synlighet", - "privacy.direct.long": "Post kun til nevnte brukere", - "privacy.direct.short": "Direkte", - "privacy.private.long": "Post kun til følgere", - "privacy.private.short": "Privat", - "privacy.public.long": "Post kun til offentlige tidslinjer", - "privacy.public.short": "Offentlig", - "privacy.unlisted.long": "Ikke vis i offentlige tidslinjer", - "privacy.unlisted.short": "Uoppført", - "reply_indicator.cancel": "Avbryt", - "report.heading": "Ny rapport", - "report.placeholder": "Tilleggskommentarer", - "report.submit": "Send inn", - "report.target": "Rapporterer", - "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}", - "search.placeholder": "Søk", - "search.status_by": "Status fra {name}", - "status.delete": "Slett", - "status.favourite": "Lik", - "status.load_more": "Last mer", - "status.media_hidden": "Media skjult", - "status.mention": "Nevn @{name}", - "status.open": "Utvid denne statusen", - "status.reblog": "Fremhev", - "status.reblogged_by": "Fremhevd av {name}", - "status.reply": "Svar", - "status.report": "Rapporter @{name}", - "status.sensitive_toggle": "Klikk for å vise", - "status.sensitive_warning": "Følsomt innhold", - "status.show_less": "Vis mindre", - "status.show_more": "Vis mer", - "tabs_bar.compose": "Komponer", - "tabs_bar.federated_timeline": "Felles", - "tabs_bar.home": "Hjem", - "tabs_bar.local_timeline": "Lokal", - "tabs_bar.notifications": "Varslinger", - "upload_area.title": "Dra og slipp for å laste opp", - "upload_button.label": "Legg til media", - "upload_form.undo": "Angre", - "upload_progress.label": "Laster opp...", - "video_player.toggle_sound": "Veksle lyd", - "video_player.toggle_visible": "Veksle synlighet", - "video_player.expand": "Utvid video", - "getting_started.about_addressing": "Du kan følge noen hvis du vet brukernavnet deres og domenet de er på ved å skrive en e-postadresse inn i søkeskjemaet.", - "getting_started.about_shortcuts": "Hvis målbrukeren er på samme domene som deg, vil kun brukernavnet også fungere. Den samme regelen gjelder når man nevner noen i statuser.", - "tabs_bar.mentions": "Nevninger", - "tabs_bar.public": "Felles tidslinje", - "compose_form.private": "Merk som privat", - "compose_form.unlisted": "Ikke vis på offentlige tidslinjer", - "search.account": "Konto", - "search.hashtag": "Hashtag", - "notification.mention": "{name} nevnte deg" -}; - -export default no; diff --git a/app/assets/javascripts/components/locales/oc.jsx b/app/assets/javascripts/components/locales/oc.jsx deleted file mode 100644 index 14f64e762..000000000 --- a/app/assets/javascripts/components/locales/oc.jsx +++ /dev/null @@ -1,128 +0,0 @@ -const oc = { - "column_back_button.label": "Tornar", - "lightbox.close": "Tampar", - "loading_indicator.label": "Cargament…", - "status.mention": "Mencionar", - "status.delete": "Escafar", - "status.reply": "Respondre", - "status.reblog": "Partejar", - "status.favourite": "Apondre als favorits", - "status.reblogged_by": "{name} a partejat :", - "status.sensitive_warning": "Contengut embarrassant", - "status.sensitive_toggle": "Clicar per mostrar", - "status.show_more": "Desplegar", - "status.show_less": "Tornar plegar", - "status.open": "Desplegar aqueste estatut", - "status.report": "Senhalar @{name}", - "status.load_more": "Cargar mai", - "status.media_hidden": "Mèdia rescondut", - "video_player.toggle_sound": "Activar/Desactivar lo son", - "video_player.toggle_visible": "Mostrar/Rescondre la vidèo", - "account.mention": "Mencionar", - "account.edit_profile": "Modificar lo perfil", - "account.unblock": "Desblocar", - "account.unfollow": "Quitar de sègre", - "account.block": "Blocar", - "account.mute": "Rescondre", - "account.unmute": "Quitar de rescondre", - "account.follow": "Sègre", - "account.posts": "Estatuts", - "account.follows": "Abonaments", - "account.followers": "Abonats", - "account.follows_you": "Vos sèc", - "account.requested": "Invitacion mandada", - "account.report": "Senhalar", - "account.disclaimer": "Aqueste compte es sus una autra instància. Los nombres pòdon èsser mai grandes.", - "getting_started.heading": "Per començar", - "getting_started.about_addressing": "Podètz sègre los estatuts de qualqu’un en picant son identificant e lo domeni de l’instància separat amb un @ coma una adreça de corrièl dins lo camp de recèrca.", - "getting_started.about_shortcuts": "S’aquesta persona emplega la meteissa instància que vos l’identifican basta. Atal foncionan tanben las mencions dins vòstres estatuts.", - "getting_started.about_developer": "Per sègre lo desvolopaire d’aqueste projècte : Gargron@mastodon.social", - "getting_started.open_source_notice": "Mastodon es un logicial liure. Podètz contribuir e mandar vòstres comentaris e rapòrt de bug via{github} sus GitHub.", - "column.home": "Acuèlh", - "column.community": "Fil public local", - "column.public": "Fil public global", - "column.notifications": "Notificacions", - "column.blocks": "Personas blocadas", - "column.favourites": "Favorits", - "column.follow_requests": "Demandas d’abonament", - "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.", - "empty_column.public": "I a pas res aquí ! Escribètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo fil public.", - "empty_column.home": "Pel moment segètz pas segun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.", - "empty_column.home.public_timeline": "lo fil public", - "empty_column.community": "Lo fil public local es void. Escribètz quicòm per lo garnir !", - "empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag", - "tabs_bar.compose": "Compausar", - "tabs_bar.home": "Acuèlh", - "tabs_bar.mentions": "Mencions", - "tabs_bar.public": "Fil public global", - "tabs_bar.notifications": "Notifications", - "tabs_bar.local_timeline": "Fil public local", - "tabs_bar.federated_timeline": "Fil public global", - "compose_form.placeholder": "A de qué pensatz ?", - "compose_form.publish": "Tut", - "compose_form.sensitive": "Marcar lo mèdia coma embarrassant", - "compose_form.spoiler": "Rescondre lo tèxte darrièr un avertiment", - "compose_form.spoiler_placeholder": "Avertiment", - "compose_form.private": "Far venir privat", - "compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz d’aqueste{domainsCount, plural, one { servidor} other {s servidors}} per divulgar pas vòstre estatut ? Los estatuts privats foncionan pas que sus las instàncias a Mastodons. Se {domains} {domainsCount, plural, one {es pas una instància a Mastodon} other {son pas d'instàncias a Mastodon}}, i aurà pas d’indicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists", - "compose_form.unlisted": "Mostrar pas dins los fils publics", - "emoji_button.label": "Inserir un emoji", - "navigation_bar.edit_profile": "Modificar lo perfil", - "navigation_bar.preferences": "Preferéncias", - "navigation_bar.community_timeline": "Fil public local", - "navigation_bar.public_timeline": "Fil public global", - "navigation_bar.blocks": "Personas blocadas", - "navigation_bar.favourites": "Favorits", - "navigation_bar.info": "Mai informacions", - "navigation_bar.logout": "Desconnexion", - "navigation_bar.follow_requests": "Demandas d'abonament", - "reply_indicator.cancel": "Anullar", - "search.placeholder": "Recercar", - "search.account": "Compte", - "search.hashtag": "Mot-clau", - "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}", - "search.status_by": "Estatuts de {name}", - "upload_button.label": "Apondre un mèdia", - "upload_form.undo": "Anullar", - "upload_progress.label": "Mandadís…", - "upload_area.title": "Lisatz e depausatz per mandar", - "notification.follow": "{name} vos sèc.", - "notification.favourite": "{name} a apondut a sos favorits :", - "notification.reblog": "{name} a partejat vòstre estatut :", - "notification.mention": "{name} vos a mencionat :", - "notifications.column_settings.alert": "Notificacions localas", - "notifications.column_settings.show": "Mostrar dins la colomna", - "notifications.column_settings.sound": "Emetre un son", - "notifications.column_settings.follow": "Nòus abonats :", - "notifications.column_settings.favourite": "Favorits :", - "notifications.column_settings.mention": "Mencions :", - "notifications.column_settings.reblog": "Partatges :", - "notifications.clear": "Levar", - "notifications.clear_confirmation": "Volètz vertadièrament levar totas vòstras las notificacions ?", - "notifications.settings": "Paramètres de la colomna", - "privacy.public.short": "Public", - "privacy.public.long": "Mostrar dins los fils publics", - "privacy.unlisted.short": "Pas-listat", - "privacy.unlisted.long": "Mostrar pas dins los fils publics", - "privacy.private.short": "Privat", - "privacy.private.long": "Mostrar pas qu'a vòstres abonats", - "privacy.direct.short": "Dirècte", - "privacy.direct.long": "Mostrar pas qu'a las personas mencionadas", - "privacy.change": "Ajustar la confidencialitat del messatge", - "media_gallery.toggle_visible": "Modificar la visibilitat", - "missing_indicator.label": "Pas trobat", - "follow_request.authorize": "Autorizar", - "follow_request.reject": "Regetar", - "home.settings": "Paramètres de la colomna", - "home.column_settings.basic": "Basic", - "home.column_settings.show_reblogs": "Mostrar los partatges", - "home.column_settings.show_replies": "Mostrar las responsas", - "home.column_settings.advanced": "Avançat", - "home.column_settings.filter_regex": "Filtrar amb una expression racionala", - "report.heading": "Nòu senhalament", - "report.placeholder": "Comentaris addicionals", - "report.submit": "Mandat", - "report.target": "Senhalament" -}; - -export default oc; diff --git a/app/assets/javascripts/components/locales/pt-br.jsx b/app/assets/javascripts/components/locales/pt-br.jsx deleted file mode 100644 index 91c74bb19..000000000 --- a/app/assets/javascripts/components/locales/pt-br.jsx +++ /dev/null @@ -1,125 +0,0 @@ -const pt_br = { - "account.block": "Bloquear @{name}", - "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.", - "account.edit_profile": "Editar perfil", - "account.follow": "Seguir", - "account.followers": "Seguidores", - "account.follows_you": "É teu seguidor", - "account.follows": "Segue", - "account.mention": "Mencionar @{name}", - "account.mute": "Silenciar @{name}", - "account.posts": "Posts", - "account.report": "Denunciar @{name}", - "account.requested": "A aguardar aprovação", - "account.unblock": "Não bloquear @{name}", - "account.unfollow": "Deixar de seguir", - "account.unmute": "Não silenciar @{name}", - "boost_modal.combo": "Pode clicar {combo} para não voltar a ver", - "column_back_button.label": "Voltar", - "column.blocks": "Utilizadores Bloqueados", - "column.community": "Local", - "column.favourites": "Favoritos", - "column.follow_requests": "Seguidores Pendentes", - "column.home": "Home", - "column.mutes": "Utilizadores silenciados", - "column.notifications": "Notificações", - "column.public": "Global", - "compose_form.placeholder": "Em que estás a pensar?", - "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.", - "compose_form.publish": "Publicar", - "compose_form.sensitive": "Marcar media como conteúdo sensível", - "compose_form.spoiler_placeholder": "Aviso de conteúdo", - "compose_form.spoiler": "Esconder texto com aviso", - "emoji_button.label": "Inserir Emoji", - "empty_column.community": "Ainda não existem conteúdo local para mostrar!", - "empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag", - "empty_column.home.public_timeline": "global", - "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.", - "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.", - "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.", - "follow_request.authorize": "Autorizar", - "follow_request.reject": "Rejeitar", - "getting_started.apps": "Existem várias aplicações disponíveis", - "getting_started.heading": "Primeiros passos", - "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.", - "home.column_settings.advanced": "Avançado", - "home.column_settings.basic": "Básico", - "home.column_settings.filter_regex": "Filtrar com uma expressão regular", - "home.column_settings.show_reblogs": "Mostrar as partilhas", - "home.column_settings.show_replies": "Mostrar as respostas", - "home.settings": "Parâmetros da listagem Home", - "lightbox.close": "Fechar", - "loading_indicator.label": "Carregando...", - "media_gallery.toggle_visible": "Esconder/Mostrar", - "missing_indicator.label": "Não encontrado", - "navigation_bar.blocks": "Utilizadores bloqueados", - "navigation_bar.community_timeline": "Local", - "navigation_bar.edit_profile": "Editar perfil", - "navigation_bar.favourites": "Favoritos", - "navigation_bar.follow_requests": "Seguidores pendentes", - "navigation_bar.info": "Mais informações", - "navigation_bar.logout": "Sair", - "navigation_bar.mutes": "Utilizadores silenciados", - "navigation_bar.preferences": "Preferências", - "navigation_bar.public_timeline": "Global", - "notification.favourite": "{name} adicionou o teu post aos favoritos", - "notification.follow": "{name} seguiu-te", - "notification.mention": "{name} mencionou-te", - "notification.reblog": "{name} partilhou o teu post", - "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?", - "notifications.clear": "Limpar notificações", - "notifications.column_settings.alert": "Notificações no computador", - "notifications.column_settings.favourite": "Favoritos:", - "notifications.column_settings.follow": "Novos seguidores:", - "notifications.column_settings.mention": "Menções:", - "notifications.column_settings.reblog": "Partilhas:", - "notifications.column_settings.show": "Mostrar nas colunas", - "notifications.column_settings.sound": "Reproduzir som", - "notifications.settings": "Parâmetros da listagem de Notificações", - "privacy.change": "Ajustar a privacidade da mensagem", - "privacy.direct.long": "Apenas para utilizadores mencionados", - "privacy.direct.short": "Directo", - "privacy.private.long": "Apenas para os seguidores", - "privacy.private.short": "Privado", - "privacy.public.long": "Publicar em todos os feeds", - "privacy.public.short": "Público", - "privacy.unlisted.long": "Não publicar nos feeds públicos", - "privacy.unlisted.short": "Não listar", - "reply_indicator.cancel": "Cancelar", - "report.heading": "Nova denúncia", - "report.placeholder": "Comentários adicionais", - "report.submit": "Enviar", - "report.target": "Denunciar", - "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", - "search.placeholder": "Pesquisar", - "search.status_by": "Post de {name}", - "status.delete": "Eliminar", - "status.favourite": "Adicionar aos favoritos", - "status.load_more": "Carregar mais", - "status.media_hidden": "Media escondida", - "status.mention": "Mencionar @{name}", - "status.open": "Expandir", - "status.reblog": "Partilhar", - "status.reblogged_by": "{name} partilhou", - "status.reply": "Responder", - "status.report": "Denúnciar @{name}", - "status.sensitive_toggle": "Clique para ver", - "status.sensitive_warning": "Conteúdo sensível", - "status.show_less": "Mostrar menos", - "status.show_more": "Mostrar mais", - "tabs_bar.compose": "Criar", - "tabs_bar.federated_timeline": "Global", - "tabs_bar.home": "Home", - "tabs_bar.local_timeline": "Local", - "tabs_bar.notifications": "Notificações", - "upload_area.title": "Arraste e solte para enviar", - "upload_button.label": "Adicionar media", - "upload_form.undo": "Anular", - "upload_progress.label": "A gravar...", - "video_player.toggle_sound": "Ligar/Desligar som", - "video_player.toggle_visible": "Ligar/Desligar vídeo", - "video_player.expand": "Expandir vídeo", - "video_player.video_error": "Não é possível ver o vídeo", -}; - -export default pt_br; diff --git a/app/assets/javascripts/components/locales/pt.jsx b/app/assets/javascripts/components/locales/pt.jsx deleted file mode 100644 index 03095d20d..000000000 --- a/app/assets/javascripts/components/locales/pt.jsx +++ /dev/null @@ -1,125 +0,0 @@ -const pt = { - "account.block": "Bloquear @{name}", - "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.", - "account.edit_profile": "Editar perfil", - "account.follow": "Seguir", - "account.followers": "Seguidores", - "account.follows_you": "É teu seguidor", - "account.follows": "Segue", - "account.mention": "Mencionar @{name}", - "account.mute": "Silenciar @{name}", - "account.posts": "Posts", - "account.report": "Denunciar @{name}", - "account.requested": "A aguardar aprovação", - "account.unblock": "Não bloquear @{name}", - "account.unfollow": "Deixar de seguir", - "account.unmute": "Não silenciar @{name}", - "boost_modal.combo": "Pode clicar {combo} para não voltar a ver", - "column_back_button.label": "Voltar", - "column.blocks": "Utilizadores Bloqueados", - "column.community": "Local", - "column.favourites": "Favoritos", - "column.follow_requests": "Seguidores Pendentes", - "column.home": "Home", - "column.mutes": "Utilizadores silenciados", - "column.notifications": "Notificações", - "column.public": "Global", - "compose_form.placeholder": "Em que estás a pensar?", - "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.", - "compose_form.publish": "Publicar", - "compose_form.sensitive": "Marcar media como conteúdo sensível", - "compose_form.spoiler_placeholder": "Aviso de conteúdo", - "compose_form.spoiler": "Esconder texto com aviso", - "emoji_button.label": "Inserir Emoji", - "empty_column.community": "Ainda não existem conteúdo local para mostrar!", - "empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag", - "empty_column.home.public_timeline": "global", - "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.", - "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.", - "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.", - "follow_request.authorize": "Autorizar", - "follow_request.reject": "Rejeitar", - "getting_started.apps": "Existem várias aplicações disponíveis", - "getting_started.heading": "Primeiros passos", - "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.", - "home.column_settings.advanced": "Avançado", - "home.column_settings.basic": "Básico", - "home.column_settings.filter_regex": "Filtrar com uma expressão regular", - "home.column_settings.show_reblogs": "Mostrar as partilhas", - "home.column_settings.show_replies": "Mostrar as respostas", - "home.settings": "Parâmetros da listagem Home", - "lightbox.close": "Fechar", - "loading_indicator.label": "Carregando...", - "media_gallery.toggle_visible": "Esconder/Mostrar", - "missing_indicator.label": "Não encontrado", - "navigation_bar.blocks": "Utilizadores bloqueados", - "navigation_bar.community_timeline": "Local", - "navigation_bar.edit_profile": "Editar perfil", - "navigation_bar.favourites": "Favoritos", - "navigation_bar.follow_requests": "Seguidores pendentes", - "navigation_bar.info": "Mais informações", - "navigation_bar.logout": "Sair", - "navigation_bar.mutes": "Utilizadores silenciados", - "navigation_bar.preferences": "Preferências", - "navigation_bar.public_timeline": "Global", - "notification.favourite": "{name} adicionou o teu post aos favoritos", - "notification.follow": "{name} seguiu-te", - "notification.mention": "{name} mencionou-te", - "notification.reblog": "{name} partilhou o teu post", - "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?", - "notifications.clear": "Limpar notificações", - "notifications.column_settings.alert": "Notificações no computador", - "notifications.column_settings.favourite": "Favoritos:", - "notifications.column_settings.follow": "Novos seguidores:", - "notifications.column_settings.mention": "Menções:", - "notifications.column_settings.reblog": "Partilhas:", - "notifications.column_settings.show": "Mostrar nas colunas", - "notifications.column_settings.sound": "Reproduzir som", - "notifications.settings": "Parâmetros da listagem de Notificações", - "privacy.change": "Ajustar a privacidade da mensagem", - "privacy.direct.long": "Apenas para utilizadores mencionados", - "privacy.direct.short": "Directo", - "privacy.private.long": "Apenas para os seguidores", - "privacy.private.short": "Privado", - "privacy.public.long": "Publicar em todos os feeds", - "privacy.public.short": "Público", - "privacy.unlisted.long": "Não publicar nos feeds públicos", - "privacy.unlisted.short": "Não listar", - "reply_indicator.cancel": "Cancelar", - "report.heading": "Nova denúncia", - "report.placeholder": "Comentários adicionais", - "report.submit": "Enviar", - "report.target": "Denunciar", - "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", - "search.placeholder": "Pesquisar", - "search.status_by": "Post de {name}", - "status.delete": "Eliminar", - "status.favourite": "Adicionar aos favoritos", - "status.load_more": "Carregar mais", - "status.media_hidden": "Media escondida", - "status.mention": "Mencionar @{name}", - "status.open": "Expandir", - "status.reblog": "Partilhar", - "status.reblogged_by": "{name} partilhou", - "status.reply": "Responder", - "status.report": "Denúnciar @{name}", - "status.sensitive_toggle": "Clique para ver", - "status.sensitive_warning": "Conteúdo sensível", - "status.show_less": "Mostrar menos", - "status.show_more": "Mostrar mais", - "tabs_bar.compose": "Criar", - "tabs_bar.federated_timeline": "Global", - "tabs_bar.home": "Home", - "tabs_bar.local_timeline": "Local", - "tabs_bar.notifications": "Notificações", - "upload_area.title": "Arraste e solte para enviar", - "upload_button.label": "Adicionar media", - "upload_form.undo": "Anular", - "upload_progress.label": "A gravar...", - "video_player.toggle_sound": "Ligar/Desligar som", - "video_player.toggle_visible": "Ligar/Desligar vídeo", - "video_player.expand": "Expandir vídeo", - "video_player.video_error": "Não é possível ver o vídeo", -}; - -export default pt; diff --git a/app/assets/javascripts/components/locales/ru.jsx b/app/assets/javascripts/components/locales/ru.jsx deleted file mode 100644 index a1c9044bf..000000000 --- a/app/assets/javascripts/components/locales/ru.jsx +++ /dev/null @@ -1,138 +0,0 @@ -const ru = { - "account.block": "Блокировать", - "account.disclaimer": "Это пользователь с другого узла. Число может быть больше.", - "account.edit_profile": "Изменить профиль", - "account.follow": "Подписаться", - "account.followers": "Подписаны", - "account.follows": "Подписки", - "account.follows_you": "Подписан(а) на Вас", - "account.mention": "Упомянуть", - "account.mute": "Заглушить", - "account.posts": "Посты", - "account.report": "Пожаловаться", - "account.requested": "Ожидает подтверждения", - "account.unblock": "Разблокировать", - "account.unfollow": "Отписаться", - "account.unmute": "Снять глушение", - "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз", - "column.blocks": "Список блокировки", - "column.community": "Локальная лента", - "column.favourites": "Понравившееся", - "column.follow_requests": "Запросы на подписку", - "column.home": "Главная", - "column.mutes": "Список глушения", - "column.notifications": "Уведомления", - "column.public": "Глобальная лента", - "column_back_button.label": "Назад", - "column_subheading.navigation": "Навигация", - "column_subheading.settings": "Настройки", - "compose_form.placeholder": "О чем Вы думаете?", - "compose_form.privacy_disclaimer": "Ваш приватный статус будет доставлен упомянутым пользователям на доменах {domains}. Доверяете ли вы {domainsCount, plural, one {этому серверу} other {этим серверам}}? Приватность постов работает только на узлах Mastodon. Если {domains} {domainsCount, plural, one {не является узлом Mastodon} other {не являются узлами Mastodon}}, приватность поста не будет указана, и он может оказаться продвинут или иным образом показан не обозначенным Вами пользователям.", - "compose_form.publish": "Трубить", - "compose_form.sensitive": "Отметить как чувствительный контент", - "compose_form.spoiler": "Скрыть текст за предупреждением", - "compose_form.spoiler_placeholder": "Предупреждение о скрытом тексте", - "emoji_button.activity": "Занятия", - "emoji_button.flags": "Флаги", - "emoji_button.food": "Еда и напитки", - "emoji_button.label": "Вставить эмодзи", - "emoji_button.nature": "Природа", - "emoji_button.objects": "Предметы", - "emoji_button.people": "Люди", - "emoji_button.search": "Найти...", - "emoji_button.symbols": "Символы", - "emoji_button.travel": "Путешествия", - "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!", - "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.", - "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.", - "empty_column.home.public_timeline": "публичные ленты", - "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.", - "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.", - "follow_request.authorize": "Авторизовать", - "follow_request.reject": "Отказать", - "getting_started.apps": "Доступны различные приложения.", - "getting_started.heading": "Добро пожаловать", - "getting_started.open_source_notice": "Mastodon - программа с открытым исходным кодом. Вы можете помочь проекту или сообщить о проблемах на GitHub по адресу {github}. {apps}.", - "home.column_settings.advanced": "Дополнительные", - "home.column_settings.basic": "Основные", - "home.column_settings.filter_regex": "Отфильтровать регулярным выражением", - "home.column_settings.show_reblogs": "Показывать продвижения", - "home.column_settings.show_replies": "Показывать ответы", - "home.settings": "Настройки колонки", - "lightbox.close": "Закрыть", - "loading_indicator.label": "Загрузка...", - "media_gallery.toggle_visible": "Показать/скрыть", - "missing_indicator.label": "Не найдено", - "navigation_bar.blocks": "Список блокировки", - "navigation_bar.community_timeline": "Локальная лента", - "navigation_bar.edit_profile": "Изменить профиль", - "navigation_bar.favourites": "Понравившееся", - "navigation_bar.follow_requests": "Запросы на подписку", - "navigation_bar.info": "Об узле", - "navigation_bar.logout": "Выйти", - "navigation_bar.mutes": "Список глушения", - "navigation_bar.preferences": "Опции", - "navigation_bar.public_timeline": "Глобальная лента", - "notification.favourite": "{name} понравился Ваш статус", - "notification.follow": "{name} подписался(-лась) на Вас", - "notification.mention": "{name} упомянул(а) Вас", - "notification.reblog": "{name} продвинул(а) Ваш статус", - "notifications.clear": "Очистить уведомления", - "notifications.clear_confirmation": "Вы уверены, что хотите очистить все уведомления?", - "notifications.column_settings.alert": "Десктопные уведомления", - "notifications.column_settings.favourite": "Нравится:", - "notifications.column_settings.follow": "Новые подписчики:", - "notifications.column_settings.mention": "Упоминания:", - "notifications.column_settings.reblog": "Продвижения:", - "notifications.column_settings.show": "Показывать в колонке", - "notifications.column_settings.sound": "Проигрывать звук", - "notifications.settings": "Настройки колонки", - "privacy.change": "Изменить видимость статуса", - "privacy.direct.long": "Показать только упомянутым", - "privacy.direct.short": "Направленный", - "privacy.private.long": "Показать только подписчикам", - "privacy.private.short": "Приватный", - "privacy.public.long": "Показать в публичных лентах", - "privacy.public.short": "Публичный", - "privacy.unlisted.long": "Не показывать в лентах", - "privacy.unlisted.short": "Скрытый", - "reply_indicator.cancel": "Отмена", - "report.heading": "Новая жалоба", - "report.placeholder": "Комментарий", - "report.submit": "Отправить", - "report.target": "Жалуемся на", - "search.placeholder": "Поиск", - "search.status_by": "Статус от {name}", - "search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}", - "status.cannot_reblog": "Этот статус не может быть продвинут", - "status.delete": "Удалить", - "status.favourite": "Нравится", - "status.load_more": "Показать еще", - "status.media_hidden": "Медиаконтент скрыт", - "status.mention": "Упомянуть @{name}", - "status.open": "Развернуть статус", - "status.reblog": "Продвинуть", - "status.reblogged_by": "{name} продвинул(а)", - "status.reply": "Ответить", - "status.replyAll": "Ответить на тред", - "status.report": "Пожаловаться", - "status.sensitive_toggle": "Нажмите для просмотра", - "status.sensitive_warning": "Чувствительный контент", - "status.show_less": "Свернуть", - "status.show_more": "Развернуть", - "tabs_bar.compose": "Написать", - "tabs_bar.federated_timeline": "Глобальная", - "tabs_bar.home": "Главная", - "tabs_bar.local_timeline": "Локальная", - "tabs_bar.notifications": "Уведомления", - "upload_area.title": "Перетащите сюда, чтобы загрузить", - "upload_button.label": "Добавить медиаконтент", - "upload_form.undo": "Отменить", - "upload_progress.label": "Загрузка...", - "video_player.expand": "Развернуть видео", - "video_player.toggle_sound": "Вкл./выкл. звук", - "video_player.toggle_visible": "Показать/скрыть", - "video_player.video_error": "Видео не может быть проиграно", -}; - -export default ru; diff --git a/app/assets/javascripts/components/locales/uk.jsx b/app/assets/javascripts/components/locales/uk.jsx deleted file mode 100644 index 84a348c21..000000000 --- a/app/assets/javascripts/components/locales/uk.jsx +++ /dev/null @@ -1,57 +0,0 @@ -const uk = { - "column_back_button.label": "Назад", - "lightbox.close": "Закрити", - "loading_indicator.label": "Завантаження...", - "status.mention": "Згадати", - "status.delete": "Видалити", - "status.reply": "Відповісти", - "status.reblog": "Передмухнути", - "status.favourite": "Подобається", - "status.reblogged_by": "{name} передмухнув(-ла)", - "status.sensitive_warning": "Непристойний зміст", - "status.sensitive_toggle": "Натисніть, щоб подивитися", - "video_player.toggle_sound": "Увімкнути/вимкнути звук", - "account.mention": "Згадати", - "account.edit_profile": "Налаштування профілю", - "account.unblock": "Розблокувати", - "account.unfollow": "Відписатися", - "account.block": "Заблокувати", - "account.follow": "Підписатися", - "account.posts": "Пости", - "account.follows": "Підписки", - "account.followers": "Підписники", - "account.follows_you": "Підписаний", - "getting_started.heading": "Ласкаво просимо", - "getting_started.about_addressing": "Ви можете підписуватись на людей, якщо ви знаєте їх ім'я користувача чи домен, шляхом введення email-подібної адреси у верхньому рядку бокової панелі.", - "getting_started.about_shortcuts": "Якщо користувач, якого ви шукаєте, знаходиться на тому ж домені, що й ви, можна просто ввести ім'я користувача. Це правило стосується й згадування людей у статусах.", - "getting_started.about_developer": "Розробник проекту знаходиться за адресою Gargron@mastodon.social", - "column.home": "Головна", - "column.mentions": "Згадування", - "column.public": "Стіна", - "column.notifications": "Сповіщення", - "tabs_bar.compose": "Написати", - "tabs_bar.home": "Головна", - "tabs_bar.mentions": "Згадування", - "tabs_bar.public": "Стіна", - "tabs_bar.notifications": "Сповіщення", - "compose_form.placeholder": "Що у Вас на думці?", - "compose_form.publish": "Дмухнути", - "compose_form.sensitive": "Непристойний зміст", - "compose_form.unlisted": "Таємний режим", - "navigation_bar.edit_profile": "Редагувати профіль", - "navigation_bar.preferences": "Налаштування", - "navigation_bar.public_timeline": "Публічна стіна", - "navigation_bar.logout": "Вийти", - "reply_indicator.cancel": "Відмінити", - "search.placeholder": "Пошук", - "search.account": "Аккаунт", - "search.hashtag": "Хештеґ", - "upload_button.label": "Додати медіа", - "upload_form.undo": "Відмінити", - "notification.follow": "{name} підписався(-лась) на Вас", - "notification.favourite": "{name} сподобався ваш допис", - "notification.reblog": "{name} передмухнув(-ла) Ваш статус", - "notification.mention": "{name} згадав(-ла) Вас" -}; - -export default uk; diff --git a/app/assets/javascripts/components/locales/zh-cn.jsx b/app/assets/javascripts/components/locales/zh-cn.jsx deleted file mode 100644 index ee4eee427..000000000 --- a/app/assets/javascripts/components/locales/zh-cn.jsx +++ /dev/null @@ -1,157 +0,0 @@ -import zh from 'react-intl/locale-data/zh'; - -const localeData = zh.reduce(function (acc, localeData) { - if (localeData.locale === "zh-Hans-CN") { - // rename the locale "zh-Hans-CN" as "zh-CN" - // (match the code usually used in Accepted-Language header) - acc.push(Object.assign({}, - localeData, - { - "locale": "zh-CN", - "parentLocale": "zh-Hans-CN", - } - )); - } - return acc; -}, []); - -export { localeData as localeData }; - -const zh_cn = { - "account.block": "屏蔽 @{name}", - "account.disclaimer": "由于这个账户处于另一个服务站,实际数字会比这个更多。", - "account.edit_profile": "修改个人资料", - "account.follow": "关注", - "account.followers": "关注者", - "account.follows_you": "关注你", - "account.follows": "正关注", - "account.mention": "提及 @{name}", - "account.mute": "将 @{name} 静音", - "account.posts": "嘟文", - "account.report": "举报 @{name}", - "account.requested": "等候审批", - "account.unblock": "解除对 @{name} 的屏蔽", - "account.unfollow": "取消关注", - "account.unmute": "取消 @{name} 的静音", - "boost_modal.combo": "如你想在下次路过时显示,请按{combo},", - "column_back_button.label": "返回", - "column.blocks": "屏蔽用户", - "column.community": "本站时间轴", - // intentional departure from existing "推文" translation for posts: - // "推文" refers to "推特", the official translation for Twitter. - // Currently using a semi-phonetic translation "嘟", which refers - // to train horn sounds, for "toot". - "column.favourites": "赞过的嘟文", - "column.follow_requests": "关注请求", - "column.home": "主页", - "column.notifications": "通知", - "column.public": "跨站公共时间轴", - "compose_form.placeholder": "在想啥?", - "compose_form.privacy_disclaimer": "你的私人嘟文,将被发送至你所提及的 {domains} 用户。你是否信任{domainsCount, plural, one {这个网站} other {这些网站}}?请留意,嘟文隐私设置只适用于各 Mastodon 服务站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服务站} other {之中有些不是 Mastodon 服务站}},对方将无法收到这篇嘟文的隐私设置,然后可能被转嘟给不能预知的用户阅读。", - "compose_form.private": "标示为“只有关注你的人能看”", - // Going "toot-toot" here below. - "compose_form.publish": "嘟嘟", - "compose_form.sensitive": "将媒体文件标示为“敏感内容”", - "compose_form.spoiler_placeholder": "敏感内容的警告消息", - "compose_form.spoiler": "将部分文本藏于警告消息之后", - "compose_form.unlisted": "请勿在公共时间轴显示", - "emoji_button.label": "加入表情符号", - "empty_column.community": "本站时间轴暂时未有内容,快贴文来抢头香啊!", - "empty_column.hashtag": "这个标签暂时未有内容。", - "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。", - "empty_column.home.public_timeline": "公共时间轴", - "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。", - "empty_column.notifications": "你没有任何通知纪录,快向其他用户搭讪吧。", - "empty_column.public": "跨站公共时间轴暂时没有内容!快写一些公共的嘟文,或者关注另一些服务站的用户吧!你和本站、友站的交流,将决定这里出现的内容。", - "follow_request.authorize": "批准", - "follow_request.reject": "拒绝", - "getting_started.about_addressing": "只要你知道一位用户的用户名称和域名,你可以用“@用户名称@域名”的格式在搜索栏寻找该用户。", - "getting_started.about_shortcuts": "只要该用户是在你现在的服务站开立,你就可以直接输入用户名搜索。在嘟文中提及别的用户也是如此。", - "getting_started.apps": "手机或桌面应用程序", - "getting_started.heading": "开始使用", - "getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。你亦可通过{apps}阅读 Mastodon 上的消息。", - "home.column_settings.advanced": "高端", - "home.column_settings.basic": "基本", - "home.column_settings.filter_regex": "使用正则表达式 (regex) 过滤", - "home.column_settings.show_reblogs": "显示被转的嘟文", - "home.column_settings.show_replies": "显示回应嘟文", - "home.settings": "字段设置", - "lightbox.close": "关闭", - "loading_indicator.label": "加载中……", - "media_gallery.toggle_visible": "打开或关上", - "missing_indicator.label": "找不到内容", - "navigation_bar.blocks": "被屏蔽的用户", - "navigation_bar.community_timeline": "本站时间轴", - "navigation_bar.edit_profile": "修改个人资料", - "navigation_bar.favourites": "赞的内容", - "navigation_bar.follow_requests": "关注请求", - "navigation_bar.info": "关于本服务站", - "navigation_bar.logout": "注销", - // intentional departure from https://github.com/tootsuite/mastodon/blob/f864fee1/config/locales/zh-CN.yml#L126: - // clashes for settings/preferences - "navigation_bar.preferences": "首选项", - "navigation_bar.public_timeline": "跨站公共时间轴", - "notification.favourite": "{name} 赞你的嘟文", - "notification.follow": "{name} 开始关注你", - "notification.mention": "{name} 提及你", - "notification.reblog": "{name} 转嘟你的嘟文", - "notifications.clear_confirmation": "你确定要清空通知纪录吗?", - "notifications.clear": "清空通知纪录", - "notifications.column_settings.alert": "显示桌面通知", - "notifications.column_settings.favourite": "赞你的嘟文:", - "notifications.column_settings.follow": "关注你:", - "notifications.column_settings.mention": "提及你:", - "notifications.column_settings.reblog": "转你的嘟文:", - "notifications.column_settings.show": "在通知栏显示", - "notifications.column_settings.sound": "播放音效", - "notifications.settings": "字段设置", - "privacy.change": "调整隐私设置", - "privacy.direct.long": "只有提及的用户能看到", - "privacy.direct.short": "私人消息", - "privacy.private.long": "只有关注你用户能看到", - "privacy.private.short": "关注者", - "privacy.public.long": "在公共时间轴显示", - "privacy.public.short": "公共", - "privacy.unlisted.long": "公开,但不在公共时间轴显示", - "privacy.unlisted.short": "公开", - "reply_indicator.cancel": "取消", - "report.heading": "举报", - "report.placeholder": "额外消息", - "report.submit": "提交", - "report.target": "Reporting", - "search_results.total": "{count, number} 项结果", - "search.account": "用户", - "search.hashtag": "标签", - "search.placeholder": "搜索", - "search.status_by": "按{name}搜索嘟文", - "status.delete": "删除", - "status.favourite": "赞", - "status.load_more": "加载更多", - "status.media_hidden": "隐藏媒体内容", - "status.mention": "提及 @{name}", - "status.open": "展开嘟文", - "status.reblog": "转嘟", - "status.reblogged_by": "{name} 转嘟", - "status.reply": "回应", - "status.report": "举报 @{name}", - "status.sensitive_toggle": "点击显示", - "status.sensitive_warning": "敏感内容", - "status.show_less": "减少显示", - "status.show_more": "显示更多", - "tabs_bar.compose": "撰写", - "tabs_bar.federated_timeline": "跨站", - "tabs_bar.home": "主页", - "tabs_bar.local_timeline": "本站", - "tabs_bar.mentions": "提及", - "tabs_bar.notifications": "通知", - "tabs_bar.public": "跨站公共时间轴", - "upload_area.title": "将文件拖放至此上传", - "upload_button.label": "上传媒体文件", - "upload_form.undo": "还原", - "upload_progress.label": "上传中……", - "video_player.expand": "展开影片", - "video_player.toggle_sound": "开关音效", - "video_player.toggle_visible": "打开或关上", -}; - -export default zh_cn; diff --git a/app/assets/javascripts/components/locales/zh-hk.jsx b/app/assets/javascripts/components/locales/zh-hk.jsx deleted file mode 100644 index 3ecb4737b..000000000 --- a/app/assets/javascripts/components/locales/zh-hk.jsx +++ /dev/null @@ -1,150 +0,0 @@ -import zh from 'react-intl/locale-data/zh'; - -const localeData = zh.reduce(function (acc, localeData) { - if (localeData.locale === "zh-Hant-HK") { - // rename the locale "zh-Hant-HK" as "zh-HK" - // (match the code usually used in Accepted-Language header) - acc.push(Object.assign({}, - localeData, - { - "locale": "zh-HK", - "parentLocale": "zh-Hant-HK", - } - )); - } - return acc; -}, []); - -export { localeData as localeData }; - -const zh_hk = { - "account.block": "封鎖 @{name}", - "account.disclaimer": "由於這個用戶在另一個服務站,實際數字會比這個更多。", - "account.edit_profile": "修改個人資料", - "account.follow": "關注", - "account.followers": "關注的人", - "account.follows_you": "關注你", - "account.follows": "正在關注", - "account.mention": "提及 @{name}", - "account.mute": "將 @{name} 靜音", - "account.posts": "文章", - "account.report": "舉報 @{name}", - "account.requested": "等候審批", - "account.unblock": "解除對 @{name} 的封鎖", - "account.unfollow": "取消關注", - "account.unmute": "取消 @{name} 的靜音", - "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},", - "column_back_button.label": "返回", - "column.blocks": "封鎖用戶", - "column.community": "本站時間軸", - "column.favourites": "喜歡的文章", - "column.follow_requests": "關注請求", - "column.home": "主頁", - "column.notifications": "通知", - "column.public": "跨站公共時間軸", - "compose_form.placeholder": "你在想甚麼?", - "compose_form.privacy_disclaimer": "你的私人文章,將被遞送至你所提及的 {domains} 用戶。你是否信任{domainsCount, plural, one {這個網站} other {這些網站}}?請留意,文章私隱設定只適用於各 Mastodon 服務站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服務站} other {之中有些不是 Mastodon 服務站}},對方將無法收到這篇文章的私隱設定,然後可能被轉推給不能預知的用戶閱讀。", - "compose_form.private": "標示為「只有關注你的人能看」", - "compose_form.publish": "發文", - "compose_form.sensitive": "將媒體檔案標示為「敏感內容」", - "compose_form.spoiler_placeholder": "敏感警告訊息", - "compose_form.spoiler": "將部份文字藏於警告訊息之後", - "compose_form.unlisted": "請勿在公共時間軸顯示", - "emoji_button.label": "加入表情符號", - "empty_column.community": "本站時間軸暫時未有內容,快貼文來搶頭香啊!", - "empty_column.hashtag": "這個標籤暫時未有內容。", - "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。", - "empty_column.home.public_timeline": "公共時間軸", - "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。", - "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。", - "empty_column.public": "跨站公共時間軸暫時沒有內容!快寫一些公共的文章,或者關注另一些服務站的用戶吧!你和本站、友站的交流,將決定這裏出現的內容。", - "follow_request.authorize": "批准", - "follow_request.reject": "拒絕", - "getting_started.about_addressing": "只要你知道一位用戶的用戶名稱和域名,你可以用「@用戶名稱@域名」的格式在搜尋欄尋找該用戶。", - "getting_started.about_shortcuts": "只要該用戶是在你現在的服務站開立,你可以直接輸入用戶𠱷搜尋。同樣的規則適用於在文章提及別的用戶。", - "getting_started.apps": "手機或桌面應用程式", - "getting_started.heading": "開始使用", - "getting_started.open_source_notice": "Mastodon 是一個開放源碼的軟件。你可以在官方 GitHub ({github}) 貢獻或者回報問題。你亦可透過{apps}閱讀 Mastodon 上的消息。", - "home.column_settings.advanced": "進階", - "home.column_settings.basic": "基本", - "home.column_settings.filter_regex": "使用正規表達式 (regular expression) 過濾", - "home.column_settings.show_reblogs": "顯示被轉推的文章", - "home.column_settings.show_replies": "顯示回應文章", - "home.settings": "欄位設定", - "lightbox.close": "Close", - "loading_indicator.label": "載入中...", - "media_gallery.toggle_visible": "打開或關上", - "missing_indicator.label": "找不到內容", - "navigation_bar.blocks": "被封鎖的用戶", - "navigation_bar.community_timeline": "本站時間軸", - "navigation_bar.edit_profile": "修改個人資料", - "navigation_bar.favourites": "喜歡的內容", - "navigation_bar.follow_requests": "關注請求", - "navigation_bar.info": "關於本服務站", - "navigation_bar.logout": "登出", - "navigation_bar.preferences": "偏好設定", - "navigation_bar.public_timeline": "跨站公共時間軸", - "notification.favourite": "{name} 喜歡你的文章", - "notification.follow": "{name} 開始關注你", - "notification.mention": "{name} 提及你", - "notification.reblog": "{name} 轉推你的文章", - "notifications.clear_confirmation": "你確定要清空通知紀錄嗎?", - "notifications.clear": "清空通知紀錄", - "notifications.column_settings.alert": "顯示桌面通知", - "notifications.column_settings.favourite": "喜歡你的文章:", - "notifications.column_settings.follow": "關注你:", - "notifications.column_settings.mention": "提及你:", - "notifications.column_settings.reblog": "轉推你的文章:", - "notifications.column_settings.show": "在通知欄顯示", - "notifications.column_settings.sound": "播放音效", - "notifications.settings": "欄位設定", - "privacy.change": "調整私隱設定", - "privacy.direct.long": "只有提及的用戶能看到", - "privacy.direct.short": "私人訊息", - "privacy.private.long": "只有關注你用戶能看到", - "privacy.private.short": "關注者", - "privacy.public.long": "在公共時間軸顯示", - "privacy.public.short": "公共", - "privacy.unlisted.long": "公開,但不在公共時間軸顯示", - "privacy.unlisted.short": "公開", - "reply_indicator.cancel": "取消", - "report.heading": "舉報", - "report.placeholder": "額外訊息", - "report.submit": "提交", - "report.target": "Reporting", - "search_results.total": "{count, number} 項結果", - "search.account": "用戶", - "search.hashtag": "標籤", - "search.placeholder": "搜尋", - "search.status_by": "按{name}搜尋文章", - "status.delete": "刪除", - "status.favourite": "喜歡", - "status.load_more": "載入更多", - "status.media_hidden": "隱藏媒體內容", - "status.mention": "提及 @{name}", - "status.open": "展開文章", - "status.reblog": "轉推", - "status.reblogged_by": "{name} 轉推", - "status.reply": "回應", - "status.report": "舉報 @{name}", - "status.sensitive_toggle": "點擊顯示", - "status.sensitive_warning": "敏感內容", - "status.show_less": "減少顯示", - "status.show_more": "顯示更多", - "tabs_bar.compose": "撰寫", - "tabs_bar.federated_timeline": "跨站", - "tabs_bar.home": "主頁", - "tabs_bar.local_timeline": "本站", - "tabs_bar.mentions": "提及", - "tabs_bar.notifications": "通知", - "tabs_bar.public": "跨站公共時間軸", - "upload_area.title": "將檔案拖放至此上載", - "upload_button.label": "上載媒體檔案", - "upload_form.undo": "還原", - "upload_progress.label": "上載中……", - "video_player.expand": "展開影片", - "video_player.toggle_sound": "開關音效", - "video_player.toggle_visible": "打開或關上", -}; - -export default zh_hk; diff --git a/app/assets/javascripts/components/middleware/errors.jsx b/app/assets/javascripts/components/middleware/errors.jsx deleted file mode 100644 index 9a51257cb..000000000 --- a/app/assets/javascripts/components/middleware/errors.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import { showAlert } from '../actions/alerts'; - -const defaultSuccessSuffix = 'SUCCESS'; -const defaultFailSuffix = 'FAIL'; - -export default function errorsMiddleware() { - return ({ dispatch }) => next => action => { - if (action.type && !action.skipAlert) { - const isFail = new RegExp(`${defaultFailSuffix}$`, 'g'); - const isSuccess = new RegExp(`${defaultSuccessSuffix}$`, 'g'); - - if (action.type.match(isFail)) { - if (action.error.response) { - const { data, status, statusText } = action.error.response; - - let message = statusText; - let title = `${status}`; - - if (data.error) { - message = data.error; - } - - dispatch(showAlert(title, message)); - } else { - console.error(action.error); // eslint-disable-line no-console - dispatch(showAlert('Oops!', 'An unexpected error occurred.')); - } - } - } - - return next(action); - }; -}; diff --git a/app/assets/javascripts/components/middleware/loading_bar.jsx b/app/assets/javascripts/components/middleware/loading_bar.jsx deleted file mode 100644 index a98f1bb2b..000000000 --- a/app/assets/javascripts/components/middleware/loading_bar.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { showLoading, hideLoading } from 'react-redux-loading-bar'; - -const defaultTypeSuffixes = ['PENDING', 'FULFILLED', 'REJECTED']; - -export default function loadingBarMiddleware(config = {}) { - const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypeSuffixes; - - return ({ dispatch }) => next => (action) => { - if (action.type && !action.skipLoading) { - const [PENDING, FULFILLED, REJECTED] = promiseTypeSuffixes; - - const isPending = new RegExp(`${PENDING}$`, 'g'); - const isFulfilled = new RegExp(`${FULFILLED}$`, 'g'); - const isRejected = new RegExp(`${REJECTED}$`, 'g'); - - if (action.type.match(isPending)) { - dispatch(showLoading()); - } else if (action.type.match(isFulfilled) || action.type.match(isRejected)) { - dispatch(hideLoading()); - } - } - - return next(action); - }; -}; diff --git a/app/assets/javascripts/components/middleware/sounds.jsx b/app/assets/javascripts/components/middleware/sounds.jsx deleted file mode 100644 index 200efa3d7..000000000 --- a/app/assets/javascripts/components/middleware/sounds.jsx +++ /dev/null @@ -1,22 +0,0 @@ -const play = audio => { - if (!audio.paused) { - audio.pause(); - audio.fastSeek(0); - } - - audio.play(); -}; - -export default function soundsMiddleware() { - const soundCache = { - boop: new Audio(['/sounds/boop.mp3']) - }; - - return ({ dispatch }) => next => (action) => { - if (action.meta && action.meta.sound && soundCache[action.meta.sound]) { - play(soundCache[action.meta.sound]); - } - - return next(action); - }; -}; diff --git a/app/assets/javascripts/components/reducers/accounts.jsx b/app/assets/javascripts/components/reducers/accounts.jsx deleted file mode 100644 index 60d283b0f..000000000 --- a/app/assets/javascripts/components/reducers/accounts.jsx +++ /dev/null @@ -1,131 +0,0 @@ -import { - ACCOUNT_FETCH_SUCCESS, - FOLLOWERS_FETCH_SUCCESS, - FOLLOWERS_EXPAND_SUCCESS, - FOLLOWING_FETCH_SUCCESS, - FOLLOWING_EXPAND_SUCCESS, - ACCOUNT_TIMELINE_FETCH_SUCCESS, - ACCOUNT_TIMELINE_EXPAND_SUCCESS, - FOLLOW_REQUESTS_FETCH_SUCCESS, - FOLLOW_REQUESTS_EXPAND_SUCCESS, - ACCOUNT_FOLLOW_SUCCESS, - ACCOUNT_UNFOLLOW_SUCCESS -} from '../actions/accounts'; -import { - BLOCKS_FETCH_SUCCESS, - BLOCKS_EXPAND_SUCCESS -} from '../actions/blocks'; -import { - MUTES_FETCH_SUCCESS, - MUTES_EXPAND_SUCCESS -} from '../actions/mutes'; -import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose'; -import { - REBLOG_SUCCESS, - UNREBLOG_SUCCESS, - FAVOURITE_SUCCESS, - UNFAVOURITE_SUCCESS, - REBLOGS_FETCH_SUCCESS, - FAVOURITES_FETCH_SUCCESS -} from '../actions/interactions'; -import { - TIMELINE_REFRESH_SUCCESS, - TIMELINE_UPDATE, - TIMELINE_EXPAND_SUCCESS -} from '../actions/timelines'; -import { - STATUS_FETCH_SUCCESS, - CONTEXT_FETCH_SUCCESS -} from '../actions/statuses'; -import { SEARCH_FETCH_SUCCESS } from '../actions/search'; -import { - NOTIFICATIONS_UPDATE, - NOTIFICATIONS_REFRESH_SUCCESS, - NOTIFICATIONS_EXPAND_SUCCESS -} from '../actions/notifications'; -import { - FAVOURITED_STATUSES_FETCH_SUCCESS, - FAVOURITED_STATUSES_EXPAND_SUCCESS -} from '../actions/favourites'; -import { STORE_HYDRATE } from '../actions/store'; -import Immutable from 'immutable'; - -const normalizeAccount = (state, account) => state.set(account.id, Immutable.fromJS(account)); - -const normalizeAccounts = (state, accounts) => { - accounts.forEach(account => { - state = normalizeAccount(state, account); - }); - - return state; -}; - -const normalizeAccountFromStatus = (state, status) => { - state = normalizeAccount(state, status.account); - - if (status.reblog && status.reblog.account) { - state = normalizeAccount(state, status.reblog.account); - } - - return state; -}; - -const normalizeAccountsFromStatuses = (state, statuses) => { - statuses.forEach(status => { - state = normalizeAccountFromStatus(state, status); - }); - - return state; -}; - -const initialState = Immutable.Map(); - -export default function accounts(state = initialState, action) { - switch(action.type) { - case STORE_HYDRATE: - return state.merge(action.state.get('accounts')); - case ACCOUNT_FETCH_SUCCESS: - case NOTIFICATIONS_UPDATE: - return normalizeAccount(state, action.account); - case FOLLOWERS_FETCH_SUCCESS: - case FOLLOWERS_EXPAND_SUCCESS: - case FOLLOWING_FETCH_SUCCESS: - case FOLLOWING_EXPAND_SUCCESS: - case REBLOGS_FETCH_SUCCESS: - case FAVOURITES_FETCH_SUCCESS: - case COMPOSE_SUGGESTIONS_READY: - case FOLLOW_REQUESTS_FETCH_SUCCESS: - case FOLLOW_REQUESTS_EXPAND_SUCCESS: - case BLOCKS_FETCH_SUCCESS: - case BLOCKS_EXPAND_SUCCESS: - case MUTES_FETCH_SUCCESS: - case MUTES_EXPAND_SUCCESS: - return normalizeAccounts(state, action.accounts); - case NOTIFICATIONS_REFRESH_SUCCESS: - case NOTIFICATIONS_EXPAND_SUCCESS: - case SEARCH_FETCH_SUCCESS: - return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses); - case TIMELINE_REFRESH_SUCCESS: - case TIMELINE_EXPAND_SUCCESS: - case ACCOUNT_TIMELINE_FETCH_SUCCESS: - case ACCOUNT_TIMELINE_EXPAND_SUCCESS: - case CONTEXT_FETCH_SUCCESS: - case FAVOURITED_STATUSES_FETCH_SUCCESS: - case FAVOURITED_STATUSES_EXPAND_SUCCESS: - return normalizeAccountsFromStatuses(state, action.statuses); - case REBLOG_SUCCESS: - case FAVOURITE_SUCCESS: - case UNREBLOG_SUCCESS: - case UNFAVOURITE_SUCCESS: - return normalizeAccountFromStatus(state, action.response); - case TIMELINE_UPDATE: - case STATUS_FETCH_SUCCESS: - return normalizeAccountFromStatus(state, action.status); - case ACCOUNT_FOLLOW_SUCCESS: - return state.updateIn([action.relationship.id, 'followers_count'], num => num + 1); - case ACCOUNT_UNFOLLOW_SUCCESS: - return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1)); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/alerts.jsx b/app/assets/javascripts/components/reducers/alerts.jsx deleted file mode 100644 index dc0145824..000000000 --- a/app/assets/javascripts/components/reducers/alerts.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { - ALERT_SHOW, - ALERT_DISMISS, - ALERT_CLEAR -} from '../actions/alerts'; -import Immutable from 'immutable'; - -const initialState = Immutable.List([]); - -export default function alerts(state = initialState, action) { - switch(action.type) { - case ALERT_SHOW: - return state.push(Immutable.Map({ - key: state.size > 0 ? state.last().get('key') + 1 : 0, - title: action.title, - message: action.message - })); - case ALERT_DISMISS: - return state.filterNot(item => item.get('key') === action.alert.key); - case ALERT_CLEAR: - return state.clear(); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/cards.jsx b/app/assets/javascripts/components/reducers/cards.jsx deleted file mode 100644 index 3c9395011..000000000 --- a/app/assets/javascripts/components/reducers/cards.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import { STATUS_CARD_FETCH_SUCCESS } from '../actions/cards'; - -import Immutable from 'immutable'; - -const initialState = Immutable.Map(); - -export default function cards(state = initialState, action) { - switch(action.type) { - case STATUS_CARD_FETCH_SUCCESS: - return state.set(action.id, Immutable.fromJS(action.card)); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx deleted file mode 100644 index c87384780..000000000 --- a/app/assets/javascripts/components/reducers/compose.jsx +++ /dev/null @@ -1,232 +0,0 @@ -import { - COMPOSE_MOUNT, - COMPOSE_UNMOUNT, - COMPOSE_CHANGE, - COMPOSE_REPLY, - COMPOSE_REPLY_CANCEL, - COMPOSE_MENTION, - COMPOSE_SUBMIT_REQUEST, - COMPOSE_SUBMIT_SUCCESS, - COMPOSE_SUBMIT_FAIL, - COMPOSE_UPLOAD_REQUEST, - COMPOSE_UPLOAD_SUCCESS, - COMPOSE_UPLOAD_FAIL, - COMPOSE_UPLOAD_UNDO, - COMPOSE_UPLOAD_PROGRESS, - COMPOSE_SUGGESTIONS_CLEAR, - COMPOSE_SUGGESTIONS_READY, - COMPOSE_SUGGESTION_SELECT, - COMPOSE_SENSITIVITY_CHANGE, - COMPOSE_SPOILERNESS_CHANGE, - COMPOSE_SPOILER_TEXT_CHANGE, - COMPOSE_VISIBILITY_CHANGE, - COMPOSE_LISTABILITY_CHANGE, - COMPOSE_EMOJI_INSERT -} from '../actions/compose'; -import { TIMELINE_DELETE } from '../actions/timelines'; -import { STORE_HYDRATE } from '../actions/store'; -import Immutable from 'immutable'; -import uuid from '../uuid'; - -const initialState = Immutable.Map({ - mounted: false, - sensitive: false, - spoiler: false, - spoiler_text: '', - privacy: null, - text: '', - focusDate: null, - preselectDate: null, - in_reply_to: null, - is_submitting: false, - is_uploading: false, - progress: 0, - media_attachments: Immutable.List(), - suggestion_token: null, - suggestions: Immutable.List(), - me: null, - default_privacy: 'public', - resetFileKey: Math.floor((Math.random() * 0x10000)), - idempotencyKey: null -}); - -function statusToTextMentions(state, status) { - let set = Immutable.OrderedSet([]); - let me = state.get('me'); - - if (status.getIn(['account', 'id']) !== me) { - set = set.add(`@${status.getIn(['account', 'acct'])} `); - } - - return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join(''); -}; - -function clearAll(state) { - return state.withMutations(map => { - map.set('text', ''); - map.set('spoiler', false); - map.set('spoiler_text', ''); - map.set('is_submitting', false); - map.set('in_reply_to', null); - map.set('privacy', state.get('default_privacy')); - map.set('sensitive', false); - map.update('media_attachments', list => list.clear()); - map.set('idempotencyKey', uuid()); - }); -}; - -function appendMedia(state, media) { - return state.withMutations(map => { - map.update('media_attachments', list => list.push(media)); - map.set('is_uploading', false); - map.set('resetFileKey', Math.floor((Math.random() * 0x10000))); - map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`); - map.set('focusDate', new Date()); - map.set('idempotencyKey', uuid()); - }); -}; - -function removeMedia(state, mediaId) { - const media = state.get('media_attachments').find(item => item.get('id') === mediaId); - const prevSize = state.get('media_attachments').size; - - return state.withMutations(map => { - map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId)); - map.update('text', text => text.replace(media.get('text_url'), '').trim()); - map.set('idempotencyKey', uuid()); - - if (prevSize === 1) { - map.set('sensitive', false); - } - }); -}; - -const insertSuggestion = (state, position, token, completion) => { - return state.withMutations(map => { - map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`); - map.set('suggestion_token', null); - map.update('suggestions', Immutable.List(), list => list.clear()); - map.set('focusDate', new Date()); - map.set('idempotencyKey', uuid()); - }); -}; - -const insertEmoji = (state, position, emojiData) => { - const emoji = emojiData.shortname; - - return state.withMutations(map => { - map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`); - map.set('focusDate', new Date()); - map.set('idempotencyKey', uuid()); - }); -}; - -const privacyPreference = (a, b) => { - if (a === 'direct' || b === 'direct') { - return 'direct'; - } else if (a === 'private' || b === 'private') { - return 'private'; - } else if (a === 'unlisted' || b === 'unlisted') { - return 'unlisted'; - } else { - return 'public'; - } -}; - -export default function compose(state = initialState, action) { - switch(action.type) { - case STORE_HYDRATE: - return clearAll(state.merge(action.state.get('compose'))); - case COMPOSE_MOUNT: - return state.set('mounted', true); - case COMPOSE_UNMOUNT: - return state.set('mounted', false); - case COMPOSE_SENSITIVITY_CHANGE: - return state - .set('sensitive', !state.get('sensitive')) - .set('idempotencyKey', uuid()); - case COMPOSE_SPOILERNESS_CHANGE: - return state.withMutations(map => { - map.set('spoiler_text', ''); - map.set('spoiler', !state.get('spoiler')); - map.set('idempotencyKey', uuid()); - }); - case COMPOSE_SPOILER_TEXT_CHANGE: - return state - .set('spoiler_text', action.text) - .set('idempotencyKey', uuid()); - case COMPOSE_VISIBILITY_CHANGE: - return state - .set('privacy', action.value) - .set('idempotencyKey', uuid()); - case COMPOSE_CHANGE: - return state - .set('text', action.text) - .set('idempotencyKey', uuid()); - case COMPOSE_REPLY: - return state.withMutations(map => { - map.set('in_reply_to', action.status.get('id')); - map.set('text', statusToTextMentions(state, action.status)); - map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); - map.set('focusDate', new Date()); - map.set('preselectDate', new Date()); - map.set('idempotencyKey', uuid()); - - if (action.status.get('spoiler_text').length > 0) { - map.set('spoiler', true); - map.set('spoiler_text', action.status.get('spoiler_text')); - } else { - map.set('spoiler', false); - map.set('spoiler_text', ''); - } - }); - case COMPOSE_REPLY_CANCEL: - return state.withMutations(map => { - map.set('in_reply_to', null); - map.set('text', ''); - map.set('spoiler', false); - map.set('spoiler_text', ''); - map.set('privacy', state.get('default_privacy')); - map.set('idempotencyKey', uuid()); - }); - case COMPOSE_SUBMIT_REQUEST: - return state.set('is_submitting', true); - case COMPOSE_SUBMIT_SUCCESS: - return clearAll(state); - case COMPOSE_SUBMIT_FAIL: - return state.set('is_submitting', false); - case COMPOSE_UPLOAD_REQUEST: - return state.withMutations(map => { - map.set('is_uploading', true); - }); - case COMPOSE_UPLOAD_SUCCESS: - return appendMedia(state, Immutable.fromJS(action.media)); - case COMPOSE_UPLOAD_FAIL: - return state.set('is_uploading', false); - case COMPOSE_UPLOAD_UNDO: - return removeMedia(state, action.media_id); - case COMPOSE_UPLOAD_PROGRESS: - return state.set('progress', Math.round((action.loaded / action.total) * 100)); - case COMPOSE_MENTION: - return state - .update('text', text => `${text}@${action.account.get('acct')} `) - .set('focusDate', new Date()) - .set('idempotencyKey', uuid()); - case COMPOSE_SUGGESTIONS_CLEAR: - return state.update('suggestions', Immutable.List(), list => list.clear()).set('suggestion_token', null); - case COMPOSE_SUGGESTIONS_READY: - return state.set('suggestions', Immutable.List(action.accounts.map(item => item.id))).set('suggestion_token', action.token); - case COMPOSE_SUGGESTION_SELECT: - return insertSuggestion(state, action.position, action.token, action.completion); - case TIMELINE_DELETE: - if (action.id === state.get('in_reply_to')) { - return state.set('in_reply_to', null); - } else { - return state; - } - case COMPOSE_EMOJI_INSERT: - return insertEmoji(state, action.position, action.emoji); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/index.jsx b/app/assets/javascripts/components/reducers/index.jsx deleted file mode 100644 index 147030cca..000000000 --- a/app/assets/javascripts/components/reducers/index.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import { combineReducers } from 'redux-immutable'; -import timelines from './timelines'; -import meta from './meta'; -import compose from './compose'; -import alerts from './alerts'; -import { loadingBarReducer } from 'react-redux-loading-bar'; -import modal from './modal'; -import user_lists from './user_lists'; -import accounts from './accounts'; -import statuses from './statuses'; -import relationships from './relationships'; -import search from './search'; -import notifications from './notifications'; -import settings from './settings'; -import status_lists from './status_lists'; -import cards from './cards'; -import reports from './reports'; - -export default combineReducers({ - timelines, - meta, - compose, - alerts, - loadingBar: loadingBarReducer, - modal, - user_lists, - status_lists, - accounts, - statuses, - relationships, - search, - notifications, - settings, - cards, - reports -}); diff --git a/app/assets/javascripts/components/reducers/meta.jsx b/app/assets/javascripts/components/reducers/meta.jsx deleted file mode 100644 index acf6d4be1..000000000 --- a/app/assets/javascripts/components/reducers/meta.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { STORE_HYDRATE } from '../actions/store'; -import Immutable from 'immutable'; - -const initialState = Immutable.Map({ - streaming_api_base_url: null, - access_token: null, - me: null -}); - -export default function meta(state = initialState, action) { - switch(action.type) { - case STORE_HYDRATE: - return state.merge(action.state.get('meta')); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/modal.jsx b/app/assets/javascripts/components/reducers/modal.jsx deleted file mode 100644 index 3566820ef..000000000 --- a/app/assets/javascripts/components/reducers/modal.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal'; -import Immutable from 'immutable'; - -const initialState = { - modalType: null, - modalProps: {} -}; - -export default function modal(state = initialState, action) { - switch(action.type) { - case MODAL_OPEN: - return { modalType: action.modalType, modalProps: action.modalProps }; - case MODAL_CLOSE: - return initialState; - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/notifications.jsx b/app/assets/javascripts/components/reducers/notifications.jsx deleted file mode 100644 index 1406a388a..000000000 --- a/app/assets/javascripts/components/reducers/notifications.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import { - NOTIFICATIONS_UPDATE, - NOTIFICATIONS_REFRESH_SUCCESS, - NOTIFICATIONS_EXPAND_SUCCESS, - NOTIFICATIONS_REFRESH_REQUEST, - NOTIFICATIONS_EXPAND_REQUEST, - NOTIFICATIONS_REFRESH_FAIL, - NOTIFICATIONS_EXPAND_FAIL, - NOTIFICATIONS_CLEAR, - NOTIFICATIONS_SCROLL_TOP -} from '../actions/notifications'; -import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts'; -import Immutable from 'immutable'; - -const initialState = Immutable.Map({ - items: Immutable.List(), - next: null, - top: true, - unread: 0, - loaded: false, - isLoading: true -}); - -const notificationToMap = notification => Immutable.Map({ - id: notification.id, - type: notification.type, - account: notification.account.id, - status: notification.status ? notification.status.id : null -}); - -const normalizeNotification = (state, notification) => { - if (!state.get('top')) { - state = state.update('unread', unread => unread + 1); - } - - return state.update('items', list => list.unshift(notificationToMap(notification))); -}; - -const normalizeNotifications = (state, notifications, next) => { - let items = Immutable.List(); - const loaded = state.get('loaded'); - - notifications.forEach((n, i) => { - items = items.set(i, notificationToMap(n)); - }); - - if (state.get('next') === null) { - state = state.set('next', next); - } - - return state - .update('items', list => loaded ? list.unshift(...items) : list.push(...items)) - .set('loaded', true) - .set('isLoading', false); -}; - -const appendNormalizedNotifications = (state, notifications, next) => { - let items = Immutable.List(); - - notifications.forEach((n, i) => { - items = items.set(i, notificationToMap(n)); - }); - - return state - .update('items', list => list.push(...items)) - .set('next', next) - .set('isLoading', false); -}; - -const filterNotifications = (state, relationship) => { - return state.update('items', list => list.filterNot(item => item.get('account') === relationship.id)); -}; - -const updateTop = (state, top) => { - if (top) { - state = state.set('unread', 0); - } - - return state.set('top', top); -}; - -export default function notifications(state = initialState, action) { - switch(action.type) { - case NOTIFICATIONS_REFRESH_REQUEST: - case NOTIFICATIONS_EXPAND_REQUEST: - case NOTIFICATIONS_REFRESH_FAIL: - case NOTIFICATIONS_EXPAND_FAIL: - return state.set('isLoading', true); - case NOTIFICATIONS_SCROLL_TOP: - return updateTop(state, action.top); - case NOTIFICATIONS_UPDATE: - return normalizeNotification(state, action.notification); - case NOTIFICATIONS_REFRESH_SUCCESS: - return normalizeNotifications(state, action.notifications, action.next); - case NOTIFICATIONS_EXPAND_SUCCESS: - return appendNormalizedNotifications(state, action.notifications, action.next); - case ACCOUNT_BLOCK_SUCCESS: - return filterNotifications(state, action.relationship); - case NOTIFICATIONS_CLEAR: - return state.set('items', Immutable.List()).set('next', null); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/relationships.jsx b/app/assets/javascripts/components/reducers/relationships.jsx deleted file mode 100644 index c65c48b43..000000000 --- a/app/assets/javascripts/components/reducers/relationships.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - ACCOUNT_FOLLOW_SUCCESS, - ACCOUNT_UNFOLLOW_SUCCESS, - ACCOUNT_BLOCK_SUCCESS, - ACCOUNT_UNBLOCK_SUCCESS, - ACCOUNT_MUTE_SUCCESS, - ACCOUNT_UNMUTE_SUCCESS, - RELATIONSHIPS_FETCH_SUCCESS -} from '../actions/accounts'; -import Immutable from 'immutable'; - -const normalizeRelationship = (state, relationship) => state.set(relationship.id, Immutable.fromJS(relationship)); - -const normalizeRelationships = (state, relationships) => { - relationships.forEach(relationship => { - state = normalizeRelationship(state, relationship); - }); - - return state; -}; - -const initialState = Immutable.Map(); - -export default function relationships(state = initialState, action) { - switch(action.type) { - case ACCOUNT_FOLLOW_SUCCESS: - case ACCOUNT_UNFOLLOW_SUCCESS: - case ACCOUNT_BLOCK_SUCCESS: - case ACCOUNT_UNBLOCK_SUCCESS: - case ACCOUNT_MUTE_SUCCESS: - case ACCOUNT_UNMUTE_SUCCESS: - return normalizeRelationship(state, action.relationship); - case RELATIONSHIPS_FETCH_SUCCESS: - return normalizeRelationships(state, action.relationships); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/reports.jsx b/app/assets/javascripts/components/reducers/reports.jsx deleted file mode 100644 index eab004377..000000000 --- a/app/assets/javascripts/components/reducers/reports.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import { - REPORT_INIT, - REPORT_SUBMIT_REQUEST, - REPORT_SUBMIT_SUCCESS, - REPORT_SUBMIT_FAIL, - REPORT_CANCEL, - REPORT_STATUS_TOGGLE, - REPORT_COMMENT_CHANGE -} from '../actions/reports'; -import Immutable from 'immutable'; - -const initialState = Immutable.Map({ - new: Immutable.Map({ - isSubmitting: false, - account_id: null, - status_ids: Immutable.Set(), - comment: '' - }) -}); - -export default function reports(state = initialState, action) { - switch(action.type) { - case REPORT_INIT: - return state.withMutations(map => { - map.setIn(['new', 'isSubmitting'], false); - map.setIn(['new', 'account_id'], action.account.get('id')); - - if (state.getIn(['new', 'account_id']) !== action.account.get('id')) { - map.setIn(['new', 'status_ids'], action.status ? Immutable.Set([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : Immutable.Set()); - map.setIn(['new', 'comment'], ''); - } else { - map.updateIn(['new', 'status_ids'], Immutable.Set(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id')))); - } - }); - case REPORT_STATUS_TOGGLE: - return state.updateIn(['new', 'status_ids'], Immutable.Set(), set => { - if (action.checked) { - return set.add(action.statusId); - } - - return set.remove(action.statusId); - }); - case REPORT_COMMENT_CHANGE: - return state.setIn(['new', 'comment'], action.comment); - case REPORT_SUBMIT_REQUEST: - return state.setIn(['new', 'isSubmitting'], true); - case REPORT_SUBMIT_FAIL: - return state.setIn(['new', 'isSubmitting'], false); - case REPORT_CANCEL: - case REPORT_SUBMIT_SUCCESS: - return state.withMutations(map => { - map.setIn(['new', 'account_id'], null); - map.setIn(['new', 'status_ids'], Immutable.Set()); - map.setIn(['new', 'comment'], ''); - map.setIn(['new', 'isSubmitting'], false); - }); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/search.jsx b/app/assets/javascripts/components/reducers/search.jsx deleted file mode 100644 index b3fe6c7be..000000000 --- a/app/assets/javascripts/components/reducers/search.jsx +++ /dev/null @@ -1,96 +0,0 @@ -import { - SEARCH_CHANGE, - SEARCH_CLEAR, - SEARCH_FETCH_SUCCESS, - SEARCH_SHOW -} from '../actions/search'; -import { COMPOSE_MENTION, COMPOSE_REPLY } from '../actions/compose'; -import Immutable from 'immutable'; - -const initialState = Immutable.Map({ - value: '', - submitted: false, - hidden: false, - results: Immutable.Map() -}); - -const normalizeSuggestions = (state, value, accounts, hashtags, statuses) => { - let newSuggestions = []; - - if (accounts.length > 0) { - newSuggestions.push({ - title: 'account', - items: accounts.map(item => ({ - type: 'account', - id: item.id, - value: item.acct - })) - }); - } - - if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 || hashtags.length > 0) { - let hashtagItems = hashtags.map(item => ({ - type: 'hashtag', - id: item, - value: `#${item}` - })); - - if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 && !value.startsWith('http://') && !value.startsWith('https://') && hashtags.indexOf(value) === -1) { - hashtagItems.unshift({ - type: 'hashtag', - id: value, - value: `#${value}` - }); - } - - if (hashtagItems.length > 0) { - newSuggestions.push({ - title: 'hashtag', - items: hashtagItems - }); - } - } - - if (statuses.length > 0) { - newSuggestions.push({ - title: 'status', - items: statuses.map(item => ({ - type: 'status', - id: item.id, - value: item.id - })) - }); - } - - return state.withMutations(map => { - map.set('suggestions', newSuggestions); - map.set('loaded_value', value); - }); -}; - -export default function search(state = initialState, action) { - switch(action.type) { - case SEARCH_CHANGE: - return state.set('value', action.value); - case SEARCH_CLEAR: - return state.withMutations(map => { - map.set('value', ''); - map.set('results', Immutable.Map()); - map.set('submitted', false); - map.set('hidden', false); - }); - case SEARCH_SHOW: - return state.set('hidden', false); - case COMPOSE_REPLY: - case COMPOSE_MENTION: - return state.set('hidden', true); - case SEARCH_FETCH_SUCCESS: - return state.set('results', Immutable.Map({ - accounts: Immutable.List(action.results.accounts.map(item => item.id)), - statuses: Immutable.List(action.results.statuses.map(item => item.id)), - hashtags: Immutable.List(action.results.hashtags) - })).set('submitted', true); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/settings.jsx b/app/assets/javascripts/components/reducers/settings.jsx deleted file mode 100644 index 820af99ed..000000000 --- a/app/assets/javascripts/components/reducers/settings.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import { SETTING_CHANGE } from '../actions/settings'; -import { STORE_HYDRATE } from '../actions/store'; -import Immutable from 'immutable'; - -const initialState = Immutable.Map({ - onboarded: false, - - home: Immutable.Map({ - shows: Immutable.Map({ - reblog: true, - reply: true - }) - }), - - notifications: Immutable.Map({ - alerts: Immutable.Map({ - follow: true, - favourite: true, - reblog: true, - mention: true - }), - - shows: Immutable.Map({ - follow: true, - favourite: true, - reblog: true, - mention: true - }), - - sounds: Immutable.Map({ - follow: true, - favourite: true, - reblog: true, - mention: true - }) - }) -}); - -export default function settings(state = initialState, action) { - switch(action.type) { - case STORE_HYDRATE: - return state.mergeDeep(action.state.get('settings')); - case SETTING_CHANGE: - return state.setIn(action.key, action.value); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/status_lists.jsx b/app/assets/javascripts/components/reducers/status_lists.jsx deleted file mode 100644 index b883b1c58..000000000 --- a/app/assets/javascripts/components/reducers/status_lists.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import { - FAVOURITED_STATUSES_FETCH_SUCCESS, - FAVOURITED_STATUSES_EXPAND_SUCCESS -} from '../actions/favourites'; -import Immutable from 'immutable'; - -const initialState = Immutable.Map({ - favourites: Immutable.Map({ - next: null, - loaded: false, - items: Immutable.List() - }) -}); - -const normalizeList = (state, listType, statuses, next) => { - return state.update(listType, listMap => listMap.withMutations(map => { - map.set('next', next); - map.set('loaded', true); - map.set('items', Immutable.List(statuses.map(item => item.id))); - })); -}; - -const appendToList = (state, listType, statuses, next) => { - return state.update(listType, listMap => listMap.withMutations(map => { - map.set('next', next); - map.set('items', map.get('items').push(...statuses.map(item => item.id))); - })); -}; - -export default function statusLists(state = initialState, action) { - switch(action.type) { - case FAVOURITED_STATUSES_FETCH_SUCCESS: - return normalizeList(state, 'favourites', action.statuses, action.next); - case FAVOURITED_STATUSES_EXPAND_SUCCESS: - return appendToList(state, 'favourites', action.statuses, action.next); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/statuses.jsx b/app/assets/javascripts/components/reducers/statuses.jsx deleted file mode 100644 index 2002d2223..000000000 --- a/app/assets/javascripts/components/reducers/statuses.jsx +++ /dev/null @@ -1,124 +0,0 @@ -import { - REBLOG_REQUEST, - REBLOG_SUCCESS, - REBLOG_FAIL, - UNREBLOG_SUCCESS, - FAVOURITE_REQUEST, - FAVOURITE_SUCCESS, - FAVOURITE_FAIL, - UNFAVOURITE_SUCCESS -} from '../actions/interactions'; -import { - STATUS_FETCH_SUCCESS, - CONTEXT_FETCH_SUCCESS -} from '../actions/statuses'; -import { - TIMELINE_REFRESH_SUCCESS, - TIMELINE_UPDATE, - TIMELINE_DELETE, - TIMELINE_EXPAND_SUCCESS -} from '../actions/timelines'; -import { - ACCOUNT_TIMELINE_FETCH_SUCCESS, - ACCOUNT_TIMELINE_EXPAND_SUCCESS, - ACCOUNT_BLOCK_SUCCESS -} from '../actions/accounts'; -import { - NOTIFICATIONS_UPDATE, - NOTIFICATIONS_REFRESH_SUCCESS, - NOTIFICATIONS_EXPAND_SUCCESS -} from '../actions/notifications'; -import { - FAVOURITED_STATUSES_FETCH_SUCCESS, - FAVOURITED_STATUSES_EXPAND_SUCCESS -} from '../actions/favourites'; -import { SEARCH_FETCH_SUCCESS } from '../actions/search'; -import Immutable from 'immutable'; - -const normalizeStatus = (state, status) => { - if (!status) { - return state; - } - - const normalStatus = { ...status }; - normalStatus.account = status.account.id; - - if (status.reblog && status.reblog.id) { - state = normalizeStatus(state, status.reblog); - normalStatus.reblog = status.reblog.id; - } - - const linebreakComplemented = status.content.replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n'); - normalStatus.unescaped_content = new DOMParser().parseFromString(linebreakComplemented, 'text/html').documentElement.textContent; - - return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(normalStatus))); -}; - -const normalizeStatuses = (state, statuses) => { - statuses.forEach(status => { - state = normalizeStatus(state, status); - }); - - return state; -}; - -const deleteStatus = (state, id, references) => { - references.forEach(ref => { - state = deleteStatus(state, ref[0], []); - }); - - return state.delete(id); -}; - -const filterStatuses = (state, relationship) => { - state.forEach(status => { - if (status.get('account') !== relationship.id) { - return; - } - - state = deleteStatus(state, status.get('id'), state.filter(item => item.get('reblog') === status.get('id'))); - }); - - return state; -}; - -const initialState = Immutable.Map(); - -export default function statuses(state = initialState, action) { - switch(action.type) { - case TIMELINE_UPDATE: - case STATUS_FETCH_SUCCESS: - case NOTIFICATIONS_UPDATE: - return normalizeStatus(state, action.status); - case REBLOG_SUCCESS: - case UNREBLOG_SUCCESS: - case FAVOURITE_SUCCESS: - case UNFAVOURITE_SUCCESS: - return normalizeStatus(state, action.response); - case FAVOURITE_REQUEST: - return state.setIn([action.status.get('id'), 'favourited'], true); - case FAVOURITE_FAIL: - return state.setIn([action.status.get('id'), 'favourited'], false); - case REBLOG_REQUEST: - return state.setIn([action.status.get('id'), 'reblogged'], true); - case REBLOG_FAIL: - return state.setIn([action.status.get('id'), 'reblogged'], false); - case TIMELINE_REFRESH_SUCCESS: - case TIMELINE_EXPAND_SUCCESS: - case ACCOUNT_TIMELINE_FETCH_SUCCESS: - case ACCOUNT_TIMELINE_EXPAND_SUCCESS: - case CONTEXT_FETCH_SUCCESS: - case NOTIFICATIONS_REFRESH_SUCCESS: - case NOTIFICATIONS_EXPAND_SUCCESS: - case FAVOURITED_STATUSES_FETCH_SUCCESS: - case FAVOURITED_STATUSES_EXPAND_SUCCESS: - case SEARCH_FETCH_SUCCESS: - return normalizeStatuses(state, action.statuses); - case TIMELINE_DELETE: - return deleteStatus(state, action.id, action.references); - case ACCOUNT_BLOCK_SUCCESS: - return filterStatuses(state, action.relationship); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx deleted file mode 100644 index fa9863250..000000000 --- a/app/assets/javascripts/components/reducers/timelines.jsx +++ /dev/null @@ -1,317 +0,0 @@ -import { - TIMELINE_REFRESH_REQUEST, - TIMELINE_REFRESH_SUCCESS, - TIMELINE_REFRESH_FAIL, - TIMELINE_UPDATE, - TIMELINE_DELETE, - TIMELINE_EXPAND_SUCCESS, - TIMELINE_EXPAND_REQUEST, - TIMELINE_EXPAND_FAIL, - TIMELINE_SCROLL_TOP, - TIMELINE_CONNECT, - TIMELINE_DISCONNECT -} from '../actions/timelines'; -import { - REBLOG_SUCCESS, - UNREBLOG_SUCCESS, - FAVOURITE_SUCCESS, - UNFAVOURITE_SUCCESS -} from '../actions/interactions'; -import { - ACCOUNT_TIMELINE_FETCH_REQUEST, - ACCOUNT_TIMELINE_FETCH_SUCCESS, - ACCOUNT_TIMELINE_FETCH_FAIL, - ACCOUNT_TIMELINE_EXPAND_REQUEST, - ACCOUNT_TIMELINE_EXPAND_SUCCESS, - ACCOUNT_TIMELINE_EXPAND_FAIL, - ACCOUNT_BLOCK_SUCCESS, - ACCOUNT_MUTE_SUCCESS -} from '../actions/accounts'; -import { - CONTEXT_FETCH_SUCCESS -} from '../actions/statuses'; -import Immutable from 'immutable'; - -const initialState = Immutable.Map({ - home: Immutable.Map({ - path: () => '/api/v1/timelines/home', - next: null, - isLoading: false, - online: false, - loaded: false, - top: true, - unread: 0, - items: Immutable.List() - }), - - public: Immutable.Map({ - path: () => '/api/v1/timelines/public', - next: null, - isLoading: false, - online: false, - loaded: false, - top: true, - unread: 0, - items: Immutable.List() - }), - - community: Immutable.Map({ - path: () => '/api/v1/timelines/public', - next: null, - params: { local: true }, - isLoading: false, - online: false, - loaded: false, - top: true, - unread: 0, - items: Immutable.List() - }), - - tag: Immutable.Map({ - path: (id) => `/api/v1/timelines/tag/${id}`, - next: null, - isLoading: false, - id: null, - loaded: false, - top: true, - unread: 0, - items: Immutable.List() - }), - - accounts_timelines: Immutable.Map(), - ancestors: Immutable.Map(), - descendants: Immutable.Map() -}); - -const normalizeStatus = (state, status) => { - const replyToId = status.get('in_reply_to_id'); - const id = status.get('id'); - - if (replyToId) { - if (!state.getIn(['descendants', replyToId], Immutable.List()).includes(id)) { - state = state.updateIn(['descendants', replyToId], Immutable.List(), set => set.push(id)); - } - - if (!state.getIn(['ancestors', id], Immutable.List()).includes(replyToId)) { - state = state.updateIn(['ancestors', id], Immutable.List(), set => set.push(replyToId)); - } - } - - return state; -}; - -const normalizeTimeline = (state, timeline, statuses, next) => { - let ids = Immutable.List(); - const loaded = state.getIn([timeline, 'loaded']); - - statuses.forEach((status, i) => { - state = normalizeStatus(state, status); - ids = ids.set(i, status.get('id')); - }); - - state = state.setIn([timeline, 'loaded'], true); - state = state.setIn([timeline, 'isLoading'], false); - - if (state.getIn([timeline, 'next']) === null) { - state = state.setIn([timeline, 'next'], next); - } - - return state.updateIn([timeline, 'items'], Immutable.List(), list => (loaded ? list.unshift(...ids) : ids)); -}; - -const appendNormalizedTimeline = (state, timeline, statuses, next) => { - let moreIds = Immutable.List(); - - statuses.forEach((status, i) => { - state = normalizeStatus(state, status); - moreIds = moreIds.set(i, status.get('id')); - }); - - state = state.setIn([timeline, 'isLoading'], false); - state = state.setIn([timeline, 'next'], next); - - return state.updateIn([timeline, 'items'], Immutable.List(), list => list.push(...moreIds)); -}; - -const normalizeAccountTimeline = (state, accountId, statuses, replace = false) => { - let ids = Immutable.List(); - - statuses.forEach((status, i) => { - state = normalizeStatus(state, status); - ids = ids.set(i, status.get('id')); - }); - - return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map - .set('isLoading', false) - .set('loaded', true) - .set('next', true) - .update('items', Immutable.List(), list => (replace ? ids : list.unshift(...ids)))); -}; - -const appendNormalizedAccountTimeline = (state, accountId, statuses, next) => { - let moreIds = Immutable.List([]); - - statuses.forEach((status, i) => { - state = normalizeStatus(state, status); - moreIds = moreIds.set(i, status.get('id')); - }); - - return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map - .set('isLoading', false) - .set('next', next) - .update('items', list => list.push(...moreIds))); -}; - -const updateTimeline = (state, timeline, status, references) => { - const top = state.getIn([timeline, 'top']); - - state = normalizeStatus(state, status); - - if (!top) { - state = state.updateIn([timeline, 'unread'], unread => unread + 1); - } - - state = state.updateIn([timeline, 'items'], Immutable.List(), list => { - if (top && list.size > 40) { - list = list.take(20); - } - - if (list.includes(status.get('id'))) { - return list; - } - - const reblogOfId = status.getIn(['reblog', 'id'], null); - - if (reblogOfId !== null) { - list = list.filterNot(itemId => references.includes(itemId)); - } - - return list.unshift(status.get('id')); - }); - - return state; -}; - -const deleteStatus = (state, id, accountId, references, reblogOf) => { - if (reblogOf) { - // If we are deleting a reblog, just replace reblog with its original - return state.updateIn(['home', 'items'], list => list.map(item => item === id ? reblogOf : item)); - } - - // Remove references from timelines - ['home', 'public', 'community', 'tag'].forEach(function (timeline) { - state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id)); - }); - - // Remove references from account timelines - state = state.updateIn(['accounts_timelines', accountId, 'items'], Immutable.List([]), list => list.filterNot(item => item === id)); - - // Remove references from context - state.getIn(['descendants', id], Immutable.List()).forEach(descendantId => { - state = state.updateIn(['ancestors', descendantId], Immutable.List(), list => list.filterNot(itemId => itemId === id)); - }); - - state.getIn(['ancestors', id], Immutable.List()).forEach(ancestorId => { - state = state.updateIn(['descendants', ancestorId], Immutable.List(), list => list.filterNot(itemId => itemId === id)); - }); - - state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]); - - // Remove reblogs of deleted status - references.forEach(ref => { - state = deleteStatus(state, ref[0], ref[1], []); - }); - - return state; -}; - -const filterTimelines = (state, relationship, statuses) => { - let references; - - statuses.forEach(status => { - if (status.get('account') !== relationship.id) { - return; - } - - references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]); - state = deleteStatus(state, status.get('id'), status.get('account'), references); - }); - - return state; -}; - -const normalizeContext = (state, id, ancestors, descendants) => { - const ancestorsIds = ancestors.map(ancestor => ancestor.get('id')); - const descendantsIds = descendants.map(descendant => descendant.get('id')); - - return state.withMutations(map => { - map.setIn(['ancestors', id], ancestorsIds); - map.setIn(['descendants', id], descendantsIds); - }); -}; - -const resetTimeline = (state, timeline, id) => { - if (timeline === 'tag' && typeof id !== 'undefined' && state.getIn([timeline, 'id']) !== id) { - state = state.update(timeline, map => map - .set('id', id) - .set('isLoading', true) - .set('loaded', false) - .set('next', null) - .set('top', true) - .update('items', list => list.clear())); - } else { - state = state.setIn([timeline, 'isLoading'], true); - } - - return state; -}; - -const updateTop = (state, timeline, top) => { - if (top) { - state = state.setIn([timeline, 'unread'], 0); - } - - return state.setIn([timeline, 'top'], top); -}; - -export default function timelines(state = initialState, action) { - switch(action.type) { - case TIMELINE_REFRESH_REQUEST: - case TIMELINE_EXPAND_REQUEST: - return resetTimeline(state, action.timeline, action.id); - case TIMELINE_REFRESH_FAIL: - case TIMELINE_EXPAND_FAIL: - return state.setIn([action.timeline, 'isLoading'], false); - case TIMELINE_REFRESH_SUCCESS: - return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next); - case TIMELINE_EXPAND_SUCCESS: - return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next); - case TIMELINE_UPDATE: - return updateTimeline(state, action.timeline, Immutable.fromJS(action.status), action.references); - case TIMELINE_DELETE: - return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf); - case CONTEXT_FETCH_SUCCESS: - return normalizeContext(state, action.id, Immutable.fromJS(action.ancestors), Immutable.fromJS(action.descendants)); - case ACCOUNT_TIMELINE_FETCH_REQUEST: - case ACCOUNT_TIMELINE_EXPAND_REQUEST: - return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', true)); - case ACCOUNT_TIMELINE_FETCH_FAIL: - case ACCOUNT_TIMELINE_EXPAND_FAIL: - return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', false)); - case ACCOUNT_TIMELINE_FETCH_SUCCESS: - return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace); - case ACCOUNT_TIMELINE_EXPAND_SUCCESS: - return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.next); - case ACCOUNT_BLOCK_SUCCESS: - case ACCOUNT_MUTE_SUCCESS: - return filterTimelines(state, action.relationship, action.statuses); - case TIMELINE_SCROLL_TOP: - return updateTop(state, action.timeline, action.top); - case TIMELINE_CONNECT: - return state.setIn([action.timeline, 'online'], true); - case TIMELINE_DISCONNECT: - return state.setIn([action.timeline, 'online'], false); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/reducers/user_lists.jsx b/app/assets/javascripts/components/reducers/user_lists.jsx deleted file mode 100644 index 7f55c3641..000000000 --- a/app/assets/javascripts/components/reducers/user_lists.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import { - FOLLOWERS_FETCH_SUCCESS, - FOLLOWERS_EXPAND_SUCCESS, - FOLLOWING_FETCH_SUCCESS, - FOLLOWING_EXPAND_SUCCESS, - FOLLOW_REQUESTS_FETCH_SUCCESS, - FOLLOW_REQUESTS_EXPAND_SUCCESS, - FOLLOW_REQUEST_AUTHORIZE_SUCCESS, - FOLLOW_REQUEST_REJECT_SUCCESS -} from '../actions/accounts'; -import { - REBLOGS_FETCH_SUCCESS, - FAVOURITES_FETCH_SUCCESS -} from '../actions/interactions'; -import { - BLOCKS_FETCH_SUCCESS, - BLOCKS_EXPAND_SUCCESS -} from '../actions/blocks'; -import { - MUTES_FETCH_SUCCESS, - MUTES_EXPAND_SUCCESS -} from '../actions/mutes'; -import Immutable from 'immutable'; - -const initialState = Immutable.Map({ - followers: Immutable.Map(), - following: Immutable.Map(), - reblogged_by: Immutable.Map(), - favourited_by: Immutable.Map(), - follow_requests: Immutable.Map(), - blocks: Immutable.Map(), - mutes: Immutable.Map() -}); - -const normalizeList = (state, type, id, accounts, next) => { - return state.setIn([type, id], Immutable.Map({ - next, - items: Immutable.List(accounts.map(item => item.id)) - })); -}; - -const appendToList = (state, type, id, accounts, next) => { - return state.updateIn([type, id], map => { - return map.set('next', next).update('items', list => list.push(...accounts.map(item => item.id))); - }); -}; - -export default function userLists(state = initialState, action) { - switch(action.type) { - case FOLLOWERS_FETCH_SUCCESS: - return normalizeList(state, 'followers', action.id, action.accounts, action.next); - case FOLLOWERS_EXPAND_SUCCESS: - return appendToList(state, 'followers', action.id, action.accounts, action.next); - case FOLLOWING_FETCH_SUCCESS: - return normalizeList(state, 'following', action.id, action.accounts, action.next); - case FOLLOWING_EXPAND_SUCCESS: - return appendToList(state, 'following', action.id, action.accounts, action.next); - case REBLOGS_FETCH_SUCCESS: - return state.setIn(['reblogged_by', action.id], Immutable.List(action.accounts.map(item => item.id))); - case FAVOURITES_FETCH_SUCCESS: - return state.setIn(['favourited_by', action.id], Immutable.List(action.accounts.map(item => item.id))); - case FOLLOW_REQUESTS_FETCH_SUCCESS: - return state.setIn(['follow_requests', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next); - case FOLLOW_REQUESTS_EXPAND_SUCCESS: - return state.updateIn(['follow_requests', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next); - case FOLLOW_REQUEST_AUTHORIZE_SUCCESS: - case FOLLOW_REQUEST_REJECT_SUCCESS: - return state.updateIn(['follow_requests', 'items'], list => list.filterNot(item => item === action.id)); - case BLOCKS_FETCH_SUCCESS: - return state.setIn(['blocks', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next); - case BLOCKS_EXPAND_SUCCESS: - return state.updateIn(['blocks', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next); - case MUTES_FETCH_SUCCESS: - return state.setIn(['mutes', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next); - case MUTES_EXPAND_SUCCESS: - return state.updateIn(['mutes', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next); - default: - return state; - } -}; diff --git a/app/assets/javascripts/components/rtl.jsx b/app/assets/javascripts/components/rtl.jsx deleted file mode 100644 index 8f14bb338..000000000 --- a/app/assets/javascripts/components/rtl.jsx +++ /dev/null @@ -1,27 +0,0 @@ -// U+0590 to U+05FF - Hebrew -// U+0600 to U+06FF - Arabic -// U+0700 to U+074F - Syriac -// U+0750 to U+077F - Arabic Supplement -// U+0780 to U+07BF - Thaana -// U+07C0 to U+07FF - N'Ko -// U+0800 to U+083F - Samaritan -// U+08A0 to U+08FF - Arabic Extended-A -// U+FB1D to U+FB4F - Hebrew presentation forms -// U+FB50 to U+FDFF - Arabic presentation forms A -// U+FE70 to U+FEFF - Arabic presentation forms B - -const rtlChars = /[\u0590-\u083F]|[\u08A0-\u08FF]|[\uFB1D-\uFDFF]|[\uFE70-\uFEFF]/mg; - -export function isRtl(text) { - if (text.length === 0) { - return false; - } - - const matches = text.match(rtlChars); - - if (!matches) { - return false; - } - - return matches.length / text.trim().length > 0.3; -}; diff --git a/app/assets/javascripts/components/selectors/index.jsx b/app/assets/javascripts/components/selectors/index.jsx deleted file mode 100644 index 01a6cb264..000000000 --- a/app/assets/javascripts/components/selectors/index.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import { createSelector } from 'reselect'; -import Immutable from 'immutable'; - -const getStatuses = state => state.get('statuses'); -const getAccounts = state => state.get('accounts'); - -const getAccountBase = (state, id) => state.getIn(['accounts', id], null); -const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null); - -export const makeGetAccount = () => { - return createSelector([getAccountBase, getAccountRelationship], (base, relationship) => { - if (base === null) { - return null; - } - - return base.set('relationship', relationship); - }); -}; - -export const makeGetStatus = () => { - return createSelector( - [ - (state, id) => state.getIn(['statuses', id]), - (state, id) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]), - (state, id) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]), - (state, id) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]), - ], - - (statusBase, statusReblog, accountBase, accountReblog) => { - if (!statusBase) { - return null; - } - - if (statusReblog) { - statusReblog = statusReblog.set('account', accountReblog); - } else { - statusReblog = null; - } - - return statusBase.withMutations(map => { - map.set('reblog', statusReblog); - map.set('account', accountBase); - }); - } - ); -}; - -const getAlertsBase = state => state.get('alerts'); - -export const getAlerts = createSelector([getAlertsBase], (base) => { - let arr = []; - - base.forEach(item => { - arr.push({ - message: item.get('message'), - title: item.get('title'), - key: item.get('key'), - dismissAfter: 5000 - }); - }); - - return arr; -}); - -export const makeGetNotification = () => { - return createSelector([ - (_, base) => base, - (state, _, accountId) => state.getIn(['accounts', accountId]) - ], (base, account) => { - return base.set('account', account); - }); -}; diff --git a/app/assets/javascripts/components/store/configureStore.jsx b/app/assets/javascripts/components/store/configureStore.jsx deleted file mode 100644 index a92d756f5..000000000 --- a/app/assets/javascripts/components/store/configureStore.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import { createStore, applyMiddleware, compose } from 'redux'; -import thunk from 'redux-thunk'; -import appReducer from '../reducers'; -import loadingBarMiddleware from '../middleware/loading_bar'; -import errorsMiddleware from '../middleware/errors'; -import soundsMiddleware from '../middleware/sounds'; -import Immutable from 'immutable'; - -export default function configureStore() { - return createStore(appReducer, compose(applyMiddleware( - thunk, - loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }), - errorsMiddleware(), - soundsMiddleware() - ), window.devToolsExtension ? window.devToolsExtension() : f => f)); -}; diff --git a/app/assets/javascripts/components/stream.jsx b/app/assets/javascripts/components/stream.jsx deleted file mode 100644 index 08da71607..000000000 --- a/app/assets/javascripts/components/stream.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import WebSocketClient from 'websocket.js'; - -const createWebSocketURL = (url) => { - const a = document.createElement('a'); - - a.href = url; - a.href = a.href; - a.protocol = a.protocol.replace('http', 'ws'); - - return a.href; -}; - -export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) { - const ws = new WebSocketClient(`${createWebSocketURL(streamingAPIBaseURL)}/api/v1/streaming/?access_token=${accessToken}&stream=${stream}`); - - ws.onopen = connected; - ws.onmessage = e => received(JSON.parse(e.data)); - ws.onclose = disconnected; - ws.onreconnect = reconnected; - - return ws; -}; diff --git a/app/assets/javascripts/components/uuid.jsx b/app/assets/javascripts/components/uuid.jsx deleted file mode 100644 index be1899305..000000000 --- a/app/assets/javascripts/components/uuid.jsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function uuid(a) { - return a ? (a^Math.random() * 16 >> a / 4).toString(16) : ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, uuid); -}; diff --git a/app/assets/javascripts/extras.jsx b/app/assets/javascripts/extras.jsx deleted file mode 100644 index 7e3b7a256..000000000 --- a/app/assets/javascripts/extras.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import emojify from './components/emoji'; -import { length } from 'stringz'; - -$(() => { - $.each($('.emojify'), (_, content) => { - const $content = $(content); - $content.html(emojify($content.html())); - }); - - $('.video-player video').on('click', e => { - if (e.target.paused) { - e.target.play(); - } else { - e.target.pause(); - } - }); - - $('.media-spoiler').on('click', e => { - $(e.target).hide(); - }); - - $('.webapp-btn').on('click', e => { - if (e.button === 0) { - e.preventDefault(); - window.location.href = $(e.target).attr('href'); - } - }); - - $('.status__content__spoiler-link').on('click', e => { - e.preventDefault(); - const contentEl = $(e.target).parent().parent().find('div'); - - if (contentEl.is(':visible')) { - contentEl.hide(); - $(e.target).parent().attr('style', 'margin-bottom: 0'); - } else { - contentEl.show(); - $(e.target).parent().attr('style', null); - } - }); - - // used on /settings/profile - $('.account_display_name').on('input', e => { - $('.name-counter').text(30 - length($(e.target).val())); - }); - $('.account_note').on('input', e => { - $('.note-counter').text(160 - length($(e.target).val())); - }); -}); |