Merge branch 'master' into glitch-soc/master

Conflicts:
	app/models/account.rb
	app/views/accounts/_header.html.haml
This commit is contained in:
Thibaut Girka
2018-05-09 17:43:30 +02:00
145 changed files with 3052 additions and 803 deletions
@@ -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
+11 -24
View File
@@ -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
+1
View File
@@ -6,6 +6,7 @@ module SettingsHelper
ar: 'العربية',
bg: 'Български',
ca: 'Català',
co: 'Corsu',
de: 'Deutsch',
el: 'Ελληνικά',
eo: 'Esperanto',
+2 -2
View File
@@ -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}
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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": "رُد على الخيط",
+296
View File
@@ -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"
}
+64 -64
View File
@@ -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",
+14 -14
View File
@@ -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",
+12 -12
View File
@@ -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 sest 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": "à linstant",
@@ -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",
+2 -2
View File
@@ -110,7 +110,7 @@
"empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas dautras 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 dabonament",
+1 -1
View File
@@ -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:
+6
View File
@@ -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;
};
+84 -23
View File
@@ -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);
});
+10 -5
View File
@@ -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"]');
+22 -22
View File
@@ -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;
}
}
+2 -1
View File
@@ -336,7 +336,8 @@
}
}
.simple_form.new_report_note {
.simple_form.new_report_note,
.simple_form.new_account_moderation_note {
max-width: 100%;
}
+19 -12
View File
@@ -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;
}
+4
View File
@@ -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
View File
@@ -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)
+7 -1
View File
@@ -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?
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+6 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+2
View File
@@ -141,3 +141,5 @@
%p
= link_to t('about.source_code'), @instance_presenter.source_url
= " (#{@instance_presenter.version_number})"
#modal-container
+1 -1
View File
@@ -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?
+17 -12
View File
@@ -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)) }
+2 -2
View File
@@ -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)
+24 -25
View File
@@ -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
+1 -1
View File
@@ -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}
+1 -1
View File
@@ -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?
+21 -31
View File
@@ -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
+5 -2
View File
@@ -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))
+1 -1
View File
@@ -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?
+1 -1
View File
@@ -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?
+3 -3
View File
@@ -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')
+1 -1
View File
@@ -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<
+1 -1
View File
@@ -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
+2
View File
@@ -33,3 +33,5 @@
%p= t 'about.about_mastodon_html'
= render 'features'
#modal-container