Merge tag 'v1.5.0rc3' into kosmos
This commit is contained in:
commit
1388d73e0c
@ -27,6 +27,7 @@ Metrics/AbcSize:
|
||||
Max: 100
|
||||
|
||||
Metrics/BlockLength:
|
||||
Max: 35
|
||||
Exclude:
|
||||
- 'lib/tasks/**/*'
|
||||
|
||||
@ -35,10 +36,10 @@ Metrics/BlockNesting:
|
||||
|
||||
Metrics/ClassLength:
|
||||
CountComments: false
|
||||
Max: 200
|
||||
Max: 300
|
||||
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 15
|
||||
Max: 25
|
||||
|
||||
Metrics/LineLength:
|
||||
AllowURI: true
|
||||
@ -53,11 +54,11 @@ Metrics/ModuleLength:
|
||||
Max: 200
|
||||
|
||||
Metrics/ParameterLists:
|
||||
Max: 4
|
||||
Max: 5
|
||||
CountKeywordArgs: true
|
||||
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 10
|
||||
Max: 20
|
||||
|
||||
Rails:
|
||||
Enabled: true
|
||||
|
@ -9,16 +9,19 @@ export default class ColumnBackButton extends React.PureComponent {
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
if (window.history && window.history.length === 1) this.context.router.history.push('/');
|
||||
else this.context.router.history.goBack();
|
||||
if (window.history && window.history.length === 1) {
|
||||
this.context.router.history.push('/');
|
||||
} else {
|
||||
this.context.router.history.goBack();
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button'>
|
||||
<button onClick={this.handleClick} className='column-back-button'>
|
||||
<i className='fa fa-fw fa-chevron-left column-back-button__icon' />
|
||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ export default class DropdownMenu extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<Dropdown ref={this.setRef} active={isUserTouching ? false : expanded} onShow={this.handleShow} onHide={this.handleHide}>
|
||||
<DropdownTrigger className='icon-button' style={iconStyle} role='button' aria-pressed={expanded} onKeyDown={this.handleToggle} tabIndex='0' aria-label={ariaLabel}>
|
||||
<DropdownTrigger className='icon-button' style={iconStyle} role='button' aria-expanded={expanded} onKeyDown={this.handleToggle} tabIndex='0' aria-label={ariaLabel}>
|
||||
<i className={iconClassname} aria-hidden />
|
||||
</DropdownTrigger>
|
||||
|
||||
|
@ -13,6 +13,7 @@ export default class IconButton extends React.PureComponent {
|
||||
size: PropTypes.number,
|
||||
active: PropTypes.bool,
|
||||
pressed: PropTypes.bool,
|
||||
expanded: PropTypes.bool,
|
||||
style: PropTypes.object,
|
||||
activeStyle: PropTypes.object,
|
||||
disabled: PropTypes.bool,
|
||||
@ -77,6 +78,7 @@ export default class IconButton extends React.PureComponent {
|
||||
<button
|
||||
aria-label={this.props.title}
|
||||
aria-pressed={this.props.pressed}
|
||||
aria-expanded={this.props.expanded}
|
||||
title={this.props.title}
|
||||
className={classes.join(' ')}
|
||||
onClick={this.handleClick}
|
||||
|
@ -212,10 +212,10 @@ export default class MediaGallery extends React.PureComponent {
|
||||
}
|
||||
|
||||
children = (
|
||||
<div role='button' tabIndex='0' className='media-spoiler' onClick={this.handleOpen}>
|
||||
<button className='media-spoiler' onClick={this.handleOpen}>
|
||||
<span className='media-spoiler__warning'>{warning}</span>
|
||||
<span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
const size = media.take(4).size;
|
||||
|
@ -172,7 +172,7 @@ export default class Status extends ImmutablePureComponent {
|
||||
|
||||
// Exclude intersectionObserverWrapper from `other` variable
|
||||
// because intersection is managed in here.
|
||||
const { status, account, intersectionObserverWrapper, index, listLength, ...other } = this.props;
|
||||
const { status, account, intersectionObserverWrapper, index, listLength, wrapped, ...other } = this.props;
|
||||
const { isExpanded, isIntersecting, isHidden } = this.state;
|
||||
|
||||
if (status === null) {
|
||||
@ -234,7 +234,7 @@ export default class Status extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<article aria-posinset={index} aria-setsize={listLength} className={`status ${this.props.muted ? 'muted' : ''} status-${status.get('visibility')}`} data-id={status.get('id')} tabIndex='0' ref={this.handleRef}>
|
||||
<article aria-posinset={index} aria-setsize={listLength} className={`status ${this.props.muted ? 'muted' : ''} status-${status.get('visibility')}`} data-id={status.get('id')} tabIndex={wrapped ? null : '0'} ref={this.handleRef}>
|
||||
<div className='status__info'>
|
||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
||||
|
||||
|
@ -151,8 +151,8 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
||||
return (
|
||||
<div className='status__action-bar'>
|
||||
<IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={replyIcon} onClick={this.handleReplyClick} />
|
||||
<IconButton className='status__action-bar-button' disabled={anonymousAccess || reblogDisabled} active={status.get('reblogged')} title={reblogDisabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
|
||||
<IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
||||
<IconButton className='status__action-bar-button' disabled={anonymousAccess || reblogDisabled} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogDisabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
|
||||
<IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
||||
{shareButton}
|
||||
|
||||
<div className='status__action-bar-dropdown'>
|
||||
|
@ -146,7 +146,7 @@ export default class StatusContent extends React.PureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames} ref={this.setRef} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' aria-label={status.get('search_index')} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
||||
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
|
||||
<span dangerouslySetInnerHTML={spoilerContent} />
|
||||
{' '}
|
||||
@ -155,13 +155,15 @@ export default class StatusContent extends React.PureComponent {
|
||||
|
||||
{mentionsPlaceholder}
|
||||
|
||||
<div className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
|
||||
<div tabIndex={!hidden && 0} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
);
|
||||
} else if (this.props.onClick) {
|
||||
return (
|
||||
<div
|
||||
ref={this.setRef}
|
||||
tabIndex='0'
|
||||
aria-label={status.get('search_index')}
|
||||
className={classNames}
|
||||
style={directionStyle}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
@ -172,6 +174,8 @@ export default class StatusContent extends React.PureComponent {
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
tabIndex='0'
|
||||
aria-label={status.get('search_index')}
|
||||
ref={this.setRef}
|
||||
className='status__content'
|
||||
style={directionStyle}
|
||||
|
@ -109,9 +109,9 @@ export default class StatusList extends ImmutablePureComponent {
|
||||
const article = (() => {
|
||||
switch (e.key) {
|
||||
case 'PageDown':
|
||||
return e.nativeEvent.path[0].nodeName === 'ARTICLE' && e.nativeEvent.path[0].nextElementSibling;
|
||||
return e.target.nodeName === 'ARTICLE' && e.target.nextElementSibling;
|
||||
case 'PageUp':
|
||||
return e.nativeEvent.path[0].nodeName === 'ARTICLE' && e.nativeEvent.path[0].previousElementSibling;
|
||||
return e.target.nodeName === 'ARTICLE' && e.target.previousElementSibling;
|
||||
case 'End':
|
||||
return this.node.querySelector('[role="feed"] > article:last-of-type');
|
||||
case 'Home':
|
||||
|
@ -13,12 +13,12 @@ export default class CharacterCounter extends React.PureComponent {
|
||||
if (diff < 0) {
|
||||
return <span className='character-counter character-counter--over'>{diff}</span>;
|
||||
}
|
||||
|
||||
return <span className='character-counter'>{diff}</span>;
|
||||
}
|
||||
|
||||
render () {
|
||||
const diff = this.props.max - length(this.props.text);
|
||||
|
||||
return this.checkRemainingText(diff);
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import WarningContainer from '../containers/warning_container';
|
||||
import { isMobile } from '../../../is_mobile';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { length } from 'stringz';
|
||||
import { countableText } from '../util/counter';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
|
||||
@ -145,9 +146,9 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
render () {
|
||||
const { intl, onPaste, showSearch } = this.props;
|
||||
const disabled = this.props.is_submitting;
|
||||
const text = [this.props.spoiler_text, this.props.text].join('');
|
||||
const text = [this.props.spoiler_text, countableText(this.props.text)].join('');
|
||||
|
||||
let publishText = '';
|
||||
let publishText = '';
|
||||
|
||||
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
||||
publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
|
||||
@ -203,7 +204,7 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||
|
||||
<div className='compose-form__publish'>
|
||||
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
|
||||
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !==0 && text.trim().length === 0)} block /></div>
|
||||
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -124,7 +124,7 @@ export default class EmojiPickerDropdown extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<Dropdown ref={this.setRef} className='emoji-picker__dropdown' active={active && !loading} onShow={this.onShowDropdown} onHide={this.onHideDropdown}>
|
||||
<DropdownTrigger className='emoji-button' title={title} aria-label={title} aria-pressed={active} role='button' onKeyDown={this.onToggle} tabIndex={0} >
|
||||
<DropdownTrigger className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onKeyDown={this.onToggle} tabIndex={0} >
|
||||
<img
|
||||
className={`emojione ${active && loading ? 'pulse-loading' : ''}`}
|
||||
alt='🙂'
|
||||
|
@ -109,7 +109,7 @@ export default class PrivacyDropdown extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}>
|
||||
<div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} pressed={open} active={open} inverted onClick={this.handleToggle} style={iconStyle} /></div>
|
||||
<div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} expanded={open} active={open} inverted onClick={this.handleToggle} style={iconStyle} /></div>
|
||||
<div className='privacy-dropdown__dropdown'>
|
||||
{open && this.options.map(item =>
|
||||
<div role='button' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleClick} onClick={this.handleClick} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}>
|
||||
|
@ -15,6 +15,7 @@ const messages = defineMessages({
|
||||
const mapStateToProps = state => ({
|
||||
visible: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
active: state.getIn(['compose', 'sensitive']),
|
||||
disabled: state.getIn(['compose', 'spoiler']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
@ -30,12 +31,13 @@ class SensitiveButton extends React.PureComponent {
|
||||
static propTypes = {
|
||||
visible: PropTypes.bool,
|
||||
active: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { visible, active, onClick, intl } = this.props;
|
||||
const { visible, active, disabled, onClick, intl } = this.props;
|
||||
|
||||
return (
|
||||
<Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(visible ? 1 : 0.87, { stiffness: 200, damping: 3 }) }}>
|
||||
@ -53,6 +55,7 @@ class SensitiveButton extends React.PureComponent {
|
||||
onClick={onClick}
|
||||
size={18}
|
||||
active={active}
|
||||
disabled={disabled}
|
||||
style={{ lineHeight: null, height: null }}
|
||||
inverted
|
||||
/>
|
||||
|
7
app/javascript/mastodon/features/compose/util/counter.js
Normal file
7
app/javascript/mastodon/features/compose/util/counter.js
Normal file
@ -0,0 +1,7 @@
|
||||
const urlPlaceholder = 'xxxxxxxxxxxxxxxxxxxxxxx';
|
||||
|
||||
export function countableText(inputText) {
|
||||
return inputText
|
||||
.replace(/https?:\/\/\S+/g, urlPlaceholder)
|
||||
.replace(/(?:^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+)/ig, '@$2');
|
||||
};
|
@ -10,6 +10,8 @@ import ImageLoader from './image_loader';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
|
||||
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
@ -66,16 +68,10 @@ export default class MediaModal extends ImmutablePureComponent {
|
||||
|
||||
const index = this.getIndex();
|
||||
|
||||
let leftNav, rightNav, content;
|
||||
const leftNav = media.size > 1 && <button tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>;
|
||||
const rightNav = media.size > 1 && <button tabIndex='0' className='modal-container__nav modal-container__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>;
|
||||
|
||||
leftNav = rightNav = content = '';
|
||||
|
||||
if (media.size > 1) {
|
||||
leftNav = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>;
|
||||
rightNav = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--right' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>;
|
||||
}
|
||||
|
||||
content = media.map((image) => {
|
||||
const content = media.map((image) => {
|
||||
const width = image.getIn(['meta', 'original', 'width']) || null;
|
||||
const height = image.getIn(['meta', 'original', 'height']) || null;
|
||||
|
||||
|
@ -53,7 +53,7 @@ export default class ModalRoot extends React.PureComponent {
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (!this.type && !!prevProps.type) {
|
||||
if (!this.props.type && !!prevProps.type) {
|
||||
this.getSiblings().forEach(sibling => sibling.removeAttribute('inert'));
|
||||
this.activeElement.focus();
|
||||
this.activeElement = null;
|
||||
|
@ -2,6 +2,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import NavLink from 'react-router-dom/NavLink';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { debounce } from 'lodash';
|
||||
import { isUserTouching } from '../../../is_mobile';
|
||||
|
||||
export const links = [
|
||||
<NavLink className='tabs-bar__link primary' to='/statuses/new' data-preview-title-id='tabs_bar.compose' data-preview-icon='pencil' ><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>,
|
||||
@ -25,16 +27,56 @@ export function getLink (index) {
|
||||
@injectIntl
|
||||
export default class TabsBar extends React.Component {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
setRef = ref => {
|
||||
this.node = ref;
|
||||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
// Only apply optimization for touch devices, which we assume are slower
|
||||
// We thus avoid the 250ms delay for non-touch devices and the lag for touch devices
|
||||
if (isUserTouching()) {
|
||||
e.preventDefault();
|
||||
e.persist();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const tabs = Array(...this.node.querySelectorAll('.tabs-bar__link'));
|
||||
const currentTab = tabs.find(tab => tab.classList.contains('active'));
|
||||
const nextTab = tabs.find(tab => tab.contains(e.target));
|
||||
const { props: { to } } = links[Array(...this.node.childNodes).indexOf(nextTab)];
|
||||
|
||||
|
||||
if (currentTab !== nextTab) {
|
||||
if (currentTab) {
|
||||
currentTab.classList.remove('active');
|
||||
}
|
||||
|
||||
const listener = debounce(() => {
|
||||
nextTab.removeEventListener('transitionend', listener);
|
||||
this.context.router.history.push(to);
|
||||
}, 50);
|
||||
|
||||
nextTab.addEventListener('transitionend', listener);
|
||||
nextTab.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl: { formatMessage } } = this.props;
|
||||
|
||||
return (
|
||||
<nav className='tabs-bar'>
|
||||
{links.map(link => React.cloneElement(link, { key: link.props.to, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))}
|
||||
<nav className='tabs-bar' ref={this.setRef}>
|
||||
{links.map(link => React.cloneElement(link, { key: link.props.to, onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "عرض الردود",
|
||||
"home.settings": "إعدادات العمود",
|
||||
"lightbox.close": "إغلاق",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "تحميل ...",
|
||||
"media_gallery.toggle_visible": "عرض / إخفاء",
|
||||
"missing_indicator.label": "تعذر العثور عليه",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.settings": "Column settings",
|
||||
"lightbox.close": "Затвори",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Зареждане...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
"missing_indicator.label": "Not found",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Mostrar respostes",
|
||||
"home.settings": "Ajustos de columna",
|
||||
"lightbox.close": "Tancar",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Carregant...",
|
||||
"media_gallery.toggle_visible": "Alternar visibilitat",
|
||||
"missing_indicator.label": "No trobat",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Antworten anzeigen",
|
||||
"home.settings": "Spalteneinstellungen",
|
||||
"lightbox.close": "Schließen",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Lade…",
|
||||
"media_gallery.toggle_visible": "Sichtbarkeit einstellen",
|
||||
"missing_indicator.label": "Nicht gefunden",
|
||||
|
@ -1113,6 +1113,14 @@
|
||||
{
|
||||
"defaultMessage": "Close",
|
||||
"id": "lightbox.close"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Previous",
|
||||
"id": "lightbox.previous"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Next",
|
||||
"id": "lightbox.next"
|
||||
}
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/ui/components/media_modal.json"
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.settings": "Column settings",
|
||||
"lightbox.close": "Close",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Loading...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
"missing_indicator.label": "Not found",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.settings": "Column settings",
|
||||
"lightbox.close": "Fermi",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Ŝarĝanta...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
"missing_indicator.label": "Not found",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.settings": "Column settings",
|
||||
"lightbox.close": "Cerrar",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Cargando...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
"missing_indicator.label": "Not found",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "نمایش پاسخها",
|
||||
"home.settings": "تنظیمات ستون",
|
||||
"lightbox.close": "بستن",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "بارگیری...",
|
||||
"media_gallery.toggle_visible": "تغییر پیدایی",
|
||||
"missing_indicator.label": "پیدا نشد",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.settings": "Column settings",
|
||||
"lightbox.close": "Sulje",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Ladataan...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
"missing_indicator.label": "Not found",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Afficher les réponses",
|
||||
"home.settings": "Paramètres de la colonne",
|
||||
"lightbox.close": "Fermer",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Chargement…",
|
||||
"media_gallery.toggle_visible": "Modifier la visibilité",
|
||||
"missing_indicator.label": "Non trouvé",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "הצגת תגובות",
|
||||
"home.settings": "הגדרות טור",
|
||||
"lightbox.close": "סגירה",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "טוען...",
|
||||
"media_gallery.toggle_visible": "נראה\\בלתי נראה",
|
||||
"missing_indicator.label": "לא נמצא",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Pokaži odgovore",
|
||||
"home.settings": "Postavke Stupca",
|
||||
"lightbox.close": "Zatvori",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Učitavam...",
|
||||
"media_gallery.toggle_visible": "Preklopi vidljivost",
|
||||
"missing_indicator.label": "Nije nađen",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.settings": "Column settings",
|
||||
"lightbox.close": "Bezárás",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Betöltés...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
"missing_indicator.label": "Not found",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Tampilkan balasan",
|
||||
"home.settings": "Pengaturan kolom",
|
||||
"lightbox.close": "Tutup",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Tunggu sebentar...",
|
||||
"media_gallery.toggle_visible": "Tampil/Sembunyikan",
|
||||
"missing_indicator.label": "Tidak ditemukan",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Montrar respondi",
|
||||
"home.settings": "Aranji di la kolumno",
|
||||
"lightbox.close": "Klozar",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Kargante...",
|
||||
"media_gallery.toggle_visible": "Chanjar videbleso",
|
||||
"missing_indicator.label": "Ne trovita",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Mostra risposte",
|
||||
"home.settings": "Impostazioni colonna",
|
||||
"lightbox.close": "Chiudi",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Carico...",
|
||||
"media_gallery.toggle_visible": "Imposta visibilità",
|
||||
"missing_indicator.label": "Non trovato",
|
||||
|
@ -13,7 +13,7 @@
|
||||
"account.posts": "投稿",
|
||||
"account.report": "通報",
|
||||
"account.requested": "承認待ち",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.share": "@{name} のプロフィールを共有する",
|
||||
"account.unblock": "ブロック解除",
|
||||
"account.unblock_domain": "{domain}を表示",
|
||||
"account.unfollow": "フォロー解除",
|
||||
@ -36,8 +36,8 @@
|
||||
"column.public": "連合タイムライン",
|
||||
"column_back_button.label": "戻る",
|
||||
"column_header.hide_settings": "設定を隠す",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.moveLeft_settings": "カラムを左に移動する",
|
||||
"column_header.moveRight_settings": "カラムを右に移動する",
|
||||
"column_header.pin": "ピン留めする",
|
||||
"column_header.show_settings": "設定を表示",
|
||||
"column_header.unpin": "ピン留めを外す",
|
||||
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "返信表示",
|
||||
"home.settings": "カラム設定",
|
||||
"lightbox.close": "閉じる",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "読み込み中...",
|
||||
"media_gallery.toggle_visible": "表示切り替え",
|
||||
"missing_indicator.label": "見つかりません",
|
||||
@ -154,7 +156,7 @@
|
||||
"reply_indicator.cancel": "キャンセル",
|
||||
"report.placeholder": "コメント",
|
||||
"report.submit": "通報する",
|
||||
"report.target": "問題のユーザー",
|
||||
"report.target": "{target} を通報する",
|
||||
"search.placeholder": "検索",
|
||||
"search_results.total": "{count, number}件の結果",
|
||||
"standalone.public_title": "連合タイムライン",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "답글 표시",
|
||||
"home.settings": "컬럼 설정",
|
||||
"lightbox.close": "닫기",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "불러오는 중...",
|
||||
"media_gallery.toggle_visible": "표시 전환",
|
||||
"missing_indicator.label": "찾을 수 없습니다",
|
||||
|
@ -13,7 +13,7 @@
|
||||
"account.posts": "Toots",
|
||||
"account.report": "Rapporteer @{name}",
|
||||
"account.requested": "Wacht op goedkeuring",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.share": "Profiel van @{name} delen",
|
||||
"account.unblock": "Deblokkeer @{name}",
|
||||
"account.unblock_domain": "{domain} niet meer negeren",
|
||||
"account.unfollow": "Ontvolgen",
|
||||
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Reacties tonen",
|
||||
"home.settings": "Kolom-instellingen",
|
||||
"lightbox.close": "Sluiten",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Laden…",
|
||||
"media_gallery.toggle_visible": "Media wel/niet tonen",
|
||||
"missing_indicator.label": "Niet gevonden",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Vis svar",
|
||||
"home.settings": "Kolonneinnstillinger",
|
||||
"lightbox.close": "Lukk",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Laster...",
|
||||
"media_gallery.toggle_visible": "Veksle synlighet",
|
||||
"missing_indicator.label": "Ikke funnet",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Mostrar las responsas",
|
||||
"home.settings": "Paramètres de la colomna",
|
||||
"lightbox.close": "Tampar",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Cargament…",
|
||||
"media_gallery.toggle_visible": "Modificar la visibilitat",
|
||||
"missing_indicator.label": "Pas trobat",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Pokazuj odpowiedzi",
|
||||
"home.settings": "Ustawienia kolumny",
|
||||
"lightbox.close": "Zamknij",
|
||||
"lightbox.next": "Następne",
|
||||
"lightbox.previous": "Poprzednie",
|
||||
"loading_indicator.label": "Ładowanie...",
|
||||
"media_gallery.toggle_visible": "Przełącz widoczność",
|
||||
"missing_indicator.label": "Nie znaleziono",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Mostrar as respostas",
|
||||
"home.settings": "Parâmetros da listagem",
|
||||
"lightbox.close": "Fechar",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Carregando...",
|
||||
"media_gallery.toggle_visible": "Esconder/Mostrar",
|
||||
"missing_indicator.label": "Não encontrado",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Mostrar as respostas",
|
||||
"home.settings": "Parâmetros da listagem",
|
||||
"lightbox.close": "Fechar",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Carregando...",
|
||||
"media_gallery.toggle_visible": "Esconder/Mostrar",
|
||||
"missing_indicator.label": "Não encontrado",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Показывать ответы",
|
||||
"home.settings": "Настройки колонки",
|
||||
"lightbox.close": "Закрыть",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Загрузка...",
|
||||
"media_gallery.toggle_visible": "Показать/скрыть",
|
||||
"missing_indicator.label": "Не найдено",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.settings": "Column settings",
|
||||
"lightbox.close": "Close",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Loading...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
"missing_indicator.label": "Not found",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Cevapları göster",
|
||||
"home.settings": "Kolon ayarları",
|
||||
"lightbox.close": "Kapat",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Yükleniyor...",
|
||||
"media_gallery.toggle_visible": "Görünürlüğü değiştir",
|
||||
"missing_indicator.label": "Bulunamadı",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "Показувати відповіді",
|
||||
"home.settings": "Налаштування колонок",
|
||||
"lightbox.close": "Закрити",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Завантаження...",
|
||||
"media_gallery.toggle_visible": "Показати/приховати",
|
||||
"missing_indicator.label": "Не знайдено",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "显示回应嘟文",
|
||||
"home.settings": "字段设置",
|
||||
"lightbox.close": "关闭",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "加载中……",
|
||||
"media_gallery.toggle_visible": "打开或关上",
|
||||
"missing_indicator.label": "找不到内容",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "顯示回應文章",
|
||||
"home.settings": "欄位設定",
|
||||
"lightbox.close": "關閉",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "載入中...",
|
||||
"media_gallery.toggle_visible": "打開或關上",
|
||||
"missing_indicator.label": "找不到內容",
|
||||
|
@ -94,6 +94,8 @@
|
||||
"home.column_settings.show_replies": "顯示回應",
|
||||
"home.settings": "欄位設定",
|
||||
"lightbox.close": "關閉",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "讀取中...",
|
||||
"media_gallery.toggle_visible": "切換可見性",
|
||||
"missing_indicator.label": "找不到",
|
||||
|
@ -88,7 +88,7 @@ function appendMedia(state, media) {
|
||||
map.set('focusDate', new Date());
|
||||
map.set('idempotencyKey', uuid());
|
||||
|
||||
if (prevSize === 0 && state.get('default_sensitive')) {
|
||||
if (prevSize === 0 && (state.get('default_sensitive') || state.get('spoiler'))) {
|
||||
map.set('sensitive', true);
|
||||
}
|
||||
});
|
||||
@ -152,14 +152,22 @@ export default function compose(state = initialState, action) {
|
||||
.set('mounted', false)
|
||||
.set('is_composing', false);
|
||||
case COMPOSE_SENSITIVITY_CHANGE:
|
||||
return state
|
||||
.set('sensitive', !state.get('sensitive'))
|
||||
.set('idempotencyKey', uuid());
|
||||
return state.withMutations(map => {
|
||||
if (!state.get('spoiler')) {
|
||||
map.set('sensitive', !state.get('sensitive'));
|
||||
}
|
||||
|
||||
map.set('idempotencyKey', uuid());
|
||||
});
|
||||
case COMPOSE_SPOILERNESS_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
map.set('spoiler_text', '');
|
||||
map.set('spoiler', !state.get('spoiler'));
|
||||
map.set('idempotencyKey', uuid());
|
||||
|
||||
if (!state.get('sensitive') && state.get('media_attachments').size >= 1) {
|
||||
map.set('sensitive', true);
|
||||
}
|
||||
});
|
||||
case COMPOSE_SPOILER_TEXT_CHANGE:
|
||||
return state
|
||||
|
@ -147,10 +147,15 @@
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
a,
|
||||
span {
|
||||
font-weight: 400;
|
||||
color: lighten($ui-base-color, 34%);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,6 +314,10 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: rgba($ui-base-lighter-color, .6);
|
||||
}
|
||||
|
||||
.header {
|
||||
line-height: 30px;
|
||||
overflow: hidden;
|
||||
|
@ -148,13 +148,17 @@
|
||||
color: $ui-base-lighter-color;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $ui-highlight-color;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: $ui-primary-color;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $ui-highlight-color;
|
||||
|
||||
&.disabled {
|
||||
color: lighten($ui-highlight-color, 13%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.overlayed {
|
||||
@ -462,6 +466,10 @@
|
||||
overflow: hidden;
|
||||
white-space: pre-wrap;
|
||||
|
||||
&:focus {
|
||||
outline: rgba($ui-highlight-color, 0.7) solid 2px;
|
||||
}
|
||||
|
||||
.emojione {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
@ -563,6 +571,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.status-direct:focus {
|
||||
outline: 0;
|
||||
background-color: lighten($ui-base-color, 10%);
|
||||
}
|
||||
|
||||
&.light {
|
||||
.status__relative-time {
|
||||
color: $ui-primary-color;
|
||||
@ -1585,6 +1599,8 @@
|
||||
cursor: pointer;
|
||||
flex: 0 0 auto;
|
||||
font-size: 16px;
|
||||
border: 0;
|
||||
text-align: start;
|
||||
padding: 15px;
|
||||
z-index: 3;
|
||||
|
||||
@ -2315,6 +2331,8 @@ button.icon-button.active i.fa-retweet {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
@ -2388,6 +2406,7 @@ button.icon-button.active i.fa-retweet {
|
||||
align-items: center;
|
||||
background: rgba($base-overlay-background, 0.5);
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
color: $primary-text-color;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
@ -3480,7 +3499,7 @@ button.icon-button.active i.fa-retweet {
|
||||
}
|
||||
|
||||
.confirmation-modal {
|
||||
max-width: 280px;
|
||||
max-width: 85vw;
|
||||
|
||||
@media screen and (min-width: 480px) {
|
||||
max-width: 380px;
|
||||
|
@ -6,7 +6,7 @@ class Emoji
|
||||
include Singleton
|
||||
|
||||
def initialize
|
||||
data = Oj.load(File.open(File.join(Rails.root, 'lib', 'assets', 'emoji.json')))
|
||||
data = Oj.load(File.open(Rails.root.join('lib', 'assets', 'emoji.json')))
|
||||
|
||||
@map = {}
|
||||
|
||||
|
@ -33,9 +33,7 @@ class LanguageDetector
|
||||
|
||||
def simplified_text
|
||||
text.dup.tap do |new_text|
|
||||
URI.extract(new_text).each do |url|
|
||||
new_text.gsub!(url, '')
|
||||
end
|
||||
new_text.gsub!(FetchLinkCardService::URL_PATTERN, '')
|
||||
new_text.gsub!(Account::MENTION_RE, '')
|
||||
new_text.gsub!(Tag::HASHTAG_RE, '')
|
||||
new_text.gsub!(/\s+/, ' ')
|
||||
|
@ -44,7 +44,7 @@
|
||||
#
|
||||
|
||||
class Account < ApplicationRecord
|
||||
MENTION_RE = /(?:^|[^\/[:word:]])@([a-z0-9_]+(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
|
||||
MENTION_RE = /(?:^|[^\/[:word:]])@(([a-z0-9_]+)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
|
||||
|
||||
include AccountAvatar
|
||||
include AccountFinderConcern
|
||||
|
@ -135,7 +135,7 @@ class Web::PushSubscription < ApplicationRecord
|
||||
end
|
||||
|
||||
if can_boost
|
||||
actions << { title: translate('push_notifications.mention.action_boost'), icon: full_asset_url('web-push-icon_boost.png', skip_pipeline: true), todo: 'request', method: 'POST', action: "/api/v1/statuses/#{notification.target_status.id}/reblog" }
|
||||
actions << { title: translate('push_notifications.mention.action_boost'), icon: full_asset_url('web-push-icon_reblog.png', skip_pipeline: true), todo: 'request', method: 'POST', action: "/api/v1/statuses/#{notification.target_status.id}/reblog" }
|
||||
end
|
||||
|
||||
actions
|
||||
|
@ -90,7 +90,7 @@ class BatchedRemoveStatusService < BaseService
|
||||
key = FeedManager.instance.key(:home, follower_id)
|
||||
|
||||
originals = statuses.reject(&:reblog?)
|
||||
reblogs = statuses.reject { |s| !s.reblog? }
|
||||
reblogs = statuses.select(&:reblog?)
|
||||
|
||||
# Quickly remove all originals
|
||||
redis.pipelined do
|
||||
|
@ -5,6 +5,27 @@ class StatusLengthValidator < ActiveModel::Validator
|
||||
|
||||
def validate(status)
|
||||
return unless status.local? && !status.reblog?
|
||||
status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if [status.text, status.spoiler_text].join.mb_chars.grapheme_length > MAX_CHARS
|
||||
status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if too_long?(status)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def too_long?(status)
|
||||
countable_length(status) > MAX_CHARS
|
||||
end
|
||||
|
||||
def countable_length(status)
|
||||
total_text(status).mb_chars.grapheme_length
|
||||
end
|
||||
|
||||
def total_text(status)
|
||||
[status.spoiler_text, countable_text(status)].join
|
||||
end
|
||||
|
||||
def countable_text(status)
|
||||
status.text.dup.tap do |new_text|
|
||||
new_text.gsub!(FetchLinkCardService::URL_PATTERN, 'x' * 23)
|
||||
new_text.gsub!(Account::MENTION_RE, '@\2')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,10 @@
|
||||
.panel-header
|
||||
= succeed ':' do
|
||||
= t 'about.contact'
|
||||
%span{ title: contact.site_contact_email.presence }= contact.site_contact_email.presence
|
||||
- if contact.site_contact_email.present?
|
||||
= mail_to contact.site_contact_email, nil, title: contact.site_contact_email
|
||||
- else
|
||||
%span= t 'about.contact_unavailable'
|
||||
.panel-body
|
||||
- if contact.contact_account
|
||||
.owner
|
||||
|
@ -14,15 +14,13 @@
|
||||
required: true,
|
||||
input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
|
||||
= f.input :password,
|
||||
autocomplete: 'off',
|
||||
placeholder: t('simple_form.labels.defaults.password'),
|
||||
required: true,
|
||||
input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
|
||||
input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
|
||||
= f.input :password_confirmation,
|
||||
autocomplete: 'off',
|
||||
placeholder: t('simple_form.labels.defaults.confirm_password'),
|
||||
required: true,
|
||||
input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password') }
|
||||
input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }
|
||||
|
||||
.actions
|
||||
= f.button :button, t('auth.register'), type: :submit, class: 'button button-alternative'
|
||||
|
@ -1,4 +1,4 @@
|
||||
<%= display_name(@me) %>,
|
||||
<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
|
||||
|
||||
<%= raw t('admin_mailer.new_report.body', target: @report.target_account.acct, reporter: @report.account.acct) %>
|
||||
|
||||
|
@ -5,8 +5,8 @@
|
||||
= render 'shared/error_messages', object: resource
|
||||
= f.input :reset_password_token, as: :hidden
|
||||
|
||||
= f.input :password, autofocus: true, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password') }
|
||||
= f.input :password_confirmation, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password') }
|
||||
= f.input :password, autofocus: true, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
|
||||
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
|
||||
|
||||
.actions
|
||||
= f.button :button, t('auth.set_new_password'), type: :submit
|
||||
|
@ -5,9 +5,9 @@
|
||||
= render 'shared/error_messages', object: resource
|
||||
|
||||
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
|
||||
= f.input :password, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password') }
|
||||
= f.input :password_confirmation, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password') }
|
||||
= f.input :current_password, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password') }
|
||||
= f.input :password, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
|
||||
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
|
||||
= f.input :current_password, placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }
|
||||
|
||||
.actions
|
||||
= f.button :button, t('generic.save_changes'), type: :submit
|
||||
|
@ -11,8 +11,8 @@
|
||||
= "@#{site_hostname}"
|
||||
|
||||
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
|
||||
= f.input :password, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
|
||||
= f.input :password_confirmation, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password') }
|
||||
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
|
||||
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }
|
||||
|
||||
.actions
|
||||
= f.button :button, t('auth.register'), type: :submit
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
|
||||
= f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
|
||||
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
|
||||
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
|
||||
|
||||
.actions
|
||||
= f.button :button, t('auth.login'), type: :submit
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
= 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') }, required: true, autofocus: true, autocomplete: 'off',
|
||||
input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'off' }, required: true, autofocus: true,
|
||||
hint: t('simple_form.hints.sessions.otp')
|
||||
|
||||
.actions
|
||||
|
@ -1,4 +1,4 @@
|
||||
<%= display_name(@me) %>,
|
||||
<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
|
||||
|
||||
<%= raw t('notification_mailer.digest.body', since: l(@since), instance: root_url) %>
|
||||
<% @notifications.each do |notification| %>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<%= display_name(@me) %>,
|
||||
<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
|
||||
|
||||
<%= raw t('notification_mailer.favourite.body', name: @account.acct) %>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<%= display_name(@me) %>,
|
||||
<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
|
||||
|
||||
<%= raw t('notification_mailer.follow.body', name: @account.acct) %>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<%= display_name(@me) %>,
|
||||
<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
|
||||
|
||||
<%= raw t('notification_mailer.follow_request.body', name: @account.acct) %>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<%= display_name(@me) %>,
|
||||
<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
|
||||
|
||||
<%= raw t('notification_mailer.mention.body', name: @status.account.acct) %>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<%= display_name(@me) %>,
|
||||
<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
|
||||
|
||||
<%= raw t('notification_mailer.reblog.body', name: @account.acct) %>
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
%p.hint= t('deletes.description_html')
|
||||
|
||||
= f.input :password, autocomplete: 'off', placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password') }, hint: t('deletes.confirm_password')
|
||||
= f.input :password, placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, hint: t('deletes.confirm_password')
|
||||
|
||||
.actions
|
||||
= f.button :button, t('deletes.proceed'), type: :submit, class: 'negative'
|
||||
|
@ -11,7 +11,7 @@
|
||||
%p.hint= t('two_factor_authentication.manual_instructions')
|
||||
%samp.qr-alternative__code= current_user.otp_secret.scan(/.{4}/).join(' ')
|
||||
|
||||
= f.input :code, hint: t('two_factor_authentication.code_hint'), placeholder: t('simple_form.labels.defaults.otp_attempt')
|
||||
= f.input :code, hint: t('two_factor_authentication.code_hint'), placeholder: t('simple_form.labels.defaults.otp_attempt'), input_html: { :autocomplete => 'off' }
|
||||
|
||||
.actions
|
||||
= f.button :button, t('two_factor_authentication.enable'), type: :submit
|
||||
|
@ -10,7 +10,7 @@
|
||||
%hr/
|
||||
|
||||
= simple_form_for @confirmation, url: settings_two_factor_authentication_path, method: :delete do |f|
|
||||
= f.input :code, hint: t('two_factor_authentication.code_hint'), placeholder: t('simple_form.labels.defaults.otp_attempt')
|
||||
= f.input :code, hint: t('two_factor_authentication.code_hint'), placeholder: t('simple_form.labels.defaults.otp_attempt'), input_html: { :autocomplete => 'off' }
|
||||
|
||||
.actions
|
||||
= f.button :button, t('two_factor_authentication.disable'), type: :submit
|
||||
|
@ -14,7 +14,7 @@ class Pubsubhubbub::DistributionWorker
|
||||
@subscriptions = active_subscriptions.to_a
|
||||
|
||||
distribute_public!(stream_entries.reject(&:hidden?))
|
||||
distribute_hidden!(stream_entries.reject { |s| !s.hidden? })
|
||||
distribute_hidden!(stream_entries.select(&:hidden?))
|
||||
end
|
||||
|
||||
private
|
||||
@ -35,7 +35,7 @@ class Pubsubhubbub::DistributionWorker
|
||||
@payload = OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, stream_entries))
|
||||
@domains = @account.followers.domains
|
||||
|
||||
Pubsubhubbub::DeliveryWorker.push_bulk(@subscriptions.reject { |s| !allowed_to_receive?(s.callback_url, s.domain) }) do |subscription|
|
||||
Pubsubhubbub::DeliveryWorker.push_bulk(@subscriptions.select { |s| allowed_to_receive?(s.callback_url, s.domain) }) do |subscription|
|
||||
[subscription.id, @payload]
|
||||
end
|
||||
end
|
||||
|
@ -215,6 +215,7 @@ en:
|
||||
body: "%{reporter} has reported %{target}"
|
||||
subject: New report for %{instance} (#%{id})
|
||||
application_mailer:
|
||||
salutation: '%{name},'
|
||||
settings: 'Change e-mail preferences: %{link}'
|
||||
signature: Mastodon notifications from %{instance}
|
||||
view: 'View:'
|
||||
|
@ -5,9 +5,14 @@ ja:
|
||||
about_this: 詳細情報
|
||||
closed_registrations: 現在このインスタンスでの新規登録は受け付けていません。しかし、他のインスタンスにアカウントを作成しても全く同じネットワークに参加することができます。
|
||||
contact: 連絡先
|
||||
contact_missing: 未設定
|
||||
contact_unavailable: N/A
|
||||
description_headline: "%{domain} とは?"
|
||||
domain_count_after: 個のインスタンス
|
||||
domain_count_before: 接続中
|
||||
extended_description_html: |
|
||||
<h3>ルールを書くのに適した場所</h3>
|
||||
<p>詳細説明が設定されていません。</p>
|
||||
features:
|
||||
humane_approach_body: 他の SNS の失敗から学び、Mastodon はソーシャルメディアが誤った使い方をされることの無いように倫理的な設計を目指しています。
|
||||
humane_approach_title: より思いやりのある設計
|
||||
@ -104,12 +109,14 @@ ja:
|
||||
hint: ドメインブロックはデータベース中のアカウント項目の作成を妨げませんが、遡って自動的に指定されたモデレーションをそれらのアカウントに適用します。
|
||||
severity:
|
||||
desc_html: "<strong>サイレンス</strong>はアカウントのトゥートをフォローしていない人から隠します。<strong>停止</strong>はそのアカウントのコンテンツ、メディア、プロフィールデータをすべて削除します。"
|
||||
noop: なし
|
||||
silence: サイレンス
|
||||
suspend: 停止
|
||||
title: 新規ドメインブロック
|
||||
reject_media: メディアファイルを拒否
|
||||
reject_media_hint: ローカルに保存されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です。
|
||||
severities:
|
||||
noop: なし
|
||||
silence: サイレンス
|
||||
suspend: 停止
|
||||
severity: 深刻度
|
||||
@ -341,6 +348,8 @@ ja:
|
||||
title: あなたのトゥートが %{name} さんにお気に入り登録されました
|
||||
follow:
|
||||
title: "%{name} さんにフォローされました"
|
||||
group:
|
||||
title: "%{count} 件の通知"
|
||||
mention:
|
||||
action_boost: ブースト
|
||||
action_expand: もっと見る
|
||||
@ -392,6 +401,8 @@ ja:
|
||||
windows: Windows
|
||||
windows_mobile: Windows Mobile
|
||||
windows_phone: Windows Phone
|
||||
revoke: 削除
|
||||
revoke_success: セッションを削除しました
|
||||
title: セッション
|
||||
settings:
|
||||
authorized_apps: 認証済みアプリ
|
||||
|
@ -37,8 +37,8 @@ nl:
|
||||
type: Importtype
|
||||
username: gebruikersnaam
|
||||
interactions:
|
||||
must_be_follower: Blokkeermeldingen van mensen die jou niet volgen
|
||||
must_be_following: Blokkeermeldingen van mensen die jij niet volgt
|
||||
must_be_follower: Blokkeer meldingen van mensen die jou niet volgen
|
||||
must_be_following: Blokkeer meldingen van mensen die jij niet volgt
|
||||
notification_emails:
|
||||
digest: Verstuur periodiek e-mails met een samenvatting
|
||||
favourite: Verstuur een e-mail wanneer iemand jouw toot als favoriet markeert
|
||||
|
@ -21,7 +21,7 @@ module Mastodon
|
||||
end
|
||||
|
||||
def flags
|
||||
'rc2'
|
||||
'rc3'
|
||||
end
|
||||
|
||||
def to_a
|
||||
|
@ -9,7 +9,7 @@
|
||||
"start": "node ./streaming/index.js",
|
||||
"test": "npm run test:lint && npm run test:mocha",
|
||||
"test:lint": "eslint -c .eslintrc.yml --ext=js app/javascript/ config/webpack/ spec/javascript/ streaming/",
|
||||
"test:mocha": "cross-env NODE_ENV=test mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.js",
|
||||
"test:mocha": "cross-env NODE_ENV=test mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/**/*.test.js",
|
||||
"postinstall": "npm rebuild node-sass"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -3,7 +3,7 @@ require 'rails_helper'
|
||||
RSpec.describe Status, type: :model do
|
||||
let(:alice) { Fabricate(:account, username: 'alice') }
|
||||
let(:bob) { Fabricate(:account, username: 'bob') }
|
||||
let(:other) { Fabricate(:status, account: bob, text: 'Skulls for the skull god! The enemy\'s gates are sideways!')}
|
||||
let(:other) { Fabricate(:status, account: bob, text: 'Skulls for the skull god! The enemy\'s gates are sideways!') }
|
||||
|
||||
subject { Fabricate(:status, account: alice) }
|
||||
|
||||
|
44
spec/validators/status_length_validator_spec.rb
Normal file
44
spec/validators/status_length_validator_spec.rb
Normal file
@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe StatusLengthValidator do
|
||||
describe '#validate' do
|
||||
it 'does not add errors onto remote statuses'
|
||||
it 'does not add errors onto local reblogs'
|
||||
|
||||
it 'adds an error when content warning is over 500 characters' do
|
||||
status = double(spoiler_text: 'a' * 520, text: '', errors: double(add: nil), local?: true, reblog?: false)
|
||||
subject.validate(status)
|
||||
expect(status.errors).to have_received(:add)
|
||||
end
|
||||
|
||||
it 'adds an error when text is over 500 characters' do
|
||||
status = double(spoiler_text: '', text: 'a' * 520, errors: double(add: nil), local?: true, reblog?: false)
|
||||
subject.validate(status)
|
||||
expect(status.errors).to have_received(:add)
|
||||
end
|
||||
|
||||
it 'adds an error when text and content warning are over 500 characters total' do
|
||||
status = double(spoiler_text: 'a' * 250, text: 'b' * 251, errors: double(add: nil), local?: true, reblog?: false)
|
||||
subject.validate(status)
|
||||
expect(status.errors).to have_received(:add)
|
||||
end
|
||||
|
||||
it 'counts URLs as 23 characters flat' do
|
||||
text = ('a' * 476) + " http://#{'b' * 30}.com/example"
|
||||
status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
|
||||
|
||||
subject.validate(status)
|
||||
expect(status.errors).to_not have_received(:add)
|
||||
end
|
||||
|
||||
it 'counts only the front part of remote usernames' do
|
||||
text = ('a' * 475) + " @alice@#{'b' * 30}.com"
|
||||
status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
|
||||
|
||||
subject.validate(status)
|
||||
expect(status.errors).to_not have_received(:add)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user