Merge upstream (#111)
commit
2cdee6ae8a
|
@ -4,7 +4,6 @@ public/system
|
|||
public/assets
|
||||
public/packs
|
||||
node_modules
|
||||
storybook
|
||||
neo4j
|
||||
vendor/bundle
|
||||
.DS_Store
|
||||
|
|
|
@ -69,7 +69,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
|
|||
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
#SMTP_OPENSSL_VERIFY_MODE=peer
|
||||
#SMTP_ENABLE_STARTTLS_AUTO=true
|
||||
|
||||
#SMTP_TLS=true
|
||||
|
||||
# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
|
||||
# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
|
||||
|
|
|
@ -112,7 +112,7 @@ rules:
|
|||
jsx-a11y/iframe-has-title: warn
|
||||
jsx-a11y/img-has-alt: warn
|
||||
jsx-a11y/img-redundant-alt: warn
|
||||
jsx-a11y/label-has-for: warn
|
||||
jsx-a11y/label-has-for: off
|
||||
jsx-a11y/mouse-events-have-key-events: warn
|
||||
jsx-a11y/no-access-key: warn
|
||||
jsx-a11y/no-distracting-elements: warn
|
||||
|
@ -121,6 +121,6 @@ rules:
|
|||
jsx-a11y/onclick-has-focus: warn
|
||||
jsx-a11y/onclick-has-role: warn
|
||||
jsx-a11y/role-has-required-aria-props: warn
|
||||
jsx-a11y/role-supports-aria-props: warn
|
||||
jsx-a11y/role-supports-aria-props: off
|
||||
jsx-a11y/scope: warn
|
||||
jsx-a11y/tabindex-no-positive: warn
|
||||
|
|
|
@ -21,7 +21,6 @@ public/system
|
|||
public/assets
|
||||
public/packs
|
||||
public/packs-test
|
||||
public/sw.js
|
||||
.env
|
||||
.env.production
|
||||
node_modules/
|
||||
|
|
|
@ -14,7 +14,6 @@ node_modules/
|
|||
public/assets/
|
||||
public/system/
|
||||
spec/
|
||||
storybook/
|
||||
tmp/
|
||||
.vagrant/
|
||||
vendor/bundle/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,4 +2,3 @@ node_modules/
|
|||
.cache/
|
||||
docs/
|
||||
spec/
|
||||
storybook/
|
||||
|
|
10
Aptfile
10
Aptfile
|
@ -1,7 +1,9 @@
|
|||
protobuf-compiler
|
||||
libprotobuf-dev
|
||||
ffmpeg
|
||||
libicu-dev
|
||||
libidn11
|
||||
libidn11-dev
|
||||
libpq-dev
|
||||
libprotobuf-dev
|
||||
libxdamage1
|
||||
libxfixes3
|
||||
libicu-dev
|
||||
libidn11-dev
|
||||
protobuf-compiler
|
||||
|
|
|
@ -8,4 +8,3 @@ So here's the deal: we all work on this code, and then it runs on dev.glitch.soc
|
|||
|
||||
- You can view documentation for this project at [glitch-soc.github.io/docs/](https://glitch-soc.github.io/docs/).
|
||||
- And contributing guidelines are available [here](CONTRIBUTING.md) and [here](https://glitch-soc.github.io/docs/contributing/).
|
||||
|
||||
|
|
2
app.json
2
app.json
|
@ -2,7 +2,7 @@
|
|||
"name": "Mastodon",
|
||||
"description": "A GNU Social-compatible microblogging server",
|
||||
"repository": "https://github.com/tootsuite/mastodon",
|
||||
"logo": "https://github.com/tootsuite/mastodon/raw/master/app/javascript/images/logo.svg",
|
||||
"logo": "https://github.com/tootsuite.png",
|
||||
"env": {
|
||||
"HEROKU": {
|
||||
"description": "Leave this as true",
|
||||
|
|
|
@ -18,7 +18,7 @@ module Settings
|
|||
end
|
||||
|
||||
def destroy
|
||||
if current_user.validate_and_consume_otp!(confirmation_params[:code])
|
||||
if acceptable_code?
|
||||
current_user.otp_required_for_login = false
|
||||
current_user.save!
|
||||
redirect_to settings_two_factor_authentication_path
|
||||
|
@ -38,5 +38,10 @@ module Settings
|
|||
def verify_otp_required
|
||||
redirect_to settings_two_factor_authentication_path if current_user.otp_required_for_login?
|
||||
end
|
||||
|
||||
def acceptable_code?
|
||||
current_user.validate_and_consume_otp!(confirmation_params[:code]) ||
|
||||
current_user.invalidate_otp_backup_code!(confirmation_params[:code])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module InstanceHelper
|
||||
def site_title
|
||||
Setting.site_title.to_s
|
||||
Setting.site_title.presence || site_hostname
|
||||
end
|
||||
|
||||
def site_hostname
|
||||
|
|
|
@ -162,6 +162,8 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<div className='autosuggest-textarea'>
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{placeholder}</span>
|
||||
<Textarea
|
||||
inputRef={this.setTextarea}
|
||||
className='autosuggest-textarea__textarea'
|
||||
|
@ -176,6 +178,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
|||
onPaste={this.onPaste}
|
||||
style={style}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
|
||||
{suggestions.map((suggestion, i) => (
|
||||
|
|
|
@ -19,10 +19,10 @@ export default class ColumnBackButton extends React.PureComponent {
|
|||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import NotificationPurgeButtonsContainer from '../../glitch/components/column/notif_cleaning_widget/container';
|
||||
|
||||
const messages = defineMessages({
|
||||
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
||||
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
|
||||
moveLeft: { id: 'column_header.moveLeft_settings', defaultMessage: 'Move column to the left' },
|
||||
moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
|
||||
enterNotifCleaning : { id: 'notification_purge.start', defaultMessage: 'Enter notification cleaning mode' },
|
||||
});
|
||||
|
||||
|
@ -19,11 +23,13 @@ export default class ColumnHeader extends React.PureComponent {
|
|||
};
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
title: PropTypes.node.isRequired,
|
||||
icon: PropTypes.string.isRequired,
|
||||
active: PropTypes.bool,
|
||||
localSettings : ImmutablePropTypes.map,
|
||||
multiColumn: PropTypes.bool,
|
||||
focusable: PropTypes.bool,
|
||||
showBackButton: PropTypes.bool,
|
||||
notifCleaning: PropTypes.bool, // true only for the notification column
|
||||
notifCleaningActive: PropTypes.bool,
|
||||
|
@ -36,6 +42,10 @@ export default class ColumnHeader extends React.PureComponent {
|
|||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focusable: true,
|
||||
}
|
||||
|
||||
state = {
|
||||
collapsed: true,
|
||||
animating: false,
|
||||
|
@ -82,10 +92,9 @@ export default class ColumnHeader extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { intl, icon, active, children, pinned, onPin, multiColumn, showBackButton, notifCleaning, notifCleaningActive } = this.props;
|
||||
const { intl, icon, active, children, pinned, onPin, multiColumn, focusable, showBackButton, intl: { formatMessage }, notifCleaning, notifCleaningActive } = this.props;
|
||||
const { collapsed, animating, animatingNCD } = this.state;
|
||||
|
||||
|
||||
let title = this.props.title;
|
||||
|
||||
const wrapperClassName = classNames('column-header__wrapper', {
|
||||
|
@ -132,8 +141,8 @@ export default class ColumnHeader extends React.PureComponent {
|
|||
|
||||
moveButtons = (
|
||||
<div key='move-buttons' className='column-header__setting-arrows'>
|
||||
<button className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><i className='fa fa-chevron-left' /></button>
|
||||
<button className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><i className='fa fa-chevron-right' /></button>
|
||||
<button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><i className='fa fa-chevron-left' /></button>
|
||||
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><i className='fa fa-chevron-right' /></button>
|
||||
</div>
|
||||
);
|
||||
} else if (multiColumn) {
|
||||
|
@ -159,12 +168,12 @@ export default class ColumnHeader extends React.PureComponent {
|
|||
}
|
||||
|
||||
if (children || multiColumn) {
|
||||
collapseButton = <button className={collapsibleButtonClassName} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>;
|
||||
collapseButton = <button className={collapsibleButtonClassName} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={wrapperClassName}>
|
||||
<div role='button heading' tabIndex='0' className={buttonClassName} onClick={this.handleTitleClick}>
|
||||
<h1 tabIndex={focusable && '0'} role='button' className={buttonClassName} aria-label={title} onClick={this.handleTitleClick}>
|
||||
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
|
||||
{title}
|
||||
<div className='column-header__buttons'>
|
||||
|
@ -181,7 +190,7 @@ export default class ColumnHeader extends React.PureComponent {
|
|||
) : null}
|
||||
{collapseButton}
|
||||
</div>
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
{ notifCleaning ? (
|
||||
<div className={notifCleaningDrawerClassName} onTransitionEnd={this.handleTransitionEndNCD}>
|
||||
|
@ -191,7 +200,7 @@ export default class ColumnHeader extends React.PureComponent {
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={collapsibleClassName} onTransitionEnd={this.handleTransitionEnd}>
|
||||
<div className={collapsibleClassName} tabIndex={collapsed && -1} onTransitionEnd={this.handleTransitionEnd}>
|
||||
<div className='column-header__collapsible-inner'>
|
||||
{(!collapsed || animating) && collapsedContent}
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
@ -9,16 +10,23 @@ export default class DropdownMenu extends React.PureComponent {
|
|||
};
|
||||
|
||||
static propTypes = {
|
||||
isUserTouching: PropTypes.func,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
onModalOpen: PropTypes.func,
|
||||
onModalClose: PropTypes.func,
|
||||
icon: PropTypes.string.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
direction: PropTypes.string,
|
||||
status: ImmutablePropTypes.map,
|
||||
ariaLabel: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
ariaLabel: 'Menu',
|
||||
isModalOpen: false,
|
||||
isUserTouching: () => false,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -34,6 +42,10 @@ export default class DropdownMenu extends React.PureComponent {
|
|||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
const { action, to } = this.props.items[i];
|
||||
|
||||
if (this.props.isModalOpen) {
|
||||
this.props.onModalClose();
|
||||
}
|
||||
|
||||
// Don't call e.preventDefault() when the item uses 'href' property.
|
||||
// ex. "Edit profile" on the account action bar
|
||||
|
||||
|
@ -48,10 +60,32 @@ export default class DropdownMenu extends React.PureComponent {
|
|||
this.dropdown.hide();
|
||||
}
|
||||
|
||||
handleShow = () => this.setState({ expanded: true })
|
||||
handleShow = () => {
|
||||
if (this.props.isUserTouching()) {
|
||||
this.props.onModalOpen({
|
||||
status: this.props.status,
|
||||
actions: this.props.items,
|
||||
onClick: this.handleClick,
|
||||
});
|
||||
} else {
|
||||
this.setState({ expanded: true });
|
||||
}
|
||||
}
|
||||
|
||||
handleHide = () => this.setState({ expanded: false })
|
||||
|
||||
handleToggle = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
if (this.props.isUserTouching()) {
|
||||
this.handleShow();
|
||||
} else {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
this.setState({ expanded: false });
|
||||
}
|
||||
}
|
||||
|
||||
renderItem = (item, i) => {
|
||||
if (item === null) {
|
||||
return <li key={`sep-${i}`} className='dropdown__sep' />;
|
||||
|
@ -61,7 +95,7 @@ export default class DropdownMenu extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<li className='dropdown__content-list-item' key={`${text}-${i}`}>
|
||||
<a href={href} target='_blank' rel='noopener' onClick={this.handleClick} data-index={i} className='dropdown__content-list-link'>
|
||||
<a href={href} target='_blank' rel='noopener' role='button' tabIndex='0' autoFocus={i === 0} onClick={this.handleClick} data-index={i} className='dropdown__content-list-link'>
|
||||
{text}
|
||||
</a>
|
||||
</li>
|
||||
|
@ -71,6 +105,7 @@ export default class DropdownMenu extends React.PureComponent {
|
|||
render () {
|
||||
const { icon, items, size, direction, ariaLabel, disabled } = this.props;
|
||||
const { expanded } = this.state;
|
||||
const isUserTouching = this.props.isUserTouching();
|
||||
const directionClass = (direction === 'left') ? 'dropdown__left' : 'dropdown__right';
|
||||
const iconStyle = { fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` };
|
||||
const iconClassname = `fa fa-fw fa-${icon} dropdown__icon`;
|
||||
|
@ -84,20 +119,26 @@ export default class DropdownMenu extends React.PureComponent {
|
|||
}
|
||||
|
||||
const dropdownItems = expanded && (
|
||||
<ul className='dropdown__content-list'>
|
||||
<ul role='group' className='dropdown__content-list' onClick={this.handleHide}>
|
||||
{items.map(this.renderItem)}
|
||||
</ul>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown ref={this.setRef} onShow={this.handleShow} onHide={this.handleHide}>
|
||||
<DropdownTrigger className='icon-button' style={iconStyle} aria-label={ariaLabel}>
|
||||
<i className={iconClassname} aria-hidden />
|
||||
</DropdownTrigger>
|
||||
|
||||
// No need to render the actual dropdown if we use the modal. If we
|
||||
// don't render anything <Dropdow /> breaks, so we just put an empty div.
|
||||
const dropdownContent = !isUserTouching ? (
|
||||
<DropdownContent className={directionClass} >
|
||||
{dropdownItems}
|
||||
</DropdownContent>
|
||||
) : <div />;
|
||||
|
||||
return (
|
||||
<Dropdown ref={this.setRef} active={isUserTouching ? false : expanded} onShow={this.handleShow} onHide={this.handleHide}>
|
||||
<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>
|
||||
|
||||
{dropdownContent}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ export default class IconButton extends React.PureComponent {
|
|||
onClick: PropTypes.func,
|
||||
size: PropTypes.number,
|
||||
active: PropTypes.bool,
|
||||
pressed: PropTypes.bool,
|
||||
expanded: PropTypes.bool,
|
||||
style: PropTypes.object,
|
||||
activeStyle: PropTypes.object,
|
||||
disabled: PropTypes.bool,
|
||||
|
@ -19,6 +21,7 @@ export default class IconButton extends React.PureComponent {
|
|||
animate: PropTypes.bool,
|
||||
flip: PropTypes.bool,
|
||||
overlay: PropTypes.bool,
|
||||
tabIndex: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -27,6 +30,7 @@ export default class IconButton extends React.PureComponent {
|
|||
disabled: false,
|
||||
animate: false,
|
||||
overlay: false,
|
||||
tabIndex: '0',
|
||||
};
|
||||
|
||||
handleClick = (e) => {
|
||||
|
@ -74,10 +78,13 @@ export default class IconButton extends React.PureComponent {
|
|||
{({ rotate }) =>
|
||||
<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}
|
||||
style={style}
|
||||
tabIndex={this.props.tabIndex}
|
||||
>
|
||||
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
|
||||
</button>
|
||||
|
|
|
@ -215,10 +215,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;
|
||||
|
|
|
@ -19,12 +19,15 @@ export default class SettingText extends React.PureComponent {
|
|||
const { settings, settingKey, label } = this.props;
|
||||
|
||||
return (
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{label}</span>
|
||||
<input
|
||||
className='setting-text'
|
||||
value={settings.getIn(settingKey)}
|
||||
onChange={this.handleChange}
|
||||
placeholder={label}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ export default class Status extends ImmutablePureComponent {
|
|||
autoPlayGif: PropTypes.bool,
|
||||
muted: PropTypes.bool,
|
||||
intersectionObserverWrapper: PropTypes.object,
|
||||
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -62,6 +64,7 @@ export default class Status extends ImmutablePureComponent {
|
|||
'boostModal',
|
||||
'autoPlayGif',
|
||||
'muted',
|
||||
'listLength',
|
||||
]
|
||||
|
||||
updateOnStates = ['isExpanded']
|
||||
|
@ -70,8 +73,8 @@ export default class Status extends ImmutablePureComponent {
|
|||
if (!nextState.isIntersecting && nextState.isHidden) {
|
||||
// It's only if we're not intersecting (i.e. offscreen) and isHidden is true
|
||||
// that either "isIntersecting" or "isHidden" matter, and then they're
|
||||
// the only things that matter.
|
||||
return this.state.isIntersecting || !this.state.isHidden;
|
||||
// the only things that matter (and updated ARIA attributes).
|
||||
return this.state.isIntersecting || !this.state.isHidden || nextProps.listLength !== this.props.listLength;
|
||||
} else if (nextState.isIntersecting && !this.state.isIntersecting) {
|
||||
// If we're going from a non-intersecting state to an intersecting state,
|
||||
// (i.e. offscreen to onscreen), then we definitely need to re-render
|
||||
|
@ -110,17 +113,12 @@ export default class Status extends ImmutablePureComponent {
|
|||
this.height = getRectFromEntry(entry).height;
|
||||
}
|
||||
|
||||
// Edge 15 doesn't support isIntersecting, but we can infer it
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12156111/
|
||||
// https://github.com/WICG/IntersectionObserver/issues/211
|
||||
const isIntersecting = (typeof entry.isIntersecting === 'boolean') ?
|
||||
entry.isIntersecting : entry.intersectionRect.height > 0;
|
||||
this.setState((prevState) => {
|
||||
if (prevState.isIntersecting && !isIntersecting) {
|
||||
if (prevState.isIntersecting && !entry.isIntersecting) {
|
||||
scheduleIdleTask(this.hideIfNotIntersecting);
|
||||
}
|
||||
return {
|
||||
isIntersecting: isIntersecting,
|
||||
isIntersecting: entry.isIntersecting,
|
||||
isHidden: false,
|
||||
};
|
||||
});
|
||||
|
@ -177,7 +175,7 @@ export default class Status extends ImmutablePureComponent {
|
|||
|
||||
// Exclude intersectionObserverWrapper from `other` variable
|
||||
// because intersection is managed in here.
|
||||
const { status, account, intersectionObserverWrapper, ...other } = this.props;
|
||||
const { status, account, intersectionObserverWrapper, index, listLength, wrapped, ...other } = this.props;
|
||||
const { isExpanded, isIntersecting, isHidden } = this.state;
|
||||
|
||||
if (status === null) {
|
||||
|
@ -186,10 +184,10 @@ export default class Status extends ImmutablePureComponent {
|
|||
|
||||
if (!isIntersecting && isHidden) {
|
||||
return (
|
||||
<div ref={this.handleRef} data-id={status.get('id')} style={{ height: `${this.height}px`, opacity: 0, overflow: 'hidden' }}>
|
||||
<article ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0' style={{ height: `${this.height}px`, opacity: 0, overflow: 'hidden' }}>
|
||||
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
|
||||
{status.get('content')}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -203,14 +201,14 @@ export default class Status extends ImmutablePureComponent {
|
|||
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
|
||||
|
||||
return (
|
||||
<div className='status__wrapper' ref={this.handleRef} data-id={status.get('id')} >
|
||||
<article className='status__wrapper' ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0'>
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
|
||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={displayNameHTML} /></a> }} />
|
||||
</div>
|
||||
|
||||
<Status {...other} wrapped status={status.get('reblog')} account={status.get('account')} />
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -239,7 +237,7 @@ export default class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={`status ${this.props.muted ? 'muted' : ''} status-${status.get('visibility')}`} data-id={status.get('id')} 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>
|
||||
|
||||
|
@ -257,7 +255,7 @@ export default class Status extends ImmutablePureComponent {
|
|||
{media}
|
||||
|
||||
<StatusActionBar {...this.props} />
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import React from 'react';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import IconButton from './icon_button';
|
||||
import DropdownMenu from './dropdown_menu';
|
||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
|
@ -154,12 +154,12 @@ 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'>
|
||||
<DropdownMenu disabled={anonymousAccess} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
|
||||
<DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -149,7 +149,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} />
|
||||
{' '}
|
||||
|
@ -158,13 +158,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}
|
||||
|
@ -175,6 +177,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}
|
||||
|
|
|
@ -6,7 +6,7 @@ import StatusContainer from '../../glitch/components/status/container';
|
|||
import LoadMore from './load_more';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
|
||||
import { debounce } from 'lodash';
|
||||
import { throttle } from 'lodash';
|
||||
|
||||
export default class StatusList extends ImmutablePureComponent {
|
||||
|
||||
|
@ -30,13 +30,13 @@ export default class StatusList extends ImmutablePureComponent {
|
|||
|
||||
intersectionObserverWrapper = new IntersectionObserverWrapper();
|
||||
|
||||
handleScroll = debounce(() => {
|
||||
handleScroll = throttle(() => {
|
||||
if (this.node) {
|
||||
const { scrollTop, scrollHeight, clientHeight } = this.node;
|
||||
const offset = scrollHeight - scrollTop - clientHeight;
|
||||
this._oldScrollPosition = scrollHeight - scrollTop;
|
||||
|
||||
if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
|
||||
if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
|
||||
this.props.onScrollToBottom();
|
||||
} else if (scrollTop < 100 && this.props.onScrollToTop) {
|
||||
this.props.onScrollToTop();
|
||||
|
@ -44,7 +44,7 @@ export default class StatusList extends ImmutablePureComponent {
|
|||
this.props.onScroll();
|
||||
}
|
||||
}
|
||||
}, 200, {
|
||||
}, 150, {
|
||||
trailing: true,
|
||||
});
|
||||
|
||||
|
@ -104,6 +104,32 @@ export default class StatusList extends ImmutablePureComponent {
|
|||
this.props.onScrollToBottom();
|
||||
}
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
if (['PageDown', 'PageUp', 'End', 'Home'].includes(e.key)) {
|
||||
const article = (() => {
|
||||
switch (e.key) {
|
||||
case 'PageDown':
|
||||
return e.target.nodeName === 'ARTICLE' && e.target.nextElementSibling;
|
||||
case 'PageUp':
|
||||
return e.target.nodeName === 'ARTICLE' && e.target.previousElementSibling;
|
||||
case 'End':
|
||||
return this.node.querySelector('[role="feed"] > article:last-of-type');
|
||||
case 'Home':
|
||||
return this.node.querySelector('[role="feed"] > article:first-of-type');
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
if (article) {
|
||||
e.preventDefault();
|
||||
article.focus();
|
||||
article.scrollIntoView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { statusIds, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
|
||||
|
||||
|
@ -113,11 +139,11 @@ export default class StatusList extends ImmutablePureComponent {
|
|||
if (isLoading || statusIds.size > 0 || !emptyMessage) {
|
||||
scrollableArea = (
|
||||
<div className='scrollable' ref={this.setRef}>
|
||||
<div className='status-list'>
|
||||
<div role='feed' className='status-list' onKeyDown={this.handleKeyDown}>
|
||||
{prepend}
|
||||
|
||||
{statusIds.map((statusId) => {
|
||||
return <StatusContainer key={statusId} id={statusId} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
|
||||
{statusIds.map((statusId, index) => {
|
||||
return <StatusContainer key={statusId} id={statusId} index={index} listLength={statusIds.size} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
|
||||
})}
|
||||
|
||||
{loadMore}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { openModal, closeModal } from '../actions/modal';
|
||||
import { connect } from 'react-redux';
|
||||
import DropdownMenu from '../components/dropdown_menu';
|
||||
import { isUserTouching } from '../is_mobile';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isModalOpen: state.get('modal').modalType === 'ACTIONS',
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
isUserTouching,
|
||||
onModalOpen: props => dispatch(openModal('ACTIONS', props)),
|
||||
onModalClose: () => dispatch(closeModal()),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu);
|
|
@ -3,6 +3,8 @@ import Trie from 'substring-trie';
|
|||
|
||||
const trie = new Trie(Object.keys(unicodeMapping));
|
||||
|
||||
const excluded = ['™', '©', '®'];
|
||||
|
||||
function emojify(str) {
|
||||
// This walks through the string from start to end, ignoring any tags (<p>, <br>, etc.)
|
||||
// and replacing valid unicode strings
|
||||
|
@ -19,7 +21,7 @@ function emojify(str) {
|
|||
insideTag = true;
|
||||
} else if (!insideTag && (match = trie.search(str.substring(i)))) {
|
||||
const unicodeStr = match;
|
||||
if (unicodeStr in unicodeMapping) {
|
||||
if (unicodeStr in unicodeMapping && excluded.indexOf(unicodeStr) === -1) {
|
||||
const [filename, shortCode] = unicodeMapping[unicodeStr];
|
||||
const alt = unicodeStr;
|
||||
const replacement = `<img draggable="false" class="emojione" alt="${alt}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import DropdownMenu from '../../../components/dropdown_menu';
|
||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||
import Link from 'react-router-dom/Link';
|
||||
import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
|
||||
|
||||
|
@ -15,6 +15,7 @@ const messages = defineMessages({
|
|||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
||||
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
||||
media: { id: 'account.media', defaultMessage: 'Media' },
|
||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
||||
|
@ -36,6 +37,12 @@ export default class ActionBar extends React.PureComponent {
|
|||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleShare = () => {
|
||||
navigator.share({
|
||||
url: this.props.account.get('url'),
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, me, intl } = this.props;
|
||||
|
||||
|
@ -43,6 +50,9 @@ export default class ActionBar extends React.PureComponent {
|
|||
let extraInfo = '';
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
||||
if ('share' in navigator) {
|
||||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
||||
}
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` });
|
||||
menu.push(null);
|
||||
|
@ -96,7 +106,7 @@ export default class ActionBar extends React.PureComponent {
|
|||
|
||||
<div className='account__action-bar'>
|
||||
<div className='account__action-bar-dropdown'>
|
||||
<DropdownMenu items={menu} icon='bars' size={24} direction='right' />
|
||||
<DropdownMenuContainer items={menu} icon='bars' size={24} direction='right' />
|
||||
</div>
|
||||
|
||||
<div className='account__action-bar-links'>
|
||||
|
|
|
@ -55,9 +55,10 @@ class Avatar extends ImmutablePureComponent {
|
|||
return (
|
||||
<Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}>
|
||||
{({ radius }) =>
|
||||
<a // eslint-disable-line jsx-a11y/anchor-has-content
|
||||
<a
|
||||
href={account.get('url')}
|
||||
className='account__header__avatar'
|
||||
role='presentation'
|
||||
target='_blank'
|
||||
rel='noopener'
|
||||
style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }}
|
||||
|
@ -65,7 +66,9 @@ class Avatar extends ImmutablePureComponent {
|
|||
onMouseOut={this.handleMouseOut}
|
||||
onFocus={this.handleMouseOver}
|
||||
onBlur={this.handleMouseOut}
|
||||
/>
|
||||
>
|
||||
<span style={{ display: 'none' }}>{account.get('acct')}</span>
|
||||
</a>
|
||||
}
|
||||
</Motion>
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,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?' },
|
||||
|
@ -150,7 +151,7 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||
const { intl, onPaste, showSearch } = this.props;
|
||||
const disabled = this.props.is_submitting;
|
||||
const maybeEye = this.props.advanced_options.get('do_not_federate') ? ' 👁️' : '';
|
||||
const text = [this.props.spoiler_text, this.props.text, maybeEye].join('');
|
||||
const text = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join('');
|
||||
|
||||
let publishText = '';
|
||||
|
||||
|
@ -164,7 +165,10 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||
<div className='compose-form'>
|
||||
<Collapsable isVisible={this.props.spoiler} fullHeight={50}>
|
||||
<div className='spoiler-input'>
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
|
||||
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input' id='cw-spoiler-input' />
|
||||
</label>
|
||||
</div>
|
||||
</Collapsable>
|
||||
|
||||
|
|
|
@ -65,6 +65,22 @@ export default class EmojiPickerDropdown extends React.PureComponent {
|
|||
this.setState({ active: false });
|
||||
}
|
||||
|
||||
onToggle = (e) => {
|
||||
if (!this.state.loading && (!e.key || e.key === 'Enter')) {
|
||||
if (this.state.active) {
|
||||
this.onHideDropdown();
|
||||
} else {
|
||||
this.onShowDropdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onEmojiPickerKeyDown = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
this.onHideDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl } = this.props;
|
||||
|
||||
|
@ -104,10 +120,11 @@ export default class EmojiPickerDropdown extends React.PureComponent {
|
|||
};
|
||||
|
||||
const { active, loading } = this.state;
|
||||
const title = intl.formatMessage(messages.emoji);
|
||||
|
||||
return (
|
||||
<Dropdown ref={this.setRef} className='emoji-picker__dropdown' onShow={this.onShowDropdown} onHide={this.onHideDropdown}>
|
||||
<DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)}>
|
||||
<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-expanded={active} role='button' onKeyDown={this.onToggle} tabIndex={0} >
|
||||
<img
|
||||
className={`emojione ${active && loading ? 'pulse-loading' : ''}`}
|
||||
alt='🙂'
|
||||
|
@ -118,7 +135,7 @@ export default class EmojiPickerDropdown extends React.PureComponent {
|
|||
<DropdownContent className='dropdown__left'>
|
||||
{
|
||||
this.state.active && !this.state.loading &&
|
||||
(<EmojiPicker emojione={settings} onChange={this.handleChange} searchPlaceholder={intl.formatMessage(messages.emoji_search)} categories={categories} search />)
|
||||
(<EmojiPicker emojione={settings} onChange={this.handleChange} searchPlaceholder={intl.formatMessage(messages.emoji_search)} onKeyDown={this.onEmojiPickerKeyDown} categories={categories} search />)
|
||||
}
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
|
|
|
@ -18,6 +18,7 @@ export default class NavigationBar extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='navigation-bar'>
|
||||
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
|
||||
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
|
||||
<Avatar src={this.props.account.get('avatar')} staticSrc={this.props.account.get('avatar_static')} size={40} />
|
||||
</Permalink>
|
||||
|
||||
|
|
|
@ -24,6 +24,10 @@ const iconStyle = {
|
|||
export default class PrivacyDropdown extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
isUserTouching: PropTypes.func,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
onModalOpen: PropTypes.func,
|
||||
onModalClose: PropTypes.func,
|
||||
value: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
@ -34,15 +38,37 @@ export default class PrivacyDropdown extends React.PureComponent {
|
|||
};
|
||||
|
||||
handleToggle = () => {
|
||||
if (this.props.isUserTouching()) {
|
||||
if (this.state.open) {
|
||||
this.props.onModalClose();
|
||||
} else {
|
||||
this.props.onModalOpen({
|
||||
actions: this.options.map(option => ({ ...option, active: option.value === this.props.value })),
|
||||
onClick: this.handleModalActionClick,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({ open: !this.state.open });
|
||||
}
|
||||
}
|
||||
|
||||
handleModalActionClick = (e) => {
|
||||
e.preventDefault();
|
||||
const { value } = this.options[e.currentTarget.getAttribute('data-index')];
|
||||
this.props.onModalClose();
|
||||
this.props.onChange(value);
|
||||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
this.setState({ open: false });
|
||||
} else if (!e.key || e.key === 'Enter') {
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
e.preventDefault();
|
||||
this.setState({ open: false });
|
||||
this.props.onChange(value);
|
||||
}
|
||||
}
|
||||
|
||||
onGlobalClick = (e) => {
|
||||
if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
|
||||
|
@ -50,6 +76,17 @@ export default class PrivacyDropdown extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
const { intl: { formatMessage } } = this.props;
|
||||
|
||||
this.options = [
|
||||
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
||||
{ icon: 'unlock-alt', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
||||
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||
];
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('click', this.onGlobalClick);
|
||||
window.addEventListener('touchstart', this.onGlobalClick);
|
||||
|
@ -68,25 +105,18 @@ export default class PrivacyDropdown extends React.PureComponent {
|
|||
const { value, intl } = this.props;
|
||||
const { open } = this.state;
|
||||
|
||||
const options = [
|
||||
{ icon: 'globe', value: 'public', shortText: intl.formatMessage(messages.public_short), longText: intl.formatMessage(messages.public_long) },
|
||||
{ icon: 'unlock-alt', value: 'unlisted', shortText: intl.formatMessage(messages.unlisted_short), longText: intl.formatMessage(messages.unlisted_long) },
|
||||
{ icon: 'lock', value: 'private', shortText: intl.formatMessage(messages.private_short), longText: intl.formatMessage(messages.private_long) },
|
||||
{ icon: 'envelope', value: 'direct', shortText: intl.formatMessage(messages.direct_short), longText: intl.formatMessage(messages.direct_long) },
|
||||
];
|
||||
|
||||
const valueOption = options.find(item => item.value === value);
|
||||
const valueOption = this.options.find(item => item.value === value);
|
||||
|
||||
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} 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 && options.map(item =>
|
||||
<div role='button' tabIndex='0' key={item.value} data-index={item.value} onClick={this.handleClick} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}>
|
||||
{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' : ''}`}>
|
||||
<div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div>
|
||||
<div className='privacy-dropdown__option__content'>
|
||||
<strong>{item.shortText}</strong>
|
||||
{item.longText}
|
||||
<strong>{item.text}</strong>
|
||||
{item.meta}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -52,6 +52,8 @@ export default class Search extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<div className='search'>
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span>
|
||||
<input
|
||||
className='search__input'
|
||||
type='text'
|
||||
|
@ -61,6 +63,7 @@ export default class Search extends React.PureComponent {
|
|||
onKeyUp={this.handleKeyDown}
|
||||
onFocus={this.handleFocus}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
|
||||
<i className={`fa fa-search ${hasValue ? '' : 'active'}`} />
|
||||
|
|
|
@ -57,6 +57,8 @@ export default class UploadButton extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='compose-form__upload-button'>
|
||||
<IconButton icon='camera' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} />
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.upload)}</span>
|
||||
<input
|
||||
key={resetFileKey}
|
||||
ref={this.setRef}
|
||||
|
@ -67,6 +69,7 @@ export default class UploadButton extends ImmutablePureComponent {
|
|||
disabled={disabled}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import PrivacyDropdown from '../components/privacy_dropdown';
|
||||
import { changeComposeVisibility } from '../../../actions/compose';
|
||||
import { openModal, closeModal } from '../../../actions/modal';
|
||||
import { isUserTouching } from '../../../is_mobile';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isModalOpen: state.get('modal').modalType === 'ACTIONS',
|
||||
value: state.getIn(['compose', 'privacy']),
|
||||
});
|
||||
|
||||
|
@ -12,6 +15,10 @@ const mapDispatchToProps = dispatch => ({
|
|||
dispatch(changeComposeVisibility(value));
|
||||
},
|
||||
|
||||
isUserTouching,
|
||||
onModalOpen: props => dispatch(openModal('ACTIONS', props)),
|
||||
onModalClose: () => dispatch(closeModal()),
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
|
|
|
@ -76,23 +76,23 @@ export default class Compose extends React.PureComponent {
|
|||
if (multiColumn) {
|
||||
const { columns } = this.props;
|
||||
header = (
|
||||
<div className='drawer__header'>
|
||||
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)}><i role='img' aria-label={intl.formatMessage(messages.start)} className='fa fa-fw fa-asterisk' /></Link>
|
||||
<nav className='drawer__header'>
|
||||
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><i role='img' className='fa fa-fw fa-asterisk' /></Link>
|
||||
{!columns.some(column => column.get('id') === 'HOME') && (
|
||||
<Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)}><i role='img' className='fa fa-fw fa-home' aria-label={intl.formatMessage(messages.home_timeline)} /></Link>
|
||||
<Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><i role='img' className='fa fa-fw fa-home' /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
|
||||
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)}><i role='img' className='fa fa-fw fa-bell' aria-label={intl.formatMessage(messages.notifications)} /></Link>
|
||||
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><i role='img' className='fa fa-fw fa-bell' /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
|
||||
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role='img' aria-label={intl.formatMessage(messages.community)} className='fa fa-fw fa-users' /></Link>
|
||||
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><i role='img' className='fa fa-fw fa-users' /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'PUBLIC') && (
|
||||
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role='img' aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link>
|
||||
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><i role='img' className='fa fa-fw fa-globe' /></Link>
|
||||
)}
|
||||
<a onClick={this.openSettings} role='button' tabIndex='0' className='drawer__tab' title={intl.formatMessage(messages.settings)}><i role='img' aria-label={intl.formatMessage(messages.settings)} className='fa fa-fw fa-cogs' /></a>
|
||||
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)}><i role='img' aria-label={intl.formatMessage(messages.logout)} className='fa fa-fw fa-sign-out' /></a>
|
||||
</div>
|
||||
<a onClick={this.openSettings} role='button' tabIndex='0' className='drawer__tab' title={intl.formatMessage(messages.settings)} aria-label={intl.formatMessage(messages.settings)}><i role='img' className='fa fa-fw fa-cogs' /></a>
|
||||
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><i role='img' className='fa fa-fw fa-sign-out' /></a>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
};
|
|
@ -36,7 +36,8 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
<ClearColumnButton onClick={onClear} />
|
||||
</div>
|
||||
|
||||
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
|
||||
<div role='group' aria-labelledby='notifications-follow'>
|
||||
<span id='notifications-follow' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
|
||||
|
@ -44,8 +45,10 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
|
||||
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
|
||||
<div role='group' aria-labelledby='notifications-favourite'>
|
||||
<span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
|
||||
|
@ -53,8 +56,10 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
|
||||
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
|
||||
<div role='group' aria-labelledby='notifications-mention'>
|
||||
<span id='notifications-mention' className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
|
||||
|
@ -62,8 +67,10 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
|
||||
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
|
||||
<div role='group' aria-labelledby='notifications-reblog'>
|
||||
<span id='notifications-reblog' className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
|
||||
|
@ -72,6 +79,7 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,13 +18,19 @@ export default class SettingToggle extends React.PureComponent {
|
|||
this.props.onChange(this.props.settingKey, target.checked);
|
||||
}
|
||||
|
||||
onKeyDown = e => {
|
||||
if (e.key === ' ') {
|
||||
this.props.onChange(this.props.settingKey, !e.target.checked);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { prefix, settings, settingKey, label, meta } = this.props;
|
||||
const id = ['setting-toggle', prefix, ...settingKey].filter(Boolean).join('-');
|
||||
|
||||
return (
|
||||
<div className='setting-toggle'>
|
||||
<Toggle id={id} checked={settings.getIn(settingKey)} onChange={this.onChange} />
|
||||
<Toggle id={id} checked={settings.getIn(settingKey)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
|
||||
<label htmlFor={id} className='setting-toggle__label'>{label}</label>
|
||||
{meta && <span className='setting-meta__label'>{meta}</span>}
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import DropdownMenu from '../../../components/dropdown_menu';
|
||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -13,6 +13,7 @@ const messages = defineMessages({
|
|||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
||||
share: { id: 'status.share', defaultMessage: 'Share' },
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
|
@ -58,6 +59,13 @@ export default class ActionBar extends React.PureComponent {
|
|||
this.props.onReport(this.props.status);
|
||||
}
|
||||
|
||||
handleShare = () => {
|
||||
navigator.share({
|
||||
text: this.props.status.get('search_index'),
|
||||
url: this.props.status.get('url'),
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const { status, me, intl } = this.props;
|
||||
|
||||
|
@ -71,6 +79,10 @@ export default class ActionBar extends React.PureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
|
||||
}
|
||||
|
||||
const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
|
||||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
|
||||
);
|
||||
|
||||
let reblogIcon = 'retweet';
|
||||
//if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
|
||||
// else if (status.get('visibility') === 'private') reblogIcon = 'lock';
|
||||
|
@ -82,9 +94,10 @@ export default class ActionBar extends React.PureComponent {
|
|||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_id', null) === null ? 'reply' : 'reply-all'} onClick={this.handleReplyClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
|
||||
{shareButton}
|
||||
|
||||
<div className='detailed-status__action-bar-dropdown'>
|
||||
<DropdownMenu size={18} icon='ellipsis-h' items={menu} direction='left' ariaLabel='More' />
|
||||
<DropdownMenuContainer size={18} icon='ellipsis-h' items={menu} direction='left' ariaLabel='More' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import RelativeTimestamp from '../../../components/relative_timestamp';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
|
||||
export default class ActionsModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
actions: PropTypes.array,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
renderAction = (action, i) => {
|
||||
if (action === null) {
|
||||
return <li key={`sep-${i}`} className='dropdown__sep' />;
|
||||
}
|
||||
|
||||
const { icon = null, text, meta = null, active = false, href = '#' } = action;
|
||||
|
||||
return (
|
||||
<li key={`${text}-${i}`}>
|
||||
<a href={href} target='_blank' rel='noopener' onClick={this.props.onClick} data-index={i} className={active && 'active'}>
|
||||
{icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' />}
|
||||
<div>
|
||||
<div>{text}</div>
|
||||
<div>{meta}</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const status = this.props.status && (
|
||||
<div className='status light'>
|
||||
<div className='boost-modal__status-header'>
|
||||
<div className='boost-modal__status-time'>
|
||||
<a href={this.props.status.get('url')} className='status__relative-time' target='_blank' rel='noopener'>
|
||||
<RelativeTimestamp timestamp={this.props.status.get('created_at')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a href={this.props.status.getIn(['account', 'url'])} className='status__display-name'>
|
||||
<div className='status__avatar'>
|
||||
<Avatar src={this.props.status.getIn(['account', 'avatar'])} staticSrc={this.props.status.getIn(['account', 'avatar_static'])} size={48} />
|
||||
</div>
|
||||
|
||||
<DisplayName account={this.props.status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<StatusContent status={this.props.status} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal actions-modal'>
|
||||
{status}
|
||||
|
||||
<ul>
|
||||
{this.props.actions.map(this.renderAction)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,7 @@ import ColumnHeader from './column_header';
|
|||
import PropTypes from 'prop-types';
|
||||
import { debounce } from 'lodash';
|
||||
import scrollTop from '../../../scroll';
|
||||
import { isMobile } from '../../../is_mobile';
|
||||
|
||||
export default class Column extends React.PureComponent {
|
||||
|
||||
|
@ -37,13 +38,12 @@ export default class Column extends React.PureComponent {
|
|||
render () {
|
||||
const { heading, icon, children, active, hideHeadingOnMobile } = this.props;
|
||||
|
||||
let columnHeaderId = null;
|
||||
let header = '';
|
||||
const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth)));
|
||||
|
||||
if (heading) {
|
||||
columnHeaderId = heading.replace(/ /g, '-');
|
||||
header = <ColumnHeader icon={icon} active={active} type={heading} onClick={this.handleHeaderClick} hideOnMobile={hideHeadingOnMobile} columnHeaderId={columnHeaderId} />;
|
||||
}
|
||||
const columnHeaderId = showHeading && heading.replace(/ /g, '-');
|
||||
const header = showHeading && (
|
||||
<ColumnHeader icon={icon} active={active} type={heading} onClick={this.handleHeaderClick} columnHeaderId={columnHeaderId} />
|
||||
);
|
||||
return (
|
||||
<div
|
||||
ref={this.setRef}
|
||||
|
|
|
@ -8,7 +8,6 @@ export default class ColumnHeader extends React.PureComponent {
|
|||
type: PropTypes.string,
|
||||
active: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
hideOnMobile: PropTypes.bool,
|
||||
columnHeaderId: PropTypes.string,
|
||||
};
|
||||
|
||||
|
@ -17,7 +16,7 @@ export default class ColumnHeader extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { type, active, hideOnMobile, columnHeaderId } = this.props;
|
||||
const { type, active, columnHeaderId } = this.props;
|
||||
|
||||
let icon = '';
|
||||
|
||||
|
@ -26,7 +25,7 @@ export default class ColumnHeader extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div role='button heading' tabIndex='0' className={`column-header ${active ? 'active' : ''} ${hideOnMobile ? 'hidden-on-mobile' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}>
|
||||
<div role='heading' tabIndex='0' className={`column-header ${active ? 'active' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}>
|
||||
{icon}
|
||||
{type}
|
||||
</div>
|
||||
|
|
|
@ -2,24 +2,24 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import Link from 'react-router-dom/Link';
|
||||
|
||||
const ColumnLink = ({ icon, text, to, onClick, href, method, hideOnMobile }) => {
|
||||
const ColumnLink = ({ icon, text, to, onClick, href, method }) => {
|
||||
if (href) {
|
||||
return (
|
||||
<a href={href} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
|
||||
<a href={href} className='column-link' data-method={method}>
|
||||
<i className={`fa fa-fw fa-${icon} column-link__icon`} />
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
} else if (to) {
|
||||
return (
|
||||
<Link to={to} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`}>
|
||||
<Link to={to} className='column-link'>
|
||||
<i className={`fa fa-fw fa-${icon} column-link__icon`} />
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<a onClick={onClick} role='button' tabIndex='0' className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
|
||||
<a onClick={onClick} role='button' tabIndex='0' data-method={method}>
|
||||
<i className={`fa fa-fw fa-${icon} column-link__icon`} />
|
||||
{text}
|
||||
</a>
|
||||
|
@ -34,7 +34,6 @@ ColumnLink.propTypes = {
|
|||
onClick: PropTypes.func,
|
||||
href: PropTypes.string,
|
||||
method: PropTypes.string,
|
||||
hideOnMobile: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default ColumnLink;
|
||||
|
|
|
@ -6,7 +6,7 @@ import ColumnHeader from '../../../components/column_header';
|
|||
|
||||
const ColumnLoading = ({ title = '', icon = ' ' }) => (
|
||||
<Column>
|
||||
<ColumnHeader icon={icon} title={title} multiColumn={false} />
|
||||
<ColumnHeader icon={icon} title={title} multiColumn={false} focusable={false} />
|
||||
<div className='scrollable' />
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -56,6 +56,15 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
|
||||
handleSwipe = (index) => {
|
||||
this.pendingIndex = index;
|
||||
|
||||
const nextLinkTranslationId = links[index].props['data-preview-title-id'];
|
||||
const currentLinkSelector = '.tabs-bar__link.active';
|
||||
const nextLinkSelector = `.tabs-bar__link[data-preview-title-id="${nextLinkTranslationId}"]`;
|
||||
|
||||
// HACK: Remove the active class from the current link and set it to the next one
|
||||
// React-router does this for us, but too late, feeling laggy.
|
||||
document.querySelector(currentLinkSelector).classList.remove('active');
|
||||
document.querySelector(nextLinkSelector).classList.add('active');
|
||||
}
|
||||
|
||||
handleAnimationEnd = () => {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import spring from 'react-motion/lib/spring';
|
|||
import BundleContainer from '../containers/bundle_container';
|
||||
import BundleModalError from './bundle_modal_error';
|
||||
import ModalLoading from './modal_loading';
|
||||
import ActionsModal from '../components/actions_modal';
|
||||
import {
|
||||
MediaModal,
|
||||
OnboardingModal,
|
||||
|
@ -23,6 +24,7 @@ const MODAL_COMPONENTS = {
|
|||
'CONFIRM': ConfirmationModal,
|
||||
'REPORT': ReportModal,
|
||||
'SETTINGS': SettingsModal,
|
||||
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
||||
};
|
||||
|
||||
export default class ModalRoot extends React.PureComponent {
|
||||
|
@ -44,10 +46,34 @@ export default class ModalRoot extends React.PureComponent {
|
|||
window.addEventListener('keyup', this.handleKeyUp, false);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (!!nextProps.type && !this.props.type) {
|
||||
this.activeElement = document.activeElement;
|
||||
|
||||
this.getSiblings().forEach(sibling => sibling.setAttribute('inert', true));
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (!this.props.type && !!prevProps.type) {
|
||||
this.getSiblings().forEach(sibling => sibling.removeAttribute('inert'));
|
||||
this.activeElement.focus();
|
||||
this.activeElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('keyup', this.handleKeyUp);
|
||||
}
|
||||
|
||||
getSiblings = () => {
|
||||
return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node);
|
||||
}
|
||||
|
||||
setRef = ref => {
|
||||
this.node = ref;
|
||||
}
|
||||
|
||||
willEnter () {
|
||||
return { opacity: 0, scale: 0.98 };
|
||||
}
|
||||
|
@ -86,11 +112,11 @@ export default class ModalRoot extends React.PureComponent {
|
|||
willLeave={this.willLeave}
|
||||
>
|
||||
{interpolatedStyles =>
|
||||
<div className='modal-root'>
|
||||
<div className='modal-root' ref={this.setRef}>
|
||||
{interpolatedStyles.map(({ key, data: { type, props }, style }) => (
|
||||
<div key={key} style={{ pointerEvents: visible ? 'auto' : 'none' }}>
|
||||
<div role='presentation' className='modal-root__overlay' style={{ opacity: style.opacity }} onClick={onClose} />
|
||||
<div className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}>
|
||||
<div role='dialog' className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}>
|
||||
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading} error={this.renderError} renderDelay={200}>
|
||||
{(SpecificComponent) => <SpecificComponent {...props} onClose={onClose} />}
|
||||
</BundleContainer>
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import NavLink from 'react-router-dom/NavLink';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { debounce } from 'lodash';
|
||||
import { isUserTouching } from '../../../is_mobile';
|
||||
|
||||
export const links = [
|
||||
<NavLink className='tabs-bar__link primary' activeClassName='active' 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>,
|
||||
<NavLink className='tabs-bar__link primary' activeClassName='active' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link primary' activeClassName='active' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link primary' to='/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>,
|
||||
<NavLink className='tabs-bar__link primary' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
|
||||
|
||||
<NavLink className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link secondary' activeClassName='active' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link secondary' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link secondary' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
|
||||
|
||||
<NavLink className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='tabs_bar.federated_timeline' data-preview-icon='asterisk' ><i className='fa fa-fw fa-asterisk' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='asterisk' ><i className='fa fa-fw fa-asterisk' /></NavLink>,
|
||||
];
|
||||
|
||||
export function getIndex (path) {
|
||||
|
@ -21,13 +24,60 @@ export function getLink (index) {
|
|||
return links[index].props.to;
|
||||
}
|
||||
|
||||
@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 (
|
||||
<div className='tabs-bar'>
|
||||
{React.Children.toArray(links)}
|
||||
</div>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,10 @@ const mapStateToProps = state => ({
|
|||
@connect(mapStateToProps)
|
||||
export default class UI extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
|
@ -129,6 +133,14 @@ export default class UI extends React.PureComponent {
|
|||
this.setState({ draggingOver: false });
|
||||
}
|
||||
|
||||
handleServiceWorkerPostMessage = ({ data }) => {
|
||||
if (data.type === 'navigate') {
|
||||
this.context.router.history.push(data.path);
|
||||
} else {
|
||||
console.warn('Unknown message type:', data.type); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||
document.addEventListener('dragenter', this.handleDragEnter, false);
|
||||
|
@ -137,6 +149,10 @@ export default class UI extends React.PureComponent {
|
|||
document.addEventListener('dragleave', this.handleDragLeave, false);
|
||||
document.addEventListener('dragend', this.handleDragEnd, false);
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
|
||||
}
|
||||
|
||||
this.props.dispatch(refreshHomeTimeline());
|
||||
this.props.dispatch(refreshNotifications());
|
||||
}
|
||||
|
|
|
@ -110,9 +110,9 @@ export function SettingsModal () {
|
|||
// IF MASTODON EVER CHANGES DETAILED STATUSES TO REQUIRE THEM, WE'LL NEED TO UPDATE THE URLS OR SOMETHING LOL. //
|
||||
|
||||
export function MediaGallery () {
|
||||
return import(/* webpackChunkName: "status/MediaGallery" */'../../../components/media_gallery');
|
||||
return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery');
|
||||
}
|
||||
|
||||
export function VideoPlayer () {
|
||||
return import(/* webpackChunkName: "status/VideoPlayer" */'../../../components/video_player');
|
||||
return import(/* webpackChunkName: "status/video_player" */'../../../components/video_player');
|
||||
}
|
||||
|
|
|
@ -12,6 +12,15 @@ export function isMobile(width, columns) {
|
|||
};
|
||||
|
||||
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
let userTouching = false;
|
||||
|
||||
window.addEventListener('touchstart', () => {
|
||||
userTouching = true;
|
||||
}, { once: true });
|
||||
|
||||
export function isUserTouching() {
|
||||
return userTouching;
|
||||
}
|
||||
|
||||
export function isIOS() {
|
||||
return iOS;
|
||||
|
|
|
@ -24,6 +24,8 @@ function loadPolyfills() {
|
|||
// This avoids shipping them all the polyfills.
|
||||
const needsExtraPolyfills = !(
|
||||
window.IntersectionObserver &&
|
||||
window.IntersectionObserverEntry &&
|
||||
'isIntersecting' in IntersectionObserverEntry.prototype &&
|
||||
window.requestIdleCallback &&
|
||||
'object-fit' in (new Image()).style
|
||||
);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "المشاركات",
|
||||
"account.report": "أبلغ عن @{name}",
|
||||
"account.requested": "في انتظار الموافقة",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "إلغاء الحظر عن @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "إلغاء المتابعة",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "الإشعارات",
|
||||
"column.public": "الخيط العام الموحد",
|
||||
"column_back_button.label": "العودة",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "التصفح",
|
||||
"column_subheading.settings": "الإعدادات",
|
||||
|
@ -89,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": "تعذر العثور عليه",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "إبلِغ عن @{name}",
|
||||
"status.sensitive_toggle": "اضغط للعرض",
|
||||
"status.sensitive_warning": "محتوى حساس",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "إعرض أقلّ",
|
||||
"status.show_more": "أظهر المزيد",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Публикации",
|
||||
"account.report": "Report @{name}",
|
||||
"account.requested": "В очакване на одобрение",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Не блокирай",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Не следвай",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Известия",
|
||||
"column.public": "Публичен канал",
|
||||
"column_back_button.label": "Назад",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_toggle": "Покажи",
|
||||
"status.sensitive_warning": "Деликатно съдържание",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_more": "Show more",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Publicacions",
|
||||
"account.report": "Informe @{name}",
|
||||
"account.requested": "Esperant aprovació",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Desbloquejar @{name}",
|
||||
"account.unblock_domain": "Mostra {domain}",
|
||||
"account.unfollow": "Deixar de seguir",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Notificacions",
|
||||
"column.public": "Línia de temps federada",
|
||||
"column_back_button.label": "Enrere",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navegació",
|
||||
"column_subheading.settings": "Configuració",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Informar sobre @{name}",
|
||||
"status.sensitive_toggle": "Clic per veure",
|
||||
"status.sensitive_warning": "Contingut sensible",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Mostra menys",
|
||||
"status.show_more": "Mostra més",
|
||||
"status.unmute_conversation": "Activar conversació",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Beiträge",
|
||||
"account.report": "@{name} melden",
|
||||
"account.requested": "Warte auf Erlaubnis",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "@{name} entblocken",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Entfolgen",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Mitteilungen",
|
||||
"column.public": "Gesamtes bekanntes Netz",
|
||||
"column_back_button.label": "Zurück",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "@{name} melden",
|
||||
"status.sensitive_toggle": "Klicke, um sie zu sehen",
|
||||
"status.sensitive_warning": "Heikle Inhalte",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Weniger anzeigen",
|
||||
"status.show_more": "Mehr anzeigen",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -53,6 +53,22 @@
|
|||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
"defaultMessage": "Show settings",
|
||||
"id": "column_header.show_settings"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Hide settings",
|
||||
"id": "column_header.hide_settings"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Move column to the left",
|
||||
"id": "column_header.moveLeft_settings"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Move column to the right",
|
||||
"id": "column_header.moveRight_settings"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Unpin",
|
||||
"id": "column_header.unpin"
|
||||
|
@ -138,6 +154,10 @@
|
|||
"defaultMessage": "Reply",
|
||||
"id": "status.reply"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Share",
|
||||
"id": "status.share"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Reply to thread",
|
||||
"id": "status.replyAll"
|
||||
|
@ -354,6 +374,10 @@
|
|||
"defaultMessage": "Report @{name}",
|
||||
"id": "account.report"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Share @{name}'s profile",
|
||||
"id": "account.share"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Media",
|
||||
"id": "account.media"
|
||||
|
@ -1007,6 +1031,10 @@
|
|||
{
|
||||
"defaultMessage": "Report @{name}",
|
||||
"id": "status.report"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Share",
|
||||
"id": "status.share"
|
||||
}
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/status/components/action_bar.json"
|
||||
|
@ -1085,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"
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Posts",
|
||||
"account.report": "Report @{name}",
|
||||
"account.requested": "Awaiting approval",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Unblock @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Unfollow",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Notifications",
|
||||
"column.public": "Federated timeline",
|
||||
"column_back_button.label": "Back",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_toggle": "Click to view",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_more": "Show more",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Mesaĝoj",
|
||||
"account.report": "Report @{name}",
|
||||
"account.requested": "Atendas aprobon",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Malbloki @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Malsekvi",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Sciigoj",
|
||||
"column.public": "Fratara tempolinio",
|
||||
"column_back_button.label": "Reveni",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_toggle": "Alklaki por vidi",
|
||||
"status.sensitive_warning": "Tikla enhavo",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_more": "Show more",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Publicaciones",
|
||||
"account.report": "Report @{name}",
|
||||
"account.requested": "Esperando aprobación",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Desbloquear",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Dejar de seguir",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Notificaciones",
|
||||
"column.public": "Historia federada",
|
||||
"column_back_button.label": "Atrás",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Reportar",
|
||||
"status.sensitive_toggle": "Click para ver",
|
||||
"status.sensitive_warning": "Contenido sensible",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Mostrar menos",
|
||||
"status.show_more": "Mostrar más",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,18 +13,19 @@
|
|||
"account.posts": "نوشتهها",
|
||||
"account.report": "گزارش @{name}",
|
||||
"account.requested": "در انتظار پذیرش",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "رفع انسداد @{name}",
|
||||
"account.unblock_domain": "رفع پنهانسازی از {domain}",
|
||||
"account.unfollow": "پایان پیگیری",
|
||||
"account.unmute": "باصدا کردن @{name}",
|
||||
"account.view_full_profile": "View full profile",
|
||||
"account.view_full_profile": "نمایش نمایهٔ کامل",
|
||||
"boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
|
||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
||||
"bundle_column_error.retry": "Try again",
|
||||
"bundle_column_error.title": "Network error",
|
||||
"bundle_modal_error.close": "Close",
|
||||
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
||||
"bundle_modal_error.retry": "Try again",
|
||||
"bundle_column_error.body": "هنگام بازکردن این بخش خطایی رخ داد.",
|
||||
"bundle_column_error.retry": "تلاش دوباره",
|
||||
"bundle_column_error.title": "خطای شبکه",
|
||||
"bundle_modal_error.close": "بستن",
|
||||
"bundle_modal_error.message": "هنگام بازکردن این بخش خطایی رخ داد.",
|
||||
"bundle_modal_error.retry": "تلاش دوباره",
|
||||
"column.blocks": "کاربران مسدودشده",
|
||||
"column.community": "نوشتههای محلی",
|
||||
"column.favourites": "پسندیدهها",
|
||||
|
@ -34,8 +35,12 @@
|
|||
"column.notifications": "اعلانها",
|
||||
"column.public": "نوشتههای همهجا",
|
||||
"column_back_button.label": "بازگشت",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_header.hide_settings": "نهفتن تنظیمات",
|
||||
"column_header.moveLeft_settings": "انتقال ستون به چپ",
|
||||
"column_header.moveRight_settings": "انتقال ستون به راست",
|
||||
"column_header.pin": "ثابتکردن",
|
||||
"column_header.show_settings": "نمایش تنظیمات",
|
||||
"column_header.unpin": "رهاکردن",
|
||||
"column_subheading.navigation": "گشت و گذار",
|
||||
"column_subheading.settings": "تنظیمات",
|
||||
"compose_form.lock_disclaimer": "حساب شما {locked} نیست. هر کسی میتواند پیگیر شما شود و نوشتههای ویژهٔ پیگیران شما را ببیند.",
|
||||
|
@ -56,8 +61,8 @@
|
|||
"confirmations.domain_block.message": "آیا جدی جدی میخواهید کل دامین {domain} را مسدود کنید؟ بیشتر وقتها مسدودکردن یا بیصداکردن چند حساب کاربری خاص کافی است و توصیه میشود.",
|
||||
"confirmations.mute.confirm": "بیصدا کن",
|
||||
"confirmations.mute.message": "آیا واقعاً میخواهید {name} را بیصدا کنید؟",
|
||||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||
"confirmations.unfollow.confirm": "لغو پیگیری",
|
||||
"confirmations.unfollow.message": "آیا واقعاً میخواهید به پیگیری از {name} پایان دهید؟",
|
||||
"emoji_button.activity": "فعالیت",
|
||||
"emoji_button.flags": "پرچمها",
|
||||
"emoji_button.food": "غذا و نوشیدنی",
|
||||
|
@ -89,6 +94,8 @@
|
|||
"home.column_settings.show_replies": "نمایش پاسخها",
|
||||
"home.settings": "تنظیمات ستون",
|
||||
"lightbox.close": "بستن",
|
||||
"lightbox.next": "بعدی",
|
||||
"lightbox.previous": "قبلی",
|
||||
"loading_indicator.label": "بارگیری...",
|
||||
"media_gallery.toggle_visible": "تغییر پیدایی",
|
||||
"missing_indicator.label": "پیدا نشد",
|
||||
|
@ -112,8 +119,8 @@
|
|||
"notifications.column_settings.favourite": "پسندیدهها:",
|
||||
"notifications.column_settings.follow": "پیگیران تازه:",
|
||||
"notifications.column_settings.mention": "نامبردنها:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.push_meta": "This device",
|
||||
"notifications.column_settings.push": "اعلانها از سمت سرور",
|
||||
"notifications.column_settings.push_meta": "این دستگاه",
|
||||
"notifications.column_settings.reblog": "بازبوقها:",
|
||||
"notifications.column_settings.show": "نمایش در ستون",
|
||||
"notifications.column_settings.sound": "پخش صدا",
|
||||
|
@ -152,7 +159,7 @@
|
|||
"report.target": "گزارشدادن",
|
||||
"search.placeholder": "جستجو",
|
||||
"search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
|
||||
"standalone.public_title": "A look inside...",
|
||||
"standalone.public_title": "نگاهی به کاربران این سرور...",
|
||||
"status.cannot_reblog": "این نوشته را نمیشود بازبوقید",
|
||||
"status.delete": "پاککردن",
|
||||
"status.favourite": "پسندیدن",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "گزارش دادن @{name}",
|
||||
"status.sensitive_toggle": "برای دیدن کلیک کنید",
|
||||
"status.sensitive_warning": "محتوای حساس",
|
||||
"status.share": "همرسانی",
|
||||
"status.show_less": "نهفتن",
|
||||
"status.show_more": "نمایش",
|
||||
"status.unmute_conversation": "باصداکردن گفتگو",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Postit",
|
||||
"account.report": "Report @{name}",
|
||||
"account.requested": "Odottaa hyväksyntää",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Salli @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Lopeta seuraaminen",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Ilmoitukset",
|
||||
"column.public": "Yleinen aikajana",
|
||||
"column_back_button.label": "Takaisin",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_toggle": "Klikkaa nähdäksesi",
|
||||
"status.sensitive_warning": "Arkaluontoista sisältöä",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_more": "Show more",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Statuts",
|
||||
"account.report": "Signaler",
|
||||
"account.requested": "Invitation envoyée",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Débloquer",
|
||||
"account.unblock_domain": "Ne plus masquer {domain}",
|
||||
"account.unfollow": "Ne plus suivre",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Notifications",
|
||||
"column.public": "Fil public global",
|
||||
"column_back_button.label": "Retour",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Épingler",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Retirer",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Paramètres",
|
||||
|
@ -89,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é",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Signaler @{name}",
|
||||
"status.sensitive_toggle": "Cliquer pour afficher",
|
||||
"status.sensitive_warning": "Contenu sensible",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Replier",
|
||||
"status.show_more": "Déplier",
|
||||
"status.unmute_conversation": "Ne plus masquer la conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "הודעות",
|
||||
"account.report": "לדווח על @{name}",
|
||||
"account.requested": "בהמתנה לאישור",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "הסרת חסימה מעל @{name}",
|
||||
"account.unblock_domain": "הסר חסימה מקהילת {domain}",
|
||||
"account.unfollow": "הפסקת מעקב",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "התראות",
|
||||
"column.public": "בפרהסיה",
|
||||
"column_back_button.label": "חזרה",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "ניווט",
|
||||
"column_subheading.settings": "אפשרויות",
|
||||
|
@ -89,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": "לא נמצא",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "דיווח על @{name}",
|
||||
"status.sensitive_toggle": "לחצו כדי לראות",
|
||||
"status.sensitive_warning": "תוכן רגיש",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "הראה פחות",
|
||||
"status.show_more": "הראה יותר",
|
||||
"status.unmute_conversation": "הסרת השתקת שיחה",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Postovi",
|
||||
"account.report": "Prijavi @{name}",
|
||||
"account.requested": "Čeka pristanak",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Deblokiraj @{name}",
|
||||
"account.unblock_domain": "Otkrij {domain}",
|
||||
"account.unfollow": "Prestani slijediti",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Notifikacije",
|
||||
"column.public": "Federalni timeline",
|
||||
"column_back_button.label": "Natrag",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigacija",
|
||||
"column_subheading.settings": "Postavke",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Prijavi @{name}",
|
||||
"status.sensitive_toggle": "Klikni da bi vidio",
|
||||
"status.sensitive_warning": "Osjetljiv sadržaj",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Pokaži manje",
|
||||
"status.show_more": "Pokaži više",
|
||||
"status.unmute_conversation": "Poništi utišavanje razgovora",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Posts",
|
||||
"account.report": "Report @{name}",
|
||||
"account.requested": "Awaiting approval",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Blokkolás levétele",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Követés abbahagyása",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Értesítések",
|
||||
"column.public": "Nyilvános",
|
||||
"column_back_button.label": "Vissza",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_toggle": "Katt a megtekintéshez",
|
||||
"status.sensitive_warning": "Érzékeny tartalom",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_more": "Show more",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Postingan",
|
||||
"account.report": "Laporkan @{name}",
|
||||
"account.requested": "Menunggu persetujuan",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Hapus blokir @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Berhenti mengikuti",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Notifikasi",
|
||||
"column.public": "Linimasa gabunggan",
|
||||
"column_back_button.label": "Kembali",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigasi",
|
||||
"column_subheading.settings": "Pengaturan",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Laporkan @{name}",
|
||||
"status.sensitive_toggle": "Klik untuk menampilkan",
|
||||
"status.sensitive_warning": "Konten sensitif",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Tampilkan lebih sedikit",
|
||||
"status.show_more": "Tampilkan semua",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Mesaji",
|
||||
"account.report": "Denuncar @{name}",
|
||||
"account.requested": "Vartante aprobo",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Desblokusar @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Ne plus sequar",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Savigi",
|
||||
"column.public": "Federata tempolineo",
|
||||
"column_back_button.label": "Retro",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Denuncar @{name}",
|
||||
"status.sensitive_toggle": "Kliktar por vidar",
|
||||
"status.sensitive_warning": "Trubliva kontenajo",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Montrar mine",
|
||||
"status.show_more": "Montrar plue",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Posts",
|
||||
"account.report": "Segnala @{name}",
|
||||
"account.requested": "In attesa di approvazione",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Sblocca @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Non seguire",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Notifiche",
|
||||
"column.public": "Timeline federata",
|
||||
"column_back_button.label": "Indietro",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Segnala @{name}",
|
||||
"status.sensitive_toggle": "Clicca per vedere",
|
||||
"status.sensitive_warning": "Materiale sensibile",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Mostra meno",
|
||||
"status.show_more": "Mostra di più",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"account.block": "ブロック",
|
||||
"account.block_domain": "{domain}全体を非表示",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.disclaimer_full": "以下の情報は不正確な可能性があります。",
|
||||
"account.edit_profile": "プロフィールを編集",
|
||||
"account.follow": "フォロー",
|
||||
"account.followers": "フォロワー",
|
||||
|
@ -13,11 +13,12 @@
|
|||
"account.posts": "投稿",
|
||||
"account.report": "通報",
|
||||
"account.requested": "承認待ち",
|
||||
"account.share": "@{name} のプロフィールを共有する",
|
||||
"account.unblock": "ブロック解除",
|
||||
"account.unblock_domain": "{domain}を表示",
|
||||
"account.unfollow": "フォロー解除",
|
||||
"account.unmute": "ミュート解除",
|
||||
"account.view_full_profile": "View full profile",
|
||||
"account.view_full_profile": "全ての情報を見る",
|
||||
"boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。",
|
||||
"bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。",
|
||||
"bundle_column_error.retry": "再試行",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "通知",
|
||||
"column.public": "連合タイムライン",
|
||||
"column_back_button.label": "戻る",
|
||||
"column_header.hide_settings": "設定を隠す",
|
||||
"column_header.moveLeft_settings": "カラムを左に移動する",
|
||||
"column_header.moveRight_settings": "カラムを右に移動する",
|
||||
"column_header.pin": "ピン留めする",
|
||||
"column_header.show_settings": "設定を表示",
|
||||
"column_header.unpin": "ピン留めを外す",
|
||||
"column_subheading.navigation": "ナビゲーション",
|
||||
"column_subheading.settings": "設定",
|
||||
|
@ -89,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": "見つかりません",
|
||||
|
@ -149,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": "連合タイムライン",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "通報",
|
||||
"status.sensitive_toggle": "クリックして表示",
|
||||
"status.sensitive_warning": "閲覧注意",
|
||||
"status.share": "共有",
|
||||
"status.show_less": "隠す",
|
||||
"status.show_more": "もっと見る",
|
||||
"status.unmute_conversation": "会話のミュートを解除",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "포스트",
|
||||
"account.report": "신고",
|
||||
"account.requested": "승인 대기 중",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "차단 해제",
|
||||
"account.unblock_domain": "{domain} 숨김 해제",
|
||||
"account.unfollow": "팔로우 해제",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "알림",
|
||||
"column.public": "연합 타임라인",
|
||||
"column_back_button.label": "돌아가기",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "고정하기",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "고정 해제",
|
||||
"column_subheading.navigation": "내비게이션",
|
||||
"column_subheading.settings": "설정",
|
||||
|
@ -89,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": "찾을 수 없습니다",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "신고",
|
||||
"status.sensitive_toggle": "클릭해서 표시하기",
|
||||
"status.sensitive_warning": "민감한 미디어",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "숨기기",
|
||||
"status.show_more": "더 보기",
|
||||
"status.unmute_conversation": "이 대화의 뮤트 해제하기",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Toots",
|
||||
"account.report": "Rapporteer @{name}",
|
||||
"account.requested": "Wacht op goedkeuring",
|
||||
"account.share": "Profiel van @{name} delen",
|
||||
"account.unblock": "Deblokkeer @{name}",
|
||||
"account.unblock_domain": "{domain} niet meer negeren",
|
||||
"account.unfollow": "Ontvolgen",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Meldingen",
|
||||
"column.public": "Globale tijdlijn",
|
||||
"column_back_button.label": "terug",
|
||||
"column_header.hide_settings": "Instellingen verbergen",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Vastmaken",
|
||||
"column_header.show_settings": "Instellingen tonen",
|
||||
"column_header.unpin": "Losmaken",
|
||||
"column_subheading.navigation": "Navigatie",
|
||||
"column_subheading.settings": "Instellingen",
|
||||
|
@ -89,6 +94,8 @@
|
|||
"home.column_settings.show_replies": "Reacties tonen",
|
||||
"home.settings": "Kolom-instellingen",
|
||||
"lightbox.close": "Sluiten",
|
||||
"lightbox.next": "Volgende",
|
||||
"lightbox.previous": "Vorige",
|
||||
"loading_indicator.label": "Laden…",
|
||||
"media_gallery.toggle_visible": "Media wel/niet tonen",
|
||||
"missing_indicator.label": "Niet gevonden",
|
||||
|
@ -147,12 +154,12 @@
|
|||
"privacy.unlisted.long": "Niet op openbare tijdlijnen tonen",
|
||||
"privacy.unlisted.short": "Minder openbaar",
|
||||
"reply_indicator.cancel": "Annuleren",
|
||||
"report.heading": "Rapporteren",
|
||||
"report.placeholder": "Extra opmerkingen",
|
||||
"report.submit": "Verzenden",
|
||||
"report.target": "Rapporteren van",
|
||||
"search.placeholder": "Zoeken",
|
||||
"search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
|
||||
"standalone.public_title": "Een kijkje binnenin...",
|
||||
"status.cannot_reblog": "Deze toot kan niet geboost worden",
|
||||
"status.delete": "Verwijderen",
|
||||
"status.favourite": "Favoriet",
|
||||
|
@ -166,8 +173,9 @@
|
|||
"status.reply": "Reageren",
|
||||
"status.replyAll": "Reageer op iedereen",
|
||||
"status.report": "Rapporteer @{name}",
|
||||
"status.sensitive_toggle": "Klik om te zien",
|
||||
"status.sensitive_toggle": "Klik om te bekijken",
|
||||
"status.sensitive_warning": "Gevoelige inhoud",
|
||||
"status.share": "Delen",
|
||||
"status.show_less": "Minder tonen",
|
||||
"status.show_more": "Meer tonen",
|
||||
"status.unmute_conversation": "Conversatie niet meer negeren",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Innlegg",
|
||||
"account.report": "Rapportér @{name}",
|
||||
"account.requested": "Venter på godkjennelse",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Avblokker @{name}",
|
||||
"account.unblock_domain": "Vis {domain}",
|
||||
"account.unfollow": "Avfølg",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Varsler",
|
||||
"column.public": "Felles tidslinje",
|
||||
"column_back_button.label": "Tilbake",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigasjon",
|
||||
"column_subheading.settings": "Innstillinger",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Rapporter @{name}",
|
||||
"status.sensitive_toggle": "Klikk for å vise",
|
||||
"status.sensitive_warning": "Følsomt innhold",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Vis mindre",
|
||||
"status.show_more": "Vis mer",
|
||||
"status.unmute_conversation": "Ikke demp samtale",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"account.block": "Blocar @{name}",
|
||||
"account.block_domain": "Tot amagar del domeni {domain}",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incompletas.",
|
||||
"account.edit_profile": "Modificar lo perfil",
|
||||
"account.follow": "Sègre",
|
||||
"account.followers": "Seguidors",
|
||||
|
@ -13,18 +13,19 @@
|
|||
"account.posts": "Estatuts",
|
||||
"account.report": "Senhalar @{name}",
|
||||
"account.requested": "Invitacion mandada",
|
||||
"account.share": "Partejar lo perfil a @{name}",
|
||||
"account.unblock": "Desblocar @{name}",
|
||||
"account.unblock_domain": "Desblocar {domain}",
|
||||
"account.unfollow": "Quitar de sègre",
|
||||
"account.unmute": "Quitar de rescondre @{name}",
|
||||
"account.view_full_profile": "View full profile",
|
||||
"account.view_full_profile": "Veire lo perfil complet",
|
||||
"boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven",
|
||||
"bundle_column_error.body": "Quicòm a fach meuca pendent lo cargament d’aqueste compausant.",
|
||||
"bundle_column_error.retry": "Tornar ensejar",
|
||||
"bundle_column_error.retry": "Tornar ensajar",
|
||||
"bundle_column_error.title": "Error de ret",
|
||||
"bundle_modal_error.close": "Tampar",
|
||||
"bundle_modal_error.message": "Quicòm a fach meuca pendent lo cargament d’aqueste compausant.",
|
||||
"bundle_modal_error.retry": "Tornar ensejar",
|
||||
"bundle_modal_error.message": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.",
|
||||
"bundle_modal_error.retry": "Tornar ensajar",
|
||||
"column.blocks": "Personas blocadas",
|
||||
"column.community": "Flux public local",
|
||||
"column.favourites": "Favorits",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Notificacions",
|
||||
"column.public": "Flux public global",
|
||||
"column_back_button.label": "Tornar",
|
||||
"column_header.hide_settings": "Amagar los paramètres",
|
||||
"column_header.moveLeft_settings": "Desplaçar la colomna a man drecha",
|
||||
"column_header.moveRight_settings": "Desplaçar la colomna a man esquèrra",
|
||||
"column_header.pin": "Penjar",
|
||||
"column_header.show_settings": "Mostrar los paramètres",
|
||||
"column_header.unpin": "Despenjar",
|
||||
"column_subheading.navigation": "Navigacion",
|
||||
"column_subheading.settings": "Paramètres",
|
||||
|
@ -46,35 +51,35 @@
|
|||
"compose_form.publish_loud": "{publish} !",
|
||||
"compose_form.sensitive": "Marcar lo mèdia coma sensible",
|
||||
"compose_form.spoiler": "Rescondre lo tèxte darrièr un avertiment",
|
||||
"compose_form.spoiler_placeholder": "Avertiment",
|
||||
"compose_form.spoiler_placeholder": "Escrivètz l’avertiment aquí",
|
||||
"confirmation_modal.cancel": "Anullar",
|
||||
"confirmations.block.confirm": "Blocar",
|
||||
"confirmations.block.message": "Sètz segur de voler blocar {name} ?",
|
||||
"confirmations.delete.confirm": "Suprimir",
|
||||
"confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?",
|
||||
"confirmations.domain_block.confirm": "Amagar tot lo domeni",
|
||||
"confirmations.domain_block.message": "Sètz segur segur de voler blocar complètament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.",
|
||||
"confirmations.domain_block.message": "Sètz segur segur de voler blocar completament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.",
|
||||
"confirmations.mute.confirm": "Metre en silenci",
|
||||
"confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?",
|
||||
"confirmations.unfollow.confirm": "Quitar de sègre",
|
||||
"confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?",
|
||||
"emoji_button.activity": "Activitat",
|
||||
"emoji_button.activity": "Activitats",
|
||||
"emoji_button.flags": "Drapèus",
|
||||
"emoji_button.food": "Beure e manjar",
|
||||
"emoji_button.label": "Inserir un emoji",
|
||||
"emoji_button.nature": "Natura",
|
||||
"emoji_button.objects": "Objèctes",
|
||||
"emoji_button.people": "Gents",
|
||||
"emoji_button.search": "Cercar...",
|
||||
"emoji_button.search": "Cercar…",
|
||||
"emoji_button.symbols": "Simbòls",
|
||||
"emoji_button.travel": "Viatges & lòcs",
|
||||
"empty_column.community": "Lo flux public local es void. Escribètz quicòm per lo garnir !",
|
||||
"empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !",
|
||||
"empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag",
|
||||
"empty_column.home": "Pel moment segètz pas degun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
|
||||
"empty_column.home": "Pel moment seguètz pas degun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
|
||||
"empty_column.home.inactivity": "Vòstra pagina d’acuèlh es voida. Se sètz estat inactiu per un moment, serà tornada generar per vos dins una estona.",
|
||||
"empty_column.home.public_timeline": "lo flux public",
|
||||
"empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
|
||||
"empty_column.public": "I a pas res aquí ! Escribètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.",
|
||||
"empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.",
|
||||
"follow_request.authorize": "Autorizar",
|
||||
"follow_request.reject": "Regetar",
|
||||
"getting_started.appsshort": "Apps",
|
||||
|
@ -89,6 +94,8 @@
|
|||
"home.column_settings.show_replies": "Mostrar las responsas",
|
||||
"home.settings": "Paramètres de la colomna",
|
||||
"lightbox.close": "Tampar",
|
||||
"lightbox.next": "Seguent",
|
||||
"lightbox.previous": "Precedent",
|
||||
"loading_indicator.label": "Cargament…",
|
||||
"media_gallery.toggle_visible": "Modificar la visibilitat",
|
||||
"missing_indicator.label": "Pas trobat",
|
||||
|
@ -103,11 +110,11 @@
|
|||
"navigation_bar.preferences": "Preferéncias",
|
||||
"navigation_bar.public_timeline": "Flux public global",
|
||||
"notification.favourite": "{name} a ajustat a sos favorits :",
|
||||
"notification.follow": "{name} vos sèc.",
|
||||
"notification.follow": "{name} vos sèc",
|
||||
"notification.mention": "{name} vos a mencionat :",
|
||||
"notification.reblog": "{name} a partejat vòstre estatut :",
|
||||
"notifications.clear": "Levar",
|
||||
"notifications.clear_confirmation": "Volètz vertadièrament levar totas vòstras las notificacions ?",
|
||||
"notifications.clear": "Escafar",
|
||||
"notifications.clear_confirmation": "Volètz vertadièrament escafar totas vòstras las notificacions ?",
|
||||
"notifications.column_settings.alert": "Notificacions localas",
|
||||
"notifications.column_settings.favourite": "Favorits :",
|
||||
"notifications.column_settings.follow": "Nòus seguidors :",
|
||||
|
@ -119,15 +126,15 @@
|
|||
"notifications.column_settings.sound": "Emetre un son",
|
||||
"onboarding.done": "Fach",
|
||||
"onboarding.next": "Seguent",
|
||||
"onboarding.page_five.public_timelines": "Lo flux local mòstra los estatuts publics del monde de vòstra intància, aquí {domain}. Lo flux federat mòstra los estatuts publics de tot lo mond sus {domain} sègon. Son los fluxes publics, un bon biais de trobar de mond.",
|
||||
"onboarding.page_five.public_timelines": "Lo flux local mòstra los estatuts publics del monde de vòstra instància, aquí {domain}. Lo flux federat mòstra los estatuts publics de tot lo mond sus {domain} sègon. Son los fluxes publics, un bon biais de trobar de mond.",
|
||||
"onboarding.page_four.home": "Lo flux d’acuèlh mòstra los estatuts del mond que seguètz.",
|
||||
"onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un enteragís amb vos",
|
||||
"onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos",
|
||||
"onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per bastir un malhum ma larg. Òm los apèla instàncias.",
|
||||
"onboarding.page_one.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}",
|
||||
"onboarding.page_one.welcome": "Benvengut a Mastodon !",
|
||||
"onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.",
|
||||
"onboarding.page_six.almost_done": "Gaireben acabat…",
|
||||
"onboarding.page_six.appetoot": "Bon Appetoot!",
|
||||
"onboarding.page_six.appetoot": "Bon Appetut!",
|
||||
"onboarding.page_six.apps_available": "I a d’aplicacions per mobil per iOS, Android e mai.",
|
||||
"onboarding.page_six.github": "Mastodon es un logicial liure e open-source. Podètz senhalar de bugs, demandar de foncionalitats e contribuir al còdi sus {github}.",
|
||||
"onboarding.page_six.guidelines": "guida de la comunitat",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Senhalar @{name}",
|
||||
"status.sensitive_toggle": "Clicar per mostrar",
|
||||
"status.sensitive_warning": "Contengut sensible",
|
||||
"status.share": "Partejar",
|
||||
"status.show_less": "Tornar plegar",
|
||||
"status.show_more": "Desplegar",
|
||||
"status.unmute_conversation": "Conversacions amb silenci levat",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Posty",
|
||||
"account.report": "Zgłoś @{name}",
|
||||
"account.requested": "Oczekująca prośba",
|
||||
"account.share": "Udostępnij profil @{name}",
|
||||
"account.unblock": "Odblokuj @{name}",
|
||||
"account.unblock_domain": "Odblokuj domenę {domain}",
|
||||
"account.unfollow": "Przestań śledzić",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Powiadomienia",
|
||||
"column.public": "Globalna oś czasu",
|
||||
"column_back_button.label": "Wróć",
|
||||
"column_header.hide_settings": "Ukryj ustawienia",
|
||||
"column_header.moveLeft_settings": "Przesuń kolumnę w lewo",
|
||||
"column_header.moveRight_settings": "Przesuń kolumnę w prawo",
|
||||
"column_header.pin": "Przypnij",
|
||||
"column_header.show_settings": "Pokaż ustawienia",
|
||||
"column_header.unpin": "Cofnij przypięcie",
|
||||
"column_subheading.navigation": "Nawigacja",
|
||||
"column_subheading.settings": "Ustawienia",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Zgłoś @{name}",
|
||||
"status.sensitive_toggle": "Naciśnij aby wyświetlić",
|
||||
"status.sensitive_warning": "Wrażliwa zawartość",
|
||||
"status.share": "Udostępnij",
|
||||
"status.show_less": "Pokaż mniej",
|
||||
"status.show_more": "Pokaż więcej",
|
||||
"status.unmute_conversation": "Cofnij wyciezenie konwersacji",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Posts",
|
||||
"account.report": "Denunciar @{name}",
|
||||
"account.requested": "A aguardar aprovação",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Não bloquear @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Deixar de seguir",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Notificações",
|
||||
"column.public": "Global",
|
||||
"column_back_button.label": "Voltar",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Denúnciar @{name}",
|
||||
"status.sensitive_toggle": "Clique para ver",
|
||||
"status.sensitive_warning": "Conteúdo sensível",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Mostrar menos",
|
||||
"status.show_more": "Mostrar mais",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Posts",
|
||||
"account.report": "Denunciar @{name}",
|
||||
"account.requested": "A aguardar aprovação",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Não bloquear @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Deixar de seguir",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Notificações",
|
||||
"column.public": "Global",
|
||||
"column_back_button.label": "Voltar",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Denúnciar @{name}",
|
||||
"status.sensitive_toggle": "Clique para ver",
|
||||
"status.sensitive_warning": "Conteúdo sensível",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Mostrar menos",
|
||||
"status.show_more": "Mostrar mais",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Посты",
|
||||
"account.report": "Пожаловаться",
|
||||
"account.requested": "Ожидает подтверждения",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Разблокировать",
|
||||
"account.unblock_domain": "Разблокировать {domain}",
|
||||
"account.unfollow": "Отписаться",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Уведомления",
|
||||
"column.public": "Глобальная лента",
|
||||
"column_back_button.label": "Назад",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Закрепить",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Открепить",
|
||||
"column_subheading.navigation": "Навигация",
|
||||
"column_subheading.settings": "Настройки",
|
||||
|
@ -89,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": "Не найдено",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Пожаловаться",
|
||||
"status.sensitive_toggle": "Нажмите для просмотра",
|
||||
"status.sensitive_warning": "Чувствительный контент",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Свернуть",
|
||||
"status.show_more": "Развернуть",
|
||||
"status.unmute_conversation": "Снять глушение с треда",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Posts",
|
||||
"account.report": "Report @{name}",
|
||||
"account.requested": "Awaiting approval",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Unblock @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Unfollow",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Notifications",
|
||||
"column.public": "Federated timeline",
|
||||
"column_back_button.label": "Back",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
|
@ -89,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",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_toggle": "Click to view",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_more": "Show more",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Gönderiler",
|
||||
"account.report": "Rapor et @{name}",
|
||||
"account.requested": "Onay bekleniyor",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Engeli kaldır @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "Takipten vazgeç",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Bildirimler",
|
||||
"column.public": "Federe zaman tüneli",
|
||||
"column_back_button.label": "Geri",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigasyon",
|
||||
"column_subheading.settings": "Ayarlar",
|
||||
|
@ -89,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ı",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "@{name}'i raporla",
|
||||
"status.sensitive_toggle": "Görmek için tıklayınız",
|
||||
"status.sensitive_warning": "Hassas içerik",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Daha azı",
|
||||
"status.show_more": "Daha fazlası",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "Пости",
|
||||
"account.report": "Поскаржитися",
|
||||
"account.requested": "Очікує підтвердження",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Розблокувати",
|
||||
"account.unblock_domain": "Розблокувати {domain}",
|
||||
"account.unfollow": "Відписатися",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "Сповіщення",
|
||||
"column.public": "Глобальна стрічка",
|
||||
"column_back_button.label": "Назад",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Навігація",
|
||||
"column_subheading.settings": "Налаштування",
|
||||
|
@ -89,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": "Не знайдено",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "Поскаржитися",
|
||||
"status.sensitive_toggle": "Натисніть, щоб подивитися",
|
||||
"status.sensitive_warning": "Непристойний зміст",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Згорнути",
|
||||
"status.show_more": "Розгорнути",
|
||||
"status.unmute_conversation": "Зняти глушення з діалогу",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "嘟文",
|
||||
"account.report": "举报 @{name}",
|
||||
"account.requested": "等待审批",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "解除对 @{name} 的屏蔽",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "取消关注",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "通知",
|
||||
"column.public": "跨站公共时间轴",
|
||||
"column_back_button.label": "Back",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "导航",
|
||||
"column_subheading.settings": "设置",
|
||||
|
@ -89,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": "找不到内容",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "举报 @{name}",
|
||||
"status.sensitive_toggle": "点击显示",
|
||||
"status.sensitive_warning": "敏感内容",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "减少显示",
|
||||
"status.show_more": "显示更多",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "文章",
|
||||
"account.report": "舉報 @{name}",
|
||||
"account.requested": "等候審批",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "解除對 @{name} 的封鎖",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unfollow": "取消關注",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "通知",
|
||||
"column.public": "跨站時間軸",
|
||||
"column_back_button.label": "返回",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "瀏覽",
|
||||
"column_subheading.settings": "設定",
|
||||
|
@ -89,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": "找不到內容",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "舉報 @{name}",
|
||||
"status.sensitive_toggle": "點擊顯示",
|
||||
"status.sensitive_warning": "敏感內容",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "減少顯示",
|
||||
"status.show_more": "顯示更多",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"account.posts": "貼文",
|
||||
"account.report": "檢舉 @{name}",
|
||||
"account.requested": "正在等待許可",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "取消封鎖 @{name}",
|
||||
"account.unblock_domain": "不再隱藏 {domain}",
|
||||
"account.unfollow": "取消關注",
|
||||
|
@ -34,7 +35,11 @@
|
|||
"column.notifications": "通知",
|
||||
"column.public": "聯盟時間軸",
|
||||
"column_back_button.label": "上一頁",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "瀏覽",
|
||||
"column_subheading.settings": "設定",
|
||||
|
@ -89,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": "找不到",
|
||||
|
@ -168,6 +175,7 @@
|
|||
"status.report": "通報 @{name}",
|
||||
"status.sensitive_toggle": "點來看",
|
||||
"status.sensitive_warning": "敏感內容",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "看少點",
|
||||
"status.show_more": "看更多",
|
||||
"status.unmute_conversation": "不消音對話",
|
||||
|
|
|
@ -96,7 +96,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);
|
||||
}
|
||||
});
|
||||
|
@ -165,14 +165,22 @@ export default function compose(state = initialState, action) {
|
|||
state.get('advanced_options').set(action.option, !state.getIn(['advanced_options', action.option])))
|
||||
.set('idempotencyKey', uuid());
|
||||
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
|
||||
|
|
|
@ -1,3 +1,45 @@
|
|||
const MAX_NOTIFICATIONS = 5;
|
||||
const GROUP_TAG = 'tag';
|
||||
|
||||
// Avoid loading intl-messageformat and dealing with locales in the ServiceWorker
|
||||
const formatGroupTitle = (message, count) => message.replace('%{count}', count);
|
||||
|
||||
const notify = options =>
|
||||
self.registration.getNotifications().then(notifications => {
|
||||
if (notifications.length === MAX_NOTIFICATIONS) {
|
||||
// Reached the maximum number of notifications, proceed with grouping
|
||||
const group = {
|
||||
title: formatGroupTitle(options.data.message, notifications.length + 1),
|
||||
body: notifications
|
||||
.sort((n1, n2) => n1.timestamp < n2.timestamp)
|
||||
.map(notification => notification.title).join('\n'),
|
||||
badge: '/badge.png',
|
||||
icon: '/android-chrome-192x192.png',
|
||||
tag: GROUP_TAG,
|
||||
data: {
|
||||
url: (new URL('/web/notifications', self.location)).href,
|
||||
count: notifications.length + 1,
|
||||
message: options.data.message,
|
||||
},
|
||||
};
|
||||
|
||||
notifications.forEach(notification => notification.close());
|
||||
|
||||
return self.registration.showNotification(group.title, group);
|
||||
} else if (notifications.length === 1 && notifications[0].tag === GROUP_TAG) {
|
||||
// Already grouped, proceed with appending the notification to the group
|
||||
const group = cloneNotification(notifications[0]);
|
||||
|
||||
group.title = formatGroupTitle(group.data.message, group.data.count + 1);
|
||||
group.body = `${options.title}\n${group.body}`;
|
||||
group.data = { ...group.data, count: group.data.count + 1 };
|
||||
|
||||
return self.registration.showNotification(group.title, group);
|
||||
}
|
||||
|
||||
return self.registration.showNotification(options.title, options);
|
||||
});
|
||||
|
||||
const handlePush = (event) => {
|
||||
const options = event.data.json();
|
||||
|
||||
|
@ -17,7 +59,7 @@ const handlePush = (event) => {
|
|||
options.actions = options.data.actions;
|
||||
}
|
||||
|
||||
event.waitUntil(self.registration.showNotification(options.title, options));
|
||||
event.waitUntil(notify(options));
|
||||
};
|
||||
|
||||
const cloneNotification = (notification) => {
|
||||
|
@ -50,22 +92,37 @@ const makeRequest = (notification, action) =>
|
|||
credentials: 'include',
|
||||
});
|
||||
|
||||
const findBestClient = clients => {
|
||||
const focusedClient = clients.find(client => client.focused);
|
||||
const visibleClient = clients.find(client => client.visibilityState === 'visible');
|
||||
|
||||
return focusedClient || visibleClient || clients[0];
|
||||
};
|
||||
|
||||
const openUrl = url =>
|
||||
self.clients.matchAll({ type: 'window' }).then(clientList => {
|
||||
if (clientList.length !== 0 && 'navigate' in clientList[0]) { // Chrome 42-48 does not support navigate
|
||||
const webClients = clientList
|
||||
.filter(client => /\/web\//.test(client.url))
|
||||
.sort(client => client !== 'visible');
|
||||
if (clientList.length !== 0) {
|
||||
const webClients = clientList.filter(client => /\/web\//.test(client.url));
|
||||
|
||||
const visibleClient = clientList.find(client => client.visibilityState === 'visible');
|
||||
const focusedClient = clientList.find(client => client.focused);
|
||||
if (webClients.length !== 0) {
|
||||
const client = findBestClient(webClients);
|
||||
|
||||
const client = webClients[0] || visibleClient || focusedClient || clientList[0];
|
||||
const { pathname } = new URL(url);
|
||||
|
||||
if (pathname.startsWith('/web/')) {
|
||||
return client.focus().then(client => client.postMessage({
|
||||
type: 'navigate',
|
||||
path: pathname.slice('/web/'.length - 1),
|
||||
}));
|
||||
}
|
||||
} else if ('navigate' in clientList[0]) { // Chrome 42-48 does not support navigate
|
||||
const client = findBestClient(clientList);
|
||||
|
||||
return client.navigate(url).then(client => client.focus());
|
||||
} else {
|
||||
return self.clients.openWindow(url);
|
||||
}
|
||||
}
|
||||
|
||||
return self.clients.openWindow(url);
|
||||
});
|
||||
|
||||
const removeActionFromNotification = (notification, action) => {
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
|
||||
.information-board {
|
||||
background: darken($ui-base-color, 4%);
|
||||
padding: 40px 0;
|
||||
padding: 20px 0;
|
||||
|
||||
.panel {
|
||||
position: absolute;
|
||||
|
@ -147,10 +147,15 @@
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
a,
|
||||
span {
|
||||
font-weight: 400;
|
||||
color: lighten($ui-base-color, 34%);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,13 +167,14 @@
|
|||
.information-board-sections {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.section {
|
||||
flex: 1 0 0;
|
||||
font: 16px/28px 'mastodon-font-sans-serif', sans-serif;
|
||||
text-align: right;
|
||||
padding: 0 15px;
|
||||
padding: 10px 15px;
|
||||
|
||||
span,
|
||||
strong {
|
||||
|
@ -190,14 +196,6 @@
|
|||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
flex-direction: column;
|
||||
|
||||
.section {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.owner {
|
||||
|
@ -317,6 +315,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
p,
|
||||
li {
|
||||
font: inherit;
|
||||
font-weight: inherit;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: rgba($ui-base-lighter-color, .6);
|
||||
}
|
||||
|
||||
.header {
|
||||
line-height: 30px;
|
||||
overflow: hidden;
|
||||
|
@ -553,6 +562,7 @@
|
|||
}
|
||||
|
||||
#mastodon-timeline {
|
||||
display: flex;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||
font-family: 'mastodon-font-sans-serif', sans-serif;
|
||||
|
@ -567,11 +577,20 @@
|
|||
overflow: hidden;
|
||||
box-shadow: 0 0 6px rgba($black, 0.1);
|
||||
|
||||
.column-header {
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
font-size: 16px;
|
||||
line-height: inherit;
|
||||
font-weight: inherit;
|
||||
margin: 0;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.column {
|
||||
padding: 0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
|
@ -652,21 +671,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
@media screen and (max-width: 840px) {
|
||||
.container {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.information-board {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.information-board .container {
|
||||
padding-right: 20px;
|
||||
|
||||
.panel {
|
||||
position: static;
|
||||
margin-top: 30px;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
|
||||
|
@ -694,6 +709,10 @@
|
|||
@media screen and (max-width: 675px) {
|
||||
.header-wrapper {
|
||||
padding-top: 0;
|
||||
|
||||
&.compact .hero .heading {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.header .container,
|
||||
|
@ -707,14 +726,13 @@
|
|||
}
|
||||
|
||||
.header {
|
||||
padding-top: 0;
|
||||
|
||||
.hero {
|
||||
margin-top: 30px;
|
||||
padding: 0;
|
||||
|
||||
.heading {
|
||||
padding-bottom: 20px;
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -149,13 +149,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 {
|
||||
|
@ -215,6 +219,7 @@
|
|||
}
|
||||
|
||||
.dropdown--active::after {
|
||||
@media screen and (min-width: 1025px) {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
@ -226,6 +231,7 @@
|
|||
bottom: 8px;
|
||||
right: 104px;
|
||||
}
|
||||
}
|
||||
|
||||
.invisible {
|
||||
font-size: 0;
|
||||
|
@ -1837,6 +1843,8 @@
|
|||
cursor: pointer;
|
||||
flex: 0 0 auto;
|
||||
font-size: 16px;
|
||||
border: 0;
|
||||
text-align: start;
|
||||
padding: 15px;
|
||||
z-index: 3;
|
||||
|
||||
|
@ -1999,12 +2007,6 @@
|
|||
&:hover {
|
||||
background: lighten($ui-base-color, 11%);
|
||||
}
|
||||
|
||||
&.hidden-on-mobile {
|
||||
@include single-column('screen and (max-width: 1024px)') {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-link__icon {
|
||||
|
@ -2388,12 +2390,6 @@ button.icon-button.active i.fa-retweet {
|
|||
}
|
||||
}
|
||||
|
||||
&.hidden-on-mobile {
|
||||
@include single-column('screen and (max-width: 1024px)') {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0;
|
||||
|
@ -2672,6 +2668,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;
|
||||
|
@ -2754,6 +2752,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;
|
||||
|
@ -3848,7 +3847,8 @@ button.icon-button.active i.fa-retweet {
|
|||
|
||||
.boost-modal,
|
||||
.confirmation-modal,
|
||||
.report-modal {
|
||||
.report-modal,
|
||||
.actions-modal {
|
||||
background: lighten($ui-secondary-color, 8%);
|
||||
color: $ui-base-color;
|
||||
border-radius: 8px;
|
||||
|
@ -3873,6 +3873,15 @@ button.icon-button.active i.fa-retweet {
|
|||
}
|
||||
}
|
||||
|
||||
.actions-modal {
|
||||
.status {
|
||||
background: $white;
|
||||
border-bottom-color: $ui-secondary-color;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.boost-modal__container {
|
||||
overflow-x: scroll;
|
||||
padding: 10px;
|
||||
|
@ -3914,7 +3923,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;
|
||||
|
@ -3939,6 +3948,47 @@ button.icon-button.active i.fa-retweet {
|
|||
}
|
||||
}
|
||||
|
||||
.actions-modal {
|
||||
.status {
|
||||
overflow-y: auto;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
|
||||
ul {
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
|
||||
li:empty {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li:not(:empty) {
|
||||
a {
|
||||
color: $ui-base-color;
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
|
||||
&.active {
|
||||
&,
|
||||
button {
|
||||
background: $ui-highlight-color;
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
button:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.confirmation-modal__action-bar {
|
||||
.confirmation-modal__cancel-button {
|
||||
background-color: transparent;
|
||||
|
|
|
@ -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 = {}
|
||||
|
||||
|
@ -32,7 +32,7 @@ class Emoji
|
|||
|
||||
def codepoint_to_unicode(codepoint)
|
||||
if codepoint.include?('-')
|
||||
codepoint.split('-').map(&:hex).pack('U')
|
||||
codepoint.split('-').map(&:hex).pack('U*')
|
||||
else
|
||||
[codepoint.hex].pack('U')
|
||||
end
|
||||
|
|
|
@ -8,11 +8,11 @@ module Mastodon
|
|||
|
||||
class UnexpectedResponseError < Error
|
||||
def initialize(response = nil)
|
||||
@response = response
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{@response.uri} returned code #{@response.code}"
|
||||
if response.respond_to? :uri
|
||||
super("#{response.uri} returned code #{response.code}")
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -53,6 +53,7 @@ class Web::PushSubscription < ApplicationRecord
|
|||
url: url,
|
||||
actions: actions,
|
||||
access_token: access_token,
|
||||
message: translate('push_notifications.group.title'), # Do not pass count, will be formatted in the ServiceWorker
|
||||
}
|
||||
),
|
||||
endpoint: endpoint,
|
||||
|
@ -117,7 +118,7 @@ class Web::PushSubscription < ApplicationRecord
|
|||
when :mention then [
|
||||
{
|
||||
title: translate('push_notifications.mention.action_favourite'),
|
||||
icon: full_asset_url('emoji/2764.png', skip_pipeline: true),
|
||||
icon: full_asset_url('web-push-icon_favourite.png', skip_pipeline: true),
|
||||
todo: 'request',
|
||||
method: 'POST',
|
||||
action: "/api/v1/statuses/#{notification.target_status.id}/favourite",
|
||||
|
@ -130,11 +131,11 @@ class Web::PushSubscription < ApplicationRecord
|
|||
can_boost = notification.type.equal?(:mention) && !notification.target_status.nil? && !notification.target_status.hidden?
|
||||
|
||||
if should_hide
|
||||
actions.insert(0, title: translate('push_notifications.mention.action_expand'), icon: full_asset_url('emoji/1f441.png'), todo: 'expand', action: 'expand')
|
||||
actions.insert(0, title: translate('push_notifications.mention.action_expand'), icon: full_asset_url('web-push-icon_expand.png', skip_pipeline: true), todo: 'expand', action: 'expand')
|
||||
end
|
||||
|
||||
if can_boost
|
||||
actions << { title: translate('push_notifications.mention.action_boost'), icon: full_asset_url('emoji/1f504.png'), 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
|
||||
|
@ -160,6 +161,7 @@ class Web::PushSubscription < ApplicationRecord
|
|||
content: translate('push_notifications.subscribed.body'),
|
||||
actions: [],
|
||||
url: web_url('notifications'),
|
||||
message: translate('push_notifications.group.title'), # Do not pass count, will be formatted in the ServiceWorker
|
||||
}
|
||||
),
|
||||
endpoint: endpoint,
|
||||
|
|
|
@ -18,7 +18,7 @@ class AccountSearchService < BaseService
|
|||
return [] if query_blank_or_hashtag? || limit < 1
|
||||
|
||||
if resolving_non_matching_remote_account?
|
||||
[ResolveRemoteAccountService.new.call("#{query_username}@#{query_domain}")]
|
||||
[ResolveRemoteAccountService.new.call("#{query_username}@#{query_domain}")].compact
|
||||
else
|
||||
search_results_and_exact_match.compact.uniq.slice(0, limit)
|
||||
end
|
||||
|
|
|
@ -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'
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue