Merge tag 'v2.8.0' into instance_only_statuses
This commit is contained in:
1
app/javascript/images/logo_transparent_black.svg
Normal file
1
app/javascript/images/logo_transparent_black.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976"><path d="M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z" fill="#000"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/javascript/images/proof_providers/keybase.png
Normal file
BIN
app/javascript/images/proof_providers/keybase.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -22,7 +22,7 @@ export function clearAlert() {
|
||||
};
|
||||
};
|
||||
|
||||
export function showAlert(title, message) {
|
||||
export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage) {
|
||||
return {
|
||||
type: ALERT_SHOW,
|
||||
title,
|
||||
@ -34,6 +34,11 @@ export function showAlertForError(error) {
|
||||
if (error.response) {
|
||||
const { data, status, statusText } = error.response;
|
||||
|
||||
if (status === 404 || status === 410) {
|
||||
// Skip these errors as they are reflected in the UI
|
||||
return {};
|
||||
}
|
||||
|
||||
let message = statusText;
|
||||
let title = `${status}`;
|
||||
|
||||
@ -44,6 +49,6 @@ export function showAlertForError(error) {
|
||||
return showAlert(title, message);
|
||||
} else {
|
||||
console.error(error);
|
||||
return showAlert(messages.unexpectedTitle, messages.unexpectedMessage);
|
||||
return showAlert();
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import resizeImage from '../utils/resize_image';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
import { updateTimeline } from './timelines';
|
||||
import { showAlertForError } from './alerts';
|
||||
import { showAlert } from './alerts';
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
let cancelFetchComposeSuggestionsAccounts;
|
||||
|
||||
@ -50,6 +52,18 @@ export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST'
|
||||
export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS';
|
||||
export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';
|
||||
|
||||
export const COMPOSE_POLL_ADD = 'COMPOSE_POLL_ADD';
|
||||
export const COMPOSE_POLL_REMOVE = 'COMPOSE_POLL_REMOVE';
|
||||
export const COMPOSE_POLL_OPTION_ADD = 'COMPOSE_POLL_OPTION_ADD';
|
||||
export const COMPOSE_POLL_OPTION_CHANGE = 'COMPOSE_POLL_OPTION_CHANGE';
|
||||
export const COMPOSE_POLL_OPTION_REMOVE = 'COMPOSE_POLL_OPTION_REMOVE';
|
||||
export const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE';
|
||||
|
||||
const messages = defineMessages({
|
||||
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
||||
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
||||
});
|
||||
|
||||
export function changeCompose(text) {
|
||||
return {
|
||||
type: COMPOSE_CHANGE,
|
||||
@ -126,6 +140,7 @@ export function submitCompose(routerHistory) {
|
||||
sensitive: getState().getIn(['compose', 'sensitive']),
|
||||
spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
|
||||
visibility: getState().getIn(['compose', 'privacy']),
|
||||
poll: getState().getIn(['compose', 'poll'], null),
|
||||
local_only: !getState().getIn(['compose', 'federation']),
|
||||
}, {
|
||||
headers: {
|
||||
@ -145,7 +160,9 @@ export function submitCompose(routerHistory) {
|
||||
// into the columns
|
||||
|
||||
const insertIfOnline = timelineId => {
|
||||
if (getState().getIn(['timelines', timelineId, 'items', 0]) !== null) {
|
||||
const timeline = getState().getIn(['timelines', timelineId]);
|
||||
|
||||
if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) {
|
||||
dispatch(updateTimeline(timelineId, { ...response.data }));
|
||||
}
|
||||
};
|
||||
@ -186,20 +203,38 @@ export function submitComposeFail(error) {
|
||||
|
||||
export function uploadCompose(files) {
|
||||
return function (dispatch, getState) {
|
||||
if (getState().getIn(['compose', 'media_attachments']).size > 3) {
|
||||
const uploadLimit = 4;
|
||||
const media = getState().getIn(['compose', 'media_attachments']);
|
||||
const total = Array.from(files).reduce((a, v) => a + v.size, 0);
|
||||
const progress = new Array(files.length).fill(0);
|
||||
|
||||
if (files.length + media.size > uploadLimit) {
|
||||
dispatch(showAlert(undefined, messages.uploadErrorLimit));
|
||||
return;
|
||||
}
|
||||
|
||||
if (getState().getIn(['compose', 'poll'])) {
|
||||
dispatch(showAlert(undefined, messages.uploadErrorPoll));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(uploadComposeRequest());
|
||||
|
||||
resizeImage(files[0]).then(file => {
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
for (const [i, f] of Array.from(files).entries()) {
|
||||
if (media.size + i > 3) break;
|
||||
|
||||
return api(getState).post('/api/v1/media', data, {
|
||||
onUploadProgress: ({ loaded, total }) => dispatch(uploadComposeProgress(loaded, total)),
|
||||
}).then(({ data }) => dispatch(uploadComposeSuccess(data)));
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
resizeImage(f).then(file => {
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
|
||||
return api(getState).post('/api/v1/media', data, {
|
||||
onUploadProgress: function({ loaded }){
|
||||
progress[i] = loaded;
|
||||
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
|
||||
},
|
||||
}).then(({ data }) => dispatch(uploadComposeSuccess(data)));
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -475,4 +510,46 @@ export function changeComposing(value) {
|
||||
type: COMPOSE_COMPOSING_CHANGE,
|
||||
value,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function addPoll() {
|
||||
return {
|
||||
type: COMPOSE_POLL_ADD,
|
||||
};
|
||||
};
|
||||
|
||||
export function removePoll() {
|
||||
return {
|
||||
type: COMPOSE_POLL_REMOVE,
|
||||
};
|
||||
};
|
||||
|
||||
export function addPollOption(title) {
|
||||
return {
|
||||
type: COMPOSE_POLL_OPTION_ADD,
|
||||
title,
|
||||
};
|
||||
};
|
||||
|
||||
export function changePollOption(index, title) {
|
||||
return {
|
||||
type: COMPOSE_POLL_OPTION_CHANGE,
|
||||
index,
|
||||
title,
|
||||
};
|
||||
};
|
||||
|
||||
export function removePollOption(index) {
|
||||
return {
|
||||
type: COMPOSE_POLL_OPTION_REMOVE,
|
||||
index,
|
||||
};
|
||||
};
|
||||
|
||||
export function changePollSettings(expiresIn, isMultiple) {
|
||||
return {
|
||||
type: COMPOSE_POLL_SETTINGS_CHANGE,
|
||||
expiresIn,
|
||||
isMultiple,
|
||||
};
|
||||
};
|
||||
|
@ -41,13 +41,15 @@ export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
|
||||
params.since_id = getState().getIn(['conversations', 'items', 0, 'last_status']);
|
||||
}
|
||||
|
||||
const isLoadingRecent = !!params.since_id;
|
||||
|
||||
api(getState).get('/api/v1/conversations', { params })
|
||||
.then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data.reduce((aggr, item) => aggr.concat(item.accounts), [])));
|
||||
dispatch(importFetchedStatuses(response.data.map(item => item.last_status).filter(x => !!x)));
|
||||
dispatch(expandConversationsSuccess(response.data, next ? next.uri : null));
|
||||
dispatch(expandConversationsSuccess(response.data, next ? next.uri : null, isLoadingRecent));
|
||||
})
|
||||
.catch(err => dispatch(expandConversationsFail(err)));
|
||||
};
|
||||
@ -56,10 +58,11 @@ export const expandConversationsRequest = () => ({
|
||||
type: CONVERSATIONS_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
export const expandConversationsSuccess = (conversations, next) => ({
|
||||
export const expandConversationsSuccess = (conversations, next, isLoadingRecent) => ({
|
||||
type: CONVERSATIONS_FETCH_SUCCESS,
|
||||
conversations,
|
||||
next,
|
||||
isLoadingRecent,
|
||||
});
|
||||
|
||||
export const expandConversationsFail = error => ({
|
||||
|
30
app/javascript/mastodon/actions/identity_proofs.js
Normal file
30
app/javascript/mastodon/actions/identity_proofs.js
Normal file
@ -0,0 +1,30 @@
|
||||
import api from '../api';
|
||||
|
||||
export const IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST = 'IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST';
|
||||
export const IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS = 'IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS';
|
||||
export const IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL = 'IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL';
|
||||
|
||||
export const fetchAccountIdentityProofs = accountId => (dispatch, getState) => {
|
||||
dispatch(fetchAccountIdentityProofsRequest(accountId));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${accountId}/identity_proofs`)
|
||||
.then(({ data }) => dispatch(fetchAccountIdentityProofsSuccess(accountId, data)))
|
||||
.catch(err => dispatch(fetchAccountIdentityProofsFail(accountId, err)));
|
||||
};
|
||||
|
||||
export const fetchAccountIdentityProofsRequest = id => ({
|
||||
type: IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
export const fetchAccountIdentityProofsSuccess = (accountId, identity_proofs) => ({
|
||||
type: IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS,
|
||||
accountId,
|
||||
identity_proofs,
|
||||
});
|
||||
|
||||
export const fetchAccountIdentityProofsFail = (accountId, err) => ({
|
||||
type: IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL,
|
||||
accountId,
|
||||
err,
|
||||
});
|
@ -1,11 +1,10 @@
|
||||
// import { autoPlayGif } from '../../initial_state';
|
||||
// import { putAccounts, putStatuses } from '../../storage/modifier';
|
||||
import { normalizeAccount, normalizeStatus } from './normalizer';
|
||||
import { normalizeAccount, normalizeStatus, normalizePoll } from './normalizer';
|
||||
|
||||
export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';
|
||||
export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';
|
||||
export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT';
|
||||
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||
export const POLLS_IMPORT = 'POLLS_IMPORT';
|
||||
|
||||
function pushUnique(array, object) {
|
||||
if (array.every(element => element.id !== object.id)) {
|
||||
@ -29,6 +28,10 @@ export function importStatuses(statuses) {
|
||||
return { type: STATUSES_IMPORT, statuses };
|
||||
}
|
||||
|
||||
export function importPolls(polls) {
|
||||
return { type: POLLS_IMPORT, polls };
|
||||
}
|
||||
|
||||
export function importFetchedAccount(account) {
|
||||
return importFetchedAccounts([account]);
|
||||
}
|
||||
@ -45,7 +48,6 @@ export function importFetchedAccounts(accounts) {
|
||||
}
|
||||
|
||||
accounts.forEach(processAccount);
|
||||
//putAccounts(normalAccounts, !autoPlayGif);
|
||||
|
||||
return importAccounts(normalAccounts);
|
||||
}
|
||||
@ -58,6 +60,7 @@ export function importFetchedStatuses(statuses) {
|
||||
return (dispatch, getState) => {
|
||||
const accounts = [];
|
||||
const normalStatuses = [];
|
||||
const polls = [];
|
||||
|
||||
function processStatus(status) {
|
||||
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id])));
|
||||
@ -66,12 +69,22 @@ export function importFetchedStatuses(statuses) {
|
||||
if (status.reblog && status.reblog.id) {
|
||||
processStatus(status.reblog);
|
||||
}
|
||||
|
||||
if (status.poll && status.poll.id) {
|
||||
pushUnique(polls, normalizePoll(status.poll));
|
||||
}
|
||||
}
|
||||
|
||||
statuses.forEach(processStatus);
|
||||
//putStatuses(normalStatuses);
|
||||
|
||||
dispatch(importPolls(polls));
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
dispatch(importStatuses(normalStatuses));
|
||||
};
|
||||
}
|
||||
|
||||
export function importFetchedPoll(poll) {
|
||||
return dispatch => {
|
||||
dispatch(importPolls([normalizePoll(poll)]));
|
||||
};
|
||||
}
|
||||
|
@ -43,6 +43,10 @@ export function normalizeStatus(status, normalOldStatus) {
|
||||
normalStatus.reblog = status.reblog.id;
|
||||
}
|
||||
|
||||
if (status.poll && status.poll.id) {
|
||||
normalStatus.poll = status.poll.id;
|
||||
}
|
||||
|
||||
// Only calculate these values when status first encountered
|
||||
// Otherwise keep the ones already in the reducer
|
||||
if (normalOldStatus) {
|
||||
@ -63,3 +67,16 @@ export function normalizeStatus(status, normalOldStatus) {
|
||||
|
||||
return normalStatus;
|
||||
}
|
||||
|
||||
export function normalizePoll(poll) {
|
||||
const normalPoll = { ...poll };
|
||||
|
||||
const emojiMap = makeEmojiMap(normalPoll);
|
||||
|
||||
normalPoll.options = poll.options.map(option => ({
|
||||
...option,
|
||||
title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
|
||||
}));
|
||||
|
||||
return normalPoll;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
importFetchedStatus,
|
||||
importFetchedStatuses,
|
||||
} from './importer';
|
||||
import { saveSettings } from './settings';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import { unescapeHTML } from '../utils/html';
|
||||
@ -92,7 +93,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
|
||||
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
|
||||
|
||||
const excludeTypesFromFilter = filter => {
|
||||
const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention']);
|
||||
const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention', 'poll']);
|
||||
return allTypes.filterNot(item => item === filter).toJS();
|
||||
};
|
||||
|
||||
@ -187,5 +188,6 @@ export function setFilter (filterType) {
|
||||
value: filterType,
|
||||
});
|
||||
dispatch(expandNotifications());
|
||||
dispatch(saveSettings());
|
||||
};
|
||||
};
|
||||
|
60
app/javascript/mastodon/actions/polls.js
Normal file
60
app/javascript/mastodon/actions/polls.js
Normal file
@ -0,0 +1,60 @@
|
||||
import api from '../api';
|
||||
import { importFetchedPoll } from './importer';
|
||||
|
||||
export const POLL_VOTE_REQUEST = 'POLL_VOTE_REQUEST';
|
||||
export const POLL_VOTE_SUCCESS = 'POLL_VOTE_SUCCESS';
|
||||
export const POLL_VOTE_FAIL = 'POLL_VOTE_FAIL';
|
||||
|
||||
export const POLL_FETCH_REQUEST = 'POLL_FETCH_REQUEST';
|
||||
export const POLL_FETCH_SUCCESS = 'POLL_FETCH_SUCCESS';
|
||||
export const POLL_FETCH_FAIL = 'POLL_FETCH_FAIL';
|
||||
|
||||
export const vote = (pollId, choices) => (dispatch, getState) => {
|
||||
dispatch(voteRequest());
|
||||
|
||||
api(getState).post(`/api/v1/polls/${pollId}/votes`, { choices })
|
||||
.then(({ data }) => {
|
||||
dispatch(importFetchedPoll(data));
|
||||
dispatch(voteSuccess(data));
|
||||
})
|
||||
.catch(err => dispatch(voteFail(err)));
|
||||
};
|
||||
|
||||
export const fetchPoll = pollId => (dispatch, getState) => {
|
||||
dispatch(fetchPollRequest());
|
||||
|
||||
api(getState).get(`/api/v1/polls/${pollId}`)
|
||||
.then(({ data }) => {
|
||||
dispatch(importFetchedPoll(data));
|
||||
dispatch(fetchPollSuccess(data));
|
||||
})
|
||||
.catch(err => dispatch(fetchPollFail(err)));
|
||||
};
|
||||
|
||||
export const voteRequest = () => ({
|
||||
type: POLL_VOTE_REQUEST,
|
||||
});
|
||||
|
||||
export const voteSuccess = poll => ({
|
||||
type: POLL_VOTE_SUCCESS,
|
||||
poll,
|
||||
});
|
||||
|
||||
export const voteFail = error => ({
|
||||
type: POLL_VOTE_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export const fetchPollRequest = () => ({
|
||||
type: POLL_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
export const fetchPollSuccess = poll => ({
|
||||
type: POLL_FETCH_SUCCESS,
|
||||
poll,
|
||||
});
|
||||
|
||||
export const fetchPollFail = error => ({
|
||||
type: POLL_FETCH_FAIL,
|
||||
error,
|
||||
});
|
@ -37,6 +37,7 @@ export function submitSearch() {
|
||||
params: {
|
||||
q: value,
|
||||
resolve: true,
|
||||
limit: 5,
|
||||
},
|
||||
}).then(response => {
|
||||
if (response.data.accounts) {
|
||||
|
@ -140,7 +140,11 @@ export function redraft(status) {
|
||||
|
||||
export function deleteStatus(id, router, withRedraft = false) {
|
||||
return (dispatch, getState) => {
|
||||
const status = getState().getIn(['statuses', id]);
|
||||
let status = getState().getIn(['statuses', id]);
|
||||
|
||||
if (status.get('poll')) {
|
||||
status = status.set('poll', getState().getIn(['polls', status.get('poll')]));
|
||||
}
|
||||
|
||||
dispatch(deleteStatusRequest(id));
|
||||
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
updateTimeline,
|
||||
deleteFromTimelines,
|
||||
expandHomeTimeline,
|
||||
connectTimeline,
|
||||
disconnectTimeline,
|
||||
} from './timelines';
|
||||
import { updateNotifications, expandNotifications } from './notifications';
|
||||
@ -16,7 +17,12 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
|
||||
|
||||
return connectStream (path, pollingRefresh, (dispatch, getState) => {
|
||||
const locale = getState().getIn(['meta', 'locale']);
|
||||
|
||||
return {
|
||||
onConnect() {
|
||||
dispatch(connectTimeline(timelineId));
|
||||
},
|
||||
|
||||
onDisconnect() {
|
||||
dispatch(disconnectTimeline(timelineId));
|
||||
},
|
||||
|
@ -12,6 +12,7 @@ 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 updateTimeline(timeline, status, accept) {
|
||||
@ -143,6 +144,13 @@ export function scrollTopTimeline(timeline, top) {
|
||||
};
|
||||
};
|
||||
|
||||
export function connectTimeline(timeline) {
|
||||
return {
|
||||
type: TIMELINE_CONNECT,
|
||||
timeline,
|
||||
};
|
||||
};
|
||||
|
||||
export function disconnectTimeline(timeline) {
|
||||
return {
|
||||
type: TIMELINE_DISCONNECT,
|
||||
|
@ -13,10 +13,14 @@ export const getLinks = response => {
|
||||
};
|
||||
|
||||
let csrfHeader = {};
|
||||
|
||||
function setCSRFHeader() {
|
||||
const csrfToken = document.querySelector('meta[name=csrf-token]').content;
|
||||
csrfHeader['X-CSRF-Token'] = csrfToken;
|
||||
const csrfToken = document.querySelector('meta[name=csrf-token]');
|
||||
if (csrfToken) {
|
||||
csrfHeader['X-CSRF-Token'] = csrfToken.content;
|
||||
}
|
||||
}
|
||||
|
||||
ready(setCSRFHeader);
|
||||
|
||||
export default getState => axios.create({
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
||||
|
||||
@ -24,7 +25,7 @@ export default class AttachmentList extends ImmutablePureComponent {
|
||||
|
||||
return (
|
||||
<li key={attachment.get('id')}>
|
||||
<a href={displayUrl} target='_blank' rel='noopener'><i className='fa fa-link' /> {filename(displayUrl)}</a>
|
||||
<a href={displayUrl} target='_blank' rel='noopener'><Icon id='link' /> {filename(displayUrl)}</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
@ -36,7 +37,7 @@ export default class AttachmentList extends ImmutablePureComponent {
|
||||
return (
|
||||
<div className='attachment-list'>
|
||||
<div className='attachment-list__icon'>
|
||||
<i className='fa fa-link' />
|
||||
<Icon id='link' />
|
||||
</div>
|
||||
|
||||
<ul className='attachment-list__list'>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class ColumnBackButton extends React.PureComponent {
|
||||
|
||||
@ -19,7 +20,7 @@ export default class ColumnBackButton extends React.PureComponent {
|
||||
render () {
|
||||
return (
|
||||
<button onClick={this.handleClick} className='column-back-button'>
|
||||
<i className='fa fa-fw fa-chevron-left column-back-button__icon' />
|
||||
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
||||
</button>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ColumnBackButton from './column_back_button';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class ColumnBackButtonSlim extends ColumnBackButton {
|
||||
|
||||
@ -8,7 +9,7 @@ export default class ColumnBackButtonSlim extends ColumnBackButton {
|
||||
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' />
|
||||
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
||||
@ -109,22 +110,22 @@ class ColumnHeader extends React.PureComponent {
|
||||
}
|
||||
|
||||
if (multiColumn && pinned) {
|
||||
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;
|
||||
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;
|
||||
|
||||
moveButtons = (
|
||||
<div key='move-buttons' className='column-header__setting-arrows'>
|
||||
<button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><i className='fa fa-chevron-left' /></button>
|
||||
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><i className='fa fa-chevron-right' /></button>
|
||||
<button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><Icon id='chevron-left' /></button>
|
||||
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' /></button>
|
||||
</div>
|
||||
);
|
||||
} else if (multiColumn) {
|
||||
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
|
||||
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
|
||||
}
|
||||
|
||||
if (!pinned && (multiColumn || showBackButton)) {
|
||||
backButton = (
|
||||
<button onClick={this.handleBackClick} className='column-header__back-button'>
|
||||
<i className='fa fa-fw fa-chevron-left column-back-button__icon' />
|
||||
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
||||
</button>
|
||||
);
|
||||
@ -140,7 +141,7 @@ class ColumnHeader extends React.PureComponent {
|
||||
}
|
||||
|
||||
if (children || multiColumn) {
|
||||
collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>;
|
||||
collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><Icon id='sliders' /></button>;
|
||||
}
|
||||
|
||||
const hasTitle = icon && title;
|
||||
@ -150,7 +151,7 @@ class ColumnHeader extends React.PureComponent {
|
||||
<h1 className={buttonClassName}>
|
||||
{hasTitle && (
|
||||
<button onClick={this.handleTitleClick}>
|
||||
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
|
||||
<Icon id={icon} fixedWidth className='column-header__icon' />
|
||||
{title}
|
||||
</button>
|
||||
)}
|
||||
|
@ -22,7 +22,7 @@ export default class DisplayName extends React.PureComponent {
|
||||
suffix = `+${others.size - 2}`;
|
||||
}
|
||||
} else {
|
||||
if (others) {
|
||||
if (others && others.size > 0) {
|
||||
account = others.first();
|
||||
} else {
|
||||
account = this.props.account;
|
||||
|
39
app/javascript/mastodon/components/error_boundary.js
Normal file
39
app/javascript/mastodon/components/error_boundary.js
Normal file
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import illustration from '../../images/elephant_ui_disappointed.svg';
|
||||
|
||||
export default class ErrorBoundary extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
state = {
|
||||
hasError: false,
|
||||
stackTrace: undefined,
|
||||
componentStack: undefined,
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
this.setState({
|
||||
hasError: true,
|
||||
stackTrace: error.stack,
|
||||
componentStack: info && info.componentStack,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hasError } = this.state;
|
||||
|
||||
if (!hasError) {
|
||||
return this.props.children;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<img src={illustration} alt='' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
21
app/javascript/mastodon/components/icon.js
Normal file
21
app/javascript/mastodon/components/icon.js
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default class Icon extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
fixedWidth: PropTypes.bool,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { id, className, fixedWidth, ...other } = this.props;
|
||||
|
||||
return (
|
||||
<i role='img' className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })} {...other} />
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -3,6 +3,7 @@ import Motion from '../features/ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class IconButton extends React.PureComponent {
|
||||
|
||||
@ -85,8 +86,9 @@ export default class IconButton extends React.PureComponent {
|
||||
onClick={this.handleClick}
|
||||
style={style}
|
||||
tabIndex={tabIndex}
|
||||
disabled={disabled}
|
||||
>
|
||||
<i className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
|
||||
<Icon id={icon} fixedWidth aria-hidden='true' />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@ -103,8 +105,9 @@ export default class IconButton extends React.PureComponent {
|
||||
onClick={this.handleClick}
|
||||
style={style}
|
||||
tabIndex={tabIndex}
|
||||
disabled={disabled}
|
||||
>
|
||||
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
|
||||
<Icon id={icon} style={{ transform: `rotate(${rotate}deg)` }} fixedWidth aria-hidden='true' />
|
||||
</button>
|
||||
)}
|
||||
</Motion>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
|
||||
@ -25,7 +26,7 @@ class LoadGap extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<button className='load-more load-gap' disabled={disabled} onClick={this.handleClick} aria-label={intl.formatMessage(messages.load_more)}>
|
||||
<i className='fa fa-ellipsis-h' />
|
||||
<Icon id='ellipsis-h' />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
140
app/javascript/mastodon/components/poll.js
Normal file
140
app/javascript/mastodon/components/poll.js
Normal file
@ -0,0 +1,140 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import { vote, fetchPoll } from 'mastodon/actions/polls';
|
||||
import Motion from 'mastodon/features/ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import emojify from 'mastodon/features/emoji/emoji';
|
||||
import RelativeTimestamp from './relative_timestamp';
|
||||
|
||||
const messages = defineMessages({
|
||||
closed: { id: 'poll.closed', defaultMessage: 'Closed' },
|
||||
});
|
||||
|
||||
const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
|
||||
obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
export default @injectIntl
|
||||
class Poll extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
poll: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
selected: {},
|
||||
};
|
||||
|
||||
handleOptionChange = e => {
|
||||
const { target: { value } } = e;
|
||||
|
||||
if (this.props.poll.get('multiple')) {
|
||||
const tmp = { ...this.state.selected };
|
||||
if (tmp[value]) {
|
||||
delete tmp[value];
|
||||
} else {
|
||||
tmp[value] = true;
|
||||
}
|
||||
this.setState({ selected: tmp });
|
||||
} else {
|
||||
const tmp = {};
|
||||
tmp[value] = true;
|
||||
this.setState({ selected: tmp });
|
||||
}
|
||||
};
|
||||
|
||||
handleVote = () => {
|
||||
if (this.props.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch(vote(this.props.poll.get('id'), Object.keys(this.state.selected)));
|
||||
};
|
||||
|
||||
handleRefresh = () => {
|
||||
if (this.props.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch(fetchPoll(this.props.poll.get('id')));
|
||||
};
|
||||
|
||||
renderOption (option, optionIndex) {
|
||||
const { poll, disabled } = this.props;
|
||||
const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100;
|
||||
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
|
||||
const active = !!this.state.selected[`${optionIndex}`];
|
||||
const showResults = poll.get('voted') || poll.get('expired');
|
||||
|
||||
let titleEmojified = option.get('title_emojified');
|
||||
if (!titleEmojified) {
|
||||
const emojiMap = makeEmojiMap(poll);
|
||||
titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={option.get('title')}>
|
||||
{showResults && (
|
||||
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
|
||||
{({ width }) =>
|
||||
<span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} />
|
||||
}
|
||||
</Motion>
|
||||
)}
|
||||
|
||||
<label className={classNames('poll__text', { selectable: !showResults })}>
|
||||
<input
|
||||
name='vote-options'
|
||||
type={poll.get('multiple') ? 'checkbox' : 'radio'}
|
||||
value={optionIndex}
|
||||
checked={active}
|
||||
onChange={this.handleOptionChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
{!showResults && <span className={classNames('poll__input', { checkbox: poll.get('multiple'), active })} />}
|
||||
{showResults && <span className='poll__number'>{Math.round(percent)}%</span>}
|
||||
|
||||
<span dangerouslySetInnerHTML={{ __html: titleEmojified }} />
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { poll, intl } = this.props;
|
||||
|
||||
if (!poll) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timeRemaining = poll.get('expired') ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />;
|
||||
const showResults = poll.get('voted') || poll.get('expired');
|
||||
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item);
|
||||
|
||||
return (
|
||||
<div className='poll'>
|
||||
<ul>
|
||||
{poll.get('options').map((option, i) => this.renderOption(option, i))}
|
||||
</ul>
|
||||
|
||||
<div className='poll__footer'>
|
||||
{!showResults && <button className='button button-secondary' disabled={disabled} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
|
||||
{showResults && !this.props.disabled && <span><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </span>}
|
||||
<FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} />
|
||||
{poll.get('expires_at') && <span> · {timeRemaining}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -8,6 +8,11 @@ const messages = defineMessages({
|
||||
minutes: { id: 'relative_time.minutes', defaultMessage: '{number}m' },
|
||||
hours: { id: 'relative_time.hours', defaultMessage: '{number}h' },
|
||||
days: { id: 'relative_time.days', defaultMessage: '{number}d' },
|
||||
moments_remaining: { id: 'time_remaining.moments', defaultMessage: 'Moments remaining' },
|
||||
seconds_remaining: { id: 'time_remaining.seconds', defaultMessage: '{number, plural, one {# second} other {# seconds}} left' },
|
||||
minutes_remaining: { id: 'time_remaining.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}} left' },
|
||||
hours_remaining: { id: 'time_remaining.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}} left' },
|
||||
days_remaining: { id: 'time_remaining.days', defaultMessage: '{number, plural, one {# day} other {# days}} left' },
|
||||
});
|
||||
|
||||
const dateFormatOptions = {
|
||||
@ -86,6 +91,26 @@ export const timeAgoString = (intl, date, now, year) => {
|
||||
return relativeTime;
|
||||
};
|
||||
|
||||
const timeRemainingString = (intl, date, now) => {
|
||||
const delta = date.getTime() - now;
|
||||
|
||||
let relativeTime;
|
||||
|
||||
if (delta < 10 * SECOND) {
|
||||
relativeTime = intl.formatMessage(messages.moments_remaining);
|
||||
} else if (delta < MINUTE) {
|
||||
relativeTime = intl.formatMessage(messages.seconds_remaining, { number: Math.floor(delta / SECOND) });
|
||||
} else if (delta < HOUR) {
|
||||
relativeTime = intl.formatMessage(messages.minutes_remaining, { number: Math.floor(delta / MINUTE) });
|
||||
} else if (delta < DAY) {
|
||||
relativeTime = intl.formatMessage(messages.hours_remaining, { number: Math.floor(delta / HOUR) });
|
||||
} else {
|
||||
relativeTime = intl.formatMessage(messages.days_remaining, { number: Math.floor(delta / DAY) });
|
||||
}
|
||||
|
||||
return relativeTime;
|
||||
};
|
||||
|
||||
export default @injectIntl
|
||||
class RelativeTimestamp extends React.Component {
|
||||
|
||||
@ -93,6 +118,7 @@ class RelativeTimestamp extends React.Component {
|
||||
intl: PropTypes.object.isRequired,
|
||||
timestamp: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
futureDate: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
@ -145,10 +171,10 @@ class RelativeTimestamp extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { timestamp, intl, year } = this.props;
|
||||
const { timestamp, intl, year, futureDate } = this.props;
|
||||
|
||||
const date = new Date(timestamp);
|
||||
const relativeTime = timeAgoString(intl, date, this.state.now, year);
|
||||
const relativeTime = futureDate ? timeRemainingString(intl, date, this.state.now) : timeAgoString(intl, date, this.state.now, year);
|
||||
|
||||
return (
|
||||
<time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}>
|
||||
|
@ -15,6 +15,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { MediaGallery, Video } from '../features/ui/util/async-components';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import PollContainer from 'mastodon/containers/poll_container';
|
||||
|
||||
// We use the component (and not the container) since we do not want
|
||||
// to use the progress bar to show download progress
|
||||
@ -249,7 +251,7 @@ class Status extends ImmutablePureComponent {
|
||||
if (featured) {
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-thumb-tack status__prepend-icon' /></div>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='thumb-tack' className='status__prepend-icon' fixedWidth /></div>
|
||||
<FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
|
||||
</div>
|
||||
);
|
||||
@ -258,7 +260,7 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='retweet' className='status__prepend-icon' fixedWidth /></div>
|
||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
||||
</div>
|
||||
);
|
||||
@ -269,7 +271,9 @@ class Status extends ImmutablePureComponent {
|
||||
status = status.get('reblog');
|
||||
}
|
||||
|
||||
if (status.get('media_attachments').size > 0) {
|
||||
if (status.get('poll')) {
|
||||
media = <PollContainer pollId={status.get('poll')} />;
|
||||
} else if (status.get('media_attachments').size > 0) {
|
||||
if (this.props.muted || status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
||||
media = (
|
||||
<AttachmentList
|
||||
@ -325,7 +329,7 @@ class Status extends ImmutablePureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
if (otherAccounts) {
|
||||
if (otherAccounts && otherAccounts.size > 0) {
|
||||
statusAvatar = <AvatarComposite accounts={otherAccounts} size={48} />;
|
||||
} else if (account === undefined || account === null) {
|
||||
statusAvatar = <Avatar account={status.get('account')} size={48} />;
|
||||
@ -347,7 +351,7 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))} ref={this.handleRef}>
|
||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
|
||||
{prepend}
|
||||
|
||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
|
||||
|
@ -33,6 +33,7 @@ const messages = defineMessages({
|
||||
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
|
||||
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
|
||||
});
|
||||
|
||||
const obfuscatedCount = count => {
|
||||
@ -139,7 +140,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
handleBlockClick = () => {
|
||||
this.props.onBlock(this.props.status.get('account'));
|
||||
this.props.onBlock(this.props.status);
|
||||
}
|
||||
|
||||
handleOpen = () => {
|
||||
@ -158,6 +159,25 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
this.props.onMuteConversation(this.props.status);
|
||||
}
|
||||
|
||||
handleCopy = () => {
|
||||
const url = this.props.status.get('url');
|
||||
const textarea = document.createElement('textarea');
|
||||
|
||||
textarea.textContent = url;
|
||||
textarea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
try {
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
} catch (e) {
|
||||
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { status, intl, withDismiss } = this.props;
|
||||
|
||||
@ -174,6 +194,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
||||
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||
}
|
||||
|
||||
@ -202,6 +223,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
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 });
|
||||
|
||||
if (isStaff) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
||||
|
@ -5,6 +5,7 @@ import { isRtl } from '../rtl';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Permalink from './permalink';
|
||||
import classnames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)
|
||||
|
||||
@ -160,7 +161,7 @@ export default class StatusContent extends React.PureComponent {
|
||||
|
||||
const readMoreButton = (
|
||||
<button className='status__content__read-more-button' onClick={this.props.onClick} key='read-more'>
|
||||
<FormattedMessage id='status.read_more' defaultMessage='Read more' /><i className='fa fa-fw fa-angle-right' />
|
||||
<FormattedMessage id='status.read_more' defaultMessage='Read more' /><Icon id='angle-right' fixedWidth />
|
||||
</button>
|
||||
);
|
||||
|
||||
@ -182,14 +183,14 @@ export default class StatusContent extends React.PureComponent {
|
||||
return (
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
||||
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
|
||||
<span dangerouslySetInnerHTML={spoilerContent} />
|
||||
<span dangerouslySetInnerHTML={spoilerContent} lang={status.get('language')} />
|
||||
{' '}
|
||||
<button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button>
|
||||
</p>
|
||||
|
||||
{mentionsPlaceholder}
|
||||
|
||||
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
|
||||
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
|
||||
</div>
|
||||
);
|
||||
} else if (this.props.onClick) {
|
||||
@ -201,6 +202,7 @@ export default class StatusContent extends React.PureComponent {
|
||||
className={classNames}
|
||||
style={directionStyle}
|
||||
dangerouslySetInnerHTML={content}
|
||||
lang={status.get('language')}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseUp={this.handleMouseUp}
|
||||
/>,
|
||||
@ -219,6 +221,7 @@ export default class StatusContent extends React.PureComponent {
|
||||
className='status__content'
|
||||
style={directionStyle}
|
||||
dangerouslySetInnerHTML={content}
|
||||
lang={status.get('language')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import { connectUserStream } from '../actions/streaming';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import { getLocale } from '../locales';
|
||||
import initialState from '../initial_state';
|
||||
import ErrorBoundary from '../components/error_boundary';
|
||||
|
||||
const { localeData, messages } = getLocale();
|
||||
addLocaleData(localeData);
|
||||
@ -75,7 +76,9 @@ export default class Mastodon extends React.PureComponent {
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
<Provider store={store}>
|
||||
<MastodonMount />
|
||||
<ErrorBoundary>
|
||||
<MastodonMount />
|
||||
</ErrorBoundary>
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import { getLocale } from '../locales';
|
||||
import MediaGallery from '../components/media_gallery';
|
||||
import Video from '../features/video';
|
||||
import Card from '../features/status/components/card';
|
||||
import Poll from 'mastodon/components/poll';
|
||||
import ModalRoot from '../components/modal_root';
|
||||
import MediaModal from '../features/ui/components/media_modal';
|
||||
import { List as ImmutableList, fromJS } from 'immutable';
|
||||
@ -13,7 +14,7 @@ import { List as ImmutableList, fromJS } from 'immutable';
|
||||
const { localeData, messages } = getLocale();
|
||||
addLocaleData(localeData);
|
||||
|
||||
const MEDIA_COMPONENTS = { MediaGallery, Video, Card };
|
||||
const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll };
|
||||
|
||||
export default class MediaContainer extends PureComponent {
|
||||
|
||||
@ -54,11 +55,12 @@ export default class MediaContainer extends PureComponent {
|
||||
{[].map.call(components, (component, i) => {
|
||||
const componentName = component.getAttribute('data-component');
|
||||
const Component = MEDIA_COMPONENTS[componentName];
|
||||
const { media, card, ...props } = JSON.parse(component.getAttribute('data-props'));
|
||||
const { media, card, poll, ...props } = JSON.parse(component.getAttribute('data-props'));
|
||||
|
||||
Object.assign(props, {
|
||||
...(media ? { media: fromJS(media) } : {}),
|
||||
...(card ? { card: fromJS(card) } : {}),
|
||||
...(poll ? { poll: fromJS(poll) } : {}),
|
||||
|
||||
...(componentName === 'Video' ? {
|
||||
onOpenVideo: this.handleOpenVideo,
|
||||
|
8
app/javascript/mastodon/containers/poll_container.js
Normal file
8
app/javascript/mastodon/containers/poll_container.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { connect } from 'react-redux';
|
||||
import Poll from 'mastodon/components/poll';
|
||||
|
||||
const mapStateToProps = (state, { pollId }) => ({
|
||||
poll: state.getIn(['polls', pollId]),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(Poll);
|
@ -38,6 +38,7 @@ const messages = defineMessages({
|
||||
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
@ -134,11 +135,17 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
dispatch(openModal('VIDEO', { media, time }));
|
||||
},
|
||||
|
||||
onBlock (account) {
|
||||
onBlock (status) {
|
||||
const account = status.get('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'))),
|
||||
secondary: intl.formatMessage(messages.blockAndReport),
|
||||
onSecondary: () => {
|
||||
dispatch(blockAccount(account.get('id')));
|
||||
dispatch(initReport(account, status));
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
||||
|
@ -7,7 +7,6 @@ import { hydrateStore } from '../actions/store';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import { getLocale } from '../locales';
|
||||
import PublicTimeline from '../features/standalone/public_timeline';
|
||||
import CommunityTimeline from '../features/standalone/community_timeline';
|
||||
import HashtagTimeline from '../features/standalone/hashtag_timeline';
|
||||
import ModalContainer from '../features/ui/containers/modal_container';
|
||||
import initialState from '../initial_state';
|
||||
@ -26,24 +25,22 @@ export default class TimelineContainer extends React.PureComponent {
|
||||
static propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
hashtag: PropTypes.string,
|
||||
showPublicTimeline: PropTypes.bool.isRequired,
|
||||
local: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
showPublicTimeline: initialState.settings.known_fediverse,
|
||||
local: !initialState.settings.known_fediverse,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { locale, hashtag, showPublicTimeline } = this.props;
|
||||
const { locale, hashtag, local } = this.props;
|
||||
|
||||
let timeline;
|
||||
|
||||
if (hashtag) {
|
||||
timeline = <HashtagTimeline hashtag={hashtag} />;
|
||||
} else if (showPublicTimeline) {
|
||||
timeline = <PublicTimeline />;
|
||||
} else {
|
||||
timeline = <CommunityTimeline />;
|
||||
timeline = <PublicTimeline local={local} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -51,6 +48,7 @@ export default class TimelineContainer extends React.PureComponent {
|
||||
<Provider store={store}>
|
||||
<Fragment>
|
||||
{timeline}
|
||||
|
||||
{ReactDOM.createPortal(
|
||||
<ModalContainer />,
|
||||
document.getElementById('modal-container'),
|
||||
|
@ -1,190 +0,0 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { me, isStaff } from '../../../initial_state';
|
||||
import { shortNumberFormat } from '../../../utils/numbers';
|
||||
|
||||
const messages = defineMessages({
|
||||
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
|
||||
direct: { id: 'account.direct', defaultMessage: 'Direct message @{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}' },
|
||||
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
||||
media: { id: 'account.media', defaultMessage: 'Media' },
|
||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
||||
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
|
||||
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
|
||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
|
||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
||||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
||||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class ActionBar extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
onFollow: PropTypes.func,
|
||||
onBlock: PropTypes.func.isRequired,
|
||||
onMention: PropTypes.func.isRequired,
|
||||
onDirect: PropTypes.func.isRequired,
|
||||
onReblogToggle: PropTypes.func.isRequired,
|
||||
onReport: PropTypes.func.isRequired,
|
||||
onMute: PropTypes.func.isRequired,
|
||||
onBlockDomain: PropTypes.func.isRequired,
|
||||
onUnblockDomain: PropTypes.func.isRequired,
|
||||
onEndorseToggle: PropTypes.func.isRequired,
|
||||
onAddToList: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleShare = () => {
|
||||
navigator.share({
|
||||
url: this.props.account.get('url'),
|
||||
});
|
||||
}
|
||||
|
||||
isStatusesPageActive = (match, location) => {
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
return !location.pathname.match(/\/(followers|following)\/?$/);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, intl } = this.props;
|
||||
|
||||
let menu = [];
|
||||
let extraInfo = '';
|
||||
|
||||
if (account.get('id') !== me) {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if ('share' in navigator) {
|
||||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (account.get('id') === me) {
|
||||
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
|
||||
menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
|
||||
menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
|
||||
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
|
||||
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
|
||||
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
|
||||
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
|
||||
} else {
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
||||
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
||||
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
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')) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
|
||||
extraInfo = (
|
||||
<div className='account__disclaimer'>
|
||||
<FormattedMessage
|
||||
id='account.disclaimer_full'
|
||||
defaultMessage="Information below may reflect the user's profile incompletely."
|
||||
/>
|
||||
{' '}
|
||||
<a target='_blank' rel='noopener' href={account.get('url')}>
|
||||
<FormattedMessage id='account.view_full_profile' defaultMessage='View full profile' />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
menu.push(null);
|
||||
|
||||
if (account.getIn(['relationship', 'domain_blocking'])) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
|
||||
}
|
||||
}
|
||||
|
||||
if (account.get('id') !== me && isStaff) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` });
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{extraInfo}
|
||||
|
||||
<div className='account__action-bar'>
|
||||
<div className='account__action-bar-links'>
|
||||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}`} title={intl.formatNumber(account.get('statuses_count'))}>
|
||||
<FormattedMessage id='account.posts' defaultMessage='Toots' />
|
||||
<strong>{shortNumberFormat(account.get('statuses_count'))}</strong>
|
||||
</NavLink>
|
||||
|
||||
<NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`} title={intl.formatNumber(account.get('following_count'))}>
|
||||
<FormattedMessage id='account.follows' defaultMessage='Follows' />
|
||||
<strong>{shortNumberFormat(account.get('following_count'))}</strong>
|
||||
</NavLink>
|
||||
|
||||
<NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
|
||||
<FormattedMessage id='account.followers' defaultMessage='Followers' />
|
||||
<strong>{shortNumberFormat(account.get('followers_count'))}</strong>
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div className='account__action-bar-dropdown'>
|
||||
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -2,12 +2,15 @@ import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import Button from 'mastodon/components/button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { autoPlayGif, me } from '../../../initial_state';
|
||||
import { autoPlayGif, me, isStaff } from 'mastodon/initial_state';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import Avatar from 'mastodon/components/avatar';
|
||||
import { shortNumberFormat } from 'mastodon/utils/numbers';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
@ -17,6 +20,30 @@ const messages = defineMessages({
|
||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
|
||||
account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
|
||||
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
|
||||
direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' },
|
||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
||||
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
||||
media: { id: 'account.media', defaultMessage: 'Media' },
|
||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
||||
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
|
||||
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
|
||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
|
||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
||||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
||||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
});
|
||||
|
||||
const dateFormatOptions = {
|
||||
@ -28,120 +55,66 @@ const dateFormatOptions = {
|
||||
minute: '2-digit',
|
||||
};
|
||||
|
||||
class Avatar extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
isHovered: false,
|
||||
};
|
||||
|
||||
handleMouseOver = () => {
|
||||
if (this.state.isHovered) return;
|
||||
this.setState({ isHovered: true });
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
if (!this.state.isHovered) return;
|
||||
this.setState({ isHovered: false });
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account } = this.props;
|
||||
const { isHovered } = this.state;
|
||||
|
||||
return (
|
||||
<Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}>
|
||||
{({ radius }) => (
|
||||
<a
|
||||
href={account.get('url')}
|
||||
className='account__header__avatar'
|
||||
role='presentation'
|
||||
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}
|
||||
>
|
||||
<span style={{ display: 'none' }}>{account.get('acct')}</span>
|
||||
</a>
|
||||
)}
|
||||
</Motion>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default @injectIntl
|
||||
class Header extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
identity_props: ImmutablePropTypes.list,
|
||||
onFollow: PropTypes.func.isRequired,
|
||||
onBlock: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
openEditProfile = () => {
|
||||
window.open('/settings/profile', '_blank');
|
||||
}
|
||||
|
||||
isStatusesPageActive = (match, location) => {
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !location.pathname.match(/\/(followers|following)\/?$/);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, intl } = this.props;
|
||||
const { account, intl, domain, identity_proofs } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let info = '';
|
||||
let mutingInfo = '';
|
||||
let info = [];
|
||||
let actionBtn = '';
|
||||
let lockedIcon = '';
|
||||
let menu = [];
|
||||
|
||||
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
|
||||
info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>;
|
||||
info.push(<span key='followed_by' className='relationship-tag'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>);
|
||||
} else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
|
||||
info = <span className='account--follows-info'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>;
|
||||
info.push(<span key='blocked' className='relationship-tag'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>);
|
||||
}
|
||||
|
||||
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
|
||||
mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>;
|
||||
info.push(<span key='muted' className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
|
||||
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
|
||||
mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>;
|
||||
info.push(<span key='domain_blocked' className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>);
|
||||
}
|
||||
|
||||
if (me !== account.get('id')) {
|
||||
if (!account.get('relationship')) { // Wait until the relationship is loaded
|
||||
actionBtn = '';
|
||||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
actionBtn = (
|
||||
<div className='account--action-button'>
|
||||
<IconButton size={26} active icon='hourglass' title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />
|
||||
</div>
|
||||
);
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
|
||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = (
|
||||
<div className='account--action-button'>
|
||||
<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>
|
||||
);
|
||||
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
|
||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = (
|
||||
<div className='account--action-button'>
|
||||
<IconButton size={26} icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
|
||||
</div>
|
||||
);
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
|
||||
}
|
||||
} else {
|
||||
actionBtn = (
|
||||
<div className='account--action-button'>
|
||||
<IconButton size={26} icon='pencil' title={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />
|
||||
</div>
|
||||
);
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />;
|
||||
}
|
||||
|
||||
if (account.get('moved') && !account.getIn(['relationship', 'following'])) {
|
||||
@ -149,43 +122,160 @@ class Header extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
if (account.get('locked')) {
|
||||
lockedIcon = <i className='fa fa-lock' title={intl.formatMessage(messages.account_locked)} />;
|
||||
lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />;
|
||||
}
|
||||
|
||||
if (account.get('id') !== me) {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if ('share' in navigator) {
|
||||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (account.get('id') === me) {
|
||||
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
|
||||
menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
|
||||
menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
|
||||
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
|
||||
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
|
||||
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
|
||||
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
|
||||
} else {
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
||||
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
||||
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
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')) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
|
||||
menu.push(null);
|
||||
|
||||
if (account.getIn(['relationship', 'domain_blocking'])) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
|
||||
}
|
||||
}
|
||||
|
||||
if (account.get('id') !== me && isStaff) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` });
|
||||
}
|
||||
|
||||
const content = { __html: account.get('note_emojified') };
|
||||
const displayNameHtml = { __html: account.get('display_name_html') };
|
||||
const fields = account.get('fields');
|
||||
const badge = account.get('bot') ? (<div className='roles'><div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div></div>) : null;
|
||||
const badge = account.get('bot') ? (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>) : null;
|
||||
const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
|
||||
|
||||
return (
|
||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })} style={{ backgroundImage: `url(${autoPlayGif ? account.get('header') : account.get('header_static')})` }}>
|
||||
<div>
|
||||
<Avatar account={account} />
|
||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })}>
|
||||
<div className='account__header__image'>
|
||||
<div className='account__header__info'>
|
||||
{info}
|
||||
</div>
|
||||
|
||||
<span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} />
|
||||
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
|
||||
<img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />
|
||||
</div>
|
||||
|
||||
{badge}
|
||||
<div className='account__header__bar'>
|
||||
<div className='account__header__tabs'>
|
||||
<a className='avatar' href={account.get('url')} rel='noopener' target='_blank'>
|
||||
<Avatar account={account} size={90} />
|
||||
</a>
|
||||
|
||||
<div className='account__header__content' dangerouslySetInnerHTML={content} />
|
||||
<div className='spacer' />
|
||||
|
||||
{fields.size > 0 && (
|
||||
<div className='account__header__fields'>
|
||||
{fields.map((pair, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
|
||||
<div className='account__header__tabs__buttons'>
|
||||
{actionBtn}
|
||||
|
||||
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
|
||||
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><i className='fa fa-check verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
||||
</dd>
|
||||
</dl>
|
||||
))}
|
||||
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{info}
|
||||
{mutingInfo}
|
||||
{actionBtn}
|
||||
<div className='account__header__tabs__name'>
|
||||
<h1>
|
||||
<span dangerouslySetInnerHTML={displayNameHtml} /> {badge}
|
||||
<small>@{acct} {lockedIcon}</small>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className='account__header__extra'>
|
||||
<div className='account__header__bio'>
|
||||
{ (fields.size > 0 || identity_proofs.size > 0) && (
|
||||
<div className='account__header__fields'>
|
||||
{identity_proofs.map((proof, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: proof.get('provider') }} />
|
||||
|
||||
<dd className='verified'>
|
||||
<a href={proof.get('proof_url')} target='_blank' rel='noopener'><span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
|
||||
<Icon id='check' className='verified__mark' />
|
||||
</span></a>
|
||||
<a href={proof.get('profile_url')} target='_blank' rel='noopener'><span dangerouslySetInnerHTML={{ __html: ' '+proof.get('provider_username') }} /></a>
|
||||
</dd>
|
||||
</dl>
|
||||
))}
|
||||
{fields.map((pair, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
|
||||
|
||||
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
|
||||
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
||||
</dd>
|
||||
</dl>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content' dangerouslySetInnerHTML={content} />}
|
||||
</div>
|
||||
|
||||
<div className='account__header__extra__links'>
|
||||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/accounts/${account.get('id')}`} title={intl.formatNumber(account.get('statuses_count'))}>
|
||||
<strong>{shortNumberFormat(account.get('statuses_count'))}</strong> <FormattedMessage id='account.posts' defaultMessage='Toots' />
|
||||
</NavLink>
|
||||
|
||||
<NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/following`} title={intl.formatNumber(account.get('following_count'))}>
|
||||
<strong>{shortNumberFormat(account.get('following_count'))}</strong> <FormattedMessage id='account.follows' defaultMessage='Follows' />
|
||||
</NavLink>
|
||||
|
||||
<NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
|
||||
<strong>{shortNumberFormat(account.get('followers_count'))}</strong> <FormattedMessage id='account.followers' defaultMessage='Followers' />
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Permalink from '../../../components/permalink';
|
||||
import { displayMedia } from '../../../initial_state';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class MediaItem extends ImmutablePureComponent {
|
||||
|
||||
@ -45,7 +46,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
} else {
|
||||
icon = (
|
||||
<span className='account-gallery__item__icons'>
|
||||
<i className='fa fa-eye-slash' />
|
||||
<Icon id='eye-slash' />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -13,8 +13,10 @@ import MediaItem from './components/media_item';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import { ScrollContainer } from 'react-router-scroll-4';
|
||||
import LoadMore from '../../components/load_more';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
isAccount: !!state.getIn(['accounts', props.params.accountId]),
|
||||
medias: getAccountGallery(state, props.params.accountId),
|
||||
isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']),
|
||||
@ -52,6 +54,7 @@ class AccountGallery extends ImmutablePureComponent {
|
||||
medias: ImmutablePropTypes.list.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
@ -91,7 +94,15 @@ class AccountGallery extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { medias, shouldUpdateScroll, isLoading, hasMore } = this.props;
|
||||
const { medias, shouldUpdateScroll, isLoading, hasMore, isAccount } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
let loadOlder = null;
|
||||
|
||||
|
@ -2,8 +2,6 @@ import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import InnerHeader from '../../account/components/header';
|
||||
import ActionBar from '../../account/components/action_bar';
|
||||
import MissingIndicator from '../../../components/missing_indicator';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import MovedNote from './moved_note';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
@ -13,6 +11,7 @@ export default class Header extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
identity_proofs: ImmutablePropTypes.list,
|
||||
onFollow: PropTypes.func.isRequired,
|
||||
onBlock: PropTypes.func.isRequired,
|
||||
onMention: PropTypes.func.isRequired,
|
||||
@ -25,6 +24,7 @@ export default class Header extends ImmutablePureComponent {
|
||||
onEndorseToggle: PropTypes.func.isRequired,
|
||||
onAddToList: PropTypes.func.isRequired,
|
||||
hideTabs: PropTypes.bool,
|
||||
domain: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
@ -84,10 +84,10 @@ export default class Header extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, hideTabs } = this.props;
|
||||
const { account, hideTabs, identity_proofs } = this.props;
|
||||
|
||||
if (account === null) {
|
||||
return <MissingIndicator />;
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -96,13 +96,9 @@ export default class Header extends ImmutablePureComponent {
|
||||
|
||||
<InnerHeader
|
||||
account={account}
|
||||
identity_proofs={identity_proofs}
|
||||
onFollow={this.handleFollow}
|
||||
onBlock={this.handleBlock}
|
||||
/>
|
||||
|
||||
<ActionBar
|
||||
account={account}
|
||||
onBlock={this.handleBlock}
|
||||
onMention={this.handleMention}
|
||||
onDirect={this.handleDirect}
|
||||
onReblogToggle={this.handleReblogToggle}
|
||||
@ -112,6 +108,7 @@ export default class Header extends ImmutablePureComponent {
|
||||
onUnblockDomain={this.handleUnblockDomain}
|
||||
onEndorseToggle={this.handleEndorseToggle}
|
||||
onAddToList={this.handleAddToList}
|
||||
domain={this.props.domain}
|
||||
/>
|
||||
|
||||
{!hideTabs && (
|
||||
|
@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import AvatarOverlay from '../../../components/avatar_overlay';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class MovedNote extends ImmutablePureComponent {
|
||||
|
||||
@ -33,7 +34,7 @@ export default class MovedNote extends ImmutablePureComponent {
|
||||
return (
|
||||
<div className='account__moved-note'>
|
||||
<div className='account__moved-note__message'>
|
||||
<div className='account__moved-note__icon-wrapper'><i className='fa fa-fw fa-suitcase account__moved-note__icon' /></div>
|
||||
<div className='account__moved-note__icon-wrapper'><Icon id='suitcase' className='account__moved-note__icon' fixedWidth /></div>
|
||||
<FormattedMessage id='account.moved_to' defaultMessage='{name} has moved to:' values={{ name: <bdi><strong dangerouslySetInnerHTML={displayNameHtml} /></bdi> }} />
|
||||
</div>
|
||||
|
||||
|
@ -21,11 +21,13 @@ import { openModal } from '../../../actions/modal';
|
||||
import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { unfollowModal } from '../../../initial_state';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
const messages = defineMessages({
|
||||
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
||||
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
|
||||
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
@ -33,6 +35,8 @@ const makeMapStateToProps = () => {
|
||||
|
||||
const mapStateToProps = (state, { accountId }) => ({
|
||||
account: getAccount(state, accountId),
|
||||
domain: state.getIn(['meta', 'domain']),
|
||||
identity_proofs: state.getIn(['identity_proofs', accountId], ImmutableList()),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
@ -64,6 +68,11 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
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'))),
|
||||
secondary: intl.formatMessage(messages.blockAndReport),
|
||||
onSecondary: () => {
|
||||
dispatch(blockAccount(account.get('id')));
|
||||
dispatch(initReport(account));
|
||||
},
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
@ -12,15 +12,21 @@ import ColumnBackButton from '../../components/column_back_button';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
|
||||
const emptyList = ImmutableList();
|
||||
|
||||
const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
|
||||
const path = withReplies ? `${accountId}:with_replies` : accountId;
|
||||
|
||||
return {
|
||||
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
|
||||
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()),
|
||||
isAccount: !!state.getIn(['accounts', accountId]),
|
||||
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
|
||||
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList),
|
||||
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
|
||||
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
|
||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||
};
|
||||
};
|
||||
|
||||
@ -36,24 +42,32 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||
isLoading: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
withReplies: PropTypes.bool,
|
||||
blockedBy: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
const { params: { accountId }, withReplies } = this.props;
|
||||
|
||||
this.props.dispatch(fetchAccount(accountId));
|
||||
this.props.dispatch(fetchAccountIdentityProofs(accountId));
|
||||
|
||||
if (!withReplies) {
|
||||
this.props.dispatch(expandAccountFeaturedTimeline(accountId));
|
||||
}
|
||||
|
||||
this.props.dispatch(expandAccountTimeline(accountId, { withReplies }));
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
|
||||
this.props.dispatch(fetchAccount(nextProps.params.accountId));
|
||||
this.props.dispatch(fetchAccountIdentityProofs(nextProps.params.accountId));
|
||||
|
||||
if (!nextProps.withReplies) {
|
||||
this.props.dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId));
|
||||
}
|
||||
|
||||
this.props.dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies }));
|
||||
}
|
||||
}
|
||||
@ -63,7 +77,15 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
|
||||
const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, isAccount } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
if (!statusIds && isLoading) {
|
||||
return (
|
||||
@ -73,6 +95,8 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton />
|
||||
@ -81,13 +105,13 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
|
||||
alwaysPrepend
|
||||
scrollKey='account_timeline'
|
||||
statusIds={statusIds}
|
||||
statusIds={blockedBy ? emptyList : statusIds}
|
||||
featuredStatusIds={featuredStatusIds}
|
||||
isLoading={isLoading}
|
||||
hasMore={hasMore}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
emptyMessage={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />}
|
||||
emptyMessage={emptyMessage}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
@ -5,6 +5,7 @@ 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 PollButtonContainer from '../containers/poll_button_container';
|
||||
import UploadButtonContainer from '../containers/upload_button_container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
||||
@ -12,12 +13,14 @@ import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||
import FederationDropdownContainer from '../containers/federation_dropdown_container';
|
||||
import SensitiveButtonContainer from '../containers/sensitive_button_container';
|
||||
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
||||
import PollFormContainer from '../containers/poll_form_container';
|
||||
import UploadFormContainer from '../containers/upload_form_container';
|
||||
import WarningContainer from '../containers/warning_container';
|
||||
import { isMobile } from '../../../is_mobile';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { length } from 'stringz';
|
||||
import { countableText } from '../util/counter';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
||||
|
||||
@ -167,7 +170,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
let publishText = '';
|
||||
|
||||
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>;
|
||||
publishText = <span className='compose-form__publish-private'><Icon id='lock' /> {intl.formatMessage(messages.publish)}</span>;
|
||||
} else {
|
||||
publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
|
||||
}
|
||||
@ -206,11 +209,13 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
|
||||
<div className='compose-form__modifiers'>
|
||||
<UploadFormContainer />
|
||||
<PollFormContainer />
|
||||
</div>
|
||||
|
||||
<div className='compose-form__buttons-wrapper'>
|
||||
<div className='compose-form__buttons'>
|
||||
<UploadButtonContainer />
|
||||
<PollButtonContainer />
|
||||
<PrivacyDropdownContainer />
|
||||
<SensitiveButtonContainer />
|
||||
<SpoilerButtonContainer />
|
||||
|
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
add_poll: { id: 'poll_button.add_poll', defaultMessage: 'Add a poll' },
|
||||
remove_poll: { id: 'poll_button.remove_poll', defaultMessage: 'Remove poll' },
|
||||
});
|
||||
|
||||
const iconStyle = {
|
||||
height: null,
|
||||
lineHeight: '27px',
|
||||
};
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class PollButton extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
unavailable: PropTypes.bool,
|
||||
active: PropTypes.bool,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onClick();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, active, unavailable, disabled } = this.props;
|
||||
|
||||
if (unavailable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='compose-form__poll-button'>
|
||||
<IconButton
|
||||
icon='tasks'
|
||||
title={intl.formatMessage(active ? messages.remove_poll : messages.add_poll)}
|
||||
disabled={disabled}
|
||||
onClick={this.handleClick}
|
||||
className={`compose-form__poll-button-icon ${active ? 'active' : ''}`}
|
||||
size={18}
|
||||
inverted
|
||||
style={iconStyle}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
121
app/javascript/mastodon/features/compose/components/poll_form.js
Normal file
121
app/javascript/mastodon/features/compose/components/poll_form.js
Normal file
@ -0,0 +1,121 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
option_placeholder: { id: 'compose_form.poll.option_placeholder', defaultMessage: 'Choice {number}' },
|
||||
add_option: { id: 'compose_form.poll.add_option', defaultMessage: 'Add a choice' },
|
||||
remove_option: { id: 'compose_form.poll.remove_option', defaultMessage: 'Remove this choice' },
|
||||
poll_duration: { id: 'compose_form.poll.duration', defaultMessage: 'Poll duration' },
|
||||
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
|
||||
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
|
||||
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
class Option extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
isPollMultiple: PropTypes.bool,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleOptionTitleChange = e => {
|
||||
this.props.onChange(this.props.index, e.target.value);
|
||||
};
|
||||
|
||||
handleOptionRemove = () => {
|
||||
this.props.onRemove(this.props.index);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { isPollMultiple, title, index, intl } = this.props;
|
||||
|
||||
return (
|
||||
<li>
|
||||
<label className='poll__text editable'>
|
||||
<span className={classNames('poll__input', { checkbox: isPollMultiple })} />
|
||||
|
||||
<input
|
||||
type='text'
|
||||
placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
|
||||
maxLength={25}
|
||||
value={title}
|
||||
onChange={this.handleOptionTitleChange}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className='poll__cancel'>
|
||||
<IconButton disabled={index <= 1} title={intl.formatMessage(messages.remove_option)} icon='times' onClick={this.handleOptionRemove} />
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class PollForm extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
options: ImmutablePropTypes.list,
|
||||
expiresIn: PropTypes.number,
|
||||
isMultiple: PropTypes.bool,
|
||||
onChangeOption: PropTypes.func.isRequired,
|
||||
onAddOption: PropTypes.func.isRequired,
|
||||
onRemoveOption: PropTypes.func.isRequired,
|
||||
onChangeSettings: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleAddOption = () => {
|
||||
this.props.onAddOption('');
|
||||
};
|
||||
|
||||
handleSelectDuration = e => {
|
||||
this.props.onChangeSettings(e.target.value, this.props.isMultiple);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl } = this.props;
|
||||
|
||||
if (!options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='compose-form__poll-wrapper'>
|
||||
<ul>
|
||||
{options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} />)}
|
||||
</ul>
|
||||
|
||||
<div className='poll__footer'>
|
||||
{options.size < 4 && (
|
||||
<button className='button button-secondary' onClick={this.handleAddOption}><Icon id='plus' /> <FormattedMessage {...messages.add_option} /></button>
|
||||
)}
|
||||
|
||||
<select value={expiresIn} onChange={this.handleSelectDuration}>
|
||||
<option value={300}>{intl.formatMessage(messages.minutes, { number: 5 })}</option>
|
||||
<option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option>
|
||||
<option value={3600}>{intl.formatMessage(messages.hours, { number: 1 })}</option>
|
||||
<option value={21600}>{intl.formatMessage(messages.hours, { number: 6 })}</option>
|
||||
<option value={86400}>{intl.formatMessage(messages.days, { number: 1 })}</option>
|
||||
<option value={259200}>{intl.formatMessage(messages.days, { number: 3 })}</option>
|
||||
<option value={604800}>{intl.formatMessage(messages.days, { number: 7 })}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@ import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import detectPassiveEvents from 'detect-passive-events';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
@ -132,7 +133,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
||||
{items.map(item => (
|
||||
<div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
|
||||
<div className='privacy-dropdown__option__icon'>
|
||||
<i className={`fa fa-fw fa-${item.icon}`} />
|
||||
<Icon id={item.icon} fixedWidth />
|
||||
</div>
|
||||
|
||||
<div className='privacy-dropdown__option__content'>
|
||||
|
@ -5,6 +5,7 @@ import Overlay from 'react-overlays/lib/Overlay';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { searchEnabled } from '../../../initial_state';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
||||
@ -116,8 +117,8 @@ class Search extends React.PureComponent {
|
||||
</label>
|
||||
|
||||
<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' : ''}`} />
|
||||
<Icon id='search' className={hasValue ? '' : 'active'} />
|
||||
<Icon id='times-circle' className={hasValue ? 'active' : ''} aria-label={intl.formatMessage(messages.placeholder)} />
|
||||
</div>
|
||||
|
||||
<Overlay show={expanded && !hasValue} placement='bottom' target={this}>
|
||||
|
@ -6,6 +6,7 @@ import AccountContainer from '../../../containers/account_container';
|
||||
import StatusContainer from '../../../containers/status_container';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Hashtag from '../../../components/hashtag';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
|
||||
@ -34,7 +35,7 @@ class SearchResults extends ImmutablePureComponent {
|
||||
<div className='search-results'>
|
||||
<div className='trends'>
|
||||
<div className='trends__header'>
|
||||
<i className='fa fa-user-plus fa-fw' />
|
||||
<Icon id='user-plus' fixedWidth />
|
||||
<FormattedMessage id='suggestions.header' defaultMessage='You might be interested in…' />
|
||||
</div>
|
||||
|
||||
@ -59,7 +60,7 @@ class SearchResults extends ImmutablePureComponent {
|
||||
count += results.get('accounts').size;
|
||||
accounts = (
|
||||
<div className='search-results__section'>
|
||||
<h5><i className='fa fa-fw fa-users' /><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
|
||||
<h5><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
|
||||
|
||||
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
|
||||
</div>
|
||||
@ -70,7 +71,7 @@ class SearchResults extends ImmutablePureComponent {
|
||||
count += results.get('statuses').size;
|
||||
statuses = (
|
||||
<div className='search-results__section'>
|
||||
<h5><i className='fa fa-fw fa-quote-right' /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
|
||||
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
|
||||
|
||||
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
|
||||
</div>
|
||||
@ -81,7 +82,7 @@ class SearchResults extends ImmutablePureComponent {
|
||||
count += results.get('hashtags').size;
|
||||
hashtags = (
|
||||
<div className='search-results__section'>
|
||||
<h5><i className='fa fa-fw fa-hashtag' /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
|
||||
<h5><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
|
||||
|
||||
{results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
|
||||
</div>
|
||||
@ -91,7 +92,7 @@ class SearchResults extends ImmutablePureComponent {
|
||||
return (
|
||||
<div className='search-results'>
|
||||
<div className='search-results__header'>
|
||||
<i className='fa fa-search fa-fw' />
|
||||
<Icon id='search' fixedWidth />
|
||||
<FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} />
|
||||
</div>
|
||||
|
||||
|
@ -6,6 +6,7 @@ import spring from 'react-motion/lib/spring';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
|
||||
@ -99,8 +100,8 @@ class Upload extends ImmutablePureComponent {
|
||||
{({ scale }) => (
|
||||
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
||||
<div className={classNames('compose-form__upload__actions', { active })}>
|
||||
<button className='icon-button' onClick={this.handleUndoClick}><i className='fa fa-times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
|
||||
{media.get('type') === 'image' && <button className='icon-button' onClick={this.handleFocalPointClick}><i className='fa fa-crosshairs' /> <FormattedMessage id='upload_form.focus' defaultMessage='Crop' /></button>}
|
||||
<button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
|
||||
{media.get('type') === 'image' && <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='crosshairs' /> <FormattedMessage id='upload_form.focus' defaultMessage='Crop' /></button>}
|
||||
</div>
|
||||
|
||||
<div className={classNames('compose-form__upload-description', { active })}>
|
||||
|
@ -29,6 +29,7 @@ class UploadButton extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
unavailable: PropTypes.bool,
|
||||
onSelectFile: PropTypes.func.isRequired,
|
||||
style: PropTypes.object,
|
||||
resetFileKey: PropTypes.number,
|
||||
@ -51,8 +52,11 @@ class UploadButton extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, resetFileKey, unavailable, disabled, acceptContentTypes } = this.props;
|
||||
|
||||
const { intl, resetFileKey, disabled, acceptContentTypes } = this.props;
|
||||
if (unavailable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='compose-form__upload-button'>
|
||||
@ -63,7 +67,7 @@ class UploadButton extends ImmutablePureComponent {
|
||||
key={resetFileKey}
|
||||
ref={this.setRef}
|
||||
type='file'
|
||||
multiple={false}
|
||||
multiple
|
||||
accept={acceptContentTypes.toArray().join(',')}
|
||||
onChange={this.handleChange}
|
||||
disabled={disabled}
|
||||
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class UploadProgress extends React.PureComponent {
|
||||
|
||||
@ -21,7 +22,7 @@ export default class UploadProgress extends React.PureComponent {
|
||||
return (
|
||||
<div className='upload-progress'>
|
||||
<div className='upload-progress__icon'>
|
||||
<i className='fa fa-upload' />
|
||||
<Icon id='upload' />
|
||||
</div>
|
||||
|
||||
<div className='upload-progress__message'>
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { connect } from 'react-redux';
|
||||
import PollButton from '../components/poll_button';
|
||||
import { addPoll, removePoll } from '../../../actions/compose';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
unavailable: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 0),
|
||||
active: state.getIn(['compose', 'poll']) !== null,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onClick () {
|
||||
dispatch((_, getState) => {
|
||||
if (getState().getIn(['compose', 'poll'])) {
|
||||
dispatch(removePoll());
|
||||
} else {
|
||||
dispatch(addPoll());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PollButton);
|
@ -0,0 +1,29 @@
|
||||
import { connect } from 'react-redux';
|
||||
import PollForm from '../components/poll_form';
|
||||
import { addPollOption, removePollOption, changePollOption, changePollSettings } from '../../../actions/compose';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
options: state.getIn(['compose', 'poll', 'options']),
|
||||
expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
|
||||
isMultiple: state.getIn(['compose', 'poll', 'multiple']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onAddOption(title) {
|
||||
dispatch(addPollOption(title));
|
||||
},
|
||||
|
||||
onRemoveOption(index) {
|
||||
dispatch(removePollOption(index));
|
||||
},
|
||||
|
||||
onChangeOption(index, title) {
|
||||
dispatch(changePollOption(index, title));
|
||||
},
|
||||
|
||||
onChangeSettings(expiresIn, isMultiple) {
|
||||
dispatch(changePollSettings(expiresIn, isMultiple));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PollForm);
|
@ -4,6 +4,7 @@ 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')),
|
||||
unavailable: state.getIn(['compose', 'poll']) !== null,
|
||||
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
||||
});
|
||||
|
||||
|
@ -14,6 +14,7 @@ import SearchResultsContainer from './containers/search_results_container';
|
||||
import { changeComposing } from '../../actions/compose';
|
||||
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
|
||||
import { mascot } from '../../initial_state';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
||||
@ -77,21 +78,21 @@ class Compose extends React.PureComponent {
|
||||
const { columns } = this.props;
|
||||
header = (
|
||||
<nav className='drawer__header'>
|
||||
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><i role='img' className='fa fa-fw fa-bars' /></Link>
|
||||
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' fixedWidth /></Link>
|
||||
{!columns.some(column => column.get('id') === 'HOME') && (
|
||||
<Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><i role='img' className='fa fa-fw fa-home' /></Link>
|
||||
<Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
|
||||
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><i role='img' className='fa fa-fw fa-bell' /></Link>
|
||||
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
|
||||
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><i role='img' className='fa fa-fw fa-users' /></Link>
|
||||
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'PUBLIC') && (
|
||||
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><i role='img' className='fa fa-fw fa-globe' /></Link>
|
||||
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
|
||||
)}
|
||||
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><i role='img' className='fa fa-fw fa-cog' /></a>
|
||||
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><i role='img' className='fa fa-fw fa-sign-out' /></a>
|
||||
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
|
||||
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><Icon id='sign-out' fixedWidth /></a>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import SettingText from '../../../components/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' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class ColumnSettings extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { settings, onChange, intl } = this.props;
|
||||
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import ColumnSettings from '../components/column_settings';
|
||||
import { changeSetting } from '../../../actions/settings';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
settings: state.getIn(['settings', 'direct']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onChange (key, checked) {
|
||||
dispatch(changeSetting(['direct', ...key], checked));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
|
@ -16,10 +16,13 @@ import Column from '../ui/components/column';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
isAccount: !!state.getIn(['accounts', props.params.accountId]),
|
||||
accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']),
|
||||
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@ -31,6 +34,8 @@ class Followers extends ImmutablePureComponent {
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
hasMore: PropTypes.bool,
|
||||
blockedBy: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
@ -50,7 +55,15 @@ class Followers extends ImmutablePureComponent {
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { shouldUpdateScroll, accountIds, hasMore } = this.props;
|
||||
const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
@ -60,7 +73,7 @@ class Followers extends ImmutablePureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />;
|
||||
const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
@ -75,7 +88,7 @@ class Followers extends ImmutablePureComponent {
|
||||
alwaysPrepend
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
{blockedBy ? [] : accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />
|
||||
)}
|
||||
</ScrollableList>
|
||||
|
@ -16,10 +16,13 @@ import Column from '../ui/components/column';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
isAccount: !!state.getIn(['accounts', props.params.accountId]),
|
||||
accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']),
|
||||
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@ -31,6 +34,8 @@ class Following extends ImmutablePureComponent {
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
hasMore: PropTypes.bool,
|
||||
blockedBy: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
@ -50,7 +55,15 @@ class Following extends ImmutablePureComponent {
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { shouldUpdateScroll, accountIds, hasMore } = this.props;
|
||||
const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
@ -60,7 +73,7 @@ class Following extends ImmutablePureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />;
|
||||
const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
@ -75,7 +88,7 @@ class Following extends ImmutablePureComponent {
|
||||
alwaysPrepend
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
{blockedBy ? [] : accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />
|
||||
)}
|
||||
</ScrollableList>
|
||||
|
@ -12,6 +12,7 @@ import { fetchFollowRequests } from '../../actions/accounts';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import { Link } from 'react-router-dom';
|
||||
import NavigationBar from '../compose/components/navigation_bar';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
||||
@ -140,7 +141,7 @@ class GettingStarted extends ImmutablePureComponent {
|
||||
{multiColumn && <div className='column-header__wrapper'>
|
||||
<h1 className='column-header'>
|
||||
<button>
|
||||
<i className='fa fa-bars fa-fw column-header__icon' />
|
||||
<Icon id='bars' className='column-header__icon' fixedWidth />
|
||||
<FormattedMessage id='getting_started.heading' defaultMessage='Getting started' />
|
||||
</button>
|
||||
</h1>
|
||||
@ -159,7 +160,7 @@ class GettingStarted extends ImmutablePureComponent {
|
||||
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
|
||||
{multiColumn && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
|
||||
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
|
||||
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this instance' /></a> · </li>
|
||||
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
|
||||
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
|
||||
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
|
||||
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
|
||||
|
@ -16,7 +16,7 @@ const messages = defineMessages({
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
|
||||
isPartial: state.getIn(['timelines', 'home', 'items', 0], null) === null,
|
||||
isPartial: state.getIn(['timelines', 'home', 'isPartial']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
|
@ -89,7 +89,7 @@ const FrameInteractions = ({ onNext }) => (
|
||||
</div>
|
||||
|
||||
<div className='introduction__action'>
|
||||
<button className='button' onClick={onNext}><FormattedMessage id='introduction.interactions.action' defaultMessage='Finish tutorial!' /></button>
|
||||
<button className='button' onClick={onNext}><FormattedMessage id='introduction.interactions.action' defaultMessage='Finish toot-orial!' /></button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { removeFromListAdder, addToListAdder } from '../../../actions/lists';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
|
||||
@ -53,7 +54,7 @@ class List extends ImmutablePureComponent {
|
||||
<div className='list'>
|
||||
<div className='list__wrapper'>
|
||||
<div className='list__display-name'>
|
||||
<i className='fa fa-fw fa-list-ul column-link__icon' />
|
||||
<Icon id='list-ul' className='column-link__icon' fixedWidth />
|
||||
{list.get('title')}
|
||||
</div>
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
value: state.getIn(['listEditor', 'title']),
|
||||
disabled: !state.getIn(['listEditor', 'isChanged']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onChange: value => dispatch(changeListEditorTitle(value)),
|
||||
onSubmit: () => dispatch(submitListEditor(false)),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class ListForm extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
handleChange = e => {
|
||||
this.props.onChange(e.target.value);
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
this.props.onSubmit();
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onSubmit();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, disabled, intl } = this.props;
|
||||
|
||||
const title = intl.formatMessage(messages.title);
|
||||
|
||||
return (
|
||||
<form className='column-inline-form' onSubmit={this.handleSubmit}>
|
||||
<input
|
||||
className='setting-text'
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
disabled={disabled}
|
||||
icon='check'
|
||||
title={title}
|
||||
onClick={this.handleClick}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
search: { id: 'lists.search', defaultMessage: 'Search among people you follow' },
|
||||
@ -65,8 +66,8 @@ class Search extends React.PureComponent {
|
||||
</label>
|
||||
|
||||
<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
|
||||
<i className={classNames('fa fa-search', { active: !hasValue })} />
|
||||
<i aria-label={intl.formatMessage(messages.search)} className={classNames('fa fa-times-circle', { active: hasValue })} />
|
||||
<Icon id='search' className={classNames({ active: !hasValue })} />
|
||||
<Icon id='times-circle' aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -7,11 +7,11 @@ import { injectIntl } from 'react-intl';
|
||||
import { setupListEditor, clearListSuggestions, resetListEditor } from '../../actions/lists';
|
||||
import Account from './components/account';
|
||||
import Search from './components/search';
|
||||
import EditListForm from './components/edit_list_form';
|
||||
import Motion from '../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
title: state.getIn(['listEditor', 'title']),
|
||||
accountIds: state.getIn(['listEditor', 'accounts', 'items']),
|
||||
searchAccountIds: state.getIn(['listEditor', 'suggestions', 'items']),
|
||||
});
|
||||
@ -33,7 +33,6 @@ class ListEditor extends ImmutablePureComponent {
|
||||
onInitialize: PropTypes.func.isRequired,
|
||||
onClear: PropTypes.func.isRequired,
|
||||
onReset: PropTypes.func.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
accountIds: ImmutablePropTypes.list.isRequired,
|
||||
searchAccountIds: ImmutablePropTypes.list.isRequired,
|
||||
};
|
||||
@ -49,12 +48,12 @@ class ListEditor extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { title, accountIds, searchAccountIds, onClear } = this.props;
|
||||
const { accountIds, searchAccountIds, onClear } = this.props;
|
||||
const showSearch = searchAccountIds.size > 0;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal list-editor'>
|
||||
<h4>{title}</h4>
|
||||
<EditListForm />
|
||||
|
||||
<Search />
|
||||
|
||||
|
@ -14,6 +14,7 @@ import { fetchList, deleteList } from '../../actions/lists';
|
||||
import { openModal } from '../../actions/modal';
|
||||
import MissingIndicator from '../../components/missing_indicator';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
|
||||
@ -150,11 +151,11 @@ class ListTimeline extends React.PureComponent {
|
||||
>
|
||||
<div className='column-header__links'>
|
||||
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>
|
||||
<i className='fa fa-pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
|
||||
<Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
|
||||
</button>
|
||||
|
||||
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleDeleteClick}>
|
||||
<i className='fa fa-trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
|
||||
<Icon id='trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class ClearColumnButton extends React.PureComponent {
|
||||
|
||||
@ -10,7 +11,7 @@ export default class ClearColumnButton extends React.PureComponent {
|
||||
|
||||
render () {
|
||||
return (
|
||||
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.props.onClick}><i className='fa fa-eraser' /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button>
|
||||
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.props.onClick}><Icon id='eraser' /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -89,6 +89,17 @@ export default class ColumnSettings extends React.PureComponent {
|
||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div role='group' aria-labelledby='notifications-poll'>
|
||||
<span id='notifications-poll' className='column-settings__section'><FormattedMessage id='notifications.column_settings.poll' defaultMessage='Poll results:' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} />
|
||||
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'poll']} onChange={this.onPushChange} label={pushStr} />}
|
||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'poll']} onChange={onChange} label={showStr} />
|
||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'poll']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const tooltips = defineMessages({
|
||||
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
|
||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' },
|
||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
|
||||
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
||||
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
||||
});
|
||||
|
||||
@ -62,28 +64,35 @@ class FilterBar extends React.PureComponent {
|
||||
onClick={this.onClick('mention')}
|
||||
title={intl.formatMessage(tooltips.mentions)}
|
||||
>
|
||||
<i className='fa fa-fw fa-at' />
|
||||
<Icon id='at' fixedWidth />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'favourite' ? 'active' : ''}
|
||||
onClick={this.onClick('favourite')}
|
||||
title={intl.formatMessage(tooltips.favourites)}
|
||||
>
|
||||
<i className='fa fa-fw fa-star' />
|
||||
<Icon id='star' fixedWidth />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'reblog' ? 'active' : ''}
|
||||
onClick={this.onClick('reblog')}
|
||||
title={intl.formatMessage(tooltips.boosts)}
|
||||
>
|
||||
<i className='fa fa-fw fa-retweet' />
|
||||
<Icon id='retweet' fixedWidth />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'poll' ? 'active' : ''}
|
||||
onClick={this.onClick('poll')}
|
||||
title={intl.formatMessage(tooltips.polls)}
|
||||
>
|
||||
<Icon id='tasks' fixedWidth />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'follow' ? 'active' : ''}
|
||||
onClick={this.onClick('follow')}
|
||||
title={intl.formatMessage(tooltips.follows)}
|
||||
>
|
||||
<i className='fa fa-fw fa-user-plus' />
|
||||
<Icon id='user-plus' fixedWidth />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ import { injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Permalink from '../../../components/permalink';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const notificationForScreenReader = (intl, message, timestamp) => {
|
||||
const output = [message];
|
||||
@ -32,7 +33,7 @@ class Notification extends ImmutablePureComponent {
|
||||
onFavourite: PropTypes.func.isRequired,
|
||||
onReblog: PropTypes.func.isRequired,
|
||||
onToggleHidden: PropTypes.func.isRequired,
|
||||
status: PropTypes.option,
|
||||
status: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object.isRequired,
|
||||
getScrollPosition: PropTypes.func,
|
||||
updateScrollBottom: PropTypes.func,
|
||||
@ -109,7 +110,7 @@ class Notification extends ImmutablePureComponent {
|
||||
<div className='notification notification-follow focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow', defaultMessage: '{name} followed you' }, { name: account.get('acct') }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<div className='notification__favourite-icon-wrapper'>
|
||||
<i className='fa fa-fw fa-user-plus' />
|
||||
<Icon id='user-plus' fixedWidth />
|
||||
</div>
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
@ -148,7 +149,7 @@ class Notification extends ImmutablePureComponent {
|
||||
<div className='notification notification-favourite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.favourite', defaultMessage: '{name} favourited your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<div className='notification__favourite-icon-wrapper'>
|
||||
<i className='fa fa-fw fa-star star-icon' />
|
||||
<Icon id='star' className='star-icon' fixedWidth />
|
||||
</div>
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
@ -180,7 +181,7 @@ class Notification extends ImmutablePureComponent {
|
||||
<div className='notification notification-reblog focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.reblog', defaultMessage: '{name} boosted your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<div className='notification__favourite-icon-wrapper'>
|
||||
<i className='fa fa-fw fa-retweet' />
|
||||
<Icon id='retweet' fixedWidth />
|
||||
</div>
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
@ -204,6 +205,38 @@ class Notification extends ImmutablePureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
renderPoll (notification) {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<div className='notification notification-poll focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<div className='notification__favourite-icon-wrapper'>
|
||||
<Icon id='tasks' fixedWidth />
|
||||
</div>
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
<FormattedMessage id='notification.poll' defaultMessage='A poll you have voted in has ended' />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<StatusContainer
|
||||
id={notification.get('status')}
|
||||
account={notification.get('account')}
|
||||
muted
|
||||
withDismiss
|
||||
hidden={this.props.hidden}
|
||||
getScrollPosition={this.props.getScrollPosition}
|
||||
updateScrollBottom={this.props.updateScrollBottom}
|
||||
cachedMediaWidth={this.props.cachedMediaWidth}
|
||||
cacheMediaWidth={this.props.cacheMediaWidth}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { notification } = this.props;
|
||||
const account = notification.get('account');
|
||||
@ -219,6 +252,8 @@ class Notification extends ImmutablePureComponent {
|
||||
return this.renderFavourite(notification, link);
|
||||
case 'reblog':
|
||||
return this.renderReblog(notification, link);
|
||||
case 'poll':
|
||||
return this.renderPoll(notification);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -124,7 +124,7 @@ class PublicTimeline extends React.PureComponent {
|
||||
onLoadMore={this.handleLoadMore}
|
||||
trackScroll={!pinned}
|
||||
scrollKey={`public_timeline-${columnId}`}
|
||||
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' />}
|
||||
emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />}
|
||||
shouldUpdateScroll={shouldUpdateScroll}
|
||||
/>
|
||||
</Column>
|
||||
|
@ -1,71 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import StatusListContainer from '../../ui/containers/status_list_container';
|
||||
import { expandCommunityTimeline } from '../../../actions/timelines';
|
||||
import Column from '../../../components/column';
|
||||
import ColumnHeader from '../../../components/column_header';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { connectCommunityStream } from '../../../actions/streaming';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
|
||||
});
|
||||
|
||||
export default @connect()
|
||||
@injectIntl
|
||||
class CommunityTimeline extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(expandCommunityTimeline());
|
||||
this.disconnect = dispatch(connectCommunityStream());
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
this.props.dispatch(expandCommunityTimeline({ maxId }));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='users'
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onClick={this.handleHeaderClick}
|
||||
/>
|
||||
|
||||
<StatusListContainer
|
||||
timelineId='community'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
scrollKey='standalone_public_timeline'
|
||||
trackScroll={false}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -2,13 +2,12 @@ import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { expandHashtagTimeline } from '../../../actions/timelines';
|
||||
import { connectHashtagStream } from '../../../actions/streaming';
|
||||
import { expandHashtagTimeline } from 'mastodon/actions/timelines';
|
||||
import Masonry from 'react-masonry-infinite';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import DetailedStatusContainer from '../../status/containers/detailed_status_container';
|
||||
import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
|
||||
import { debounce } from 'lodash';
|
||||
import LoadingIndicator from '../../../components/loading_indicator';
|
||||
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
||||
|
||||
const mapStateToProps = (state, { hashtag }) => ({
|
||||
statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()),
|
||||
@ -31,14 +30,6 @@ class HashtagTimeline extends React.PureComponent {
|
||||
const { dispatch, hashtag } = this.props;
|
||||
|
||||
dispatch(expandHashtagTimeline(hashtag));
|
||||
this.disconnect = dispatch(connectHashtagStream(hashtag, hashtag));
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = () => {
|
||||
|
@ -1,70 +1,98 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import StatusListContainer from '../../ui/containers/status_list_container';
|
||||
import { expandPublicTimeline } from '../../../actions/timelines';
|
||||
import Column from '../../../components/column';
|
||||
import ColumnHeader from '../../../components/column_header';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { connectPublicStream } from '../../../actions/streaming';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
|
||||
import Masonry from 'react-masonry-infinite';
|
||||
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
||||
import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
|
||||
import { debounce } from 'lodash';
|
||||
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
|
||||
});
|
||||
const mapStateToProps = (state, { local }) => {
|
||||
const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap());
|
||||
|
||||
export default @connect()
|
||||
@injectIntl
|
||||
return {
|
||||
statusIds: timeline.get('items', ImmutableList()),
|
||||
isLoading: timeline.get('isLoading', false),
|
||||
hasMore: timeline.get('hasMore', false),
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
class PublicTimeline extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
statusIds: ImmutablePropTypes.list.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
hasMore: PropTypes.bool.isRequired,
|
||||
local: PropTypes.bool,
|
||||
};
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(expandPublicTimeline());
|
||||
this.disconnect = dispatch(connectPublicStream());
|
||||
this._connect();
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.local !== this.props.local) {
|
||||
this._connect();
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
this.props.dispatch(expandPublicTimeline({ maxId }));
|
||||
_connect () {
|
||||
const { dispatch, local } = this.props;
|
||||
|
||||
dispatch(local ? expandCommunityTimeline() : expandPublicTimeline());
|
||||
}
|
||||
|
||||
handleLoadMore = () => {
|
||||
const { dispatch, statusIds, local } = this.props;
|
||||
const maxId = statusIds.last();
|
||||
|
||||
if (maxId) {
|
||||
dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId }));
|
||||
}
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.masonry = c;
|
||||
}
|
||||
|
||||
handleHeightChange = debounce(() => {
|
||||
if (!this.masonry) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.masonry.forcePack();
|
||||
}, 50)
|
||||
|
||||
render () {
|
||||
const { intl } = this.props;
|
||||
const { statusIds, hasMore, isLoading } = this.props;
|
||||
|
||||
const sizes = [
|
||||
{ columns: 1, gutter: 0 },
|
||||
{ mq: '415px', columns: 1, gutter: 10 },
|
||||
{ mq: '640px', columns: 2, gutter: 10 },
|
||||
{ mq: '960px', columns: 3, gutter: 10 },
|
||||
{ mq: '1255px', columns: 3, gutter: 10 },
|
||||
];
|
||||
|
||||
const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='globe'
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onClick={this.handleHeaderClick}
|
||||
/>
|
||||
|
||||
<StatusListContainer
|
||||
timelineId='public'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
scrollKey='standalone_public_timeline'
|
||||
trackScroll={false}
|
||||
/>
|
||||
</Column>
|
||||
<Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}>
|
||||
{statusIds.map(statusId => (
|
||||
<div className='statuses-grid__item' key={statusId}>
|
||||
<DetailedStatusContainer
|
||||
id={statusId}
|
||||
compact
|
||||
measureHeight
|
||||
onHeightChange={this.handleHeightChange}
|
||||
/>
|
||||
</div>
|
||||
)).toArray()}
|
||||
</Masonry>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ const messages = defineMessages({
|
||||
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
|
||||
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
@ -91,7 +92,7 @@ class ActionBar extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleBlockClick = () => {
|
||||
this.props.onBlock(this.props.status.get('account'));
|
||||
this.props.onBlock(this.props.status);
|
||||
}
|
||||
|
||||
handleReport = () => {
|
||||
@ -113,6 +114,25 @@ class ActionBar extends React.PureComponent {
|
||||
this.props.onEmbed(this.props.status);
|
||||
}
|
||||
|
||||
handleCopy = () => {
|
||||
const url = this.props.status.get('url');
|
||||
const textarea = document.createElement('textarea');
|
||||
|
||||
textarea.textContent = url;
|
||||
textarea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
try {
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
} catch (e) {
|
||||
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { status, intl } = this.props;
|
||||
|
||||
@ -122,6 +142,7 @@ class ActionBar extends React.PureComponent {
|
||||
let menu = [];
|
||||
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||
menu.push(null);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import Immutable from 'immutable';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import punycode from 'punycode';
|
||||
import classnames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const IDNA_PREFIX = 'xn--';
|
||||
|
||||
@ -178,8 +179,8 @@ export default class Card extends React.PureComponent {
|
||||
|
||||
<div className='status-card__actions'>
|
||||
<div>
|
||||
<button onClick={this.handleEmbedClick}><i className={`fa fa-${iconVariant}`} /></button>
|
||||
{horizontal && <a href={card.get('url')} target='_blank' rel='noopener'><i className='fa fa-external-link' /></a>}
|
||||
<button onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
|
||||
{horizontal && <a href={card.get('url')} target='_blank' rel='noopener'><Icon id='external-link' /></a>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -201,7 +202,7 @@ export default class Card extends React.PureComponent {
|
||||
} else {
|
||||
embed = (
|
||||
<div className='status-card__image'>
|
||||
<i className='fa fa-file-text' />
|
||||
<Icon id='file-text' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Video from '../../video';
|
||||
import scheduleIdleTask from '../../ui/util/schedule_idle_task';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import PollContainer from 'mastodon/containers/poll_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
local_only: { id: 'status.local_only', defaultMessage: 'This post is only visible by other users of your instance' },
|
||||
@ -26,7 +28,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
status: ImmutablePropTypes.map,
|
||||
onOpenMedia: PropTypes.func.isRequired,
|
||||
onOpenVideo: PropTypes.func.isRequired,
|
||||
onToggleHidden: PropTypes.func.isRequired,
|
||||
@ -111,7 +113,9 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
outerStyle.height = `${this.state.height}px`;
|
||||
}
|
||||
|
||||
if (status.get('media_attachments').size > 0) {
|
||||
if (status.get('poll')) {
|
||||
media = <PollContainer pollId={status.get('poll')} />;
|
||||
} else 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') {
|
||||
@ -155,11 +159,11 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
if (status.get('visibility') === 'private') {
|
||||
reblogLink = <i className={`fa fa-${reblogIcon}`} />;
|
||||
reblogLink = <Icon id={reblogIcon} />;
|
||||
} else if (this.context.router) {
|
||||
reblogLink = (
|
||||
<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||
<i className={`fa fa-${reblogIcon}`} />
|
||||
<Icon id={reblogIcon} />
|
||||
<span className='detailed-status__reblogs'>
|
||||
<FormattedNumber value={status.get('reblogs_count')} />
|
||||
</span>
|
||||
@ -168,7 +172,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
} else {
|
||||
reblogLink = (
|
||||
<a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||
<i className={`fa fa-${reblogIcon}`} />
|
||||
<Icon id={reblogIcon} />
|
||||
<span className='detailed-status__reblogs'>
|
||||
<FormattedNumber value={status.get('reblogs_count')} />
|
||||
</span>
|
||||
@ -183,7 +187,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
if (this.context.router) {
|
||||
favouriteLink = (
|
||||
<Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
|
||||
<i className='fa fa-star' />
|
||||
<Icon id='star' />
|
||||
<span className='detailed-status__favorites'>
|
||||
<FormattedNumber value={status.get('favourites_count')} />
|
||||
</span>
|
||||
@ -192,7 +196,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
} else {
|
||||
favouriteLink = (
|
||||
<a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||
<i className='fa fa-star' />
|
||||
<Icon id='star' />
|
||||
<span className='detailed-status__favorites'>
|
||||
<FormattedNumber value={status.get('favourites_count')} />
|
||||
</span>
|
||||
|
@ -38,6 +38,7 @@ const messages = defineMessages({
|
||||
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
@ -135,11 +136,17 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
dispatch(openModal('VIDEO', { media, time }));
|
||||
},
|
||||
|
||||
onBlock (account) {
|
||||
onBlock (status) {
|
||||
const account = status.get('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'))),
|
||||
secondary: intl.formatMessage(messages.blockAndReport),
|
||||
onSecondary: () => {
|
||||
dispatch(blockAccount(account.get('id')));
|
||||
dispatch(initReport(account, status));
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
||||
|
@ -44,6 +44,7 @@ import { HotKeys } from 'react-hotkeys';
|
||||
import { boostModal, deleteModal } from '../../initial_state';
|
||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
|
||||
import { textForScreenReader } from '../../components/status';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
@ -56,6 +57,7 @@ const messages = defineMessages({
|
||||
detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
|
||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
@ -252,13 +254,19 @@ class Status extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleBlockClick = (account) => {
|
||||
handleBlockClick = (status) => {
|
||||
const { dispatch, intl } = this.props;
|
||||
const account = status.get('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'))),
|
||||
secondary: intl.formatMessage(messages.blockAndReport),
|
||||
onSecondary: () => {
|
||||
dispatch(blockAccount(account.get('id')));
|
||||
dispatch(initReport(account, status));
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
@ -425,7 +433,7 @@ class Status extends ImmutablePureComponent {
|
||||
<ColumnHeader
|
||||
showBackButton
|
||||
extraButton={(
|
||||
<button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><i className={`fa fa-${status.get('hidden') ? 'eye-slash' : 'eye'}`} /></button>
|
||||
<button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -434,7 +442,7 @@ class Status extends ImmutablePureComponent {
|
||||
{ancestors}
|
||||
|
||||
<HotKeys handlers={handlers}>
|
||||
<div className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false, !status.get('hidden'))}>
|
||||
<div className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}>
|
||||
<DetailedStatus
|
||||
status={status}
|
||||
onOpenVideo={this.handleOpenVideo}
|
||||
|
@ -8,6 +8,7 @@ import Avatar from '../../../components/avatar';
|
||||
import RelativeTimestamp from '../../../components/relative_timestamp';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
|
||||
@ -74,7 +75,7 @@ class BoostModal extends ImmutablePureComponent {
|
||||
</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>
|
||||
<div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} /></div>
|
||||
<Button text={intl.formatMessage(messages.reblog)} onClick={this.handleReblog} ref={this.setRef} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -53,6 +53,11 @@ class Bundle extends React.PureComponent {
|
||||
const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
|
||||
const cachedMod = Bundle.cache.get(fetchComponent);
|
||||
|
||||
if (fetchComponent === undefined) {
|
||||
this.setState({ mod: null });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
onFetch();
|
||||
|
||||
if (cachedMod) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class ColumnHeader extends React.PureComponent {
|
||||
|
||||
@ -21,7 +22,7 @@ export default class ColumnHeader extends React.PureComponent {
|
||||
let iconElement = '';
|
||||
|
||||
if (icon) {
|
||||
iconElement = <i className={`fa fa-fw fa-${icon} column-header__icon`} />;
|
||||
iconElement = <Icon id={icon} fixedWidth className='column-header__icon' />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const ColumnLink = ({ icon, text, to, href, method, badge }) => {
|
||||
const badgeElement = typeof badge !== 'undefined' ? <span className='column-link__badge'>{badge}</span> : null;
|
||||
@ -8,7 +9,7 @@ const ColumnLink = ({ icon, text, to, href, method, badge }) => {
|
||||
if (href) {
|
||||
return (
|
||||
<a href={href} className='column-link' data-method={method}>
|
||||
<i className={`fa fa-fw fa-${icon} column-link__icon`} />
|
||||
<Icon id={icon} fixedWidth className='column-link__icon' />
|
||||
{text}
|
||||
{badgeElement}
|
||||
</a>
|
||||
@ -16,7 +17,7 @@ const ColumnLink = ({ icon, text, to, href, method, badge }) => {
|
||||
} else {
|
||||
return (
|
||||
<Link to={to} className='column-link'>
|
||||
<i className={`fa fa-fw fa-${icon} column-link__icon`} />
|
||||
<Icon id={icon} fixedWidth className='column-link__icon' />
|
||||
{text}
|
||||
{badgeElement}
|
||||
</Link>
|
||||
|
@ -13,6 +13,7 @@ import ColumnLoading from './column_loading';
|
||||
import DrawerLoading from './drawer_loading';
|
||||
import BundleColumnError from './bundle_column_error';
|
||||
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
import detectPassiveEvents from 'detect-passive-events';
|
||||
import { scrollRight } from '../../../scroll';
|
||||
@ -160,7 +161,7 @@ class ColumnsArea extends ImmutablePureComponent {
|
||||
this.pendingIndex = null;
|
||||
|
||||
if (singleColumn) {
|
||||
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><i className='fa fa-pencil' /></Link>;
|
||||
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
|
||||
|
||||
return columnIndex !== -1 ? [
|
||||
<ReactSwipeableViews key='content' index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
|
||||
|
@ -11,6 +11,8 @@ class ConfirmationModal extends React.PureComponent {
|
||||
confirm: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
secondary: PropTypes.string,
|
||||
onSecondary: PropTypes.func,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
@ -23,6 +25,11 @@ class ConfirmationModal extends React.PureComponent {
|
||||
this.props.onConfirm();
|
||||
}
|
||||
|
||||
handleSecondary = () => {
|
||||
this.props.onClose();
|
||||
this.props.onSecondary();
|
||||
}
|
||||
|
||||
handleCancel = () => {
|
||||
this.props.onClose();
|
||||
}
|
||||
@ -32,7 +39,7 @@ class ConfirmationModal extends React.PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { message, confirm } = this.props;
|
||||
const { message, confirm, secondary } = this.props;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal confirmation-modal'>
|
||||
@ -44,6 +51,9 @@ class ConfirmationModal extends React.PureComponent {
|
||||
<Button onClick={this.handleCancel} className='confirmation-modal__cancel-button'>
|
||||
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
|
||||
</Button>
|
||||
{secondary !== undefined && (
|
||||
<Button text={secondary} onClick={this.handleSecondary} className='confirmation-modal__secondary-button' />
|
||||
)}
|
||||
<Button text={confirm} onClick={this.handleClick} ref={this.setRef} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,6 +9,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImageLoader from './image_loader';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
@ -108,8 +109,8 @@ class MediaModal extends ImmutablePureComponent {
|
||||
const index = this.getIndex();
|
||||
let pagination = [];
|
||||
|
||||
const leftNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>;
|
||||
const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>;
|
||||
const leftNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>;
|
||||
const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>;
|
||||
|
||||
if (media.size > 1) {
|
||||
pagination = media.map((item, i) => {
|
||||
|
@ -97,7 +97,7 @@ class ReportModal extends ImmutablePureComponent {
|
||||
|
||||
<div className='report-modal__container'>
|
||||
<div className='report-modal__comment'>
|
||||
<p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:' /></p>
|
||||
<p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:' /></p>
|
||||
|
||||
<textarea
|
||||
className='setting-text light'
|
||||
|
@ -4,16 +4,17 @@ import { NavLink, withRouter } from 'react-router-dom';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { debounce } from 'lodash';
|
||||
import { isUserTouching } from '../../../is_mobile';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export const links = [
|
||||
<NavLink className='tabs-bar__link primary' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link primary' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><Icon id='bell' fixedWidth /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
|
||||
|
||||
<NavLink className='tabs-bar__link secondary' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link secondary' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link primary' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><i className='fa fa-fw fa-search' /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link secondary' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link secondary' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link primary' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><Icon id='search' fixedWidth /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>,
|
||||
|
||||
<NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><i className='fa fa-fw fa-bars' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>,
|
||||
];
|
||||
|
||||
export function getIndex (path) {
|
||||
|
@ -263,7 +263,7 @@ class UI extends React.PureComponent {
|
||||
this.setState({ draggingOver: false });
|
||||
this.dragTargets = [];
|
||||
|
||||
if (e.dataTransfer && e.dataTransfer.files.length === 1) {
|
||||
if (e.dataTransfer && e.dataTransfer.files.length >= 1) {
|
||||
this.props.dispatch(uploadCompose(e.dataTransfer.files));
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { throttle } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
||||
import { displayMedia } from '../../initial_state';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
play: { id: 'video.play', defaultMessage: 'Play' },
|
||||
@ -346,7 +347,6 @@ class Video extends React.PureComponent {
|
||||
width = containerWidth;
|
||||
height = containerWidth / (16/9);
|
||||
|
||||
playerStyle.width = width;
|
||||
playerStyle.height = height;
|
||||
}
|
||||
|
||||
@ -418,8 +418,8 @@ class Video extends React.PureComponent {
|
||||
|
||||
<div className='video-player__buttons-bar'>
|
||||
<div className='video-player__buttons left'>
|
||||
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
|
||||
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
|
||||
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
||||
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
||||
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
||||
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
|
||||
<span
|
||||
@ -439,10 +439,10 @@ class Video extends React.PureComponent {
|
||||
</div>
|
||||
|
||||
<div className='video-player__buttons right'>
|
||||
{!onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>}
|
||||
{(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>}
|
||||
{onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>}
|
||||
<button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button>
|
||||
{!onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye' fixedWidth /></button>}
|
||||
{(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
||||
{onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
||||
<button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,45 +1,43 @@
|
||||
{
|
||||
"account.add_or_remove_from_list": "اضافو أو حذف مِن القوائم",
|
||||
"account.add_or_remove_from_list": "أضيف/ي أو أحذف/ي من القائمة",
|
||||
"account.badges.bot": "روبوت",
|
||||
"account.block": "حظر @{name}",
|
||||
"account.block_domain": "إخفاء كل شيئ قادم من إسم النطاق {domain}",
|
||||
"account.blocked": "محظور",
|
||||
"account.direct": "رسالة خاصة إلى @{name}",
|
||||
"account.disclaimer_full": "قد لا تعكس المعلومات أدناه الملف الشخصي الكامل للمستخدم.",
|
||||
"account.domain_blocked": "النطاق مخفي",
|
||||
"account.edit_profile": "تعديل الملف الشخصي",
|
||||
"account.endorse": "إبرازه على الملف الشخصي",
|
||||
"account.endorse": "خاصّية على الملف الشخصي",
|
||||
"account.follow": "تابِع",
|
||||
"account.followers": "المتابعون",
|
||||
"account.followers": "متابعون",
|
||||
"account.followers.empty": "لا أحد يتبع هذا الحساب بعد.",
|
||||
"account.follows": "يتبع",
|
||||
"account.follows.empty": "هذا المستخدِم لا يتبع أحدًا بعد.",
|
||||
"account.follows.empty": "هذا الحساب لا يتبع أحدًا بعد.",
|
||||
"account.follows_you": "يتابعك",
|
||||
"account.hide_reblogs": "إخفاء ترقيات @{name}",
|
||||
"account.link_verified_on": "تم التحقق مِن مالك هذا الرابط بتاريخ {date}",
|
||||
"account.locked_info": "تم تأمين خصوصية هذا الحساب عبر قُفل. فصاحب الحساب يُراجِع يدويا طلبات المتابَعة و الاشتراك بحسابه.",
|
||||
"account.link_verified_on": "تم التحقق مِن مِلْكية هذا الرابط بتاريخ {date}",
|
||||
"account.locked_info": "تم تأمين خصوصية هذا الحساب عبر قفل. صاحب الحساب يُراجِع يدويا طلبات المتابَعة و الاشتراك بحسابه.",
|
||||
"account.media": "وسائط",
|
||||
"account.mention": "أُذكُر @{name}",
|
||||
"account.mention": "أُذكُر/ي @{name}",
|
||||
"account.moved_to": "{name} إنتقل إلى :",
|
||||
"account.mute": "أكتم @{name}",
|
||||
"account.mute_notifications": "كتم إخطارات @{name}",
|
||||
"account.mute": "كتم @{name}",
|
||||
"account.mute_notifications": "كتم الإخطارات من @{name}",
|
||||
"account.muted": "مكتوم",
|
||||
"account.posts": "التبويقات",
|
||||
"account.posts_with_replies": "التبويقات و الردود",
|
||||
"account.report": "أبلغ عن @{name}",
|
||||
"account.requested": "في انتظار الموافقة",
|
||||
"account.share": "مشاركة @{name}'s profile",
|
||||
"account.report": "أبلغ/ي عن @{name}",
|
||||
"account.requested": "في انتظار الموافقة. اضْغَطْ/ي لإلغاء طلب المتابعة",
|
||||
"account.share": "مشاركة حساب @{name}",
|
||||
"account.show_reblogs": "عرض ترقيات @{name}",
|
||||
"account.unblock": "إلغاء الحظر عن @{name}",
|
||||
"account.unblock_domain": "فك حظر {domain}",
|
||||
"account.unblock_domain": "فك الخْفى عن {domain}",
|
||||
"account.unendorse": "إزالة ترويجه مِن الملف الشخصي",
|
||||
"account.unfollow": "إلغاء المتابعة",
|
||||
"account.unmute": "إلغاء الكتم عن @{name}",
|
||||
"account.unmute_notifications": "إلغاء كتم إخطارات @{name}",
|
||||
"account.view_full_profile": "عرض الملف الشخصي كاملا",
|
||||
"alert.unexpected.message": "لقد طرأ هناك خطأ غير متوقّع.",
|
||||
"alert.unexpected.title": "المعذرة !",
|
||||
"boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة",
|
||||
"boost_modal.combo": "يمكنك/ي ضغط {combo} لتخطّي هذه في المرّة القادمة",
|
||||
"bundle_column_error.body": "لقد وقع هناك خطأ أثناء عملية تحميل هذا العنصر.",
|
||||
"bundle_column_error.retry": "إعادة المحاولة",
|
||||
"bundle_column_error.title": "خطأ في الشبكة",
|
||||
@ -47,7 +45,7 @@
|
||||
"bundle_modal_error.message": "لقد وقع هناك خطأ أثناء عملية تحميل هذا العنصر.",
|
||||
"bundle_modal_error.retry": "إعادة المحاولة",
|
||||
"column.blocks": "الحسابات المحجوبة",
|
||||
"column.community": "الخيط العام المحلي",
|
||||
"column.community": "التَسَلْسُل الزَمني المحلي",
|
||||
"column.direct": "الرسائل المباشرة",
|
||||
"column.domain_blocks": "النطاقات المخفية",
|
||||
"column.favourites": "المفضلة",
|
||||
@ -73,6 +71,10 @@
|
||||
"compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.",
|
||||
"compose_form.lock_disclaimer.lock": "مقفل",
|
||||
"compose_form.placeholder": "فيمَ تفكّر؟",
|
||||
"compose_form.poll.add_option": "إضافة خيار",
|
||||
"compose_form.poll.duration": "مدة استطلاع الرأي",
|
||||
"compose_form.poll.option_placeholder": "الخيار {number}",
|
||||
"compose_form.poll.remove_option": "إزالة هذا الخيار",
|
||||
"compose_form.publish": "بوّق",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive.marked": "لقد تم تحديد هذه الصورة كحساسة",
|
||||
@ -81,6 +83,7 @@
|
||||
"compose_form.spoiler.unmarked": "النص غير مخفي",
|
||||
"compose_form.spoiler_placeholder": "تنبيه عن المحتوى",
|
||||
"confirmation_modal.cancel": "إلغاء",
|
||||
"confirmations.block.block_and_report": "Block & Report",
|
||||
"confirmations.block.confirm": "حجب",
|
||||
"confirmations.block.message": "هل أنت متأكد أنك تريد حجب {name} ؟",
|
||||
"confirmations.delete.confirm": "حذف",
|
||||
@ -114,6 +117,7 @@
|
||||
"emoji_button.symbols": "رموز",
|
||||
"emoji_button.travel": "أماكن و أسفار",
|
||||
"empty_column.account_timeline": "ليس هناك تبويقات!",
|
||||
"empty_column.account_unavailable": "الملف الشخصي غير متوفر",
|
||||
"empty_column.blocks": "لم تقم بحظر أي مستخدِم بعد.",
|
||||
"empty_column.community": "الخط الزمني المحلي فارغ. أكتب شيئا ما للعامة كبداية !",
|
||||
"empty_column.direct": "لم تتلق أية رسالة خاصة مباشِرة بعد. سوف يتم عرض الرسائل المباشرة هنا إن قمت بإرسال واحدة أو تلقيت البعض منها.",
|
||||
@ -128,7 +132,7 @@
|
||||
"empty_column.lists": "ليس عندك أية قائمة بعد. سوف تظهر قائمتك هنا إن قمت بإنشاء واحدة.",
|
||||
"empty_column.mutes": "لم تقم بكتم أي مستخدم بعد.",
|
||||
"empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
|
||||
"empty_column.public": "لا يوجد أي شيء هنا ! قم بنشر شيء ما للعامة، أو إتبع مستخدمين آخرين في الخوادم المثيلة الأخرى لملء خيط المحادثات العام",
|
||||
"empty_column.public": "لا يوجد أي شيء هنا ! قم بنشر شيء ما للعامة، أو إتبع المستخدمين الآخرين المتواجدين على الخوادم الأخرى لملء خيط المحادثات",
|
||||
"federation.change": "Adjust status federation",
|
||||
"federation.federated.long": "Allow toot to reach other instances",
|
||||
"federation.federated.short": "Federated",
|
||||
@ -147,13 +151,18 @@
|
||||
"hashtag.column_header.tag_mode.all": "و {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "أو {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "بدون {additional}",
|
||||
"hashtag.column_settings.select.no_options_message": "لم يُعثَر على أي اقتراح",
|
||||
"hashtag.column_settings.select.placeholder": "قم بإدخال وسوم…",
|
||||
"hashtag.column_settings.tag_mode.all": "كلها",
|
||||
"hashtag.column_settings.tag_mode.any": "أي كان مِن هذه",
|
||||
"hashtag.column_settings.tag_mode.none": "لا شيء مِن هذه",
|
||||
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
|
||||
"hashtag.column_settings.tag_toggle": "إدراج الوسوم الإضافية لهذا العمود",
|
||||
"home.column_settings.basic": "أساسية",
|
||||
"home.column_settings.show_reblogs": "عرض الترقيات",
|
||||
"home.column_settings.show_replies": "عرض الردود",
|
||||
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
||||
"introduction.federation.action": "التالي",
|
||||
"introduction.federation.federated.headline": "Federated",
|
||||
"introduction.federation.federated.text": "كافة المنشورات التي نُشِرت إلى العامة على الخوادم الأخرى للفديفرس سوف يتم عرضها على الخيط المُوحَّد.",
|
||||
@ -209,6 +218,7 @@
|
||||
"lists.account.remove": "إحذف من القائمة",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "تعديل القائمة",
|
||||
"lists.edit.submit": "تعديل العنوان",
|
||||
"lists.new.create": "إنشاء قائمة",
|
||||
"lists.new.title_placeholder": "عنوان القائمة الجديدة",
|
||||
"lists.search": "إبحث في قائمة الحسابات التي تُتابِعها",
|
||||
@ -229,7 +239,7 @@
|
||||
"navigation_bar.favourites": "المفضلة",
|
||||
"navigation_bar.filters": "الكلمات المكتومة",
|
||||
"navigation_bar.follow_requests": "طلبات المتابعة",
|
||||
"navigation_bar.info": "معلومات إضافية",
|
||||
"navigation_bar.info": "عن هذا الخادم",
|
||||
"navigation_bar.keyboard_shortcuts": "إختصارات لوحة المفاتيح",
|
||||
"navigation_bar.lists": "القوائم",
|
||||
"navigation_bar.logout": "خروج",
|
||||
@ -242,6 +252,7 @@
|
||||
"notification.favourite": "أُعجِب {name} بمنشورك",
|
||||
"notification.follow": "{name} يتابعك",
|
||||
"notification.mention": "{name} ذكرك",
|
||||
"notification.poll": "A poll you have voted in has ended",
|
||||
"notification.reblog": "{name} قام بترقية تبويقك",
|
||||
"notifications.clear": "إمسح الإخطارات",
|
||||
"notifications.clear_confirmation": "أمتأكد من أنك تود مسح جل الإخطارات الخاصة بك و المتلقاة إلى حد الآن ؟",
|
||||
@ -252,6 +263,7 @@
|
||||
"notifications.column_settings.filter_bar.show": "عرض",
|
||||
"notifications.column_settings.follow": "متابعُون جُدُد :",
|
||||
"notifications.column_settings.mention": "الإشارات :",
|
||||
"notifications.column_settings.poll": "نتائج استطلاع الرأي:",
|
||||
"notifications.column_settings.push": "الإخطارات المدفوعة",
|
||||
"notifications.column_settings.reblog": "الترقيّات:",
|
||||
"notifications.column_settings.show": "إعرِضها في عمود",
|
||||
@ -261,7 +273,14 @@
|
||||
"notifications.filter.favourites": "المفضلة",
|
||||
"notifications.filter.follows": "يتابِع",
|
||||
"notifications.filter.mentions": "الإشارات",
|
||||
"notifications.filter.polls": "نتائج استطلاع الرأي",
|
||||
"notifications.group": "{count} إشعارات",
|
||||
"poll.closed": "انتهى",
|
||||
"poll.refresh": "تحديث",
|
||||
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
|
||||
"poll.vote": "صَوّت",
|
||||
"poll_button.add_poll": "إضافة استطلاع للرأي",
|
||||
"poll_button.remove_poll": "إزالة استطلاع الرأي",
|
||||
"privacy.change": "إضبط خصوصية المنشور",
|
||||
"privacy.direct.long": "أنشر إلى المستخدمين المشار إليهم فقط",
|
||||
"privacy.direct.short": "مباشر",
|
||||
@ -281,7 +300,7 @@
|
||||
"reply_indicator.cancel": "إلغاء",
|
||||
"report.forward": "التحويل إلى {target}",
|
||||
"report.forward_hint": "هذا الحساب ينتمي إلى خادوم آخَر. هل تودّ إرسال نسخة مجهولة مِن التقرير إلى هنالك أيضًا ؟",
|
||||
"report.hint": "سوف يتم إرسال التقرير إلى مُشرِفي مثيل خادومكم. بإمكانك الإدلاء بشرح عن سبب الإبلاغ عن الحساب أسفله :",
|
||||
"report.hint": "سوف يتم إرسال التقرير إلى المُشرِفين على خادومكم. بإمكانكم الإدلاء بشرح عن سبب الإبلاغ عن الحساب أسفله:",
|
||||
"report.placeholder": "تعليقات إضافية",
|
||||
"report.submit": "إرسال",
|
||||
"report.target": "إبلاغ",
|
||||
@ -296,12 +315,12 @@
|
||||
"search_results.hashtags": "الوُسوم",
|
||||
"search_results.statuses": "التبويقات",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} و {results}}",
|
||||
"standalone.public_title": "نظرة على ...",
|
||||
"status.admin_account": "افتح الواجهة الإدارية لـ @{name}",
|
||||
"status.admin_status": "افتح هذا المنشور على واجهة الإشراف",
|
||||
"status.block": "Block @{name}",
|
||||
"status.cancel_reblog_private": "إلغاء الترقية",
|
||||
"status.cannot_reblog": "تعذرت ترقية هذا المنشور",
|
||||
"status.copy": "نسخ رابط المنشور",
|
||||
"status.delete": "إحذف",
|
||||
"status.detailed_status": "تفاصيل المحادثة",
|
||||
"status.direct": "رسالة خاصة إلى @{name}",
|
||||
@ -344,10 +363,17 @@
|
||||
"tabs_bar.local_timeline": "المحلي",
|
||||
"tabs_bar.notifications": "الإخطارات",
|
||||
"tabs_bar.search": "البحث",
|
||||
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
|
||||
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
|
||||
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
|
||||
"time_remaining.moments": "Moments remaining",
|
||||
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
|
||||
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} آخرون {people}} يتحدثون",
|
||||
"ui.beforeunload": "سوف تفقد مسودتك إن تركت ماستدون.",
|
||||
"upload_area.title": "إسحب ثم أفلت للرفع",
|
||||
"upload_button.label": "إضافة وسائط (JPEG، PNG، GIF، WebM، MP4، MOV)",
|
||||
"upload_error.limit": "لقد تم بلوغ الحد الأقصى المسموح به لإرسال الملفات.",
|
||||
"upload_error.poll": "لا يمكن إدراج ملفات في استطلاعات الرأي.",
|
||||
"upload_form.description": "وصف للمعاقين بصريا",
|
||||
"upload_form.focus": "قص",
|
||||
"upload_form.undo": "حذف",
|
||||
|
@ -5,7 +5,6 @@
|
||||
"account.block_domain": "Anubrir tolo de {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.direct": "Unviar un mensaxe direutu a @{name}",
|
||||
"account.disclaimer_full": "La información d'embaxo podría reflexar de mou incompletu'l perfil del usuariu.",
|
||||
"account.domain_blocked": "Dominiu anubríu",
|
||||
"account.edit_profile": "Editar el perfil",
|
||||
"account.endorse": "Destacar nel perfil",
|
||||
@ -36,7 +35,6 @@
|
||||
"account.unfollow": "Unfollow",
|
||||
"account.unmute": "Unmute @{name}",
|
||||
"account.unmute_notifications": "Unmute notifications from @{name}",
|
||||
"account.view_full_profile": "Ver el perfil completu",
|
||||
"alert.unexpected.message": "Asocedió un fallu inesperáu.",
|
||||
"alert.unexpected.title": "¡Ups!",
|
||||
"boost_modal.combo": "Pues primir {combo} pa saltar esto la próxima vegada",
|
||||
@ -73,6 +71,10 @@
|
||||
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
|
||||
"compose_form.lock_disclaimer.lock": "locked",
|
||||
"compose_form.placeholder": "¿En qué pienses?",
|
||||
"compose_form.poll.add_option": "Add a choice",
|
||||
"compose_form.poll.duration": "Poll duration",
|
||||
"compose_form.poll.option_placeholder": "Choice {number}",
|
||||
"compose_form.poll.remove_option": "Remove this choice",
|
||||
"compose_form.publish": "Toot",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive.marked": "Media is marked as sensitive",
|
||||
@ -81,6 +83,7 @@
|
||||
"compose_form.spoiler.unmarked": "El testu va anubrise",
|
||||
"compose_form.spoiler_placeholder": "Escribi equí l'avertencia",
|
||||
"confirmation_modal.cancel": "Encaboxar",
|
||||
"confirmations.block.block_and_report": "Block & Report",
|
||||
"confirmations.block.confirm": "Block",
|
||||
"confirmations.block.message": "¿De xuru que quies bloquiar a {name}?",
|
||||
"confirmations.delete.confirm": "Delete",
|
||||
@ -114,6 +117,7 @@
|
||||
"emoji_button.symbols": "Símbolos",
|
||||
"emoji_button.travel": "Viaxes y llugares",
|
||||
"empty_column.account_timeline": "No toots here!",
|
||||
"empty_column.account_unavailable": "Profile unavailable",
|
||||
"empty_column.blocks": "Entá nun bloquiesti a dengún usuariu.",
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"empty_column.direct": "Entá nun tienes dengún mensaxe direutu. Cuando unvies o recibas dalgún, va apaecer equí.",
|
||||
@ -147,6 +151,8 @@
|
||||
"hashtag.column_header.tag_mode.all": "and {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "or {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "without {additional}",
|
||||
"hashtag.column_settings.select.no_options_message": "No suggestions found",
|
||||
"hashtag.column_settings.select.placeholder": "Enter hashtags…",
|
||||
"hashtag.column_settings.tag_mode.all": "All of these",
|
||||
"hashtag.column_settings.tag_mode.any": "Any of these",
|
||||
"hashtag.column_settings.tag_mode.none": "None of these",
|
||||
@ -154,6 +160,9 @@
|
||||
"home.column_settings.basic": "Basic",
|
||||
"home.column_settings.show_reblogs": "Amosar toots compartíos",
|
||||
"home.column_settings.show_replies": "Amosar rempuestes",
|
||||
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
||||
"introduction.federation.action": "Next",
|
||||
"introduction.federation.federated.headline": "Federated",
|
||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
||||
@ -209,6 +218,7 @@
|
||||
"lists.account.remove": "Desaniciar de la llista",
|
||||
"lists.delete": "Desaniciar la llista",
|
||||
"lists.edit": "Editar la llista",
|
||||
"lists.edit.submit": "Change title",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "Títulu nuevu de la llista",
|
||||
"lists.search": "Guetar ente la xente que sigues",
|
||||
@ -242,6 +252,7 @@
|
||||
"notification.favourite": "{name} favourited your status",
|
||||
"notification.follow": "{name} siguióte",
|
||||
"notification.mention": "{name} mentóte",
|
||||
"notification.poll": "A poll you have voted in has ended",
|
||||
"notification.reblog": "{name} compartió'l to estáu",
|
||||
"notifications.clear": "Llimpiar avisos",
|
||||
"notifications.clear_confirmation": "¿De xuru que quies llimpiar dafechu tolos avisos?",
|
||||
@ -252,6 +263,7 @@
|
||||
"notifications.column_settings.filter_bar.show": "Show",
|
||||
"notifications.column_settings.follow": "Siguidores nuevos:",
|
||||
"notifications.column_settings.mention": "Menciones:",
|
||||
"notifications.column_settings.poll": "Poll results:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.reblog": "Toots compartíos:",
|
||||
"notifications.column_settings.show": "Amosar en columna",
|
||||
@ -261,7 +273,14 @@
|
||||
"notifications.filter.favourites": "Favourites",
|
||||
"notifications.filter.follows": "Follows",
|
||||
"notifications.filter.mentions": "Mentions",
|
||||
"notifications.filter.polls": "Poll results",
|
||||
"notifications.group": "{count} avisos",
|
||||
"poll.closed": "Closed",
|
||||
"poll.refresh": "Refresh",
|
||||
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
|
||||
"poll.vote": "Vote",
|
||||
"poll_button.add_poll": "Add a poll",
|
||||
"poll_button.remove_poll": "Remove poll",
|
||||
"privacy.change": "Adjust status privacy",
|
||||
"privacy.direct.long": "Post to mentioned users only",
|
||||
"privacy.direct.short": "Direct",
|
||||
@ -296,12 +315,12 @@
|
||||
"search_results.hashtags": "Etiquetes",
|
||||
"search_results.statuses": "Toots",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
||||
"standalone.public_title": "A look inside...",
|
||||
"status.admin_account": "Open moderation interface for @{name}",
|
||||
"status.admin_status": "Open this status in the moderation interface",
|
||||
"status.block": "Bloquiar a @{name}",
|
||||
"status.cancel_reblog_private": "Dexar de compartir",
|
||||
"status.cannot_reblog": "Esti artículu nun pue compartise",
|
||||
"status.copy": "Copy link to status",
|
||||
"status.delete": "Delete",
|
||||
"status.detailed_status": "Detailed conversation view",
|
||||
"status.direct": "Unviar un mensaxe direutu a @{name}",
|
||||
@ -344,10 +363,17 @@
|
||||
"tabs_bar.local_timeline": "Llocal",
|
||||
"tabs_bar.notifications": "Avisos",
|
||||
"tabs_bar.search": "Search",
|
||||
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
|
||||
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
|
||||
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
|
||||
"time_remaining.moments": "Moments remaining",
|
||||
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
|
||||
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
|
||||
"ui.beforeunload": "El borrador va perdese si coles de Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.description": "Descripción pa discapacitaos visuales",
|
||||
"upload_form.focus": "Crop",
|
||||
"upload_form.undo": "Desaniciar",
|
||||
|
@ -5,7 +5,6 @@
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
"account.direct": "Direct Message @{name}",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Редактирай профила си",
|
||||
"account.endorse": "Feature on profile",
|
||||
@ -36,7 +35,6 @@
|
||||
"account.unfollow": "Не следвай",
|
||||
"account.unmute": "Unmute @{name}",
|
||||
"account.unmute_notifications": "Unmute notifications from @{name}",
|
||||
"account.view_full_profile": "View full profile",
|
||||
"alert.unexpected.message": "An unexpected error occurred.",
|
||||
"alert.unexpected.title": "Oops!",
|
||||
"boost_modal.combo": "You can press {combo} to skip this next time",
|
||||
@ -73,6 +71,10 @@
|
||||
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
|
||||
"compose_form.lock_disclaimer.lock": "locked",
|
||||
"compose_form.placeholder": "Какво си мислиш?",
|
||||
"compose_form.poll.add_option": "Add a choice",
|
||||
"compose_form.poll.duration": "Poll duration",
|
||||
"compose_form.poll.option_placeholder": "Choice {number}",
|
||||
"compose_form.poll.remove_option": "Remove this choice",
|
||||
"compose_form.publish": "Раздумай",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive.marked": "Media is marked as sensitive",
|
||||
@ -81,6 +83,7 @@
|
||||
"compose_form.spoiler.unmarked": "Text is not hidden",
|
||||
"compose_form.spoiler_placeholder": "Content warning",
|
||||
"confirmation_modal.cancel": "Cancel",
|
||||
"confirmations.block.block_and_report": "Block & Report",
|
||||
"confirmations.block.confirm": "Block",
|
||||
"confirmations.block.message": "Are you sure you want to block {name}?",
|
||||
"confirmations.delete.confirm": "Delete",
|
||||
@ -114,6 +117,7 @@
|
||||
"emoji_button.symbols": "Symbols",
|
||||
"emoji_button.travel": "Travel & Places",
|
||||
"empty_column.account_timeline": "No toots here!",
|
||||
"empty_column.account_unavailable": "Profile unavailable",
|
||||
"empty_column.blocks": "You haven't blocked any users yet.",
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
|
||||
@ -147,6 +151,8 @@
|
||||
"hashtag.column_header.tag_mode.all": "and {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "or {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "without {additional}",
|
||||
"hashtag.column_settings.select.no_options_message": "No suggestions found",
|
||||
"hashtag.column_settings.select.placeholder": "Enter hashtags…",
|
||||
"hashtag.column_settings.tag_mode.all": "All of these",
|
||||
"hashtag.column_settings.tag_mode.any": "Any of these",
|
||||
"hashtag.column_settings.tag_mode.none": "None of these",
|
||||
@ -154,6 +160,9 @@
|
||||
"home.column_settings.basic": "Basic",
|
||||
"home.column_settings.show_reblogs": "Show boosts",
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
||||
"introduction.federation.action": "Next",
|
||||
"introduction.federation.federated.headline": "Federated",
|
||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
||||
@ -209,6 +218,7 @@
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.edit.submit": "Change title",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among people you follow",
|
||||
@ -242,6 +252,7 @@
|
||||
"notification.favourite": "{name} хареса твоята публикация",
|
||||
"notification.follow": "{name} те последва",
|
||||
"notification.mention": "{name} те спомена",
|
||||
"notification.poll": "A poll you have voted in has ended",
|
||||
"notification.reblog": "{name} сподели твоята публикация",
|
||||
"notifications.clear": "Clear notifications",
|
||||
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
|
||||
@ -252,6 +263,7 @@
|
||||
"notifications.column_settings.filter_bar.show": "Show",
|
||||
"notifications.column_settings.follow": "Нови последователи:",
|
||||
"notifications.column_settings.mention": "Споменавания:",
|
||||
"notifications.column_settings.poll": "Poll results:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.reblog": "Споделяния:",
|
||||
"notifications.column_settings.show": "Покажи в колона",
|
||||
@ -261,7 +273,14 @@
|
||||
"notifications.filter.favourites": "Favourites",
|
||||
"notifications.filter.follows": "Follows",
|
||||
"notifications.filter.mentions": "Mentions",
|
||||
"notifications.filter.polls": "Poll results",
|
||||
"notifications.group": "{count} notifications",
|
||||
"poll.closed": "Closed",
|
||||
"poll.refresh": "Refresh",
|
||||
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
|
||||
"poll.vote": "Vote",
|
||||
"poll_button.add_poll": "Add a poll",
|
||||
"poll_button.remove_poll": "Remove poll",
|
||||
"privacy.change": "Adjust status privacy",
|
||||
"privacy.direct.long": "Post to mentioned users only",
|
||||
"privacy.direct.short": "Direct",
|
||||
@ -296,12 +315,12 @@
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.statuses": "Toots",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
||||
"standalone.public_title": "A look inside...",
|
||||
"status.admin_account": "Open moderation interface for @{name}",
|
||||
"status.admin_status": "Open this status in the moderation interface",
|
||||
"status.block": "Block @{name}",
|
||||
"status.cancel_reblog_private": "Unboost",
|
||||
"status.cannot_reblog": "This post cannot be boosted",
|
||||
"status.copy": "Copy link to status",
|
||||
"status.delete": "Изтриване",
|
||||
"status.detailed_status": "Detailed conversation view",
|
||||
"status.direct": "Direct message @{name}",
|
||||
@ -344,10 +363,17 @@
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
"tabs_bar.notifications": "Известия",
|
||||
"tabs_bar.search": "Search",
|
||||
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
|
||||
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
|
||||
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
|
||||
"time_remaining.moments": "Moments remaining",
|
||||
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
|
||||
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Добави медия",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.description": "Describe for the visually impaired",
|
||||
"upload_form.focus": "Crop",
|
||||
"upload_form.undo": "Отмяна",
|
||||
|
384
app/javascript/mastodon/locales/bn.json
Normal file
384
app/javascript/mastodon/locales/bn.json
Normal file
@ -0,0 +1,384 @@
|
||||
{
|
||||
"account.add_or_remove_from_list": "লিস্টে আরো যুক্ত বা মুছে ফেলুন",
|
||||
"account.badges.bot": "রোবট",
|
||||
"account.block": "@{name} কে বন্ধ করুন",
|
||||
"account.block_domain": "{domain} থেকে সব সরিয়ে ফেলুন",
|
||||
"account.blocked": "বন্ধ করা হয়েছে",
|
||||
"account.direct": "@{name}কে সরকারি পাঠান",
|
||||
"account.domain_blocked": "বেবিসিটটি সরানো আছে",
|
||||
"account.edit_profile": "Edit profile",
|
||||
"account.endorse": "Feature on profile",
|
||||
"account.follow": "Follow",
|
||||
"account.followers": "Followers",
|
||||
"account.followers.empty": "No one follows this user yet.",
|
||||
"account.follows": "Follows",
|
||||
"account.follows.empty": "This user doesn't follow anyone yet.",
|
||||
"account.follows_you": "Follows you",
|
||||
"account.hide_reblogs": "Hide boosts from @{name}",
|
||||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mention @{name}",
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Mute @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.muted": "Muted",
|
||||
"account.posts": "Toots",
|
||||
"account.posts_with_replies": "Toots and replies",
|
||||
"account.report": "Report @{name}",
|
||||
"account.requested": "Awaiting approval. Click to cancel follow request",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.show_reblogs": "Show boosts from @{name}",
|
||||
"account.unblock": "Unblock @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unendorse": "Don't feature on profile",
|
||||
"account.unfollow": "Unfollow",
|
||||
"account.unmute": "Unmute @{name}",
|
||||
"account.unmute_notifications": "Unmute notifications from @{name}",
|
||||
"alert.unexpected.message": "An unexpected error occurred.",
|
||||
"alert.unexpected.title": "Oops!",
|
||||
"boost_modal.combo": "You can press {combo} to skip this next time",
|
||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
||||
"bundle_column_error.retry": "Try again",
|
||||
"bundle_column_error.title": "Network error",
|
||||
"bundle_modal_error.close": "Close",
|
||||
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
||||
"bundle_modal_error.retry": "Try again",
|
||||
"column.blocks": "Blocked users",
|
||||
"column.community": "Local timeline",
|
||||
"column.direct": "Direct messages",
|
||||
"column.domain_blocks": "Hidden domains",
|
||||
"column.favourites": "Favourites",
|
||||
"column.follow_requests": "Follow requests",
|
||||
"column.home": "Home",
|
||||
"column.lists": "Lists",
|
||||
"column.mutes": "Muted users",
|
||||
"column.notifications": "Notifications",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Federated timeline",
|
||||
"column_back_button.label": "Back",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.settings": "Settings",
|
||||
"community.column_settings.media_only": "Media Only",
|
||||
"compose_form.direct_message_warning": "This toot will only be sent to all the mentioned users.",
|
||||
"compose_form.direct_message_warning_learn_more": "Learn more",
|
||||
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
|
||||
"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.poll.add_option": "Add a choice",
|
||||
"compose_form.poll.duration": "Poll duration",
|
||||
"compose_form.poll.option_placeholder": "Choice {number}",
|
||||
"compose_form.poll.remove_option": "Remove this choice",
|
||||
"compose_form.publish": "Toot",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive.marked": "Media is marked as sensitive",
|
||||
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
|
||||
"compose_form.spoiler.marked": "Text is hidden behind warning",
|
||||
"compose_form.spoiler.unmarked": "Text is not hidden",
|
||||
"compose_form.spoiler_placeholder": "Write your warning here",
|
||||
"confirmation_modal.cancel": "Cancel",
|
||||
"confirmations.block.block_and_report": "Block & Report",
|
||||
"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.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
||||
"confirmations.mute.confirm": "Mute",
|
||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||
"confirmations.redraft.confirm": "Delete & redraft",
|
||||
"confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
|
||||
"confirmations.reply.confirm": "Reply",
|
||||
"confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||
"embed.preview": "Here is what it will look like:",
|
||||
"emoji_button.activity": "Activity",
|
||||
"emoji_button.custom": "Custom",
|
||||
"emoji_button.flags": "Flags",
|
||||
"emoji_button.food": "Food & Drink",
|
||||
"emoji_button.label": "Insert emoji",
|
||||
"emoji_button.nature": "Nature",
|
||||
"emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.objects": "Objects",
|
||||
"emoji_button.people": "People",
|
||||
"emoji_button.recent": "Frequently used",
|
||||
"emoji_button.search": "Search...",
|
||||
"emoji_button.search_results": "Search results",
|
||||
"emoji_button.symbols": "Symbols",
|
||||
"emoji_button.travel": "Travel & Places",
|
||||
"empty_column.account_timeline": "No toots here!",
|
||||
"empty_column.account_unavailable": "Profile unavailable",
|
||||
"empty_column.blocks": "You haven't blocked any users yet.",
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
|
||||
"empty_column.domain_blocks": "There are no hidden domains yet.",
|
||||
"empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
|
||||
"empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
|
||||
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
|
||||
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
||||
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
|
||||
"empty_column.home.public_timeline": "the public timeline",
|
||||
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
|
||||
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
|
||||
"empty_column.mutes": "You haven't muted any users yet.",
|
||||
"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 servers to fill it up",
|
||||
"follow_request.authorize": "Authorize",
|
||||
"follow_request.reject": "Reject",
|
||||
"getting_started.developers": "Developers",
|
||||
"getting_started.directory": "Profile directory",
|
||||
"getting_started.documentation": "Documentation",
|
||||
"getting_started.heading": "Getting started",
|
||||
"getting_started.invite": "Invite people",
|
||||
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
|
||||
"getting_started.security": "Security",
|
||||
"getting_started.terms": "Terms of service",
|
||||
"hashtag.column_header.tag_mode.all": "and {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "or {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "without {additional}",
|
||||
"hashtag.column_settings.select.no_options_message": "No suggestions found",
|
||||
"hashtag.column_settings.select.placeholder": "Enter hashtags…",
|
||||
"hashtag.column_settings.tag_mode.all": "All of these",
|
||||
"hashtag.column_settings.tag_mode.any": "Any of these",
|
||||
"hashtag.column_settings.tag_mode.none": "None of these",
|
||||
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
|
||||
"home.column_settings.basic": "Basic",
|
||||
"home.column_settings.show_reblogs": "Show boosts",
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
||||
"introduction.federation.action": "Next",
|
||||
"introduction.federation.federated.headline": "Federated",
|
||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
||||
"introduction.federation.home.headline": "Home",
|
||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
||||
"introduction.federation.local.headline": "Local",
|
||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
||||
"introduction.interactions.action": "Finish toot-orial!",
|
||||
"introduction.interactions.favourite.headline": "Favourite",
|
||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
||||
"introduction.interactions.reblog.headline": "Boost",
|
||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
||||
"introduction.interactions.reply.headline": "Reply",
|
||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
||||
"introduction.welcome.action": "Let's go!",
|
||||
"introduction.welcome.headline": "First steps",
|
||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
||||
"keyboard_shortcuts.back": "to navigate back",
|
||||
"keyboard_shortcuts.blocked": "to open blocked users list",
|
||||
"keyboard_shortcuts.boost": "to boost",
|
||||
"keyboard_shortcuts.column": "to focus a status in one of the columns",
|
||||
"keyboard_shortcuts.compose": "to focus the compose textarea",
|
||||
"keyboard_shortcuts.description": "Description",
|
||||
"keyboard_shortcuts.direct": "to open direct messages column",
|
||||
"keyboard_shortcuts.down": "to move down in the list",
|
||||
"keyboard_shortcuts.enter": "to open status",
|
||||
"keyboard_shortcuts.favourite": "to favourite",
|
||||
"keyboard_shortcuts.favourites": "to open favourites list",
|
||||
"keyboard_shortcuts.federated": "to open federated timeline",
|
||||
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
|
||||
"keyboard_shortcuts.home": "to open home timeline",
|
||||
"keyboard_shortcuts.hotkey": "Hotkey",
|
||||
"keyboard_shortcuts.legend": "to display this legend",
|
||||
"keyboard_shortcuts.local": "to open local timeline",
|
||||
"keyboard_shortcuts.mention": "to mention author",
|
||||
"keyboard_shortcuts.muted": "to open muted users list",
|
||||
"keyboard_shortcuts.my_profile": "to open your profile",
|
||||
"keyboard_shortcuts.notifications": "to open notifications column",
|
||||
"keyboard_shortcuts.pinned": "to open pinned toots list",
|
||||
"keyboard_shortcuts.profile": "to open author's profile",
|
||||
"keyboard_shortcuts.reply": "to reply",
|
||||
"keyboard_shortcuts.requests": "to open follow requests list",
|
||||
"keyboard_shortcuts.search": "to focus search",
|
||||
"keyboard_shortcuts.start": "to open \"get started\" column",
|
||||
"keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
|
||||
"keyboard_shortcuts.toot": "to start a brand new toot",
|
||||
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
||||
"keyboard_shortcuts.up": "to move up in the list",
|
||||
"lightbox.close": "Close",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.edit.submit": "Change title",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"loading_indicator.label": "Loading...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
"missing_indicator.label": "Not found",
|
||||
"missing_indicator.sublabel": "This resource could not be found",
|
||||
"mute_modal.hide_notifications": "Hide notifications from this user?",
|
||||
"navigation_bar.apps": "Mobile apps",
|
||||
"navigation_bar.blocks": "Blocked users",
|
||||
"navigation_bar.community_timeline": "Local timeline",
|
||||
"navigation_bar.compose": "Compose new toot",
|
||||
"navigation_bar.direct": "Direct messages",
|
||||
"navigation_bar.discover": "Discover",
|
||||
"navigation_bar.domain_blocks": "Hidden domains",
|
||||
"navigation_bar.edit_profile": "Edit profile",
|
||||
"navigation_bar.favourites": "Favourites",
|
||||
"navigation_bar.filters": "Muted words",
|
||||
"navigation_bar.follow_requests": "Follow requests",
|
||||
"navigation_bar.info": "About this server",
|
||||
"navigation_bar.keyboard_shortcuts": "Hotkeys",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.logout": "Logout",
|
||||
"navigation_bar.mutes": "Muted users",
|
||||
"navigation_bar.personal": "Personal",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Preferences",
|
||||
"navigation_bar.public_timeline": "Federated timeline",
|
||||
"navigation_bar.security": "Security",
|
||||
"notification.favourite": "{name} favourited your status",
|
||||
"notification.follow": "{name} followed you",
|
||||
"notification.mention": "{name} mentioned you",
|
||||
"notification.poll": "A poll you have voted in has ended",
|
||||
"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.filter_bar.advanced": "Display all categories",
|
||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
||||
"notifications.column_settings.filter_bar.show": "Show",
|
||||
"notifications.column_settings.follow": "New followers:",
|
||||
"notifications.column_settings.mention": "Mentions:",
|
||||
"notifications.column_settings.poll": "Poll results:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.reblog": "Boosts:",
|
||||
"notifications.column_settings.show": "Show in column",
|
||||
"notifications.column_settings.sound": "Play sound",
|
||||
"notifications.filter.all": "All",
|
||||
"notifications.filter.boosts": "Boosts",
|
||||
"notifications.filter.favourites": "Favourites",
|
||||
"notifications.filter.follows": "Follows",
|
||||
"notifications.filter.mentions": "Mentions",
|
||||
"notifications.filter.polls": "Poll results",
|
||||
"notifications.group": "{count} notifications",
|
||||
"poll.closed": "Closed",
|
||||
"poll.refresh": "Refresh",
|
||||
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
|
||||
"poll.vote": "Vote",
|
||||
"poll_button.add_poll": "Add a poll",
|
||||
"poll_button.remove_poll": "Remove poll",
|
||||
"privacy.change": "Adjust status privacy",
|
||||
"privacy.direct.long": "Post to mentioned users only",
|
||||
"privacy.direct.short": "Direct",
|
||||
"privacy.private.long": "Post to followers only",
|
||||
"privacy.private.short": "Followers-only",
|
||||
"privacy.public.long": "Post to public timelines",
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.long": "Do not show in public timelines",
|
||||
"privacy.unlisted.short": "Unlisted",
|
||||
"regeneration_indicator.label": "Loading…",
|
||||
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "now",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Cancel",
|
||||
"report.forward": "Forward to {target}",
|
||||
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
|
||||
"report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:",
|
||||
"report.placeholder": "Additional comments",
|
||||
"report.submit": "Submit",
|
||||
"report.target": "Report {target}",
|
||||
"search.placeholder": "Search",
|
||||
"search_popout.search_format": "Advanced search format",
|
||||
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
|
||||
"search_popout.tips.hashtag": "hashtag",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
"search_popout.tips.user": "user",
|
||||
"search_results.accounts": "People",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.statuses": "Toots",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
||||
"status.admin_account": "Open moderation interface for @{name}",
|
||||
"status.admin_status": "Open this status in the moderation interface",
|
||||
"status.block": "Block @{name}",
|
||||
"status.cancel_reblog_private": "Unboost",
|
||||
"status.cannot_reblog": "This post cannot be boosted",
|
||||
"status.copy": "Copy link to status",
|
||||
"status.delete": "Delete",
|
||||
"status.detailed_status": "Detailed conversation view",
|
||||
"status.direct": "Direct message @{name}",
|
||||
"status.embed": "Embed",
|
||||
"status.favourite": "Favourite",
|
||||
"status.filtered": "Filtered",
|
||||
"status.load_more": "Load more",
|
||||
"status.media_hidden": "Media hidden",
|
||||
"status.mention": "Mention @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute": "Mute @{name}",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expand this status",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.read_more": "Read more",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblog_private": "Boost to original audience",
|
||||
"status.reblogged_by": "{name} boosted",
|
||||
"status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
|
||||
"status.redraft": "Delete & re-draft",
|
||||
"status.reply": "Reply",
|
||||
"status.replyAll": "Reply to thread",
|
||||
"status.report": "Report @{name}",
|
||||
"status.sensitive_toggle": "Click to view",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_less_all": "Show less for all",
|
||||
"status.show_more": "Show more",
|
||||
"status.show_more_all": "Show more for all",
|
||||
"status.show_thread": "Show thread",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"suggestions.dismiss": "Dismiss suggestion",
|
||||
"suggestions.header": "You might be interested in…",
|
||||
"tabs_bar.federated_timeline": "Federated",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
"tabs_bar.notifications": "Notifications",
|
||||
"tabs_bar.search": "Search",
|
||||
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
|
||||
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
|
||||
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
|
||||
"time_remaining.moments": "Moments remaining",
|
||||
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
|
||||
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.description": "Describe for the visually impaired",
|
||||
"upload_form.focus": "Crop",
|
||||
"upload_form.undo": "Delete",
|
||||
"upload_progress.label": "Uploading...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound"
|
||||
}
|
@ -5,7 +5,6 @@
|
||||
"account.block_domain": "Amaga-ho tot de {domain}",
|
||||
"account.blocked": "Bloquejat",
|
||||
"account.direct": "Missatge directe @{name}",
|
||||
"account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.",
|
||||
"account.domain_blocked": "Domini ocult",
|
||||
"account.edit_profile": "Editar el perfil",
|
||||
"account.endorse": "Recomanar en el teu perfil",
|
||||
@ -36,7 +35,6 @@
|
||||
"account.unfollow": "Deixa de seguir",
|
||||
"account.unmute": "Treure silenci de @{name}",
|
||||
"account.unmute_notifications": "Activar notificacions de @{name}",
|
||||
"account.view_full_profile": "Mostra el perfil complet",
|
||||
"alert.unexpected.message": "S'ha produït un error inesperat.",
|
||||
"alert.unexpected.title": "Vaja!",
|
||||
"boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop",
|
||||
@ -73,6 +71,10 @@
|
||||
"compose_form.lock_disclaimer": "El teu compte no està bloquejat {locked}. Tothom pot seguir-te i veure els teus missatges a seguidors.",
|
||||
"compose_form.lock_disclaimer.lock": "blocat",
|
||||
"compose_form.placeholder": "En què estàs pensant?",
|
||||
"compose_form.poll.add_option": "Afegeix una opció",
|
||||
"compose_form.poll.duration": "Durada de l'enquesta",
|
||||
"compose_form.poll.option_placeholder": "Opció {number}",
|
||||
"compose_form.poll.remove_option": "Elimina aquesta opció",
|
||||
"compose_form.publish": "Toot",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive.marked": "Mèdia marcat com a sensible",
|
||||
@ -81,6 +83,7 @@
|
||||
"compose_form.spoiler.unmarked": "Text no ocult",
|
||||
"compose_form.spoiler_placeholder": "Escriu l'avís aquí",
|
||||
"confirmation_modal.cancel": "Cancel·la",
|
||||
"confirmations.block.block_and_report": "Block & Report",
|
||||
"confirmations.block.confirm": "Bloca",
|
||||
"confirmations.block.message": "Estàs segur que vols blocar {name}?",
|
||||
"confirmations.delete.confirm": "Suprimeix",
|
||||
@ -105,7 +108,7 @@
|
||||
"emoji_button.food": "Menjar i beure",
|
||||
"emoji_button.label": "Insereix un emoji",
|
||||
"emoji_button.nature": "Natura",
|
||||
"emoji_button.not_found": "Emojos no!! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.not_found": "Emojis no!! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.objects": "Objectes",
|
||||
"emoji_button.people": "Gent",
|
||||
"emoji_button.recent": "Usats freqüentment",
|
||||
@ -114,6 +117,7 @@
|
||||
"emoji_button.symbols": "Símbols",
|
||||
"emoji_button.travel": "Viatges i Llocs",
|
||||
"empty_column.account_timeline": "No hi ha toots aquí!",
|
||||
"empty_column.account_unavailable": "Profile unavailable",
|
||||
"empty_column.blocks": "Encara no has bloquejat cap usuari.",
|
||||
"empty_column.community": "La línia de temps local és buida. Escriu alguna cosa públicament per fer rodar la pilota!",
|
||||
"empty_column.direct": "Encara no tens missatges directes. Quan enviïs o rebis un, es mostrarà aquí.",
|
||||
@ -128,7 +132,7 @@
|
||||
"empty_column.lists": "Encara no tens cap llista. Quan en facis una, apareixerà aquí.",
|
||||
"empty_column.mutes": "Encara no has silenciat cap usuari.",
|
||||
"empty_column.notifications": "Encara no tens notificacions. Interactua amb altres per iniciar la conversa.",
|
||||
"empty_column.public": "No hi ha res aquí! Escriu alguna cosa públicament o segueix manualment usuaris d'altres instàncies per omplir-ho",
|
||||
"empty_column.public": "No hi ha res aquí! Escriu públicament alguna cosa o manualment segueix usuaris d'altres servidors per omplir-ho",
|
||||
"federation.change": "Adjust status federation",
|
||||
"federation.federated.long": "Allow toot to reach other instances",
|
||||
"federation.federated.short": "Federated",
|
||||
@ -147,13 +151,18 @@
|
||||
"hashtag.column_header.tag_mode.all": "i {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "o {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "sense {additional}",
|
||||
"hashtag.column_settings.select.no_options_message": "No s'ha trobat cap suggeriment",
|
||||
"hashtag.column_settings.select.placeholder": "Introdueix etiquetes…",
|
||||
"hashtag.column_settings.tag_mode.all": "Tots aquests",
|
||||
"hashtag.column_settings.tag_mode.any": "Qualsevol d’aquests",
|
||||
"hashtag.column_settings.tag_mode.none": "Cap d’aquests",
|
||||
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
|
||||
"hashtag.column_settings.tag_toggle": "Inclou etiquetes addicionals per a aquesta columna",
|
||||
"home.column_settings.basic": "Bàsic",
|
||||
"home.column_settings.show_reblogs": "Mostrar impulsos",
|
||||
"home.column_settings.show_replies": "Mostrar respostes",
|
||||
"intervals.full.days": "{number, plural, one {# dia} other {# dies}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hora} other {# hores}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minut} other {# minuts}}",
|
||||
"introduction.federation.action": "Següent",
|
||||
"introduction.federation.federated.headline": "Federada",
|
||||
"introduction.federation.federated.text": "Les publicacions públiques d'altres servidors del fedivers apareixeran a la línia de temps federada.",
|
||||
@ -176,14 +185,14 @@
|
||||
"keyboard_shortcuts.boost": "impulsar",
|
||||
"keyboard_shortcuts.column": "per centrar un estat en una de les columnes",
|
||||
"keyboard_shortcuts.compose": "per centrar l'area de composició de text",
|
||||
"keyboard_shortcuts.description": "Description",
|
||||
"keyboard_shortcuts.description": "Descripció",
|
||||
"keyboard_shortcuts.direct": "per obrir la columna de missatges directes",
|
||||
"keyboard_shortcuts.down": "per baixar en la llista",
|
||||
"keyboard_shortcuts.enter": "ampliar estat",
|
||||
"keyboard_shortcuts.favourite": "afavorir",
|
||||
"keyboard_shortcuts.favourites": "per obrir la llista de favorits",
|
||||
"keyboard_shortcuts.federated": "per obrir la línia de temps federada",
|
||||
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
|
||||
"keyboard_shortcuts.heading": "Dreçeres de teclat",
|
||||
"keyboard_shortcuts.home": "per obrir la línia de temps Inici",
|
||||
"keyboard_shortcuts.hotkey": "Tecla d'accés directe",
|
||||
"keyboard_shortcuts.legend": "per a mostrar aquesta llegenda",
|
||||
@ -209,6 +218,7 @@
|
||||
"lists.account.remove": "Treure de la llista",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Editar llista",
|
||||
"lists.edit.submit": "Canvi de títol",
|
||||
"lists.new.create": "Afegir llista",
|
||||
"lists.new.title_placeholder": "Nova llista",
|
||||
"lists.search": "Cercar entre les persones que segueixes",
|
||||
@ -229,7 +239,7 @@
|
||||
"navigation_bar.favourites": "Favorits",
|
||||
"navigation_bar.filters": "Paraules silenciades",
|
||||
"navigation_bar.follow_requests": "Sol·licituds de seguiment",
|
||||
"navigation_bar.info": "Informació addicional",
|
||||
"navigation_bar.info": "Sobre aquest servidor",
|
||||
"navigation_bar.keyboard_shortcuts": "Dreceres de teclat",
|
||||
"navigation_bar.lists": "Llistes",
|
||||
"navigation_bar.logout": "Tancar sessió",
|
||||
@ -242,7 +252,8 @@
|
||||
"notification.favourite": "{name} ha afavorit el teu estat",
|
||||
"notification.follow": "{name} et segueix",
|
||||
"notification.mention": "{name} t'ha esmentat",
|
||||
"notification.reblog": "{name} ha retootejat el teu estat",
|
||||
"notification.poll": "Ha finalitzat una enquesta en la que has votat",
|
||||
"notification.reblog": "{name} ha impulsat el teu estat",
|
||||
"notifications.clear": "Netejar notificacions",
|
||||
"notifications.clear_confirmation": "Estàs segur que vols esborrar permanenment totes les teves notificacions?",
|
||||
"notifications.column_settings.alert": "Notificacions d'escriptori",
|
||||
@ -252,7 +263,8 @@
|
||||
"notifications.column_settings.filter_bar.show": "Mostra",
|
||||
"notifications.column_settings.follow": "Nous seguidors:",
|
||||
"notifications.column_settings.mention": "Mencions:",
|
||||
"notifications.column_settings.push": "Push notificacions",
|
||||
"notifications.column_settings.poll": "Resultats de l’enquesta:",
|
||||
"notifications.column_settings.push": "Notificacions push",
|
||||
"notifications.column_settings.reblog": "Impulsos:",
|
||||
"notifications.column_settings.show": "Mostrar en la columna",
|
||||
"notifications.column_settings.sound": "Reproduïr so",
|
||||
@ -261,7 +273,14 @@
|
||||
"notifications.filter.favourites": "Favorits",
|
||||
"notifications.filter.follows": "Seguiments",
|
||||
"notifications.filter.mentions": "Mencions",
|
||||
"notifications.filter.polls": "Resultats de l'enquesta",
|
||||
"notifications.group": "{count} notificacions",
|
||||
"poll.closed": "Finalitzada",
|
||||
"poll.refresh": "Actualitza",
|
||||
"poll.total_votes": "{count, plural, one {# vot} other {# vots}}",
|
||||
"poll.vote": "Vota",
|
||||
"poll_button.add_poll": "Afegeix una enquesta",
|
||||
"poll_button.remove_poll": "Elimina l'enquesta",
|
||||
"privacy.change": "Ajusta l'estat de privacitat",
|
||||
"privacy.direct.long": "Publicar només per als usuaris esmentats",
|
||||
"privacy.direct.short": "Directe",
|
||||
@ -281,7 +300,7 @@
|
||||
"reply_indicator.cancel": "Cancel·lar",
|
||||
"report.forward": "Reenvia a {target}",
|
||||
"report.forward_hint": "Aquest compte és d'un altre servidor. Enviar-hi també una copia anònima del informe?",
|
||||
"report.hint": "El informe s'enviarà als moderadors de la teva instància. Pots explicar perquè vols informar d'aquest compte aquí:",
|
||||
"report.hint": "El informe s'enviarà als moderadors del teu servidor. Pots explicar perquè vols informar d'aquest compte aquí:",
|
||||
"report.placeholder": "Comentaris addicionals",
|
||||
"report.submit": "Enviar",
|
||||
"report.target": "Informes",
|
||||
@ -296,12 +315,12 @@
|
||||
"search_results.hashtags": "Etiquetes",
|
||||
"search_results.statuses": "Toots",
|
||||
"search_results.total": "{count, number} {count, plural, un {result} altres {results}}",
|
||||
"standalone.public_title": "Una mirada a l'interior ...",
|
||||
"status.admin_account": "Obre l'interfície de moderació per a @{name}",
|
||||
"status.admin_status": "Obre aquest estat a la interfície de moderació",
|
||||
"status.block": "Block @{name}",
|
||||
"status.block": "Bloqueja @{name}",
|
||||
"status.cancel_reblog_private": "Desfer l'impuls",
|
||||
"status.cannot_reblog": "Aquesta publicació no pot ser retootejada",
|
||||
"status.cannot_reblog": "Aquesta publicació no pot ser impulsada",
|
||||
"status.copy": "Copia l'enllaç a l'estat",
|
||||
"status.delete": "Esborrar",
|
||||
"status.detailed_status": "Visualització detallada de la conversa",
|
||||
"status.direct": "Missatge directe @{name}",
|
||||
@ -321,7 +340,7 @@
|
||||
"status.read_more": "Llegir més",
|
||||
"status.reblog": "Impuls",
|
||||
"status.reblog_private": "Impulsar a l'audiència original",
|
||||
"status.reblogged_by": "{name} ha retootejat",
|
||||
"status.reblogged_by": "{name} ha impulsat",
|
||||
"status.reblogs.empty": "Encara ningú no ha impulsat aquest toot. Quan algú ho faci, apareixeran aquí.",
|
||||
"status.redraft": "Esborrar i reescriure",
|
||||
"status.reply": "Respondre",
|
||||
@ -344,10 +363,17 @@
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
"tabs_bar.notifications": "Notificacions",
|
||||
"tabs_bar.search": "Cerca",
|
||||
"time_remaining.days": "{number, plural, one {# dia} other {# dies}} restants",
|
||||
"time_remaining.hours": "{number, plural, one {# hora} other {# hores}} restants",
|
||||
"time_remaining.minutes": "{number, plural, one {# minut} other {# minuts}} restants",
|
||||
"time_remaining.moments": "Moments restants",
|
||||
"time_remaining.seconds": "{number, plural, one {# segon} other {# segons}} restants",
|
||||
"trends.count_by_accounts": "{count} {rawCount, plural, una {person} altres {people}} parlant",
|
||||
"ui.beforeunload": "El vostre esborrany es perdrà si sortiu de Mastodon.",
|
||||
"upload_area.title": "Arrossega i deixa anar per carregar",
|
||||
"upload_button.label": "Afegir multimèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||
"upload_error.limit": "S'ha superat el límit de càrrega d'arxius.",
|
||||
"upload_error.poll": "No es permet l'enviament de fitxers amb les enquestes.",
|
||||
"upload_form.description": "Descriure els problemes visuals",
|
||||
"upload_form.focus": "Modificar la previsualització",
|
||||
"upload_form.undo": "Esborra",
|
||||
|
@ -5,7 +5,6 @@
|
||||
"account.block_domain": "Piattà tuttu da {domain}",
|
||||
"account.blocked": "Bluccatu",
|
||||
"account.direct": "Missaghju direttu @{name}",
|
||||
"account.disclaimer_full": "Ghjè pussibule chì l’infurmazione quì sottu ùn rifletta micca u prufile sanu di l’utilizatore.",
|
||||
"account.domain_blocked": "Duminiu piattatu",
|
||||
"account.edit_profile": "Mudificà u prufile",
|
||||
"account.endorse": "Fà figurà nant'à u prufilu",
|
||||
@ -36,7 +35,6 @@
|
||||
"account.unfollow": "Ùn siguità più",
|
||||
"account.unmute": "Ùn piattà più @{name}",
|
||||
"account.unmute_notifications": "Ùn piattà più nutificazione da @{name}",
|
||||
"account.view_full_profile": "Vede tuttu u prufile",
|
||||
"alert.unexpected.message": "Un prublemu inaspettatu hè accadutu.",
|
||||
"alert.unexpected.title": "Uups!",
|
||||
"boost_modal.combo": "Pudete appughjà nant'à {combo} per saltà quessa a prussima volta",
|
||||
@ -73,6 +71,10 @@
|
||||
"compose_form.lock_disclaimer": "U vostru contu ùn hè micca {locked}. Tuttu u mondu pò seguitavi è vede i vostri statuti privati.",
|
||||
"compose_form.lock_disclaimer.lock": "privatu",
|
||||
"compose_form.placeholder": "À chè pensate?",
|
||||
"compose_form.poll.add_option": "Aghjustà una scelta",
|
||||
"compose_form.poll.duration": "Durata di u scandagliu",
|
||||
"compose_form.poll.option_placeholder": "Scelta {number}",
|
||||
"compose_form.poll.remove_option": "Toglie sta scelta",
|
||||
"compose_form.publish": "Toot",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive.marked": "Media indicatu cum'è sensibile",
|
||||
@ -81,6 +83,7 @@
|
||||
"compose_form.spoiler.unmarked": "Testu micca piattatu",
|
||||
"compose_form.spoiler_placeholder": "Scrive u vostr'avertimentu quì",
|
||||
"confirmation_modal.cancel": "Annullà",
|
||||
"confirmations.block.block_and_report": "Bluccà è signalà",
|
||||
"confirmations.block.confirm": "Bluccà",
|
||||
"confirmations.block.message": "Site sicuru·a che vulete bluccà @{name}?",
|
||||
"confirmations.delete.confirm": "Toglie",
|
||||
@ -114,6 +117,7 @@
|
||||
"emoji_button.symbols": "Simbuli",
|
||||
"emoji_button.travel": "Lochi è Viaghju",
|
||||
"empty_column.account_timeline": "Nisun statutu quì!",
|
||||
"empty_column.account_unavailable": "Prufile micca dispunibule",
|
||||
"empty_column.blocks": "Per avà ùn avete bluccatu manc'un utilizatore.",
|
||||
"empty_column.community": "Ùn c'hè nunda indè a linea lucale. Scrivete puru qualcosa!",
|
||||
"empty_column.direct": "Ùn avete ancu nisun missaghju direttu. S'è voi mandate o ricevete unu, u vidarete quì.",
|
||||
@ -128,7 +132,7 @@
|
||||
"empty_column.lists": "Ùn avete manc'una lista. Quandu farete una, sarà mustrata quì.",
|
||||
"empty_column.mutes": "Per avà ùn avete manc'un utilizatore piattatu.",
|
||||
"empty_column.notifications": "Ùn avete ancu nisuna nutificazione. Interact with others to start the conversation.",
|
||||
"empty_column.public": "Ùn c'hè nunda quì! Scrivete qualcosa in pubblicu o seguitate utilizatori d'altre istanze per empie a linea pubblica",
|
||||
"empty_column.public": "Ùn c'hè nunda quì! Scrivete qualcosa in pubblicu o seguitate utilizatori d'altri servori per empie a linea pubblica",
|
||||
"federation.change": "Adjust status federation",
|
||||
"federation.federated.long": "Allow toot to reach other instances",
|
||||
"federation.federated.short": "Federated",
|
||||
@ -138,7 +142,7 @@
|
||||
"follow_request.reject": "Righjittà",
|
||||
"getting_started.developers": "Sviluppatori",
|
||||
"getting_started.directory": "Annuariu di i prufili",
|
||||
"getting_started.documentation": "Documentation",
|
||||
"getting_started.documentation": "Ducumentazione",
|
||||
"getting_started.heading": "Per principià",
|
||||
"getting_started.invite": "Invità ghjente",
|
||||
"getting_started.open_source_notice": "Mastodon ghjè un lugiziale liberu. Pudete cuntribuisce à u codice o a traduzione, o palisà un bug, nant'à GitHub: {github}.",
|
||||
@ -147,6 +151,8 @@
|
||||
"hashtag.column_header.tag_mode.all": "è {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "o {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "senza {additional}",
|
||||
"hashtag.column_settings.select.no_options_message": "Nisuna sugestione trova",
|
||||
"hashtag.column_settings.select.placeholder": "Entrà l'hashtag…",
|
||||
"hashtag.column_settings.tag_mode.all": "Tutti quessi",
|
||||
"hashtag.column_settings.tag_mode.any": "Unu di quessi",
|
||||
"hashtag.column_settings.tag_mode.none": "Nisunu di quessi",
|
||||
@ -154,6 +160,9 @@
|
||||
"home.column_settings.basic": "Bàsichi",
|
||||
"home.column_settings.show_reblogs": "Vede e spartere",
|
||||
"home.column_settings.show_replies": "Vede e risposte",
|
||||
"intervals.full.days": "{number, plural, one {# ghjornu} other {# ghjorni}}",
|
||||
"intervals.full.hours": "{number, plural, one {# ora} other {# ore}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minuta} other {# minute}}",
|
||||
"introduction.federation.action": "Cuntinuà",
|
||||
"introduction.federation.federated.headline": "Federata",
|
||||
"introduction.federation.federated.text": "I statuti pubblichi da l'altri servori di u fediverse saranu mustrati nant'à a linea pubblica federata.",
|
||||
@ -209,6 +218,7 @@
|
||||
"lists.account.remove": "Toglie di a lista",
|
||||
"lists.delete": "Supprime a lista",
|
||||
"lists.edit": "Mudificà a lista",
|
||||
"lists.edit.submit": "Cambià u titulu",
|
||||
"lists.new.create": "Aghjustà una lista",
|
||||
"lists.new.title_placeholder": "Titulu di a lista",
|
||||
"lists.search": "Circà indè i vostr'abbunamenti",
|
||||
@ -229,12 +239,12 @@
|
||||
"navigation_bar.favourites": "Favuriti",
|
||||
"navigation_bar.filters": "Parolle silenzate",
|
||||
"navigation_bar.follow_requests": "Dumande d'abbunamentu",
|
||||
"navigation_bar.info": "À prupositu di l'istanza",
|
||||
"navigation_bar.info": "À prupositu di u servore",
|
||||
"navigation_bar.keyboard_shortcuts": "Accorte cù a tastera",
|
||||
"navigation_bar.lists": "Liste",
|
||||
"navigation_bar.logout": "Scunnettassi",
|
||||
"navigation_bar.mutes": "Utilizatori piattati",
|
||||
"navigation_bar.personal": "Personal",
|
||||
"navigation_bar.personal": "Persunale",
|
||||
"navigation_bar.pins": "Statuti puntarulati",
|
||||
"navigation_bar.preferences": "Preferenze",
|
||||
"navigation_bar.public_timeline": "Linea pubblica glubale",
|
||||
@ -242,6 +252,7 @@
|
||||
"notification.favourite": "{name} hà aghjuntu u vostru statutu à i so favuriti",
|
||||
"notification.follow": "{name} v'hà seguitatu",
|
||||
"notification.mention": "{name} v'hà mintuvatu",
|
||||
"notification.poll": "Un scandagliu induve avete vutatu hè finitu",
|
||||
"notification.reblog": "{name} hà spartutu u vostru statutu",
|
||||
"notifications.clear": "Purgà e nutificazione",
|
||||
"notifications.clear_confirmation": "Site sicuru·a che vulete toglie tutte ste nutificazione?",
|
||||
@ -252,6 +263,7 @@
|
||||
"notifications.column_settings.filter_bar.show": "Mustrà",
|
||||
"notifications.column_settings.follow": "Abbunati novi:",
|
||||
"notifications.column_settings.mention": "Minzione:",
|
||||
"notifications.column_settings.poll": "Risultati:",
|
||||
"notifications.column_settings.push": "Nutificazione Push",
|
||||
"notifications.column_settings.reblog": "Spartere:",
|
||||
"notifications.column_settings.show": "Mustrà indè a colonna",
|
||||
@ -261,7 +273,14 @@
|
||||
"notifications.filter.favourites": "Favuriti",
|
||||
"notifications.filter.follows": "Abbunamenti",
|
||||
"notifications.filter.mentions": "Minzione",
|
||||
"notifications.filter.polls": "Risultati di u scandagliu",
|
||||
"notifications.group": "{count} nutificazione",
|
||||
"poll.closed": "Chjosu",
|
||||
"poll.refresh": "Attualizà",
|
||||
"poll.total_votes": "{count, plural, one {# votu} other {# voti}}",
|
||||
"poll.vote": "Vutà",
|
||||
"poll_button.add_poll": "Aghjustà un scandagliu",
|
||||
"poll_button.remove_poll": "Toglie u scandagliu",
|
||||
"privacy.change": "Mudificà a cunfidenzialità di u statutu",
|
||||
"privacy.direct.long": "Mandà solu à quelli chì so mintuvati",
|
||||
"privacy.direct.short": "Direttu",
|
||||
@ -281,7 +300,7 @@
|
||||
"reply_indicator.cancel": "Annullà",
|
||||
"report.forward": "Trasferisce à {target}",
|
||||
"report.forward_hint": "U contu hè nant'à un'altru servore. Vulete ancu mandà una copia anonima di u signalamentu quallà?",
|
||||
"report.hint": "U signalamentu sarà mandatu à i muderatori di l'istanza. Pudete spiegà perchè avete palisatu stu contu quì sottu:",
|
||||
"report.hint": "U signalamentu sarà mandatu à i muderatori di u servore. Pudete spiegà perchè avete palisatu stu contu quì sottu:",
|
||||
"report.placeholder": "Altri cummenti",
|
||||
"report.submit": "Mandà",
|
||||
"report.target": "Signalamentu",
|
||||
@ -296,12 +315,12 @@
|
||||
"search_results.hashtags": "Hashtag",
|
||||
"search_results.statuses": "Statuti",
|
||||
"search_results.total": "{count, number} {count, plural, one {risultatu} other {risultati}}",
|
||||
"standalone.public_title": "Una vista à l'internu...",
|
||||
"status.admin_account": "Apre l'interfaccia di muderazione per @{name}",
|
||||
"status.admin_status": "Apre stu statutu in l'interfaccia di muderazione",
|
||||
"status.block": "Bluccà @{name}",
|
||||
"status.cancel_reblog_private": "Ùn sparte più",
|
||||
"status.cannot_reblog": "Stu statutu ùn pò micca esse spartutu",
|
||||
"status.copy": "Cupià ligame indè u statutu",
|
||||
"status.delete": "Toglie",
|
||||
"status.detailed_status": "Vista in ditagliu di a cunversazione",
|
||||
"status.direct": "Mandà un missaghju @{name}",
|
||||
@ -344,10 +363,17 @@
|
||||
"tabs_bar.local_timeline": "Lucale",
|
||||
"tabs_bar.notifications": "Nutificazione",
|
||||
"tabs_bar.search": "Cercà",
|
||||
"time_remaining.days": "{number, plural, one {# ghjornu ferma} other {# ghjorni fermanu}}",
|
||||
"time_remaining.hours": "{number, plural, one {# ora ferma} other {# ore fermanu}}",
|
||||
"time_remaining.minutes": "{number, plural, one {# minuta ferma} other {# minute fermanu}} left",
|
||||
"time_remaining.moments": "Ci fermanu qualchi mumentu",
|
||||
"time_remaining.seconds": "{number, plural, one {# siconda ferma} other {# siconde fermanu}}",
|
||||
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} parlanu",
|
||||
"ui.beforeunload": "A bruttacopia sarà persa s'ellu hè chjosu Mastodon.",
|
||||
"upload_area.title": "Drag & drop per caricà un fugliale",
|
||||
"upload_button.label": "Aghjunghje un media (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||
"upload_error.limit": "Limita di caricamentu di fugliali trapassata.",
|
||||
"upload_error.poll": "Ùn si pò micca caricà fugliali cù i scandagli.",
|
||||
"upload_form.description": "Discrive per i malvistosi",
|
||||
"upload_form.focus": "Cambià a vista",
|
||||
"upload_form.undo": "Sguassà",
|
||||
|
@ -4,13 +4,12 @@
|
||||
"account.block": "Zablokovat uživatele @{name}",
|
||||
"account.block_domain": "Skrýt vše z {domain}",
|
||||
"account.blocked": "Blokován/a",
|
||||
"account.direct": "Přímá zpráva pro uživatele @{name}",
|
||||
"account.disclaimer_full": "Níže uvedené informace nemusejí zcela odrážet profil uživatele.",
|
||||
"account.direct": "Poslat přímou zprávu uživateli @{name}",
|
||||
"account.domain_blocked": "Doména skryta",
|
||||
"account.edit_profile": "Upravit profil",
|
||||
"account.endorse": "Představit na profilu",
|
||||
"account.follow": "Sledovat",
|
||||
"account.followers": "Sledovatelé",
|
||||
"account.followers": "Sledující",
|
||||
"account.followers.empty": "Tohoto uživatele ještě nikdo nesleduje.",
|
||||
"account.follows": "Sledovaní",
|
||||
"account.follows.empty": "Tento uživatel ještě nikoho nesleduje.",
|
||||
@ -21,22 +20,21 @@
|
||||
"account.media": "Média",
|
||||
"account.mention": "Zmínit uživatele @{name}",
|
||||
"account.moved_to": "{name} se přesunul/a na:",
|
||||
"account.mute": "Ignorovat uživatele @{name}",
|
||||
"account.mute": "Skrýt uživatele @{name}",
|
||||
"account.mute_notifications": "Skrýt oznámení od uživatele @{name}",
|
||||
"account.muted": "Ztišen/a",
|
||||
"account.muted": "Skryt/a",
|
||||
"account.posts": "Tooty",
|
||||
"account.posts_with_replies": "Tooty a odpovědi",
|
||||
"account.report": "Nahlásit uživatele @{name}",
|
||||
"account.requested": "Požadavek čeká na schválení. Kliknutím zrušíte požadavek o sledování",
|
||||
"account.requested": "Čekám na schválení. Kliknutím zrušíte požadavek o sledování",
|
||||
"account.share": "Sdílet profil uživatele @{name}",
|
||||
"account.show_reblogs": "Zobrazit boosty od uživatele @{name}",
|
||||
"account.unblock": "Odblokovat uživatele @{name}",
|
||||
"account.unblock_domain": "Odkrýt doménu {domain}",
|
||||
"account.unendorse": "Nepředstavit na profilu",
|
||||
"account.unfollow": "Přestat sledovat",
|
||||
"account.unmute": "Přestat ignorovat uživatele @{name}",
|
||||
"account.unmute_notifications": "Odtišit oznámení od uživatele @{name}",
|
||||
"account.view_full_profile": "Zobrazit celý profil",
|
||||
"account.unmute": "Odkrýt uživatele @{name}",
|
||||
"account.unmute_notifications": "Odkrýt oznámení od uživatele @{name}",
|
||||
"alert.unexpected.message": "Objevila se neočekávaná chyba.",
|
||||
"alert.unexpected.title": "Jejda!",
|
||||
"boost_modal.combo": "Příště můžete pro přeskočení kliknout na {combo}",
|
||||
@ -54,14 +52,14 @@
|
||||
"column.follow_requests": "Požadavky o sledování",
|
||||
"column.home": "Domů",
|
||||
"column.lists": "Seznamy",
|
||||
"column.mutes": "Ignorovaní uživatelé",
|
||||
"column.mutes": "Skrytí uživatelé",
|
||||
"column.notifications": "Oznámení",
|
||||
"column.pins": "Připnuté tooty",
|
||||
"column.public": "Federovaná časová osa",
|
||||
"column_back_button.label": "Zpět",
|
||||
"column_header.hide_settings": "Skrýt nastavení",
|
||||
"column_header.moveLeft_settings": "Přesunout sloupec doleva",
|
||||
"column_header.moveRight_settings": "Přesunout sloupec doprava",
|
||||
"column_header.moveLeft_settings": "Posunout sloupec doleva",
|
||||
"column_header.moveRight_settings": "Posunout sloupec doprava",
|
||||
"column_header.pin": "Připnout",
|
||||
"column_header.show_settings": "Zobrazit nastavení",
|
||||
"column_header.unpin": "Odepnout",
|
||||
@ -70,34 +68,39 @@
|
||||
"compose_form.direct_message_warning": "Tento toot bude odeslán pouze zmíněným uživatelům.",
|
||||
"compose_form.direct_message_warning_learn_more": "Zjistit více",
|
||||
"compose_form.hashtag_warning": "Tento toot nebude zobrazen pod žádným hashtagem, neboť je neuvedený. Pouze veřejné tooty mohou být vyhledány podle hashtagu.",
|
||||
"compose_form.lock_disclaimer": "Váš účet není {locked}. Kdokoliv vás může sledovat a vidět vaše příspěvky pouze pro sledovatele.",
|
||||
"compose_form.lock_disclaimer.lock": "zamčený",
|
||||
"compose_form.placeholder": "Co máte na mysli?",
|
||||
"compose_form.lock_disclaimer": "Váš účet není {locked}. Kdokoliv vás může sledovat a vidět vaše příspěvky pouze pro sledující.",
|
||||
"compose_form.lock_disclaimer.lock": "uzamčen",
|
||||
"compose_form.placeholder": "Co se vám honí hlavou?",
|
||||
"compose_form.poll.add_option": "Přidat volbu",
|
||||
"compose_form.poll.duration": "Délka ankety",
|
||||
"compose_form.poll.option_placeholder": "Volba {number}",
|
||||
"compose_form.poll.remove_option": "Odstranit tuto volbu",
|
||||
"compose_form.publish": "Tootnout",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive.marked": "Mediální obsah je označen jako citlivý",
|
||||
"compose_form.sensitive.unmarked": "Mediální obsah není označen jako citlivý",
|
||||
"compose_form.sensitive.marked": "Média jsou označena jako citlivá",
|
||||
"compose_form.sensitive.unmarked": "Média nejsou označena jako citlivá",
|
||||
"compose_form.spoiler.marked": "Text je skrytý za varováním",
|
||||
"compose_form.spoiler.unmarked": "Text není skrytý",
|
||||
"compose_form.spoiler_placeholder": "Sem napište vaše varování",
|
||||
"confirmation_modal.cancel": "Zrušit",
|
||||
"confirmations.block.block_and_report": "Blokovat a nahlásit",
|
||||
"confirmations.block.confirm": "Blokovat",
|
||||
"confirmations.block.message": "Jste si jistý/á, že chcete zablokovat uživatele {name}?",
|
||||
"confirmations.delete.confirm": "Smazat",
|
||||
"confirmations.delete.message": "Jste si jistý/á, že chcete smazat tento příspěvek?",
|
||||
"confirmations.delete.message": "Jste si jistý/á, že chcete smazat tento toot?",
|
||||
"confirmations.delete_list.confirm": "Smazat",
|
||||
"confirmations.delete_list.message": "Jste si jistý/á, že chcete tento seznam navždy vymazat?",
|
||||
"confirmations.delete_list.message": "Jste si jistý/á, že chcete tento seznam navždy smazat?",
|
||||
"confirmations.domain_block.confirm": "Skrýt celou doménu",
|
||||
"confirmations.domain_block.message": "Jste si opravdu, opravdu jistý/á, že chcete blokovat celou {domain}? Ve většině případů stačí zablokovat nebo ignorovat pár konkrétních uživatelů, což se doporučuje. Z této domény neuvidíte obsah v žádné veřejné časové ose ani v oznámeních. Vaši sledovatelé z této domény budou odstraněni.",
|
||||
"confirmations.mute.confirm": "Ignorovat",
|
||||
"confirmations.mute.message": "Jste si jistý/á, že chcete ignorovat uživatele {name}?",
|
||||
"confirmations.redraft.confirm": "Vymazat a přepsat",
|
||||
"confirmations.redraft.message": "Jste si jistý/á, že chcete vymazat a přepsat tento příspěvek? Oblíbení a boosty budou ztraceny a odpovědi na původní příspěvek budou opuštěny.",
|
||||
"confirmations.domain_block.message": "Jste si opravdu, opravdu jistý/á, že chcete blokovat celou doménu {domain}? Ve většině případů stačí zablokovat nebo skrýt pár konkrétních uživatelů, což se doporučuje. Z této domény neuvidíte obsah v žádné veřejné časové ose ani v oznámeních. Vaši sledující z této domény budou odstraněni.",
|
||||
"confirmations.mute.confirm": "Skrýt",
|
||||
"confirmations.mute.message": "Jste si jistý/á, že chcete skrýt uživatele {name}?",
|
||||
"confirmations.redraft.confirm": "Smazat a přepsat",
|
||||
"confirmations.redraft.message": "Jste si jistý/á, že chcete smazat a přepsat tento toot? Oblíbení a boosty budou ztraceny a odpovědi na původní příspěvek budou opuštěny.",
|
||||
"confirmations.reply.confirm": "Odpovědět",
|
||||
"confirmations.reply.message": "Odpovězením nyní přepíšete zprávu, kterou aktuálně píšete. Jste si jistý/á, že chcete pokračovat?",
|
||||
"confirmations.unfollow.confirm": "Přestat sledovat",
|
||||
"confirmations.unfollow.message": "jste si jistý/á, že chcete přestat sledovat uživatele {name}?",
|
||||
"embed.instructions": "Pro přidání příspěvku na vaši webovou stránku zkopírujte níže uvedený kód.",
|
||||
"embed.instructions": "Pro přidání tootu na vaši webovou stránku zkopírujte níže uvedený kód.",
|
||||
"embed.preview": "Takhle to bude vypadat:",
|
||||
"emoji_button.activity": "Aktivita",
|
||||
"emoji_button.custom": "Vlastní",
|
||||
@ -105,15 +108,16 @@
|
||||
"emoji_button.food": "Jídla a nápoje",
|
||||
"emoji_button.label": "Vložit emoji",
|
||||
"emoji_button.nature": "Příroda",
|
||||
"emoji_button.not_found": "Žádné emoji!! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.not_found": "Žádná emoji!! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.objects": "Předměty",
|
||||
"emoji_button.people": "Lidé",
|
||||
"emoji_button.recent": "Často používané",
|
||||
"emoji_button.search": "Hledat...",
|
||||
"emoji_button.recent": "Často používaná",
|
||||
"emoji_button.search": "Hledat…",
|
||||
"emoji_button.search_results": "Výsledky hledání",
|
||||
"emoji_button.symbols": "Symboly",
|
||||
"emoji_button.travel": "Cestování a místa",
|
||||
"empty_column.account_timeline": "Tady nejsou žádné tooty!",
|
||||
"empty_column.account_unavailable": "Profil nedostupný",
|
||||
"empty_column.blocks": "Ještě jste nezablokoval/a žádného uživatele.",
|
||||
"empty_column.community": "Místní časová osa je prázdná. Napište něco veřejně a rozhýbejte to tu!",
|
||||
"empty_column.direct": "Ještě nemáte žádné přímé zprávy. Pokud nějakou pošlete nebo dostanete, zobrazí se zde.",
|
||||
@ -124,11 +128,11 @@
|
||||
"empty_column.hashtag": "Pod tímto hashtagem ještě nic není.",
|
||||
"empty_column.home": "Vaše domovská časová osa je prázdná! Začněte navštívením {public} nebo použijte hledání a seznamte se s dalšími uživateli.",
|
||||
"empty_column.home.public_timeline": "veřejné časové osy",
|
||||
"empty_column.list": "V tomto seznamu ještě nic není. Pokud budou členové tohoto seznamu psát nové příspěvky, objeví se zde.",
|
||||
"empty_column.list": "V tomto seznamu ještě nic není. Pokud budou členové tohoto seznamu psát nové tooty, objeví se zde.",
|
||||
"empty_column.lists": "Ještě nemáte žádný seznam. Pokud nějaký vytvoříte, zobrazí se zde.",
|
||||
"empty_column.mutes": "Ještě neignorujete žádné uživatele.",
|
||||
"empty_column.mutes": "Ještě jste neskryl/a žádné uživatele.",
|
||||
"empty_column.notifications": "Ještě nemáte žádná oznámení. Začněte konverzaci komunikováním s ostatními.",
|
||||
"empty_column.public": "Tady nic není! Napište něco veřejně, nebo manuálně začněte sledovat uživatele z jiných instancí, aby tu něco přibylo",
|
||||
"empty_column.public": "Tady nic není! Napište něco veřejně, nebo začněte ručně sledovat uživatele z jiných serverů, aby tu něco přibylo",
|
||||
"federation.change": "Adjust status federation",
|
||||
"federation.federated.long": "Allow toot to reach other instances",
|
||||
"federation.federated.short": "Federated",
|
||||
@ -147,6 +151,8 @@
|
||||
"hashtag.column_header.tag_mode.all": "a {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "nebo {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "bez {additional}",
|
||||
"hashtag.column_settings.select.no_options_message": "Žádné návrhy nenalezeny",
|
||||
"hashtag.column_settings.select.placeholder": "Zadejte hashtagy…",
|
||||
"hashtag.column_settings.tag_mode.all": "Všechny z těchto",
|
||||
"hashtag.column_settings.tag_mode.any": "Jakékoliv z těchto",
|
||||
"hashtag.column_settings.tag_mode.none": "Žádné z těchto",
|
||||
@ -154,42 +160,45 @@
|
||||
"home.column_settings.basic": "Základní",
|
||||
"home.column_settings.show_reblogs": "Zobrazit boosty",
|
||||
"home.column_settings.show_replies": "Zobrazit odpovědi",
|
||||
"intervals.full.days": "{number, plural, one {# den} few {# dny} many {# dne} other {# dní}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hodina} few {# hodiny} many {# hodiny} other {# hodin}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minuta} few {# minuty} many {# minuty} other {# minut}}",
|
||||
"introduction.federation.action": "Další",
|
||||
"introduction.federation.federated.headline": "Federovaná",
|
||||
"introduction.federation.federated.text": "Veřejné příspěvky z jiných serverů na fediverse se zobrazí na federované časové ose.",
|
||||
"introduction.federation.federated.text": "Veřejné příspěvky z jiných serverů ve fedivesmíru se zobrazí na federované časové ose.",
|
||||
"introduction.federation.home.headline": "Domů",
|
||||
"introduction.federation.home.text": "Příspěvky od lidí, které sledujete, se objeví ve vašem domovském proudu. Můžete sledovat kohokoliv na jakémkoliv serveru!",
|
||||
"introduction.federation.local.headline": "Místní",
|
||||
"introduction.federation.local.text": "Veřejné příspěvky od lidí ze stejného serveru, jako vy, se zobrazí na místní časové ose.",
|
||||
"introduction.federation.local.text": "Veřejné příspěvky od lidí ze stejného serveru jako vy se zobrazí na místní časové ose.",
|
||||
"introduction.interactions.action": "Dokončit tutoriál!",
|
||||
"introduction.interactions.favourite.headline": "Oblíbení",
|
||||
"introduction.interactions.favourite.text": "Oblíbením si můžete uložit toot na později a dát jeho autorovi vědět, že se vám líbí.",
|
||||
"introduction.interactions.reblog.headline": "Boost",
|
||||
"introduction.interactions.reblog.text": "Boostnutím můžete sdílet tooty jiných lidí s vašimi sledovately.",
|
||||
"introduction.interactions.reblog.text": "Boostnutím můžete sdílet tooty jiných lidí s vašimi sledujícími.",
|
||||
"introduction.interactions.reply.headline": "Odpověď",
|
||||
"introduction.interactions.reply.text": "Můžete odpovídat na tooty jiných lidí i vaše vlastní, což je propojí do konverzace.",
|
||||
"introduction.welcome.action": "Jdeme na to!",
|
||||
"introduction.welcome.headline": "První kroky",
|
||||
"introduction.welcome.text": "Vítejte na fediverse! Za malou chvíli budete moci posílat zprávy a povídat si se svými přátely přes širokou škálu serverů. Tento server, {domain}, je však speciální—je na něm váš profil, proto si zapamatujte jeho jméno.",
|
||||
"introduction.welcome.text": "Vítejte ve fedivesmíru! Za malou chvíli budete moci posílat zprávy a povídat si se svými přátely přes širokou škálu serverů. Tento server, {domain}, je však speciální—je na něm váš profil, proto si zapamatujte jeho jméno.",
|
||||
"keyboard_shortcuts.back": "k návratu zpět",
|
||||
"keyboard_shortcuts.blocked": "k otevření seznamu blokovaných uživatelů",
|
||||
"keyboard_shortcuts.boost": "k boostnutí",
|
||||
"keyboard_shortcuts.column": "k zaměření na příspěvek v jednom ze sloupců",
|
||||
"keyboard_shortcuts.column": "k zaměření na toot v jednom ze sloupců",
|
||||
"keyboard_shortcuts.compose": "k zaměření na psací prostor",
|
||||
"keyboard_shortcuts.description": "Popis",
|
||||
"keyboard_shortcuts.direct": "k otevření sloupce s přímými zprávami",
|
||||
"keyboard_shortcuts.down": "k posunutí dolů v seznamu",
|
||||
"keyboard_shortcuts.enter": "k otevření příspěvku",
|
||||
"keyboard_shortcuts.enter": "k otevření tootu",
|
||||
"keyboard_shortcuts.favourite": "k oblíbení",
|
||||
"keyboard_shortcuts.favourites": "k otevření seznamu oblíbených",
|
||||
"keyboard_shortcuts.federated": "k otevření federované časové osy",
|
||||
"keyboard_shortcuts.heading": "Klávesové zkratky",
|
||||
"keyboard_shortcuts.home": "k otevření domovské časové osy",
|
||||
"keyboard_shortcuts.hotkey": "Horká klávesa",
|
||||
"keyboard_shortcuts.hotkey": "Klávesová zkratka",
|
||||
"keyboard_shortcuts.legend": "k zobrazení této legendy",
|
||||
"keyboard_shortcuts.local": "k otevření místní časové osy",
|
||||
"keyboard_shortcuts.mention": "ke zmínění autora",
|
||||
"keyboard_shortcuts.muted": "k otevření seznamu ignorovaných uživatelů",
|
||||
"keyboard_shortcuts.muted": "k otevření seznamu skrytých uživatelů",
|
||||
"keyboard_shortcuts.my_profile": "k otevření vašeho profilu",
|
||||
"keyboard_shortcuts.notifications": "k otevření sloupce s oznámeními",
|
||||
"keyboard_shortcuts.pinned": "k otevření seznamu připnutých tootů",
|
||||
@ -200,7 +209,7 @@
|
||||
"keyboard_shortcuts.start": "k otevření sloupce „začínáme“",
|
||||
"keyboard_shortcuts.toggle_hidden": "k zobrazení/skrytí textu za varováním o obsahu",
|
||||
"keyboard_shortcuts.toot": "k napsání úplně nového tootu",
|
||||
"keyboard_shortcuts.unfocus": "ke zrušení soustředění na psací prostor/hledání",
|
||||
"keyboard_shortcuts.unfocus": "ke zrušení zaměření na psací prostor/hledání",
|
||||
"keyboard_shortcuts.up": "k posunutí nahoru v seznamu",
|
||||
"lightbox.close": "Zavřít",
|
||||
"lightbox.next": "Další",
|
||||
@ -209,11 +218,12 @@
|
||||
"lists.account.remove": "Odebrat ze seznamu",
|
||||
"lists.delete": "Smazat seznam",
|
||||
"lists.edit": "Upravit seznam",
|
||||
"lists.edit.submit": "Změnit název",
|
||||
"lists.new.create": "Přidat seznam",
|
||||
"lists.new.title_placeholder": "Název nového seznamu",
|
||||
"lists.search": "Hledejte mezi lidmi, které sledujete",
|
||||
"lists.subheading": "Vaše seznamy",
|
||||
"loading_indicator.label": "Načítám...",
|
||||
"loading_indicator.label": "Načítám…",
|
||||
"media_gallery.toggle_visible": "Přepínat viditelnost",
|
||||
"missing_indicator.label": "Nenalezeno",
|
||||
"missing_indicator.sublabel": "Tento zdroj se nepodařilo najít",
|
||||
@ -229,29 +239,31 @@
|
||||
"navigation_bar.favourites": "Oblíbené",
|
||||
"navigation_bar.filters": "Skrytá slova",
|
||||
"navigation_bar.follow_requests": "Požadavky o sledování",
|
||||
"navigation_bar.info": "O této instanci",
|
||||
"navigation_bar.info": "O tomto serveru",
|
||||
"navigation_bar.keyboard_shortcuts": "Klávesové zkratky",
|
||||
"navigation_bar.lists": "Seznamy",
|
||||
"navigation_bar.logout": "Odhlásit se",
|
||||
"navigation_bar.mutes": "Ignorovaní uživatelé",
|
||||
"navigation_bar.logout": "Odhlásit",
|
||||
"navigation_bar.mutes": "Skrytí uživatelé",
|
||||
"navigation_bar.personal": "Osobní",
|
||||
"navigation_bar.pins": "Připnuté tooty",
|
||||
"navigation_bar.preferences": "Předvolby",
|
||||
"navigation_bar.public_timeline": "Federovaná časová osa",
|
||||
"navigation_bar.security": "Zabezpečení",
|
||||
"notification.favourite": "{name} si oblíbil/a váš příspěvek",
|
||||
"notification.favourite": "{name} si oblíbil/a váš toot",
|
||||
"notification.follow": "{name} vás začal/a sledovat",
|
||||
"notification.mention": "{name} vás zmínil/a",
|
||||
"notification.reblog": "{name} boostnul/a váš příspěvek",
|
||||
"notification.poll": "Anketa, ve které jste hlasoval/a, skončila",
|
||||
"notification.reblog": "{name} boostnul/a váš toot",
|
||||
"notifications.clear": "Vymazat oznámení",
|
||||
"notifications.clear_confirmation": "Jste si jistý/á, že chcete trvale vymazat všechna vaše oznámení?",
|
||||
"notifications.column_settings.alert": "Desktopová oznámení",
|
||||
"notifications.column_settings.favourite": "Oblíbené:",
|
||||
"notifications.column_settings.favourite": "Oblíbení:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Zobrazit všechny kategorie",
|
||||
"notifications.column_settings.filter_bar.category": "Panel rychlého filtrování",
|
||||
"notifications.column_settings.filter_bar.show": "Zobrazit",
|
||||
"notifications.column_settings.follow": "Noví sledovatelé:",
|
||||
"notifications.column_settings.follow": "Noví sledující:",
|
||||
"notifications.column_settings.mention": "Zmínky:",
|
||||
"notifications.column_settings.poll": "Výsledky anket:",
|
||||
"notifications.column_settings.push": "Push oznámení",
|
||||
"notifications.column_settings.reblog": "Boosty:",
|
||||
"notifications.column_settings.show": "Zobrazit ve sloupci",
|
||||
@ -261,12 +273,19 @@
|
||||
"notifications.filter.favourites": "Oblíbení",
|
||||
"notifications.filter.follows": "Sledování",
|
||||
"notifications.filter.mentions": "Zmínky",
|
||||
"notifications.filter.polls": "Výsledky anket",
|
||||
"notifications.group": "{count} oznámení",
|
||||
"privacy.change": "Změnit soukromí příspěvku",
|
||||
"poll.closed": "Uzavřena",
|
||||
"poll.refresh": "Obnovit",
|
||||
"poll.total_votes": "{count, plural, one {# hlas} few {# hlasy} many {# hlasu} other {# hlasů}}",
|
||||
"poll.vote": "Hlasovat",
|
||||
"poll_button.add_poll": "Přidat anketu",
|
||||
"poll_button.remove_poll": "Odstranit anketu",
|
||||
"privacy.change": "Změnit soukromí tootu",
|
||||
"privacy.direct.long": "Odeslat pouze zmíněným uživatelům",
|
||||
"privacy.direct.short": "Přímý",
|
||||
"privacy.private.long": "Odeslat pouze sledovatelům",
|
||||
"privacy.private.short": "Pouze pro sledovatele",
|
||||
"privacy.private.long": "Odeslat pouze sledujícím",
|
||||
"privacy.private.short": "Pouze pro sledující",
|
||||
"privacy.public.long": "Odeslat na veřejné časové osy",
|
||||
"privacy.public.short": "Veřejný",
|
||||
"privacy.unlisted.long": "Neodeslat na veřejné časové osy",
|
||||
@ -281,31 +300,31 @@
|
||||
"reply_indicator.cancel": "Zrušit",
|
||||
"report.forward": "Přeposlat na {target}",
|
||||
"report.forward_hint": "Tento účet je z jiného serveru. Chcete na něj také poslat anonymizovanou kopii?",
|
||||
"report.hint": "Toto nahlášení bude zasláno moderátorům vaší instance. Níže můžete uvést, proč tento účet nahlašujete:",
|
||||
"report.hint": "Toto nahlášení bude zasláno moderátorům vašeho serveru. Níže můžete uvést, proč tento účet nahlašujete:",
|
||||
"report.placeholder": "Dodatečné komentáře",
|
||||
"report.submit": "Odeslat",
|
||||
"report.target": "Nahlášení uživatele {target}",
|
||||
"search.placeholder": "Hledat",
|
||||
"search_popout.search_format": "Pokročilé hledání",
|
||||
"search_popout.tips.full_text": "Jednoduchý textový výpis příspěvků, které jste napsal/a, oblíbil/a si, boostnul/a, nebo v nich byl/a zmíněn/a, včetně odpovídajících přezdívek, zobrazovaných jmen a hashtagů.",
|
||||
"search_popout.tips.full_text": "Jednoduchý text navrátí tooty, které jste napsal/a, oblíbil/a si, boostnul/a, nebo v nich byl/a zmíněn/a, a také odpovídající přezdívky, zobrazovaná jména a hashtagy.",
|
||||
"search_popout.tips.hashtag": "hashtag",
|
||||
"search_popout.tips.status": "příspěvek",
|
||||
"search_popout.tips.text": "Jednoduchý textový výpis odpovídajících zobrazovaných jmen, přezdívek a hashtagů",
|
||||
"search_popout.tips.status": "toot",
|
||||
"search_popout.tips.text": "Jednoduchý text navrátí odpovídající zobrazovaná jména, přezdívky a hashtagy",
|
||||
"search_popout.tips.user": "uživatel",
|
||||
"search_results.accounts": "Lidé",
|
||||
"search_results.hashtags": "Hashtagy",
|
||||
"search_results.statuses": "Tooty",
|
||||
"search_results.total": "{count, number} {count, plural, one {výsledek} few {výsledky} many {výsledku} other {výsledků}}",
|
||||
"standalone.public_title": "Nahlédněte dovnitř...",
|
||||
"status.admin_account": "Otevřít moderační rozhraní pro uživatele @{name}",
|
||||
"status.admin_status": "Otevřít tento příspěvek v moderačním rozhraní",
|
||||
"status.admin_account": "Otevřít moderátorské rozhraní pro uživatele @{name}",
|
||||
"status.admin_status": "Otevřít tento toot v moderátorském rozhraní",
|
||||
"status.block": "Zablokovat uživatele @{name}",
|
||||
"status.cancel_reblog_private": "Zrušit boost",
|
||||
"status.cannot_reblog": "Tento příspěvek nemůže být boostnutý",
|
||||
"status.copy": "Kopírovat odkaz k tootu",
|
||||
"status.delete": "Smazat",
|
||||
"status.detailed_status": "Detailní zobrazení konverzace",
|
||||
"status.direct": "Poslat přímou zprávu uživateli @{name}",
|
||||
"status.embed": "Vložit",
|
||||
"status.embed": "Vložit na web",
|
||||
"status.favourite": "Oblíbit",
|
||||
"status.filtered": "Filtrováno",
|
||||
"status.load_more": "Zobrazit více",
|
||||
@ -313,9 +332,9 @@
|
||||
"status.media_hidden": "Média skryta",
|
||||
"status.mention": "Zmínit uživatele @{name}",
|
||||
"status.more": "Více",
|
||||
"status.mute": "Ignorovat uživatele @{name}",
|
||||
"status.mute_conversation": "Ignorovat konverzaci",
|
||||
"status.open": "Rozbalit tento příspěvek",
|
||||
"status.mute": "Skrýt uživatele @{name}",
|
||||
"status.mute_conversation": "Skrýt konverzaci",
|
||||
"status.open": "Otevřít tento toot",
|
||||
"status.pin": "Připnout na profil",
|
||||
"status.pinned": "Připnutý toot",
|
||||
"status.read_more": "Číst více",
|
||||
@ -323,7 +342,7 @@
|
||||
"status.reblog_private": "Boostnout původnímu publiku",
|
||||
"status.reblogged_by": "{name} boostnul/a",
|
||||
"status.reblogs.empty": "Tento toot ještě nikdo neboostnul. Pokud to někdo udělá, zobrazí se zde.",
|
||||
"status.redraft": "Vymazat a přepsat",
|
||||
"status.redraft": "Smazat a přepsat",
|
||||
"status.reply": "Odpovědět",
|
||||
"status.replyAll": "Odpovědět na vlákno",
|
||||
"status.report": "Nahlásit uživatele @{name}",
|
||||
@ -335,23 +354,30 @@
|
||||
"status.show_more": "Zobrazit více",
|
||||
"status.show_more_all": "Zobrazit více pro všechny",
|
||||
"status.show_thread": "Zobrazit vlákno",
|
||||
"status.unmute_conversation": "Přestat ignorovat konverzaci",
|
||||
"status.unmute_conversation": "Odkrýt konverzaci",
|
||||
"status.unpin": "Odepnout z profilu",
|
||||
"suggestions.dismiss": "Odmítnout návrh",
|
||||
"suggestions.header": "Mohlo by vás zajímat…",
|
||||
"suggestions.header": "Mohli by vás zajímat…",
|
||||
"tabs_bar.federated_timeline": "Federovaná",
|
||||
"tabs_bar.home": "Domů",
|
||||
"tabs_bar.local_timeline": "Místní",
|
||||
"tabs_bar.notifications": "Oznámení",
|
||||
"tabs_bar.search": "Hledat",
|
||||
"time_remaining.days": "{number, plural, one {Zbývá # den} few {Zbývají # dny} many {Zbývá # dne} other {Zbývá # dní}}",
|
||||
"time_remaining.hours": "{number, plural, one {Zbývá # hodina} few {Zbývají # hodiny} many {Zbývá # hodiny} other {Zbývá # hodin}}",
|
||||
"time_remaining.minutes": "{number, plural, one {Zbývá # minuta} few {Zbývají # minuty} many {Zbývá # minuty} other {Zbývá # minut}}",
|
||||
"time_remaining.moments": "Zbývá několik sekund",
|
||||
"time_remaining.seconds": "{number, plural, one {Zbývá # sekunda} few {Zbývají # sekundy} many {Zbývá # sekundy} other {Zbývá # sekund}}",
|
||||
"trends.count_by_accounts": "{count} {rawCount, plural, one {člověk} few {lidé} many {lidí} other {lidí}} hovoří",
|
||||
"ui.beforeunload": "Váš koncept se ztratí, pokud Mastodon opustíte.",
|
||||
"upload_area.title": "Přetažením nahrajete",
|
||||
"upload_button.label": "Přidat média (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||
"upload_error.limit": "Byl překročen limit nahraných souborů.",
|
||||
"upload_error.poll": "Nahrávání souborů není povoleno u anket.",
|
||||
"upload_form.description": "Popis pro zrakově postižené",
|
||||
"upload_form.focus": "Změnit náhled",
|
||||
"upload_form.undo": "Smazat",
|
||||
"upload_progress.label": "Nahrávám...",
|
||||
"upload_progress.label": "Nahrávám…",
|
||||
"video.close": "Zavřít video",
|
||||
"video.exit_fullscreen": "Ukončit celou obrazovku",
|
||||
"video.expand": "Otevřít video",
|
||||
|
@ -1,11 +1,10 @@
|
||||
{
|
||||
"account.add_or_remove_from_list": "Add or Remove from lists",
|
||||
"account.add_or_remove_from_list": "Ychwanegu neu Dileu o'r rhestrau",
|
||||
"account.badges.bot": "Bot",
|
||||
"account.block": "Blocio @{name}",
|
||||
"account.block_domain": "Cuddio popeth rhag {domain}",
|
||||
"account.blocked": "Blociwyd",
|
||||
"account.direct": "Neges breifat @{name}",
|
||||
"account.disclaimer_full": "Gall y wybodaeth isod adlewyrchu darlun anghyflawn o broffil defnyddiwr.",
|
||||
"account.domain_blocked": "Parth wedi ei guddio",
|
||||
"account.edit_profile": "Golygu proffil",
|
||||
"account.endorse": "Arddangos ar fy mhroffil",
|
||||
@ -17,7 +16,7 @@
|
||||
"account.follows_you": "Yn eich dilyn chi",
|
||||
"account.hide_reblogs": "Cuddio bwstiau o @{name}",
|
||||
"account.link_verified_on": "Gwiriwyd perchnogaeth y ddolen yma ar {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.locked_info": "Mae'r statws preifatrwydd cyfrif hwn wedi'i osod i gloi. Mae'r perchennog yn adolygu'r sawl sy'n gallu eu dilyn.",
|
||||
"account.media": "Cyfryngau",
|
||||
"account.mention": "Crybwyll @{name}",
|
||||
"account.moved_to": "Mae @{name} wedi symud i:",
|
||||
@ -36,7 +35,6 @@
|
||||
"account.unfollow": "Dad-ddilyn",
|
||||
"account.unmute": "Dad-dawelu @{name}",
|
||||
"account.unmute_notifications": "Dad-dawelu hysbysiadau o @{name}",
|
||||
"account.view_full_profile": "Gweld proffil llawn",
|
||||
"alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
|
||||
"alert.unexpected.title": "Wps!",
|
||||
"boost_modal.combo": "Mae modd gwasgu {combo} er mwyn sgipio hyn tro nesa",
|
||||
@ -73,6 +71,10 @@
|
||||
"compose_form.lock_disclaimer": "Nid yw eich cyfri wedi'i {locked}. Gall unrhyw un eich dilyn i weld eich tŵtiau dilynwyr-yn-unig.",
|
||||
"compose_form.lock_disclaimer.lock": "wedi ei gloi",
|
||||
"compose_form.placeholder": "Beth sydd ar eich meddwl?",
|
||||
"compose_form.poll.add_option": "Add a choice",
|
||||
"compose_form.poll.duration": "Poll duration",
|
||||
"compose_form.poll.option_placeholder": "Choice {number}",
|
||||
"compose_form.poll.remove_option": "Remove this choice",
|
||||
"compose_form.publish": "Tŵt",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive.marked": "Cyfryngau wedi'u marcio'n sensitif",
|
||||
@ -81,6 +83,7 @@
|
||||
"compose_form.spoiler.unmarked": "Nid yw'r testun wedi ei guddio",
|
||||
"compose_form.spoiler_placeholder": "Ysgrifenwch eich rhybudd yma",
|
||||
"confirmation_modal.cancel": "Canslo",
|
||||
"confirmations.block.block_and_report": "Block & Report",
|
||||
"confirmations.block.confirm": "Blocio",
|
||||
"confirmations.block.message": "Ydych chi'n sicr eich bod eisiau blocio {name}?",
|
||||
"confirmations.delete.confirm": "Dileu",
|
||||
@ -114,6 +117,7 @@
|
||||
"emoji_button.symbols": "Symbolau",
|
||||
"emoji_button.travel": "Teithio & Llefydd",
|
||||
"empty_column.account_timeline": "No toots here!",
|
||||
"empty_column.account_unavailable": "Profile unavailable",
|
||||
"empty_column.blocks": "Nid ydych wedi blocio unrhyw ddefnyddwyr eto.",
|
||||
"empty_column.community": "Mae'r ffrwd lleol yn wag. Ysgrifenwch rhywbeth yn gyhoeddus i gael dechrau arni!",
|
||||
"empty_column.direct": "Nid oes gennych unrhyw negeseuon preifat eto. Pan y byddwch yn anfon neu derbyn un, mi fydd yn ymddangos yma.",
|
||||
@ -137,7 +141,7 @@
|
||||
"follow_request.authorize": "Caniatau",
|
||||
"follow_request.reject": "Gwrthod",
|
||||
"getting_started.developers": "Datblygwyr",
|
||||
"getting_started.directory": "Profile directory",
|
||||
"getting_started.directory": "Cyfeiriadur proffil",
|
||||
"getting_started.documentation": "Dogfennaeth",
|
||||
"getting_started.heading": "Dechrau",
|
||||
"getting_started.invite": "Gwahodd pobl",
|
||||
@ -147,6 +151,8 @@
|
||||
"hashtag.column_header.tag_mode.all": "a {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "neu {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "heb {additional}",
|
||||
"hashtag.column_settings.select.no_options_message": "No suggestions found",
|
||||
"hashtag.column_settings.select.placeholder": "Enter hashtags…",
|
||||
"hashtag.column_settings.tag_mode.all": "Pob un o'r rhain",
|
||||
"hashtag.column_settings.tag_mode.any": "Unrhyw un o'r rhain",
|
||||
"hashtag.column_settings.tag_mode.none": "Dim o'r rhain",
|
||||
@ -154,6 +160,9 @@
|
||||
"home.column_settings.basic": "Syml",
|
||||
"home.column_settings.show_reblogs": "Dangos bŵstiau",
|
||||
"home.column_settings.show_replies": "Dangos ymatebion",
|
||||
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
||||
"introduction.federation.action": "Nesaf",
|
||||
"introduction.federation.federated.headline": "Ffederasiwn",
|
||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
||||
@ -161,15 +170,15 @@
|
||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
||||
"introduction.federation.local.headline": "Local",
|
||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
||||
"introduction.interactions.action": "Finish tutorial!",
|
||||
"introduction.interactions.action": "Gorffen tiwtorial!",
|
||||
"introduction.interactions.favourite.headline": "Ffefryn",
|
||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
||||
"introduction.interactions.reblog.headline": "Hwb",
|
||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
||||
"introduction.interactions.reply.headline": "Ateb",
|
||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
||||
"introduction.welcome.action": "Let's go!",
|
||||
"introduction.welcome.headline": "First steps",
|
||||
"introduction.welcome.action": "Awn ni!",
|
||||
"introduction.welcome.headline": "Camau cyntaf",
|
||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
||||
"keyboard_shortcuts.back": "i lywio nôl",
|
||||
"keyboard_shortcuts.blocked": "i agor rhestr defnyddwyr a flociwyd",
|
||||
@ -209,6 +218,7 @@
|
||||
"lists.account.remove": "Dileu o'r rhestr",
|
||||
"lists.delete": "Dileu rhestr",
|
||||
"lists.edit": "Golygwch rhestr",
|
||||
"lists.edit.submit": "Change title",
|
||||
"lists.new.create": "Ychwanegu rhestr",
|
||||
"lists.new.title_placeholder": "Teitl rhestr newydd",
|
||||
"lists.search": "Chwilio ymysg pobl yr ydych yn ei ddilyn",
|
||||
@ -242,16 +252,18 @@
|
||||
"notification.favourite": "hoffodd {name} eich tŵt",
|
||||
"notification.follow": "dilynodd {name} chi",
|
||||
"notification.mention": "Soniodd {name} amdanoch chi",
|
||||
"notification.poll": "A poll you have voted in has ended",
|
||||
"notification.reblog": "Hysbysebodd {name} eich tŵt",
|
||||
"notifications.clear": "Clirio hysbysiadau",
|
||||
"notifications.clear_confirmation": "Ydych chi'n sicr eich bod am glirio'ch holl hysbysiadau am byth?",
|
||||
"notifications.column_settings.alert": "Hysbysiadau bwrdd gwaith",
|
||||
"notifications.column_settings.favourite": "Ffefrynnau:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
||||
"notifications.column_settings.filter_bar.advanced": "Dangos pob categori",
|
||||
"notifications.column_settings.filter_bar.category": "Bar hidlo",
|
||||
"notifications.column_settings.filter_bar.show": "Dangos",
|
||||
"notifications.column_settings.follow": "Dilynwyr newydd:",
|
||||
"notifications.column_settings.mention": "Crybwylliadau:",
|
||||
"notifications.column_settings.poll": "Poll results:",
|
||||
"notifications.column_settings.push": "Hysbysiadau push",
|
||||
"notifications.column_settings.reblog": "Hybiadau:",
|
||||
"notifications.column_settings.show": "Dangos yn y golofn",
|
||||
@ -260,8 +272,15 @@
|
||||
"notifications.filter.boosts": "Hybiadau",
|
||||
"notifications.filter.favourites": "Ffefrynnau",
|
||||
"notifications.filter.follows": "Yn dilyn",
|
||||
"notifications.filter.mentions": "Mentions",
|
||||
"notifications.filter.mentions": "Crybwylliadau",
|
||||
"notifications.filter.polls": "Poll results",
|
||||
"notifications.group": "{count} o hysbysiadau",
|
||||
"poll.closed": "Closed",
|
||||
"poll.refresh": "Refresh",
|
||||
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
|
||||
"poll.vote": "Vote",
|
||||
"poll_button.add_poll": "Add a poll",
|
||||
"poll_button.remove_poll": "Remove poll",
|
||||
"privacy.change": "Addasu preifatrwdd y tŵt",
|
||||
"privacy.direct.long": "Cyhoeddi i'r defnyddwyr sy'n cael eu crybwyll yn unig",
|
||||
"privacy.direct.short": "Uniongyrchol",
|
||||
@ -295,13 +314,13 @@
|
||||
"search_results.accounts": "Pobl",
|
||||
"search_results.hashtags": "Hanshnodau",
|
||||
"search_results.statuses": "Tŵtiau",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
||||
"standalone.public_title": "Golwg tu fewn...",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} arall {results}}",
|
||||
"status.admin_account": "Open moderation interface for @{name}",
|
||||
"status.admin_status": "Open this tŵt in the moderation interface",
|
||||
"status.block": "Blocio @{name}",
|
||||
"status.cancel_reblog_private": "Dadfŵstio",
|
||||
"status.cannot_reblog": "Ni ellir sbarduno'r tŵt hwn",
|
||||
"status.copy": "Copy link to status",
|
||||
"status.delete": "Dileu",
|
||||
"status.detailed_status": "Golwg manwl o'r sgwrs",
|
||||
"status.direct": "Neges breifat @{name}",
|
||||
@ -337,17 +356,24 @@
|
||||
"status.show_thread": "Dangos edefyn",
|
||||
"status.unmute_conversation": "Dad-dawelu sgwrs",
|
||||
"status.unpin": "Dadbinio o'r proffil",
|
||||
"suggestions.dismiss": "Dismiss suggestion",
|
||||
"suggestions.header": "You might be interested in…",
|
||||
"suggestions.dismiss": "Diswyddo",
|
||||
"suggestions.header": "Efallai y bydd gennych ddiddordeb mewn…",
|
||||
"tabs_bar.federated_timeline": "Ffederasiwn",
|
||||
"tabs_bar.home": "Hafan",
|
||||
"tabs_bar.local_timeline": "Lleol",
|
||||
"tabs_bar.notifications": "Hysbysiadau",
|
||||
"tabs_bar.search": "Chwilio",
|
||||
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
|
||||
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
|
||||
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
|
||||
"time_remaining.moments": "Moments remaining",
|
||||
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
|
||||
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} yn siarad",
|
||||
"ui.beforeunload": "Mi fyddwch yn colli eich drafft os gadewch Mastodon.",
|
||||
"upload_area.title": "Llusgwch & gollwing i uwchlwytho",
|
||||
"upload_button.label": "Ychwanegwch gyfryngau (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.description": "Disgrifio i'r rheini a nam ar ei golwg",
|
||||
"upload_form.focus": "Newid rhagolwg",
|
||||
"upload_form.undo": "Dileu",
|
||||
|
@ -5,7 +5,6 @@
|
||||
"account.block_domain": "Skjul alt fra {domain}",
|
||||
"account.blocked": "Blokeret",
|
||||
"account.direct": "Send en direkte besked til @{name}",
|
||||
"account.disclaimer_full": "Nedenstående oplysninger reflekterer ikke nødvendigvis brugerens profil fuldstændigt.",
|
||||
"account.domain_blocked": "Domænet er blevet skjult",
|
||||
"account.edit_profile": "Rediger profil",
|
||||
"account.endorse": "Fremhæv på profil",
|
||||
@ -36,7 +35,6 @@
|
||||
"account.unfollow": "Følg ikke længere",
|
||||
"account.unmute": "Fjern dæmpningen af @{name}",
|
||||
"account.unmute_notifications": "Fjern dæmpningen af notifikationer fra @{name}",
|
||||
"account.view_full_profile": "Se fuld profil",
|
||||
"alert.unexpected.message": "Der opstod en uventet fejl.",
|
||||
"alert.unexpected.title": "Ups!",
|
||||
"boost_modal.combo": "Du kan trykke {combo} for at springe dette over næste gang",
|
||||
@ -73,6 +71,10 @@
|
||||
"compose_form.lock_disclaimer": "Din konto er ikke {locked}. Alle kan følge dig for at se dine følger-kun indlæg.",
|
||||
"compose_form.lock_disclaimer.lock": "låst",
|
||||
"compose_form.placeholder": "Hvad har du på hjertet?",
|
||||
"compose_form.poll.add_option": "Add a choice",
|
||||
"compose_form.poll.duration": "Poll duration",
|
||||
"compose_form.poll.option_placeholder": "Choice {number}",
|
||||
"compose_form.poll.remove_option": "Remove this choice",
|
||||
"compose_form.publish": "Trut",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive.marked": "Medie er markeret som værende følsomt",
|
||||
@ -81,6 +83,7 @@
|
||||
"compose_form.spoiler.unmarked": "Teksten er ikke skjult",
|
||||
"compose_form.spoiler_placeholder": "Skriv din advarsel her",
|
||||
"confirmation_modal.cancel": "Annuller",
|
||||
"confirmations.block.block_and_report": "Block & Report",
|
||||
"confirmations.block.confirm": "Bloker",
|
||||
"confirmations.block.message": "Er du sikker på, du vil blokere {name}?",
|
||||
"confirmations.delete.confirm": "Slet",
|
||||
@ -113,7 +116,8 @@
|
||||
"emoji_button.search_results": "Søgeresultater",
|
||||
"emoji_button.symbols": "Symboler",
|
||||
"emoji_button.travel": "Rejser & steder",
|
||||
"empty_column.account_timeline": "No toots here!",
|
||||
"empty_column.account_timeline": "Ingen bidrag her!",
|
||||
"empty_column.account_unavailable": "Profile unavailable",
|
||||
"empty_column.blocks": "Du har ikke blokeret nogen endnu.",
|
||||
"empty_column.community": "Den lokale tidslinje er tom. Skriv noget offentligt for at starte lavinen!",
|
||||
"empty_column.direct": "Du har endnu ingen direkte beskeder. Når du sender eller modtager en, vil den vises her.",
|
||||
@ -128,7 +132,7 @@
|
||||
"empty_column.lists": "Du har endnu ingen lister. Når du opretter en, vil den blive vist her.",
|
||||
"empty_column.mutes": "Du har endnu ikke dæmpet nogen som helst bruger.",
|
||||
"empty_column.notifications": "Du har endnu ingen notifikationer. Tag ud og bland dig med folkemængden for at starte samtalen.",
|
||||
"empty_column.public": "Der er ikke noget at se her! Skriv noget offentligt eller start ud med manuelt at følge brugere fra andre instanser for st udfylde tomrummet",
|
||||
"empty_column.public": "Der er ikke noget at se her! Skriv noget offentligt eller start ud med manuelt at følge brugere fra andre server for at udfylde tomrummet",
|
||||
"federation.change": "Adjust status federation",
|
||||
"federation.federated.long": "Allow toot to reach other instances",
|
||||
"federation.federated.short": "Federated",
|
||||
@ -144,9 +148,11 @@
|
||||
"getting_started.open_source_notice": "Mastodon er et open source software. Du kan bidrage eller rapporterer fejl på GitHub {github}.",
|
||||
"getting_started.security": "Sikkerhed",
|
||||
"getting_started.terms": "Vilkår",
|
||||
"hashtag.column_header.tag_mode.all": "and {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "or {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "without {additional}",
|
||||
"hashtag.column_header.tag_mode.all": "og {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "eller {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "uden {additional}",
|
||||
"hashtag.column_settings.select.no_options_message": "No suggestions found",
|
||||
"hashtag.column_settings.select.placeholder": "Enter hashtags…",
|
||||
"hashtag.column_settings.tag_mode.all": "All of these",
|
||||
"hashtag.column_settings.tag_mode.any": "Any of these",
|
||||
"hashtag.column_settings.tag_mode.none": "None of these",
|
||||
@ -154,22 +160,25 @@
|
||||
"home.column_settings.basic": "Grundlæggende",
|
||||
"home.column_settings.show_reblogs": "Vis fremhævelser",
|
||||
"home.column_settings.show_replies": "Vis svar",
|
||||
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
||||
"introduction.federation.action": "Næste",
|
||||
"introduction.federation.federated.headline": "Federated",
|
||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
||||
"introduction.federation.federated.text": "Offentlige bidrag fra andre servere af fediversen vil komme til syne i den federated timeline.",
|
||||
"introduction.federation.home.headline": "Home",
|
||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
||||
"introduction.federation.local.headline": "Local",
|
||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
||||
"introduction.interactions.action": "Finish tutorial!",
|
||||
"introduction.interactions.favourite.headline": "Favourite",
|
||||
"introduction.interactions.action": "Slut tutorial!",
|
||||
"introduction.interactions.favourite.headline": "Favorisere",
|
||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
||||
"introduction.interactions.reblog.headline": "Boost",
|
||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
||||
"introduction.interactions.reply.headline": "Reply",
|
||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
||||
"introduction.welcome.action": "Let's go!",
|
||||
"introduction.welcome.headline": "First steps",
|
||||
"introduction.interactions.reply.headline": "Svar",
|
||||
"introduction.interactions.reply.text": "Du kan svare andres og din egen bidrag, hvilke vil kæde dem sammen i en konversation.",
|
||||
"introduction.welcome.action": "Læd os gå!",
|
||||
"introduction.welcome.headline": "Første skridt",
|
||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
||||
"keyboard_shortcuts.back": "for at navigere dig tilbage",
|
||||
"keyboard_shortcuts.blocked": "for at åbne listen over blokerede brugere",
|
||||
@ -209,6 +218,7 @@
|
||||
"lists.account.remove": "Fjern fra liste",
|
||||
"lists.delete": "Slet liste",
|
||||
"lists.edit": "Rediger liste",
|
||||
"lists.edit.submit": "Change title",
|
||||
"lists.new.create": "Tilføj liste",
|
||||
"lists.new.title_placeholder": "Ny liste titel",
|
||||
"lists.search": "Søg iblandt folk du følger",
|
||||
@ -242,6 +252,7 @@
|
||||
"notification.favourite": "{name} favoriserede din status",
|
||||
"notification.follow": "{name} fulgte dig",
|
||||
"notification.mention": "{name} nævnte dig",
|
||||
"notification.poll": "A poll you have voted in has ended",
|
||||
"notification.reblog": "{name} fremhævede din status",
|
||||
"notifications.clear": "Ryd notifikationer",
|
||||
"notifications.clear_confirmation": "Er du sikker på, du vil rydde alle dine notifikationer permanent?",
|
||||
@ -252,6 +263,7 @@
|
||||
"notifications.column_settings.filter_bar.show": "Show",
|
||||
"notifications.column_settings.follow": "Nye følgere:",
|
||||
"notifications.column_settings.mention": "Omtale:",
|
||||
"notifications.column_settings.poll": "Poll results:",
|
||||
"notifications.column_settings.push": "Push notifikationer",
|
||||
"notifications.column_settings.reblog": "Fremhævelser:",
|
||||
"notifications.column_settings.show": "Vis i kolonne",
|
||||
@ -261,7 +273,14 @@
|
||||
"notifications.filter.favourites": "Favoritter",
|
||||
"notifications.filter.follows": "Følger",
|
||||
"notifications.filter.mentions": "Mentions",
|
||||
"notifications.filter.polls": "Poll results",
|
||||
"notifications.group": "{count} notifikationer",
|
||||
"poll.closed": "Closed",
|
||||
"poll.refresh": "Refresh",
|
||||
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
|
||||
"poll.vote": "Vote",
|
||||
"poll_button.add_poll": "Add a poll",
|
||||
"poll_button.remove_poll": "Remove poll",
|
||||
"privacy.change": "Ændre status privatliv",
|
||||
"privacy.direct.long": "Post til kun de nævnte brugere",
|
||||
"privacy.direct.short": "Direkte",
|
||||
@ -296,12 +315,12 @@
|
||||
"search_results.hashtags": "Emnetags",
|
||||
"search_results.statuses": "Trut",
|
||||
"search_results.total": "{count, number} {count, plural, et {result} andre {results}}",
|
||||
"standalone.public_title": "Et kig indenfor...",
|
||||
"status.admin_account": "Open moderation interface for @{name}",
|
||||
"status.admin_status": "Open this status in the moderation interface",
|
||||
"status.block": "Bloker @{name}",
|
||||
"status.cancel_reblog_private": "Fremhæv ikke længere",
|
||||
"status.cannot_reblog": "Denne post kan ikke fremhæves",
|
||||
"status.copy": "Copy link to status",
|
||||
"status.delete": "Slet",
|
||||
"status.detailed_status": "Detaljeret visning af samtale",
|
||||
"status.direct": "Send direkte besked til @{name}",
|
||||
@ -344,10 +363,17 @@
|
||||
"tabs_bar.local_timeline": "Lokal",
|
||||
"tabs_bar.notifications": "Notifikationer",
|
||||
"tabs_bar.search": "Søg",
|
||||
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
|
||||
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
|
||||
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
|
||||
"time_remaining.moments": "Moments remaining",
|
||||
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
|
||||
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} snakker",
|
||||
"ui.beforeunload": "Din kladde vil gå tabt hvis du forlader Mastodon.",
|
||||
"upload_area.title": "Træk og slip for at uploade",
|
||||
"upload_button.label": "Tilføj medie (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.description": "Beskriv for de svagtseende",
|
||||
"upload_form.focus": "Beskær",
|
||||
"upload_form.undo": "Slet",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user