Merge branch 'master' into glitch-soc/master
Conflicts: app/models/account.rb app/views/accounts/_header.html.haml
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
module Admin
|
||||
class ConfirmationsController < BaseController
|
||||
before_action :set_user
|
||||
before_action :check_confirmation, only: [:resend]
|
||||
|
||||
def create
|
||||
authorize @user, :confirm?
|
||||
@@ -11,10 +12,28 @@ module Admin
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
def resend
|
||||
authorize @user, :confirm?
|
||||
|
||||
@user.resend_confirmation_instructions
|
||||
|
||||
log_action :confirm, @user
|
||||
|
||||
flash[:notice] = I18n.t('admin.accounts.resend_confirmation.success')
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
def check_confirmation
|
||||
if @user.confirmed?
|
||||
flash[:error] = I18n.t('admin.accounts.resend_confirmation.already_confirmed')
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
module Admin
|
||||
class ReportedStatusesController < BaseController
|
||||
before_action :set_report
|
||||
before_action :set_status, only: [:update, :destroy]
|
||||
|
||||
def create
|
||||
authorize :status, :update?
|
||||
@@ -14,20 +13,6 @@ module Admin
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @status, :update?
|
||||
@status.update!(status_params)
|
||||
log_action :update, @status
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @status, :destroy?
|
||||
RemovalWorker.perform_async(@status.id)
|
||||
log_action :destroy, @status
|
||||
render json: @status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def status_params
|
||||
@@ -51,9 +36,5 @@ module Admin
|
||||
def set_report
|
||||
@report = Report.find(params[:report_id])
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = @report.statuses.find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,6 @@ module Admin
|
||||
helper_method :current_params
|
||||
|
||||
before_action :set_account
|
||||
before_action :set_status, only: [:update, :destroy]
|
||||
|
||||
PER_PAGE = 20
|
||||
|
||||
@@ -26,40 +25,18 @@ module Admin
|
||||
def create
|
||||
authorize :status, :update?
|
||||
|
||||
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account))
|
||||
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
|
||||
|
||||
redirect_to admin_account_statuses_path(@account.id, current_params)
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @status, :update?
|
||||
@status.update!(status_params)
|
||||
log_action :update, @status
|
||||
redirect_to admin_account_statuses_path(@account.id, current_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @status, :destroy?
|
||||
RemovalWorker.perform_async(@status.id)
|
||||
log_action :destroy, @status
|
||||
render json: @status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def status_params
|
||||
params.require(:status).permit(:sensitive)
|
||||
end
|
||||
|
||||
def form_status_batch_params
|
||||
params.require(:form_status_batch).permit(:action, status_ids: [])
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:id])
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
@@ -72,5 +49,15 @@ module Admin
|
||||
page: page > 1 && page,
|
||||
}.select { |_, value| value.present? }
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:nsfw_on]
|
||||
'nsfw_on'
|
||||
elsif params[:nsfw_off]
|
||||
'nsfw_off'
|
||||
elsif params[:delete]
|
||||
'delete'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,7 +21,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
||||
private
|
||||
|
||||
def account_params
|
||||
params.permit(:display_name, :note, :avatar, :header, :locked, fields_attributes: [:name, :value])
|
||||
params.permit(:display_name, :note, :avatar, :header, :locked, :bot, fields_attributes: [:name, :value])
|
||||
end
|
||||
|
||||
def user_settings_params
|
||||
|
||||
@@ -39,7 +39,7 @@ class Api::V1::Statuses::PinsController < Api::BaseController
|
||||
adapter: ActivityPub::Adapter
|
||||
).as_json
|
||||
|
||||
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
|
||||
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account.id)
|
||||
end
|
||||
|
||||
def distribute_remove_activity!
|
||||
@@ -49,6 +49,6 @@ class Api::V1::Statuses::PinsController < Api::BaseController
|
||||
adapter: ActivityPub::Adapter
|
||||
).as_json
|
||||
|
||||
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
|
||||
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account.id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,7 +24,7 @@ class Settings::ProfilesController < Settings::BaseController
|
||||
private
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, fields_attributes: [:name, :value])
|
||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, fields_attributes: [:name, :value])
|
||||
end
|
||||
|
||||
def set_account
|
||||
|
||||
@@ -6,6 +6,7 @@ module SettingsHelper
|
||||
ar: 'العربية',
|
||||
bg: 'Български',
|
||||
ca: 'Català',
|
||||
co: 'Corsu',
|
||||
de: 'Deutsch',
|
||||
el: 'Ελληνικά',
|
||||
eo: 'Esperanto',
|
||||
|
||||
@@ -4,8 +4,8 @@ module StreamEntriesHelper
|
||||
EMBEDDED_CONTROLLER = 'statuses'
|
||||
EMBEDDED_ACTION = 'embed'
|
||||
|
||||
def display_name(account)
|
||||
account.display_name.presence || account.username
|
||||
def display_name(account, **options)
|
||||
Formatter.instance.format_display_name(account, options)
|
||||
end
|
||||
|
||||
def account_description(account)
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import emojify from '../../features/emoji/emoji';
|
||||
import { unescapeHTML } from '../../utils/html';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
||||
const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => {
|
||||
obj[`:${emoji.shortcode}:`] = emoji;
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
export function normalizeAccount(account) {
|
||||
account = { ...account };
|
||||
|
||||
const emojiMap = makeEmojiMap(account);
|
||||
const displayName = account.display_name.length === 0 ? account.username : account.display_name;
|
||||
account.display_name_html = emojify(escapeTextContentForBrowser(displayName));
|
||||
account.note_emojified = emojify(account.note);
|
||||
|
||||
account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap);
|
||||
account.note_emojified = emojify(account.note, emojiMap);
|
||||
|
||||
if (account.fields) {
|
||||
account.fields = account.fields.map(pair => ({
|
||||
...pair,
|
||||
name_emojified: emojify(escapeTextContentForBrowser(pair.name)),
|
||||
value_emojified: emojify(pair.value),
|
||||
value_emojified: emojify(pair.value, emojiMap),
|
||||
value_plain: unescapeHTML(pair.value),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -42,11 +51,7 @@ export function normalizeStatus(status, normalOldStatus) {
|
||||
normalStatus.hidden = normalOldStatus.get('hidden');
|
||||
} else {
|
||||
const searchContent = [status.spoiler_text, status.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
|
||||
|
||||
const emojiMap = normalStatus.emojis.reduce((obj, emoji) => {
|
||||
obj[`:${emoji.shortcode}:`] = emoji;
|
||||
return obj;
|
||||
}, {});
|
||||
const emojiMap = makeEmojiMap(normalStatus);
|
||||
|
||||
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
|
||||
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
importFetchedStatuses,
|
||||
} from './importer';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import { unescapeHTML } from '../utils/html';
|
||||
|
||||
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
|
||||
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
|
||||
@@ -31,13 +32,6 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
|
||||
}
|
||||
};
|
||||
|
||||
const unescapeHTML = (html) => {
|
||||
const wrapper = document.createElement('div');
|
||||
html = html.replace(/<br \/>|<br>|\n/g, ' ');
|
||||
wrapper.innerHTML = html;
|
||||
return wrapper.textContent;
|
||||
};
|
||||
|
||||
export function updateNotifications(notification, intlMessages, intlLocale) {
|
||||
return (dispatch, getState) => {
|
||||
const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true);
|
||||
|
||||
@@ -43,6 +43,7 @@ class DropdownMenu extends React.PureComponent {
|
||||
componentDidMount () {
|
||||
document.addEventListener('click', this.handleDocumentClick, false);
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
if (this.focusedItem) this.focusedItem.focus();
|
||||
this.setState({ mounted: true });
|
||||
}
|
||||
|
||||
@@ -55,6 +56,46 @@ class DropdownMenu extends React.PureComponent {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
setFocusRef = c => {
|
||||
this.focusedItem = c;
|
||||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
const items = Array.from(this.node.getElementsByTagName('a'));
|
||||
const index = items.indexOf(e.currentTarget);
|
||||
let element;
|
||||
|
||||
switch(e.key) {
|
||||
case 'Enter':
|
||||
this.handleClick(e);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
element = items[index+1];
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element = items[index-1];
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = items[0];
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
break;
|
||||
case 'End':
|
||||
element = items[items.length-1];
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
const { action, to } = this.props.items[i];
|
||||
@@ -79,7 +120,7 @@ class DropdownMenu extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<li className='dropdown-menu__item' key={`${text}-${i}`}>
|
||||
<a href={href} target='_blank' rel='noopener' role='button' tabIndex='0' autoFocus={i === 0} onClick={this.handleClick} data-index={i}>
|
||||
<a href={href} target='_blank' rel='noopener' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyDown={this.handleKeyDown} data-index={i}>
|
||||
{text}
|
||||
</a>
|
||||
</li>
|
||||
@@ -156,9 +197,6 @@ export default class Dropdown extends React.PureComponent {
|
||||
|
||||
handleKeyDown = e => {
|
||||
switch(e.key) {
|
||||
case 'Enter':
|
||||
this.handleClick(e);
|
||||
break;
|
||||
case 'Escape':
|
||||
this.handleClose();
|
||||
break;
|
||||
|
||||
@@ -35,7 +35,6 @@ export default class ScrollableList extends PureComponent {
|
||||
|
||||
state = {
|
||||
fullscreen: null,
|
||||
mouseOver: false,
|
||||
};
|
||||
|
||||
intersectionObserverWrapper = new IntersectionObserverWrapper();
|
||||
@@ -72,7 +71,7 @@ export default class ScrollableList extends PureComponent {
|
||||
const someItemInserted = React.Children.count(prevProps.children) > 0 &&
|
||||
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
|
||||
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
|
||||
if (someItemInserted && this.node.scrollTop > 0 || (this.state.mouseOver && !prevProps.isLoading)) {
|
||||
if (someItemInserted && this.node.scrollTop > 0) {
|
||||
return this.node.scrollHeight - this.node.scrollTop;
|
||||
} else {
|
||||
return null;
|
||||
@@ -140,14 +139,6 @@ export default class ScrollableList extends PureComponent {
|
||||
this.props.onLoadMore();
|
||||
}
|
||||
|
||||
handleMouseEnter = () => {
|
||||
this.setState({ mouseOver: true });
|
||||
}
|
||||
|
||||
handleMouseLeave = () => {
|
||||
this.setState({ mouseOver: false });
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage, onLoadMore } = this.props;
|
||||
const { fullscreen } = this.state;
|
||||
@@ -158,7 +149,7 @@ export default class ScrollableList extends PureComponent {
|
||||
|
||||
if (isLoading || childrenCount > 0 || !emptyMessage) {
|
||||
scrollableArea = (
|
||||
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef}>
|
||||
<div role='feed' className='item-list'>
|
||||
{prepend}
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ export default class Status extends ImmutablePureComponent {
|
||||
);
|
||||
} else {
|
||||
media = (
|
||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
|
||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
|
||||
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />}
|
||||
</Bundle>
|
||||
);
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Card from '../features/status/components/card';
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
export default class CardContainer extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string,
|
||||
card: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { card, ...props } = this.props;
|
||||
return <Card card={fromJS(card)} {...props} />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import { getLocale } from '../locales';
|
||||
import Card from '../features/status/components/card';
|
||||
import ModalRoot from '../components/modal_root';
|
||||
import MediaModal from '../features/ui/components/media_modal';
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
const { localeData, messages } = getLocale();
|
||||
addLocaleData(localeData);
|
||||
|
||||
export default class CardsContainer extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string,
|
||||
cards: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
media: null,
|
||||
};
|
||||
|
||||
handleOpenCard = (media) => {
|
||||
document.body.classList.add('card-standalone__body');
|
||||
this.setState({ media });
|
||||
}
|
||||
|
||||
handleCloseCard = () => {
|
||||
document.body.classList.remove('card-standalone__body');
|
||||
this.setState({ media: null });
|
||||
}
|
||||
|
||||
render () {
|
||||
const { locale, cards } = this.props;
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
<Fragment>
|
||||
{[].map.call(cards, container => {
|
||||
const { card, ...props } = JSON.parse(container.getAttribute('data-props'));
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<Card card={fromJS(card)} onOpenMedia={this.handleOpenCard} {...props} />,
|
||||
container,
|
||||
);
|
||||
})}
|
||||
<ModalRoot onClose={this.handleCloseCard}>
|
||||
{this.state.media && (
|
||||
<MediaModal media={this.state.media} index={0} onClose={this.handleCloseCard} />
|
||||
)}
|
||||
</ModalRoot>
|
||||
</Fragment>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import configureStore from '../store/configureStore';
|
||||
@@ -8,6 +9,7 @@ import { getLocale } from '../locales';
|
||||
import PublicTimeline from '../features/standalone/public_timeline';
|
||||
import CommunityTimeline from '../features/standalone/community_timeline';
|
||||
import HashtagTimeline from '../features/standalone/hashtag_timeline';
|
||||
import ModalContainer from '../features/ui/containers/modal_container';
|
||||
import initialState from '../initial_state';
|
||||
|
||||
const { localeData, messages } = getLocale();
|
||||
@@ -47,7 +49,13 @@ export default class TimelineContainer extends React.PureComponent {
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
<Provider store={store}>
|
||||
{timeline}
|
||||
<Fragment>
|
||||
{timeline}
|
||||
{ReactDOM.createPortal(
|
||||
<ModalContainer />,
|
||||
document.getElementById('modal-container'),
|
||||
)}
|
||||
</Fragment>
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
@@ -131,6 +131,7 @@ export default class Header extends ImmutablePureComponent {
|
||||
const content = { __html: account.get('note_emojified') };
|
||||
const displayNameHtml = { __html: account.get('display_name_html') };
|
||||
const fields = account.get('fields');
|
||||
const badge = account.get('bot') ? (<div className='roles'><div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div></div>) : null;
|
||||
|
||||
return (
|
||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })} style={{ backgroundImage: `url(${account.get('header')})` }}>
|
||||
@@ -139,19 +140,20 @@ export default class Header extends ImmutablePureComponent {
|
||||
|
||||
<span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} />
|
||||
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
|
||||
|
||||
{badge}
|
||||
|
||||
<div className='account__header__content' dangerouslySetInnerHTML={content} />
|
||||
|
||||
{fields.size > 0 && (
|
||||
<table className='account__header__fields'>
|
||||
<tbody>
|
||||
{fields.map((pair, i) => (
|
||||
<tr key={i}>
|
||||
<th dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} />
|
||||
<td dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className='account__header__fields'>
|
||||
{fields.map((pair, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
|
||||
<dd dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} title={pair.get('value_plain')} />
|
||||
</dl>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{info}
|
||||
|
||||
@@ -42,22 +42,65 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
if (e.key === 'Escape') {
|
||||
this.props.onClose();
|
||||
} else if (!e.key || e.key === 'Enter') {
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
|
||||
e.preventDefault();
|
||||
handleKeyDown = e => {
|
||||
const { items } = this.props;
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
const index = items.findIndex(item => {
|
||||
return (item.value === value);
|
||||
});
|
||||
let element;
|
||||
|
||||
switch(e.key) {
|
||||
case 'Escape':
|
||||
this.props.onClose();
|
||||
this.props.onChange(value);
|
||||
break;
|
||||
case 'Enter':
|
||||
this.handleClick(e);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
element = this.node.childNodes[index + 1];
|
||||
if (element) {
|
||||
element.focus();
|
||||
this.props.onChange(element.getAttribute('data-index'));
|
||||
}
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element = this.node.childNodes[index - 1];
|
||||
if (element) {
|
||||
element.focus();
|
||||
this.props.onChange(element.getAttribute('data-index'));
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = this.node.firstChild;
|
||||
if (element) {
|
||||
element.focus();
|
||||
this.props.onChange(element.getAttribute('data-index'));
|
||||
}
|
||||
break;
|
||||
case 'End':
|
||||
element = this.node.lastChild;
|
||||
if (element) {
|
||||
element.focus();
|
||||
this.props.onChange(element.getAttribute('data-index'));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
this.props.onClose();
|
||||
this.props.onChange(value);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
document.addEventListener('click', this.handleDocumentClick, false);
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
if (this.focusedItem) this.focusedItem.focus();
|
||||
this.setState({ mounted: true });
|
||||
}
|
||||
|
||||
@@ -70,6 +113,10 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
setFocusRef = c => {
|
||||
this.focusedItem = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { mounted } = this.state;
|
||||
const { style, items, value } = this.props;
|
||||
@@ -80,9 +127,9 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
||||
// It should not be transformed when mounting because the resulting
|
||||
// size will be used to determine the coordinate of the menu by
|
||||
// react-overlays
|
||||
<div className='privacy-dropdown__dropdown' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
|
||||
<div className='privacy-dropdown__dropdown' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} role='listbox' ref={this.setRef}>
|
||||
{items.map(item => (
|
||||
<div role='button' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleClick} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })}>
|
||||
<div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
|
||||
<div className='privacy-dropdown__option__icon'>
|
||||
<i className={`fa fa-fw fa-${item.icon}`} />
|
||||
</div>
|
||||
@@ -147,9 +194,6 @@ export default class PrivacyDropdown extends React.PureComponent {
|
||||
|
||||
handleKeyDown = e => {
|
||||
switch(e.key) {
|
||||
case 'Enter':
|
||||
this.handleToggle(e);
|
||||
break;
|
||||
case 'Escape':
|
||||
this.handleClose();
|
||||
break;
|
||||
|
||||
@@ -43,11 +43,19 @@ export default class Compose extends React.PureComponent {
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
this.props.dispatch(mountCompose());
|
||||
const { isSearchPage } = this.props;
|
||||
|
||||
if (!isSearchPage) {
|
||||
this.props.dispatch(mountCompose());
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.props.dispatch(unmountCompose());
|
||||
const { isSearchPage } = this.props;
|
||||
|
||||
if (!isSearchPage) {
|
||||
this.props.dispatch(unmountCompose());
|
||||
}
|
||||
}
|
||||
|
||||
onFocus = () => {
|
||||
|
||||
@@ -40,6 +40,17 @@ export default class ModalRoot extends React.PureComponent {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
getSnapshotBeforeUpdate () {
|
||||
const visible = !!this.props.type;
|
||||
return {
|
||||
overflowY: visible ? 'hidden' : null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps, prevState, { overflowY }) {
|
||||
document.body.style.overflowY = overflowY;
|
||||
}
|
||||
|
||||
renderLoading = modalId => () => {
|
||||
return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ const makeMapStateToProps = () => {
|
||||
account: getAccount(state, accountId),
|
||||
comment: state.getIn(['reports', 'new', 'comment']),
|
||||
forward: state.getIn(['reports', 'new', 'forward']),
|
||||
statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
|
||||
statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -64,12 +64,12 @@ export default class ReportModal extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.props.dispatch(expandAccountTimeline(this.props.account.get('id')));
|
||||
this.props.dispatch(expandAccountTimeline(this.props.account.get('id'), { withReplies: true }));
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.props.account !== nextProps.account && nextProps.account) {
|
||||
this.props.dispatch(expandAccountTimeline(nextProps.account.get('id')));
|
||||
this.props.dispatch(expandAccountTimeline(nextProps.account.get('id'), { withReplies: true }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ const makeGetStatusIds = () => createSelector([
|
||||
}
|
||||
|
||||
return statusIds.filter(id => {
|
||||
if (id === null) return true;
|
||||
|
||||
const statusForId = statuses.get(id);
|
||||
let showStatus = true;
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@
|
||||
"status.pin": "تدبيس على الملف الشخصي",
|
||||
"status.pinned": "تبويق مثبَّت",
|
||||
"status.reblog": "رَقِّي",
|
||||
"status.reblog_private": "Boost to original audience",
|
||||
"status.reblog_private": "القيام بالترقية إلى الجمهور الأصلي",
|
||||
"status.reblogged_by": "{name} رقى",
|
||||
"status.reply": "ردّ",
|
||||
"status.replyAll": "رُد على الخيط",
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
{
|
||||
"account.block": "Bluccà @{name}",
|
||||
"account.block_domain": "Piattà tuttu da {domain}",
|
||||
"account.blocked": "Bluccatu",
|
||||
"account.direct": "Missaghju direttu @{name}",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.domain_blocked": "Duminiu piattatu",
|
||||
"account.edit_profile": "Mudificà u prufile",
|
||||
"account.follow": "Siguità",
|
||||
"account.followers": "Abbunati",
|
||||
"account.follows": "Abbunamenti",
|
||||
"account.follows_you": "Vi seguita",
|
||||
"account.hide_reblogs": "Piattà spartere da @{name}",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mintuvà @{name}",
|
||||
"account.moved_to": "{name} hè partutu nant'à:",
|
||||
"account.mute": "Piattà @{name}",
|
||||
"account.mute_notifications": "Piattà nutificazione da @{name}",
|
||||
"account.muted": "Piattatu",
|
||||
"account.posts": "Statuti",
|
||||
"account.posts_with_replies": "Statuti è risposte",
|
||||
"account.report": "Palisà @{name}",
|
||||
"account.requested": "In attesa d'apprubazione. Cliccate per annullà a dumanda",
|
||||
"account.share": "Sparte u prufile di @{name}",
|
||||
"account.show_reblogs": "Vede spartere da @{name}",
|
||||
"account.unblock": "Sbluccà @{name}",
|
||||
"account.unblock_domain": "Ùn piattà più {domain}",
|
||||
"account.unfollow": "Ùn siguità più",
|
||||
"account.unmute": "Ùn piattà più @{name}",
|
||||
"account.unmute_notifications": "Ùn piattà più nutificazione da @{name}",
|
||||
"account.view_full_profile": "View full profile",
|
||||
"alert.unexpected.message": "Un prublemu inaspettatu hè accadutu.",
|
||||
"alert.unexpected.title": "Uups!",
|
||||
"boost_modal.combo": "Pudete appughjà nant'à {combo} per saltà quessa a prussima volta",
|
||||
"bundle_column_error.body": "C'hè statu un prublemu caricandu st'elementu.",
|
||||
"bundle_column_error.retry": "Pruvà torna",
|
||||
"bundle_column_error.title": "Errore di cunnessione",
|
||||
"bundle_modal_error.close": "Chjudà",
|
||||
"bundle_modal_error.message": "C'hè statu un prublemu caricandu st'elementu.",
|
||||
"bundle_modal_error.retry": "Pruvà torna",
|
||||
"column.blocks": "Utilizatori bluccati",
|
||||
"column.community": "Linea pubblica lucale",
|
||||
"column.direct": "Missaghji diretti",
|
||||
"column.domain_blocks": "Duminii piattati",
|
||||
"column.favourites": "Favuriti",
|
||||
"column.follow_requests": "Dumande d'abbunamentu",
|
||||
"column.home": "Accolta",
|
||||
"column.lists": "Liste",
|
||||
"column.mutes": "Utilizatori piattati",
|
||||
"column.notifications": "Nutificazione",
|
||||
"column.pins": "Statuti puntarulati",
|
||||
"column.public": "Linea pubblica glubale",
|
||||
"column_back_button.label": "Ritornu",
|
||||
"column_header.hide_settings": "Piattà i parametri",
|
||||
"column_header.moveLeft_settings": "Spiazzà à manca",
|
||||
"column_header.moveRight_settings": "Spiazzà à diritta",
|
||||
"column_header.pin": "Puntarulà",
|
||||
"column_header.show_settings": "Mustrà i parametri",
|
||||
"column_header.unpin": "Spuntarulà",
|
||||
"column_subheading.navigation": "Navigazione",
|
||||
"column_subheading.settings": "Parametri",
|
||||
"compose_form.direct_message_warning": "Solu l'utilizatori mintuvati puderenu vede stu statutu.",
|
||||
"compose_form.hashtag_warning": "Stu statutu ùn hè \"Micca listatu\" è ùn sarà micca listatu indè e circate da hashtag. Per esse vistu in quesse, u statutu deve esse \"Pubblicu\".",
|
||||
"compose_form.lock_disclaimer": "U vostru contu ùn hè micca {locked}. Tuttu u mondu pò seguitavi è vede i vostri statuti privati.",
|
||||
"compose_form.lock_disclaimer.lock": "privatu",
|
||||
"compose_form.placeholder": "À chè pensate?",
|
||||
"compose_form.publish": "Toot",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive.marked": "Media indicatu cum'è sensibile",
|
||||
"compose_form.sensitive.unmarked": "Media micca indicatu cum'è sensibile",
|
||||
"compose_form.spoiler.marked": "Testu piattatu daret'à un'avertimentu",
|
||||
"compose_form.spoiler.unmarked": "Testu micca piattatu",
|
||||
"compose_form.spoiler_placeholder": "Scrive u vostr'avertimentu quì",
|
||||
"confirmation_modal.cancel": "Annullà",
|
||||
"confirmations.block.confirm": "Bluccà",
|
||||
"confirmations.block.message": "Site sicuru·a che vulete bluccà @{name}?",
|
||||
"confirmations.delete.confirm": "Toglie",
|
||||
"confirmations.delete.message": "Site sicuru·a che vulete supprime stu statutu?",
|
||||
"confirmations.delete_list.confirm": "Toglie",
|
||||
"confirmations.delete_list.message": "Site sicuru·a che vulete supprime sta lista?",
|
||||
"confirmations.domain_block.confirm": "Piattà tuttu u duminiu?",
|
||||
"confirmations.domain_block.message": "Site sicuru·a che vulete piattà tuttu à {domain}? Saria forse abbastanza di bluccà ò piattà alcuni conti da quallà.",
|
||||
"confirmations.mute.confirm": "Piattà",
|
||||
"confirmations.mute.message": "Site sicuru·a che vulete piattà @{name}?",
|
||||
"confirmations.unfollow.confirm": "Disabbunassi",
|
||||
"confirmations.unfollow.message": "Site sicuru·a ch'ùn vulete più siguità @{name}?",
|
||||
"embed.instructions": "Integrà stu statutu à u vostru situ cù u codice quì sottu.",
|
||||
"embed.preview": "Assumiglierà à qualcosa cusì:",
|
||||
"emoji_button.activity": "Attività",
|
||||
"emoji_button.custom": "Persunalizati",
|
||||
"emoji_button.flags": "Bandere",
|
||||
"emoji_button.food": "Manghjusca è Bienda",
|
||||
"emoji_button.label": "Mette un'emoji",
|
||||
"emoji_button.nature": "Natura",
|
||||
"emoji_button.not_found": "Ùn c'hè nunda! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.objects": "Oggetti",
|
||||
"emoji_button.people": "Parsunaghji",
|
||||
"emoji_button.recent": "Assai utilizati",
|
||||
"emoji_button.search": "Cercà...",
|
||||
"emoji_button.search_results": "Risultati di a cerca",
|
||||
"emoji_button.symbols": "Simbuli",
|
||||
"emoji_button.travel": "Lochi è Viaghju",
|
||||
"empty_column.community": "Ùn c'hè nunda indè a linea lucale. Scrivete puru qualcosa!",
|
||||
"empty_column.direct": "Ùn avete ancu nisun missaghju direttu. S'è voi mandate o ricevete unu, u vidarete quì.",
|
||||
"empty_column.hashtag": "Ùn c'hè ancu nunda quì.",
|
||||
"empty_column.home": "A vostr'accolta hè viota! Pudete andà nant'à {public} o pruvà a ricerca per truvà parsone da siguità.",
|
||||
"empty_column.home.public_timeline": "a linea pubblica",
|
||||
"empty_column.list": "Ùn c'hè ancu nunda quì. Quandu membri di sta lista manderanu novi statuti, i vidarete quì.",
|
||||
"empty_column.notifications": "Ùn avete ancu nisuna nutificazione. Interact with others to start the conversation.",
|
||||
"empty_column.public": "Ùn c'hè nunda quì! Scrivete qualcosa in pubblicu o seguitate utilizatori d'altre istanze per empie a linea pubblica.",
|
||||
"follow_request.authorize": "Auturizà",
|
||||
"follow_request.reject": "Righjittà",
|
||||
"getting_started.appsshort": "Applicazione",
|
||||
"getting_started.faq": "FAQ",
|
||||
"getting_started.heading": "Per principià",
|
||||
"getting_started.open_source_notice": "Mastodon ghjè un lugiziale liberu. Pudete cuntribuisce à u codice o a traduzione, o palisà un bug, nant'à GitHub: {github}",
|
||||
"getting_started.userguide": "Guida d'utilizazione",
|
||||
"home.column_settings.advanced": "Avanzati",
|
||||
"home.column_settings.basic": "Bàsichi",
|
||||
"home.column_settings.filter_regex": "Filtrà cù spressione regulare (regex)",
|
||||
"home.column_settings.show_reblogs": "Vede e spartere",
|
||||
"home.column_settings.show_replies": "Vede e risposte",
|
||||
"home.settings": "Parametri di a colonna",
|
||||
"keyboard_shortcuts.back": "rivultà",
|
||||
"keyboard_shortcuts.boost": "sparte",
|
||||
"keyboard_shortcuts.column": "fucalizà un statutu indè una colonna",
|
||||
"keyboard_shortcuts.compose": "fucalizà nant'à l'area di ridazzione",
|
||||
"keyboard_shortcuts.description": "Descrizzione",
|
||||
"keyboard_shortcuts.down": "falà indè a lista",
|
||||
"keyboard_shortcuts.enter": "apre u statutu",
|
||||
"keyboard_shortcuts.favourite": "aghjunghje à i favuriti",
|
||||
"keyboard_shortcuts.heading": "Accorte cù a tastera",
|
||||
"keyboard_shortcuts.hotkey": "Accorta",
|
||||
"keyboard_shortcuts.legend": "vede a legenda",
|
||||
"keyboard_shortcuts.mention": "mintuvà l'autore",
|
||||
"keyboard_shortcuts.reply": "risponde",
|
||||
"keyboard_shortcuts.search": "fucalizà nant'à l'area di circata",
|
||||
"keyboard_shortcuts.toggle_hidden": "vede/piattà u testu daretu à l'avertimentu CW",
|
||||
"keyboard_shortcuts.toot": "scrive un novu statutu",
|
||||
"keyboard_shortcuts.unfocus": "ùn fucalizà più l'area di testu",
|
||||
"keyboard_shortcuts.up": "cullà indè a lista",
|
||||
"lightbox.close": "Chjudà",
|
||||
"lightbox.next": "Siguente",
|
||||
"lightbox.previous": "Pricidente",
|
||||
"lists.account.add": "Aghjunghje à a lista",
|
||||
"lists.account.remove": "Toglie di a lista",
|
||||
"lists.delete": "Supprime a lista",
|
||||
"lists.edit": "Mudificà a lista",
|
||||
"lists.new.create": "Aghjustà una lista",
|
||||
"lists.new.title_placeholder": "Titulu di a lista",
|
||||
"lists.search": "Circà indè i vostr'abbunamenti",
|
||||
"lists.subheading": "E vo liste",
|
||||
"loading_indicator.label": "Caricamentu...",
|
||||
"media_gallery.toggle_visible": "Cambià a visibilità",
|
||||
"missing_indicator.label": "Micca trovu",
|
||||
"missing_indicator.sublabel": "Ùn era micca pussivule di truvà sta risorsa",
|
||||
"mute_modal.hide_notifications": "Piattà nutificazione da st'utilizatore?",
|
||||
"navigation_bar.blocks": "Utilizatori bluccati",
|
||||
"navigation_bar.community_timeline": "Linea pubblica lucale",
|
||||
"navigation_bar.direct": "Missaghji diretti",
|
||||
"navigation_bar.domain_blocks": "Duminii piattati",
|
||||
"navigation_bar.edit_profile": "Mudificà u prufile",
|
||||
"navigation_bar.favourites": "Favuriti",
|
||||
"navigation_bar.follow_requests": "Dumande d'abbunamentu",
|
||||
"navigation_bar.info": "À prupositu di l'istanza",
|
||||
"navigation_bar.keyboard_shortcuts": "Accorte cù a tastera",
|
||||
"navigation_bar.lists": "Liste",
|
||||
"navigation_bar.logout": "Scunnettassi",
|
||||
"navigation_bar.mutes": "Utilizatori piattati",
|
||||
"navigation_bar.pins": "Statuti puntarulati",
|
||||
"navigation_bar.preferences": "Preferenze",
|
||||
"navigation_bar.public_timeline": "Linea pubblica glubale",
|
||||
"notification.favourite": "{name} hà aghjuntu u vostru statutu à i so favuriti",
|
||||
"notification.follow": "{name} v'hà seguitatu",
|
||||
"notification.mention": "{name} v'hà mintuvatu",
|
||||
"notification.reblog": "{name} hà spartutu u vostru statutu",
|
||||
"notifications.clear": "Purgà e nutificazione",
|
||||
"notifications.clear_confirmation": "Site sicuru·a che vulete toglie tutte ste nutificazione?",
|
||||
"notifications.column_settings.alert": "Nutificazione nant'à l'urdinatore",
|
||||
"notifications.column_settings.favourite": "Favuriti:",
|
||||
"notifications.column_settings.follow": "Abbunati novi:",
|
||||
"notifications.column_settings.mention": "Minzione:",
|
||||
"notifications.column_settings.push": "Nutificazione Push",
|
||||
"notifications.column_settings.push_meta": "Quess'apparechju",
|
||||
"notifications.column_settings.reblog": "Spartere:",
|
||||
"notifications.column_settings.show": "Mustrà indè a colonna",
|
||||
"notifications.column_settings.sound": "Sunà",
|
||||
"onboarding.done": "Fatta",
|
||||
"onboarding.next": "Siguente",
|
||||
"onboarding.page_five.public_timelines": "A linea pubblica lucale mostra statuti pubblichi da tuttu u mondu nant'à {domain}. A linea pubblica glubale mostra ancu quelli di a ghjente seguitata da l'utilizatori di {domain}. Quesse sò una bona manera d'incuntrà nove parsone.",
|
||||
"onboarding.page_four.home": "A linea d'accolta mostra i statuti di i vostr'abbunamenti.",
|
||||
"onboarding.page_four.notifications": "A colonna di nutificazione mostra l'interazzione ch'altre parsone anu cù u vostru contu.",
|
||||
"onboarding.page_one.federation": "Mastodon ghjè una rete di servori independenti, chjamati istanze, uniti indè una sola rete suciale.",
|
||||
"onboarding.page_one.full_handle": "U vostru identificatore cumplettu",
|
||||
"onboarding.page_one.handle_hint": "Quessu ghjè cio chì direte à i vostri amichi per circavi.",
|
||||
"onboarding.page_one.welcome": "Benvenuti/a nant'à Mastodon!",
|
||||
"onboarding.page_six.admin": "L'amministratore di a vostr'istanza hè {admin}.",
|
||||
"onboarding.page_six.almost_done": "Quasgi finitu...",
|
||||
"onboarding.page_six.appetoot": "Bon Appetoot!",
|
||||
"onboarding.page_six.apps_available": "Ci sò {apps} dispunibule per iOS, Android è altre piattaforme.",
|
||||
"onboarding.page_six.github": "Mastodon ghjè un lugiziale liberu. Pudete cuntribuisce à u codice o a traduzione, o palisà un prublemu, nant'à {github}.",
|
||||
"onboarding.page_six.guidelines": "regule di a cumunità",
|
||||
"onboarding.page_six.read_guidelines": "Ùn vi scurdate di leghje e {guidelines} di {domain}",
|
||||
"onboarding.page_six.various_app": "applicazione pè u telefuninu",
|
||||
"onboarding.page_three.profile": "Pudete mudificà u prufile per cambia u ritrattu, a descrizzione è u nome affissatu. Ci sò ancu alcun'altre preferenze.",
|
||||
"onboarding.page_three.search": "Fate usu di l'area di ricerca per truvà altre persone è vede hashtag cum'è {illustration} o {introductions}. Per vede qualcunu ch'ùn hè micca nant'à st'istanza, cercate u so identificatore complettu (pare un'email).",
|
||||
"onboarding.page_two.compose": "I statuti è missaghji si scrivenu indè l'area di ridazzione. Pudete caricà imagine, cambià i parametri di pubblicazione, è mette avertimenti di cuntenuti cù i buttoni quì sottu.",
|
||||
"onboarding.skip": "Passà",
|
||||
"privacy.change": "Mudificà a cunfidenzialità di u statutu",
|
||||
"privacy.direct.long": "Mandà solu à quelli chì so mintuvati",
|
||||
"privacy.direct.short": "Direttu",
|
||||
"privacy.private.long": "Mustrà solu à l'abbunati",
|
||||
"privacy.private.short": "Privatu",
|
||||
"privacy.public.long": "Mustrà à tuttu u mondu nant'a linea pubblica",
|
||||
"privacy.public.short": "Pubblicu",
|
||||
"privacy.unlisted.long": "Ùn mette micca nant'a linea pubblica (ma tutt'u mondu pò vede u statutu nant'à u vostru prufile)",
|
||||
"privacy.unlisted.short": "Micca listatu",
|
||||
"regeneration_indicator.label": "Caricamentu…",
|
||||
"regeneration_indicator.sublabel": "Priparazione di a vostra pagina d'accolta",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
"relative_time.just_now": "avà",
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"reply_indicator.cancel": "Annullà",
|
||||
"report.forward": "Trasferisce à {target}",
|
||||
"report.forward_hint": "U contu hè nant'à un'altru servore. Vulete ancu mandà una copia anonima di u signalamentu quallà?",
|
||||
"report.hint": "U signalamentu sarà mandatu à i muderatori di l'istanza. Pudete spiegà perchè avete palisatu stu contu quì sottu:",
|
||||
"report.placeholder": "Altri cummenti",
|
||||
"report.submit": "Mandà",
|
||||
"report.target": "Signalamentu",
|
||||
"search.placeholder": "Circà",
|
||||
"search_popout.search_format": "Ricerca avanzata",
|
||||
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
|
||||
"search_popout.tips.hashtag": "hashtag",
|
||||
"search_popout.tips.status": "statutu",
|
||||
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
"search_popout.tips.user": "utilizatore",
|
||||
"search_results.accounts": "Ghjente",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.statuses": "Statuti",
|
||||
"search_results.total": "{count, number} {count, plural, one {risultatu} other {risultati}}",
|
||||
"standalone.public_title": "Una vista di...",
|
||||
"status.block": "Bluccà @{name}",
|
||||
"status.cancel_reblog_private": "Ùn sparte più",
|
||||
"status.cannot_reblog": "Stu statutu ùn pò micca esse spartutu",
|
||||
"status.delete": "Toglie",
|
||||
"status.direct": "Mandà un missaghju @{name}",
|
||||
"status.embed": "Integrà",
|
||||
"status.favourite": "Aghjunghje à i favuriti",
|
||||
"status.load_more": "Vede di più",
|
||||
"status.media_hidden": "Media piattata",
|
||||
"status.mention": "Mintuvà @{name}",
|
||||
"status.more": "Più",
|
||||
"status.mute": "Piattà @{name}",
|
||||
"status.mute_conversation": "Piattà a cunversazione",
|
||||
"status.open": "Apre stu statutu",
|
||||
"status.pin": "Puntarulà à u prufile",
|
||||
"status.pinned": "Statutu puntarulatu",
|
||||
"status.reblog": "Sparte",
|
||||
"status.reblog_private": "Sparte à l'audienza uriginale",
|
||||
"status.reblogged_by": "{name} hà spartutu",
|
||||
"status.reply": "Risponde",
|
||||
"status.replyAll": "Risponde à tutti",
|
||||
"status.report": "Palisà @{name}",
|
||||
"status.sensitive_toggle": "Cliccate per vede",
|
||||
"status.sensitive_warning": "Cuntinutu sensibile",
|
||||
"status.share": "Sparte",
|
||||
"status.show_less": "Ripiegà",
|
||||
"status.show_less_all": "Ripiegà tuttu",
|
||||
"status.show_more": "Slibrà",
|
||||
"status.show_more_all": "Slibrà tuttu",
|
||||
"status.unmute_conversation": "Ùn piattà più a cunversazione",
|
||||
"status.unpin": "Spuntarulà da u prufile",
|
||||
"tabs_bar.federated_timeline": "Glubale",
|
||||
"tabs_bar.home": "Accolta",
|
||||
"tabs_bar.local_timeline": "Lucale",
|
||||
"tabs_bar.notifications": "Nutificazione",
|
||||
"tabs_bar.search": "Cercà",
|
||||
"ui.beforeunload": "A bruttacopia sarà persa s'ellu hè chjosu Mastodon.",
|
||||
"upload_area.title": "Drag & drop per caricà un fugliale",
|
||||
"upload_button.label": "Aghjunghje un media",
|
||||
"upload_form.description": "Discrive per i malvistosi",
|
||||
"upload_form.focus": "Riquatrà",
|
||||
"upload_form.undo": "Annullà",
|
||||
"upload_progress.label": "Caricamentu...",
|
||||
"video.close": "Chjudà a video",
|
||||
"video.exit_fullscreen": "Caccià u pienu screnu",
|
||||
"video.expand": "Ingrandà a video",
|
||||
"video.fullscreen": "Pienu screnu",
|
||||
"video.hide": "Piattà a video",
|
||||
"video.mute": "Surdina",
|
||||
"video.pause": "Pausa",
|
||||
"video.play": "Lettura",
|
||||
"video.unmute": "Caccià a surdina"
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
"account.block": "Απόκλεισε τον/την @{name}",
|
||||
"account.block_domain": "Απόκρυψε τα πάντα από τον/την",
|
||||
"account.blocked": "Αποκλεισμένος/η",
|
||||
"account.direct": "Απευθείας μήνυμα προς @{name}",
|
||||
"account.direct": "Άμεσο μήνυμα προς @{name}",
|
||||
"account.disclaimer_full": "Οι παρακάτω πληροφορίες μπορει να μην αντανακλούν το προφίλ του χρήστη επαρκως.",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.domain_blocked": "Κρυμμένος τομέας",
|
||||
"account.edit_profile": "Επεξεργάσου το προφίλ",
|
||||
"account.follow": "Ακολούθησε",
|
||||
"account.followers": "Ακόλουθοι",
|
||||
@@ -23,15 +23,15 @@
|
||||
"account.requested": "Εκκρεμεί έγκριση. Κάνε κλικ για να ακυρώσεις το αίτημα ακολούθησης",
|
||||
"account.share": "Μοιράσου το προφίλ του/της @{name}",
|
||||
"account.show_reblogs": "Δείξε τις προωθήσεις του/της @{name}",
|
||||
"account.unblock": "Unblock @{name}",
|
||||
"account.unblock": "Ξεμπλόκαρε τον/την @{name}",
|
||||
"account.unblock_domain": "Αποκάλυψε το {domain}",
|
||||
"account.unfollow": "Unfollow",
|
||||
"account.unmute": "Unmute @{name}",
|
||||
"account.unmute_notifications": "Unmute notifications from @{name}",
|
||||
"account.unfollow": "Διακοπή παρακολούθησης",
|
||||
"account.unmute": "Διακοπή αποσιώπησης του/της @{name}",
|
||||
"account.unmute_notifications": "Διακοπή αποσιώπησης ειδοποιήσεων του/της @{name}",
|
||||
"account.view_full_profile": "Δες το πλήρες προφίλ",
|
||||
"alert.unexpected.message": "Προέκυψε απροσδόκητο σφάλμα.",
|
||||
"alert.unexpected.title": "Εεπ!",
|
||||
"boost_modal.combo": "You can press {combo} to skip this next time",
|
||||
"boost_modal.combo": "Μπορείς να πατήσεις {combo} για να το προσπεράσεις αυτό την επόμενη φορά",
|
||||
"bundle_column_error.body": "Κάτι πήγε στραβά ενώ φορτωνόταν αυτό το στοιχείο.",
|
||||
"bundle_column_error.retry": "Δοκίμασε ξανά",
|
||||
"bundle_column_error.title": "Σφάλμα δικτύου",
|
||||
@@ -41,7 +41,7 @@
|
||||
"column.blocks": "Αποκλεισμένοι χρήστες",
|
||||
"column.community": "Τοπική ροή",
|
||||
"column.direct": "Απευθείας μηνύματα",
|
||||
"column.domain_blocks": "Hidden domains",
|
||||
"column.domain_blocks": "Κρυμμένοι τομείς",
|
||||
"column.favourites": "Αγαπημένα",
|
||||
"column.follow_requests": "Αιτήματα παρακολούθησης",
|
||||
"column.home": "Αρχική",
|
||||
@@ -60,7 +60,7 @@
|
||||
"column_subheading.navigation": "Πλοήγηση",
|
||||
"column_subheading.settings": "Ρυθμίσεις",
|
||||
"compose_form.direct_message_warning": "Αυτό το τουτ θα εμφανίζεται μόνο σε όλους τους αναφερόμενους χρήστες.",
|
||||
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
|
||||
"compose_form.hashtag_warning": "Αυτό το τουτ δεν θα εμφανίζεται κάτω από καμία ταμπέλα καθώς είναι αφανές. Μόνο τα δημόσια τουτ μπορούν να αναζητηθούν ανά ταμπέλα.",
|
||||
"compose_form.lock_disclaimer": "Ο λογαριασμός σου δεν είναι {locked}. Οποιοσδήποτε μπορεί να σε ακολουθήσει για να δει τις δημοσιεύσεις σας προς τους ακολούθους σας.",
|
||||
"compose_form.lock_disclaimer.lock": "κλειδωμένος",
|
||||
"compose_form.placeholder": "Τι σκέφτεσαι;",
|
||||
@@ -78,65 +78,65 @@
|
||||
"confirmations.delete.message": "Σίγουρα θες να διαγράψεις αυτή την κατάσταση;",
|
||||
"confirmations.delete_list.confirm": "Διέγραψε",
|
||||
"confirmations.delete_list.message": "Σίγουρα θες να διαγράψεις οριστικά αυτή τη λίστα;",
|
||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
|
||||
"confirmations.mute.confirm": "Mute",
|
||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||
"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.appsshort": "Apps",
|
||||
"confirmations.domain_block.confirm": "Απόκρυψη ολόκληρου του τομέα",
|
||||
"confirmations.domain_block.message": "Σίγουρα θες να μπλοκάρεις ολόκληρο το {domain}; Συνήθως μερικά εστιασμένα μπλοκ ή αποσιωπήσεις επαρκούν και προτιμούνται.",
|
||||
"confirmations.mute.confirm": "Αποσιώπηση",
|
||||
"confirmations.mute.message": "Σίγουρα θες να αποσιωπήσεις τον/την {name};",
|
||||
"confirmations.unfollow.confirm": "Διακοπή παρακολούθησης",
|
||||
"confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};",
|
||||
"embed.instructions": "Ενσωματώστε αυτή την κατάσταση στην ιστοσελίδα σας αντιγράφοντας τον παρακάτω κώδικα.",
|
||||
"embed.preview": "Ορίστε πως θα φαίνεται:",
|
||||
"emoji_button.activity": "Δραστηριότητα",
|
||||
"emoji_button.custom": "Προσαρμοσμένα",
|
||||
"emoji_button.flags": "Σημαίες",
|
||||
"emoji_button.food": "Φαγητά & Ποτά",
|
||||
"emoji_button.label": "Εισάγετε emoji",
|
||||
"emoji_button.nature": "Φύση",
|
||||
"emoji_button.not_found": "Ουδέν emojo!! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.objects": "Αντικείμενα",
|
||||
"emoji_button.people": "Άνθρωποι",
|
||||
"emoji_button.recent": "Δημοφιλή",
|
||||
"emoji_button.search": "Αναζήτηση…",
|
||||
"emoji_button.search_results": "Αποτελέσματα αναζήτησης",
|
||||
"emoji_button.symbols": "Σύμβολα",
|
||||
"emoji_button.travel": "Ταξίδια & Τοποθεσίες",
|
||||
"empty_column.community": "Η τοπική ροή είναι κενή. Γράψε κάτι δημόσιο παραμύθι ν' αρχινίσει!",
|
||||
"empty_column.direct": "Δεν έχεις απευθείας μηνύματα ακόμα. Όταν στείλεις ή λάβεις κανένα, θα εμφανιστεί εδώ.",
|
||||
"empty_column.hashtag": "Δεν υπάρχει ακόμα κάτι για αυτή την ταμπέλα.",
|
||||
"empty_column.home": "Η τοπική σου ροή είναι κενή! Πήγαινε στο {public} ή κάνε αναζήτηση για να ξεκινήσεις και να γνωρίσεις άλλους χρήστες.",
|
||||
"empty_column.home.public_timeline": "η δημόσια ροή",
|
||||
"empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμα. Όταν τα μέλη της δημοσιεύσουν νέες καταστάσεις, θα εμφανιστούν εδώ",
|
||||
"empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμα. Αλληλεπίδρασε με άλλους χρήστες για να ξεκινήσεις την κουβέντα.",
|
||||
"empty_column.public": "Δεν υπάρχει τίποτα εδώ! Γράψε κάτι δημόσιο, ή ακολούθησε χειροκίνητα χρήστες από άλλα instances για να το γεμίσεις",
|
||||
"follow_request.authorize": "Ενέκρινε",
|
||||
"follow_request.reject": "Απέρριψε",
|
||||
"getting_started.appsshort": "Εφαρμογές",
|
||||
"getting_started.faq": "FAQ",
|
||||
"getting_started.heading": "Getting started",
|
||||
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
|
||||
"getting_started.userguide": "User Guide",
|
||||
"home.column_settings.advanced": "Advanced",
|
||||
"home.column_settings.basic": "Basic",
|
||||
"home.column_settings.filter_regex": "Filter out by regular expressions",
|
||||
"home.column_settings.show_reblogs": "Show boosts",
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.settings": "Column settings",
|
||||
"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",
|
||||
"getting_started.heading": "Ξεκινώντας",
|
||||
"getting_started.open_source_notice": "Το Mastodon είναι ελεύθερο λογισμικό. Μπορείς να συνεισφέρεις ή να αναφέρεις ζητήματα στο GitHub στο {github}.",
|
||||
"getting_started.userguide": "Οδηγός Χρηστών",
|
||||
"home.column_settings.advanced": "Προχωρημένα",
|
||||
"home.column_settings.basic": "Βασικά",
|
||||
"home.column_settings.filter_regex": "Φιλτράρετε μέσω regular expressions",
|
||||
"home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων",
|
||||
"home.column_settings.show_replies": "Εμφάνιση απαντήσεων",
|
||||
"home.settings": "Ρυθμίσεις στηλών",
|
||||
"keyboard_shortcuts.back": "για επιστροφή πίσω",
|
||||
"keyboard_shortcuts.boost": "για προώθηση",
|
||||
"keyboard_shortcuts.column": "για εστίαση μιας κατάστασης σε μια από τις στήλες",
|
||||
"keyboard_shortcuts.compose": "για εστίαση στην περιοχή κειμένου συγγραφής",
|
||||
"keyboard_shortcuts.description": "Description",
|
||||
"keyboard_shortcuts.down": "to move down in the list",
|
||||
"keyboard_shortcuts.down": "για κίνηση προς τα κάτω στη λίστα",
|
||||
"keyboard_shortcuts.enter": "to open status",
|
||||
"keyboard_shortcuts.favourite": "to favourite",
|
||||
"keyboard_shortcuts.favourite": "για σημείωση αγαπημένου",
|
||||
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
|
||||
"keyboard_shortcuts.hotkey": "Hotkey",
|
||||
"keyboard_shortcuts.legend": "to display this legend",
|
||||
"keyboard_shortcuts.mention": "to mention author",
|
||||
"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.hotkey": "Συντόμευση",
|
||||
"keyboard_shortcuts.legend": "για να εμφανίσεις αυτόν τον οδηγό",
|
||||
"keyboard_shortcuts.mention": "για να αναφέρεις το συγγραφέα",
|
||||
"keyboard_shortcuts.reply": "για απάντηση",
|
||||
"keyboard_shortcuts.search": "για εστίαση αναζήτησης",
|
||||
"keyboard_shortcuts.toggle_hidden": "για εμφάνιση/απόκρυψη κειμένου πίσω από την προειδοποίηση",
|
||||
"keyboard_shortcuts.toot": "για δημιουργία ολοκαίνουριου τουτ",
|
||||
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
||||
"keyboard_shortcuts.up": "to move up in the list",
|
||||
"lightbox.close": "Close",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"account.block": "Bloki @{name}",
|
||||
"account.block_domain": "Kaŝi ĉion de {domain}",
|
||||
"account.blocked": "Blokita",
|
||||
"account.direct": "Direct message @{name}",
|
||||
"account.direct": "Rekte mesaĝi @{name}",
|
||||
"account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.",
|
||||
"account.domain_blocked": "Domajno kaŝita",
|
||||
"account.edit_profile": "Redakti profilon",
|
||||
@@ -29,8 +29,8 @@
|
||||
"account.unmute": "Malsilentigi @{name}",
|
||||
"account.unmute_notifications": "Malsilentigi sciigojn de @{name}",
|
||||
"account.view_full_profile": "Vidi plenan profilon",
|
||||
"alert.unexpected.message": "An unexpected error occurred.",
|
||||
"alert.unexpected.title": "Oops!",
|
||||
"alert.unexpected.message": "Neatendita eraro okazis.",
|
||||
"alert.unexpected.title": "Ups!",
|
||||
"boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje",
|
||||
"bundle_column_error.body": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
|
||||
"bundle_column_error.retry": "Bonvolu reprovi",
|
||||
@@ -40,8 +40,8 @@
|
||||
"bundle_modal_error.retry": "Bonvolu reprovi",
|
||||
"column.blocks": "Blokitaj uzantoj",
|
||||
"column.community": "Loka tempolinio",
|
||||
"column.direct": "Direct messages",
|
||||
"column.domain_blocks": "Hidden domains",
|
||||
"column.direct": "Rektaj mesaĝoj",
|
||||
"column.domain_blocks": "Kaŝitaj domajnoj",
|
||||
"column.favourites": "Stelumoj",
|
||||
"column.follow_requests": "Petoj de sekvado",
|
||||
"column.home": "Hejmo",
|
||||
@@ -59,7 +59,7 @@
|
||||
"column_header.unpin": "Depingli",
|
||||
"column_subheading.navigation": "Navigado",
|
||||
"column_subheading.settings": "Agordado",
|
||||
"compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
|
||||
"compose_form.direct_message_warning": "Tiu mesaĝo videblos nur por ĉiuj menciitaj uzantoj.",
|
||||
"compose_form.hashtag_warning": "Ĉi tiu mesaĝo ne estos listigita per ajna kradvorto. Nur publikaj mesaĝoj estas serĉeblaj per kradvortoj.",
|
||||
"compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn, kiuj estas nur por sekvantoj.",
|
||||
"compose_form.lock_disclaimer.lock": "ŝlosita",
|
||||
@@ -101,7 +101,7 @@
|
||||
"emoji_button.symbols": "Simboloj",
|
||||
"emoji_button.travel": "Vojaĝoj kaj lokoj",
|
||||
"empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!",
|
||||
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
|
||||
"empty_column.direct": "Vi ankoraŭ ne havas rektan mesaĝon. Kiam vi sendos aŭ ricevos iun, ĝi aperos ĉi tie.",
|
||||
"empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
|
||||
"empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
|
||||
"empty_column.home.public_timeline": "la publikan tempolinion",
|
||||
@@ -135,7 +135,7 @@
|
||||
"keyboard_shortcuts.mention": "por mencii la aŭtoron",
|
||||
"keyboard_shortcuts.reply": "por respondi",
|
||||
"keyboard_shortcuts.search": "por fokusigi la serĉilon",
|
||||
"keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
|
||||
"keyboard_shortcuts.toggle_hidden": "por montri/kaŝi tekston malantaŭ enhava averto",
|
||||
"keyboard_shortcuts.toot": "por komenci tute novan mesaĝon",
|
||||
"keyboard_shortcuts.unfocus": "por malfokusigi la tekstujon aŭ la serĉilon",
|
||||
"keyboard_shortcuts.up": "por iri supren en la listo",
|
||||
@@ -157,8 +157,8 @@
|
||||
"mute_modal.hide_notifications": "Ĉu vi volas kaŝi la sciigojn el ĉi tiu uzanto?",
|
||||
"navigation_bar.blocks": "Blokitaj uzantoj",
|
||||
"navigation_bar.community_timeline": "Loka tempolinio",
|
||||
"navigation_bar.direct": "Direct messages",
|
||||
"navigation_bar.domain_blocks": "Hidden domains",
|
||||
"navigation_bar.direct": "Rektaj mesaĝoj",
|
||||
"navigation_bar.domain_blocks": "Kaŝitaj domajnoj",
|
||||
"navigation_bar.edit_profile": "Redakti profilon",
|
||||
"navigation_bar.favourites": "Stelumoj",
|
||||
"navigation_bar.follow_requests": "Petoj de sekvado",
|
||||
@@ -242,10 +242,10 @@
|
||||
"search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}",
|
||||
"standalone.public_title": "Enrigardo…",
|
||||
"status.block": "Bloki @{name}",
|
||||
"status.cancel_reblog_private": "Unboost",
|
||||
"status.cancel_reblog_private": "Eksdiskonigi",
|
||||
"status.cannot_reblog": "Ĉi tiu mesaĝo ne diskonigeblas",
|
||||
"status.delete": "Forigi",
|
||||
"status.direct": "Direct message @{name}",
|
||||
"status.direct": "Rekte mesaĝi @{name}",
|
||||
"status.embed": "Enkorpigi",
|
||||
"status.favourite": "Stelumi",
|
||||
"status.load_more": "Ŝargi pli",
|
||||
@@ -258,7 +258,7 @@
|
||||
"status.pin": "Alpingli profile",
|
||||
"status.pinned": "Alpinglita mesaĝo",
|
||||
"status.reblog": "Diskonigi",
|
||||
"status.reblog_private": "Boost to original audience",
|
||||
"status.reblog_private": "Diskonigi al la originala atentaro",
|
||||
"status.reblogged_by": "{name} diskonigis",
|
||||
"status.reply": "Respondi",
|
||||
"status.replyAll": "Respondi al la fadeno",
|
||||
@@ -276,7 +276,7 @@
|
||||
"tabs_bar.home": "Hejmo",
|
||||
"tabs_bar.local_timeline": "Loka tempolinio",
|
||||
"tabs_bar.notifications": "Sciigoj",
|
||||
"tabs_bar.search": "Search",
|
||||
"tabs_bar.search": "Serĉi",
|
||||
"ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.",
|
||||
"upload_area.title": "Altreni kaj lasi por alŝuti",
|
||||
"upload_button.label": "Aldoni aŭdovidaĵon",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"account.hide_reblogs": "Masquer les partages de @{name}",
|
||||
"account.media": "Média",
|
||||
"account.mention": "Mentionner",
|
||||
"account.moved_to": "{name} a déménagé vers :",
|
||||
"account.moved_to": "{name} a déménagé vers :",
|
||||
"account.mute": "Masquer @{name}",
|
||||
"account.mute_notifications": "Ignorer les notifications de @{name}",
|
||||
"account.muted": "Silencé",
|
||||
@@ -30,7 +30,7 @@
|
||||
"account.unmute_notifications": "Réactiver les notifications de @{name}",
|
||||
"account.view_full_profile": "Afficher le profil complet",
|
||||
"alert.unexpected.message": "Une erreur non-attendue s'est produite.",
|
||||
"alert.unexpected.title": "Oups !",
|
||||
"alert.unexpected.title": "Oups !",
|
||||
"boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois",
|
||||
"bundle_column_error.body": "Une erreur s’est produite lors du chargement de ce composant.",
|
||||
"bundle_column_error.retry": "Réessayer",
|
||||
@@ -77,7 +77,7 @@
|
||||
"confirmations.delete.confirm": "Supprimer",
|
||||
"confirmations.delete.message": "Confirmez-vous la suppression de ce pouet ?",
|
||||
"confirmations.delete_list.confirm": "Supprimer",
|
||||
"confirmations.delete_list.message": "Êtes-vous sûr de vouloir supprimer définitivement cette liste ?",
|
||||
"confirmations.delete_list.message": "Êtes-vous sûr de vouloir supprimer définitivement cette liste ?",
|
||||
"confirmations.domain_block.confirm": "Masquer le domaine entier",
|
||||
"confirmations.domain_block.message": "Êtes-vous vraiment, vraiment sûr⋅e de vouloir bloquer {domain} en entier ? Dans la plupart des cas, quelques blocages ou masquages ciblés sont suffisants et préférables.",
|
||||
"confirmations.mute.confirm": "Masquer",
|
||||
@@ -85,14 +85,14 @@
|
||||
"confirmations.unfollow.confirm": "Ne plus suivre",
|
||||
"confirmations.unfollow.message": "Voulez-vous arrêter de suivre {name} ?",
|
||||
"embed.instructions": "Intégrez ce statut à votre site en copiant le code ci-dessous.",
|
||||
"embed.preview": "Il apparaîtra comme cela :",
|
||||
"embed.preview": "Il apparaîtra comme cela :",
|
||||
"emoji_button.activity": "Activités",
|
||||
"emoji_button.custom": "Personnalisés",
|
||||
"emoji_button.flags": "Drapeaux",
|
||||
"emoji_button.food": "Nourriture & Boisson",
|
||||
"emoji_button.label": "Insérer un émoji",
|
||||
"emoji_button.nature": "Nature",
|
||||
"emoji_button.not_found": "Pas d'emojis !! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.not_found": "Pas d'emojis !! (╯°□°)╯︵ ┻━┻",
|
||||
"emoji_button.objects": "Objets",
|
||||
"emoji_button.people": "Personnages",
|
||||
"emoji_button.recent": "Fréquemment utilisés",
|
||||
@@ -154,7 +154,7 @@
|
||||
"media_gallery.toggle_visible": "Modifier la visibilité",
|
||||
"missing_indicator.label": "Non trouvé",
|
||||
"missing_indicator.sublabel": "Ressource introuvable",
|
||||
"mute_modal.hide_notifications": "Masquer les notifications de cette personne ?",
|
||||
"mute_modal.hide_notifications": "Masquer les notifications de cette personne ?",
|
||||
"navigation_bar.blocks": "Comptes bloqués",
|
||||
"navigation_bar.community_timeline": "Fil public local",
|
||||
"navigation_bar.direct": "Messages directs",
|
||||
@@ -177,9 +177,9 @@
|
||||
"notifications.clear": "Nettoyer les notifications",
|
||||
"notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?",
|
||||
"notifications.column_settings.alert": "Notifications locales",
|
||||
"notifications.column_settings.favourite": "Favoris :",
|
||||
"notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e·s :",
|
||||
"notifications.column_settings.mention": "Mentions :",
|
||||
"notifications.column_settings.favourite": "Favoris :",
|
||||
"notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e·s :",
|
||||
"notifications.column_settings.mention": "Mentions :",
|
||||
"notifications.column_settings.push": "Notifications push",
|
||||
"notifications.column_settings.push_meta": "Cet appareil",
|
||||
"notifications.column_settings.reblog": "Partages :",
|
||||
@@ -216,7 +216,7 @@
|
||||
"privacy.unlisted.long": "Ne pas afficher dans les fils publics",
|
||||
"privacy.unlisted.short": "Non-listé",
|
||||
"regeneration_indicator.label": "Chargement…",
|
||||
"regeneration_indicator.sublabel": "Le flux de votre page principale est en cours de préparation !",
|
||||
"regeneration_indicator.sublabel": "Le flux de votre page principale est en cours de préparation !",
|
||||
"relative_time.days": "{number} j",
|
||||
"relative_time.hours": "{number} h",
|
||||
"relative_time.just_now": "à l’instant",
|
||||
@@ -224,8 +224,8 @@
|
||||
"relative_time.seconds": "{number} s",
|
||||
"reply_indicator.cancel": "Annuler",
|
||||
"report.forward": "Transférer à {target}",
|
||||
"report.forward_hint": "Le compte provient d'un autre serveur. Envoyez également une copie anonyme du rapport ?",
|
||||
"report.hint": "Le rapport sera envoyé aux modérateurs de votre instance. Vous pouvez expliquer pourquoi vous signalez ce compte ci-dessous :",
|
||||
"report.forward_hint": "Le compte provient d'un autre serveur. Envoyez également une copie anonyme du rapport ?",
|
||||
"report.hint": "Le rapport sera envoyé aux modérateurs de votre instance. Vous pouvez expliquer pourquoi vous signalez ce compte ci-dessous :",
|
||||
"report.placeholder": "Commentaires additionnels",
|
||||
"report.submit": "Envoyer",
|
||||
"report.target": "Signalement",
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
"empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public",
|
||||
"follow_request.authorize": "Autorizar",
|
||||
"follow_request.reject": "Regetar",
|
||||
"getting_started.appsshort": "Apps",
|
||||
"getting_started.appsshort": "Aplicacions",
|
||||
"getting_started.faq": "FAQ",
|
||||
"getting_started.heading": "Per començar",
|
||||
"getting_started.open_source_notice": "Mastodon es un logicial liure. Podètz contribuir e mandar vòstres comentaris e rapòrt de bug via {github} sus GitHub.",
|
||||
@@ -158,7 +158,7 @@
|
||||
"navigation_bar.blocks": "Personas blocadas",
|
||||
"navigation_bar.community_timeline": "Flux public local",
|
||||
"navigation_bar.direct": "Messatges dirèctes",
|
||||
"navigation_bar.domain_blocks": "Hidden domains",
|
||||
"navigation_bar.domain_blocks": "Domenis amagats",
|
||||
"navigation_bar.edit_profile": "Modificar lo perfil",
|
||||
"navigation_bar.favourites": "Favorits",
|
||||
"navigation_bar.follow_requests": "Demandas d’abonament",
|
||||
|
||||
@@ -231,7 +231,7 @@
|
||||
"report.target": "Nahlásenie {target}",
|
||||
"search.placeholder": "Hľadaj",
|
||||
"search_popout.search_format": "Pokročilé vyhľadávanie",
|
||||
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
|
||||
"search_popout.tips.full_text": "Jednoduchý textový výpis statusov ktoré si napísal/a, ktoré si obľúbil/a, povýšil/a, alebo aj tých, v ktorých si bol/a spomenutý/á, a potom všetky zadaniu odpovedajúce prezívky, mená a haštagy.",
|
||||
"search_popout.tips.hashtag": "haštag",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Jednoduchý text vráti zhodujúce sa mená, prezývky a hashtagy",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[
|
||||
]
|
||||
@@ -134,7 +134,7 @@ export default function timelines(state = initialState, action) {
|
||||
initialTimeline,
|
||||
map => map.update(
|
||||
'items',
|
||||
items => items.first() ? items : items.unshift(null)
|
||||
items => items.first() ? items.unshift(null) : items
|
||||
)
|
||||
);
|
||||
default:
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export const unescapeHTML = (html) => {
|
||||
const wrapper = document.createElement('div');
|
||||
html = html.replace(/<br \/>|<br>|\n/g, ' ');
|
||||
wrapper.innerHTML = html;
|
||||
return wrapper.textContent;
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import EXIF from 'exif-js';
|
||||
|
||||
const MAX_IMAGE_DIMENSION = 1280;
|
||||
|
||||
const getImageUrl = inputFile => new Promise((resolve, reject) => {
|
||||
@@ -28,6 +30,84 @@ const loadImage = inputFile => new Promise((resolve, reject) => {
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
const getOrientation = (img, type = 'image/png') => new Promise(resolve => {
|
||||
if (type !== 'image/jpeg') {
|
||||
resolve(1);
|
||||
return;
|
||||
}
|
||||
|
||||
EXIF.getData(img, () => {
|
||||
const orientation = EXIF.getTag(img, 'Orientation');
|
||||
resolve(orientation);
|
||||
});
|
||||
});
|
||||
|
||||
const processImage = (img, { width, height, orientation, type = 'image/png' }) => new Promise(resolve => {
|
||||
const canvas = document.createElement('canvas');
|
||||
[canvas.width, canvas.height] = orientation < 5 ? [width, height] : [height, width];
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
switch (orientation) {
|
||||
case 2:
|
||||
context.translate(width, 0);
|
||||
break;
|
||||
case 3:
|
||||
context.translate(width, height);
|
||||
break;
|
||||
case 4:
|
||||
context.translate(0, height);
|
||||
break;
|
||||
case 5:
|
||||
context.rotate(0.5 * Math.PI);
|
||||
context.translate(1, -1);
|
||||
break;
|
||||
case 6:
|
||||
context.rotate(0.5 * Math.PI);
|
||||
context.translate(0, -height);
|
||||
break;
|
||||
case 7:
|
||||
context.rotate(0.5, Math.PI);
|
||||
context.translate(width, -height);
|
||||
break;
|
||||
case 8:
|
||||
context.rotate(-0.5, Math.PI);
|
||||
context.translate(-width, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
context.drawImage(img, 0, 0, width, height);
|
||||
|
||||
canvas.toBlob(resolve, type);
|
||||
});
|
||||
|
||||
const resizeImage = (img, type = 'image/png') => new Promise((resolve, reject) => {
|
||||
const { width, height } = img;
|
||||
|
||||
let newWidth, newHeight;
|
||||
|
||||
if (width > height) {
|
||||
newHeight = height * MAX_IMAGE_DIMENSION / width;
|
||||
newWidth = MAX_IMAGE_DIMENSION;
|
||||
} else if (height > width) {
|
||||
newWidth = width * MAX_IMAGE_DIMENSION / height;
|
||||
newHeight = MAX_IMAGE_DIMENSION;
|
||||
} else {
|
||||
newWidth = MAX_IMAGE_DIMENSION;
|
||||
newHeight = MAX_IMAGE_DIMENSION;
|
||||
}
|
||||
|
||||
getOrientation(img, type)
|
||||
.then(orientation => processImage(img, {
|
||||
width: newWidth,
|
||||
height: newHeight,
|
||||
orientation,
|
||||
type,
|
||||
}))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
|
||||
export default inputFile => new Promise((resolve, reject) => {
|
||||
if (!inputFile.type.match(/image.*/) || inputFile.type === 'image/gif') {
|
||||
resolve(inputFile);
|
||||
@@ -35,32 +115,13 @@ export default inputFile => new Promise((resolve, reject) => {
|
||||
}
|
||||
|
||||
loadImage(inputFile).then(img => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const { width, height } = img;
|
||||
|
||||
let newWidth, newHeight;
|
||||
|
||||
if (width < MAX_IMAGE_DIMENSION && height < MAX_IMAGE_DIMENSION) {
|
||||
if (img.width < MAX_IMAGE_DIMENSION && img.height < MAX_IMAGE_DIMENSION) {
|
||||
resolve(inputFile);
|
||||
return;
|
||||
}
|
||||
|
||||
if (width > height) {
|
||||
newHeight = height * MAX_IMAGE_DIMENSION / width;
|
||||
newWidth = MAX_IMAGE_DIMENSION;
|
||||
} else if (height > width) {
|
||||
newWidth = width * MAX_IMAGE_DIMENSION / height;
|
||||
newHeight = MAX_IMAGE_DIMENSION;
|
||||
} else {
|
||||
newWidth = MAX_IMAGE_DIMENSION;
|
||||
newHeight = MAX_IMAGE_DIMENSION;
|
||||
}
|
||||
|
||||
canvas.width = newWidth;
|
||||
canvas.height = newHeight;
|
||||
|
||||
canvas.getContext('2d').drawImage(img, 0, 0, newWidth, newHeight);
|
||||
|
||||
canvas.toBlob(resolve, inputFile.type);
|
||||
resizeImage(img, inputFile.type)
|
||||
.then(resolve)
|
||||
.catch(() => resolve(inputFile));
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ function main() {
|
||||
const { getLocale } = require('../mastodon/locales');
|
||||
const { localeData } = getLocale();
|
||||
const VideoContainer = require('../mastodon/containers/video_container').default;
|
||||
const CardContainer = require('../mastodon/containers/card_container').default;
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
|
||||
@@ -57,10 +56,16 @@ function main() {
|
||||
ReactDOM.render(<VideoContainer locale={locale} {...props} />, content);
|
||||
});
|
||||
|
||||
[].forEach.call(document.querySelectorAll('[data-component="Card"]'), (content) => {
|
||||
const props = JSON.parse(content.getAttribute('data-props'));
|
||||
ReactDOM.render(<CardContainer locale={locale} {...props} />, content);
|
||||
});
|
||||
const cards = document.querySelectorAll('[data-component="Card"]');
|
||||
|
||||
if (cards.length > 0) {
|
||||
import(/* webpackChunkName: "containers/cards_container" */ '../mastodon/containers/cards_container').then(({ default: CardsContainer }) => {
|
||||
const content = document.createElement('div');
|
||||
|
||||
ReactDOM.render(<CardsContainer locale={locale} cards={cards} />, content);
|
||||
document.body.appendChild(content);
|
||||
}).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
const mediaGalleries = document.querySelectorAll('[data-component="MediaGallery"]');
|
||||
|
||||
|
||||
@@ -565,36 +565,41 @@
|
||||
}
|
||||
|
||||
.account__header__fields {
|
||||
border-collapse: collapse;
|
||||
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%);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 15px;
|
||||
padding-left: 15px;
|
||||
border: 0 none;
|
||||
dl {
|
||||
display: flex;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 4%);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
th {
|
||||
padding-left: 15px;
|
||||
font-weight: 500;
|
||||
dt,
|
||||
dd {
|
||||
box-sizing: border-box;
|
||||
padding: 14px;
|
||||
text-align: center;
|
||||
width: 94px;
|
||||
max-height: 48px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 500;
|
||||
width: 120px;
|
||||
flex: 0 0 auto;
|
||||
color: $secondary-text-color;
|
||||
background: rgba(darken($ui-base-color, 8%), 0.5);
|
||||
}
|
||||
|
||||
td {
|
||||
dd {
|
||||
flex: 1 1 auto;
|
||||
color: $darker-text-color;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -608,12 +613,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
&:last-child {
|
||||
th,
|
||||
td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
dl:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +336,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.simple_form.new_report_note {
|
||||
.simple_form.new_report_note,
|
||||
.simple_form.new_account_moderation_note {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -4033,7 +4033,7 @@ a.status-card {
|
||||
.report-modal__statuses {
|
||||
flex: 1 1 auto;
|
||||
min-height: 20vh;
|
||||
max-height: 40vh;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
@@ -5159,38 +5159,45 @@ noscript {
|
||||
}
|
||||
}
|
||||
|
||||
.account__header .roles {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.account__header .account__header__fields {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
overflow: hidden;
|
||||
border-collapse: collapse;
|
||||
margin: 20px -10px -20px;
|
||||
border-bottom: 0;
|
||||
|
||||
tr {
|
||||
dl {
|
||||
border-top: 1px solid lighten($ui-base-color, 8%);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
dt,
|
||||
dd {
|
||||
box-sizing: border-box;
|
||||
padding: 14px 20px;
|
||||
vertical-align: middle;
|
||||
max-height: 40px;
|
||||
text-align: center;
|
||||
max-height: 48px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
th {
|
||||
dt {
|
||||
color: $darker-text-color;
|
||||
background: darken($ui-base-color, 4%);
|
||||
max-width: 120px;
|
||||
width: 120px;
|
||||
flex: 0 0 auto;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
td {
|
||||
flex: auto;
|
||||
dd {
|
||||
flex: 1 1 auto;
|
||||
color: $primary-text-color;
|
||||
background: $ui-base-color;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.card-standalone__body,
|
||||
.media-gallery-standalone__body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,10 @@ code {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&.file .label_input {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
&.select .label_input {
|
||||
align-items: initial;
|
||||
}
|
||||
|
||||
@@ -86,6 +86,8 @@ class ActivityPub::TagManager
|
||||
end
|
||||
|
||||
def local_uri?(uri)
|
||||
return false if uri.nil?
|
||||
|
||||
uri = Addressable::URI.parse(uri)
|
||||
host = uri.normalized_host
|
||||
host = "#{host}:#{uri.port}" if uri.port
|
||||
@@ -99,6 +101,8 @@ class ActivityPub::TagManager
|
||||
end
|
||||
|
||||
def uri_to_resource(uri, klass)
|
||||
return if uri.nil?
|
||||
|
||||
if local_uri?(uri)
|
||||
case klass.name
|
||||
when 'Account'
|
||||
|
||||
+10
-2
@@ -67,9 +67,17 @@ class Formatter
|
||||
html.html_safe # rubocop:disable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def format_field(account, str)
|
||||
def format_display_name(account, **options)
|
||||
html = encode(account.display_name.presence || account.username)
|
||||
html = encode_custom_emojis(html, account.emojis) if options[:custom_emojify]
|
||||
html.html_safe # rubocop:disable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def format_field(account, str, **options)
|
||||
return reformat(str).html_safe unless account.local? # rubocop:disable Rails/OutputSafety
|
||||
encode_and_link_urls(str, me: true).html_safe # rubocop:disable Rails/OutputSafety
|
||||
html = encode_and_link_urls(str, me: true)
|
||||
html = encode_custom_emojis(html, account.emojis) if options[:custom_emojify]
|
||||
html.html_safe # rubocop:disable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def linkify(text)
|
||||
|
||||
@@ -46,7 +46,8 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
|
||||
visibility: visibility_scope,
|
||||
conversation: find_or_create_conversation,
|
||||
thread: thread? ? find_status(thread.first) || find_activitypub_status(thread.first, thread.second) : nil,
|
||||
media_attachment_ids: media_attachments.map(&:id)
|
||||
media_attachment_ids: media_attachments.map(&:id),
|
||||
sensitive: sensitive?
|
||||
)
|
||||
|
||||
save_mentions(status)
|
||||
@@ -105,6 +106,11 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
|
||||
|
||||
private
|
||||
|
||||
def sensitive?
|
||||
# OStatus-specific convention (not standard)
|
||||
@xml.xpath('./xmlns:category', xmlns: OStatus::TagManager::XMLNS).any? { |category| category['term'] == 'nsfw' }
|
||||
end
|
||||
|
||||
def find_or_create_conversation
|
||||
uri = @xml.at_xpath('./ostatus:conversation', ostatus: OStatus::TagManager::OS_XMLNS)&.attribute('ref')&.content
|
||||
return if uri.nil?
|
||||
|
||||
@@ -368,6 +368,7 @@ class OStatus::AtomSerializer
|
||||
append_element(entry, 'link', nil, rel: :enclosure, type: media.file_content_type, length: media.file_file_size, href: full_asset_url(media.file.url(:original, false)))
|
||||
end
|
||||
|
||||
append_element(entry, 'category', nil, term: 'nsfw') if status.sensitive? && status.media_attachments.any?
|
||||
append_element(entry, 'mastodon:scope', status.visibility)
|
||||
|
||||
status.emojis.each do |emoji|
|
||||
|
||||
+2
-2
@@ -51,7 +51,7 @@ class Request
|
||||
end
|
||||
|
||||
def headers
|
||||
(@account ? @headers.merge('Signature' => signature) : @headers).without(REQUEST_TARGET)
|
||||
(@account ? @headers.merge('Signature' => signature) : @headers).reverse_merge('Accept-Encoding' => 'gzip').without(REQUEST_TARGET)
|
||||
end
|
||||
|
||||
private
|
||||
@@ -100,7 +100,7 @@ class Request
|
||||
end
|
||||
|
||||
def http_client
|
||||
@http_client ||= HTTP.timeout(:per_operation, timeout).follow(max_hops: 2)
|
||||
@http_client ||= HTTP.use(:auto_inflate).timeout(:per_operation, timeout).follow(max_hops: 2)
|
||||
end
|
||||
|
||||
def use_proxy?
|
||||
|
||||
+24
-6
@@ -45,6 +45,7 @@
|
||||
# moved_to_account_id :bigint(8)
|
||||
# featured_collection_url :string
|
||||
# fields :jsonb
|
||||
# actor_type :string
|
||||
#
|
||||
|
||||
class Account < ApplicationRecord
|
||||
@@ -76,6 +77,7 @@ class Account < ApplicationRecord
|
||||
validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? }
|
||||
validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
|
||||
validate :note_length_does_not_exceed_length_limit, if: -> { local? && will_save_change_to_note? }
|
||||
validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? }
|
||||
|
||||
# Timelines
|
||||
has_many :stream_entries, inverse_of: :account, dependent: :destroy
|
||||
@@ -151,6 +153,16 @@ class Account < ApplicationRecord
|
||||
moved_to_account_id.present?
|
||||
end
|
||||
|
||||
def bot?
|
||||
%w(Application Service).include? actor_type
|
||||
end
|
||||
|
||||
alias bot bot?
|
||||
|
||||
def bot=(val)
|
||||
self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person'
|
||||
end
|
||||
|
||||
def acct
|
||||
local? ? username : "#{username}@#{domain}"
|
||||
end
|
||||
@@ -201,9 +213,11 @@ class Account < ApplicationRecord
|
||||
def fields_attributes=(attributes)
|
||||
fields = []
|
||||
|
||||
attributes.each_value do |attr|
|
||||
next if attr[:name].blank?
|
||||
fields << attr
|
||||
if attributes.is_a?(Hash)
|
||||
attributes.each_value do |attr|
|
||||
next if attr[:name].blank?
|
||||
fields << attr
|
||||
end
|
||||
end
|
||||
|
||||
self[:fields] = fields
|
||||
@@ -272,8 +286,8 @@ class Account < ApplicationRecord
|
||||
|
||||
def initialize(account, attr)
|
||||
@account = account
|
||||
@name = attr['name']
|
||||
@value = attr['value']
|
||||
@name = attr['name'].strip[0, 255]
|
||||
@value = attr['value'].strip[0, 255]
|
||||
@errors = {}
|
||||
end
|
||||
|
||||
@@ -398,7 +412,7 @@ class Account < ApplicationRecord
|
||||
end
|
||||
|
||||
def emojis
|
||||
@emojis ||= CustomEmoji.from_text(note, domain)
|
||||
@emojis ||= CustomEmoji.from_text(emojifiable_text, domain)
|
||||
end
|
||||
|
||||
before_create :generate_keys
|
||||
@@ -441,4 +455,8 @@ class Account < ApplicationRecord
|
||||
|
||||
self.domain = TagManager.instance.normalize_domain(domain)
|
||||
end
|
||||
|
||||
def emojifiable_text
|
||||
[note, display_name, fields.map(&:value)].join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ class User < ApplicationRecord
|
||||
include Settings::Extend
|
||||
include Omniauthable
|
||||
|
||||
ACTIVE_DURATION = 14.days
|
||||
ACTIVE_DURATION = 7.days
|
||||
|
||||
devise :two_factor_authenticatable,
|
||||
otp_secret_encryption_key: Rails.configuration.x.otp_secret
|
||||
|
||||
@@ -37,7 +37,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def type
|
||||
'Person'
|
||||
object.bot? ? 'Service' : 'Person'
|
||||
end
|
||||
|
||||
def following
|
||||
|
||||
@@ -5,7 +5,7 @@ class ActivityPub::BlockSerializer < ActiveModel::Serializer
|
||||
attribute :virtual_object, key: :object
|
||||
|
||||
def id
|
||||
[ActivityPub::TagManager.instance.uri_for(object.account), '#blocks/', object.id].join
|
||||
ActivityPub::TagManager.instance.uri_for(object) || [ActivityPub::TagManager.instance.uri_for(object.account), '#blocks/', object.id].join
|
||||
end
|
||||
|
||||
def type
|
||||
|
||||
@@ -5,7 +5,7 @@ class ActivityPub::FollowSerializer < ActiveModel::Serializer
|
||||
attribute :virtual_object, key: :object
|
||||
|
||||
def id
|
||||
ActivityPub::TagManager.instance.uri_for(object)
|
||||
ActivityPub::TagManager.instance.uri_for(object) || [ActivityPub::TagManager.instance.uri_for(object.account), '#follows/', object.id].join
|
||||
end
|
||||
|
||||
def type
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
class REST::AccountSerializer < ActiveModel::Serializer
|
||||
include RoutingHelper
|
||||
|
||||
attributes :id, :username, :acct, :display_name, :locked, :created_at,
|
||||
attributes :id, :username, :acct, :display_name, :locked, :bot, :created_at,
|
||||
:note, :url, :avatar, :avatar_static, :header, :header_static,
|
||||
:followers_count, :following_count, :statuses_count
|
||||
|
||||
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?
|
||||
has_many :emojis, serializer: REST::CustomEmojiSerializer
|
||||
|
||||
class FieldSerializer < ActiveModel::Serializer
|
||||
attributes :name, :value
|
||||
|
||||
@@ -34,6 +34,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
|
||||
end
|
||||
|
||||
def trustworthy_attribution?(uri, attributed_to)
|
||||
return false if uri.nil? || attributed_to.nil?
|
||||
Addressable::URI.parse(uri).normalized_host.casecmp(Addressable::URI.parse(attributed_to).normalized_host).zero?
|
||||
end
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||
@account.note = @json['summary'] || ''
|
||||
@account.locked = @json['manuallyApprovesFollowers'] || false
|
||||
@account.fields = property_values || {}
|
||||
@account.actor_type = actor_type
|
||||
end
|
||||
|
||||
def set_fetchable_attributes!
|
||||
@@ -95,6 +96,14 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||
ActivityPub::SynchronizeFeaturedCollectionWorker.perform_async(@account.id)
|
||||
end
|
||||
|
||||
def actor_type
|
||||
if @json['type'].is_a?(Array)
|
||||
@json['type'].find { |type| ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(type) }
|
||||
else
|
||||
@json['type']
|
||||
end
|
||||
end
|
||||
|
||||
def image_url(key)
|
||||
value = first_of_value(@json[key])
|
||||
|
||||
|
||||
@@ -45,5 +45,8 @@ class ActivityPub::ProcessCollectionService < BaseService
|
||||
|
||||
def verify_account!
|
||||
@account = ActivityPub::LinkedDataSignature.new(@json).verify_account!
|
||||
rescue JSON::LD::JsonLdError => e
|
||||
Rails.logger.debug "Could not verify LD-Signature for #{value_or_id(@json['actor'])}: #{e.message}"
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,7 +37,7 @@ class FanOutOnWriteService < BaseService
|
||||
def deliver_to_followers(status)
|
||||
Rails.logger.debug "Delivering status #{status.id} to followers"
|
||||
|
||||
status.account.followers.where(domain: nil).joins(:user).where('users.current_sign_in_at > ?', 14.days.ago).select(:id).reorder(nil).find_in_batches do |followers|
|
||||
status.account.followers.where(domain: nil).joins(:user).where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago).select(:id).reorder(nil).find_in_batches do |followers|
|
||||
FeedInsertWorker.push_bulk(followers) do |follower|
|
||||
[status.id, follower.id, :home]
|
||||
end
|
||||
@@ -47,7 +47,7 @@ class FanOutOnWriteService < BaseService
|
||||
def deliver_to_lists(status)
|
||||
Rails.logger.debug "Delivering status #{status.id} to lists"
|
||||
|
||||
status.account.lists.joins(account: :user).where('users.current_sign_in_at > ?', 14.days.ago).select(:id).reorder(nil).find_in_batches do |lists|
|
||||
status.account.lists.joins(account: :user).where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago).select(:id).reorder(nil).find_in_batches do |lists|
|
||||
FeedInsertWorker.push_bulk(lists) do |list|
|
||||
[status.id, list.id, :list]
|
||||
end
|
||||
|
||||
@@ -27,7 +27,7 @@ class FetchLinkCardService < BaseService
|
||||
end
|
||||
|
||||
attach_card if @card&.persisted?
|
||||
rescue HTTP::Error, Addressable::URI::InvalidURIError => e
|
||||
rescue HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::LengthValidationError => e
|
||||
Rails.logger.debug "Error fetching link #{@url}: #{e}"
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -31,12 +31,12 @@ class PostStatusService < BaseService
|
||||
sensitive: (options[:sensitive].nil? ? account.user&.setting_default_sensitive : options[:sensitive]),
|
||||
spoiler_text: options[:spoiler_text] || '',
|
||||
visibility: options[:visibility] || account.user&.setting_default_privacy,
|
||||
language: LanguageDetector.instance.detect(text, account),
|
||||
language: language_from_option(options[:language]) || LanguageDetector.instance.detect(text, account),
|
||||
application: options[:application])
|
||||
end
|
||||
|
||||
process_mentions_service.call(status)
|
||||
process_hashtags_service.call(status)
|
||||
process_mentions_service.call(status)
|
||||
|
||||
LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text?
|
||||
DistributionWorker.perform_async(status.id)
|
||||
@@ -68,6 +68,10 @@ class PostStatusService < BaseService
|
||||
media
|
||||
end
|
||||
|
||||
def language_from_option(str)
|
||||
ISO_639.find(str)&.alpha2
|
||||
end
|
||||
|
||||
def process_mentions_service
|
||||
ProcessMentionsService.new
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
.account__avatar{ style: "background-image: url(#{@instance_presenter.contact_account.avatar.url})" }
|
||||
%span.display-name
|
||||
%bdi
|
||||
%strong.display-name__html.emojify= display_name(@instance_presenter.contact_account)
|
||||
%strong.display-name__html.emojify= display_name(@instance_presenter.contact_account, custom_emojify: true)
|
||||
%span.display-name__account @#{@instance_presenter.contact_account.acct}
|
||||
- else
|
||||
.account__display-name
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
.avatar= image_tag contact.contact_account.avatar.url
|
||||
.name
|
||||
= link_to TagManager.instance.url_for(contact.contact_account) do
|
||||
%span.display_name.emojify= display_name(contact.contact_account)
|
||||
%span.display_name.emojify= display_name(contact.contact_account, custom_emojify: true)
|
||||
%span.username @#{contact.contact_account.acct}
|
||||
- else
|
||||
.owner
|
||||
|
||||
@@ -141,3 +141,5 @@
|
||||
%p
|
||||
= link_to t('about.source_code'), @instance_presenter.source_url
|
||||
= " (#{@instance_presenter.version_number})"
|
||||
|
||||
#modal-container
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
.avatar= image_tag account.avatar.url(:original)
|
||||
.name
|
||||
= link_to TagManager.instance.url_for(account) do
|
||||
%span.display_name.emojify= display_name(account)
|
||||
%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?
|
||||
|
||||
@@ -6,11 +6,16 @@
|
||||
|
||||
.card__bio
|
||||
%h1.name
|
||||
%span.p-name.emojify= display_name(account)
|
||||
%span.p-name.emojify= display_name(account, custom_emojify: true)
|
||||
%small<
|
||||
%span>< @#{account.local_username_and_domain}
|
||||
= fa_icon('lock') if account.locked?
|
||||
- if Setting.show_staff_badge
|
||||
|
||||
- if account.bot?
|
||||
.roles
|
||||
.account-role.bot
|
||||
= t 'accounts.roles.bot'
|
||||
- elsif Setting.show_staff_badge
|
||||
- if account.user_admin?
|
||||
.roles
|
||||
.account-role.admin
|
||||
@@ -21,19 +26,19 @@
|
||||
= t 'accounts.roles.moderator'
|
||||
.bio
|
||||
.account__header__content.p-note.emojify!=processed_bio[:text]
|
||||
|
||||
- if !account.fields.empty?
|
||||
%table.account__header__fields
|
||||
%tbody
|
||||
- account.fields.each do |field|
|
||||
%tr
|
||||
%th.emojify= field.name
|
||||
%td.emojify= Formatter.instance.format_field(account, field.value)
|
||||
.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)
|
||||
- elsif processed_bio[:metadata].length > 0
|
||||
%table.account__header__fields<
|
||||
.account__header__fields
|
||||
- processed_bio[:metadata].each do |i|
|
||||
%tr
|
||||
%th.emojify>!=i[0]
|
||||
%td.emojify>!=i[1]
|
||||
%dl
|
||||
%dt.emojify{ title: i[0] }= i[0]
|
||||
%dd.emojify{ title: i[1] }= i[1]
|
||||
|
||||
.details-counters
|
||||
.counter{ class: active_nav_class(short_account_url(account)) }
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
.moved-strip
|
||||
.moved-strip__message
|
||||
= fa_icon 'suitcase'
|
||||
= t('accounts.moved_html', name: content_tag(:strong, display_name(account), 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(: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
|
||||
= link_to TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'noopener' do
|
||||
@@ -13,5 +13,5 @@
|
||||
.account__avatar-overlay-overlay{ style: "background-image: url('#{account.avatar.url(:original)}')" }
|
||||
|
||||
%span.display-name
|
||||
%strong.emojify= display_name(moved_to_account)
|
||||
%strong.emojify= display_name(moved_to_account, custom_emojify: true)
|
||||
%span @#{moved_to_account.acct}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
%tr
|
||||
%td
|
||||
.speech-bubble
|
||||
.speech-bubble__bubble
|
||||
= simple_format(h(account_moderation_note.content))
|
||||
%td
|
||||
= account_moderation_note.account.acct
|
||||
%td
|
||||
%time.formatted{ datetime: account_moderation_note.created_at.iso8601, title: l(account_moderation_note.created_at) }
|
||||
= l account_moderation_note.created_at
|
||||
%td
|
||||
= link_to t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete if can?(:destroy, account_moderation_note)
|
||||
.speech-bubble__owner
|
||||
= admin_account_link_to account_moderation_note.account
|
||||
%time.formatted{ datetime: account_moderation_note.created_at.iso8601 }= l account_moderation_note.created_at
|
||||
= table_link_to 'trash', t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete if can?(:destroy, account_moderation_note)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
= @account.acct
|
||||
|
||||
.table-wrapper
|
||||
%table.table
|
||||
%table.table.inline-table
|
||||
%tbody
|
||||
%tr
|
||||
%th= t('admin.accounts.username')
|
||||
@@ -36,13 +36,19 @@
|
||||
%th= t('admin.accounts.email')
|
||||
%td
|
||||
= @account.user_email
|
||||
- if @account.user_confirmed?
|
||||
= fa_icon('check')
|
||||
= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user)
|
||||
- if @account.user_unconfirmed_email.present?
|
||||
%th= t('admin.accounts.unconfirmed_email')
|
||||
%td
|
||||
= @account.user_unconfirmed_email
|
||||
%tr
|
||||
%th= t('admin.accounts.email_status')
|
||||
%td
|
||||
- if @account.user&.confirmed?
|
||||
= t('admin.accounts.confirmed')
|
||||
- else
|
||||
= t('admin.accounts.confirming')
|
||||
= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(@account.id), method: :post if can?(:confirm, @account.user)
|
||||
%tr
|
||||
%th= t('admin.accounts.login_status')
|
||||
%td
|
||||
@@ -73,17 +79,17 @@
|
||||
|
||||
%tr
|
||||
%th= t('admin.accounts.follows')
|
||||
%td= @account.following_count
|
||||
%td= number_to_human @account.following_count
|
||||
%tr
|
||||
%th= t('admin.accounts.followers')
|
||||
%td= @account.followers_count
|
||||
%td= number_to_human @account.followers_count
|
||||
%tr
|
||||
%th= t('admin.accounts.statuses')
|
||||
%td= link_to @account.statuses_count, admin_account_statuses_path(@account.id)
|
||||
%td= link_to number_to_human(@account.statuses_count), admin_account_statuses_path(@account.id)
|
||||
%tr
|
||||
%th= t('admin.accounts.media_attachments')
|
||||
%td
|
||||
= link_to @account.media_attachments.count, admin_account_statuses_path(@account.id, { media: true })
|
||||
= link_to number_to_human(@account.media_attachments.count), admin_account_statuses_path(@account.id, { media: true })
|
||||
= surround '(', ')' do
|
||||
= number_to_human_size @account.media_attachments.sum('file_file_size')
|
||||
%tr
|
||||
@@ -120,11 +126,12 @@
|
||||
= link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:suspend, @account)
|
||||
|
||||
- if !@account.local? && @account.hub_url.present?
|
||||
%hr
|
||||
%hr.spacer/
|
||||
|
||||
%h3 OStatus
|
||||
|
||||
.table-wrapper
|
||||
%table.table
|
||||
%table.table.inline-table
|
||||
%tbody
|
||||
%tr
|
||||
%th= t('admin.accounts.feed_url')
|
||||
@@ -148,11 +155,12 @@
|
||||
= link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' if can?(:unsubscribe, @account)
|
||||
|
||||
- if !@account.local? && @account.inbox_url.present?
|
||||
%hr
|
||||
%hr.spacer/
|
||||
|
||||
%h3 ActivityPub
|
||||
|
||||
.table-wrapper
|
||||
%table.table
|
||||
%table.table.inline-table
|
||||
%tbody
|
||||
%tr
|
||||
%th= t('admin.accounts.inbox_url')
|
||||
@@ -167,24 +175,15 @@
|
||||
%th= t('admin.accounts.followers_url')
|
||||
%td= link_to @account.followers_url, @account.followers_url
|
||||
|
||||
%hr
|
||||
%h3= t('admin.accounts.moderation_notes')
|
||||
%hr.spacer/
|
||||
|
||||
= render @moderation_notes
|
||||
|
||||
= simple_form_for @account_moderation_note, url: admin_account_moderation_notes_path do |f|
|
||||
= render 'shared/error_messages', object: @account_moderation_note
|
||||
|
||||
= f.input :content
|
||||
= f.input :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6
|
||||
= f.hidden_field :target_account_id
|
||||
|
||||
.actions
|
||||
= f.button :button, t('admin.account_moderation_notes.create'), type: :submit
|
||||
|
||||
.table-wrapper
|
||||
%table.table
|
||||
%thead
|
||||
%tr
|
||||
%th
|
||||
%th= t('admin.account_moderation_notes.account')
|
||||
%th= t('admin.account_moderation_notes.created_at')
|
||||
%tbody
|
||||
= render @moderation_notes
|
||||
= f.button :button, t('admin.account_moderation_notes.create'), type: :submit
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
.account__avatar{ style: "background-image: url(#{account.avatar.url}); width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px" }
|
||||
%span.display-name
|
||||
%bdi
|
||||
%strong.display-name__html.emojify= display_name(account)
|
||||
%strong.display-name__html.emojify= display_name(account, custom_emojify: true)
|
||||
%span.display-name__account @#{account.acct}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
%p><
|
||||
%strong= Formatter.instance.format_spoiler(status)
|
||||
|
||||
= Formatter.instance.format(status)
|
||||
= Formatter.instance.format(status, custom_emojify: true)
|
||||
|
||||
- unless status.media_attachments.empty?
|
||||
- if status.media_attachments.first.video?
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
- content_for :page_title do
|
||||
= t('admin.statuses.title')
|
||||
|
||||
.back-link
|
||||
= link_to admin_account_path(@account.id) do
|
||||
%i.fa.fa-chevron-left.fa-fw
|
||||
= t('admin.statuses.back_to_account')
|
||||
\-
|
||||
= "@#{@account.acct}"
|
||||
|
||||
.filters
|
||||
.filter-subset
|
||||
@@ -12,33 +9,26 @@
|
||||
%ul
|
||||
%li= link_to t('admin.statuses.no_media'), admin_account_statuses_path(@account.id, current_params.merge(media: nil)), class: !params[:media] && 'selected'
|
||||
%li= link_to t('admin.statuses.with_media'), admin_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected'
|
||||
.back-link{ style: 'flex: 1 1 auto; text-align: right' }
|
||||
= link_to admin_account_path(@account.id) do
|
||||
%i.fa.fa-chevron-left.fa-fw
|
||||
= t('admin.statuses.back_to_account')
|
||||
|
||||
- if @statuses.empty?
|
||||
.accounts-grid
|
||||
= render 'accounts/nothing_here'
|
||||
- else
|
||||
= form_for(@form, url: admin_account_statuses_path(@account.id)) do |f|
|
||||
= hidden_field_tag :page, params[:page]
|
||||
= hidden_field_tag :media, params[:media]
|
||||
.batch-form-box
|
||||
.batch-checkbox-all
|
||||
%hr.spacer/
|
||||
|
||||
= form_for(@form, url: admin_account_statuses_path(@account.id)) do |f|
|
||||
= hidden_field_tag :page, params[:page]
|
||||
= hidden_field_tag :media, params[:media]
|
||||
|
||||
.batch-table
|
||||
.batch-table__toolbar
|
||||
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||
= check_box_tag :batch_checkbox_all, nil, false
|
||||
= f.select :action, Form::StatusBatch::ACTION_TYPE.map{|action| [t("admin.statuses.batch.#{action}"), action]}
|
||||
= f.submit t('admin.statuses.execute'), data: { confirm: t('admin.reports.are_you_sure') }, class: 'button'
|
||||
.media-spoiler-toggle-buttons
|
||||
.media-spoiler-show-button.button= t('admin.statuses.media.show')
|
||||
.media-spoiler-hide-button.button= t('admin.statuses.media.hide')
|
||||
- @statuses.each do |status|
|
||||
.account-status{ data: { id: status.id } }
|
||||
.batch-checkbox
|
||||
= f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id
|
||||
.activity-stream.activity-stream-headless
|
||||
.entry= render 'stream_entries/simple_status', status: status
|
||||
.account-status__actions
|
||||
- unless status.media_attachments.empty?
|
||||
= link_to admin_account_status_path(@account.id, status, current_params.merge(status: { sensitive: !status.sensitive })), method: :patch, class: 'icon-button nsfw-button', title: t("admin.reports.nsfw.#{!status.sensitive}") do
|
||||
= fa_icon status.sensitive? ? 'eye' : 'eye-slash'
|
||||
= link_to admin_account_status_path(@account.id, status), method: :delete, class: 'icon-button trash-button', title: t('admin.reports.delete'), data: { confirm: t('admin.reports.are_you_sure') }, remote: true do
|
||||
= fa_icon 'trash'
|
||||
.batch-table__toolbar__actions
|
||||
= f.button safe_join([fa_icon('eye-slash'), t('admin.statuses.batch.nsfw_on')]), name: :nsfw_on, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
||||
= f.button safe_join([fa_icon('eye'), t('admin.statuses.batch.nsfw_off')]), name: :nsfw_off, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
||||
= f.button safe_join([fa_icon('trash'), t('admin.statuses.batch.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
||||
.batch-table__body
|
||||
= render partial: 'admin/reports/status', collection: @statuses, locals: { f: f }
|
||||
|
||||
= paginate @statuses
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
= t('auth.login')
|
||||
|
||||
= simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
|
||||
= f.input :otp_attempt, type: :number, placeholder: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'off' }, required: true, autofocus: true, hint: t('simple_form.hints.sessions.otp')
|
||||
%p.hint{ style: 'margin-bottom: 25px' }= t('simple_form.hints.sessions.otp')
|
||||
|
||||
= f.input :otp_attempt, type: :number, placeholder: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'off' }, required: true, autofocus: true
|
||||
|
||||
.actions
|
||||
= f.button :button, t('auth.login'), type: :submit
|
||||
|
||||
.form-footer= render 'auth/shared/links'
|
||||
- if Setting.site_contact_email.present?
|
||||
%p.hint.subtle-hint= t('users.otp_lost_help_html', email: mail_to(Setting.site_contact_email, nil))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
%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)
|
||||
%strong.emojify= display_name(account, custom_emojify: true)
|
||||
%span @#{account.acct}
|
||||
|
||||
- if account.note?
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
%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)
|
||||
%strong.emojify= display_name(account, custom_emojify: true)
|
||||
%span @#{account.acct}
|
||||
|
||||
- if account.note?
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
%td
|
||||
%tr
|
||||
%th= t('exports.follows')
|
||||
%td= @export.total_follows
|
||||
%td= number_to_human @export.total_follows
|
||||
%td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv)
|
||||
%tr
|
||||
%th= t('exports.blocks')
|
||||
%td= @export.total_blocks
|
||||
%td= number_to_human @export.total_blocks
|
||||
%td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv)
|
||||
%tr
|
||||
%th= t('exports.mutes')
|
||||
%td= @export.total_mutes
|
||||
%td= number_to_human @export.total_mutes
|
||||
%td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv)
|
||||
|
||||
%p.muted-hint= t('exports.archive_takeout.hint_html')
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
.fields-group
|
||||
= f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')
|
||||
|
||||
.fields-group
|
||||
= f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
|
||||
|
||||
.fields-group
|
||||
.input.with_block_label
|
||||
%label= t('simple_form.labels.defaults.fields')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
= image_tag asset_pack_path('logo.svg'), class: 'logo'
|
||||
|
||||
%div
|
||||
= t('landing_strip_html', name: content_tag(:span, display_name(account), class: :emojify), link_to_root_path: link_to(content_tag(:strong, site_hostname), root_path))
|
||||
= 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))
|
||||
|
||||
- if open_registrations?
|
||||
= t('landing_strip_signup_html', sign_up_path: new_user_registration_path)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
.avatar
|
||||
= image_tag status.account.avatar.url(:original), width: 48, height: 48, alt: '', class: 'u-photo'
|
||||
%span.display-name
|
||||
%strong.p-name.emojify= display_name(status.account)
|
||||
%strong.p-name.emojify= display_name(status.account, custom_emojify: true)
|
||||
%span= acct(status.account)
|
||||
|
||||
- if embedded_view?
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
%div
|
||||
= image_tag status.account.avatar(:original), width: 48, height: 48, alt: '', class: 'u-photo'
|
||||
%span.display-name
|
||||
%strong.p-name.emojify= display_name(status.account)
|
||||
%strong.p-name.emojify= display_name(status.account, custom_emojify: true)
|
||||
%span= acct(status.account)
|
||||
|
||||
.status__content.p-name.emojify<
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
= fa_icon('retweet fw')
|
||||
%span
|
||||
= link_to TagManager.instance.url_for(status.account), class: 'status__display-name muted' do
|
||||
%strong.emojify= display_name(status.account)
|
||||
%strong.emojify= display_name(status.account, custom_emojify: true)
|
||||
= t('stream_entries.reblogged')
|
||||
- elsif pinned
|
||||
.pre-header
|
||||
|
||||
@@ -33,3 +33,5 @@
|
||||
%p= t 'about.about_mastodon_html'
|
||||
|
||||
= render 'features'
|
||||
|
||||
#modal-container
|
||||
|
||||
Reference in New Issue
Block a user