Merge tag 'v1.5.0rc2' into kosmos
This commit is contained in:
commit
3704f3ccb3
@ -4,7 +4,6 @@ public/system
|
|||||||
public/assets
|
public/assets
|
||||||
public/packs
|
public/packs
|
||||||
node_modules
|
node_modules
|
||||||
storybook
|
|
||||||
neo4j
|
neo4j
|
||||||
vendor/bundle
|
vendor/bundle
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
@ -112,7 +112,7 @@ rules:
|
|||||||
jsx-a11y/iframe-has-title: warn
|
jsx-a11y/iframe-has-title: warn
|
||||||
jsx-a11y/img-has-alt: warn
|
jsx-a11y/img-has-alt: warn
|
||||||
jsx-a11y/img-redundant-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/mouse-events-have-key-events: warn
|
||||||
jsx-a11y/no-access-key: warn
|
jsx-a11y/no-access-key: warn
|
||||||
jsx-a11y/no-distracting-elements: warn
|
jsx-a11y/no-distracting-elements: warn
|
||||||
@ -121,6 +121,6 @@ rules:
|
|||||||
jsx-a11y/onclick-has-focus: warn
|
jsx-a11y/onclick-has-focus: warn
|
||||||
jsx-a11y/onclick-has-role: warn
|
jsx-a11y/onclick-has-role: warn
|
||||||
jsx-a11y/role-has-required-aria-props: 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/scope: warn
|
||||||
jsx-a11y/tabindex-no-positive: warn
|
jsx-a11y/tabindex-no-positive: warn
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -21,7 +21,6 @@ public/system
|
|||||||
public/assets
|
public/assets
|
||||||
public/packs
|
public/packs
|
||||||
public/packs-test
|
public/packs-test
|
||||||
public/sw.js
|
|
||||||
.env
|
.env
|
||||||
.env.production
|
.env.production
|
||||||
node_modules/
|
node_modules/
|
||||||
|
@ -14,7 +14,6 @@ node_modules/
|
|||||||
public/assets/
|
public/assets/
|
||||||
public/system/
|
public/system/
|
||||||
spec/
|
spec/
|
||||||
storybook/
|
|
||||||
tmp/
|
tmp/
|
||||||
.vagrant/
|
.vagrant/
|
||||||
vendor/bundle/
|
vendor/bundle/
|
||||||
|
@ -2,4 +2,3 @@ node_modules/
|
|||||||
.cache/
|
.cache/
|
||||||
docs/
|
docs/
|
||||||
spec/
|
spec/
|
||||||
storybook/
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
module InstanceHelper
|
module InstanceHelper
|
||||||
def site_title
|
def site_title
|
||||||
Setting.site_title.to_s
|
Setting.site_title.presence || site_hostname
|
||||||
end
|
end
|
||||||
|
|
||||||
def site_hostname
|
def site_hostname
|
||||||
|
@ -162,6 +162,8 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='autosuggest-textarea'>
|
<div className='autosuggest-textarea'>
|
||||||
|
<label>
|
||||||
|
<span style={{ display: 'none' }}>{placeholder}</span>
|
||||||
<Textarea
|
<Textarea
|
||||||
inputRef={this.setTextarea}
|
inputRef={this.setTextarea}
|
||||||
className='autosuggest-textarea__textarea'
|
className='autosuggest-textarea__textarea'
|
||||||
@ -176,6 +178,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
|||||||
onPaste={this.onPaste}
|
onPaste={this.onPaste}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
|
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
|
||||||
{suggestions.map((suggestion, i) => (
|
{suggestions.map((suggestion, i) => (
|
||||||
|
@ -6,6 +6,8 @@ import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
|||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
||||||
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide 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' },
|
||||||
});
|
});
|
||||||
|
|
||||||
@injectIntl
|
@injectIntl
|
||||||
@ -101,8 +103,8 @@ export default class ColumnHeader extends React.PureComponent {
|
|||||||
|
|
||||||
moveButtons = (
|
moveButtons = (
|
||||||
<div key='move-buttons' className='column-header__setting-arrows'>
|
<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 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 className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><i className='fa fa-chevron-right' /></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>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (multiColumn) {
|
} else if (multiColumn) {
|
||||||
@ -133,7 +135,7 @@ export default class ColumnHeader extends React.PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={wrapperClassName}>
|
<div className={wrapperClassName}>
|
||||||
<div role='heading' tabIndex={focusable && '0'} className={buttonClassName} aria-label={title} 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`} />
|
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
|
||||||
{title}
|
{title}
|
||||||
|
|
||||||
@ -141,7 +143,7 @@ export default class ColumnHeader extends React.PureComponent {
|
|||||||
{backButton}
|
{backButton}
|
||||||
{collapseButton}
|
{collapseButton}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</h1>
|
||||||
|
|
||||||
<div className={collapsibleClassName} tabIndex={collapsed && -1} onTransitionEnd={this.handleTransitionEnd}>
|
<div className={collapsibleClassName} tabIndex={collapsed && -1} onTransitionEnd={this.handleTransitionEnd}>
|
||||||
<div className='column-header__collapsible-inner'>
|
<div className='column-header__collapsible-inner'>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
|
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
@ -9,16 +10,23 @@ export default class DropdownMenu extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
isUserTouching: PropTypes.func,
|
||||||
|
isModalOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalOpen: PropTypes.func,
|
||||||
|
onModalClose: PropTypes.func,
|
||||||
icon: PropTypes.string.isRequired,
|
icon: PropTypes.string.isRequired,
|
||||||
items: PropTypes.array.isRequired,
|
items: PropTypes.array.isRequired,
|
||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
direction: PropTypes.string,
|
direction: PropTypes.string,
|
||||||
|
status: ImmutablePropTypes.map,
|
||||||
ariaLabel: PropTypes.string,
|
ariaLabel: PropTypes.string,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
ariaLabel: 'Menu',
|
ariaLabel: 'Menu',
|
||||||
|
isModalOpen: false,
|
||||||
|
isUserTouching: () => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -34,6 +42,10 @@ export default class DropdownMenu extends React.PureComponent {
|
|||||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||||
const { action, to } = this.props.items[i];
|
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.
|
// Don't call e.preventDefault() when the item uses 'href' property.
|
||||||
// ex. "Edit profile" on the account action bar
|
// ex. "Edit profile" on the account action bar
|
||||||
|
|
||||||
@ -48,10 +60,32 @@ export default class DropdownMenu extends React.PureComponent {
|
|||||||
this.dropdown.hide();
|
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 })
|
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) => {
|
renderItem = (item, i) => {
|
||||||
if (item === null) {
|
if (item === null) {
|
||||||
return <li key={`sep-${i}`} className='dropdown__sep' />;
|
return <li key={`sep-${i}`} className='dropdown__sep' />;
|
||||||
@ -61,7 +95,7 @@ export default class DropdownMenu extends React.PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li className='dropdown__content-list-item' key={`${text}-${i}`}>
|
<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}
|
{text}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -71,6 +105,7 @@ export default class DropdownMenu extends React.PureComponent {
|
|||||||
render () {
|
render () {
|
||||||
const { icon, items, size, direction, ariaLabel, disabled } = this.props;
|
const { icon, items, size, direction, ariaLabel, disabled } = this.props;
|
||||||
const { expanded } = this.state;
|
const { expanded } = this.state;
|
||||||
|
const isUserTouching = this.props.isUserTouching();
|
||||||
const directionClass = (direction === 'left') ? 'dropdown__left' : 'dropdown__right';
|
const directionClass = (direction === 'left') ? 'dropdown__left' : 'dropdown__right';
|
||||||
const iconStyle = { fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` };
|
const iconStyle = { fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` };
|
||||||
const iconClassname = `fa fa-fw fa-${icon} dropdown__icon`;
|
const iconClassname = `fa fa-fw fa-${icon} dropdown__icon`;
|
||||||
@ -84,20 +119,26 @@ export default class DropdownMenu extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dropdownItems = expanded && (
|
const dropdownItems = expanded && (
|
||||||
<ul className='dropdown__content-list'>
|
<ul role='group' className='dropdown__content-list' onClick={this.handleHide}>
|
||||||
{items.map(this.renderItem)}
|
{items.map(this.renderItem)}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
// No need to render the actual dropdown if we use the modal. If we
|
||||||
<Dropdown ref={this.setRef} onShow={this.handleShow} onHide={this.handleHide}>
|
// don't render anything <Dropdow /> breaks, so we just put an empty div.
|
||||||
<DropdownTrigger className='icon-button' style={iconStyle} aria-label={ariaLabel}>
|
const dropdownContent = !isUserTouching ? (
|
||||||
<i className={iconClassname} aria-hidden />
|
|
||||||
</DropdownTrigger>
|
|
||||||
|
|
||||||
<DropdownContent className={directionClass} >
|
<DropdownContent className={directionClass} >
|
||||||
{dropdownItems}
|
{dropdownItems}
|
||||||
</DropdownContent>
|
</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-pressed={expanded} onKeyDown={this.handleToggle} tabIndex='0' aria-label={ariaLabel}>
|
||||||
|
<i className={iconClassname} aria-hidden />
|
||||||
|
</DropdownTrigger>
|
||||||
|
|
||||||
|
{dropdownContent}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -12,12 +12,14 @@ export default class IconButton extends React.PureComponent {
|
|||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
|
pressed: PropTypes.bool,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
activeStyle: PropTypes.object,
|
activeStyle: PropTypes.object,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
inverted: PropTypes.bool,
|
inverted: PropTypes.bool,
|
||||||
animate: PropTypes.bool,
|
animate: PropTypes.bool,
|
||||||
overlay: PropTypes.bool,
|
overlay: PropTypes.bool,
|
||||||
|
tabIndex: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -26,6 +28,7 @@ export default class IconButton extends React.PureComponent {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
animate: false,
|
animate: false,
|
||||||
overlay: false,
|
overlay: false,
|
||||||
|
tabIndex: '0',
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClick = (e) => {
|
handleClick = (e) => {
|
||||||
@ -73,10 +76,12 @@ export default class IconButton extends React.PureComponent {
|
|||||||
{({ rotate }) =>
|
{({ rotate }) =>
|
||||||
<button
|
<button
|
||||||
aria-label={this.props.title}
|
aria-label={this.props.title}
|
||||||
|
aria-pressed={this.props.pressed}
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
className={classes.join(' ')}
|
className={classes.join(' ')}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
style={style}
|
style={style}
|
||||||
|
tabIndex={this.props.tabIndex}
|
||||||
>
|
>
|
||||||
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
|
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
|
||||||
</button>
|
</button>
|
||||||
|
@ -19,12 +19,15 @@ export default class SettingText extends React.PureComponent {
|
|||||||
const { settings, settingKey, label } = this.props;
|
const { settings, settingKey, label } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<label>
|
||||||
|
<span style={{ display: 'none' }}>{label}</span>
|
||||||
<input
|
<input
|
||||||
className='setting-text'
|
className='setting-text'
|
||||||
value={settings.getIn(settingKey)}
|
value={settings.getIn(settingKey)}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
placeholder={label}
|
placeholder={label}
|
||||||
/>
|
/>
|
||||||
|
</label>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
autoPlayGif: PropTypes.bool,
|
autoPlayGif: PropTypes.bool,
|
||||||
muted: PropTypes.bool,
|
muted: PropTypes.bool,
|
||||||
intersectionObserverWrapper: PropTypes.object,
|
intersectionObserverWrapper: PropTypes.object,
|
||||||
|
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -59,6 +61,7 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
'boostModal',
|
'boostModal',
|
||||||
'autoPlayGif',
|
'autoPlayGif',
|
||||||
'muted',
|
'muted',
|
||||||
|
'listLength',
|
||||||
]
|
]
|
||||||
|
|
||||||
updateOnStates = ['isExpanded']
|
updateOnStates = ['isExpanded']
|
||||||
@ -67,8 +70,8 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
if (!nextState.isIntersecting && nextState.isHidden) {
|
if (!nextState.isIntersecting && nextState.isHidden) {
|
||||||
// It's only if we're not intersecting (i.e. offscreen) and isHidden is true
|
// 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
|
// that either "isIntersecting" or "isHidden" matter, and then they're
|
||||||
// the only things that matter.
|
// the only things that matter (and updated ARIA attributes).
|
||||||
return this.state.isIntersecting || !this.state.isHidden;
|
return this.state.isIntersecting || !this.state.isHidden || nextProps.listLength !== this.props.listLength;
|
||||||
} else if (nextState.isIntersecting && !this.state.isIntersecting) {
|
} else if (nextState.isIntersecting && !this.state.isIntersecting) {
|
||||||
// If we're going from a non-intersecting state to an intersecting state,
|
// 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
|
// (i.e. offscreen to onscreen), then we definitely need to re-render
|
||||||
@ -107,17 +110,12 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
this.height = getRectFromEntry(entry).height;
|
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) => {
|
this.setState((prevState) => {
|
||||||
if (prevState.isIntersecting && !isIntersecting) {
|
if (prevState.isIntersecting && !entry.isIntersecting) {
|
||||||
scheduleIdleTask(this.hideIfNotIntersecting);
|
scheduleIdleTask(this.hideIfNotIntersecting);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
isIntersecting: isIntersecting,
|
isIntersecting: entry.isIntersecting,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -174,7 +172,7 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
|
|
||||||
// Exclude intersectionObserverWrapper from `other` variable
|
// Exclude intersectionObserverWrapper from `other` variable
|
||||||
// because intersection is managed in here.
|
// because intersection is managed in here.
|
||||||
const { status, account, intersectionObserverWrapper, ...other } = this.props;
|
const { status, account, intersectionObserverWrapper, index, listLength, ...other } = this.props;
|
||||||
const { isExpanded, isIntersecting, isHidden } = this.state;
|
const { isExpanded, isIntersecting, isHidden } = this.state;
|
||||||
|
|
||||||
if (status === null) {
|
if (status === null) {
|
||||||
@ -183,10 +181,10 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
|
|
||||||
if (!isIntersecting && isHidden) {
|
if (!isIntersecting && isHidden) {
|
||||||
return (
|
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.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
|
||||||
{status.get('content')}
|
{status.get('content')}
|
||||||
</div>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,14 +198,14 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
|
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
|
||||||
|
|
||||||
return (
|
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'>
|
||||||
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
|
<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> }} />
|
<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>
|
</div>
|
||||||
|
|
||||||
<Status {...other} wrapped status={status.get('reblog')} account={status.get('account')} />
|
<Status {...other} wrapped status={status.get('reblog')} account={status.get('account')} />
|
||||||
</div>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,7 +234,7 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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='0' ref={this.handleRef}>
|
||||||
<div className='status__info'>
|
<div className='status__info'>
|
||||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
||||||
|
|
||||||
@ -254,7 +252,7 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
{media}
|
{media}
|
||||||
|
|
||||||
<StatusActionBar {...this.props} />
|
<StatusActionBar {...this.props} />
|
||||||
</div>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import IconButton from './icon_button';
|
import IconButton from './icon_button';
|
||||||
import DropdownMenu from './dropdown_menu';
|
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
|||||||
{shareButton}
|
{shareButton}
|
||||||
|
|
||||||
<div className='status__action-bar-dropdown'>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -104,6 +104,32 @@ export default class StatusList extends ImmutablePureComponent {
|
|||||||
this.props.onScrollToBottom();
|
this.props.onScrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleKeyDown = (e) => {
|
||||||
|
if (['PageDown', 'PageUp', 'End', 'Home'].includes(e.key)) {
|
||||||
|
const article = (() => {
|
||||||
|
switch (e.key) {
|
||||||
|
case 'PageDown':
|
||||||
|
return e.nativeEvent.path[0].nodeName === 'ARTICLE' && e.nativeEvent.path[0].nextElementSibling;
|
||||||
|
case 'PageUp':
|
||||||
|
return e.nativeEvent.path[0].nodeName === 'ARTICLE' && e.nativeEvent.path[0].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 () {
|
render () {
|
||||||
const { statusIds, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
|
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) {
|
if (isLoading || statusIds.size > 0 || !emptyMessage) {
|
||||||
scrollableArea = (
|
scrollableArea = (
|
||||||
<div className='scrollable' ref={this.setRef}>
|
<div className='scrollable' ref={this.setRef}>
|
||||||
<div className='status-list'>
|
<div role='feed' className='status-list' onKeyDown={this.handleKeyDown}>
|
||||||
{prepend}
|
{prepend}
|
||||||
|
|
||||||
{statusIds.map((statusId) => {
|
{statusIds.map((statusId, index) => {
|
||||||
return <StatusContainer key={statusId} id={statusId} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
|
return <StatusContainer key={statusId} id={statusId} index={index} listLength={statusIds.size} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{loadMore}
|
{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);
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
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 Link from 'react-router-dom/Link';
|
||||||
import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ const messages = defineMessages({
|
|||||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||||
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
||||||
|
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
||||||
media: { id: 'account.media', defaultMessage: 'Media' },
|
media: { id: 'account.media', defaultMessage: 'Media' },
|
||||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
||||||
@ -36,6 +37,12 @@ export default class ActionBar extends React.PureComponent {
|
|||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleShare = () => {
|
||||||
|
navigator.share({
|
||||||
|
url: this.props.account.get('url'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, me, intl } = this.props;
|
const { account, me, intl } = this.props;
|
||||||
|
|
||||||
@ -43,6 +50,9 @@ export default class ActionBar extends React.PureComponent {
|
|||||||
let extraInfo = '';
|
let extraInfo = '';
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
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(null);
|
||||||
menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` });
|
menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` });
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
@ -96,7 +106,7 @@ export default class ActionBar extends React.PureComponent {
|
|||||||
|
|
||||||
<div className='account__action-bar'>
|
<div className='account__action-bar'>
|
||||||
<div className='account__action-bar-dropdown'>
|
<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>
|
||||||
|
|
||||||
<div className='account__action-bar-links'>
|
<div className='account__action-bar-links'>
|
||||||
|
@ -52,9 +52,10 @@ class Avatar extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}>
|
<Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}>
|
||||||
{({ radius }) =>
|
{({ radius }) =>
|
||||||
<a // eslint-disable-line jsx-a11y/anchor-has-content
|
<a
|
||||||
href={account.get('url')}
|
href={account.get('url')}
|
||||||
className='account__header__avatar'
|
className='account__header__avatar'
|
||||||
|
role='presentation'
|
||||||
target='_blank'
|
target='_blank'
|
||||||
rel='noopener'
|
rel='noopener'
|
||||||
style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }}
|
style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }}
|
||||||
@ -62,7 +63,9 @@ class Avatar extends ImmutablePureComponent {
|
|||||||
onMouseOut={this.handleMouseOut}
|
onMouseOut={this.handleMouseOut}
|
||||||
onFocus={this.handleMouseOver}
|
onFocus={this.handleMouseOver}
|
||||||
onBlur={this.handleMouseOut}
|
onBlur={this.handleMouseOut}
|
||||||
/>
|
>
|
||||||
|
<span style={{ display: 'none' }}>{account.get('acct')}</span>
|
||||||
|
</a>
|
||||||
}
|
}
|
||||||
</Motion>
|
</Motion>
|
||||||
);
|
);
|
||||||
|
@ -159,7 +159,10 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||||||
<div className='compose-form'>
|
<div className='compose-form'>
|
||||||
<Collapsable isVisible={this.props.spoiler} fullHeight={50}>
|
<Collapsable isVisible={this.props.spoiler} fullHeight={50}>
|
||||||
<div className='spoiler-input'>
|
<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' />
|
<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>
|
</div>
|
||||||
</Collapsable>
|
</Collapsable>
|
||||||
|
|
||||||
|
@ -65,6 +65,22 @@ export default class EmojiPickerDropdown extends React.PureComponent {
|
|||||||
this.setState({ active: false });
|
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 () {
|
render () {
|
||||||
const { intl } = this.props;
|
const { intl } = this.props;
|
||||||
|
|
||||||
@ -104,10 +120,11 @@ export default class EmojiPickerDropdown extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { active, loading } = this.state;
|
const { active, loading } = this.state;
|
||||||
|
const title = intl.formatMessage(messages.emoji);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown ref={this.setRef} className='emoji-picker__dropdown' onShow={this.onShowDropdown} onHide={this.onHideDropdown}>
|
<Dropdown ref={this.setRef} className='emoji-picker__dropdown' active={active && !loading} onShow={this.onShowDropdown} onHide={this.onHideDropdown}>
|
||||||
<DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)}>
|
<DropdownTrigger className='emoji-button' title={title} aria-label={title} aria-pressed={active} role='button' onKeyDown={this.onToggle} tabIndex={0} >
|
||||||
<img
|
<img
|
||||||
className={`emojione ${active && loading ? 'pulse-loading' : ''}`}
|
className={`emojione ${active && loading ? 'pulse-loading' : ''}`}
|
||||||
alt='🙂'
|
alt='🙂'
|
||||||
@ -118,7 +135,7 @@ export default class EmojiPickerDropdown extends React.PureComponent {
|
|||||||
<DropdownContent className='dropdown__left'>
|
<DropdownContent className='dropdown__left'>
|
||||||
{
|
{
|
||||||
this.state.active && !this.state.loading &&
|
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>
|
</DropdownContent>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
@ -18,6 +18,7 @@ export default class NavigationBar extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className='navigation-bar'>
|
<div className='navigation-bar'>
|
||||||
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
|
<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')} animate size={40} />
|
<Avatar src={this.props.account.get('avatar')} animate size={40} />
|
||||||
</Permalink>
|
</Permalink>
|
||||||
|
|
||||||
|
@ -24,6 +24,10 @@ const iconStyle = {
|
|||||||
export default class PrivacyDropdown extends React.PureComponent {
|
export default class PrivacyDropdown extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
isUserTouching: PropTypes.func,
|
||||||
|
isModalOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalOpen: PropTypes.func,
|
||||||
|
onModalClose: PropTypes.func,
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
@ -34,15 +38,37 @@ export default class PrivacyDropdown extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
handleToggle = () => {
|
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 });
|
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) => {
|
handleClick = (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
this.setState({ open: false });
|
||||||
|
} else if (!e.key || e.key === 'Enter') {
|
||||||
const value = e.currentTarget.getAttribute('data-index');
|
const value = e.currentTarget.getAttribute('data-index');
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.setState({ open: false });
|
this.setState({ open: false });
|
||||||
this.props.onChange(value);
|
this.props.onChange(value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onGlobalClick = (e) => {
|
onGlobalClick = (e) => {
|
||||||
if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
|
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 () {
|
componentDidMount () {
|
||||||
window.addEventListener('click', this.onGlobalClick);
|
window.addEventListener('click', this.onGlobalClick);
|
||||||
window.addEventListener('touchstart', this.onGlobalClick);
|
window.addEventListener('touchstart', this.onGlobalClick);
|
||||||
@ -68,25 +105,18 @@ export default class PrivacyDropdown extends React.PureComponent {
|
|||||||
const { value, intl } = this.props;
|
const { value, intl } = this.props;
|
||||||
const { open } = this.state;
|
const { open } = this.state;
|
||||||
|
|
||||||
const options = [
|
const valueOption = this.options.find(item => item.value === value);
|
||||||
{ 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);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}>
|
<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} pressed={open} active={open} inverted onClick={this.handleToggle} style={iconStyle} /></div>
|
||||||
<div className='privacy-dropdown__dropdown'>
|
<div className='privacy-dropdown__dropdown'>
|
||||||
{open && options.map(item =>
|
{open && this.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' : ''}`}>
|
<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__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div>
|
||||||
<div className='privacy-dropdown__option__content'>
|
<div className='privacy-dropdown__option__content'>
|
||||||
<strong>{item.shortText}</strong>
|
<strong>{item.text}</strong>
|
||||||
{item.longText}
|
{item.meta}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -52,6 +52,8 @@ export default class Search extends React.PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='search'>
|
<div className='search'>
|
||||||
|
<label>
|
||||||
|
<span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span>
|
||||||
<input
|
<input
|
||||||
className='search__input'
|
className='search__input'
|
||||||
type='text'
|
type='text'
|
||||||
@ -61,6 +63,7 @@ export default class Search extends React.PureComponent {
|
|||||||
onKeyUp={this.handleKeyDown}
|
onKeyUp={this.handleKeyDown}
|
||||||
onFocus={this.handleFocus}
|
onFocus={this.handleFocus}
|
||||||
/>
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
|
<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
|
||||||
<i className={`fa fa-search ${hasValue ? '' : 'active'}`} />
|
<i className={`fa fa-search ${hasValue ? '' : 'active'}`} />
|
||||||
|
@ -57,6 +57,8 @@ export default class UploadButton extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className='compose-form__upload-button'>
|
<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} />
|
<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
|
<input
|
||||||
key={resetFileKey}
|
key={resetFileKey}
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
@ -67,6 +69,7 @@ export default class UploadButton extends ImmutablePureComponent {
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
/>
|
/>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PrivacyDropdown from '../components/privacy_dropdown';
|
import PrivacyDropdown from '../components/privacy_dropdown';
|
||||||
import { changeComposeVisibility } from '../../../actions/compose';
|
import { changeComposeVisibility } from '../../../actions/compose';
|
||||||
|
import { openModal, closeModal } from '../../../actions/modal';
|
||||||
|
import { isUserTouching } from '../../../is_mobile';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
isModalOpen: state.get('modal').modalType === 'ACTIONS',
|
||||||
value: state.getIn(['compose', 'privacy']),
|
value: state.getIn(['compose', 'privacy']),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -12,6 +15,10 @@ const mapDispatchToProps = dispatch => ({
|
|||||||
dispatch(changeComposeVisibility(value));
|
dispatch(changeComposeVisibility(value));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isUserTouching,
|
||||||
|
onModalOpen: props => dispatch(openModal('ACTIONS', props)),
|
||||||
|
onModalClose: () => dispatch(closeModal()),
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);
|
export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);
|
||||||
|
@ -65,21 +65,21 @@ export default class Compose extends React.PureComponent {
|
|||||||
const { columns } = this.props;
|
const { columns } = this.props;
|
||||||
header = (
|
header = (
|
||||||
<nav className='drawer__header'>
|
<nav className='drawer__header'>
|
||||||
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)}><i role='img' className='fa fa-fw fa-asterisk' /></Link>
|
<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') && (
|
{!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' /></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') && (
|
{!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' /></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') && (
|
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
|
||||||
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role='img' 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') && (
|
{!columns.some(column => column.get('id') === 'PUBLIC') && (
|
||||||
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role='img' 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 href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)}><i role='img' className='fa fa-fw fa-cog' /></a>
|
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><i role='img' className='fa fa-fw fa-cog' /></a>
|
||||||
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)}><i role='img' className='fa fa-fw fa-sign-out' /></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>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -18,13 +18,19 @@ export default class SettingToggle extends React.PureComponent {
|
|||||||
this.props.onChange(this.props.settingKey, target.checked);
|
this.props.onChange(this.props.settingKey, target.checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onKeyDown = e => {
|
||||||
|
if (e.key === ' ') {
|
||||||
|
this.props.onChange(this.props.settingKey, !e.target.checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { prefix, settings, settingKey, label, meta } = this.props;
|
const { prefix, settings, settingKey, label, meta } = this.props;
|
||||||
const id = ['setting-toggle', prefix, ...settingKey].filter(Boolean).join('-');
|
const id = ['setting-toggle', prefix, ...settingKey].filter(Boolean).join('-');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='setting-toggle'>
|
<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>
|
<label htmlFor={id} className='setting-toggle__label'>{label}</label>
|
||||||
{meta && <span className='setting-meta__label'>{meta}</span>}
|
{meta && <span className='setting-meta__label'>{meta}</span>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import IconButton from '../../../components/icon_button';
|
import IconButton from '../../../components/icon_button';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
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';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@ -13,6 +13,7 @@ const messages = defineMessages({
|
|||||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||||
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
||||||
|
share: { id: 'status.share', defaultMessage: 'Share' },
|
||||||
});
|
});
|
||||||
|
|
||||||
@injectIntl
|
@injectIntl
|
||||||
@ -58,6 +59,13 @@ export default class ActionBar extends React.PureComponent {
|
|||||||
this.props.onReport(this.props.status);
|
this.props.onReport(this.props.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleShare = () => {
|
||||||
|
navigator.share({
|
||||||
|
text: this.props.status.get('search_index'),
|
||||||
|
url: this.props.status.get('url'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { status, me, intl } = this.props;
|
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 });
|
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';
|
let reblogIcon = 'retweet';
|
||||||
if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
|
if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
|
||||||
else if (status.get('visibility') === 'private') reblogIcon = 'lock';
|
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 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 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>
|
<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'>
|
<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>
|
||||||
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,6 +5,7 @@ import spring from 'react-motion/lib/spring';
|
|||||||
import BundleContainer from '../containers/bundle_container';
|
import BundleContainer from '../containers/bundle_container';
|
||||||
import BundleModalError from './bundle_modal_error';
|
import BundleModalError from './bundle_modal_error';
|
||||||
import ModalLoading from './modal_loading';
|
import ModalLoading from './modal_loading';
|
||||||
|
import ActionsModal from '../components/actions_modal';
|
||||||
import {
|
import {
|
||||||
MediaModal,
|
MediaModal,
|
||||||
OnboardingModal,
|
OnboardingModal,
|
||||||
@ -21,6 +22,7 @@ const MODAL_COMPONENTS = {
|
|||||||
'BOOST': BoostModal,
|
'BOOST': BoostModal,
|
||||||
'CONFIRM': ConfirmationModal,
|
'CONFIRM': ConfirmationModal,
|
||||||
'REPORT': ReportModal,
|
'REPORT': ReportModal,
|
||||||
|
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ModalRoot extends React.PureComponent {
|
export default class ModalRoot extends React.PureComponent {
|
||||||
@ -42,10 +44,34 @@ export default class ModalRoot extends React.PureComponent {
|
|||||||
window.addEventListener('keyup', this.handleKeyUp, false);
|
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.type && !!prevProps.type) {
|
||||||
|
this.getSiblings().forEach(sibling => sibling.removeAttribute('inert'));
|
||||||
|
this.activeElement.focus();
|
||||||
|
this.activeElement = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
window.removeEventListener('keyup', this.handleKeyUp);
|
window.removeEventListener('keyup', this.handleKeyUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSiblings = () => {
|
||||||
|
return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
setRef = ref => {
|
||||||
|
this.node = ref;
|
||||||
|
}
|
||||||
|
|
||||||
willEnter () {
|
willEnter () {
|
||||||
return { opacity: 0, scale: 0.98 };
|
return { opacity: 0, scale: 0.98 };
|
||||||
}
|
}
|
||||||
@ -84,11 +110,11 @@ export default class ModalRoot extends React.PureComponent {
|
|||||||
willLeave={this.willLeave}
|
willLeave={this.willLeave}
|
||||||
>
|
>
|
||||||
{interpolatedStyles =>
|
{interpolatedStyles =>
|
||||||
<div className='modal-root'>
|
<div className='modal-root' ref={this.setRef}>
|
||||||
{interpolatedStyles.map(({ key, data: { type, props }, style }) => (
|
{interpolatedStyles.map(({ key, data: { type, props }, style }) => (
|
||||||
<div key={key} style={{ pointerEvents: visible ? 'auto' : 'none' }}>
|
<div key={key} style={{ pointerEvents: visible ? 'auto' : 'none' }}>
|
||||||
<div role='presentation' className='modal-root__overlay' style={{ opacity: style.opacity }} onClick={onClose} />
|
<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}>
|
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading} error={this.renderError} renderDelay={200}>
|
||||||
{(SpecificComponent) => <SpecificComponent {...props} onClose={onClose} />}
|
{(SpecificComponent) => <SpecificComponent {...props} onClose={onClose} />}
|
||||||
</BundleContainer>
|
</BundleContainer>
|
||||||
|
@ -49,6 +49,10 @@ const mapStateToProps = state => ({
|
|||||||
@connect(mapStateToProps)
|
@connect(mapStateToProps)
|
||||||
export default class UI extends React.PureComponent {
|
export default class UI extends React.PureComponent {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
router: PropTypes.object.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
@ -123,6 +127,14 @@ export default class UI extends React.PureComponent {
|
|||||||
this.setState({ draggingOver: false });
|
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 () {
|
componentWillMount () {
|
||||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||||
document.addEventListener('dragenter', this.handleDragEnter, false);
|
document.addEventListener('dragenter', this.handleDragEnter, false);
|
||||||
@ -131,6 +143,10 @@ export default class UI extends React.PureComponent {
|
|||||||
document.addEventListener('dragleave', this.handleDragLeave, false);
|
document.addEventListener('dragleave', this.handleDragLeave, false);
|
||||||
document.addEventListener('dragend', this.handleDragEnd, false);
|
document.addEventListener('dragend', this.handleDragEnd, false);
|
||||||
|
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
|
||||||
|
}
|
||||||
|
|
||||||
this.props.dispatch(refreshHomeTimeline());
|
this.props.dispatch(refreshHomeTimeline());
|
||||||
this.props.dispatch(refreshNotifications());
|
this.props.dispatch(refreshNotifications());
|
||||||
}
|
}
|
||||||
|
@ -103,9 +103,9 @@ export function ReportModal () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function MediaGallery () {
|
export function MediaGallery () {
|
||||||
return import(/* webpackChunkName: "status/MediaGallery" */'../../../components/media_gallery');
|
return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoPlayer () {
|
export function VideoPlayer () {
|
||||||
return import(/* webpackChunkName: "status/VideoPlayer" */'../../../components/video_player');
|
return import(/* webpackChunkName: "status/video_player" */'../../../components/video_player');
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,15 @@ export function isMobile(width) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
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() {
|
export function isIOS() {
|
||||||
return iOS;
|
return iOS;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "المشاركات",
|
"account.posts": "المشاركات",
|
||||||
"account.report": "أبلغ عن @{name}",
|
"account.report": "أبلغ عن @{name}",
|
||||||
"account.requested": "في انتظار الموافقة",
|
"account.requested": "في انتظار الموافقة",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "إلغاء الحظر عن @{name}",
|
"account.unblock": "إلغاء الحظر عن @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "إلغاء المتابعة",
|
"account.unfollow": "إلغاء المتابعة",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "الخيط العام الموحد",
|
"column.public": "الخيط العام الموحد",
|
||||||
"column_back_button.label": "العودة",
|
"column_back_button.label": "العودة",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Публикации",
|
"account.posts": "Публикации",
|
||||||
"account.report": "Report @{name}",
|
"account.report": "Report @{name}",
|
||||||
"account.requested": "В очакване на одобрение",
|
"account.requested": "В очакване на одобрение",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Не блокирай",
|
"account.unblock": "Не блокирай",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Не следвай",
|
"account.unfollow": "Не следвай",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Публичен канал",
|
"column.public": "Публичен канал",
|
||||||
"column_back_button.label": "Назад",
|
"column_back_button.label": "Назад",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Publicacions",
|
"account.posts": "Publicacions",
|
||||||
"account.report": "Informe @{name}",
|
"account.report": "Informe @{name}",
|
||||||
"account.requested": "Esperant aprovació",
|
"account.requested": "Esperant aprovació",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Desbloquejar @{name}",
|
"account.unblock": "Desbloquejar @{name}",
|
||||||
"account.unblock_domain": "Mostra {domain}",
|
"account.unblock_domain": "Mostra {domain}",
|
||||||
"account.unfollow": "Deixar de seguir",
|
"account.unfollow": "Deixar de seguir",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Línia de temps federada",
|
"column.public": "Línia de temps federada",
|
||||||
"column_back_button.label": "Enrere",
|
"column_back_button.label": "Enrere",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Beiträge",
|
"account.posts": "Beiträge",
|
||||||
"account.report": "@{name} melden",
|
"account.report": "@{name} melden",
|
||||||
"account.requested": "Warte auf Erlaubnis",
|
"account.requested": "Warte auf Erlaubnis",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "@{name} entblocken",
|
"account.unblock": "@{name} entblocken",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Entfolgen",
|
"account.unfollow": "Entfolgen",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Gesamtes bekanntes Netz",
|
"column.public": "Gesamtes bekanntes Netz",
|
||||||
"column_back_button.label": "Zurück",
|
"column_back_button.label": "Zurück",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -61,6 +61,14 @@
|
|||||||
"defaultMessage": "Hide settings",
|
"defaultMessage": "Hide settings",
|
||||||
"id": "column_header.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",
|
"defaultMessage": "Unpin",
|
||||||
"id": "column_header.unpin"
|
"id": "column_header.unpin"
|
||||||
@ -366,6 +374,10 @@
|
|||||||
"defaultMessage": "Report @{name}",
|
"defaultMessage": "Report @{name}",
|
||||||
"id": "account.report"
|
"id": "account.report"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Share @{name}'s profile",
|
||||||
|
"id": "account.share"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Media",
|
"defaultMessage": "Media",
|
||||||
"id": "account.media"
|
"id": "account.media"
|
||||||
@ -1019,6 +1031,10 @@
|
|||||||
{
|
{
|
||||||
"defaultMessage": "Report @{name}",
|
"defaultMessage": "Report @{name}",
|
||||||
"id": "status.report"
|
"id": "status.report"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Share",
|
||||||
|
"id": "status.share"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"path": "app/javascript/mastodon/features/status/components/action_bar.json"
|
"path": "app/javascript/mastodon/features/status/components/action_bar.json"
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Posts",
|
"account.posts": "Posts",
|
||||||
"account.report": "Report @{name}",
|
"account.report": "Report @{name}",
|
||||||
"account.requested": "Awaiting approval",
|
"account.requested": "Awaiting approval",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Unblock @{name}",
|
"account.unblock": "Unblock @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Unfollow",
|
"account.unfollow": "Unfollow",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Federated timeline",
|
"column.public": "Federated timeline",
|
||||||
"column_back_button.label": "Back",
|
"column_back_button.label": "Back",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Mesaĝoj",
|
"account.posts": "Mesaĝoj",
|
||||||
"account.report": "Report @{name}",
|
"account.report": "Report @{name}",
|
||||||
"account.requested": "Atendas aprobon",
|
"account.requested": "Atendas aprobon",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Malbloki @{name}",
|
"account.unblock": "Malbloki @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Malsekvi",
|
"account.unfollow": "Malsekvi",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Fratara tempolinio",
|
"column.public": "Fratara tempolinio",
|
||||||
"column_back_button.label": "Reveni",
|
"column_back_button.label": "Reveni",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Publicaciones",
|
"account.posts": "Publicaciones",
|
||||||
"account.report": "Report @{name}",
|
"account.report": "Report @{name}",
|
||||||
"account.requested": "Esperando aprobación",
|
"account.requested": "Esperando aprobación",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Desbloquear",
|
"account.unblock": "Desbloquear",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Dejar de seguir",
|
"account.unfollow": "Dejar de seguir",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Historia federada",
|
"column.public": "Historia federada",
|
||||||
"column_back_button.label": "Atrás",
|
"column_back_button.label": "Atrás",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "نوشتهها",
|
"account.posts": "نوشتهها",
|
||||||
"account.report": "گزارش @{name}",
|
"account.report": "گزارش @{name}",
|
||||||
"account.requested": "در انتظار پذیرش",
|
"account.requested": "در انتظار پذیرش",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "رفع انسداد @{name}",
|
"account.unblock": "رفع انسداد @{name}",
|
||||||
"account.unblock_domain": "رفع پنهانسازی از {domain}",
|
"account.unblock_domain": "رفع پنهانسازی از {domain}",
|
||||||
"account.unfollow": "پایان پیگیری",
|
"account.unfollow": "پایان پیگیری",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "نوشتههای همهجا",
|
"column.public": "نوشتههای همهجا",
|
||||||
"column_back_button.label": "بازگشت",
|
"column_back_button.label": "بازگشت",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Postit",
|
"account.posts": "Postit",
|
||||||
"account.report": "Report @{name}",
|
"account.report": "Report @{name}",
|
||||||
"account.requested": "Odottaa hyväksyntää",
|
"account.requested": "Odottaa hyväksyntää",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Salli @{name}",
|
"account.unblock": "Salli @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Lopeta seuraaminen",
|
"account.unfollow": "Lopeta seuraaminen",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Yleinen aikajana",
|
"column.public": "Yleinen aikajana",
|
||||||
"column_back_button.label": "Takaisin",
|
"column_back_button.label": "Takaisin",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Statuts",
|
"account.posts": "Statuts",
|
||||||
"account.report": "Signaler",
|
"account.report": "Signaler",
|
||||||
"account.requested": "Invitation envoyée",
|
"account.requested": "Invitation envoyée",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Débloquer",
|
"account.unblock": "Débloquer",
|
||||||
"account.unblock_domain": "Ne plus masquer {domain}",
|
"account.unblock_domain": "Ne plus masquer {domain}",
|
||||||
"account.unfollow": "Ne plus suivre",
|
"account.unfollow": "Ne plus suivre",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Fil public global",
|
"column.public": "Fil public global",
|
||||||
"column_back_button.label": "Retour",
|
"column_back_button.label": "Retour",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Épingler",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Retirer",
|
"column_header.unpin": "Retirer",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "הודעות",
|
"account.posts": "הודעות",
|
||||||
"account.report": "לדווח על @{name}",
|
"account.report": "לדווח על @{name}",
|
||||||
"account.requested": "בהמתנה לאישור",
|
"account.requested": "בהמתנה לאישור",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "הסרת חסימה מעל @{name}",
|
"account.unblock": "הסרת חסימה מעל @{name}",
|
||||||
"account.unblock_domain": "הסר חסימה מקהילת {domain}",
|
"account.unblock_domain": "הסר חסימה מקהילת {domain}",
|
||||||
"account.unfollow": "הפסקת מעקב",
|
"account.unfollow": "הפסקת מעקב",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "בפרהסיה",
|
"column.public": "בפרהסיה",
|
||||||
"column_back_button.label": "חזרה",
|
"column_back_button.label": "חזרה",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Postovi",
|
"account.posts": "Postovi",
|
||||||
"account.report": "Prijavi @{name}",
|
"account.report": "Prijavi @{name}",
|
||||||
"account.requested": "Čeka pristanak",
|
"account.requested": "Čeka pristanak",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Deblokiraj @{name}",
|
"account.unblock": "Deblokiraj @{name}",
|
||||||
"account.unblock_domain": "Otkrij {domain}",
|
"account.unblock_domain": "Otkrij {domain}",
|
||||||
"account.unfollow": "Prestani slijediti",
|
"account.unfollow": "Prestani slijediti",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Federalni timeline",
|
"column.public": "Federalni timeline",
|
||||||
"column_back_button.label": "Natrag",
|
"column_back_button.label": "Natrag",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Posts",
|
"account.posts": "Posts",
|
||||||
"account.report": "Report @{name}",
|
"account.report": "Report @{name}",
|
||||||
"account.requested": "Awaiting approval",
|
"account.requested": "Awaiting approval",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Blokkolás levétele",
|
"account.unblock": "Blokkolás levétele",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Követés abbahagyása",
|
"account.unfollow": "Követés abbahagyása",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Nyilvános",
|
"column.public": "Nyilvános",
|
||||||
"column_back_button.label": "Vissza",
|
"column_back_button.label": "Vissza",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Postingan",
|
"account.posts": "Postingan",
|
||||||
"account.report": "Laporkan @{name}",
|
"account.report": "Laporkan @{name}",
|
||||||
"account.requested": "Menunggu persetujuan",
|
"account.requested": "Menunggu persetujuan",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Hapus blokir @{name}",
|
"account.unblock": "Hapus blokir @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Berhenti mengikuti",
|
"account.unfollow": "Berhenti mengikuti",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Linimasa gabunggan",
|
"column.public": "Linimasa gabunggan",
|
||||||
"column_back_button.label": "Kembali",
|
"column_back_button.label": "Kembali",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Mesaji",
|
"account.posts": "Mesaji",
|
||||||
"account.report": "Denuncar @{name}",
|
"account.report": "Denuncar @{name}",
|
||||||
"account.requested": "Vartante aprobo",
|
"account.requested": "Vartante aprobo",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Desblokusar @{name}",
|
"account.unblock": "Desblokusar @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Ne plus sequar",
|
"account.unfollow": "Ne plus sequar",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Federata tempolineo",
|
"column.public": "Federata tempolineo",
|
||||||
"column_back_button.label": "Retro",
|
"column_back_button.label": "Retro",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Posts",
|
"account.posts": "Posts",
|
||||||
"account.report": "Segnala @{name}",
|
"account.report": "Segnala @{name}",
|
||||||
"account.requested": "In attesa di approvazione",
|
"account.requested": "In attesa di approvazione",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Sblocca @{name}",
|
"account.unblock": "Sblocca @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Non seguire",
|
"account.unfollow": "Non seguire",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Timeline federata",
|
"column.public": "Timeline federata",
|
||||||
"column_back_button.label": "Indietro",
|
"column_back_button.label": "Indietro",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "投稿",
|
"account.posts": "投稿",
|
||||||
"account.report": "通報",
|
"account.report": "通報",
|
||||||
"account.requested": "承認待ち",
|
"account.requested": "承認待ち",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "ブロック解除",
|
"account.unblock": "ブロック解除",
|
||||||
"account.unblock_domain": "{domain}を表示",
|
"account.unblock_domain": "{domain}を表示",
|
||||||
"account.unfollow": "フォロー解除",
|
"account.unfollow": "フォロー解除",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "連合タイムライン",
|
"column.public": "連合タイムライン",
|
||||||
"column_back_button.label": "戻る",
|
"column_back_button.label": "戻る",
|
||||||
"column_header.hide_settings": "設定を隠す",
|
"column_header.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.pin": "ピン留めする",
|
||||||
"column_header.show_settings": "設定を表示",
|
"column_header.show_settings": "設定を表示",
|
||||||
"column_header.unpin": "ピン留めを外す",
|
"column_header.unpin": "ピン留めを外す",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "포스트",
|
"account.posts": "포스트",
|
||||||
"account.report": "신고",
|
"account.report": "신고",
|
||||||
"account.requested": "승인 대기 중",
|
"account.requested": "승인 대기 중",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "차단 해제",
|
"account.unblock": "차단 해제",
|
||||||
"account.unblock_domain": "{domain} 숨김 해제",
|
"account.unblock_domain": "{domain} 숨김 해제",
|
||||||
"account.unfollow": "팔로우 해제",
|
"account.unfollow": "팔로우 해제",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "연합 타임라인",
|
"column.public": "연합 타임라인",
|
||||||
"column_back_button.label": "돌아가기",
|
"column_back_button.label": "돌아가기",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "고정하기",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "고정 해제",
|
"column_header.unpin": "고정 해제",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Toots",
|
"account.posts": "Toots",
|
||||||
"account.report": "Rapporteer @{name}",
|
"account.report": "Rapporteer @{name}",
|
||||||
"account.requested": "Wacht op goedkeuring",
|
"account.requested": "Wacht op goedkeuring",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Deblokkeer @{name}",
|
"account.unblock": "Deblokkeer @{name}",
|
||||||
"account.unblock_domain": "{domain} niet meer negeren",
|
"account.unblock_domain": "{domain} niet meer negeren",
|
||||||
"account.unfollow": "Ontvolgen",
|
"account.unfollow": "Ontvolgen",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Globale tijdlijn",
|
"column.public": "Globale tijdlijn",
|
||||||
"column_back_button.label": "terug",
|
"column_back_button.label": "terug",
|
||||||
"column_header.hide_settings": "Instellingen verbergen",
|
"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.pin": "Vastmaken",
|
||||||
"column_header.show_settings": "Instellingen tonen",
|
"column_header.show_settings": "Instellingen tonen",
|
||||||
"column_header.unpin": "Losmaken",
|
"column_header.unpin": "Losmaken",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Innlegg",
|
"account.posts": "Innlegg",
|
||||||
"account.report": "Rapportér @{name}",
|
"account.report": "Rapportér @{name}",
|
||||||
"account.requested": "Venter på godkjennelse",
|
"account.requested": "Venter på godkjennelse",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Avblokker @{name}",
|
"account.unblock": "Avblokker @{name}",
|
||||||
"account.unblock_domain": "Vis {domain}",
|
"account.unblock_domain": "Vis {domain}",
|
||||||
"account.unfollow": "Avfølg",
|
"account.unfollow": "Avfølg",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Felles tidslinje",
|
"column.public": "Felles tidslinje",
|
||||||
"column_back_button.label": "Tilbake",
|
"column_back_button.label": "Tilbake",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"account.block": "Blocar @{name}",
|
"account.block": "Blocar @{name}",
|
||||||
"account.block_domain": "Tot amagar del domeni {domain}",
|
"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.edit_profile": "Modificar lo perfil",
|
||||||
"account.follow": "Sègre",
|
"account.follow": "Sègre",
|
||||||
"account.followers": "Seguidors",
|
"account.followers": "Seguidors",
|
||||||
@ -13,18 +13,19 @@
|
|||||||
"account.posts": "Estatuts",
|
"account.posts": "Estatuts",
|
||||||
"account.report": "Senhalar @{name}",
|
"account.report": "Senhalar @{name}",
|
||||||
"account.requested": "Invitacion mandada",
|
"account.requested": "Invitacion mandada",
|
||||||
|
"account.share": "Partejar lo perfil a @{name}",
|
||||||
"account.unblock": "Desblocar @{name}",
|
"account.unblock": "Desblocar @{name}",
|
||||||
"account.unblock_domain": "Desblocar {domain}",
|
"account.unblock_domain": "Desblocar {domain}",
|
||||||
"account.unfollow": "Quitar de sègre",
|
"account.unfollow": "Quitar de sègre",
|
||||||
"account.unmute": "Quitar de rescondre @{name}",
|
"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",
|
"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.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_column_error.title": "Error de ret",
|
||||||
"bundle_modal_error.close": "Tampar",
|
"bundle_modal_error.close": "Tampar",
|
||||||
"bundle_modal_error.message": "Quicòm a fach meuca pendent lo cargament d’aqueste compausant.",
|
"bundle_modal_error.message": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.",
|
||||||
"bundle_modal_error.retry": "Tornar ensejar",
|
"bundle_modal_error.retry": "Tornar ensajar",
|
||||||
"column.blocks": "Personas blocadas",
|
"column.blocks": "Personas blocadas",
|
||||||
"column.community": "Flux public local",
|
"column.community": "Flux public local",
|
||||||
"column.favourites": "Favorits",
|
"column.favourites": "Favorits",
|
||||||
@ -34,9 +35,11 @@
|
|||||||
"column.notifications": "Notificacions",
|
"column.notifications": "Notificacions",
|
||||||
"column.public": "Flux public global",
|
"column.public": "Flux public global",
|
||||||
"column_back_button.label": "Tornar",
|
"column_back_button.label": "Tornar",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Penjar",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Mostrar los paramètres",
|
||||||
"column_header.unpin": "Despenjar",
|
"column_header.unpin": "Despenjar",
|
||||||
"column_subheading.navigation": "Navigacion",
|
"column_subheading.navigation": "Navigacion",
|
||||||
"column_subheading.settings": "Paramètres",
|
"column_subheading.settings": "Paramètres",
|
||||||
@ -48,35 +51,35 @@
|
|||||||
"compose_form.publish_loud": "{publish} !",
|
"compose_form.publish_loud": "{publish} !",
|
||||||
"compose_form.sensitive": "Marcar lo mèdia coma sensible",
|
"compose_form.sensitive": "Marcar lo mèdia coma sensible",
|
||||||
"compose_form.spoiler": "Rescondre lo tèxte darrièr un avertiment",
|
"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",
|
"confirmation_modal.cancel": "Anullar",
|
||||||
"confirmations.block.confirm": "Blocar",
|
"confirmations.block.confirm": "Blocar",
|
||||||
"confirmations.block.message": "Sètz segur de voler blocar {name} ?",
|
"confirmations.block.message": "Sètz segur de voler blocar {name} ?",
|
||||||
"confirmations.delete.confirm": "Suprimir",
|
"confirmations.delete.confirm": "Suprimir",
|
||||||
"confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?",
|
"confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?",
|
||||||
"confirmations.domain_block.confirm": "Amagar tot lo domeni",
|
"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.confirm": "Metre en silenci",
|
||||||
"confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?",
|
"confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?",
|
||||||
"confirmations.unfollow.confirm": "Quitar de sègre",
|
"confirmations.unfollow.confirm": "Quitar de sègre",
|
||||||
"confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?",
|
"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.flags": "Drapèus",
|
||||||
"emoji_button.food": "Beure e manjar",
|
"emoji_button.food": "Beure e manjar",
|
||||||
"emoji_button.label": "Inserir un emoji",
|
"emoji_button.label": "Inserir un emoji",
|
||||||
"emoji_button.nature": "Natura",
|
"emoji_button.nature": "Natura",
|
||||||
"emoji_button.objects": "Objèctes",
|
"emoji_button.objects": "Objèctes",
|
||||||
"emoji_button.people": "Gents",
|
"emoji_button.people": "Gents",
|
||||||
"emoji_button.search": "Cercar...",
|
"emoji_button.search": "Cercar…",
|
||||||
"emoji_button.symbols": "Simbòls",
|
"emoji_button.symbols": "Simbòls",
|
||||||
"emoji_button.travel": "Viatges & lòcs",
|
"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.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.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.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.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.authorize": "Autorizar",
|
||||||
"follow_request.reject": "Regetar",
|
"follow_request.reject": "Regetar",
|
||||||
"getting_started.appsshort": "Apps",
|
"getting_started.appsshort": "Apps",
|
||||||
@ -105,11 +108,11 @@
|
|||||||
"navigation_bar.preferences": "Preferéncias",
|
"navigation_bar.preferences": "Preferéncias",
|
||||||
"navigation_bar.public_timeline": "Flux public global",
|
"navigation_bar.public_timeline": "Flux public global",
|
||||||
"notification.favourite": "{name} a ajustat a sos favorits :",
|
"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.mention": "{name} vos a mencionat :",
|
||||||
"notification.reblog": "{name} a partejat vòstre estatut :",
|
"notification.reblog": "{name} a partejat vòstre estatut :",
|
||||||
"notifications.clear": "Levar",
|
"notifications.clear": "Escafar",
|
||||||
"notifications.clear_confirmation": "Volètz vertadièrament levar totas vòstras las notificacions ?",
|
"notifications.clear_confirmation": "Volètz vertadièrament escafar totas vòstras las notificacions ?",
|
||||||
"notifications.column_settings.alert": "Notificacions localas",
|
"notifications.column_settings.alert": "Notificacions localas",
|
||||||
"notifications.column_settings.favourite": "Favorits :",
|
"notifications.column_settings.favourite": "Favorits :",
|
||||||
"notifications.column_settings.follow": "Nòus seguidors :",
|
"notifications.column_settings.follow": "Nòus seguidors :",
|
||||||
@ -121,15 +124,15 @@
|
|||||||
"notifications.column_settings.sound": "Emetre un son",
|
"notifications.column_settings.sound": "Emetre un son",
|
||||||
"onboarding.done": "Fach",
|
"onboarding.done": "Fach",
|
||||||
"onboarding.next": "Seguent",
|
"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.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.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.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}",
|
||||||
"onboarding.page_one.welcome": "Benvengut a Mastodon !",
|
"onboarding.page_one.welcome": "Benvengut a Mastodon !",
|
||||||
"onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.",
|
"onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.",
|
||||||
"onboarding.page_six.almost_done": "Gaireben acabat…",
|
"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.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.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",
|
"onboarding.page_six.guidelines": "guida de la comunitat",
|
||||||
@ -170,7 +173,7 @@
|
|||||||
"status.report": "Senhalar @{name}",
|
"status.report": "Senhalar @{name}",
|
||||||
"status.sensitive_toggle": "Clicar per mostrar",
|
"status.sensitive_toggle": "Clicar per mostrar",
|
||||||
"status.sensitive_warning": "Contengut sensible",
|
"status.sensitive_warning": "Contengut sensible",
|
||||||
"status.share": "Share",
|
"status.share": "Partejar",
|
||||||
"status.show_less": "Tornar plegar",
|
"status.show_less": "Tornar plegar",
|
||||||
"status.show_more": "Desplegar",
|
"status.show_more": "Desplegar",
|
||||||
"status.unmute_conversation": "Conversacions amb silenci levat",
|
"status.unmute_conversation": "Conversacions amb silenci levat",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Posty",
|
"account.posts": "Posty",
|
||||||
"account.report": "Zgłoś @{name}",
|
"account.report": "Zgłoś @{name}",
|
||||||
"account.requested": "Oczekująca prośba",
|
"account.requested": "Oczekująca prośba",
|
||||||
|
"account.share": "Udostępnij profil @{name}",
|
||||||
"account.unblock": "Odblokuj @{name}",
|
"account.unblock": "Odblokuj @{name}",
|
||||||
"account.unblock_domain": "Odblokuj domenę {domain}",
|
"account.unblock_domain": "Odblokuj domenę {domain}",
|
||||||
"account.unfollow": "Przestań śledzić",
|
"account.unfollow": "Przestań śledzić",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Globalna oś czasu",
|
"column.public": "Globalna oś czasu",
|
||||||
"column_back_button.label": "Wróć",
|
"column_back_button.label": "Wróć",
|
||||||
"column_header.hide_settings": "Ukryj ustawienia",
|
"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.pin": "Przypnij",
|
||||||
"column_header.show_settings": "Pokaż ustawienia",
|
"column_header.show_settings": "Pokaż ustawienia",
|
||||||
"column_header.unpin": "Cofnij przypięcie",
|
"column_header.unpin": "Cofnij przypięcie",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Posts",
|
"account.posts": "Posts",
|
||||||
"account.report": "Denunciar @{name}",
|
"account.report": "Denunciar @{name}",
|
||||||
"account.requested": "A aguardar aprovação",
|
"account.requested": "A aguardar aprovação",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Não bloquear @{name}",
|
"account.unblock": "Não bloquear @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Deixar de seguir",
|
"account.unfollow": "Deixar de seguir",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Global",
|
"column.public": "Global",
|
||||||
"column_back_button.label": "Voltar",
|
"column_back_button.label": "Voltar",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Posts",
|
"account.posts": "Posts",
|
||||||
"account.report": "Denunciar @{name}",
|
"account.report": "Denunciar @{name}",
|
||||||
"account.requested": "A aguardar aprovação",
|
"account.requested": "A aguardar aprovação",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Não bloquear @{name}",
|
"account.unblock": "Não bloquear @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Deixar de seguir",
|
"account.unfollow": "Deixar de seguir",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Global",
|
"column.public": "Global",
|
||||||
"column_back_button.label": "Voltar",
|
"column_back_button.label": "Voltar",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Посты",
|
"account.posts": "Посты",
|
||||||
"account.report": "Пожаловаться",
|
"account.report": "Пожаловаться",
|
||||||
"account.requested": "Ожидает подтверждения",
|
"account.requested": "Ожидает подтверждения",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Разблокировать",
|
"account.unblock": "Разблокировать",
|
||||||
"account.unblock_domain": "Разблокировать {domain}",
|
"account.unblock_domain": "Разблокировать {domain}",
|
||||||
"account.unfollow": "Отписаться",
|
"account.unfollow": "Отписаться",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Глобальная лента",
|
"column.public": "Глобальная лента",
|
||||||
"column_back_button.label": "Назад",
|
"column_back_button.label": "Назад",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Закрепить",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Открепить",
|
"column_header.unpin": "Открепить",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Posts",
|
"account.posts": "Posts",
|
||||||
"account.report": "Report @{name}",
|
"account.report": "Report @{name}",
|
||||||
"account.requested": "Awaiting approval",
|
"account.requested": "Awaiting approval",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Unblock @{name}",
|
"account.unblock": "Unblock @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Unfollow",
|
"account.unfollow": "Unfollow",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Federated timeline",
|
"column.public": "Federated timeline",
|
||||||
"column_back_button.label": "Back",
|
"column_back_button.label": "Back",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Gönderiler",
|
"account.posts": "Gönderiler",
|
||||||
"account.report": "Rapor et @{name}",
|
"account.report": "Rapor et @{name}",
|
||||||
"account.requested": "Onay bekleniyor",
|
"account.requested": "Onay bekleniyor",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Engeli kaldır @{name}",
|
"account.unblock": "Engeli kaldır @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "Takipten vazgeç",
|
"account.unfollow": "Takipten vazgeç",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Federe zaman tüneli",
|
"column.public": "Federe zaman tüneli",
|
||||||
"column_back_button.label": "Geri",
|
"column_back_button.label": "Geri",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "Пости",
|
"account.posts": "Пости",
|
||||||
"account.report": "Поскаржитися",
|
"account.report": "Поскаржитися",
|
||||||
"account.requested": "Очікує підтвердження",
|
"account.requested": "Очікує підтвердження",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Розблокувати",
|
"account.unblock": "Розблокувати",
|
||||||
"account.unblock_domain": "Розблокувати {domain}",
|
"account.unblock_domain": "Розблокувати {domain}",
|
||||||
"account.unfollow": "Відписатися",
|
"account.unfollow": "Відписатися",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "Глобальна стрічка",
|
"column.public": "Глобальна стрічка",
|
||||||
"column_back_button.label": "Назад",
|
"column_back_button.label": "Назад",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "嘟文",
|
"account.posts": "嘟文",
|
||||||
"account.report": "举报 @{name}",
|
"account.report": "举报 @{name}",
|
||||||
"account.requested": "等待审批",
|
"account.requested": "等待审批",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "解除对 @{name} 的屏蔽",
|
"account.unblock": "解除对 @{name} 的屏蔽",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "取消关注",
|
"account.unfollow": "取消关注",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "跨站公共时间轴",
|
"column.public": "跨站公共时间轴",
|
||||||
"column_back_button.label": "Back",
|
"column_back_button.label": "Back",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "文章",
|
"account.posts": "文章",
|
||||||
"account.report": "舉報 @{name}",
|
"account.report": "舉報 @{name}",
|
||||||
"account.requested": "等候審批",
|
"account.requested": "等候審批",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "解除對 @{name} 的封鎖",
|
"account.unblock": "解除對 @{name} 的封鎖",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
"account.unfollow": "取消關注",
|
"account.unfollow": "取消關注",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "跨站時間軸",
|
"column.public": "跨站時間軸",
|
||||||
"column_back_button.label": "返回",
|
"column_back_button.label": "返回",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account.posts": "貼文",
|
"account.posts": "貼文",
|
||||||
"account.report": "檢舉 @{name}",
|
"account.report": "檢舉 @{name}",
|
||||||
"account.requested": "正在等待許可",
|
"account.requested": "正在等待許可",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "取消封鎖 @{name}",
|
"account.unblock": "取消封鎖 @{name}",
|
||||||
"account.unblock_domain": "不再隱藏 {domain}",
|
"account.unblock_domain": "不再隱藏 {domain}",
|
||||||
"account.unfollow": "取消關注",
|
"account.unfollow": "取消關注",
|
||||||
@ -35,6 +36,8 @@
|
|||||||
"column.public": "聯盟時間軸",
|
"column.public": "聯盟時間軸",
|
||||||
"column_back_button.label": "上一頁",
|
"column_back_button.label": "上一頁",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"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.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
@ -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(notifications[0].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: notifications[0].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 handlePush = (event) => {
|
||||||
const options = event.data.json();
|
const options = event.data.json();
|
||||||
|
|
||||||
@ -17,7 +59,7 @@ const handlePush = (event) => {
|
|||||||
options.actions = options.data.actions;
|
options.actions = options.data.actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.waitUntil(self.registration.showNotification(options.title, options));
|
event.waitUntil(notify(options));
|
||||||
};
|
};
|
||||||
|
|
||||||
const cloneNotification = (notification) => {
|
const cloneNotification = (notification) => {
|
||||||
@ -50,22 +92,37 @@ const makeRequest = (notification, action) =>
|
|||||||
credentials: 'include',
|
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 =>
|
const openUrl = url =>
|
||||||
self.clients.matchAll({ type: 'window' }).then(clientList => {
|
self.clients.matchAll({ type: 'window' }).then(clientList => {
|
||||||
if (clientList.length !== 0 && 'navigate' in clientList[0]) { // Chrome 42-48 does not support navigate
|
if (clientList.length !== 0) {
|
||||||
const webClients = clientList
|
const webClients = clientList.filter(client => /\/web\//.test(client.url));
|
||||||
.filter(client => /\/web\//.test(client.url))
|
|
||||||
.sort(client => client !== 'visible');
|
|
||||||
|
|
||||||
const visibleClient = clientList.find(client => client.visibilityState === 'visible');
|
if (webClients.length !== 0) {
|
||||||
const focusedClient = clientList.find(client => client.focused);
|
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());
|
return client.navigate(url).then(client => client.focus());
|
||||||
} else {
|
|
||||||
return self.clients.openWindow(url);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.clients.openWindow(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
const removeActionFromNotification = (notification, action) => {
|
const removeActionFromNotification = (notification, action) => {
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
|
|
||||||
.information-board {
|
.information-board {
|
||||||
background: darken($ui-base-color, 4%);
|
background: darken($ui-base-color, 4%);
|
||||||
padding: 40px 0;
|
padding: 20px 0;
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -162,13 +162,14 @@
|
|||||||
.information-board-sections {
|
.information-board-sections {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0;
|
||||||
font: 16px/28px 'mastodon-font-sans-serif', sans-serif;
|
font: 16px/28px 'mastodon-font-sans-serif', sans-serif;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding: 0 15px;
|
padding: 10px 15px;
|
||||||
|
|
||||||
span,
|
span,
|
||||||
strong {
|
strong {
|
||||||
@ -190,14 +191,6 @@
|
|||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.section {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.owner {
|
.owner {
|
||||||
@ -547,6 +540,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#mastodon-timeline {
|
#mastodon-timeline {
|
||||||
|
display: flex;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||||
font-family: 'mastodon-font-sans-serif', sans-serif;
|
font-family: 'mastodon-font-sans-serif', sans-serif;
|
||||||
@ -561,11 +555,20 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 0 6px rgba($black, 0.1);
|
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 {
|
.column {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollable {
|
.scrollable {
|
||||||
@ -651,16 +654,12 @@
|
|||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.information-board {
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.information-board .container {
|
.information-board .container {
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
position: static;
|
position: static;
|
||||||
margin-top: 30px;
|
margin-top: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
|
@ -214,6 +214,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropdown--active::after {
|
.dropdown--active::after {
|
||||||
|
@media screen and (min-width: 1025px) {
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -225,6 +226,7 @@
|
|||||||
bottom: 8px;
|
bottom: 8px;
|
||||||
right: 104px;
|
right: 104px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.invisible {
|
.invisible {
|
||||||
font-size: 0;
|
font-size: 0;
|
||||||
@ -3402,7 +3404,8 @@ button.icon-button.active i.fa-retweet {
|
|||||||
|
|
||||||
.boost-modal,
|
.boost-modal,
|
||||||
.confirmation-modal,
|
.confirmation-modal,
|
||||||
.report-modal {
|
.report-modal,
|
||||||
|
.actions-modal {
|
||||||
background: lighten($ui-secondary-color, 8%);
|
background: lighten($ui-secondary-color, 8%);
|
||||||
color: $ui-base-color;
|
color: $ui-base-color;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -3427,6 +3430,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 {
|
.boost-modal__container {
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -3493,6 +3505,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__action-bar {
|
||||||
.confirmation-modal__cancel-button {
|
.confirmation-modal__cancel-button {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
@ -53,6 +53,7 @@ class Web::PushSubscription < ApplicationRecord
|
|||||||
url: url,
|
url: url,
|
||||||
actions: actions,
|
actions: actions,
|
||||||
access_token: access_token,
|
access_token: access_token,
|
||||||
|
message: translate('push_notifications.group.title'), # Do not pass count, will be formatted in the ServiceWorker
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
@ -117,7 +118,7 @@ class Web::PushSubscription < ApplicationRecord
|
|||||||
when :mention then [
|
when :mention then [
|
||||||
{
|
{
|
||||||
title: translate('push_notifications.mention.action_favourite'),
|
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',
|
todo: 'request',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
action: "/api/v1/statuses/#{notification.target_status.id}/favourite",
|
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?
|
can_boost = notification.type.equal?(:mention) && !notification.target_status.nil? && !notification.target_status.hidden?
|
||||||
|
|
||||||
if should_hide
|
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
|
end
|
||||||
|
|
||||||
if can_boost
|
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_boost.png', skip_pipeline: true), todo: 'request', method: 'POST', action: "/api/v1/statuses/#{notification.target_status.id}/reblog" }
|
||||||
end
|
end
|
||||||
|
|
||||||
actions
|
actions
|
||||||
|
@ -18,7 +18,7 @@ class AccountSearchService < BaseService
|
|||||||
return [] if query_blank_or_hashtag? || limit < 1
|
return [] if query_blank_or_hashtag? || limit < 1
|
||||||
|
|
||||||
if resolving_non_matching_remote_account?
|
if resolving_non_matching_remote_account?
|
||||||
[ResolveRemoteAccountService.new.call("#{query_username}@#{query_domain}")]
|
[ResolveRemoteAccountService.new.call("#{query_username}@#{query_domain}")].compact
|
||||||
else
|
else
|
||||||
search_results_and_exact_match.compact.uniq.slice(0, limit)
|
search_results_and_exact_match.compact.uniq.slice(0, limit)
|
||||||
end
|
end
|
||||||
|
@ -19,10 +19,10 @@
|
|||||||
%td
|
%td
|
||||||
%samp= session.ip
|
%samp= session.ip
|
||||||
%td
|
%td
|
||||||
- if request.session['auth_id'] == session.session_id
|
- if current_session.session_id == session.session_id
|
||||||
= t 'sessions.current_session'
|
= t 'sessions.current_session'
|
||||||
- else
|
- else
|
||||||
%time.time-ago{ datetime: session.updated_at.iso8601, title: l(session.updated_at) }= l(session.updated_at)
|
%time.time-ago{ datetime: session.updated_at.iso8601, title: l(session.updated_at) }= l(session.updated_at)
|
||||||
%td
|
%td
|
||||||
- if request.session['auth_id'] != session.session_id
|
- if current_session.session_id != session.session_id
|
||||||
= table_link_to 'times', t('sessions.revoke'), settings_session_path(session), method: :delete
|
= table_link_to 'times', t('sessions.revoke'), settings_session_path(session), method: :delete
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
class Pubsubhubbub::SubscribeWorker
|
class Pubsubhubbub::SubscribeWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options queue: 'push', retry: 10, unique: :until_executed
|
sidekiq_options queue: 'push', retry: 10, unique: :until_executed, dead: false
|
||||||
|
|
||||||
sidekiq_retry_in do |count|
|
sidekiq_retry_in do |count|
|
||||||
case count
|
case count
|
||||||
@ -18,6 +18,12 @@ class Pubsubhubbub::SubscribeWorker
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sidekiq_retries_exhausted do |msg, _e|
|
||||||
|
account = Account.find(msg['args'].first)
|
||||||
|
logger.error "PuSH subscription attempts for #{account.acct} exhausted. Unsubscribing"
|
||||||
|
::UnsubscribeService.new.call(account)
|
||||||
|
end
|
||||||
|
|
||||||
def perform(account_id)
|
def perform(account_id)
|
||||||
account = Account.find(account_id)
|
account = Account.find(account_id)
|
||||||
logger.debug "PuSH re-subscribing to #{account.acct}"
|
logger.debug "PuSH re-subscribing to #{account.acct}"
|
||||||
|
@ -9,16 +9,17 @@ class WebPushNotificationWorker
|
|||||||
session_activation = SessionActivation.find(session_activation_id)
|
session_activation = SessionActivation.find(session_activation_id)
|
||||||
notification = Notification.find(notification_id)
|
notification = Notification.find(notification_id)
|
||||||
|
|
||||||
return if session_activation.nil? || notification.nil?
|
return if session_activation.web_push_subscription.nil? || notification.activity.nil?
|
||||||
|
|
||||||
begin
|
|
||||||
session_activation.web_push_subscription.push(notification)
|
session_activation.web_push_subscription.push(notification)
|
||||||
rescue Webpush::InvalidSubscription, Webpush::ExpiredSubscription => e
|
rescue Webpush::InvalidSubscription, Webpush::ExpiredSubscription
|
||||||
# Subscription expiration is not currently implemented in any browser
|
# Subscription expiration is not currently implemented in any browser
|
||||||
|
|
||||||
session_activation.web_push_subscription.destroy!
|
session_activation.web_push_subscription.destroy!
|
||||||
session_activation.update!(web_push_subscription: nil)
|
session_activation.update!(web_push_subscription: nil)
|
||||||
|
|
||||||
raise e
|
true
|
||||||
end
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
run.config:
|
run.config:
|
||||||
engine: ruby
|
engine: ruby
|
||||||
engine.config:
|
engine.config:
|
||||||
runtime: ruby-2.4.1
|
runtime: ruby-2.4
|
||||||
|
|
||||||
extra_packages:
|
extra_packages:
|
||||||
# basic servers:
|
# basic servers:
|
||||||
@ -20,6 +20,9 @@ run.config:
|
|||||||
# for node-gyp, used in the asset compilation process:
|
# for node-gyp, used in the asset compilation process:
|
||||||
- python-2
|
- python-2
|
||||||
|
|
||||||
|
# i18n:
|
||||||
|
- libidn
|
||||||
|
|
||||||
cache_dirs:
|
cache_dirs:
|
||||||
- node_modules
|
- node_modules
|
||||||
|
|
||||||
@ -35,10 +38,6 @@ run.config:
|
|||||||
|
|
||||||
extra_steps:
|
extra_steps:
|
||||||
- envsubst < .env.nanobox > .env
|
- envsubst < .env.nanobox > .env
|
||||||
- gem install bundler
|
|
||||||
- bundle config build.nokogiri --with-iconv-dir=/data/ --with-zlib-dir=/data/
|
|
||||||
- bundle config build.nokogumbo --with-iconv-dir=/data/ --with-zlib-dir=/data/
|
|
||||||
- bundle install --clean
|
|
||||||
- yarn
|
- yarn
|
||||||
|
|
||||||
fs_watch: true
|
fs_watch: true
|
||||||
|
@ -348,6 +348,8 @@ en:
|
|||||||
title: "%{name} favourited your status"
|
title: "%{name} favourited your status"
|
||||||
follow:
|
follow:
|
||||||
title: "%{name} is now following you"
|
title: "%{name} is now following you"
|
||||||
|
group:
|
||||||
|
title: "%{count} notifications"
|
||||||
mention:
|
mention:
|
||||||
action_boost: Boost
|
action_boost: Boost
|
||||||
action_expand: Show more
|
action_expand: Show more
|
||||||
|
@ -1,19 +1,38 @@
|
|||||||
---
|
---
|
||||||
oc:
|
oc:
|
||||||
about:
|
about:
|
||||||
about_mastodon_html: Mastodon es un malhum social <em>liure e open-source</em>. Una alternativa <em>descentralizada</em> a las plat-formas comercialas, aquò evita qu’una sola companhiá monopolize vòstra comunicacion. Causissètz un servidor que vos fisatz, quina que siasque vòstra causida, podètz interagir amb tot lo mond. Qual que siasque pòt aver son instància Mastodon e participar al <em>malhum social</em> sens cap de problèmas.
|
about_mastodon_html: Mastodon es un malhum social bastit amb de protocòls liures e gratuits. Es descentralizat coma los corrièls.
|
||||||
about_this: A prepaus d’aquesta instància
|
about_this: A prepaus d’aquesta instància
|
||||||
closed_registrations: Las inscripcions son clavadas pel moment sus aquesta instància.
|
closed_registrations: Las inscripcions son clavadas pel moment sus aquesta instància.
|
||||||
contact: Contacte
|
contact: Contacte
|
||||||
|
contact_missing: Pas parametrat
|
||||||
|
contact_unavailable: Pas disponible
|
||||||
description_headline: Qué es %{domain} ?
|
description_headline: Qué es %{domain} ?
|
||||||
domain_count_after: autras instàncias
|
domain_count_after: autras instàncias
|
||||||
domain_count_before: Connectat a
|
domain_count_before: Connectat a
|
||||||
other_instances: Autras instàncias
|
extended_description_html: |
|
||||||
|
<h3>Una bona plaça per las règlas</h3>
|
||||||
|
<p>La descripcion longa es pas estada causida pel moment.</p>
|
||||||
|
features:
|
||||||
|
humane_approach_body: Amb l’experiéncia dels fracasses d’autres malhums, Mastodon ten per objectiu de lutar contra los abuses dels malhums socials en far de causidas eticas.
|
||||||
|
humane_approach_title: Un biais mai uman
|
||||||
|
not_a_product_body: Mastodon es pas un malhum comercial. Pas cap de reclama, d’utilizacion de vòstras donadas o d’òrt daurat clavat. I a pas cap d’autoritat centrala.
|
||||||
|
not_a_product_title: Sètz una persona, non pas un produit
|
||||||
|
real_conversation_body: Amb 500 caractèrs a vòstra disposicion e un nivèl de confidencialitat per cada publicacion, podètz vos exprimir coma volètz.
|
||||||
|
real_conversation_title: Fach per de conversacions vertadièras
|
||||||
|
within_reach_body: Multiplas aplicacion per iOS, Android, e autras plataformas mercés a un entorn API de bon utilizar, vos permet de gardar lo contacte pertot.
|
||||||
|
within_reach_title: Totjorn al costat
|
||||||
|
find_another_instance: Trobar mai instàncias
|
||||||
|
generic_description: "%{domain} es un dels servidors del malhum"
|
||||||
|
hosted_on: Mastodon albergat sus %{domain}
|
||||||
|
learn_more: Ne saber mai
|
||||||
|
other_instances: Lista d’instàncias
|
||||||
source_code: Còdi font
|
source_code: Còdi font
|
||||||
status_count_after: estatuts
|
status_count_after: estatuts
|
||||||
status_count_before: qu’an escrich
|
status_count_before: qu’an escrich
|
||||||
user_count_after: personas
|
user_count_after: personas
|
||||||
user_count_before: Ostal de
|
user_count_before: Ostal de
|
||||||
|
what_is_mastodon: Qu’es Mastodon ?
|
||||||
accounts:
|
accounts:
|
||||||
follow: Sègre
|
follow: Sègre
|
||||||
followers: Seguidors
|
followers: Seguidors
|
||||||
@ -23,6 +42,7 @@ oc:
|
|||||||
people_who_follow: Lo mond que sègon %{name}
|
people_who_follow: Lo mond que sègon %{name}
|
||||||
posts: Estatuts
|
posts: Estatuts
|
||||||
remote_follow: Sègre a distància
|
remote_follow: Sègre a distància
|
||||||
|
reserved_username: Aqueste nom d’utilizaire es reservat
|
||||||
unfollow: Quitar de sègre
|
unfollow: Quitar de sègre
|
||||||
admin:
|
admin:
|
||||||
accounts:
|
accounts:
|
||||||
@ -60,8 +80,10 @@ oc:
|
|||||||
profile_url: URL del perfil
|
profile_url: URL del perfil
|
||||||
public: Public
|
public: Public
|
||||||
push_subscription_expires: Fin de l’abonament PuSH
|
push_subscription_expires: Fin de l’abonament PuSH
|
||||||
|
redownload: Actualizar los avatars
|
||||||
reset: Reïnicializar
|
reset: Reïnicializar
|
||||||
reset_password: Reïnicializar lo senhal
|
reset_password: Reïnicializar lo senhal
|
||||||
|
resubscribe: Se tornar abonar
|
||||||
salmon_url: URL Salmon
|
salmon_url: URL Salmon
|
||||||
search: Cercar
|
search: Cercar
|
||||||
show:
|
show:
|
||||||
@ -70,13 +92,14 @@ oc:
|
|||||||
targeted_reports: Rapòrts faches tocant aqueste compte
|
targeted_reports: Rapòrts faches tocant aqueste compte
|
||||||
silence: Silenci
|
silence: Silenci
|
||||||
statuses: Estatuts
|
statuses: Estatuts
|
||||||
|
subscribe: S’abonar
|
||||||
title: Comptes
|
title: Comptes
|
||||||
undo_silenced: Levar lo silenci
|
undo_silenced: Levar lo silenci
|
||||||
undo_suspension: Levar la suspension
|
undo_suspension: Levar la suspension
|
||||||
username: Nom d’utilizaire
|
username: Nom d’utilizaire
|
||||||
web: Web
|
web: Web
|
||||||
domain_blocks:
|
domain_blocks:
|
||||||
add_new: Ajustar un nòu
|
add_new: N’ajustar un nòu
|
||||||
created_msg: Domeni blocat es a èsser tractat
|
created_msg: Domeni blocat es a èsser tractat
|
||||||
destroyed_msg: Lo blocatge del domeni es estat levat
|
destroyed_msg: Lo blocatge del domeni es estat levat
|
||||||
domain: Domeni
|
domain: Domeni
|
||||||
@ -85,12 +108,14 @@ oc:
|
|||||||
hint: Lo blocatge empacharà pas la creacion de compte dins la basa de donadas, mai aplicarà la moderacion sus aquestes comptes.
|
hint: Lo blocatge empacharà pas la creacion de compte dins la basa de donadas, mai aplicarà la moderacion sus aquestes comptes.
|
||||||
severity:
|
severity:
|
||||||
desc_html: "<strong>Silenci</strong> farà venir invisibles los estatuts del compte al mond que son pas de seguidors. <strong>Suspendre</strong> levarà tot lo contengut del compte, los mèdias e las donadas de perfil."
|
desc_html: "<strong>Silenci</strong> farà venir invisibles los estatuts del compte al mond que son pas de seguidors. <strong>Suspendre</strong> levarà tot lo contengut del compte, los mèdias e las donadas de perfil."
|
||||||
|
noop: Cap
|
||||||
silence: Silenci
|
silence: Silenci
|
||||||
suspend: Suspendre
|
suspend: Suspendre
|
||||||
title: Nòu blocatge domeni
|
title: Nòu blocatge domeni
|
||||||
reject_media: Regetar los fichièrs mèdias
|
reject_media: Regetar los fichièrs mèdias
|
||||||
reject_media_hint: Lèva los fichièrs gardats localament e regèta las demandas de telecargament dins lo futur. Servís pas a res per las suspensions
|
reject_media_hint: Lèva los fichièrs gardats localament e regèta las demandas de telecargament dins lo futur. Servís pas a res per las suspensions
|
||||||
severities:
|
severities:
|
||||||
|
noop: Cap
|
||||||
silence: Silenci
|
silence: Silenci
|
||||||
suspend: Suspendre
|
suspend: Suspendre
|
||||||
severity: Severitat
|
severity: Severitat
|
||||||
@ -110,6 +135,7 @@ oc:
|
|||||||
domain_name: Domeni
|
domain_name: Domeni
|
||||||
title: Instàncias conegudas
|
title: Instàncias conegudas
|
||||||
reports:
|
reports:
|
||||||
|
action_taken_by: Accion menada per
|
||||||
are_you_sure: Es segur ?
|
are_you_sure: Es segur ?
|
||||||
comment:
|
comment:
|
||||||
label: Comentari
|
label: Comentari
|
||||||
@ -147,7 +173,7 @@ oc:
|
|||||||
desc_html: Autorizar lo monde a se marcar
|
desc_html: Autorizar lo monde a se marcar
|
||||||
title: Inscripcions
|
title: Inscripcions
|
||||||
site_description:
|
site_description:
|
||||||
desc_html: Afichada jos la forma de paragrafe sus la pagina d’acuèlh e utilizada coma balisa meta.<br> Podètz utilizar de balisas HTML, coma <code><a></code> e <code><em></code>.
|
desc_html: Afichada jos la forma de paragraf sus la pagina d’acuèlh e utilizada coma balisa meta.<br> Podètz utilizar de balisas HTML, coma <code><a></code> e <code><em></code>.
|
||||||
title: Descripcion del site
|
title: Descripcion del site
|
||||||
site_description_extended:
|
site_description_extended:
|
||||||
desc_html: Afichada sus la pagina d’informacion complementària del site<br>Podètz utilizar de balisas HTML
|
desc_html: Afichada sus la pagina d’informacion complementària del site<br>Podètz utilizar de balisas HTML
|
||||||
@ -183,6 +209,10 @@ oc:
|
|||||||
title: WebSub
|
title: WebSub
|
||||||
topic: Subjècte
|
topic: Subjècte
|
||||||
title: Administracion
|
title: Administracion
|
||||||
|
admin_mailer:
|
||||||
|
new_report:
|
||||||
|
body: "%{reporter} a senhalat %{target}"
|
||||||
|
subject: Novèl senhalament per %{instance} (#%{id})
|
||||||
application_mailer:
|
application_mailer:
|
||||||
settings: 'Cambiar las preferéncias de corrièl : %{link}'
|
settings: 'Cambiar las preferéncias de corrièl : %{link}'
|
||||||
signature: Notificacion de Mastodon sus %{instance}
|
signature: Notificacion de Mastodon sus %{instance}
|
||||||
@ -190,7 +220,8 @@ oc:
|
|||||||
applications:
|
applications:
|
||||||
invalid_url: L’URL donada es invalida
|
invalid_url: L’URL donada es invalida
|
||||||
auth:
|
auth:
|
||||||
change_password: Cambiar lo senhal
|
agreement_html: En vos marcar acceptatz <a href="%{rules_path}">nòstres tèrmes de servici</a> e <a href="%{terms_path}">politica de confidencialitat</a>.
|
||||||
|
change_password: Seguretat
|
||||||
delete_account: Suprimir lo compte
|
delete_account: Suprimir lo compte
|
||||||
delete_account_html: Se volètz suprimir vòstre compte, podètz <a href="%{path}">o far aquí</a>. Vos demandarem que confirmetz.
|
delete_account_html: Se volètz suprimir vòstre compte, podètz <a href="%{path}">o far aquí</a>. Vos demandarem que confirmetz.
|
||||||
didnt_get_confirmation: Avètz pas recebut las instruccions de confirmacion ?
|
didnt_get_confirmation: Avètz pas recebut las instruccions de confirmacion ?
|
||||||
@ -204,6 +235,12 @@ oc:
|
|||||||
authorize_follow:
|
authorize_follow:
|
||||||
error: O planhèm, i a agut una error al moment de cercar lo compte
|
error: O planhèm, i a agut una error al moment de cercar lo compte
|
||||||
follow: Sègre
|
follow: Sègre
|
||||||
|
follow_request: 'Avètz demandat de sègre :'
|
||||||
|
following: 'Felicitacion ! Seguètz ara :'
|
||||||
|
post_follow:
|
||||||
|
close: O podètz tampar aquesta fenèstra.
|
||||||
|
return: Tornar al perfil
|
||||||
|
web: Tornar a l’interfàcia Web
|
||||||
prompt_html: 'Avètz (<strong>%{self}</strong>) demandat de sègre :'
|
prompt_html: 'Avètz (<strong>%{self}</strong>) demandat de sègre :'
|
||||||
title: Sègre %{acct}
|
title: Sègre %{acct}
|
||||||
date:
|
date:
|
||||||
@ -288,12 +325,14 @@ oc:
|
|||||||
warning_html: La supression del contengut d’aquesta instància es sola assegurada. Lo contengut fòrça partejat daissarà probablament de traças. Los servidors fòra-linha e los que vos sègon pas mai auràn pas la mesa a jorn de lor basa de donada.
|
warning_html: La supression del contengut d’aquesta instància es sola assegurada. Lo contengut fòrça partejat daissarà probablament de traças. Los servidors fòra-linha e los que vos sègon pas mai auràn pas la mesa a jorn de lor basa de donada.
|
||||||
warning_title: Disponibilitat del contengut difusat
|
warning_title: Disponibilitat del contengut difusat
|
||||||
errors:
|
errors:
|
||||||
|
'403': Avètz pas l’autorizacion de veire aquesta pagina.
|
||||||
'404': La pagina que recercatz existís pas.
|
'404': La pagina que recercatz existís pas.
|
||||||
'410': La pagina que cercatz existís pas mai.
|
'410': La pagina que cercatz existís pas mai.
|
||||||
'422':
|
'422':
|
||||||
content: Verificacion de seguretat fracassada. Blocatz los cookies ?
|
content: Verificacion de seguretat fracassada. Blocatz los cookies ?
|
||||||
title: Verificacion de seguretat fracassada
|
title: Verificacion de seguretat fracassada
|
||||||
'429': Lo servidor mòla (subrecargada)
|
'429': Lo servidor mòla (subrecargada)
|
||||||
|
noscript: Per utilizar l’aplicacion web de Mastodon, mercés d’activar JavaScript. O podètz utilizar una aplicacion per vòstra plataforma coma alernativa.
|
||||||
exports:
|
exports:
|
||||||
blocks: Personas que blocatz
|
blocks: Personas que blocatz
|
||||||
csv: CSV
|
csv: CSV
|
||||||
@ -327,7 +366,7 @@ oc:
|
|||||||
following: Lista de mond que seguètz
|
following: Lista de mond que seguètz
|
||||||
muting: Lista de mond que volètz pas legir
|
muting: Lista de mond que volètz pas legir
|
||||||
upload: Importar
|
upload: Importar
|
||||||
landing_strip_html: "<strong>%{name}</strong> es un utilizaire de %{link_to_root_path}. Podètz lo/la sègre o interagir amb el o ela s’avètz un compte ont que siasque sul fediverse."
|
landing_strip_html: "<strong>%{name}</strong> utiliza %{link_to_root_path}. Podètz lo/la sègre o interagir amb el o ela s’avètz un compte ont que siasque sul fediverse."
|
||||||
landing_strip_signup_html: S’es pas lo cas, podètz <a href="%{sign_up_path}">vos marcar aquí</a>.
|
landing_strip_signup_html: S’es pas lo cas, podètz <a href="%{sign_up_path}">vos marcar aquí</a>.
|
||||||
media_attachments:
|
media_attachments:
|
||||||
validations:
|
validations:
|
||||||
@ -362,6 +401,23 @@ oc:
|
|||||||
next: Seguent
|
next: Seguent
|
||||||
prev: Precedent
|
prev: Precedent
|
||||||
truncate: "…"
|
truncate: "…"
|
||||||
|
push_notifications:
|
||||||
|
favourite:
|
||||||
|
title: "%{name} a mes vòstre estatut en favorit"
|
||||||
|
follow:
|
||||||
|
title: "%{name} vos sèc ara"
|
||||||
|
group:
|
||||||
|
title: "%{count} notificacions"
|
||||||
|
mention:
|
||||||
|
action_boost: Partejar
|
||||||
|
action_expand: Ne veire mai
|
||||||
|
action_favourite: Ajustar als favorits
|
||||||
|
title: "%{name} vos a mencionat"
|
||||||
|
reblog:
|
||||||
|
title: "%{name} a partejat vòstre estatut"
|
||||||
|
subscribed:
|
||||||
|
body: Podètz ara recebre las notificacions push.
|
||||||
|
title: Abonament enregistrat !
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: Picatz vòstre utilizaire@instància que cal utilizar per sègre aqueste utilizaire
|
acct: Picatz vòstre utilizaire@instància que cal utilizar per sègre aqueste utilizaire
|
||||||
missing_resource: URL de redireccion pas trobada
|
missing_resource: URL de redireccion pas trobada
|
||||||
@ -438,7 +494,7 @@ oc:
|
|||||||
|
|
||||||
<h3 id="collect">Quinas informacions collectem ?</h3>
|
<h3 id="collect">Quinas informacions collectem ?</h3>
|
||||||
|
|
||||||
<p>Collectem informacions sus vos quand vos marcatz sus nòstre site e juntem las donadas quand participatz a nòstre forum ne legissent, escrivent e notant lo contengut partejat aquí.</p>
|
<p>Collectem informacions sus vos quand vos marcatz sus nòstre site e juntem las donadas quand participatz a nòstre forum en legissent, escrivent e notant lo contengut partejat aquí.</p>
|
||||||
|
|
||||||
<p>Pendent l’inscripcion podèm vos demandar vòstre nom e adreça de corrièl. Podètz çaquelà visitar nòstre site sens vos marcar. Verificarem vòstra adreça amb un messatge donant un ligam unic. Se clicatz sul ligam sauprem qu’avètz lo contraròtle de l’adreça.</p>
|
<p>Pendent l’inscripcion podèm vos demandar vòstre nom e adreça de corrièl. Podètz çaquelà visitar nòstre site sens vos marcar. Verificarem vòstra adreça amb un messatge donant un ligam unic. Se clicatz sul ligam sauprem qu’avètz lo contraròtle de l’adreça.</p>
|
||||||
|
|
||||||
@ -472,13 +528,13 @@ oc:
|
|||||||
|
|
||||||
<p>Òc-ben. Los cookies son de pichons fichièrs qu’un site o sos forneires de servicis plaçan dins lo disc dur de vòstre ordenador via lo navigator Web (Se los acceptatz). Aqueles cookies permeton al site de reconéisser vòstre navigator e se tenètz un compte enregistrat de l’associar a vòstre compte.</p>
|
<p>Òc-ben. Los cookies son de pichons fichièrs qu’un site o sos forneires de servicis plaçan dins lo disc dur de vòstre ordenador via lo navigator Web (Se los acceptatz). Aqueles cookies permeton al site de reconéisser vòstre navigator e se tenètz un compte enregistrat de l’associar a vòstre compte.</p>
|
||||||
|
|
||||||
<p>Empleguem de cookies per comprendre e enregistrar vòstras preferéncias per vòstras visitas venentas, per recampar de donadas sul trafic del site e las interaccions per fin que posquem ofrir una melhora experiéncia del site e de las aisinas pel futur. Pòt arribar que contractèssem amb de forneires de servicis tèrces per nos ajudar a comprendre melhor nòstres visitors. Aqueles forneires an pas lo drech que d’utilizar las donadas collectadas per nos ajudar a menar e melhorar nòstre afar.</p>
|
<p>Empleguem de cookies per comprendre e enregistrar vòstras preferéncias per vòstras visitas venentas, per recampar de donadas sul trafic del site e las interaccions per dire que posquem ofrir una melhora experiéncia del site e de las aisinas pel futur. Pòt arribar que contractèssem amb de forneires de servicis tèrces per nos ajudar a comprendre melhor nòstres visitors. Aqueles forneires an pas lo drech que d’utilizar las donadas collectadas per nos ajudar a menar e melhorar nòstre afar.</p>
|
||||||
|
|
||||||
<h3 id="disclose">Divulguem d’informacions a de partits exteriors ?</h3>
|
<h3 id="disclose">Divulguem d’informacions a de tèrces ?</h3>
|
||||||
|
|
||||||
<p>Vendèm pas, comercem o qualque transferiment que siasque a de partits exteriors vòstras informacions personalas identificablas. Aquò inclutz pas los tèrces partits de confisança que nos assiston a menar nòstre site, menar nòstre afar o vos servir, baste que son d’acòrd per gardar aquelas informacions confidencialas. Pòt tanben arribar que liberèssem vòstras informacions quand cresèm qu’es apropriat d’o far per se sometre a la lei, per refortir nòstras politicas, o per protegir los dreches, proprietats o seguritat de qualqu’un o de nosautres. Pasmens es possible que mandèssem d’informacions non-personalas e identificablas de nòstres visitors a d’autres partits per d’utilizacion en marketing, publicitat o un emplec mai.</p>
|
<p>Vendèm pas, comercem o qualque transferiment que siasque a de tèrces vòstras informacions personalas identificablas. Aquò inclutz pas los tèrces partits de confisança que nos assiston a menar nòstre site, menar nòstre afar o vos servir, baste que son d’acòrd per gardar aquelas informacions confidencialas. Pòt tanben arribar que liberèssem vòstras informacions quand cresèm qu’es apropriat d’o far per se sometre a la lei, per refortir nòstras politicas, o per protegir los dreches, proprietats o seguritat de qualqu’un o de nosautres. Pasmens es possible que mandèssem d’informacions non-personalas e identificablas de nòstres visitors a d’autres partits per d’utilizacion en marketing, publicitat o un emplec mai.</p>
|
||||||
|
|
||||||
<h3 id="third-party">Ligams de tèrces partits</h3>
|
<h3 id="third-party">Ligams de tèrces</h3>
|
||||||
|
|
||||||
<p>Pòt arribar, a nòstra discrecion, qu’incluguèssem o ofriguèssem de produches o servicis de tèrces partits sus nòstre site. Aqueles sites tèrces an de politicas de confidencialitats separadas e independentas. En consequéncia avèm pas cap de responsabilitat pel contengut e las activitats d’aqueles sites ligats. Pasmens cerquem de protegir l’integritat de nòstre site e aculhèm los comentaris tocant aqueles sites.</p>
|
<p>Pòt arribar, a nòstra discrecion, qu’incluguèssem o ofriguèssem de produches o servicis de tèrces partits sus nòstre site. Aqueles sites tèrces an de politicas de confidencialitats separadas e independentas. En consequéncia avèm pas cap de responsabilitat pel contengut e las activitats d’aqueles sites ligats. Pasmens cerquem de protegir l’integritat de nòstre site e aculhèm los comentaris tocant aqueles sites.</p>
|
||||||
|
|
||||||
@ -515,6 +571,7 @@ oc:
|
|||||||
instructions_html: "<strong>Escanatz aqueste còdi QR amb Google Authenticator o una aplicacion similària sus vòstre mobil</strong>. A partir d’ara, aquesta aplicacion generarà un geton que vos caldrà picar per vos connectar."
|
instructions_html: "<strong>Escanatz aqueste còdi QR amb Google Authenticator o una aplicacion similària sus vòstre mobil</strong>. A partir d’ara, aquesta aplicacion generarà un geton que vos caldrà picar per vos connectar."
|
||||||
lost_recovery_codes: Los còdi de recuperacion vos permeton d’accedir a vòstre compte se perdètz vòstre mobil. S’avètz perdut vòstres còdis de recuperacion los podètz tornar generar aquí. Los ancians còdis seràn pas mai valides.
|
lost_recovery_codes: Los còdi de recuperacion vos permeton d’accedir a vòstre compte se perdètz vòstre mobil. S’avètz perdut vòstres còdis de recuperacion los podètz tornar generar aquí. Los ancians còdis seràn pas mai valides.
|
||||||
manual_instructions: 'Se podètz pas numerizar lo còdi QR e que vos cal picar lo còdi a la man, vaquí lo còdi en clar :'
|
manual_instructions: 'Se podètz pas numerizar lo còdi QR e que vos cal picar lo còdi a la man, vaquí lo còdi en clar :'
|
||||||
|
recovery_codes: Salvar los còdis de recuperacion
|
||||||
recovery_codes_regenerated: Los còdis de recuperacion son ben estats tornats generar
|
recovery_codes_regenerated: Los còdis de recuperacion son ben estats tornats generar
|
||||||
recovery_instructions_html: Se vos arriba de perdre vòstre mobil, podètz utilizar un dels còdis de recuperacion cai-jos per poder tornar accedir a vòstre compte. Gardatz los còdis en seguretat, per exemple, imprimissètz los e gardatz los amb vòstres documents importants.
|
recovery_instructions_html: Se vos arriba de perdre vòstre mobil, podètz utilizar un dels còdis de recuperacion cai-jos per poder tornar accedir a vòstre compte. Gardatz los còdis en seguretat, per exemple, imprimissètz los e gardatz los amb vòstres documents importants.
|
||||||
setup: Paramètres
|
setup: Paramètres
|
||||||
|
@ -350,6 +350,8 @@ pl:
|
|||||||
title: "%{name} dodał Twój status do ulubionych"
|
title: "%{name} dodał Twój status do ulubionych"
|
||||||
follow:
|
follow:
|
||||||
title: "%{name} zaczął Cię śledzić"
|
title: "%{name} zaczął Cię śledzić"
|
||||||
|
group:
|
||||||
|
title: "%{count} powiadomień"
|
||||||
mention:
|
mention:
|
||||||
action_boost: Podbij
|
action_boost: Podbij
|
||||||
action_expand: Pokaż więcej
|
action_expand: Pokaż więcej
|
||||||
|
@ -267,6 +267,21 @@ ru:
|
|||||||
next: След
|
next: След
|
||||||
prev: Пред
|
prev: Пред
|
||||||
truncate: "…"
|
truncate: "…"
|
||||||
|
push_notifications:
|
||||||
|
favourite:
|
||||||
|
title: "Ваш статус понравился %{name}"
|
||||||
|
follow:
|
||||||
|
title: "%{name} теперь подписан(а) на Вас"
|
||||||
|
mention:
|
||||||
|
action_boost: Продвинуть
|
||||||
|
action_expand: Развернуть
|
||||||
|
action_favourite: Нравится
|
||||||
|
title: "Вас упомянул(а) %{name}"
|
||||||
|
reblog:
|
||||||
|
title: "%{name} продвинул(а) Ваш статус"
|
||||||
|
subscribed:
|
||||||
|
body: Теперь Вы можете получать push-уведомления.
|
||||||
|
title: Подписка зарегистрирована!
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: Введите username@domain, откуда Вы хотите подписаться
|
acct: Введите username@domain, откуда Вы хотите подписаться
|
||||||
missing_resource: Поиск требуемого перенаправления URL для Вашего аккаунта завершился неудачей
|
missing_resource: Поиск требуемого перенаправления URL для Вашего аккаунта завершился неудачей
|
||||||
@ -335,6 +350,8 @@ ru:
|
|||||||
click_to_show: Показать
|
click_to_show: Показать
|
||||||
reblogged: продвинул(а)
|
reblogged: продвинул(а)
|
||||||
sensitive_content: Чувствительный контент
|
sensitive_content: Чувствительный контент
|
||||||
|
terms:
|
||||||
|
title: "Условия обслуживания и политика конфиденциальности %{instance}"
|
||||||
time:
|
time:
|
||||||
formats:
|
formats:
|
||||||
default: "%b %d, %Y, %H:%M"
|
default: "%b %d, %Y, %H:%M"
|
||||||
|
@ -16,6 +16,7 @@ ru:
|
|||||||
many: Осталось <span class="name-counter">%{count}</span> символов
|
many: Осталось <span class="name-counter">%{count}</span> символов
|
||||||
one: Остался <span class="name-counter">1</span> символ
|
one: Остался <span class="name-counter">1</span> символ
|
||||||
other: Осталось <span class="name-counter">%{count}</span> символов
|
other: Осталось <span class="name-counter">%{count}</span> символов
|
||||||
|
setting_noindex: Относится к Вашему публичному профилю и страницам статусов
|
||||||
imports:
|
imports:
|
||||||
data: Файл CSV, экспортированный с другого узла Mastodon
|
data: Файл CSV, экспортированный с другого узла Mastodon
|
||||||
sessions:
|
sessions:
|
||||||
@ -42,7 +43,11 @@ ru:
|
|||||||
setting_auto_play_gif: Автоматически проигрывать анимированные GIF
|
setting_auto_play_gif: Автоматически проигрывать анимированные GIF
|
||||||
setting_boost_modal: Показывать диалог подтверждения перед продвижением
|
setting_boost_modal: Показывать диалог подтверждения перед продвижением
|
||||||
setting_default_privacy: Видимость постов
|
setting_default_privacy: Видимость постов
|
||||||
|
setting_default_sensitive: Всегда отмечать медиаконтент как чувствительный
|
||||||
setting_delete_modal: Показывать диалог подтверждения перед удалением
|
setting_delete_modal: Показывать диалог подтверждения перед удалением
|
||||||
|
setting_noindex: Отказаться от индексации в поисковых машинах
|
||||||
|
setting_system_font_ui: Использовать шрифт системы по умолчанию
|
||||||
|
setting_unfollow_modal: Показывать диалог подтверждения перед тем, как отписаться от аккаунта
|
||||||
severity: Строгость
|
severity: Строгость
|
||||||
type: Тип импорта
|
type: Тип импорта
|
||||||
username: Имя пользователя
|
username: Имя пользователя
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
require 'sidekiq/web'
|
require 'sidekiq/web'
|
||||||
require 'sidekiq-scheduler/web'
|
require 'sidekiq-scheduler/web'
|
||||||
|
|
||||||
|
Sidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base]
|
||||||
|
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
mount LetterOpenerWeb::Engine, at: 'letter_opener' if Rails.env.development?
|
mount LetterOpenerWeb::Engine, at: 'letter_opener' if Rails.env.development?
|
||||||
|
|
||||||
|
@ -17,9 +17,11 @@ defaults: &defaults
|
|||||||
closed_registrations_message: ''
|
closed_registrations_message: ''
|
||||||
open_deletion: true
|
open_deletion: true
|
||||||
timeline_preview: true
|
timeline_preview: true
|
||||||
|
default_sensitive: false
|
||||||
|
unfollow_modal: false
|
||||||
boost_modal: false
|
boost_modal: false
|
||||||
auto_play_gif: false
|
|
||||||
delete_modal: true
|
delete_modal: true
|
||||||
|
auto_play_gif: false
|
||||||
system_font_ui: false
|
system_font_ui: false
|
||||||
noindex: false
|
noindex: false
|
||||||
notification_emails:
|
notification_emails:
|
||||||
|
@ -10,7 +10,11 @@ const { publicPath } = require('./configuration.js');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = merge(sharedConfig, {
|
module.exports = merge(sharedConfig, {
|
||||||
output: { filename: '[name]-[chunkhash].js' },
|
output: {
|
||||||
|
filename: '[name]-[chunkhash].js',
|
||||||
|
chunkFilename: '[name]-[chunkhash].js',
|
||||||
|
},
|
||||||
|
|
||||||
devtool: 'source-map', // separate sourcemap file, suitable for production
|
devtool: 'source-map', // separate sourcemap file, suitable for production
|
||||||
stats: 'normal',
|
stats: 'normal',
|
||||||
|
|
||||||
@ -48,7 +52,7 @@ module.exports = merge(sharedConfig, {
|
|||||||
ServiceWorker: {
|
ServiceWorker: {
|
||||||
entry: path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'),
|
entry: path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'),
|
||||||
cacheName: 'mastodon',
|
cacheName: 'mastodon',
|
||||||
output: '../sw.js',
|
output: '../assets/sw.js',
|
||||||
publicPath: '/sw.js',
|
publicPath: '/sw.js',
|
||||||
minify: true,
|
minify: true,
|
||||||
},
|
},
|
||||||
|
@ -33,7 +33,7 @@ module.exports = {
|
|||||||
|
|
||||||
output: {
|
output: {
|
||||||
filename: '[name].js',
|
filename: '[name].js',
|
||||||
chunkFilename: '[name]-[chunkhash].js',
|
chunkFilename: '[name].js',
|
||||||
path: output.path,
|
path: output.path,
|
||||||
publicPath: output.publicPath,
|
publicPath: output.publicPath,
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,7 @@ module Mastodon
|
|||||||
end
|
end
|
||||||
|
|
||||||
def flags
|
def flags
|
||||||
'rc1'
|
'rc2'
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_a
|
def to_a
|
||||||
|
15
package.json
15
package.json
@ -7,9 +7,8 @@
|
|||||||
"build:production": "cross-env RAILS_ENV=production ./bin/webpack",
|
"build:production": "cross-env RAILS_ENV=production ./bin/webpack",
|
||||||
"manage:translations": "node ./config/webpack/translationRunner.js",
|
"manage:translations": "node ./config/webpack/translationRunner.js",
|
||||||
"start": "node ./streaming/index.js",
|
"start": "node ./streaming/index.js",
|
||||||
"storybook": "cross-env NODE_ENV=test start-storybook -s ./public -p 9001 -c storybook",
|
|
||||||
"test": "npm run test:lint && npm run test:mocha",
|
"test": "npm run test:lint && npm run test:mocha",
|
||||||
"test:lint": "eslint -c .eslintrc.yml --ext=js app/javascript/ config/webpack/ spec/javascript/ storybook/ streaming/",
|
"test:lint": "eslint -c .eslintrc.yml --ext=js app/javascript/ config/webpack/ spec/javascript/ streaming/",
|
||||||
"test:mocha": "cross-env NODE_ENV=test mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.js",
|
"test:mocha": "cross-env NODE_ENV=test mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.js",
|
||||||
"postinstall": "npm rebuild node-sass"
|
"postinstall": "npm rebuild node-sass"
|
||||||
},
|
},
|
||||||
@ -57,7 +56,7 @@
|
|||||||
"glob": "^7.1.1",
|
"glob": "^7.1.1",
|
||||||
"http-link-header": "^0.8.0",
|
"http-link-header": "^0.8.0",
|
||||||
"immutable": "^3.8.1",
|
"immutable": "^3.8.1",
|
||||||
"intersection-observer": "^0.3.2",
|
"intersection-observer": "^0.4.0",
|
||||||
"intl": "^1.2.5",
|
"intl": "^1.2.5",
|
||||||
"intl-relativeformat": "^2.0.0",
|
"intl-relativeformat": "^2.0.0",
|
||||||
"is-nan": "^1.2.1",
|
"is-nan": "^1.2.1",
|
||||||
@ -113,15 +112,13 @@
|
|||||||
"tiny-queue": "^0.2.1",
|
"tiny-queue": "^0.2.1",
|
||||||
"uuid": "^3.1.0",
|
"uuid": "^3.1.0",
|
||||||
"uws": "^8.14.0",
|
"uws": "^8.14.0",
|
||||||
"webpack": "^3.0.0",
|
"webpack": "^3.4.1",
|
||||||
"webpack-bundle-analyzer": "^2.8.2",
|
"webpack-bundle-analyzer": "^2.8.3",
|
||||||
"webpack-manifest-plugin": "^1.1.2",
|
"webpack-manifest-plugin": "^1.2.1",
|
||||||
"webpack-merge": "^4.1.0",
|
"webpack-merge": "^4.1.0",
|
||||||
"websocket.js": "^0.1.12"
|
"websocket.js": "^0.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@storybook/addon-actions": "^3.1.8",
|
|
||||||
"@storybook/react": "^3.1.8",
|
|
||||||
"babel-eslint": "^7.2.3",
|
"babel-eslint": "^7.2.3",
|
||||||
"chai": "^4.1.0",
|
"chai": "^4.1.0",
|
||||||
"chai-enzyme": "^0.8.0",
|
"chai-enzyme": "^0.8.0",
|
||||||
@ -134,7 +131,7 @@
|
|||||||
"react-intl-translations-manager": "^5.0.0",
|
"react-intl-translations-manager": "^5.0.0",
|
||||||
"react-test-renderer": "^15.6.1",
|
"react-test-renderer": "^15.6.1",
|
||||||
"sinon": "^2.3.7",
|
"sinon": "^2.3.7",
|
||||||
"webpack-dev-server": "^2.5.1",
|
"webpack-dev-server": "^2.6.1",
|
||||||
"yargs": "^8.0.2"
|
"yargs": "^8.0.2"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
1
public/sw.js
Symbolic link
1
public/sw.js
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
assets/sw.js
|
BIN
public/web-push-icon_expand.png
Normal file
BIN
public/web-push-icon_expand.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
public/web-push-icon_favourite.png
Normal file
BIN
public/web-push-icon_favourite.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
public/web-push-icon_reblog.png
Normal file
BIN
public/web-push-icon_reblog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 851 B |
@ -4,7 +4,7 @@ describe Api::V1::Accounts::CredentialsController do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(controller).to receive(:doorkeeper_token) { token }
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
|
@ -4,7 +4,7 @@ describe Api::V1::Accounts::FollowerAccountsController do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Fabricate(:follow, target_account: user.account)
|
Fabricate(:follow, target_account: user.account)
|
||||||
|
@ -4,7 +4,7 @@ describe Api::V1::Accounts::FollowingAccountsController do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Fabricate(:follow, account: user.account)
|
Fabricate(:follow, account: user.account)
|
||||||
|
@ -4,7 +4,7 @@ describe Api::V1::Accounts::RelationshipsController do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(controller).to receive(:doorkeeper_token) { token }
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
|
@ -4,7 +4,7 @@ RSpec.describe Api::V1::Accounts::SearchController, type: :controller do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(controller).to receive(:doorkeeper_token) { token }
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
|
@ -4,7 +4,7 @@ describe Api::V1::Accounts::StatusesController do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(controller).to receive(:doorkeeper_token) { token }
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
|
@ -4,7 +4,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow read') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(controller).to receive(:doorkeeper_token) { token }
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
|
@ -4,7 +4,7 @@ RSpec.describe Api::V1::BlocksController, type: :controller do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Fabricate(:block, account: user.account)
|
Fabricate(:block, account: user.account)
|
||||||
|
@ -4,7 +4,7 @@ RSpec.describe Api::V1::DomainBlocksController, type: :controller do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
user.account.block_domain!('example.com')
|
user.account.block_domain!('example.com')
|
||||||
|
@ -3,19 +3,77 @@ require 'rails_helper'
|
|||||||
RSpec.describe Api::V1::FavouritesController, type: :controller do
|
RSpec.describe Api::V1::FavouritesController, type: :controller do
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
let(:user) { Fabricate(:user) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
|
||||||
|
|
||||||
|
describe 'GET #index' do
|
||||||
|
context 'without token' do
|
||||||
|
it 'returns http unauthorized' do
|
||||||
|
get :index
|
||||||
|
expect(response).to have_http_status :unauthorized
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with token' do
|
||||||
|
context 'without read scope' do
|
||||||
before do
|
before do
|
||||||
Fabricate(:favourite, account: user.account)
|
allow(controller).to receive(:doorkeeper_token) do
|
||||||
|
Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: '')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http forbidden' do
|
||||||
|
get :index
|
||||||
|
expect(response).to have_http_status :forbidden
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without valid resource owner' do
|
||||||
|
before do
|
||||||
|
token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read')
|
||||||
|
user.destroy!
|
||||||
|
|
||||||
allow(controller).to receive(:doorkeeper_token) { token }
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET #index' do
|
it 'returns http unprocessable entity' do
|
||||||
it 'returns http success' do
|
get :index
|
||||||
|
expect(response).to have_http_status :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with read scope and valid resource owner' do
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:doorkeeper_token) do
|
||||||
|
Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows favourites owned by the user' do
|
||||||
|
favourite_by_user = Fabricate(:favourite, account: user.account)
|
||||||
|
favourite_by_others = Fabricate(:favourite)
|
||||||
|
|
||||||
|
get :index
|
||||||
|
|
||||||
|
expect(assigns(:statuses)).to match_array [favourite_by_user.status]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds pagination headers if necessary' do
|
||||||
|
favourite = Fabricate(:favourite, account: user.account)
|
||||||
|
|
||||||
get :index, params: { limit: 1 }
|
get :index, params: { limit: 1 }
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response.headers['Link'].find_link(['rel', 'next']).href).to eq "http://test.host/api/v1/favourites?limit=1&max_id=#{favourite.id}"
|
||||||
|
expect(response.headers['Link'].find_link(['rel', 'prev']).href).to eq "http://test.host/api/v1/favourites?limit=1&since_id=#{favourite.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not add pagination headers if not necessary' do
|
||||||
|
get :index
|
||||||
|
|
||||||
|
expect(response.headers['Link'].find_link(['rel', 'next'])).to eq nil
|
||||||
|
expect(response.headers['Link'].find_link(['rel', 'prev'])).to eq nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -4,7 +4,7 @@ RSpec.describe Api::V1::FollowRequestsController, type: :controller do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice', locked: true)) }
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice', locked: true)) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
|
||||||
let(:follower) { Fabricate(:account, username: 'bob') }
|
let(:follower) { Fabricate(:account, username: 'bob') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -4,7 +4,7 @@ RSpec.describe Api::V1::FollowsController, type: :controller do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(controller).to receive(:doorkeeper_token) { token }
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user