Merge branch 'master' into glitch-soc/merge-upstream

Manually-resolved conflicts:
	.circleci/config.yml
	app/controllers/accounts_controller.rb
	app/controllers/auth/passwords_controller.rb
	app/controllers/statuses_controller.rb
	app/javascript/packs/public.js
	app/models/media_attachment.rb
	app/views/stream_entries/_content_spoiler.html.haml
	app/views/stream_entries/_media.html.haml
	config/locales/en.yml
	config/locales/ja.yml
	config/locales/pl.yml
	lib/mastodon/version.rb

Some content from app/javascript/packs/public.js has been split to
app/javascript/core/settings.js.

Translation strings for glitch-soc's keyword mutes were dropped.

Everything else was mostly “take both”.
th-downstream
Thibaut Girka 6 years ago
commit 1159490f00

@ -31,7 +31,7 @@ class AboutController < ApplicationController
end
def set_body_classes
@body_classes = 'about-body'
@body_classes = 'with-modals'
end
def initial_state_params

@ -11,6 +11,7 @@ class AccountsController < ApplicationController
respond_to do |format|
format.html do
use_pack 'public'
@body_classes = 'with-modals'
@pinned_statuses = []
if current_account && @account.blocking?(current_account)

@ -3,6 +3,7 @@
class Auth::ConfirmationsController < Devise::ConfirmationsController
layout 'auth'
before_action :set_body_classes
before_action :set_user, only: [:finish_signup]
before_action :set_pack
@ -28,6 +29,10 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
@user = current_user
end
def set_body_classes
@body_classes = 'lighter'
end
def user_params
params.require(:user).permit(:email)
end

@ -3,6 +3,7 @@
class Auth::PasswordsController < Devise::PasswordsController
before_action :check_validity_of_reset_password_token, only: :edit
before_action :set_pack
before_action :set_body_classes
layout 'auth'
@ -15,6 +16,10 @@ class Auth::PasswordsController < Devise::PasswordsController
end
end
def set_body_classes
@body_classes = 'lighter'
end
def reset_password_token_is_valid?
resource_class.with_reset_password_token(params[:reset_password_token]).present?
end

@ -9,6 +9,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_pack
before_action :set_sessions, only: [:edit, :update]
before_action :set_instance_presenter, only: [:new, :create, :update]
before_action :set_body_classes, only: [:new, :create]
def destroy
not_found
@ -84,6 +85,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
@instance_presenter = InstancePresenter.new
end
def set_body_classes
@body_classes = 'lighter'
end
def set_invite
@invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
end

@ -10,6 +10,7 @@ class Auth::SessionsController < Devise::SessionsController
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
prepend_before_action :set_pack
before_action :set_instance_presenter, only: [:new]
before_action :set_body_classes
def new
Devise.omniauth_configs.each do |provider, config|
@ -114,6 +115,10 @@ class Auth::SessionsController < Devise::SessionsController
@instance_presenter = InstancePresenter.new
end
def set_body_classes
@body_classes = 'lighter'
end
def home_paths(resource)
paths = [about_path]
if single_user_mode? && resource.is_a?(User)

@ -8,6 +8,7 @@ module AccountControllerConcern
included do
layout 'public'
before_action :set_account
before_action :set_instance_presenter
before_action :set_link_headers
before_action :check_account_suspension
end
@ -18,6 +19,10 @@ module AccountControllerConcern
@account = Account.find_local!(params[:account_username])
end
def set_instance_presenter
@instance_presenter = InstancePresenter.new
end
def set_link_headers
response.headers['Link'] = LinkHeader.new(
[

@ -43,7 +43,7 @@ class InvitesController < ApplicationController
end
def invites
Invite.where(user: current_user)
Invite.where(user: current_user).order(id: :desc)
end
def resource_params

@ -12,6 +12,7 @@ class StatusesController < ApplicationController
before_action :set_account
before_action :set_status
before_action :set_instance_presenter
before_action :set_link_headers
before_action :check_account_suspension
before_action :redirect_to_original, only: [:show]
@ -22,6 +23,8 @@ class StatusesController < ApplicationController
respond_to do |format|
format.html do
use_pack 'public'
@body_classes = 'with-modals'
set_ancestors
set_descendants
@ -150,6 +153,10 @@ class StatusesController < ApplicationController
raise ActiveRecord::RecordNotFound
end
def set_instance_presenter
@instance_presenter = InstancePresenter.new
end
def check_account_suspension
gone if @account.suspended?
end

@ -38,7 +38,7 @@ class TagsController < ApplicationController
private
def set_body_classes
@body_classes = 'tag-body'
@body_classes = 'with-modals'
end
def set_instance_presenter

@ -12,6 +12,52 @@ module StreamEntriesHelper
end
end
def account_action_button(account)
if user_signed_in?
if account.id == current_user.account_id
link_to settings_profile_url, class: 'button logo-button' do
safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('settings.edit_profile')])
end
elsif current_account.following?(account) || current_account.requested?(account)
link_to account_unfollow_path(account), class: 'button logo-button', data: { method: :post } do
safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.unfollow')])
end
else
link_to account_follow_path(account), class: 'button logo-button', data: { method: :post } do
safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.follow')])
end
end
else
link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do
safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.follow')])
end
end
end
def account_badge(account)
if account.bot?
content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles')
elsif Setting.show_staff_badge && account.user_staff?
content_tag(:div, class: 'roles') do
if account.user_admin?
content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin')
elsif account.user_moderator?
content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator')
end
end
end
end
def link_to_more(url)
link_to t('statuses.show_more'), url, class: 'load-more load-gap'
end
def nothing_here(extra_classes = '')
content_tag(:div, class: "nothing-here #{extra_classes}") do
t('accounts.nothing_here')
end
end
def account_description(account)
prepend_str = [
[

@ -1,6 +1,7 @@
// This file will be loaded on public pages, regardless of theme.
const { delegate } = require('rails-ujs');
const { length } = require('stringz');
delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
if (button !== 0) {

@ -3,24 +3,29 @@
const { length } = require('stringz');
const { delegate } = require('rails-ujs');
delegate(document, '.account_display_name', 'input', ({ target }) => {
delegate(document, '#account_display_name', 'input', ({ target }) => {
const nameCounter = document.querySelector('.name-counter');
const name = document.querySelector('.card .display-name strong');
if (nameCounter) {
nameCounter.textContent = 30 - length(target.value);
}
if (name) {
name.innerHTML = emojify(target.value);
}
});
delegate(document, '.account_note', 'input', ({ target }) => {
delegate(document, '#account_note', 'input', ({ target }) => {
const noteCounter = document.querySelector('.note-counter');
if (noteCounter) {
noteCounter.textContent = 500 - length(target.value);
noteCounter.textContent = 160 - length(target.value);
}
});
delegate(document, '#account_avatar', 'change', ({ target }) => {
const avatar = document.querySelector('.card.compact .avatar img');
const avatar = document.querySelector('.card .avatar img');
const [file] = target.files || [];
const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
@ -28,9 +33,19 @@ delegate(document, '#account_avatar', 'change', ({ target }) => {
});
delegate(document, '#account_header', 'change', ({ target }) => {
const header = document.querySelector('.card.compact');
const header = document.querySelector('.card .card__img img');
const [file] = target.files || [];
const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc;
header.style.backgroundImage = `url(${url})`;
header.src = url;
});
delegate(document, '#account_locked', 'change', ({ target }) => {
const lock = document.querySelector('.card .display-name i');
if (target.checked) {
lock.style.display = 'inline';
} else {
lock.style.display = 'none';
}
});

@ -60,6 +60,32 @@ const getUnitDelay = units => {
}
};
export const timeAgoString = (intl, date, now, year) => {
const delta = now - date.getTime();
let relativeTime;
if (delta < 10 * SECOND) {
relativeTime = intl.formatMessage(messages.just_now);
} else if (delta < 7 * DAY) {
if (delta < MINUTE) {
relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) });
} else if (delta < HOUR) {
relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) });
} else if (delta < DAY) {
relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) });
} else {
relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) });
}
} else if (date.getFullYear() === year) {
relativeTime = intl.formatDate(date, shortDateFormatOptions);
} else {
relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' });
}
return relativeTime;
};
@injectIntl
export default class RelativeTimestamp extends React.Component {
@ -121,28 +147,8 @@ export default class RelativeTimestamp extends React.Component {
render () {
const { timestamp, intl, year } = this.props;
const date = new Date(timestamp);
const delta = this.state.now - date.getTime();
let relativeTime;
if (delta < 10 * SECOND) {
relativeTime = intl.formatMessage(messages.just_now);
} else if (delta < 7 * DAY) {
if (delta < MINUTE) {
relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) });
} else if (delta < HOUR) {
relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) });
} else if (delta < DAY) {
relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) });
} else {
relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) });
}
} else if (date.getFullYear() === year) {
relativeTime = intl.formatDate(date, shortDateFormatOptions);
} else {
relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' });
}
const date = new Date(timestamp);
const relativeTime = timeAgoString(intl, date, this.state.now, year);
return (
<time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}>

@ -1,12 +1,12 @@
import { debounce } from 'lodash';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import StatusContainer from '../containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import LoadGap from './load_gap';
import ScrollableList from './scrollable_list';
import { FormattedMessage } from 'react-intl';
export default class StatusList extends ImmutablePureComponent {
@ -71,7 +71,7 @@ export default class StatusList extends ImmutablePureComponent {
}
render () {
const { statusIds, featuredStatusIds, onLoadMore, timelineId, ...other } = this.props;
const { statusIds, featuredStatusIds, shouldUpdateScroll, onLoadMore, timelineId, ...other } = this.props;
const { isLoading, isPartial } = other;
if (isPartial) {
@ -122,7 +122,7 @@ export default class StatusList extends ImmutablePureComponent {
}
return (
<ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
<ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} shouldUpdateScroll={shouldUpdateScroll} ref={this.setRef}>
{scrollableContent}
</ScrollableList>
);

@ -29,19 +29,19 @@ export default class MediaContainer extends PureComponent {
};
handleOpenMedia = (media, index) => {
document.body.classList.add('media-standalone__body');
document.body.classList.add('with-modals--active');
this.setState({ media, index });
}
handleOpenVideo = (video, time) => {
const media = ImmutableList([video]);
document.body.classList.add('media-standalone__body');
document.body.classList.add('with-modals--active');
this.setState({ media, time });
}
handleCloseMedia = () => {
document.body.classList.remove('media-standalone__body');
document.body.classList.remove('with-modals--active');
this.setState({ media: null, index: null, time: null });
}

@ -142,17 +142,17 @@ export default class ActionBar extends React.PureComponent {
<div className='account__action-bar'>
<div className='account__action-bar-links'>
<Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}`}>
<span><FormattedMessage id='account.posts' defaultMessage='Toots' /></span>
<FormattedMessage id='account.posts' defaultMessage='Toots' />
<strong>{shortNumberFormat(account.get('statuses_count'))}</strong>
</Link>
<Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`}>
<span><FormattedMessage id='account.follows' defaultMessage='Follows' /></span>
<FormattedMessage id='account.follows' defaultMessage='Follows' />
<strong>{shortNumberFormat(account.get('following_count'))}</strong>
</Link>
<Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`}>
<span><FormattedMessage id='account.followers' defaultMessage='Followers' /></span>
<FormattedMessage id='account.followers' defaultMessage='Followers' />
<strong>{shortNumberFormat(account.get('followers_count'))}</strong>
</Link>
</div>

@ -23,6 +23,7 @@ const mapStateToProps = (state, props) => ({
class LoadMoreMedia extends ImmutablePureComponent {
static propTypes = {
shouldUpdateScroll: PropTypes.func,
maxId: PropTypes.string,
onLoadMore: PropTypes.func.isRequired,
};
@ -90,7 +91,7 @@ export default class AccountGallery extends ImmutablePureComponent {
}
render () {
const { medias, isLoading, hasMore } = this.props;
const { medias, shouldUpdateScroll, isLoading, hasMore } = this.props;
let loadOlder = null;
@ -110,7 +111,7 @@ export default class AccountGallery extends ImmutablePureComponent {
<Column>
<ColumnBackButton />
<ScrollContainer scrollKey='account_gallery'>
<ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={shouldUpdateScroll}>
<div className='scrollable' onScroll={this.handleScroll}>
<HeaderContainer accountId={this.props.params.accountId} />

@ -29,6 +29,7 @@ export default class AccountTimeline extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
statusIds: ImmutablePropTypes.list,
featuredStatusIds: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
@ -61,7 +62,7 @@ export default class AccountTimeline extends ImmutablePureComponent {
}
render () {
const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
if (!statusIds && isLoading) {
return (
@ -83,6 +84,7 @@ export default class AccountTimeline extends ImmutablePureComponent {
isLoading={isLoading}
hasMore={hasMore}
onLoadMore={this.handleLoadMore}
shouldUpdateScroll={shouldUpdateScroll}
/>
</Column>
);

@ -1,5 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import LoadingIndicator from '../../components/loading_indicator';
@ -8,8 +10,6 @@ import Column from '../ui/components/column';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import AccountContainer from '../../containers/account_container';
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
const messages = defineMessages({
heading: { id: 'column.blocks', defaultMessage: 'Blocked users' },
@ -26,6 +26,7 @@ export default class Blocks extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
};
@ -43,7 +44,7 @@ export default class Blocks extends ImmutablePureComponent {
}
render () {
const { intl, accountIds } = this.props;
const { intl, accountIds, shouldUpdateScroll } = this.props;
if (!accountIds) {
return (
@ -56,7 +57,7 @@ export default class Blocks extends ImmutablePureComponent {
return (
<Column icon='ban' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<ScrollContainer scrollKey='blocks'>
<ScrollContainer scrollKey='blocks' shouldUpdateScroll={shouldUpdateScroll}>
<div className='scrollable' onScroll={this.handleScroll}>
{accountIds.map(id =>
<AccountContainer key={id} id={id} />

@ -39,6 +39,7 @@ export default class CommunityTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
@ -100,7 +101,7 @@ export default class CommunityTimeline extends React.PureComponent {
}
render () {
const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
const pinned = !!columnId;
return (
@ -124,6 +125,7 @@ export default class CommunityTimeline extends React.PureComponent {
timelineId={`community${onlyMedia ? ':media' : ''}`}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
shouldUpdateScroll={shouldUpdateScroll}
/>
</Column>
);

@ -23,6 +23,7 @@ export default class DirectTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
@ -71,7 +72,7 @@ export default class DirectTimeline extends React.PureComponent {
}
render () {
const { intl, hasUnread, columnId, multiColumn } = this.props;
const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
const pinned = !!columnId;
return (
@ -93,6 +94,7 @@ export default class DirectTimeline extends React.PureComponent {
timelineId='direct'
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
shouldUpdateScroll={shouldUpdateScroll}
/>
</Column>
);

@ -1,15 +1,15 @@
import React from 'react';
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash';
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import DomainContainer from '../../containers/domain_container';
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { debounce } from 'lodash';
import ScrollableList from '../../components/scrollable_list';
const messages = defineMessages({
@ -28,6 +28,7 @@ export default class Blocks extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
domains: ImmutablePropTypes.orderedSet,
intl: PropTypes.object.isRequired,
};
@ -41,7 +42,7 @@ export default class Blocks extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { intl, domains } = this.props;
const { intl, domains, shouldUpdateScroll } = this.props;
if (!domains) {
return (
@ -54,7 +55,7 @@ export default class Blocks extends ImmutablePureComponent {
return (
<Column icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore}>
<ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore} shouldUpdateScroll={shouldUpdateScroll}>
{domains.map(domain =>
<DomainContainer key={domain} domain={domain} />
)}

@ -27,6 +27,7 @@ export default class Favourites extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
statusIds: ImmutablePropTypes.list.isRequired,
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
@ -67,7 +68,7 @@ export default class Favourites extends ImmutablePureComponent {
}, 300, { leading: true })
render () {
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
const pinned = !!columnId;
return (
@ -90,6 +91,7 @@ export default class Favourites extends ImmutablePureComponent {
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={this.handleLoadMore}
shouldUpdateScroll={shouldUpdateScroll}
/>
</Column>
);

@ -1,5 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../components/loading_indicator';
@ -8,7 +9,6 @@ import { ScrollContainer } from 'react-router-scroll-4';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';
import ColumnBackButton from '../../components/column_back_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
@ -20,6 +20,7 @@ export default class Favourites extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
};
@ -34,7 +35,7 @@ export default class Favourites extends ImmutablePureComponent {
}
render () {
const { accountIds } = this.props;
const { shouldUpdateScroll, accountIds } = this.props;
if (!accountIds) {
return (
@ -48,7 +49,7 @@ export default class Favourites extends ImmutablePureComponent {
<Column>
<ColumnBackButton />
<ScrollContainer scrollKey='favourites'>
<ScrollContainer scrollKey='favourites' shouldUpdateScroll={shouldUpdateScroll}>
<div className='scrollable'>
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
</div>

@ -1,5 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../components/loading_indicator';
@ -8,8 +10,6 @@ import Column from '../ui/components/column';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import AccountAuthorizeContainer from './containers/account_authorize_container';
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
const messages = defineMessages({
heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' },
@ -26,6 +26,7 @@ export default class FollowRequests extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
};
@ -43,7 +44,7 @@ export default class FollowRequests extends ImmutablePureComponent {
}
render () {
const { intl, accountIds } = this.props;
const { intl, shouldUpdateScroll, accountIds } = this.props;
if (!accountIds) {
return (
@ -57,7 +58,7 @@ export default class FollowRequests extends ImmutablePureComponent {
<Column icon='users' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<ScrollContainer scrollKey='follow_requests'>
<ScrollContainer scrollKey='follow_requests' shouldUpdateScroll={shouldUpdateScroll}>
<div className='scrollable' onScroll={this.handleScroll}>
{accountIds.map(id =>
<AccountAuthorizeContainer key={id} id={id} />

@ -1,5 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../components/loading_indicator';
@ -14,7 +15,6 @@ import Column from '../ui/components/column';
import HeaderContainer from '../account_timeline/containers/header_container';
import LoadMore from '../../components/load_more';
import ColumnBackButton from '../../components/column_back_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']),
@ -27,6 +27,7 @@ export default class Followers extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
};
@ -57,7 +58,7 @@ export default class Followers extends ImmutablePureComponent {
}
render () {
const { accountIds, hasMore } = this.props;
const { shouldUpdateScroll, accountIds, hasMore } = this.props;
let loadMore = null;
@ -77,7 +78,7 @@ export default class Followers extends ImmutablePureComponent {
<Column>
<ColumnBackButton />
<ScrollContainer scrollKey='followers'>
<ScrollContainer scrollKey='followers' shouldUpdateScroll={shouldUpdateScroll}>
<div className='scrollable' onScroll={this.handleScroll}>
<div className='followers'>
<HeaderContainer accountId={this.props.params.accountId} hideTabs />

@ -1,5 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../components/loading_indicator';
@ -14,7 +15,6 @@ import Column from '../ui/components/column';
import HeaderContainer from '../account_timeline/containers/header_container';
import LoadMore from '../../components/load_more';
import ColumnBackButton from '../../components/column_back_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
@ -27,6 +27,7 @@ export default class Following extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
};
@ -57,7 +58,7 @@ export default class Following extends ImmutablePureComponent {
}
render () {
const { accountIds, hasMore } = this.props;
const { shouldUpdateScroll, accountIds, hasMore } = this.props;
let loadMore = null;
@ -77,7 +78,7 @@ export default class Following extends ImmutablePureComponent {
<Column>
<ColumnBackButton />
<ScrollContainer scrollKey='following'>
<ScrollContainer scrollKey='following' shouldUpdateScroll={shouldUpdateScroll}>
<div className='scrollable' onScroll={this.handleScroll}>
<div className='following'>
<HeaderContainer accountId={this.props.params.accountId} hideTabs />

@ -20,6 +20,7 @@ export default class HashtagTimeline extends React.PureComponent {
params: PropTypes.object.isRequired,
columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool,
};
@ -83,7 +84,7 @@ export default class HashtagTimeline extends React.PureComponent {
}
render () {
const { hasUnread, columnId, multiColumn } = this.props;
const { shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
const { id } = this.props.params;
const pinned = !!columnId;
@ -107,6 +108,7 @@ export default class HashtagTimeline extends React.PureComponent {
timelineId={`hashtag:${id}`}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
shouldUpdateScroll={shouldUpdateScroll}
/>
</Column>
);

@ -25,6 +25,7 @@ export default class HomeTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
isPartial: PropTypes.bool,
@ -93,7 +94,7 @@ export default class HomeTimeline extends React.PureComponent {
}
render () {
const { intl, hasUnread, columnId, multiColumn } = this.props;
const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
const pinned = !!columnId;
return (
@ -117,6 +118,7 @@ export default class HomeTimeline extends React.PureComponent {
onLoadMore={this.handleLoadMore}
timelineId='home'
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />}
shouldUpdateScroll={shouldUpdateScroll}
/>
</Column>
);

@ -35,6 +35,7 @@ export default class ListTimeline extends React.PureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
columnId: PropTypes.string,
hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool,
@ -112,7 +113,7 @@ export default class ListTimeline extends React.PureComponent {
}
render () {
const { hasUnread, columnId, multiColumn, list } = this.props;
const { shouldUpdateScroll, hasUnread, columnId, multiColumn, list } = this.props;
const { id } = this.props.params;
const pinned = !!columnId;
const title = list ? list.get('title') : id;
@ -166,6 +167,7 @@ export default class ListTimeline extends React.PureComponent {
timelineId={`list:${id}`}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
shouldUpdateScroll={shouldUpdateScroll}
/>
</Column>
);

@ -1,5 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../components/loading_indicator';
@ -8,8 +10,6 @@ import Column from '../ui/components/column';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import AccountContainer from '../../containers/account_container';
import { fetchMutes, expandMutes } from '../../actions/mutes';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
const messages = defineMessages({
heading: { id: 'column.mutes', defaultMessage: 'Muted users' },
@ -26,6 +26,7 @@ export default class Mutes extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
};
@ -43,7 +44,7 @@ export default class Mutes extends ImmutablePureComponent {
}
render () {
const { intl, accountIds } = this.props;
const { intl, shouldUpdateScroll, accountIds } = this.props;
if (!accountIds) {
return (
@ -56,7 +57,7 @@ export default class Mutes extends ImmutablePureComponent {
return (
<Column icon='volume-off' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<ScrollContainer scrollKey='mutes'>
<ScrollContainer scrollKey='mutes' shouldUpdateScroll={shouldUpdateScroll}>
<div className='scrollable mutes' onScroll={this.handleScroll}>
{accountIds.map(id =>
<AccountContainer key={id} id={id} />

@ -24,6 +24,7 @@ export default class PinnedStatuses extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
statusIds: ImmutablePropTypes.list.isRequired,
intl: PropTypes.object.isRequired,
hasMore: PropTypes.bool.isRequired,
@ -42,7 +43,7 @@ export default class PinnedStatuses extends ImmutablePureComponent {
}
render () {
const { intl, statusIds, hasMore } = this.props;
const { intl, shouldUpdateScroll, statusIds, hasMore } = this.props;
return (
<Column icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}>
@ -51,6 +52,7 @@ export default class PinnedStatuses extends ImmutablePureComponent {
statusIds={statusIds}
scrollKey='pinned_statuses'
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
/>
</Column>
);

@ -39,6 +39,7 @@ export default class PublicTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
@ -107,7 +108,7 @@ export default class PublicTimeline extends React.PureComponent {
}
render () {
const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props;
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia } = this.props;
const pinned = !!columnId;
return (
@ -131,6 +132,7 @@ export default class PublicTimeline extends React.PureComponent {
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' />}
shouldUpdateScroll={shouldUpdateScroll}
/>
</Column>
);

@ -1,5 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../components/loading_indicator';
@ -8,7 +9,6 @@ import { ScrollContainer } from 'react-router-scroll-4';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';
import ColumnBackButton from '../../components/column_back_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]),
@ -20,6 +20,7 @@ export default class Reblogs extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
};
@ -34,7 +35,7 @@ export default class Reblogs extends ImmutablePureComponent {
}
render () {
const { accountIds } = this.props;
const { shouldUpdateScroll, accountIds } = this.props;
if (!accountIds) {
return (
@ -48,7 +49,7 @@ export default class Reblogs extends ImmutablePureComponent {
<Column>
<ColumnBackButton />
<ScrollContainer scrollKey='reblogs'>
<ScrollContainer scrollKey='reblogs' shouldUpdateScroll={shouldUpdateScroll}>
<div className='scrollable reblogs'>
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
</div>

@ -42,7 +42,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import { boostModal, deleteModal } from '../../initial_state';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../../features/ui/util/fullscreen';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
@ -370,7 +370,7 @@ export default class Status extends ImmutablePureComponent {
render () {
let ancestors, descendants;
const { status, ancestorsIds, descendantsIds, intl } = this.props;
const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl } = this.props;
const { fullscreen } = this.state;
if (status === null) {
@ -410,7 +410,7 @@ export default class Status extends ImmutablePureComponent {
)}
/>
<ScrollContainer scrollKey='thread'>
<ScrollContainer scrollKey='thread' shouldUpdateScroll={shouldUpdateScroll}>
<div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}>
{ancestors}

@ -16,7 +16,7 @@ const messages = defineMessages({
next: { id: 'lightbox.next', defaultMessage: 'Next' },
});
const previewState = 'previewMediaModal';
export const previewState = 'previewMediaModal';
@injectIntl
export default class MediaModal extends ImmutablePureComponent {

@ -41,14 +41,15 @@ export default class ModalRoot extends React.PureComponent {
};
getSnapshotBeforeUpdate () {
const visible = !!this.props.type;
return {
overflowY: visible ? 'hidden' : null,
};
return { visible: !!this.props.type };
}
componentDidUpdate (prevProps, prevState, { overflowY }) {
document.body.style.overflowY = overflowY;
componentDidUpdate (prevProps, prevState, { visible }) {
if (visible) {
document.body.classList.add('with-modals--active');
} else {
document.body.classList.remove('with-modals--active');
}
}
renderLoading = modalId => () => {

@ -1,12 +1,14 @@
import classNames from 'classnames';
import React from 'react';
import NotificationsContainer from './containers/notifications_container';
import { HotKeys } from 'react-hotkeys';
import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { Redirect, withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import NotificationsContainer from './containers/notifications_container';
import LoadingBarContainer from './containers/loading_bar_container';
import TabsBar from './components/tabs_bar';
import ModalContainer from './containers/modal_container';
import { connect } from 'react-redux';
import { Redirect, withRouter } from 'react-router-dom';
import { isMobile } from '../../is_mobile';
import { debounce } from 'lodash';
import { uploadCompose, resetCompose } from '../../actions/compose';
@ -44,9 +46,8 @@ import {
PinnedStatuses,
Lists,
} from './util/async-components';
import { HotKeys } from 'react-hotkeys';
import { me } from '../../initial_state';
import { defineMessages, injectIntl } from 'react-intl';
import { previewState } from './components/media_modal';
// Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
@ -117,6 +118,10 @@ class SwitchingColumnsArea extends React.PureComponent {
window.removeEventListener('resize', this.handleResize);
}
shouldUpdateScroll (_, { location }) {
return location.state !== previewState;
}
handleResize = debounce(() => {
// The cached heights are no longer accurate, invalidate
this.props.onLayoutChange();
@ -141,36 +146,36 @@ class SwitchingColumnsArea extends React.PureComponent {
{redirect}
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
<WrappedRoute path='/timelines/public/media' component={PublicTimeline} content={children} componentParams={{ onlyMedia: true }} />
<WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} />
<WrappedRoute path='/timelines/public/local/media' component={CommunityTimeline} content={children} componentParams={{ onlyMedia: true }} />
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
<WrappedRoute path='/notifications' component={Notifications} content={children} />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/timelines/public/media' component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, onlyMedia: true }} />
<WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/timelines/public/local/media' component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, onlyMedia: true }} />
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/notifications' component={Notifications} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/search' component={Compose} content={children} componentParams={{ isSearchPage: true }} />
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' component={Blocks} content={children} />
<WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} />
<WrappedRoute path='/mutes' component={Mutes} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }} />
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/blocks' component={Blocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/mutes' component={Mutes} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/lists' component={Lists} content={children} />
<WrappedRoute component={GenericNotFound} content={children} />

@ -1,17 +1,17 @@
{
"account.badges.bot": "Robot",
"account.block": "Blokovat @{name}",
"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.domain_blocked": "Doména skryta",
"account.edit_profile": "Uprav profil",
"account.edit_profile": "Upravit profil",
"account.follow": "Sleduj",
"account.followers": "Sledovatelé",
"account.follows": "Sleduje",
"account.follows_you": "Sleduje vás",
"account.hide_reblogs": "Skrýt povýšení od uživatele @{name}",
"account.hide_reblogs": "Skrýt boosty od uživatele @{name}",
"account.media": "Média",
"account.mention": "Zmínit uživatele @{name}",
"account.moved_to": "{name} se přesunul/a na:",
@ -23,7 +23,7 @@
"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.share": "Sdílet profil uživatele @{name}",
"account.show_reblogs": "Zobrazit povýšení od 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.unfollow": "Přestat sledovat",
@ -64,7 +64,7 @@
"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": "zamknutý",
"compose_form.lock_disclaimer.lock": "zamčený",
"compose_form.placeholder": "Co máte na mysli?",
"compose_form.publish": "Tootnout",
"compose_form.publish_loud": "{publish}!",
@ -85,225 +85,225 @@
"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": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
"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.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.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.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
"getting_started.developers": "Developers",
"confirmations.redraft.message": "Jste si jistý/á, že chcete vymazat a přepsat tento status? Ztratíte všechny jeho odpovědi, boosty a oblíbení.",
"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í statusu 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í",
"emoji_button.flags": "Vlajky",
"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.objects": "Předměty",
"emoji_button.people": "Lidé",
"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.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.",
"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é statusy, objeví se zde.",
"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",
"follow_request.authorize": "Autorizovat",
"follow_request.reject": "Odmítnout",
"getting_started.developers": "Vývojáři",
"getting_started.documentation": "Documentation",
"getting_started.find_friends": "Find friends from Twitter",
"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",
"home.column_settings.basic": "Basic",
"home.column_settings.show_reblogs": "Show boosts",
"home.column_settings.show_replies": "Show replies",
"keyboard_shortcuts.back": "to navigate back",
"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.down": "to move down in the list",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "to favourite",
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
"keyboard_shortcuts.hotkey": "Hotkey",
"keyboard_shortcuts.legend": "to display this legend",
"keyboard_shortcuts.mention": "to mention author",
"getting_started.find_friends": "Najděte si přátele z Twitteru",
"getting_started.heading": "Začínáme",
"getting_started.invite": "Pozvat lidi",
"getting_started.open_source_notice": "Mastodon je otevřený software. Na GitHubu k němu můžete přispět nebo nahlásit chyby: {github}.",
"getting_started.security": "Zabezpečení",
"getting_started.terms": "Podmínky používání",
"home.column_settings.basic": "Základní",
"home.column_settings.show_reblogs": "Zobrazit boosty",
"home.column_settings.show_replies": "Zobrazit odpovědi",
"keyboard_shortcuts.back": "k návratu zpět",
"keyboard_shortcuts.boost": "k boostnutí",
"keyboard_shortcuts.column": "k zaměření na status v jednom ze sloupců",
"keyboard_shortcuts.compose": "k zaměření na psací prostor",
"keyboard_shortcuts.description": "Popis",
"keyboard_shortcuts.down": "k přesunutí dolů v seznamu",
"keyboard_shortcuts.enter": "k otevření statusu",
"keyboard_shortcuts.favourite": "k oblíbení",
"keyboard_shortcuts.heading": "Klávesové zkratky",
"keyboard_shortcuts.hotkey": "Horká klávesa",
"keyboard_shortcuts.legend": "k zobrazení této legendy",
"keyboard_shortcuts.mention": "ke zmínění autora",
"keyboard_shortcuts.profile": "to open author's profile",
"keyboard_shortcuts.reply": "to reply",
"keyboard_shortcuts.search": "to focus search",
"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.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.blocks": "Blocked users",
"navigation_bar.community_timeline": "Local timeline",
"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 instance",
"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.reblog": "{name} boosted your status",
"notifications.clear": "Clear notifications",
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
"notifications.column_settings.alert": "Desktop notifications",
"notifications.column_settings.favourite": "Favourites:",
"notifications.column_settings.follow": "New followers:",
"notifications.column_settings.mention": "Mentions:",
"notifications.column_settings.push": "Push notifications",
"notifications.column_settings.push_meta": "This device",
"notifications.column_settings.reblog": "Boosts:",
"notifications.column_settings.show": "Show in column",
"notifications.column_settings.sound": "Play sound",
"notifications.group": "{count} notifications",
"onboarding.done": "Done",
"onboarding.next": "Next",
"onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
"onboarding.page_four.home": "The home timeline shows posts from people you follow.",
"onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
"onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
"onboarding.page_one.full_handle": "Your full handle",
"onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
"onboarding.page_one.welcome": "Welcome to Mastodon!",
"onboarding.page_six.admin": "Your instance's admin is {admin}.",
"onboarding.page_six.almost_done": "Almost done...",
"onboarding.page_six.appetoot": "Bon Appetoot!",
"onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
"onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
"onboarding.page_six.guidelines": "community guidelines",
"onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
"onboarding.page_six.various_app": "mobile apps",
"onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
"onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
"onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
"onboarding.skip": "Skip",
"privacy.change": "Adjust status privacy",
"privacy.direct.long": "Post to mentioned users only",
"privacy.direct.short": "Direct",
"privacy.private.long": "Post to followers only",
"privacy.private.short": "Followers-only",
"privacy.public.long": "Post to public timelines",
"privacy.public.short": "Public",
"keyboard_shortcuts.reply": "k odpovězení",
"keyboard_shortcuts.search": "k zaměření na vyhledávání",
"keyboard_shortcuts.toggle_hidden": "k zobrazení/skrytí textu za CW",
"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.up": "k posunutí nahoru v seznamu",
"lightbox.close": "Zavřít",
"lightbox.next": "Další",
"lightbox.previous": "Předchozí",
"lists.account.add": "Přidat do seznamu",
"lists.account.remove": "Odebrat ze seznamu",
"lists.delete": "Smazat seznam",
"lists.edit": "Upravit seznam",
"lists.new.create": "Přidat seznam",
"lists.new.title_placeholder": "Název nového seznamu",
"lists.search": "Hledejte mezi uživateli, které sledujete",
"lists.subheading": "Vaše seznamy",
"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",
"mute_modal.hide_notifications": "Skrýt oznámení před tímto uživatelem?",
"navigation_bar.blocks": "Blokovaní uživatelé",
"navigation_bar.community_timeline": "Místní časová osa",
"navigation_bar.direct": "Přímé zprávy",
"navigation_bar.discover": "Objevujte",
"navigation_bar.domain_blocks": "Skryté domény",
"navigation_bar.edit_profile": "Upravit profil",
"navigation_bar.favourites": "Oblíbené",
"navigation_bar.filters": "Skrytá slova",
"navigation_bar.follow_requests": "Žádosti o sledování",
"navigation_bar.info": "O této instanci",
"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.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} označil/a váš status jako oblíbený",
"notification.follow": "{name} vás začal/a sledovat",
"notification.mention": "{name} vás zmínil/a",
"notification.reblog": "{name} vám boostnul/a status",
"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.follow": "Noví sledovatelé:",
"notifications.column_settings.mention": "Zmínky:",
"notifications.column_settings.push": "Push oznámení",
"notifications.column_settings.push_meta": "Toto zařízení",
"notifications.column_settings.reblog": "Boosty:",
"notifications.column_settings.show": "Zobrazit ve sloupci",
"notifications.column_settings.sound": "Přehrát zvuk",
"notifications.group": "{count} oznámení",
"onboarding.done": "Hotovo",
"onboarding.next": "Další",
"onboarding.page_five.public_timelines": "Místní časová osa zobrazuje veřejné příspěvky od všech lidí na {domain}. Federovaná časová osa zobrazuje veřejné příspěvky ode všech, které lidé na {domain} sledují. Toto jsou veřejné časové osy, výborný způsob, jak objevovat nové lidi.",
"onboarding.page_four.home": "Domovská časová osa zobrazuje příspěvky od lidí, které sledujete.",
"onboarding.page_four.notifications": "Sloupec oznámení se zobrazí, když s vámi někdo bude komunikovat.",
"onboarding.page_one.federation": "Mastodon je síť nezávislých serverů, jejichž propojením vzniká jedna velká sociální síť. Těmto serverům říkáme instance.",
"onboarding.page_one.full_handle": "Vaše celá adresa profilu",
"onboarding.page_one.handle_hint": "Tohle je, co byste řekl/a svým přátelům, aby hledali.",
"onboarding.page_one.welcome": "Vítejte na Mastodonu!",
"onboarding.page_six.admin": "Administrátorem vaší instance je {admin}.",
"onboarding.page_six.almost_done": "Skoro hotovo...",
"onboarding.page_six.appetoot": "Bon appetoot!",
"onboarding.page_six.apps_available": "Jsou dostupné {apps} pro iOS, Android a jiné platformy.",
"onboarding.page_six.github": "Mastodon je svobodný a otevřený software. Na {github} můžete nahlásit chyby, požádat o nové funkce, nebo přispívat ke kódu.",
"onboarding.page_six.guidelines": "komunitní pravidla",
"onboarding.page_six.read_guidelines": "Prosím přečtěte si {guidelines} {domain}!",
"onboarding.page_six.various_app": "mobilní aplikace",
"onboarding.page_three.profile": "Upravte si svůj profil a změňte si svůj avatar, popis profilu a zobrazované jméno. V nastaveních najdete i další možnosti.",
"onboarding.page_three.search": "Pomocí vyhledávacího řádku najděte lidi a podívejte se na hashtagy jako {illustration} a {introductions}. Chcete-li najít někoho, kdo není na této instanci, použijte jeho celou adresu profilu.",
"onboarding.page_two.compose": "Příspěvky pište z pole na komponování. Ikonami níže můžete nahrávat obrázky, změnit nastavení soukromí a přidat varování o obsahu.",
"onboarding.skip": "Přeskočit",
"privacy.change": "Změnit viditelnost statusu",
"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.public.long": "Odeslat na veřejné časové osy",
"privacy.public.short": "Veřejné",
"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!",
"privacy.unlisted.short": "Nezobrazované",
"regeneration_indicator.label": "Načítám…",
"regeneration_indicator.sublabel": "Váš domovský proud se připravuje!",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "now",
"relative_time.just_now": "teď",
"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 instance 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.",
"reply_indicator.cancel": "Zrušit",
"report.forward": "Přeposlat k {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.placeholder": "Další komentáře",
"report.submit": "Odeslat",
"report.target": "Nahlásit {target}",
"search.placeholder": "Hledat",
"search_popout.search_format": "Pokročilé vyhledávání",
"search_popout.tips.full_text": "Jednoduchý textový výpis statusů, které jste napsal/a, oblíbil/a si, povýšil/a, nebo v nich byl/a zmíněn/a, včetně odpovídajících přezdívek, jmen a hashtagů.",
"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}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cancel_reblog_private": "Unboost",
"status.cannot_reblog": "This post cannot be boosted",
"search_popout.tips.text": "Jednoduchý textový výpis odpovídajících jmen, přezdívek a hashtagů",
"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} other {výsledků}}",
"standalone.public_title": "Nahlédnout dovnitř...",
"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.delete": "Delete",
"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.reblog": "Boost",
"status.reblog_private": "Boost to original audience",
"status.reblogged_by": "{name} boosted",
"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.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Local",
"tabs_bar.notifications": "Notifications",
"tabs_bar.search": "Search",
"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",
"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"
"status.direct": "Poslat přímou zprávu uživateli @{name}",
"status.embed": "Vložit",
"status.favourite": "Oblíbit",
"status.filtered": "Filtrováno",
"status.load_more": "Zobrazit více",
"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": "Otevřít tento status",
"status.pin": "Připnout na profil",
"status.pinned": "Připnutý toot",
"status.reblog": "Boostnout",
"status.reblog_private": "Boostnout původnímu publiku",
"status.reblogged_by": "{name} boostnul/a",
"status.redraft": "Vymazat a přepsat",
"status.reply": "Odpovědět",
"status.replyAll": "Odpovědět na vlákno",
"status.report": "Nahlásit uživatele @{name}",
"status.sensitive_toggle": "Klikněte pro zobrazení",
"status.sensitive_warning": "Citlivý obsah",
"status.share": "Sdílet",
"status.show_less": "Zobrazit méně",
"status.show_less_all": "Zobrazit méně pro všechny",
"status.show_more": "Zobrazit více",
"status.show_more_all": "Zobrazit více pro všechny",
"status.unmute_conversation": "Přestat ignorovat konverzaci",
"status.unpin": "Odepnout z profilu",
"tabs_bar.federated_timeline": "Federovaná",
"tabs_bar.home": "Domů",
"tabs_bar.local_timeline": "Místní",
"tabs_bar.notifications": "Oznámení",
"tabs_bar.search": "Hledat",
"trends.count_by_accounts": "{count} {rawCount, plural, one {člověk} other {lidí}} diskutuje",
"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",
"upload_form.description": "Popis pro zrakově postižené",
"upload_form.focus": "Vystřihnout",
"upload_form.undo": "Smazat",
"upload_progress.label": "Nahrávám...",
"video.close": "Zavřít video",
"video.exit_fullscreen": "Ukončit celou obrazovku",
"video.expand": "Otevřít video",
"video.fullscreen": "Celá obrazovka",
"video.hide": "Skrýt video",
"video.mute": "Vypnout zvuk",
"video.pause": "Pauza",
"video.play": "Přehrát",
"video.unmute": "Zapnout zvuk"
}

@ -167,7 +167,7 @@
"navigation_bar.domain_blocks": "숨겨진 도메인",
"navigation_bar.edit_profile": "프로필 편집",
"navigation_bar.favourites": "즐겨찾기",
"navigation_bar.filters": "Muted words",
"navigation_bar.filters": "뮤트",
"navigation_bar.follow_requests": "팔로우 요청",
"navigation_bar.info": "이 인스턴스에 대해서",
"navigation_bar.keyboard_shortcuts": "단축키",

@ -129,11 +129,11 @@
"keyboard_shortcuts.boost": "para compartilhar",
"keyboard_shortcuts.column": "Focar um status em uma das colunas",
"keyboard_shortcuts.compose": "para focar a área de redação",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.description": "Descrição",
"keyboard_shortcuts.down": "para mover para baixo na lista",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.enter": "para expandir um status",
"keyboard_shortcuts.favourite": "para adicionar aos favoritos",
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
"keyboard_shortcuts.heading": "Atalhos de teclado",
"keyboard_shortcuts.hotkey": "Atalho",
"keyboard_shortcuts.legend": "para mostrar essa legenda",
"keyboard_shortcuts.mention": "para mencionar o autor",

@ -65,7 +65,7 @@
"compose_form.hashtag_warning": "ఈ టూట్ అన్లిస్టెడ్ కాబట్టి ఏ హాష్ ట్యాగ్ క్రిందకూ రాదు. పబ్లిక్ టూట్ లను మాత్రమే హాష్ ట్యాగ్ ద్వారా శోధించవచ్చు.",
"compose_form.lock_disclaimer": "మీ ఖాతా {locked} చేయబడలేదు. ఎవరైనా మిమ్మల్ని అనుసరించి మీ అనుచరులకు-మాత్రమే పోస్ట్లను వీక్షించవచ్చు.",
"compose_form.lock_disclaimer.lock": "బిగించబడినది",
"compose_form.placeholder": "మీ మనస్సులో ఏమి ఉంది?",
"compose_form.placeholder": "మీ మనస్సులో ఏమంది?",
"compose_form.publish": "టూట్",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.marked": "మీడియా సున్నితమైనదిగా గుర్తించబడింది",
@ -115,7 +115,7 @@
"follow_request.authorize": "అనుమతించు",
"follow_request.reject": "తిరస్కరించు",
"getting_started.developers": "డెవలపర్లు",
"getting_started.documentation": "Documentation",
"getting_started.documentation": "డాక్యుమెంటేషన్",
"getting_started.find_friends": "ట్విట్టర్ నుండి స్నేహితులను కనుగొనండి",
"getting_started.heading": "మొదలుపెడదాం",
"getting_started.invite": "వ్యక్తులను ఆహ్వానించండి",
@ -167,7 +167,7 @@
"navigation_bar.domain_blocks": "దాచిన డొమైన్లు",
"navigation_bar.edit_profile": "ప్రొఫైల్ని సవరించండి",
"navigation_bar.favourites": "ఇష్టపడినవి",
"navigation_bar.filters": "Muted words",
"navigation_bar.filters": "మ్యూట్ చేయబడిన పదాలు",
"navigation_bar.follow_requests": "అనుసరించడానికి అభ్యర్ధనలు",
"navigation_bar.info": "ఈ దృష్టాంతం గురించి",
"navigation_bar.keyboard_shortcuts": "హాట్ కీలు",
@ -258,7 +258,7 @@
"status.direct": "@{name}కు నేరుగా సందేశం పంపు",
"status.embed": "ఎంబెడ్",
"status.favourite": "ఇష్టపడు",
"status.filtered": "Filtered",
"status.filtered": "వడకట్టబడిన",
"status.load_more": "మరిన్ని లోడ్ చేయి",
"status.media_hidden": "మీడియా దాచబడింది",
"status.mention": "@{name}ను ప్రస్తావించు",

@ -5,14 +5,16 @@ import { start } from '../mastodon/common';
start();
function main() {
const IntlRelativeFormat = require('intl-relativeformat').default;
const { length } = require('stringz');
const IntlMessageFormat = require('intl-messageformat').default;
const { timeAgoString } = require('../mastodon/components/relative_timestamp');
const { delegate } = require('rails-ujs');
const emojify = require('../mastodon/features/emoji/emoji').default;
const { getLocale } = require('../mastodon/locales');
const { localeData } = getLocale();
const { messages } = getLocale();
const React = require('react');
const ReactDOM = require('react-dom');
localeData.forEach(IntlRelativeFormat.__addLocaleData);
const Rellax = require('rellax');
ready(() => {
const locale = document.documentElement.lang;
@ -25,8 +27,6 @@ function main() {
minute: 'numeric',
});
const relativeFormat = new IntlRelativeFormat(locale);
[].forEach.call(document.querySelectorAll('.emojify'), (content) => {
content.innerHTML = emojify(content.innerHTML);
});
@ -41,12 +41,16 @@ function main() {
[].forEach.call(document.querySelectorAll('time.time-ago'), (content) => {
const datetime = new Date(content.getAttribute('datetime'));
const now = new Date();
content.title = dateTimeFormat.format(datetime);
content.textContent = relativeFormat.format(datetime);
content.textContent = timeAgoString({
formatMessage: ({ id, defaultMessage }, values) => (new IntlMessageFormat(messages[id] || defaultMessage, locale)).format(values),
formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date),
}, datetime, now, datetime.getFullYear());
});
[].forEach.call(document.querySelectorAll('.logo-button'), (content) => {
[].forEach.call(document.querySelectorAll('.modal-button'), (content) => {
content.addEventListener('click', (e) => {
e.preventDefault();
window.open(e.target.href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
@ -64,6 +68,8 @@ function main() {
})
.catch(error => console.error(error));
}
new Rellax('.parallax', { speed: -1 });
});
}

@ -10,7 +10,7 @@
@import 'mastodon/lists';
@import 'mastodon/footer';
@import 'mastodon/compact_header';
@import 'mastodon/landing_strip';
@import 'mastodon/widgets';
@import 'mastodon/forms';
@import 'mastodon/accounts';
@import 'mastodon/stream_entries';

@ -1115,6 +1115,21 @@ $small-breakpoint: 960px;
}
&.tag-page {
@media screen and (max-width: $column-breakpoint) {
padding: 0;
.container {
padding: 0;
}
#mastodon-timeline {
display: block;
width: 100vw;
height: 100vh;
border-radius: 0;
}
}
.grid {
@media screen and (min-width: $small-breakpoint) {
grid-template-columns: 33% 67%;
@ -1146,24 +1161,17 @@ $small-breakpoint: 960px;
@media screen and (max-width: $column-breakpoint) {
.grid {
grid-gap: 0;
.column-1 {
grid-column: 1;
grid-row: 2;
grid-row: 1;
}
.column-2 {
grid-column: 1;
grid-row: 1;
display: none;
}
}
.brand {
margin: 0;
}
.landing-page__features {
display: none;
}
}
}
}

@ -1,243 +1,100 @@
.card {
background-color: $base-shadow-color;
background-size: cover;
background-position: center;
border-radius: 4px 4px 0 0;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
overflow: hidden;
position: relative;
display: flex;
&::after {
background: rgba(darken($ui-base-color, 8%), 0.5);
& > a {
display: block;
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
}
@media screen and (max-width: 740px) {
border-radius: 0;
box-shadow: none;
}
.card__illustration {
padding: 60px 0;
position: relative;
flex: 1 1 auto;
display: flex;
justify-content: center;
align-items: center;
}
.card__bio {
max-width: 260px;
flex: 1 1 auto;
display: flex;
flex-direction: column;
justify-content: space-between;
background: rgba(darken($ui-base-color, 8%), 0.8);
position: relative;
z-index: 2;
}
&.compact {
padding: 30px 0;
border-radius: 4px;
.avatar {
margin-bottom: 0;
text-decoration: none;
color: inherit;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
img {
object-fit: cover;
}
@media screen and (max-width: $no-gap-breakpoint) {
box-shadow: none;
}
}
.name {
display: block;
font-size: 20px;
line-height: 18px * 1.5;
color: $primary-text-color;
padding: 10px 15px;
padding-bottom: 0;
font-weight: 500;
position: relative;
z-index: 2;
margin-bottom: 30px;
overflow: hidden;
text-overflow: ellipsis;
small {
display: block;
font-size: 14px;
color: $highlight-text-color;
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
.fa {
margin-left: 3px;
&:hover,
&:active,
&:focus {
.card__bar {
background: lighten($ui-base-color, 8%);
}
}
}
.avatar {
width: 120px;
margin: 0 auto;
&__img {
height: 130px;
position: relative;
z-index: 2;
background: darken($ui-base-color, 12%);
border-radius: 4px 4px 0 0;
img {
width: 120px;
height: 120px;
display: block;
border-radius: 120px;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
}
}
.roles {
margin-bottom: 30px;
padding: 0 15px;
}
.details-counters {
margin-top: 30px;
display: flex;
flex-direction: row;
width: 100%;
}
.counter {
width: 33.3%;
box-sizing: border-box;
flex: 0 0 auto;
color: $darker-text-color;
padding: 5px 10px 0;
margin-bottom: 10px;
border-right: 1px solid lighten($ui-base-color, 4%);
cursor: default;
text-align: center;
position: relative;
a {
display: block;
}
&:last-child {
border-right: 0;
}
&::after {
display: block;
content: "";
position: absolute;
bottom: -10px;
left: 0;
width: 100%;
border-bottom: 4px solid $ui-primary-color;
opacity: 0.5;
transition: all 400ms ease;
}
&.active {
&::after {
border-bottom: 4px solid $highlight-text-color;
opacity: 1;
}
}
&:hover {
&::after {
opacity: 1;
transition-duration: 100ms;
}
height: 100%;
margin: 0;
object-fit: cover;
border-radius: 4px 4px 0 0;
}
a {
text-decoration: none;
color: inherit;
@media screen and (max-width: 600px) {
height: 200px;
}
.counter-label {
font-size: 12px;
display: block;
margin-bottom: 5px;
}
.counter-number {
font-weight: 500;
font-size: 18px;
color: $primary-text-color;
font-family: 'mastodon-font-display', sans-serif;
@media screen and (max-width: $no-gap-breakpoint) {
display: none;
}
}
.bio {
font-size: 14px;
line-height: 18px;
padding: 0 15px;
color: $secondary-text-color;
}
@media screen and (max-width: 480px) {
display: block;
&__bar {
position: relative;
padding: 15px;
display: flex;
justify-content: flex-start;
align-items: center;
background: lighten($ui-base-color, 4%);
border-radius: 0 0 4px 4px;
.card__bio {
max-width: none;
@media screen and (max-width: $no-gap-breakpoint) {
border-radius: 0;
}
.name,
.roles {
text-align: center;
margin-bottom: 15px;
}
.avatar {
flex: 0 0 auto;
width: 48px;
height: 48px;
padding-top: 2px;
.bio {
margin-bottom: 15px;
img {
width: 100%;
height: 100%;
display: block;
margin: 0;
border-radius: 4px;
background: darken($ui-base-color, 8%);
}
}
}
}
.card,
.account-grid-card {
.controls {
position: absolute;
top: 15px;
left: 15px;
z-index: 2;
.icon-button {
color: rgba($white, 0.8);
text-decoration: none;
font-size: 13px;
line-height: 13px;
font-weight: 500;
.fa {
font-weight: 400;
margin-right: 5px;
.display-name {
margin-left: 15px;
text-align: left;
strong {
font-size: 15px;
color: $primary-text-color;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
}
&:hover,
&:active,
&:focus {
color: $white;
span {
display: block;
font-size: 14px;
color: $darker-text-color;
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.account-grid-card .controls {
left: auto;
right: 15px;
}
.pagination {
padding: 30px 0;
text-align: center;
@ -314,289 +171,23 @@
}
}
.accounts-grid {
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
background: darken($simple-background-color, 8%);
border-radius: 0 0 4px 4px;
padding: 20px 5px;
padding-bottom: 10px;
overflow: hidden;
display: flex;
flex-wrap: wrap;
z-index: 2;
position: relative;
&.empty img {
position: absolute;
opacity: 0.2;
height: 200px;
left: 0;
bottom: 0;
pointer-events: none;
}
@media screen and (max-width: 740px) {
border-radius: 0;
box-shadow: none;
}
.account-grid-card {
box-sizing: border-box;
width: 335px;
background: $simple-background-color;
border-radius: 4px;
color: $inverted-text-color;
margin: 0 5px 10px;
position: relative;
@media screen and (max-width: 740px) {
width: calc(100% - 10px);
}
.account-grid-card__header {
overflow: hidden;
height: 100px;
border-radius: 4px 4px 0 0;
background-color: lighten($inverted-text-color, 4%);
background-size: cover;
background-position: center;
position: relative;
&::after {
background: rgba(darken($ui-base-color, 8%), 0.5);
display: block;
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
}
}
.account-grid-card__avatar {
box-sizing: border-box;
padding: 15px;
position: absolute;
z-index: 2;
top: 100px - (40px + 2px);
left: -2px;
}
.avatar {
width: 80px;
height: 80px;
img {
display: block;
width: 80px;
height: 80px;
border-radius: 80px;
border: 2px solid $simple-background-color;
background: $simple-background-color;
}
}
.name {
padding: 15px;
padding-top: 10px;
padding-left: 15px + 80px + 15px;
a {
display: block;
color: $inverted-text-color;
text-decoration: none;
text-overflow: ellipsis;
overflow: hidden;
font-weight: 500;
&:hover {
.display_name {
text-decoration: underline;
}
}
}
}
.display_name {
font-size: 16px;
display: block;
text-overflow: ellipsis;
overflow: hidden;
}
.username {
color: $lighter-text-color;
font-size: 14px;
font-weight: 400;
}
.account__header__content {
padding: 10px 15px;
padding-top: 15px;
color: $lighter-text-color;
word-wrap: break-word;
overflow: hidden;
text-overflow: ellipsis;
height: 5.5em;
position: relative;
&::after {
display: block;
content: "";
width: 100%;
height: 100px;
position: absolute;
bottom: 0;
background: linear-gradient(to bottom, rgba($simple-background-color, 0.01) 0%, rgba($simple-background-color, 1) 100%);
left: 0;
border-radius: 0 0 4px 4px;
pointer-events: none;
}
}
}
}
.nothing-here {
width: 100%;
display: block;
background: $ui-base-color;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
color: $light-text-color;
font-size: 14px;
font-weight: 500;
text-align: center;
padding: 130px 0;
padding-top: 125px;
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
cursor: default;
}
.account-card {
border-radius: 4px;
text-align: left;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
background: $simple-background-color;
&__header {
background: $base-shadow-color;
background-size: cover;
background-position: center center;
height: 90px;
border-radius: 4px 4px 0 0;
}
padding: 20px;
min-height: 30vh;
& > .detailed-status__display-name {
display: block;
overflow: hidden;
display: flex;
align-items: center;
padding: 10px;
&:last-child {
margin-bottom: 0;
}
& > div:first-child {
flex: 0 0 auto;
margin-right: 10px;
width: 48px;
height: 48px;
}
.avatar {
display: block;
border-radius: 4px;
margin: 0;
}
.display-name {
flex: 1 0 auto;
display: block;
max-width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
cursor: default;
& > .detailed-status__display-name {
margin-bottom: 0;
}
strong {
font-weight: 500;
color: $ui-base-color;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
span {
font-size: 14px;
color: $light-text-color;
}
}
&:hover {
.display-name {
strong {
text-decoration: none;
}
}
}
}
.counter {
box-sizing: border-box;
flex: 0 0 auto;
color: $light-text-color;
padding: 0 10px;
cursor: default;
text-align: center;
position: relative;
line-height: 24px;
.counter-label {
font-size: 12px;
display: block;
text-transform: uppercase;
}
.counter-number {
font-weight: 500;
font-size: 16px;
color: $inverted-text-color;
font-family: 'mastodon-font-display', sans-serif;
}
}
}
.activity-stream-tabs {
background: $simple-background-color;
border-bottom: 1px solid $ui-secondary-color;
position: relative;
z-index: 2;
a {
display: inline-block;
padding: 15px;
text-decoration: none;
color: $highlight-text-color;
text-transform: uppercase;
font-weight: 500;
&:hover,
&:active,
&:focus {
color: lighten($highlight-text-color, 8%);
}
&.active {
color: $inverted-text-color;
cursor: default;
}
&--under-tabs {
border-radius: 0 0 4px 4px;
}
}
@ -629,14 +220,14 @@
padding: 0;
margin: 15px -15px -15px;
border: 0 none;
border-top: 1px solid lighten($ui-base-color, 4%);
border-bottom: 1px solid lighten($ui-base-color, 4%);
border-top: 1px solid lighten($ui-base-color, 12%);
border-bottom: 1px solid lighten($ui-base-color, 12%);
font-size: 14px;
line-height: 20px;
dl {
display: flex;
border-bottom: 1px solid lighten($ui-base-color, 4%);
border-bottom: 1px solid lighten($ui-base-color, 12%);
}
dt,

@ -1,13 +1,10 @@
body {
font-family: 'mastodon-font-sans-serif', sans-serif;
background: $ui-base-color;
background-size: cover;
background-attachment: fixed;
background: darken($ui-base-color, 8%);
font-size: 13px;
line-height: 18px;
font-weight: 400;
color: $primary-text-color;
padding-bottom: 20px;
text-rendering: optimizelegibility;
font-feature-settings: "kern";
text-size-adjust: none;
@ -35,16 +32,24 @@ body {
height: 100%;
padding: 0;
background: $ui-base-color;
&.with-modals--active {
overflow-y: hidden;
}
}
&.about-body {
background: darken($ui-base-color, 8%);
padding-bottom: 0;
&.lighter {
background: $ui-base-color;
}
&.tag-body {
background: darken($ui-base-color, 8%);
padding-bottom: 0;
&.with-modals {
overflow-x: hidden;
overflow-y: scroll;
&--active {
overflow-y: hidden;
margin-right: 13px;
}
}
&.player {
@ -52,7 +57,7 @@ body {
}
&.embed {
background: transparent;
background: lighten($ui-base-color, 4%);
margin: 0;
padding-bottom: 0;

@ -946,6 +946,18 @@
background: lighten($ui-base-color, 4%);
padding: 14px 10px;
&--flex {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
.status__content,
.detailed-status__meta {
flex: 100%;
}
}
.status__content {
font-size: 19px;
line-height: 24px;
@ -1224,7 +1236,6 @@ a .account__avatar {
}
.account__action-bar-dropdown {
flex: 0 1 calc(50% - 140px);
padding: 10px;
.icon-button {
@ -1256,9 +1267,9 @@ a .account__avatar {
.account__action-bar__tab {
text-decoration: none;
overflow: hidden;
flex: 0 1 80px;
flex: 0 1 100%;
border-right: 1px solid lighten($ui-base-color, 8%);
padding: 10px 5px;
padding: 10px 0;
& > span {
display: block;

@ -60,10 +60,6 @@
}
}
.media-standalone__body {
overflow: hidden;
}
.account-header {
width: 400px;
margin: 0 auto;
@ -118,3 +114,576 @@
margin-left: 8px;
}
}
.public-layout {
@media screen and (max-width: $no-gap-breakpoint) {
padding-top: 48px;
}
.container {
max-width: 960px;
@media screen and (max-width: $no-gap-breakpoint) {
padding: 0;
}
}
.header {
background: lighten($ui-base-color, 8%);
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
border-radius: 4px;
height: 48px;
margin: 10px 0;
display: flex;
align-items: stretch;
justify-content: center;
flex-wrap: nowrap;
overflow: hidden;
@media screen and (max-width: $no-gap-breakpoint) {
position: fixed;
width: 100%;
top: 0;
left: 0;
margin: 0;
border-radius: 0;
box-shadow: none;
z-index: 110;
}
& > div {
flex: 1 1 33.3%;
min-height: 1px;
}
.nav-left {
display: flex;
align-items: stretch;
justify-content: flex-start;
flex-wrap: nowrap;
}
.nav-center {
display: flex;
align-items: stretch;
justify-content: center;
flex-wrap: nowrap;
}
.nav-right {
display: flex;
align-items: stretch;
justify-content: flex-end;
flex-wrap: nowrap;
}
.brand {
display: block;
padding: 15px;
img {
display: block;
height: 18px;
width: auto;
position: relative;
bottom: -2px;
@media screen and (max-width: $no-gap-breakpoint) {
height: 20px;
}
}
&:hover,
&:focus,
&:active {
background: lighten($ui-base-color, 12%);
}
}
.nav-link {
display: flex;
align-items: center;
padding: 0 1rem;
font-size: 12px;
font-weight: 500;
text-decoration: none;
color: $darker-text-color;
white-space: nowrap;
text-align: center;
&:hover,
&:focus,
&:active {
text-decoration: underline;
color: $primary-text-color;
}
}
.nav-button {
background: lighten($ui-base-color, 16%);
margin: 8px;
margin-left: 0;
border-radius: 4px;
&:hover,
&:focus,
&:active {
text-decoration: none;
background: lighten($ui-base-color, 20%);
}
}
}
$no-columns-breakpoint: 600px;
.grid {
display: grid;
grid-gap: 10px;
grid-template-columns: minmax(300px, 3fr) minmax(298px, 1fr);
grid-auto-columns: 25%;
grid-auto-rows: max-content;
.column-0 {
grid-row: 1;
grid-column: 1;
}
.column-1 {
grid-row: 1;
grid-column: 2;
}
@media screen and (max-width: $no-columns-breakpoint) {
grid-template-columns: 100%;
grid-gap: 0;
.column-1 {
display: none;
}
}
}
.public-account-header {
overflow: hidden;
margin-bottom: 10px;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
&__image {
border-radius: 4px 4px 0 0;
overflow: hidden;
height: 300px;
position: relative;
background: darken($ui-base-color, 12%);
&::after {
content: "";
display: block;
position: absolute;
width: 100%;
height: 100%;
box-shadow: inset 0 -1px 1px 1px rgba($base-shadow-color, 0.15);
top: 0;
left: 0;
}
img {
object-fit: cover;
display: block;
width: 100%;
height: 100%;
margin: 0;
border-radius: 4px 4px 0 0;
}
@media screen and (max-width: 600px) {
height: 200px;
}
}
@media screen and (max-width: $no-gap-breakpoint) {
margin-bottom: 0;
box-shadow: none;
&__image::after {
display: none;
}
&__image,
&__image img {
border-radius: 0;
}
}
&__bar {
position: relative;
margin-top: -80px;
display: flex;
justify-content: flex-start;
&::before {
content: "";
display: block;
background: lighten($ui-base-color, 4%);
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 60px;
border-radius: 0 0 4px 4px;
z-index: -1;
}
.avatar {
display: block;
width: 120px;
height: 120px;
padding-left: 20px - 4px;
flex: 0 0 auto;
img {
display: block;
width: 100%;
height: 100%;
margin: 0;
border-radius: 50%;
border: 4px solid lighten($ui-base-color, 4%);
background: darken($ui-base-color, 8%);
}
}
@media screen and (max-width: 600px) {
margin-top: 0;
background: lighten($ui-base-color, 4%);
border-radius: 0 0 4px 4px;
padding: 5px;
&::before {
display: none;
}
.avatar {
width: 48px;
height: 48px;
padding: 7px 0;
padding-left: 10px;
img {
border: 0;
border-radius: 4px;
}
@media screen and (max-width: 360px) {
display: none;
}
}
}
@media screen and (max-width: $no-gap-breakpoint) {
border-radius: 0;
}
@media screen and (max-width: $no-columns-breakpoint) {
flex-wrap: wrap;
}
}
&__tabs {
flex: 1 1 auto;
margin-left: 20px;
&__name {
padding-top: 20px;
padding-bottom: 8px;
h1 {
font-size: 20px;
line-height: 18px * 1.5;
color: $primary-text-color;
font-weight: 500;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-shadow: 1px 1px 1px $base-shadow-color;
small {
display: block;
font-size: 14px;
color: $primary-text-color;
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
@media screen and (max-width: 600px) {
margin-left: 15px;
display: flex;
justify-content: space-between;
align-items: center;
&__name {
padding-top: 0;
padding-bottom: 0;
h1 {
font-size: 16px;
line-height: 24px;
text-shadow: none;
small {
color: $darker-text-color;
}
}
}
}
&__tabs {
display: flex;
justify-content: flex-start;
align-items: stretch;
height: 58px;
.details-counters {
display: flex;
flex-direction: row;
min-width: 300px;
}
@media screen and (max-width: $no-columns-breakpoint) {
.details-counters {
display: none;
}
}
.counter {
width: 33.3%;
box-sizing: border-box;
flex: 0 0 auto;
color: $darker-text-color;
padding: 10px;
border-right: 1px solid lighten($ui-base-color, 4%);
cursor: default;
text-align: center;
position: relative;
a {
display: block;
}
&:last-child {
border-right: 0;
}
&::after {
display: block;
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
border-bottom: 4px solid $ui-primary-color;
opacity: 0.5;
transition: all 400ms ease;
}
&.active {
&::after {
border-bottom: 4px solid $highlight-text-color;
opacity: 1;
}
}
&:hover {
&::after {
opacity: 1;
transition-duration: 100ms;
}
}
a {
text-decoration: none;
color: inherit;
}
.counter-label {
font-size: 12px;
display: block;
}
.counter-number {
font-weight: 500;
font-size: 18px;
margin-bottom: 5px;
color: $primary-text-color;
font-family: 'mastodon-font-display', sans-serif;
}
}
.spacer {
flex: 1 1 auto;
height: 1px;
}
&__buttons {
padding: 7px 8px;
}
}
}
&__extra {
display: none;
margin-top: 4px;
.public-account-bio {
border-radius: 0;
box-shadow: none;
background: transparent;
margin: 0 -5px;
.account__header__fields {
border-top: 1px solid lighten($ui-base-color, 12%);
}
.roles {
display: none;
}
}
&__links {
margin-top: -15px;
font-size: 14px;
color: $darker-text-color;
a {
display: inline-block;
color: $darker-text-color;
text-decoration: none;
padding: 15px;
strong {
font-weight: 700;
color: $primary-text-color;
}
}
}
@media screen and (max-width: $no-columns-breakpoint) {
display: block;
flex: 100%;
}
}
}
.account__section-headline {
border-radius: 4px 4px 0 0;
@media screen and (max-width: $no-gap-breakpoint) {
border-radius: 0;
}
}
.detailed-status__meta {
margin-top: 25px;
}
.public-account-bio {
background: lighten($ui-base-color, 8%);
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
border-radius: 4px;
overflow: hidden;
margin-bottom: 10px;
@media screen and (max-width: $no-gap-breakpoint) {
box-shadow: none;
margin-bottom: 0;
border-radius: 0;
}
.account__header__fields {
margin: 0;
border-top: 0;
a {
color: lighten($ui-highlight-color, 8%);
}
}
.account__header__content {
padding: 20px;
padding-bottom: 0;
color: $primary-text-color;
}
&__extra,
.roles {
padding: 20px;
font-size: 14px;
color: $darker-text-color;
}
.roles {
padding-bottom: 0;
}
}
.static-icon-button {
color: $action-button-color;
font-size: 18px;
& > span {
font-size: 14px;
font-weight: 500;
}
}
.card-grid {
display: flex;
flex-wrap: wrap;
min-width: 100%;
margin: 0 -5px;
& > div {
box-sizing: border-box;
flex: 1 0 auto;
width: 300px;
padding: 0 5px;
margin-bottom: 10px;
max-width: 33.333%;
@media screen and (max-width: 900px) {
max-width: 50%;
}
@media screen and (max-width: 600px) {
max-width: 100%;
}
}
@media screen and (max-width: $no-gap-breakpoint) {
margin: 0;
border-top: 1px solid lighten($ui-base-color, 8%);
& > div {
width: 100%;
padding: 0;
margin-bottom: 0;
border-bottom: 1px solid lighten($ui-base-color, 8%);
&:last-child {
border-bottom: 0;
}
.card__bar {
background: $ui-base-color;
&:hover,
&:active,
&:focus {
background: lighten($ui-base-color, 4%);
}
}
}
}
}
}

@ -1,39 +1,140 @@
.footer {
text-align: center;
margin-top: 30px;
padding-bottom: 60px;
font-size: 12px;
color: $darker-text-color;
.footer__domain {
font-weight: 500;
a {
color: inherit;
text-decoration: none;
.public-layout {
.footer {
text-align: left;
padding-top: 20px;
padding-bottom: 60px;
font-size: 12px;
color: lighten($ui-base-color, 34%);
@media screen and (max-width: $no-gap-breakpoint) {
padding-left: 20px;
padding-right: 20px;
}
}
.powered-by,
.single-user-login {
font-weight: 400;
.grid {
display: grid;
grid-gap: 10px;
grid-template-columns: 1fr 1fr 2fr 1fr 1fr;
.column-0 {
grid-column: 1;
grid-row: 1;
min-width: 0;
}
.column-1 {
grid-column: 2;
grid-row: 1;
min-width: 0;
}
.column-2 {
grid-column: 3;
grid-row: 1;
min-width: 0;
text-align: center;
h4 a {
color: lighten($ui-base-color, 34%);
}
}
.column-3 {
grid-column: 4;
grid-row: 1;
min-width: 0;
}
.column-4 {
grid-column: 5;
grid-row: 1;
min-width: 0;
}
@media screen and (max-width: 690px) {
grid-template-columns: 1fr 2fr 1fr;
.column-0,
.column-1 {
grid-column: 1;
}
.column-1 {
grid-row: 2;
}
.column-2 {
grid-column: 2;
}
a {
color: inherit;
text-decoration: underline;
font-weight: 500;
.column-3,
.column-4 {
grid-column: 3;
}
&:hover {
.column-4 {
grid-row: 2;
}
}
@media screen and (max-width: 600px) {
.column-1 {
display: block;
}
}
@media screen and (max-width: $no-gap-breakpoint) {
.column-0,
.column-1,
.column-3,
.column-4 {
display: none;
}
}
}
h4 {
text-transform: uppercase;
font-weight: 700;
margin-bottom: 8px;
color: $darker-text-color;
a {
color: inherit;
text-decoration: none;
}
}
img {
margin: 0 4px;
position: relative;
bottom: -1px;
height: 18px;
vertical-align: top;
ul a {
text-decoration: none;
color: lighten($ui-base-color, 34%);
&:hover,
&:active,
&:focus {
text-decoration: underline;
}
}
.brand {
svg {
display: block;
height: 36px;
width: auto;
margin: 0 auto;
path {
fill: lighten($ui-base-color, 34%);
}
}
&:hover,
&:focus,
&:active {
svg path {
fill: lighten($ui-base-color, 38%);
}
}
}
}
}

@ -1,111 +0,0 @@
.landing-strip,
.memoriam-strip {
background: rgba(darken($ui-base-color, 7%), 0.8);
color: $darker-text-color;
font-weight: 400;
padding: 14px;
border-radius: 4px;
margin-bottom: 20px;
display: flex;
align-items: center;
strong,
a {
font-weight: 500;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
a {
color: inherit;
text-decoration: underline;
}
.logo {
width: 30px;
height: 30px;
flex: 0 0 auto;
margin-right: 15px;
}
@media screen and (max-width: 740px) {
margin-bottom: 0;
}
}
.memoriam-strip {
background: rgba($base-shadow-color, 0.7);
}
.moved-strip {
padding: 14px;
border-radius: 4px;
background: rgba(darken($ui-base-color, 7%), 0.8);
color: $secondary-text-color;
font-weight: 400;
margin-bottom: 20px;
strong,
a {
font-weight: 500;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
a {
color: inherit;
text-decoration: underline;
&.mention {
text-decoration: none;
span {
text-decoration: none;
}
&:focus,
&:hover,
&:active {
text-decoration: none;
span {
text-decoration: underline;
}
}
}
}
&__message {
margin-bottom: 15px;
.fa {
margin-right: 5px;
color: $darker-text-color;
}
}
&__card {
.detailed-status__display-avatar {
position: relative;
cursor: pointer;
}
.detailed-status__display-name {
margin-bottom: 0;
text-decoration: none;
span {
color: $highlight-text-color;
font-weight: 400;
}
}
}
}

@ -1,367 +1,145 @@
.activity-stream {
clear: both;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
border-radius: 4px;
overflow: hidden;
margin-bottom: 10px;
@media screen and (max-width: $no-gap-breakpoint) {
margin-bottom: 0;
border-radius: 0;
box-shadow: none;
}
&--headless {
border-radius: 0;
margin: 0;
box-shadow: none;
.detailed-status,
.status {
border-radius: 0 !important;
}
}
div[data-component] {
width: 100%;
}
.entry {
background: $simple-background-color;
background: $ui-base-color;
.detailed-status.light,
.status.light,
.more.light {
border-bottom: 1px solid $ui-secondary-color;
.detailed-status,
.status,
.load-more {
animation: none;
}
&:last-child {
&,
.detailed-status.light,
.status.light {
.detailed-status,
.status {
border-bottom: 0;
border-radius: 0 0 4px 4px;
}
}
&:first-child {
&,
.detailed-status.light,
.status.light {
.detailed-status,
.status {
border-radius: 4px 4px 0 0;
}
&:last-child {
&,
.detailed-status.light,
.status.light {
.detailed-status,
.status {
border-radius: 4px;
}
}
}
@media screen and (max-width: 740px) {
&,
.detailed-status.light,
.status.light {
.detailed-status,
.status {
border-radius: 0 !important;
}
}
}
}
&.with-header {
.entry {
&:first-child {
&,
.detailed-status.light,
.status.light {
border-radius: 0;
}
&:last-child {
&,
.detailed-status.light,
.status.light {
border-radius: 0 0 4px 4px;
}
}
}
}
}
.media-gallery__gifv__label {
bottom: 9px;
}
.status.light {
padding: 14px 14px 14px (48px + 14px * 2);
position: relative;
min-height: 48px;
cursor: default;
.status__header {
font-size: 15px;
.status__meta {
float: right;
font-size: 14px;
.status__relative-time {
color: $lighter-text-color;
}
}
}
.status__display-name {
display: block;
max-width: 100%;
padding-right: 25px;
color: $inverted-text-color;
}
.status__avatar {
position: absolute;
left: 14px;
top: 14px;
width: 48px;
height: 48px;
& > div {
width: 48px;
height: 48px;
}
img {
display: block;
border-radius: 4px;
}
}
.display-name {
display: block;
max-width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
strong {
font-weight: 500;
color: $inverted-text-color;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
span {
font-size: 14px;
color: $light-text-color;
}
}
.status__content {
color: $inverted-text-color;
a {
color: $highlight-text-color;
}
a.status__content__spoiler-link {
color: $primary-text-color;
background: $ui-base-color;
&:hover {
background: lighten($ui-base-color, 8%);
}
}
}
}
.detailed-status.light {
padding: 14px;
background: $simple-background-color;
cursor: default;
.detailed-status__display-name {
display: block;
overflow: hidden;
margin-bottom: 15px;
& > div {
float: left;
margin-right: 10px;
}
.display-name {
display: block;
max-width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
strong {
font-weight: 500;
color: $inverted-text-color;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
span {
font-size: 14px;
color: $light-text-color;
}
}
}
.avatar {
width: 48px;
height: 48px;
img {
display: block;
border-radius: 4px;
}
}
.status__content {
color: $inverted-text-color;
a {
color: $highlight-text-color;
}
a.status__content__spoiler-link {
color: $primary-text-color;
background: $ui-base-color;
&:hover {
background: lighten($ui-base-color, 8%);
}
}
}
.detailed-status__meta {
margin-top: 15px;
color: $light-text-color;
font-size: 14px;
line-height: 18px;
a {
color: inherit;
}
span > span {
font-weight: 500;
font-size: 12px;
margin-left: 6px;
display: inline-block;
}
}
.status-card {
border-color: lighten($ui-secondary-color, 4%);
color: $lighter-text-color;
&:hover {
background: lighten($ui-secondary-color, 4%);
}
}
.status-card__title,
.status-card__description {
color: $inverted-text-color;
}
.status-card__image {
background: $ui-secondary-color;
}
}
.media-spoiler {
background: $ui-base-color;
color: $darker-text-color;
}
.button.logo-button {
flex: 0 auto;
font-size: 14px;
background: $ui-highlight-color;
color: $primary-text-color;
text-transform: none;
line-height: 36px;
height: auto;
padding: 3px 15px;
border: 0;
.pre-header {
padding: 14px 0;
padding-left: (48px + 14px * 2);
padding-bottom: 0;
margin-bottom: -4px;
color: $light-text-color;
font-size: 14px;
position: relative;
svg {
width: 20px;
height: auto;
vertical-align: middle;
margin-right: 5px;
.pre-header__icon {
position: absolute;
left: (48px + 14px * 2 - 30px);
path:first-child {
fill: $primary-text-color;
}
.status__display-name.muted strong {
color: $light-text-color;
path:last-child {
fill: $ui-highlight-color;
}
}
.open-in-web-link {
text-decoration: none;
&:active,
&:focus,
&:hover {
background: lighten($ui-highlight-color, 10%);
&:hover {
text-decoration: underline;
svg path:last-child {
fill: lighten($ui-highlight-color, 10%);
}
}
.more {
color: $darker-text-color;
display: block;
padding: 14px;
text-align: center;
&:not(:hover) {
text-decoration: none;
@media screen and (max-width: $no-gap-breakpoint) {
svg {
display: none;
}
}
}
.embed {
.activity-stream {
box-shadow: none;
.embed,
.public-layout {
.detailed-status {
padding: 15px;
}
}
.entry {
.detailed-status.light {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
.status {
padding: 15px 15px 15px (48px + 15px * 2);
min-height: 48px + 2px;
.detailed-status__display-name {
flex: 1;
margin: 0 5px 15px 0;
&__avatar {
left: 15px;
top: 17px;
}
.button.button-secondary.logo-button {
flex: 0 auto;
font-size: 14px;
background: $ui-highlight-color;
color: $primary-text-color;
border: 0;
svg {
width: 20px;
height: auto;
vertical-align: middle;
margin-right: 5px;
path:first-child {
fill: $primary-text-color;
}
path:last-child {
fill: $ui-highlight-color;
}
}
&__content {
padding-top: 5px;
}
&:active,
&:focus,
&:hover {
background: lighten($ui-highlight-color, 10%);
&__prepend {
margin-left: 48px + 15px * 2;
padding-top: 15px;
}
svg path:last-child {
fill: lighten($ui-highlight-color, 10%);
}
}
&__prepend-icon-wrapper {
left: -32px;
}
.status__content,
.detailed-status__meta {
flex: 100%;
.media-gallery,
&__action-bar,
.video-player {
margin-top: 10px;
}
}
}

@ -46,3 +46,5 @@ $cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW;
$media-modal-media-max-width: 100%;
// put margins on top and bottom of image to avoid the screen covered by image.
$media-modal-media-max-height: 80%;
$no-gap-breakpoint: 415px;

@ -0,0 +1,161 @@
.hero-widget {
margin-bottom: 10px;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
&__img {
width: 100%;
height: 167px;
position: relative;
overflow: hidden;
border-radius: 4px 4px 0 0;
background: $base-shadow-color;
img {
object-fit: cover;
display: block;
width: 100%;
height: 100%;
margin: 0;
border-radius: 4px 4px 0 0;
}
}
&__text {
background: $ui-base-color;
padding: 20px;
border-radius: 0 0 4px 4px;
font-size: 15px;
color: $darker-text-color;
line-height: 20px;
word-wrap: break-word;
font-weight: 400;
.emojione {
width: 20px;
height: 20px;
margin: -3px 0 0;
}
p {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
em {
display: inline;
margin: 0;
padding: 0;
font-weight: 700;
background: transparent;
font-family: inherit;
font-size: inherit;
line-height: inherit;
color: lighten($darker-text-color, 10%);
}
a {
color: $secondary-text-color;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
@media screen and (max-width: $no-gap-breakpoint) {
display: none;
}
}
.moved-account-widget {
padding: 15px;
padding-bottom: 20px;
border-radius: 4px;
background: $ui-base-color;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
color: $secondary-text-color;
font-weight: 400;
margin-bottom: 10px;
strong,
a {
font-weight: 500;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
a {
color: inherit;
text-decoration: underline;
&.mention {
text-decoration: none;
span {
text-decoration: none;
}
&:focus,
&:hover,
&:active {
text-decoration: none;
span {
text-decoration: underline;
}
}
}
}
&__message {
margin-bottom: 15px;
.fa {
margin-right: 5px;
color: $darker-text-color;
}
}
&__card {
.detailed-status__display-avatar {
position: relative;
cursor: pointer;
}
.detailed-status__display-name {
margin-bottom: 0;
text-decoration: none;
span {
font-weight: 400;
}
}
}
}
.memoriam-widget {
padding: 20px;
border-radius: 4px;
background: $base-shadow-color;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
font-size: 14px;
color: $darker-text-color;
margin-bottom: 10px;
}
.moved-account-widget,
.memoriam-widget {
@media screen and (max-width: $no-gap-breakpoint) {
margin-bottom: 0;
box-shadow: none;
border-radius: 0;
}
}

@ -9,7 +9,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
{
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
'sensitive' => 'as:sensitive',
'movedTo' => 'as:movedTo',
'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' },
'Hashtag' => 'as:Hashtag',
'ostatus' => 'http://ostatus.org#',
'atomUri' => 'ostatus:atomUri',
@ -18,7 +18,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
'toot' => 'http://joinmastodon.org/ns#',
'Emoji' => 'toot:Emoji',
'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' },
'featured' => 'toot:featured',
'featured' => { '@id' => 'toot:featured', '@type' => '@id' },
'schema' => 'http://schema.org#',
'PropertyValue' => 'schema:PropertyValue',
'value' => 'schema:value',

@ -70,6 +70,7 @@ class Account < ApplicationRecord
# Remote user validations
validates :username, uniqueness: { scope: :domain, case_sensitive: true }, if: -> { !local? && will_save_change_to_username? }
validates :username, format: { with: /\A#{USERNAME_RE}\z/i }, if: -> { !local? && will_save_change_to_username? }
# Local user validations
validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? }

@ -5,11 +5,12 @@ module AccountHeader
IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
LIMIT = 2.megabytes
MAX_PIXELS = 750_000 # 1500x500px
class_methods do
def header_styles(file)
styles = { original: { geometry: '700x335#', file_geometry_parser: FastGeometryParser } }
styles[:static] = { geometry: '700x335#', format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif'
styles = { original: { pixels: MAX_PIXELS, file_geometry_parser: FastGeometryParser } }
styles[:static] = { format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif'
styles
end

@ -25,12 +25,13 @@ class MediaAttachment < ApplicationRecord
enum type: [:image, :gifv, :video, :audio, :unknown]
IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif'].freeze
VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v'].freeze
VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v', '.mov'].freeze
AUDIO_FILE_EXTENSIONS = ['.mp3', '.m4a', '.wav', '.ogg'].freeze
IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
VIDEO_MIME_TYPES = ['video/webm', 'video/mp4'].freeze
AUDIO_MIME_TYPES = ['audio/mpeg', 'audio/mp4', 'audio/vnd.wav', 'audio/wav', 'audio/x-wav', 'audio/x-wave', 'audio/ogg',].freeze
IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
VIDEO_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime'].freeze
VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze
AUDIO_MIME_TYPES = ['audio/mpeg', 'audio/mp4', 'audio/vnd.wav', 'audio/wav', 'audio/x-wav', 'audio/x-wave', 'audio/ogg',].freeze
IMAGE_STYLES = {
original: {
@ -72,7 +73,25 @@ class MediaAttachment < ApplicationRecord
},
}.freeze
LIMIT = 8.megabytes
VIDEO_FORMAT = {
format: 'mp4',
convert_options: {
output: {
'movflags' => 'faststart',
'pix_fmt' => 'yuv420p',
'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
'vsync' => 'cfr',
'c:v' => 'h264',
'b:v' => '500K',
'maxrate' => '1300K',
'bufsize' => '1300K',
'crf' => 18,
},
},
}.freeze
IMAGE_LIMIT = 8.megabytes
VIDEO_LIMIT = 40.megabytes
belongs_to :account, inverse_of: :media_attachments, optional: true
belongs_to :status, inverse_of: :media_attachments, optional: true
@ -82,11 +101,10 @@ class MediaAttachment < ApplicationRecord
processors: ->(f) { file_processors f },
convert_options: { all: '-quality 90 -strip' }
include Remotable
validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES
validates_attachment_size :file, less_than: LIMIT
remotable_attachment :file, LIMIT
validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :video?
validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :video?
remotable_attachment :file, VIDEO_LIMIT
include Attachmentable
@ -142,27 +160,17 @@ class MediaAttachment < ApplicationRecord
if f.instance.file_content_type == 'image/gif'
{
small: IMAGE_STYLES[:small],
original: {
format: 'mp4',
convert_options: {
output: {
'movflags' => 'faststart',
'pix_fmt' => 'yuv420p',
'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
'vsync' => 'cfr',
'c:v' => 'h264',
'b:v' => '500K',
'maxrate' => '1300K',
'bufsize' => '1300K',
'crf' => 18,
},
},
},
original: VIDEO_FORMAT,
}
elsif IMAGE_MIME_TYPES.include? f.instance.file_content_type
IMAGE_STYLES
elsif AUDIO_MIME_TYPES.include? f.instance.file_content_type
AUDIO_STYLES
elsif VIDEO_CONVERTIBLE_MIME_TYPES.include?(f.instance.file_content_type)
{
small: VIDEO_STYLES[:small],
original: VIDEO_FORMAT,
}
else
VIDEO_STYLES
end

@ -7,14 +7,14 @@ class ActivityPub::FetchRemoteAccountService < BaseService
# Should be called when uri has already been checked for locality
# Does a WebFinger roundtrip on each call
def call(uri, id: true, prefetched_body: nil)
def call(uri, id: true, prefetched_body: nil, break_on_redirect: false)
@json = if prefetched_body.nil?
fetch_resource(uri, id)
else
body_to_json(prefetched_body)
end
return unless supported_context? && expected_type?
return if !supported_context? || !expected_type? || (break_on_redirect && @json['movedTo'].present?)
@uri = @json['id']
@username = @json['preferredUsername']

@ -175,7 +175,7 @@ class ActivityPub::ProcessAccountService < BaseService
def moved_account
account = ActivityPub::TagManager.instance.uri_to_resource(@json['movedTo'], Account)
account ||= ActivityPub::FetchRemoteAccountService.new.call(@json['movedTo'], id: true)
account ||= ActivityPub::FetchRemoteAccountService.new.call(@json['movedTo'], id: true, break_on_redirect: true)
account
end

@ -0,0 +1,15 @@
.public-account-bio
- unless account.fields.empty?
.account__header__fields
- account.fields.each do |field|
%dl
%dt.emojify{ title: field.name }= field.name
%dd.emojify{ title: field.value }= Formatter.instance.format_field(account, field.value, custom_emojify: true)
= account_badge(account)
- if account.note.present?
.account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
.public-account-bio__extra
= t 'accounts.joined', date: l(account.created_at, format: :month)

@ -1,28 +0,0 @@
- relationships ||= nil
- unless account.memorial? || account.moved?
- if user_signed_in?
- requested = relationships ? relationships.requested[account.id].present? : current_account.requested?(account)
- following = relationships ? relationships.following[account.id].present? : current_account.following?(account)
- if user_signed_in? && current_account.id != account.id && !requested
.controls
- if following
= link_to (account.local? ? account_unfollow_path(account) : remote_unfollow_path(acct: account.acct)), data: { method: :post }, class: 'icon-button' do
= fa_icon 'user-times'
= t('accounts.unfollow')
- else
= link_to (account.local? ? account_follow_path(account) : authorize_follow_path(acct: account.acct)), data: { method: :post }, class: 'icon-button' do
= fa_icon 'user-plus'
= t('accounts.follow')
- elsif user_signed_in? && current_account.id == account.id
.controls
= link_to settings_profile_url, class: 'icon-button' do
= fa_icon 'pencil'
= t('settings.edit_profile')
- elsif !user_signed_in?
.controls
.remote-follow
= link_to (account.local? ? account_remote_follow_path(account) : "web+mastodon://follow?uri=#{account.uri}"), class: 'icon-button' do
= fa_icon 'user-plus'
= t('accounts.remote_follow')

@ -1,8 +0,0 @@
.accounts-grid{ class: accounts.empty? ? 'empty' : '' }
- if accounts.empty?
= image_tag asset_pack_path('elephant_ui_greeting.svg'), alt: '', role: 'presentational'
= render partial: 'accounts/nothing_here'
- else
= render partial: 'accounts/grid_card', collection: accounts, as: :account, cached: !user_signed_in?
= paginate follows

@ -1,3 +0,0 @@
.accounts-grid.empty
= image_tag asset_pack_path('elephant_ui_greeting.svg'), alt: '', role: 'presentational'
%p.nothing-here= t('accounts.network_hidden')

@ -1,12 +0,0 @@
.account-grid-card
.account-grid-card__header{ style: "background-image: url(#{account.header.url(:original)})" }
= render 'accounts/follow_button', account: account, relationships: @relationships
.account-grid-card__avatar
.avatar= image_tag account.avatar.url(:original)
.name
= link_to TagManager.instance.url_for(account) do
%span.display_name.emojify= display_name(account, custom_emojify: true)
%span.username
@#{account.local? ? account.local_username_and_domain : account.acct}
= fa_icon('lock') if account.locked?
.account__header__content.p-note.emojify= Formatter.instance.simplified_format(account)

@ -1,51 +1,43 @@
.card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" }
.card__illustration
= render 'accounts/follow_button', account: account
.avatar= image_tag account.avatar.url(:original), class: 'u-photo'
.public-account-header
.public-account-header__image
= image_tag account.header.url, class: 'parallax'
.public-account-header__bar
= link_to short_account_url(account), class: 'avatar' do
= image_tag account.avatar.url
.public-account-header__tabs
.public-account-header__tabs__name
%h1
= display_name(account)
%small
= acct(account)
= fa_icon('lock') if account.locked?
.public-account-header__tabs__tabs
.details-counters
.counter{ class: active_nav_class(short_account_url(account)) }
= link_to short_account_url(account), class: 'u-url u-uid' do
%span.counter-number= number_to_human account.statuses_count, strip_insignificant_zeros: true
%span.counter-label= t('accounts.posts')
.card__bio
%h1.name
%span.p-name.emojify= display_name(account, custom_emojify: true)
%small<
%span>< @#{account.local_username_and_domain}
= fa_icon('lock') if account.locked?
.counter{ class: active_nav_class(account_following_index_url(account)) }
= link_to account_following_index_url(account) do
%span.counter-number= number_to_human account.following_count, strip_insignificant_zeros: true
%span.counter-label= t('accounts.following')
- if account.bot?
.roles
.account-role.bot
= t 'accounts.roles.bot'
- elsif Setting.show_staff_badge
- if account.user_admin?
.roles
.account-role.admin
= t 'accounts.roles.admin'
- elsif account.user_moderator?
.roles
.account-role.moderator
= t 'accounts.roles.moderator'
.counter{ class: active_nav_class(account_followers_url(account)) }
= link_to account_followers_url(account) do
%span.counter-number= number_to_human account.followers_count, strip_insignificant_zeros: true
%span.counter-label= t('accounts.followers')
.spacer
.public-account-header__tabs__tabs__buttons
= account_action_button(account)
.bio
.account__header__content.p-note.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
.public-account-header__extra
= render 'accounts/bio', account: account
- unless account.fields.empty?
.account__header__fields
- account.fields.each do |field|
%dl
%dt.emojify{ title: field.name }= field.name
%dd.emojify{ title: field.value }= Formatter.instance.format_field(account, field.value, custom_emojify: true)
.details-counters
.counter{ class: active_nav_class(short_account_url(account)) }
= link_to short_account_url(account), class: 'u-url u-uid' do
%span.counter-number= number_to_human account.statuses_count, strip_insignificant_zeros: true
%span.counter-label= t('accounts.posts')
.counter{ class: active_nav_class(account_following_index_url(account)) }
.public-account-header__extra__links
= link_to account_following_index_url(account) do
%span.counter-number= number_to_human account.following_count, strip_insignificant_zeros: true
%span.counter-label= t('accounts.following')
.counter{ class: active_nav_class(account_followers_url(account)) }
%strong= number_to_human account.following_count, strip_insignificant_zeros: true
= t('accounts.following')
= link_to account_followers_url(account) do
%span.counter-number= number_to_human account.followers_count, strip_insignificant_zeros: true
%span.counter-label= t('accounts.followers')
%strong= number_to_human account.followers_count, strip_insignificant_zeros: true
= t('accounts.followers')

@ -1,11 +1,11 @@
- moved_to_account = account.moved_to_account
.moved-strip
.moved-strip__message
.moved-account-widget
.moved-account-widget__message
= fa_icon 'suitcase'
= t('accounts.moved_html', name: content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.acct)])), TagManager.instance.url_for(moved_to_account), class: 'mention'))
= t('accounts.moved_html', name: content_tag(:bdi, content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify)), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.acct)])), TagManager.instance.url_for(moved_to_account), class: 'mention'))
.moved-strip__card
.moved-account-widget__card
= link_to TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'noopener' do
.detailed-status__display-avatar
.account__avatar-overlay
@ -13,5 +13,6 @@
.account__avatar-overlay-overlay{ style: "background-image: url('#{account.avatar.url(:original)}')" }
%span.display-name
%strong.emojify= display_name(moved_to_account, custom_emojify: true)
%bdi
%strong.emojify= display_name(moved_to_account, custom_emojify: true)
%span @#{moved_to_account.acct}

@ -1 +0,0 @@
%p.nothing-here= t('accounts.nothing_here')

@ -20,36 +20,39 @@
= opengraph 'og:type', 'profile'
= render 'og', account: @account, url: short_account_url(@account, only_path: false)
- if @account.memorial?
.memoriam-strip= t('in_memoriam_html')
- elsif @account.moved?
= render partial: 'moved_strip', locals: { account: @account }
- elsif show_landing_strip?
= render partial: 'shared/landing_strip', locals: { account: @account }
.h-feed
%data.p-name{ value: "#{@account.username} on #{site_hostname}" }/
= render 'header', account: @account
.activity-stream-tabs
= active_link_to t('accounts.posts'), short_account_url(@account)
= active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account)
= active_link_to t('accounts.media'), short_account_media_url(@account)
- if @statuses.empty?
.accounts-grid
= render 'nothing_here'
- else
.activity-stream.with-header
- if params[:page].to_i.zero?
= render partial: 'stream_entries/status', collection: @pinned_statuses, as: :status, locals: { pinned: true }
= render partial: 'stream_entries/status', collection: @statuses, as: :status
- if @newer_url || @older_url
.pagination
- if @older_url
= link_to safe_join([fa_icon('chevron-left'), t('pagination.older')], ' '), @older_url, class: 'older', rel: 'next'
- if @newer_url
= link_to safe_join([t('pagination.newer'), fa_icon('chevron-right')], ' '), @newer_url, class: 'newer', rel: 'prev'
= render 'header', account: @account, with_bio: true
.grid
.column-0
.h-feed
%data.p-name{ value: "#{@account.username} on #{site_hostname}" }/
.account__section-headline
= active_link_to t('accounts.posts'), short_account_url(@account)
= active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account)
= active_link_to t('accounts.media'), short_account_media_url(@account)
- if @statuses.empty?
= nothing_here 'nothing-here--under-tabs'
- else
.activity-stream
- if params[:page].to_i.zero?
= render partial: 'stream_entries/status', collection: @pinned_statuses, as: :status, locals: { pinned: true }
- if @newer_url
.entry= link_to_more @newer_url
= render partial: 'stream_entries/status', collection: @statuses, as: :status
- if @older_url
.entry= link_to_more @older_url
.column-1
- if @account.memorial?
.memoriam-widget= t('in_memoriam_html')
- elsif @account.moved?
= render 'moved', account: @account
= render 'bio', account: @account
= render 'application/sidebar'

@ -0,0 +1,16 @@
- account_url = local_assigns[:admin] ? admin_account_path(account.id) : TagManager.instance.url_for(account)
.card.h-card
= link_to account_url, target: '_blank', rel: 'noopener' do
.card__img
= image_tag account.header.url, alt: ''
.card__bar
.avatar
= image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
.display-name
%bdi
%strong.emojify.p-name= display_name(account, custom_emojify: true)
%span
= acct(account)
= fa_icon('lock') if account.locked?

@ -0,0 +1,6 @@
.hero-widget
.hero-widget__img
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
.hero-widget__text
%p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)

@ -10,7 +10,7 @@
- if @invite.present? && @invite.autofollow?
.fields-group{ style: 'margin-bottom: 30px' }
%p.hint{ style: 'text-align: center' }= t('invites.invited_by')
= render 'authorize_follows/card', account: @invite.user.account
= render 'application/card', account: @invite.user.account
= f.simple_fields_for :account do |ff|
.input-with-append

@ -1,23 +0,0 @@
.account-card
.account-card__header{ style: "background-image: url(#{account.header.url(:original)})" }
.detailed-status__display-name
%div
= image_tag account.avatar.url(:original), alt: '', width: 48, height: 48, class: 'avatar'
%span.display-name
- account_url = local_assigns[:admin] ? admin_account_path(account.id) : TagManager.instance.url_for(account)
= link_to account_url, class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'noopener' do
%strong.emojify= display_name(account, custom_emojify: true)
%span @#{account.acct}
.counter
%span.counter-number= number_to_human account.statuses_count, strip_insignificant_zeros: true
%span.counter-label= t('accounts.posts')
.counter
%span.counter-number= number_to_human account.following_count, strip_insignificant_zeros: true
%span.counter-label= t('accounts.following')
.counter
%span.counter-number= number_to_human account.followers_count, strip_insignificant_zeros: true
%span.counter-label= t('accounts.followers')

@ -3,7 +3,7 @@
.form-container
.follow-prompt
= render 'card', account: @account
= render 'application/card', account: @account
- if current_account.following?(@account)
.flash-message

@ -8,6 +8,6 @@
- else
%h2= t('authorize_follow.following')
= render 'card', account: @account
= render 'application/card', account: @account
= render 'post_follow_actions'

@ -8,6 +8,11 @@
= render 'accounts/header', account: @account
- if @account.user_hides_network?
= render 'accounts/follow_grid_hidden'
.nothing-here= t('accounts.network_hidden')
- elsif @follows.empty?
= nothing_here
- else
= render 'accounts/follow_grid', follows: @follows, accounts: @follows.map(&:account)
.card-grid
= render partial: 'application/card', collection: @follows.map(&:account), as: :account
= paginate @follows

@ -8,6 +8,11 @@
= render 'accounts/header', account: @account
- if @account.user_hides_network?
= render 'accounts/follow_grid_hidden'
.nothing-here= t('accounts.network_hidden')
- elsif @follows.empty?
= nothing_here
- else
= render 'accounts/follow_grid', follows: @follows, accounts: @follows.map(&:target_account)
.card-grid
= render partial: 'application/card', collection: @follows.map(&:target_account), as: :account
= paginate @follows

@ -1,14 +1,47 @@
- content_for :content do
.container-alt= yield
.footer
- if !user_signed_in? && single_user_mode?
%span.single-user-login
= link_to t('auth.login'), new_user_session_path
&mdash;
%span.footer__domain= link_to site_hostname, about_path
- else
%span.footer__domain= link_to site_hostname, root_path
%span.powered-by
!= t('generic.powered_by', link: link_to('https://joinmastodon.org') { image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' })
.public-layout
.container
%nav.header
.nav-left
= link_to root_url, class: 'brand' do
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
.nav-center
.nav-right
- if user_signed_in?
= link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn'
- else
= link_to t('auth.login'), new_user_session_path, class: 'webapp-btn nav-link nav-button'
= link_to t('auth.register'), new_user_registration_path, class: 'webapp-btn nav-link nav-button'
.container= yield
.container
.footer
.grid
.column-0
%h4= t 'footer.resources'
%ul
%li= link_to t('about.terms'), terms_path
%li= link_to t('about.privacy_policy'), terms_path
.column-1
%h4= t 'footer.developers'
%ul
%li= link_to t('about.documentation'), 'https://github.com/tootsuite/documentation'
%li= link_to t('about.api'), 'https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md'
.column-2
%h4= link_to t('about.what_is_mastodon'), 'https://joinmastodon.org/'
= link_to root_url, class: 'brand' do
= render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg')
.column-3
%h4= site_hostname
%ul
%li= link_to t('about.about_this'), about_more_path
%li= "v#{Mastodon::Version.to_s}"
.column-4
%h4= t 'footer.more'
%ul
%li= link_to t('about.source_code'), Mastodon::Version.source_url
%li= link_to 'joinmastodon.org', 'https://joinmastodon.org'
= render template: 'layouts/application'

@ -6,7 +6,7 @@
.follow-prompt
%h2= t('remote_follow.prompt')
= render partial: 'authorize_follows/card', locals: { account: @account }
= render partial: 'application/card', locals: { account: @account }
= simple_form_for @remote_follow, as: :remote_follow, url: account_remote_follow_path(@account) do |f|
= render 'shared/error_messages', object: @remote_follow

@ -5,6 +5,6 @@
.follow-prompt
%h2= t('remote_unfollow.unfollowed')
= render 'card', account: @account
= render 'application/card', account: @account
= render 'post_follow_actions'

@ -6,7 +6,7 @@
%p.hint= t('migrations.currently_redirecting')
.fields-group
= render partial: 'authorize_follows/card', locals: { account: @migration.account }
= render partial: 'application/card', locals: { account: @migration.account }
= render 'shared/error_messages', object: @migration

@ -8,13 +8,12 @@
= f.input :display_name, placeholder: t('simple_form.labels.defaults.display_name'), hint: t('simple_form.hints.defaults.display_name', count: 30 - @account.display_name.size).html_safe
= f.input :note, placeholder: t('simple_form.labels.defaults.note'), hint: t('simple_form.hints.defaults.note', count: 500 - @account.note.size).html_safe
.card.compact{ style: "background-image: url(#{@account.header.url(:original)})", data: { original_src: @account.header.url(:original) } }
.avatar= image_tag @account.avatar.url(:original), data: { original_src: @account.avatar.url(:original) }
= render 'application/card', account: @account
.fields-group
= f.input :avatar, wrapper: :with_label, input_html: { accept: AccountAvatar::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.avatar')
= f.input :avatar, wrapper: :with_label, input_html: { accept: AccountAvatar::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(AccountAvatar::LIMIT))
= f.input :header, wrapper: :with_label, input_html: { accept: AccountHeader::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.header')
= f.input :header, wrapper: :with_label, input_html: { accept: AccountHeader::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.header', dimensions: '1500x500', size: number_to_human_size(AccountHeader::LIMIT))
.fields-group
= f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')

@ -1,6 +0,0 @@
.landing-strip
= image_tag asset_pack_path('logo.svg'), class: 'logo'
%div
= t('landing_strip_html', name: content_tag(:span, display_name(account, custom_emojify: true), class: :emojify), link_to_root_path: link_to(content_tag(:strong, site_hostname), root_path))
= t('landing_strip_signup_html', sign_up_path: open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started')

@ -1,7 +0,0 @@
.media-spoiler-wrapper{ class: sensitive == false && 'media-spoiler-wrapper__visible' }><
.spoiler-button
.icon-button.overlayed
%i.fa.fa-fw.fa-eye
.media-spoiler
%span= t('stream_entries.sensitive_content')
%span= t('stream_entries.click_to_show')

@ -1,16 +1,15 @@
.detailed-status.light
.detailed-status.detailed-status--flex
= link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name p-author h-card', target: stream_link_target, rel: 'noopener' do
%div
.avatar
= image_tag status.account.avatar.url(:original), width: 48, height: 48, alt: '', class: 'u-photo'
.detailed-status__display-avatar
= image_tag status.account.avatar.url(:original), width: 48, height: 48, alt: '', class: 'account__avatar u-photo'
%span.display-name
%strong.p-name.emojify= display_name(status.account, custom_emojify: true)
%span= acct(status.account)
%bdi
%strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true)
%span.display-name__account
= acct(status.account)
= fa_icon('lock') if status.account.locked?
- if !user_signed_in? || embedded_view?
= link_to account_remote_follow_path(status.account), class: 'button button-secondary logo-button', target: '_new' do
= render file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')
= t('accounts.follow')
= account_action_button(status.account)
.status__content.emojify<
- if status.spoiler_text?
@ -30,6 +29,7 @@
.detailed-status__meta
%data.dt-published{ value: status.created_at.to_time.iso8601 }
= link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
·
@ -40,20 +40,20 @@
= link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener'
·
- if status.direct_visibility?
%span<
%span.detailed-status__link<
= fa_icon('envelope')
- elsif status.private_visibility?
%span<
%span.detailed-status__link<
= fa_icon('lock')
- else
%span<
%span.detailed-status__link<
= fa_icon('retweet')
%span= status.reblogs_count
%span.detailed-status__reblogs= number_to_human status.reblogs_count, strip_insignificant_zeros: true
·
%span<
%span.detailed-status__link<
= fa_icon('star')
%span= status.favourites_count
%span.detailed-status__favorites= number_to_human status.favourites_count, strip_insignificant_zeros: true
- if user_signed_in?
·
= link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'open-in-web-link', target: '_blank'
= link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'detailed-status__application', target: '_blank'

@ -1,4 +0,0 @@
.media-item><
= link_to media.remote_url.blank? ? media.file.url(:original) : media.remote_url, style: media.image? ? "background-image: url(#{media.file.url(:original)})" : '', target: '_blank', rel: 'noopener', class: "u-#{media.video? || media.gifv? ? 'video' : 'photo'}" do
- unless media.image?
%video{ src: media.file.url(:original), autoplay: true, loop: true }/

@ -1,2 +0,0 @@
= link_to url, class: 'more light' do
= t('statuses.show_more')

@ -1,18 +1,19 @@
.status.light
.status__header
.status__meta
= link_to TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', target: stream_link_target, rel: 'noopener' do
%time.time-ago{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
%data.dt-published{ value: status.created_at.to_time.iso8601 }
.status
.status__info
= link_to TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', target: stream_link_target, rel: 'noopener' do
%time.time-ago{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
%data.dt-published{ value: status.created_at.to_time.iso8601 }
= link_to TagManager.instance.url_for(status.account), class: 'status__display-name p-author h-card', target: stream_link_target, rel: 'noopener' do
.status__avatar
%div
= image_tag status.account.avatar(:original), width: 48, height: 48, alt: '', class: 'u-photo'
= image_tag status.account.avatar(:original), width: 48, height: 48, alt: '', class: 'u-photo account__avatar'
%span.display-name
%strong.p-name.emojify= display_name(status.account, custom_emojify: true)
%span= acct(status.account)
%bdi
%strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true)
%span.display-name__account
= acct(status.account)
= fa_icon('lock') if status.account.locked?
.status__content.emojify<
- if status.spoiler_text?
%p{ style: 'margin-bottom: 0' }<
@ -27,3 +28,16 @@
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true
- else
= react_component :media_gallery, height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
.status__action-bar
.status__action-bar-button.static-icon-button<
- if status.public_visibility? || status.unlisted_visibility?
= fa_icon 'retweet fw'
%span.detailed-status__reblogs= number_to_human status.reblogs_count, strip_insignificant_zeros: true
- elsif status.private_visibility?
= fa_icon 'lock fw'
- else
= fa_icon 'envelope fw'
.status__action-bar-button.static-icon-button<
= fa_icon 'star fw'
%span.detailed-status__favorites= number_to_human status.favourites_count, strip_insignificant_zeros: true

@ -16,24 +16,25 @@
- if status.reply? && include_threads
- if @next_ancestor
.entry{ class: entry_classes }
= render 'stream_entries/more', url: TagManager.instance.url_for(@next_ancestor)
= link_to_more TagManager.instance.url_for(@next_ancestor)
= render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id }
.entry{ class: entry_classes }
- if status.reblog?
.pre-header
.pre-header__icon
= fa_icon('retweet fw')
.status__prepend
.status__prepend-icon-wrapper
%i.status__prepend-icon.fa.fa-fw.fa-retweet
%span
= link_to TagManager.instance.url_for(status.account), class: 'status__display-name muted' do
%strong.emojify= display_name(status.account, custom_emojify: true)
%bdi
%strong.emojify= display_name(status.account, custom_emojify: true)
= t('stream_entries.reblogged')
- elsif pinned
.pre-header
.pre-header__icon
= fa_icon('thumb-tack fw')
.status__prepend
.status__prepend-icon-wrapper
%i.status__prepend-icon.fa.fa-fw.fa-thumb-tack
%span
= t('stream_entries.pinned')
@ -42,13 +43,13 @@
- if include_threads
- if @since_descendant_thread_id
.entry{ class: entry_classes }
= render 'stream_entries/more', url: short_account_status_url(status.account.username, status, max_descendant_thread_id: @since_descendant_thread_id + 1)
= link_to_more short_account_status_url(status.account.username, status, max_descendant_thread_id: @since_descendant_thread_id + 1)
- @descendant_threads.each do |thread|
= render partial: 'stream_entries/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id }
- if thread[:next_status]
.entry{ class: entry_classes }
= render 'stream_entries/more', url: TagManager.instance.url_for(thread[:next_status])
= link_to_more TagManager.instance.url_for(thread[:next_status])
- if @next_descendant_thread
.entry{ class: entry_classes }
= render 'stream_entries/more', url: short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1)
= link_to_more short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1)

@ -1,3 +1,3 @@
- cache @stream_entry.activity do
.activity-stream.activity-stream-headless
.activity-stream.activity-stream--headless
= render "stream_entries/#{@type}", @type.to_sym => @stream_entry.activity, centered: true

@ -17,8 +17,9 @@
= render 'stream_entries/og_description', activity: @stream_entry.activity
= render 'stream_entries/og_image', activity: @stream_entry.activity, account: @account
- if show_landing_strip?
= render partial: 'shared/landing_strip', locals: { account: @stream_entry.account }
.activity-stream.activity-stream-headless.h-entry
= render partial: "stream_entries/#{@type}", locals: { @type.to_sym => @stream_entry.activity, include_threads: true }
.grid
.column-0
.activity-stream.activity-stream-headless.h-entry
= render partial: "stream_entries/#{@type}", locals: { @type.to_sym => @stream_entry.activity, include_threads: true }
.column-1
= render 'application/sidebar'

@ -46,7 +46,6 @@ ar:
people_who_follow: الأشخاص الذين يتبعون %{name}
posts: منشورات
posts_with_replies: التبويقات و الردود
remote_follow: إتبع عن بعد
reserved_username: إسم المستخدم محجوز
roles:
admin: المدير
@ -516,7 +515,6 @@ ar:
unlocked_warning_title: إنّ حسابك غير مقفل
generic:
changes_saved_msg: تم حفظ التعديلات بنجاح !
powered_by: مدعوم بـ %{link}
save_changes: حفظ التغييرات
validation_errors:
one: لا يزال هناك خلل ما إلى حد الآن. يُرجى إعادة النظر في الخطأ أسفله
@ -551,8 +549,6 @@ ar:
expires_at: تنتهي مدة صلاحيتها في
uses: يستخدِم
title: دعوة أشخاص
landing_strip_html: "<strong>%{name}</strong> هو أحد مُستخدِمي %{link_to_root_path}. بإمكانك متابعته أو التواصل معه إن كنت تملك حسابًا أيا كان على البيئة الموحَّدة فيديفرس."
landing_strip_signup_html: إن كنت لا تملك واحدا، يمكنك <a href="%{sign_up_path}">التسجيل مِن هنا</a>.
lists:
errors:
limit: لقد بلغت الحد الأقصى للقوائم
@ -720,7 +716,6 @@ ar:
unlisted: غير مُدرَج
unlisted_long: يُمكن لأيٍ كان رُؤيتَه و لكن لن يُعرَض على الخيوط العامة
stream_entries:
click_to_show: إضغط للعرض
pinned: تبويق مثبّت
reblogged: رقى
sensitive_content: محتوى حساس

@ -96,8 +96,6 @@ ast:
followers:
domain: Dominiu
followers_count: Númberu de siguidores
generic:
powered_by: cola potencia de %{link}
invites:
expires_in:
'1800': 30 minutos
@ -183,7 +181,6 @@ ast:
other: "%{count} vídeos"
title: "%{name}: «%{quote}»"
stream_entries:
click_to_show: Primi p'amosar
sensitive_content: Conteníu sensible
themes:
default: Mastodon

@ -22,7 +22,6 @@ bg:
people_followed_by: Хора, които %{name} следва
people_who_follow: Хора, които следват %{name}
posts: Публикации
remote_follow: Последвай
unfollow: Не следвай
application_mailer:
settings: 'Промяна на предпочитанията за e-mail: %{link}'
@ -64,7 +63,6 @@ bg:
storage: Съхранение на мултимедия
generic:
changes_saved_msg: Успешно запазване на промените!
powered_by: поддържано от %{link}
save_changes: Запази промените
validation_errors:
one: Нещо все още не е наред! Моля, прегледай грешката по-долу
@ -76,8 +74,6 @@ bg:
blocking: Списък на блокираните
following: Списък на последователите
upload: Качване
landing_strip_html: "<strong>%{name}</strong> е потребител от %{link_to_root_path}. Можеш да ги следваш, или да контактуваш с тях, ако имаш акаунт където и да е из федерираната вселена на Mastodon."
landing_strip_signup_html: Ако нямаш акаунт, можеш да си <a href="%{sign_up_path}">създадеш ето тук</a>.
media_attachments:
validations:
images_and_video: Не мога да прикача видеоклип към публикация, която вече съдържа изображения
@ -144,7 +140,6 @@ bg:
public: Публично
unlisted: Публично, но не показвай в публичния канал
stream_entries:
click_to_show: Покажи
reblogged: споделено
sensitive_content: Деликатно съдържание
time:

@ -46,7 +46,6 @@ ca:
people_who_follow: Usuaris que segueixen %{name}
posts: Toots
posts_with_replies: Toots i respostes
remote_follow: Seguiment remot
reserved_username: El nom d'usuari està reservat
roles:
admin: Administrador
@ -504,7 +503,6 @@ ca:
unlocked_warning_title: El teu compte no està blocat
generic:
changes_saved_msg: Els canvis s'han desat correctament!
powered_by: amb tecnologia %{link}
save_changes: Desa els canvis
validation_errors:
one: Alguna cosa no va bé! Si us plau, revisa l'error
@ -540,8 +538,6 @@ ca:
expires_at: Caduca
uses: Usos
title: Convida persones
landing_strip_html: "<strong>%{name}</strong> és un usuari/a de %{link_to_root_path}. Pots seguir-lo/la o interactuar amb ell/a si tens un compte a qualsevol node del fediverse."
landing_strip_signup_html: Si no en tens, pots <a href="%{sign_up_path}">registrar-te aquí</a>.
lists:
errors:
limit: Has assolit la quantitat màxima de llistes
@ -710,7 +706,6 @@ ca:
unlisted: No llistat
unlisted_long: Tothom ho pot veure, però no es mostra en la història federada
stream_entries:
click_to_show: Clic per mostrar
pinned: Toot fixat
reblogged: ha impulsat
sensitive_content: Contingut sensible

@ -46,7 +46,6 @@ co:
people_who_follow: Seguitanu %{name}
posts: Statuti
posts_with_replies: Statuti è risposte
remote_follow: Siguità daltrò
reserved_username: Stu cugnome hè riservatu
roles:
admin: Amministratore
@ -488,7 +487,6 @@ co:
unlocked_warning_title: U vostru contu hè pubblicu
generic:
changes_saved_msg: Cambiamenti salvati!
powered_by: mossu da %{link}
save_changes: Salvà e mudificazione
validation_errors:
one: Qualcosa ùn và bè! Verificate u prublemu quì sottu
@ -523,8 +521,6 @@ co:
expires_at: Spira
uses: Utiliza
title: Invità ghjente
landing_strip_html: "<strong>%{name}</strong> hè nantà %{link_to_root_path}. Pudete seguitallu·a o cumunicà cù ellu·a cù un contu in qualche parte di u fediverse."
landing_strip_signup_html: Pudete ancu <a href="%{sign_up_path}">arrigistravi quì</a>.
lists:
errors:
limit: Ùn pudete più creà altre liste
@ -691,7 +687,6 @@ co:
unlisted: Micca listatu
unlisted_long: Tuttu u mondu pò vede, mà micca indè e linee pubbliche
stream_entries:
click_to_show: Cliccà per vede
pinned: Statutu puntarulatu
reblogged: spartutu
sensitive_content: Cuntenutu sensibile

@ -1,7 +1,7 @@
---
cs:
about:
about_hashtag_html: Toto jsou veřejné tooty označené jako <strong>#%{hashtag}</strong>. Pokud máte účet kdekoliv na fediverse, můžete s nimi interagovat.
about_hashtag_html: Toto jsou veřejné tooty označené hashtagem<strong>#%{hashtag}</strong>. Pokud máte účet kdekoliv na fediverse, můžete s nimi interagovat.
about_mastodon_html: Mastodon je sociální síť založená na otevřených webových protokolech a svobodném, otevřeném softwaru. Je decentrovalizovaná jako e-mail.
about_this: O této instanci
administered_by: 'Server spravuje:'
@ -18,14 +18,21 @@ cs:
features:
humane_approach_body: Mastodon, poučen z chyb jiných sociálních sítí, se snaží bojovat se zneužíváním sociálních sítí vytvářením etických možností.
humane_approach_title: Lidštější přístup
not_a_product_body: Mastodon není komerční síť. Žádné reklamy, žádné dolování dat, žádné hranice. Žádná centrální autorita.
not_a_product_title: Jste osoba, ne produkt
real_conversation_body: S 500 znaky k vaší dispozici a podporou pro varování o obsahu a médiích se můžete vyjadřovat tak, jak chcete.
real_conversation_title: Vytvořen pro opravdovou konverzaci
within_reach_body: Několik aplikací pro iOS, Android a jiné platformy vám díky jednoduchému API ekosystému dovolují držet krok s vašimi přáteli, ať už jste kdekoliv.
within_reach_title: Vždy v dosahu
generic_description: "%{domain} je jedním serverem v síti"
hosted_on: Mastodon hostovaný na %{domain}
learn_more: Zjistit více
other_instances: Seznam instancí
source_code: Zdrojový kód
status_count_after: příspěvků
status_count_before: Kdo je autorem
status_count_before: Kteří napsali
user_count_after: uživatelů
user_count_before: Domov pro
user_count_before: Domov
what_is_mastodon: Co je Mastodon?
accounts:
follow: Sledovat
@ -35,10 +42,836 @@ cs:
moved_html: 'Účet %{name} byl přesunut na %{new_profile_link}:'
network_hidden: Tato informace není k dispozici
nothing_here: Tady nic není!
people_followed_by: Lidé, které %{name} sleduje
people_who_follow: Lidé, kteří sledují uživatele %{name}
posts: Tooty
posts_with_replies: Tooty s odpověďmi
reserved_username: Toto uživatelské jméno je rezervováno
roles:
admin: Administrátor
bot: Robot
moderator: Moderátor
unfollow: Přestat sledovat
admin:
account_moderation_notes:
create: Zanechat poznámku
created_msg: Poznámka moderátora byla úspěšně vytvořena!
delete: Smazat
destroyed_msg: Poznámka moderátora byla úspěšně zničena!
accounts:
are_you_sure: Jste si jistý/á?
avatar: Avatar
by_domain: Doména
change_email:
changed_msg: E-mail k tomuto účtu byl úspěšně změněn!
current_email: Současný e-mail
label: Změnit e-mail
new_email: Nový e-mail
submit: Změnit e-mail
title: Změnit e-mail pro uživatele %{username}
confirm: Potvrdit
confirmed: Potvrzeno
confirming: Potvrzující
demote: Degradovat
disable: Zablokovat
disable_two_factor_authentication: Zakázat 2FA
disabled: Blokováno
display_name: Zobrazované jméno
domain: Doména
edit: Upravit
email: E-mail
email_status: Stav e-mailu
enable: Povolit
enabled: Povoleno
feed_url: URL proudu
followers: Sledovatelé
followers_url: URL sledovatelů
follows: Sleduje
inbox_url: URL přijatých zpráv
ip: IP
location:
all: Vše
local: Místní
remote: Vzdálené
title: Umístění
login_status: Stav přihlášení
media_attachments: Mediální přílohy
memorialize: Změnit na "in memoriam"
moderation:
all: Vše
silenced: Utišen
suspended: Suspendován
title: Moderace
moderation_notes: Moderační poznámky
most_recent_activity: Nejnovější aktivita
most_recent_ip: Nejnovější IP
not_subscribed: Neodebírá
order:
alphabetic: Abecedně
most_recent: Nejnovější
title: Pořadí
outbox_url: URL odchozích zpráv
perform_full_suspension: Provést plnou suspenzaci
profile_url: URL profilu
promote: Povýšit
protocol: Protokol
public: Veřejný
push_subscription_expires: Odebírání PuSH expiruje
redownload: Obnovit avatar
remove_avatar: Odstranit avatar
resend_confirmation:
already_confirmed: Tento uživatel je již potvrzen
send: Znovu odeslat potvrzovací e-mail
success: Potvrzovací e-mail byl úspěšně odeslán!
reset: Resetovat
reset_password: Obnovit heslo
resubscribe: Znovu odebírat
role: Oprávnění
roles:
admin: Administrátor
moderator: Moderátor
staff: Personál
user: Uživatel
salmon_url: URL Salmon
search: Hledat
shared_inbox_url: URL sdílené schránky
show:
created_reports: Nahlášení vytvořené z tohoto účtu
report: nahlášení
targeted_reports: Nahlášení vytvořena o tomto účtu
silence: Utišit
statuses: Statusy
subscribe: Odebírat
title: Účty
unconfirmed_email: Nepotvrzený e-mail
undo_silenced: Zrušit utišení
undo_suspension: Zrušit suspenzaci
unsubscribe: Přestat odebírat
username: Uživatelské jméno
web: Web
action_logs:
actions:
assigned_to_self_report: "%{name} přidělil/a hlášení %{target} sobě"
change_email_user: "%{name} změnil/a e-mailovou adresu uživatele %{target}"
confirm_user: "%{name} potvrdil/a e-mailovou adresu uživatele %{target}"
create_custom_emoji: "%{name} nahrál/a nové emoji %{target}"
create_domain_block: "%{name} zablokoval/a doménu %{target}"
create_email_domain_block: "%{name} přidal/a e-mailovou doménu %{target} na černou listinu"
demote_user: "%{name} degradoval/a uživatele %{target}"
destroy_domain_block: "%{name} odblokoval/a doménu %{target}"
destroy_email_domain_block: "%{name} odebral/a e-mailovou doménu %{target} z černé listiny"
destroy_status: "%{name} odstranil/a status uživatele %{target}"
disable_2fa_user: "%{name} vypnul/a požadavek pro dvoufaktorovou autentikaci pro uživatele %{target}"
disable_custom_emoji: "%{name} zakázal/a emoji %{target}"
disable_user: "%{name} zakázal/a přihlašování pro uživatele %{target}"
enable_custom_emoji: "%{name} povolil/a emoji %{target}"
enable_user: "%{name} povolil/a přihlašování pro uživatele %{target}"
memorialize_account: '%{name} změnil/a účet %{target} na stránku "in memoriam"'
promote_user: "%{name} povýšil/a uživatele %{target}"
remove_avatar_user: "%{name} odstranil/a avatar uživatele %{target}"
reopen_report: "%{name} znovuotevřel/a nahlášení %{target}"
reset_password_user: "%{name} resetoval/a heslo uživatele %{target}"
resolve_report: "%{name} vyřešil/a nahlášení %{target}"
silence_account: "%{name} utišil/a účet uživatele %{target}"
suspend_account: "%{name} suspendoval/a účet uživatele %{target}"
unassigned_report: "%{name} odebral/a nahlášení %{target}"
unsilence_account: "%{name} odtišil/a účet uživatele %{target}"
unsuspend_account: "%{name} zrušil/a suspenzaci účtu uživatele %{target}"
update_custom_emoji: "%{name} aktualizoval/a emoji %{target}"
update_status: "%{name} aktualizoval/a status uživatele %{target}"
title: Záznam auditu
custom_emojis:
by_domain: Doména
copied_msg: Místní kopie emoji byla úspěšně vytvořena
copy: Kopírovat
copy_failed_msg: Nebylo možné vytvořit místní kopii tohoto emoji
created_msg: Emoji úspěšně vytvořeno!
delete: Smazat
destroyed_msg: Emoji úspěšně zničeno!
disable: Zakázat
disabled_msg: Emoji bylo úspěšně zakázáno
emoji: Emoji
enable: Povolit
enabled_msg: Emoji bylo úspěšně povoleno
image_hint: PNG až do 50KB
listed: Uvedené
new:
title: Přidat nové vlastní emoji
overwrite: Přepsat
shortcode: Zkratka
shortcode_hint: Alespoň 2 znaky, pouze alfanumerické znaky a podtržítka
title: Vlastní emoji
unlisted: Neuvedené
update_failed_msg: Nebylo možné aktualizovat toto emoji
updated_msg: Emoji úspěšně aktualizováno!
upload: Nahrát
dashboard:
backlog: opožděné úlohy
config: Konfigurace
feature_deletions: Smazání účtů
feature_invites: Pozvánky
feature_registrations: Registrace
feature_relay: Federovací most
features: Vlastnosti
hidden_service: Federace se skrytými službami
open_reports: otevřená hlášení
recent_users: Nedávní uživatelé
search: Fulltextové vyhledávání
single_user_mode: Režim jednoho uživatele
software: Software
space: Využití prostoru
title: Přehled
total_users: uživatelů celkem
trends: Trendy
week_interactions: interakcí tento týden
week_users_active: aktivních tento týden
week_users_new: uživatelů tento týden
domain_blocks:
add_new: Přidat nové
created_msg: Blokace domény se právě vyřizuje
destroyed_msg: Blokace domény byla zrušena
domain: Doména
new:
create: Vytvořit blokaci
hint: Blokace domény nezakáže vytváření účtových záznamů v databázi, ale bude na tyto účty zpětně a automaticky aplikovat specifické metody moderace.
severity:
desc_html: Funkce <strong>Utišit</strong> zneviditelní příspěvky z účtu komukoliv, kdo jej nesleduje. Funkce <strong>Suspendovat</strong> odstraní všechen obsah, média a profilová data účtu. Pro pouhé odmítnutí mediálních souborů použijte funkci <strong>Žádné</strong>.
noop: Žádné
silence: Utišit
suspend: Suspendovat
title: Nová doménová blokace
reject_media: Odmítat mediální soubory
reject_media_hint: Odstraní lokálně uložené soubory a odmítne jejich stažení v budoucnosti. Irelevantní pro suspenzace
severities:
noop: Žádné
silence: Utišit
suspend: Suspendovat
severity: Přísnost
show:
affected_accounts:
one: Jeden účet v databázi byl ovlivněn
other: "%{count} účtů v databázi byl ovlivněn"
retroactive:
silence: Odtišit všechny existující účty z této domény
suspend: Zrušit suspenzaci všech existujících účtů z této domény
title: Zrušit blokaci domény %{domain}
undo: Odvolat
title: Doménové blokace
undo: Odvolat
email_domain_blocks:
add_new: Přidat nový
created_msg: E-mailová doména úspěšně přidána na černou listinu
delete: Smazat
destroyed_msg: E-mailová doména úspěšně odstraněna z černé listiny
domain: Doména
new:
create: Přidat doménu
title: Nový e-mail pro zablokování
title: Černá listina e-mailů
instances:
account_count: Známé účty
domain_name: Doména
reset: Resetovat
search: Hledat
title: Známé instance
invites:
filter:
all: Vše
available: Dostupné
expired: Vypršelé
title: Filtrovat
title: Pozvánky
relays:
add_new: Přidat nový most
description_html: "<strong>Federovací most</strong> je přechodný server, který vyměňuje velká množství veřejných tootů mezi servery, které z něj odebírají a poblikují na něj. <strong>Může pomoci malým a středně velkým serverům objevovat obsah z fediverse</strong>, což by jinak vyžadovalo, aby místní uživatelé manuálně sledovali jiné lidi na vzdálených serverech."
enable_hint: Je-li tohle povoleno, začne váš server odebírat všechny veřejné tooty z tohoto mostu a odesílat na něj své vlastní veřejné tooty.
inbox_url: URL mostu
setup: Nastavit připojení k mostu
status: Stav
title: Mosty
report_notes:
created_msg: Poznámka o nahlášení úspěšně vytvořena!
destroyed_msg: Poznámka o nahlášení úspěšně smazána!
reports:
account:
note: poznámka
report: nahlášení
action_taken_by: Akci vykonal/a
are_you_sure: Jste si jistý/á?
assign_to_self: Přidělit ke mně
assigned: Přiřazený moderátor
comment:
none: Žádné
created_at: Nahlášené
id: ID
mark_as_resolved: Označit jako vyřešené
mark_as_unresolved: Označit jako nevyřešené
notes:
create: Přidat poznámku
create_and_resolve: Vyřešit s poznámkou
create_and_unresolve: Znovu otevřít s poznámkou
delete: Smazat
placeholder: Popište, jaké akce byly vykonány, nebo jakékoliv jiné související aktuality...
reopen: Znovu otevřít nahlášení
report: 'Nahlásit #%{id}'
report_contents: Obsah
reported_account: Nahlášený účet
reported_by: Nahlášeno uživatelem
resolved: Vyřešeno
resolved_msg: Nahlášení úspěšně vyřešeno!
silence_account: Utišit účet
status: Stav
suspend_account: Suspendovat účet
target: Cíl
title: Nahlášení
unassign: Odebrat
unresolved: Nevyřešeno
updated_at: Aktualizováno
view: Zobrazit
settings:
activity_api_enabled:
desc_html: Počty lokálně publikovaných statusů, aktivních uživatelů a nových registrací, v týdenních intervalech
title: Publikovat hromadné statistiky o uživatelské aktivitě
bootstrap_timeline_accounts:
desc_html: Je-li uživatelskch jmen více, oddělujte je čárkami. Lze zadat pouze místní a odemknuté účty. Je-li tohle prázdné, jsou výchozí hodnotou všichni místní administrátoři.
title: Výchozí sledování pro nové uživatele
contact_information:
email: Pracovní e-mail
username: Uživatelské jméno kontaktu
hero:
desc_html: Zobrazuje se na hlavní stránce. Doporučuje se rozlišení alespoň 600x100px. Pokud toto není nastavené, bude zobrazena miniatura instance
title: Hlavní obrázek
peers_api_enabled:
desc_html: Domény, na které tato instanve narazila ve fediverse
title: Zveřejnit seznam objevených instancí
preview_sensitive_media:
desc_html: Náhledy odkazů na jiných stránkách budou zobrazeny i pokud jsou media označena jako citlivá
title: Zobrazovat v náhledech OpenGraph i citlivá média
registrations:
closed_message:
desc_html: Zobrazí se na hlavní stránce, jsou-li registrace uzavřeny. Můžete použít i HTML značky
title: Zpráva o uzavřených registracích
deletion:
desc_html: Dovolit každému smazání svého účtu
title: Zpřístupnit smazání účtu
min_invite_role:
disabled: Nikdo
title: Povolit pozvánky od
open:
desc_html: Povolit každému vytvořit si účet
title: Zpřístupnit registraci
show_known_fediverse_at_about_page:
desc_html: Je-li toto zapnuto, zobrazí se v náhledu tooty ze všech známých serverů na fediverse. Jinak budou zobrazeny pouze místní tooty.
title: Zobrazit celou známou fediverse na náhledu časové osy
show_staff_badge:
desc_html: Zobrazit na stránce uživatele odznak člena personálu
title: Zobrazit odznak personálu
site_description:
desc_html: Úvodní odstavec na hlavní straně a v meta značkách. Můžete zde používat HTML značky, hlavně <code>&lt;a&gt;</code> a <code>&lt;em&gt;</code>.
title: Popis instance
site_description_extended:
desc_html: Dobré místo pro vaše pravidla, pokyny a jiné věci, které vaši instanci odlišují od ostatních. Lze použít HTML značky
title: Vlastní doplňujísí informace
site_terms:
desc_html: Můžete si napsat vlastní zásady soukromí, podmínky používání či jiné legality. Můžete použít HTML značky
title: Vlastní podmínky používání
site_title: Název instance
thumbnail:
desc_html: Používáno pro náhledy přes OpenGraph a API. Doporučuje se rozlišení 1200x630px
title: Miniatura instance
timeline_preview:
desc_html: Zobrazit na hlavní straně veřejnou časovou osu
title: Náhled časové osy
title: Nastavení stránky
statuses:
back_to_account: Zpět na stránku účtu
batch:
delete: Vymazat
nsfw_off: Označit, že není citlivý
nsfw_on: Označit jako citlivý
failed_to_execute: Nepodařilo se vykonat
media:
title: Média
no_media: Žádná média
title: Statusy účtů
with_media: S médii
subscriptions:
callback_url: Zpáteční URL
confirmed: Potvrzeno
expires_in: Vyprší v
last_delivery: Poslední doručení
title: WebSub
topic: Téma
title: Administrace
admin_mailer:
new_report:
body: "%{reporter} nahlásil/a uživatele %{target}"
body_remote: Někdo z %{domain} nahlásil uživatele %{target}
subject: Nové nahlášení pro %{instance} (#%{id})
application_mailer:
notification_preferences: Změnit volby e-mailu
salutation: "%{name},"
settings: 'Změnit volby e-mailu: %{link}'
view: 'Zobrazit:'
view_profile: Zobrazit profil
view_status: Zobrazit status
applications:
created: Aplikace úspěšně vytvořena
destroyed: Aplikace úspěšně smazána
invalid_url: Zadaná adresa URL je neplatná
regenerate_token: Znovu vygenerovat přístupový token
token_regenerated: Přístupový token byl úspěšně vygenerován
warning: Buďte s těmito daty velmi opatrní. Nikdy je s nikým nesdílejte!
your_token: Váš přístupový token
auth:
agreement_html: Registrací souhlasíte s následováním <a href="%{rules_path}">pravidel této instance</a> a <a href="%{terms_path}">našich podmínek používání</a>.
change_password: Heslo
confirm_email: Potvrdit e-mail
delete_account: Odstranit účet
delete_account_html: Chcete-li odstranit svůj účet, <a href="%{path}">pokračujte zde</a>. Budete požádán/a o potvrzení.
didnt_get_confirmation: Neobdržel/a jste pokyny pro potvrzení?
forgot_password: Zapomněl/a jste heslo?
invalid_reset_password_token: Token na obnovu hesla je buď neplatný, nebo vypršel. Prosím vyžádejte si nový.
login: Přihlásit
logout: Odhlásit
migrate_account: Přesunout se na jiný účet
migrate_account_html: Chcete-li přesměrovat tento účet na jiný, můžete to <a href="%{path}">nastavit zde</a>.
or: nebo
or_log_in_with: Nebo se přihlaste pomocí
providers:
cas: CAS
saml: SAML
register: Registrovat
register_elsewhere: Registrovat na jiném serveru
resend_confirmation: Znovu odeslat pokyny pro potvrzení
reset_password: Obnovit heslo
security: Zabezpečení
set_new_password: Nastavit nové heslo
authorize_follow:
already_following: Tento účet již sledujete
error: Při hledání vzdáleného účtu bohužel nastala chyba
follow: Sledovat
follow_request: 'Poslal/a jste žádost o sledování uživateli:'
following: 'Podařilo se! Nyní sledujete uživatele:'
post_follow:
close: Nebo můžete toto okno klidně zavřít.
return: Zobrazit profil uživatele
web: Přejít na web
title: Sledovat uživatele %{acct}
datetime:
distance_in_words:
about_x_hours: "%{count} hod"
about_x_months: "%{count} měsíců"
about_x_years: "%{count} let"
almost_x_years: "%{count} let"
half_a_minute: Právě teď
less_than_x_minutes: "%{count} min"
less_than_x_seconds: Právě teď
over_x_years: "%{count} let"
x_days: "%{count} dní"
x_minutes: "%{count} min"
x_months: "%{count} mesíců"
x_seconds: "%{count} s"
deletes:
bad_password_msg: Dobrý pokus, hackeři! Nesprávné heslo
confirm_password: Zadejte svoje současné heslo pro ověření vaší identity
description_html: Tímto <strong>trvale a nenávratně</strong> odstraníte obsah z vašeho účtu a deaktivuje ho. Vaše uživatelské jméno zůstane rezervované pro zabránění budoucím napodobováním.
proceed: Odstranit účet
success_msg: Váš účet byl úspěšně odstraněn
warning_html: Pouze vymazání obsahu z této konkrétní instance je zaručeno. Obsah, který byl široce sdílen, po sobě pravděpodobně zanechá stopy. U offline serverů a serverů, které vaše aktualizace již neodebírají, nebudou databáze aktualizovány.
warning_title: Dostupnost rozšířeného obsahu
errors:
'403': Nemáte povolení zobrazit tuto stránku.
'404': Stránka, kterou hledáte, neexistuje.
'410': Stránka, kterou hledáte, již neexistuje.
'422':
content: Bezpečnostní ověření selhalo. Neblokujete cookoes?
title: Bezpečnostní ověření selhalo
'429': Příliš mnoho požadavků
'500':
content: Omlouváme se, ale něco se pokazilo u nás.
title: Tato stránka není správná
noscript_html: Pro použití webové aplikace Mastodon prosím povolte JavaScript. Nebo zkuste jednu z <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">nativních aplikací</a> pro Mastodon pro vaši platformu.
exports:
archive_takeout:
date: Datum
download: Stáhnout svůj archiv
hint_html: Můžete si vyžádat archiv vašich <strong>tootů a nahraných médií</strong>. Exportovaná data budou ve formátu ActivityPub a budou čitelné kterýmkoliv kompatibilním softwarem. Archiv si můžete vyžádat každých 7 dní.
in_progress: Kompiluji váš archiv...
request: Vyžádat svůj archiv
size: Velikost
blocks: Blokujete
csv: CSV
follows: Sledujete
mutes: Ignorujete
storage: Úložisko médií
filters:
contexts:
home: Domovská časová osa
notifications: Oznámení
public: Veřejný časové osy
thread: Konverzace
edit:
title: Upravit filtr
errors:
invalid_context: Nebylo poskytnuto nic, nebo má neplatný kontext
invalid_irreversible: Nezvratné filtrování funguje pouze v souvislosti s domovskou osou či oznámeními
index:
delete: Smazat
title: Filtry
new:
title: Přidat nový filtr
followers:
domain: Doména
explanation_html: Chcete-li zaručit soukromí vašich statusů, musíte mít na vědomí, kdo vás sleduje. <strong>Vaše soukromé statusy jsou doručeny na všechny instance, kde máte sledovatele</strong>. Nejspíš si je budete chtít zkontrolovat a odstranit sledovatele na instancích, jejichž personálu či softwaru nedůvěřujete s respektováním vašeho soukromí.
followers_count: Počet sledovatelů
lock_link: Zamkněte svůj účet
purge: Odstranit ze sledovatelů
success:
one: V průběhu utišování sledovatelů z jedné domény...
other: V průběhu utišování sledovatelů z %{count} domén...
true_privacy_html: Berte prosím na vědomí, že <strong>skutečného soukromí se dá dosáhnout pouze za pomoci end-to-end šifrování</strong>.
unlocked_warning_html: Kdokoliv vás může sledovat a okamžitě vidět vaše soukromé statusy. %{lock_link}, abyste mohl/a zkontrolovat a odmítnout sledovatele.
unlocked_warning_title: Váš účet není zamknutý
generic:
changes_saved_msg: Změny byly úspěšně uloženy!
save_changes: Uložit změny
validation_errors:
one: Něco ještě není úplně v pořádku! Prosím zkontrolujte chybu níže
other: Něco ještě není úplně v pořádku! Prosím zkontrolujte %{count} chyb níže
imports:
preface: Můžete importovat data, která jste exportoval/a z jiné instance, jako například seznam lidí, které sledujete či blokujete.
success: Vaše data byla úspěšně nahrána a nyní budou zpracována v daný čas
types:
blocking: Seznam blokovaných
following: Seznam sledovaných
muting: Seznam ignorovaných
upload: Nahrát
in_memoriam_html: Navždy budeme vzpomínat.
invites:
delete: Deaktivovat
expired: Vypršelé
expires_in:
'1800': 30 minut
'21600': 6 hodin
'3600': 1 hodina
'43200': 12 hodin
'604800': 1 týden
'86400': 1 den
expires_in_prompt: Nikdy
generate: Vygenerovat
invited_by: 'Byl/a jste pozván/a uživatelem:'
max_uses:
one: 1 použití
other: "%{count} použití"
max_uses_prompt: Bez limitu
prompt: Vygenerujte a sdílejte s ostatními odkazy a umožněte jim přístup na tuto instanci
table:
expires_at: Vyprší
uses: Použití
title: Pozvěte lidi
lists:
errors:
limit: Dosáhl/a jste maximálního počtu seznamů
media_attachments:
validations:
images_and_video: Ke statusu, který již obsahuje obrázky, nelze připojit video
too_many: Nelze připojit více než 4 soubory
migrations:
acct: přezdívka@doména nového účtu
currently_redirecting: 'Váš profil má nastaveno přesměrování na:'
proceed: Uložit
updated_msg: Vaše nastavení migrace účtu bylo úspěšně aktualizováno!
moderation:
title: Moderace
notification_mailer:
digest:
action: Zobrazit všechna oznámení
body: Zde najdete stručný souhrn zpráv, které jste zmeškal/a od vaší poslední návštěvy %{since}
mention: "%{name} vás zmínil/a v:"
new_followers_summary:
one: Navíc jste získal/a jednoho nového sledovatele, zatímco jste byl/a pryč! Hurá!
other: Navíc jste získal/a %{count} nových sledovatelů, zatímco jste byl/a pryč! Hurá!
subject:
one: "Jedno nové oznámení od vaší poslední návštěvy \U0001F418"
other: "%{count} nových oznámení od vaší poslední návštěvy \U0001F418"
title: Ve vaší absenci...
favourite:
body: 'Váš status si oblíbil/a %{name}:'
subject: "%{name} si oblíbil/a váš status"
title: Nové oblíbení
follow:
body: "%{name} vás nyní sleduje!"
subject: "%{name} vás nyní sleduje"
title: Nový sledovatel
follow_request:
action: Spravovat žádosti o sledování
body: "%{name} žádá o povolení vás následovat"
subject: 'Čekající sledovatel: %{name}'
title: Nová žádost o sledování
mention:
action: Odpovědět
body: 'Byl/a jste zmíněn/a uživatelem %{name} v:'
subject: Byl/a jste zmíněn/a uživatelem %{name}
title: Nová zmínka
reblog:
body: 'Váš status byl boostnutý uživatelem %{name}:'
subject: "%{name} boostnul/a váš status"
title: Nové boostnutí
number:
human:
decimal_units:
format: "%n %u"
units:
billion: mld
million: mil
quadrillion: bld
thousand: tis
trillion: T
pagination:
newer: Novější
next: Starší
older: Starší
prev: Před
truncate: "&hellip;"
preferences:
languages: Jazyky
other: Ostatní
publishing: Publikování
web: Web
remote_follow:
acct: Napište svou přezdívku@doménu, ze které chcete sledovat
missing_resource: Nemůžeme najít požadované přesměrovací URL pro váš účet
no_account_html: Ještě nemáte účet? Můžete se <a href='%{sign_up_path}' target='_blank'>registrovat zde</a>
proceed: Pokračujte pro sledování
prompt: 'Budete sledovat:'
remote_unfollow:
error: Chyba
title: Nadpis
unfollowed: Už nesledujete
sessions:
activity: Nejnovější aktivita
browser: Prohlížeč
browsers:
alipay: Alipay
blackberry: Blackberry
chrome: Chrome
edge: Microsoft Edge
electron: Electron
firefox: Firefox
generic: Neznámý prohlížeč
ie: Internet Explorer
micro_messenger: MicroMessenger
nokia: Nokia S40 Ovi Browser
opera: Opera
otter: Otter
phantom_js: PhantomJS
qq: QQ Browser
safari: Safari
uc_browser: UCBrowser
weibo: Weibo
current_session: Aktuální relace
description: "%{browser} na %{platform}"
explanation: Toto jsou webové prohlížeče aktuálně přihlášené na váš účet Mastodon.
ip: IP
platforms:
adobe_air: Adobe Air
android: Android
blackberry: Blackberry
chrome_os: Chrome OS
firefox_os: Firefox OS
ios: iOS
linux: Linux
mac: Mac
other: neznámé platformě
windows: Windows
windows_mobile: Windows Mobile
windows_phone: Windows Phone
revoke: Zamítnout
revoke_success: Relace úspěšně zamítnuta
title: Relace
settings:
authorized_apps: Autorizované aplikace
back: Zpět na Mastodon
delete: Smazání účtu
development: Vývoj
edit_profile: Upravit profil
export: Export dat
followers: Autorizovaní sledovatelé
import: Import
migrate: Přesunutí účtu
notifications: Oznámení
preferences: Předvolby
settings: Nastavení
two_factor_authentication: Dvoufaktorové ověřování
your_apps: Vaše aplikace
statuses:
attached:
description: 'Přiloženo: %{attached}'
image:
one: "%{count} obrázek"
other: "%{count} obrázků"
video:
one: "%{count} video"
other: "%{count} videí"
boosted_from_html: Boostnuto z %{acct_link}
content_warning: 'Varování o obsahu: %{warning}'
disallowed_hashtags:
one: 'obsahuje nepovolený hashtag: %{tags}'
other: 'obsahuje nepovolené hashtagy: %{tags}'
language_detection: Zjistit jazyk automaticky
open_in_web: Otevřít na webu
over_character_limit: limit %{max} znaků byl překročen
pin_errors:
limit: Už jste si připnul/a maximální počet tootů
ownership: Nelže připnout toot někoho jiného
private: Nelze připnout neveřejné tooty
reblog: Nelze připnout boostnutí
show_more: Zobrazit více
title: '%{name}: "%{quote}"'
visibilities:
private: Pouze pro sledovatele
private_long: Zobrazit pouze sledovatelům
public: Veřejné
public_long: Všichni mohou vidět
unlisted: Neuvedené
unlisted_long: Všichni mohou vidět, ale není zahrnut ve veřejných časových osách
stream_entries:
pinned: Připnutý toot
reblogged: boostnutý
sensitive_content: CItlivý obsah
terms:
body_html: |
<h2>Zásady soukromí</h2>
<h3 id="collect">Jaké informace sbíráme?</h3>
<ul>
<li><em>Základní informace o účtu</em>: Pokud se na tomto serveru zaregistrujete, můžeme vás požádat o zadání uživatelského jména, e-mailové adresy a hesla. Můžete také zadat dodatečné profilové informace, jako například zobrazované jméno a krátký životopis, a nahrát si profilovou fotografii a hlavičkový obrázek. Uživatelské i zobrazované jméno, životopis, profilová fotografie a hlavičkový obrázek jsou vždy uvedeny veřejně.</li>
<li><em>Příspěvky, sledovatelé a další veřejné informace</em>: Seznam lidí, které sledujete, je uveden veřejně, totéž platí i pro vaše sledovatele. Když sem nahrajete zprávu, bude uloženo datum a čas, společně s aplikací, ze které jste zprávu odeslali. Zprávy mohou obsahovat mediální přílohy, jako jsou obrázky a videa. Veřejné a nezobrazované příspěvky jsou dostupné veřejně. Pokud na vašem profilu uvedete příspěvek, je to také veřejně dostupná informace. Vaše příspěvky jsou doručeny vašim sledovatelům, což v některých případech znamená, že budou doručeny na různé servery, na kterých budou ukládány kopie. Pokud příspěvky smažete, bude tohle taktéž doručeno vašim sledovatelům. Akce znovusdílení nebo oblíbení jiného příspěvku je vždy veřejná.</li>
<li><em>Příspěvky přímé a pouze pro sledovatele</em>: Všechny příspěvky jsou uloženy a zpracovány na serveru. Příspěvky pouze pro sledovatele jsou doručeny vašim sledovatelům a uživateům v nich zmíněných a přímé příspěvky jsou doručeny pouze uživatelům v nich zmíněných. V některých případech tohle znamená, že budou doručeny na různé servery, na kterých budou ukládány kopie. Snažíme se omezit přístup k těmto příspěvkům pouze na autorizované uživatele, ovšem jiné servery tak nemusejí učinit. Proto je důležité posoudit servery, ke kterým vaši sledovatelé patří. V nastavení si můžete zapnout volbu pro manuální schvalování či odmítnutí nových sledovatelů. <em>Prosím mějte na paměti, že operátoři tohoto serveru a kteréhokoliv přijímacího serveru mohou tyto zprávy vidět</em> a příjemci mohou vytvořit jejich snímek, zkopírovat je, nebo je jinak sdílet. <em>Nesdílejte přes Mastodon jakékoliv nebezpečné informace.</em></li>
<li><em>IP adresy a další metadata</em>: Když se přihlásíte, zaznamenáváme IP adresu, ze které se přihlašujete, jakožto i název vašeho webového prohlížeče. Všechny vaše webové relace jsou v nastavení přístupné k vašemu posouzení a odvolání. Nejpozdější IP adresa použita je uložena maximálně do 12 měsíců. Můžeme také uchovávat serverové záznamy, které obsahují IP adresy každého požadavku odeslaného na náš server.</li>
</ul>
<hr class="spacer" />
<h3 id="use">Na co používáme vaše informace?</h3>
<p>Jakékoliv informace, které sbíráme, mohou být použity následujícími způsoby:</p>
<ul>
<li>K poskytnutí základních funkcí Mastodonu. Interagovat s obsahem od jiných lidí a přispívat svým vlastním obsahem můžete pouze, pokud jste přihlášeni. Můžete například sledovat jiné lidi a zobrazit si jejich kombinované příspěvky ve vaší vlastní personalizované časové ose.</li>
<li>Pro pomoc moderaci komunity, například porovnáním vaší IP adresy s dalšími známými adresami pro určení vyhýbání se zákazům či jiných přestupků.</li>
<li>E-mailová adresa, kterou nám poskytnete, může být použita pro zasílání informací, oznámení o interakcích jiných uživatelů s vaším obsahem nebo přijatých zprávách a k odpovědím na dotazy a/nebo další požadavky či otázky.</li>
</ul>
<hr class="spacer" />
<h3 id="protect">Jak vaše informace chráníme?</h3>
<p>Implenentujeme různá bezpečnostní opatření pro udržování bezpečnosti vašich osobních dat, když zadáváte, odesíláte, či přistupujete k vašim osobním datům. Mimo jiné je vaše relace v prohlížeči, jakož i provoz mezi vašimi aplikacemi a API, zabezpečena pomocí SSL, a vaše heslo je hashováno pomocí silného jednosměrného algoritmu. Pro větší zabezpečení vašeho účtu můžete povolit dvoufaktorovou autentikaci.</p>
<hr class="spacer" />
<h3 id="data-retention">Jaké jsou naše zásady o uchovávání údajů?</h3>
<p>Budeme se snažit:</p>
<ul>
<li>Uchovávat serverové záznamy obsahující IP adresy všech požadavků pro tento server, pokud se takové záznamy uchovávají, maximálně 90 dní.</li>
<li>Uchovávat IP adresy související s registrovanými uživateli maximálně 12 měsíců.</li>
</ul>
<p>Kdykoliv si můžete vyžádat a stáhnout archiv vašeho obsahu, včetně vašich příspěvků, mediálních příloh, profilové fotografie a hlavičkového obrázku.</p>
<p>Kdykoliv můžete nenávratně smazat váš účet.</p>
<hr class="spacer"/>
<h3 id="cookies">Používáme cookies?</h3>
<p>Ano. Cookies jsou malé soubory, které stránka nebo její poskytovatel uloží na pevný disk vašeho počítače (pokud to dovolíte). Tyto cookies umožňují stránce rozpoznat váš prohlížeč a, pokud máte registrovaný účet, přidružit ho s vaším registrovaným účtem.</p>
<p>POužíváme cookies pro pochopení a ukládání vašich předvoleb pro budoucí návštěvy.</p>
<hr class="spacer" />
<h3 id="disclose">Zveřejňujeme jakékoliv informace třetím stranám?</h3>
<p>Vaše osobně identifikovatelné informace neprodáváme, neobchodujeme s nimi, ani je nijak nepřenášíme vnějším stranám. Do tohoto se nepočítají důvěryhodné třetí strany, které nám pomáhají provozovat naši stránku, podnikat, nebo vás obsluhovat, pokud tyto strany souhlasí se zachováním důvěrnosti těchto informací. Můžeme také uvolnit vaše informace, pokud věříme, že je to nutné pro soulad se zákonem, prosazování našich zásad, nebo ochranu práv, majetku, či bezpečnost nás či ostatních.</p>
<p>Váš veřejný obsah může být stažen jinými servery na síti. Vaše příspěvky veřejné a pouze pro sledovatele budou doručeny na servery vašich sledovatelů a přímé zprávy budou doručeny na servery příjemců, pokud jsou tito sledovatelé nebo příjemci zaregistrováni na jiném serveru, než je tento.</p>
<p>Když autorizujete aplikaci, aby používala váš účet, může, v závislosti na rozsahu oprávnění, které jí udělíte, přistupovat k vašim veřejným profilovým informacím, seznamu lidí, které sledujete, vašim sledovatelům, vašim seznamům, všem vašim příspěvkům a příspěvkům, které jste si oblíbili. Aplikace nikdy nemohou získat vaši e-mailovou adresu či heslo.</p>
<hr class="spacer" />
<h3 id="children">Používání stránky dětmi</h3>
<p>Pokud se tento server nachází v EU nebo EHP: Naše stránka, produkty a služby jsou všechny směřovány na lidi, kterým je alespoň 16 let. Pokud je vám méně než 16, dle požadavků nařízení GDPR (<a href="https://cs.wikipedia.org/wiki/Obecn%C3%A9_na%C5%99%C3%ADzen%C3%AD_o_ochran%C4%9B_osobn%C3%ADch_%C3%BAdaj%C5%AF">Obecné nařízení o ochě sobních údajů</a>) </p>
<p>Pokud se tento server nachází v USA: Naše stránka, produkty a služby jsou všechny směřovány na lidi, kterým je alespoň 13 let. Pokud je vám méně než 13, dle požadavků zákona COPPA (<a href="https://cs.wikipedia.org/wiki/Children%27s_online_privacy_protection_act">Children's Online Privacy Protection Act</a>) tuto stránku nepoužívejte.</p>
<p>Právní požadavky mohou být jiné, pokud se tento server nachází v jiné jurisdikci.</p>
<hr class="spacer" />
<h3 id="changes">Změny v našich zásadách soukromí</h3>
<p>Rozhodneme-li se naše zásady soukromí změnit, zveřejníme tyto změny na této stránce.</p>
<p>Tento dokument je dostupný pod licencí CC-BY-SA. Byl naposledy aktualizován 7 března 2018.</p>
<p>Původně adaptováno ze <a href="https://github.com/discourse/discourse">zásad soukromí Discourse</a>.</p>
title: Podmínky používání a zásady soukromí %{instance}
themes:
contrast: Vysoký kontrast
default: Mastodon
mastodon-light: Mastodon (světlý)
time:
formats:
default: "%d. %b %Y, %H:%M"
two_factor_authentication:
code_hint: Pro potvrzení zadejte kód vygenerovaný vaší autentikační aplikací
description_html: Povolíte-li <strong>dvoufaktorové ověřování</strong>, budete při přihlášení potřebovat telefon, který vám vygeneruje přístupové tokeny, které musíte zadat.
disable: Zakázat
enable: Povolit
enabled: Dvoufaktorové ověřování je povoleno
enabled_success: Dvoufaktorové ověřování bylo úspěšně povoleno
generate_recovery_codes: Vygenerovat záložní kódy
instructions_html: "<strong>Naskenujte tento QR kód Google Authenticatorem nebo jinou TOTP aplikací na vašem telefonu</strong>. Od teď bude tato aplikace generovat tokeny, které budete muset zadat při přihlášení."
lost_recovery_codes: Záložní kódy vám dovolí dostat se k vašemu účtu, pokud ztratíte telefon. Ztratíte-li záložní kódy, můžete je zde znovu vygenerovat. Vaše staré záložní kódy budou zneplatněny.
manual_instructions: 'Nemůžete-li oskenovat QR kód a je potřebovat ho zadat ručně, zde je tajemství v prostém textu:'
recovery_codes: Záložní kódy pro obnovu
recovery_codes_regenerated: Záložní kódy byly úspěšně znovu vygenerované
recovery_instructions_html: Ztratíte-li někdy přístup k vašemu telefonu, můžete k získání přístupu k účtu použít jeden ze záložních kódů. <strong>Uchovávejte tyto kódy v bezpečí</strong>. Můžete si je například vytisknout a uložit je mezi jiné důležité dokumenty.
setup: Nastavit
wrong_code: Zadaný kód byl neplatný! Je serverový čas a čas na zařízení správný?
user_mailer:
backup_ready:
explanation: Vyžádal/a jste si úplnou zálohu svého účtu Mastodon. Nyní je připravena ke stažení!
subject: Váš archiv je připraven ke stažení
title: Stažení archivu
welcome:
edit_profile_action: Nastavit profil
edit_profile_step: Můžete si přizpůsobit svůj profil nahráním avataru a obrázku na hlavičce, změnou zobrazovaného jména a dalších. Chcete-li posoudit nové sledovatele předtím, než vás mohou sledovat, můžete svůj účet uzamknout.
explanation: Zde je pár tipů na začátek
final_action: Začněte přispívat
final_step: 'Začněte psát! I když nemáte sledovatele, mohou vaše zprávy vidět jiní lidé, například na místní časové ose a mezi hashtagy. Můžete se ostatním představit pomocí hashtagu #introductions.'
full_handle: Vaše celá adresa profilu
full_handle_hint: Tohle je, co byste řekl/a svým přátelům, aby vám mohli posílat zprávy nebo vás sledovat z jiné instance.
review_preferences_action: Změnit nastavení
review_preferences_step: Nezapomeňte si nastavit své volby, například jaké e-maily chcete přijímat či jak soukromé mají být vaše příspěvky ve výchozím stavu. Nemáte-li epilepsii, můžete si nastavit automatické přehrávání obrázků GIF.
subject: Vítejte na Mastodonu
tip_bridge_html: Pokud přicházíte z Twitteru, můžete najít vaše přátele na Mastodonu pomocí <a href="%{bridge_url}">mostové aplikace</a>. Funguje ovšem pouze, pokud ji oni někdy také použili!
tip_federated_timeline: Federovaná časová osa je náhled celé sítě Mastodon. Zahrnuje ovšem pouze lidi, které sledují vaši sousedé, takže není úplná.
tip_following: Administrátora/y serveru sledujete automaticky. Chcete-li najít další zajímavé lidi, podívejte se na místní a federované časové osy.
tip_local_timeline: Místní časová osa je náhled lidí na %{instance}. Toto jsou vaši nejbližší sousedé!
tip_mobile_webapp: Pokud vám váš mobilní prohlížeč nabídne přidat si Mastodon na vaši domovskou obrazovku, můžete dostávat oznámení. V mnoha ohledech to funguje jako nativní aplikace!
tips: Tipy
title: Vítejte na palubě, %{name}!
users:
invalid_email: E-mailová adresa je neplatná
invalid_otp_token: Neplatný kód pro dvoufaktorovou autentikaci
otp_lost_help_html: Pokud jste ztratil/a přístup k oběma, můžete se spojit %{email}
seamless_external_login: Jste přihlášen/a přes externí službu, nastavení hesla a e-mailu proto nejsou dostupná.
signed_in_as: 'Přihlášen/a jako:'

@ -46,7 +46,6 @@ da:
people_who_follow: Folk der følger %{name}
posts: Dyt
posts_with_replies: Toots og svar
remote_follow: Følg fra andre instanser
reserved_username: Brugernavnet er reserveret
roles:
admin: Administrator
@ -464,7 +463,6 @@ da:
unlocked_warning_title: Din konto er ikke låst
generic:
changes_saved_msg: Ændringerne blev gemt!
powered_by: drevet af %{link}
save_changes: Gem ændringer
imports:
types:
@ -511,6 +509,9 @@ da:
action: Se alle notifikationer
mention: "%{name} nævnte dig i:"
title: Mens du var væk...
favourite:
body: 'Din status blev favoriseret af %{name}:'
subject: "%{name} favoriserede din status"
follow:
body: "%{name} følger dig nu!"
subject: "%{name} følger dig nu"
@ -538,18 +539,22 @@ da:
preferences:
languages: Sprog
other: Andet
publishing: Offentligører
web: Web
remote_unfollow:
error: Fejl
title: Titel
unfollowed: Følger ikke længere
sessions:
activity: Sidste aktivitet
browser: Browser
browsers:
blackberry: Blackberry OS
chrome: Google Chrome
firefox: Mozilla Firefox
generic: Ukendt browser
ie: IE
safari: Apple Safari
description: "%{browser} på %{platform}"
ip: IP
platforms:
@ -583,7 +588,9 @@ da:
content_warning: 'Advarsel om indhold: %{warning}'
language_detection: Opfang automatisk sprog
pin_errors:
limit: Du har allerede fastgjort det maksimale antal trut
ownership: Dun kan ikke fastgøre en anden persons toot
private: Ikke offentlige trut kan ikke blive fastgjort
show_more: Vis mere
title: '%{name}: "%{quote}"'
visibilities:
@ -593,7 +600,6 @@ da:
public_long: Alle kan se
unlisted: Ikke listet
stream_entries:
click_to_show: Tryk for at vise
pinned: Fastgjort toot
sensitive_content: Følsomt indhold
themes:

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

Loading…
Cancel
Save