mirror of
https://git.bsd.gay/fef/nyastodon.git
synced 2024-12-27 22:23:43 +01:00
Merge pull request #1682 from ClearlyClaire/glitch-soc/fixes/dropdowns-modals
Refactor and fix dropdown/action dialog
This commit is contained in:
commit
8987ea4d6b
7 changed files with 174 additions and 214 deletions
|
@ -116,7 +116,7 @@ class DropdownMenu extends React.PureComponent {
|
||||||
|
|
||||||
if (typeof action === 'function') {
|
if (typeof action === 'function') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
action();
|
action(e);
|
||||||
} else if (to) {
|
} else if (to) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.context.router.history.push(to);
|
this.context.router.history.push(to);
|
||||||
|
@ -128,11 +128,11 @@ class DropdownMenu extends React.PureComponent {
|
||||||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { text, href = '#' } = option;
|
const { text, href = '#', target = '_blank', method } = option;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className='dropdown-menu__item' key={`${text}-${i}`}>
|
<li className='dropdown-menu__item' key={`${text}-${i}`}>
|
||||||
<a href={href} target='_blank' rel='noopener noreferrer' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
|
<a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
|
||||||
{text}
|
{text}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -149,7 +149,7 @@ class DropdownMenu extends React.PureComponent {
|
||||||
// It should not be transformed when mounting because the resulting
|
// It should not be transformed when mounting because the resulting
|
||||||
// size will be used to determine the coordinate of the menu by
|
// size will be used to determine the coordinate of the menu by
|
||||||
// react-overlays
|
// react-overlays
|
||||||
<div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
|
<div className={`dropdown-menu ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
|
||||||
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
|
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -236,7 +236,8 @@ export default class Dropdown extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleItemClick = (i, e) => {
|
handleItemClick = e => {
|
||||||
|
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||||
const { action, to } = this.props.items[i];
|
const { action, to } = this.props.items[i];
|
||||||
|
|
||||||
this.handleClose();
|
this.handleClose();
|
||||||
|
|
|
@ -14,15 +14,11 @@ const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
|
||||||
onOpen(id, onItemClick, dropdownPlacement, keyboard) {
|
onOpen(id, onItemClick, dropdownPlacement, keyboard) {
|
||||||
dispatch(isUserTouching() ? openModal('ACTIONS', {
|
dispatch(isUserTouching() ? openModal('ACTIONS', {
|
||||||
status,
|
status,
|
||||||
actions: items.map(
|
actions: items,
|
||||||
(item, i) => item ? {
|
onClick: onItemClick,
|
||||||
...item,
|
|
||||||
name: `${item.text}-${i}`,
|
|
||||||
onClick: item.action ? ((e) => { return onItemClick(i, e) }) : null,
|
|
||||||
} : null
|
|
||||||
),
|
|
||||||
}) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey));
|
}) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey));
|
||||||
},
|
},
|
||||||
|
|
||||||
onClose(id) {
|
onClose(id) {
|
||||||
dispatch(closeModal('ACTIONS'));
|
dispatch(closeModal('ACTIONS'));
|
||||||
dispatch(closeDropdownMenu(id));
|
dispatch(closeDropdownMenu(id));
|
||||||
|
|
|
@ -21,22 +21,25 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
items: PropTypes.arrayOf(PropTypes.shape({
|
items: PropTypes.arrayOf(PropTypes.shape({
|
||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
meta: PropTypes.node,
|
meta: PropTypes.string,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
on: PropTypes.bool,
|
text: PropTypes.string,
|
||||||
text: PropTypes.node,
|
|
||||||
})).isRequired,
|
})).isRequired,
|
||||||
onModalOpen: PropTypes.func,
|
onModalOpen: PropTypes.func,
|
||||||
onModalClose: PropTypes.func,
|
onModalClose: PropTypes.func,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
noModal: PropTypes.bool,
|
|
||||||
container: PropTypes.func,
|
container: PropTypes.func,
|
||||||
|
renderItemContents: PropTypes.func,
|
||||||
|
closeOnChange: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
closeOnChange: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
needsModalUpdate: false,
|
|
||||||
open: false,
|
open: false,
|
||||||
openedViaKeyboard: undefined,
|
openedViaKeyboard: undefined,
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
|
@ -44,10 +47,10 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
|
|
||||||
// Toggles opening and closing the dropdown.
|
// Toggles opening and closing the dropdown.
|
||||||
handleToggle = ({ target, type }) => {
|
handleToggle = ({ target, type }) => {
|
||||||
const { onModalOpen, noModal } = this.props;
|
const { onModalOpen } = this.props;
|
||||||
const { open } = this.state;
|
const { open } = this.state;
|
||||||
|
|
||||||
if (!noModal && isUserTouching()) {
|
if (isUserTouching()) {
|
||||||
if (this.state.open) {
|
if (this.state.open) {
|
||||||
this.props.onModalClose();
|
this.props.onModalClose();
|
||||||
} else {
|
} else {
|
||||||
|
@ -107,9 +110,25 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
this.setState({ open: false });
|
this.setState({ open: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleItemClick = (e) => {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
onChange,
|
||||||
|
onModalClose,
|
||||||
|
closeOnChange,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||||
|
|
||||||
|
const { name } = items[i];
|
||||||
|
|
||||||
|
e.preventDefault(); // Prevents focus from changing
|
||||||
|
if (closeOnChange) onModalClose();
|
||||||
|
onChange(name);
|
||||||
|
};
|
||||||
|
|
||||||
// Creates an action modal object.
|
// Creates an action modal object.
|
||||||
handleMakeModal = () => {
|
handleMakeModal = () => {
|
||||||
const component = this;
|
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
onChange,
|
onChange,
|
||||||
|
@ -125,6 +144,8 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
|
|
||||||
// The object.
|
// The object.
|
||||||
return {
|
return {
|
||||||
|
renderItemContents: this.props.renderItemContents,
|
||||||
|
onClick: this.handleItemClick,
|
||||||
actions: items.map(
|
actions: items.map(
|
||||||
({
|
({
|
||||||
name,
|
name,
|
||||||
|
@ -133,48 +154,11 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
...rest,
|
...rest,
|
||||||
active: value && name === value,
|
active: value && name === value,
|
||||||
name,
|
name,
|
||||||
onClick (e) {
|
|
||||||
e.preventDefault(); // Prevents focus from changing
|
|
||||||
onModalClose();
|
|
||||||
onChange(name);
|
|
||||||
},
|
|
||||||
onPassiveClick (e) {
|
|
||||||
e.preventDefault(); // Prevents focus from changing
|
|
||||||
onChange(name);
|
|
||||||
component.setState({ needsModalUpdate: true });
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// If our modal is open and our props update, we need to also update
|
|
||||||
// the modal.
|
|
||||||
handleUpdate = () => {
|
|
||||||
const { onModalOpen } = this.props;
|
|
||||||
const { needsModalUpdate } = this.state;
|
|
||||||
|
|
||||||
// Gets our modal object.
|
|
||||||
const modal = this.handleMakeModal();
|
|
||||||
|
|
||||||
// Reopens the modal with the new object.
|
|
||||||
if (needsModalUpdate && modal && onModalOpen) {
|
|
||||||
onModalOpen(modal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updates our modal as necessary.
|
|
||||||
componentDidUpdate (prevProps) {
|
|
||||||
const { items } = this.props;
|
|
||||||
const { needsModalUpdate } = this.state;
|
|
||||||
if (needsModalUpdate && items.find(
|
|
||||||
(item, i) => item.on !== prevProps.items[i].on
|
|
||||||
)) {
|
|
||||||
this.handleUpdate();
|
|
||||||
this.setState({ needsModalUpdate: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
|
@ -186,6 +170,8 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
onChange,
|
onChange,
|
||||||
value,
|
value,
|
||||||
container,
|
container,
|
||||||
|
renderItemContents,
|
||||||
|
closeOnChange,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { open, placement } = this.state;
|
const { open, placement } = this.state;
|
||||||
const computedClass = classNames('composer--options--dropdown', {
|
const computedClass = classNames('composer--options--dropdown', {
|
||||||
|
@ -226,10 +212,12 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
>
|
>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
items={items}
|
items={items}
|
||||||
|
renderItemContents={renderItemContents}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onClose={this.handleClose}
|
onClose={this.handleClose}
|
||||||
value={value}
|
value={value}
|
||||||
openedViaKeyboard={this.state.openedViaKeyboard}
|
openedViaKeyboard={this.state.openedViaKeyboard}
|
||||||
|
closeOnChange={closeOnChange}
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
import Toggle from 'react-toggle';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
@ -28,18 +27,20 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
meta: PropTypes.node,
|
meta: PropTypes.node,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
on: PropTypes.bool,
|
|
||||||
text: PropTypes.node,
|
text: PropTypes.node,
|
||||||
})),
|
})),
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
|
renderItemContents: PropTypes.func,
|
||||||
openedViaKeyboard: PropTypes.bool,
|
openedViaKeyboard: PropTypes.bool,
|
||||||
|
closeOnChange: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
style: {},
|
style: {},
|
||||||
|
closeOnChange: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -77,16 +78,19 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||||
document.removeEventListener('touchend', this.handleDocumentClick, withPassive);
|
document.removeEventListener('touchend', this.handleDocumentClick, withPassive);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = (name, e) => {
|
handleClick = (e) => {
|
||||||
|
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
onChange,
|
onChange,
|
||||||
onClose,
|
onClose,
|
||||||
|
closeOnChange,
|
||||||
items,
|
items,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { on } = this.props.items.find(item => item.name === name);
|
const { name } = this.props.items[i];
|
||||||
e.preventDefault(); // Prevents change in focus on click
|
e.preventDefault(); // Prevents change in focus on click
|
||||||
if ((on === null || typeof on === 'undefined')) {
|
if (closeOnChange) {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
onChange(name);
|
onChange(name);
|
||||||
|
@ -101,11 +105,9 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown = (name, e) => {
|
handleKeyDown = (e) => {
|
||||||
|
const index = Number(e.currentTarget.getAttribute('data-index'));
|
||||||
const { items } = this.props;
|
const { items } = this.props;
|
||||||
const index = items.findIndex(item => {
|
|
||||||
return (item.name === name);
|
|
||||||
});
|
|
||||||
let element = null;
|
let element = null;
|
||||||
|
|
||||||
switch(e.key) {
|
switch(e.key) {
|
||||||
|
@ -139,7 +141,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
element.focus();
|
element.focus();
|
||||||
this.handleChange(element.getAttribute('data-index'));
|
this.handleChange(this.props.items[Number(element.getAttribute('data-index'))].name);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
@ -149,44 +151,40 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||||
this.focusedItem = c;
|
this.focusedItem = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem = (item) => {
|
renderItem = (item, i) => {
|
||||||
const { name, icon, meta, on, text } = item;
|
const { name, icon, meta, text } = item;
|
||||||
|
|
||||||
const active = (name === (this.props.value || this.state.value));
|
const active = (name === (this.props.value || this.state.value));
|
||||||
|
|
||||||
const computedClass = classNames('composer--options--dropdown--content--item', {
|
const computedClass = classNames('composer--options--dropdown--content--item', { active });
|
||||||
active,
|
|
||||||
lengthy: meta,
|
|
||||||
'toggled-off': !on && on !== null && typeof on !== 'undefined',
|
|
||||||
'toggled-on': on,
|
|
||||||
'with-icon': icon,
|
|
||||||
});
|
|
||||||
|
|
||||||
let prefix = null;
|
let contents = this.props.renderItemContents && this.props.renderItemContents(item, i);
|
||||||
|
|
||||||
if (on !== null && typeof on !== 'undefined') {
|
if (!contents) {
|
||||||
prefix = <Toggle checked={on} onChange={this.handleClick.bind(this, name)} />;
|
contents = (
|
||||||
} else if (icon) {
|
<React.Fragment>
|
||||||
prefix = <Icon className='icon' fixedWidth id={icon} />
|
{icon && <Icon className='icon' fixedWidth id={icon} />}
|
||||||
|
|
||||||
|
<div className='content'>
|
||||||
|
<strong>{text}</strong>
|
||||||
|
{meta}
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={computedClass}
|
className={computedClass}
|
||||||
onClick={this.handleClick.bind(this, name)}
|
onClick={this.handleClick}
|
||||||
onKeyDown={this.handleKeyDown.bind(this, name)}
|
onKeyDown={this.handleKeyDown}
|
||||||
role='option'
|
role='option'
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
key={name}
|
key={name}
|
||||||
data-index={name}
|
data-index={i}
|
||||||
ref={active ? this.setFocusRef : null}
|
ref={active ? this.setFocusRef : null}
|
||||||
>
|
>
|
||||||
{prefix}
|
{contents}
|
||||||
|
|
||||||
<div className='content'>
|
|
||||||
<strong>{text}</strong>
|
|
||||||
{meta}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -229,7 +227,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||||
transform: mounted ? `scale(${scaleX}, ${scaleY})` : null,
|
transform: mounted ? `scale(${scaleX}, ${scaleY})` : null,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!!items && items.map(item => this.renderItem(item))}
|
{!!items && items.map((item, i) => this.renderItem(item, i))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Motion>
|
</Motion>
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
|
import Toggle from 'react-toggle';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
// Components.
|
// Components.
|
||||||
import IconButton from 'flavours/glitch/components/icon_button';
|
import IconButton from 'flavours/glitch/components/icon_button';
|
||||||
|
@ -80,6 +82,36 @@ const messages = defineMessages({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@connect((state, { name }) => ({ checked: state.getIn(['compose', 'advanced_options', name]) }))
|
||||||
|
class ToggleOption extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
checked: PropTypes.bool,
|
||||||
|
onChangeAdvancedOption: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = () => {
|
||||||
|
this.props.onChangeAdvancedOption(this.props.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { meta, text, checked } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Toggle checked={checked} onChange={this.handleChange} />
|
||||||
|
|
||||||
|
<div className='content'>
|
||||||
|
<strong>{text}</strong>
|
||||||
|
{meta}
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export default @injectIntl
|
export default @injectIntl
|
||||||
class ComposerOptions extends ImmutablePureComponent {
|
class ComposerOptions extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
@ -141,6 +173,13 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
this.fileElement = fileElement;
|
this.fileElement = fileElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderToggleItemContents = (item) => {
|
||||||
|
const { onChangeAdvancedOption } = this.props;
|
||||||
|
const { name, meta, text } = item;
|
||||||
|
|
||||||
|
return <ToggleOption name={name} text={text} meta={meta} onChangeAdvancedOption={onChangeAdvancedOption} />;
|
||||||
|
};
|
||||||
|
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
|
@ -152,7 +191,6 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
hasMedia,
|
hasMedia,
|
||||||
allowPoll,
|
allowPoll,
|
||||||
hasPoll,
|
hasPoll,
|
||||||
intl,
|
|
||||||
onChangeAdvancedOption,
|
onChangeAdvancedOption,
|
||||||
onChangeContentType,
|
onChangeContentType,
|
||||||
onChangeVisibility,
|
onChangeVisibility,
|
||||||
|
@ -164,23 +202,24 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
resetFileKey,
|
resetFileKey,
|
||||||
spoiler,
|
spoiler,
|
||||||
showContentTypeChoice,
|
showContentTypeChoice,
|
||||||
|
intl: { formatMessage },
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const contentTypeItems = {
|
const contentTypeItems = {
|
||||||
plain: {
|
plain: {
|
||||||
icon: 'file-text',
|
icon: 'file-text',
|
||||||
name: 'text/plain',
|
name: 'text/plain',
|
||||||
text: <FormattedMessage {...messages.plain} />,
|
text: formatMessage(messages.plain),
|
||||||
},
|
},
|
||||||
html: {
|
html: {
|
||||||
icon: 'code',
|
icon: 'code',
|
||||||
name: 'text/html',
|
name: 'text/html',
|
||||||
text: <FormattedMessage {...messages.html} />,
|
text: formatMessage(messages.html),
|
||||||
},
|
},
|
||||||
markdown: {
|
markdown: {
|
||||||
icon: 'arrow-circle-down',
|
icon: 'arrow-circle-down',
|
||||||
name: 'text/markdown',
|
name: 'text/markdown',
|
||||||
text: <FormattedMessage {...messages.markdown} />,
|
text: formatMessage(messages.markdown),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -204,18 +243,18 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
{
|
{
|
||||||
icon: 'cloud-upload',
|
icon: 'cloud-upload',
|
||||||
name: 'upload',
|
name: 'upload',
|
||||||
text: <FormattedMessage {...messages.upload} />,
|
text: formatMessage(messages.upload),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'paint-brush',
|
icon: 'paint-brush',
|
||||||
name: 'doodle',
|
name: 'doodle',
|
||||||
text: <FormattedMessage {...messages.doodle} />,
|
text: formatMessage(messages.doodle),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onChange={this.handleClickAttach}
|
onChange={this.handleClickAttach}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
onModalOpen={onModalOpen}
|
onModalOpen={onModalOpen}
|
||||||
title={intl.formatMessage(messages.attach)}
|
title={formatMessage(messages.attach)}
|
||||||
/>
|
/>
|
||||||
{!!pollLimits && (
|
{!!pollLimits && (
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -229,7 +268,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
height: null,
|
height: null,
|
||||||
lineHeight: null,
|
lineHeight: null,
|
||||||
}}
|
}}
|
||||||
title={intl.formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)}
|
title={formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -252,7 +291,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
onChange={onChangeContentType}
|
onChange={onChangeContentType}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
onModalOpen={onModalOpen}
|
onModalOpen={onModalOpen}
|
||||||
title={intl.formatMessage(messages.content_type)}
|
title={formatMessage(messages.content_type)}
|
||||||
value={contentType}
|
value={contentType}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -262,7 +301,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
ariaControls='glitch.composer.spoiler.input'
|
ariaControls='glitch.composer.spoiler.input'
|
||||||
label='CW'
|
label='CW'
|
||||||
onClick={onToggleSpoiler}
|
onClick={onToggleSpoiler}
|
||||||
title={intl.formatMessage(messages.spoiler)}
|
title={formatMessage(messages.spoiler)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Dropdown
|
<Dropdown
|
||||||
|
@ -271,22 +310,22 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
icon='ellipsis-h'
|
icon='ellipsis-h'
|
||||||
items={advancedOptions ? [
|
items={advancedOptions ? [
|
||||||
{
|
{
|
||||||
meta: <FormattedMessage {...messages.local_only_long} />,
|
meta: formatMessage(messages.local_only_long),
|
||||||
name: 'do_not_federate',
|
name: 'do_not_federate',
|
||||||
on: advancedOptions.get('do_not_federate'),
|
text: formatMessage(messages.local_only_short),
|
||||||
text: <FormattedMessage {...messages.local_only_short} />,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
meta: <FormattedMessage {...messages.threaded_mode_long} />,
|
meta: formatMessage(messages.threaded_mode_long),
|
||||||
name: 'threaded_mode',
|
name: 'threaded_mode',
|
||||||
on: advancedOptions.get('threaded_mode'),
|
text: formatMessage(messages.threaded_mode_short),
|
||||||
text: <FormattedMessage {...messages.threaded_mode_short} />,
|
|
||||||
},
|
},
|
||||||
] : null}
|
] : null}
|
||||||
onChange={onChangeAdvancedOption}
|
onChange={onChangeAdvancedOption}
|
||||||
|
renderItemContents={this.renderToggleItemContents}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
onModalOpen={onModalOpen}
|
onModalOpen={onModalOpen}
|
||||||
title={intl.formatMessage(messages.advanced_options_icon_title)}
|
title={formatMessage(messages.advanced_options_icon_title)}
|
||||||
|
closeOnChange={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,46 +1,19 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import Dropdown from './dropdown';
|
import Dropdown from './dropdown';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
change_privacy: {
|
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||||
defaultMessage: 'Adjust status privacy',
|
public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for all, shown in public timelines' },
|
||||||
id: 'privacy.change',
|
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||||
},
|
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but not in public timelines' },
|
||||||
direct_long: {
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||||
defaultMessage: 'Visible for mentioned users only',
|
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
||||||
id: 'privacy.direct.long',
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||||
},
|
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
||||||
direct_short: {
|
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
||||||
defaultMessage: 'Direct',
|
|
||||||
id: 'privacy.direct.short',
|
|
||||||
},
|
|
||||||
private_long: {
|
|
||||||
defaultMessage: 'Visible for followers only',
|
|
||||||
id: 'privacy.private.long',
|
|
||||||
},
|
|
||||||
private_short: {
|
|
||||||
defaultMessage: 'Followers-only',
|
|
||||||
id: 'privacy.private.short',
|
|
||||||
},
|
|
||||||
public_long: {
|
|
||||||
defaultMessage: 'Visible for all, shown in public timelines',
|
|
||||||
id: 'privacy.public.long',
|
|
||||||
},
|
|
||||||
public_short: {
|
|
||||||
defaultMessage: 'Public',
|
|
||||||
id: 'privacy.public.short',
|
|
||||||
},
|
|
||||||
unlisted_long: {
|
|
||||||
defaultMessage: 'Visible for all, but not in public timelines',
|
|
||||||
id: 'privacy.unlisted.long',
|
|
||||||
},
|
|
||||||
unlisted_short: {
|
|
||||||
defaultMessage: 'Unlisted',
|
|
||||||
id: 'privacy.unlisted.short',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @injectIntl
|
export default @injectIntl
|
||||||
|
@ -53,40 +26,39 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
noDirect: PropTypes.bool,
|
noDirect: PropTypes.bool,
|
||||||
noModal: PropTypes.bool,
|
|
||||||
container: PropTypes.func,
|
container: PropTypes.func,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, noModal, container, intl } = this.props;
|
const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, intl: { formatMessage } } = this.props;
|
||||||
|
|
||||||
// We predefine our privacy items so that we can easily pick the
|
// We predefine our privacy items so that we can easily pick the
|
||||||
// dropdown icon later.
|
// dropdown icon later.
|
||||||
const privacyItems = {
|
const privacyItems = {
|
||||||
direct: {
|
direct: {
|
||||||
icon: 'envelope',
|
icon: 'envelope',
|
||||||
meta: <FormattedMessage {...messages.direct_long} />,
|
meta: formatMessage(messages.direct_long),
|
||||||
name: 'direct',
|
name: 'direct',
|
||||||
text: <FormattedMessage {...messages.direct_short} />,
|
text: formatMessage(messages.direct_short),
|
||||||
},
|
},
|
||||||
private: {
|
private: {
|
||||||
icon: 'lock',
|
icon: 'lock',
|
||||||
meta: <FormattedMessage {...messages.private_long} />,
|
meta: formatMessage(messages.private_long),
|
||||||
name: 'private',
|
name: 'private',
|
||||||
text: <FormattedMessage {...messages.private_short} />,
|
text: formatMessage(messages.private_short),
|
||||||
},
|
},
|
||||||
public: {
|
public: {
|
||||||
icon: 'globe',
|
icon: 'globe',
|
||||||
meta: <FormattedMessage {...messages.public_long} />,
|
meta: formatMessage(messages.public_long),
|
||||||
name: 'public',
|
name: 'public',
|
||||||
text: <FormattedMessage {...messages.public_short} />,
|
text: formatMessage(messages.public_short),
|
||||||
},
|
},
|
||||||
unlisted: {
|
unlisted: {
|
||||||
icon: 'unlock',
|
icon: 'unlock',
|
||||||
meta: <FormattedMessage {...messages.unlisted_long} />,
|
meta: formatMessage(messages.unlisted_long),
|
||||||
name: 'unlisted',
|
name: 'unlisted',
|
||||||
text: <FormattedMessage {...messages.unlisted_short} />,
|
text: formatMessage(messages.unlisted_short),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -104,9 +76,8 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
onModalOpen={onModalOpen}
|
onModalOpen={onModalOpen}
|
||||||
title={intl.formatMessage(messages.change_privacy)}
|
title={formatMessage(messages.change_privacy)}
|
||||||
container={container}
|
container={container}
|
||||||
noModal={noModal}
|
|
||||||
value={value}
|
value={value}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,24 +7,22 @@ import Avatar from 'flavours/glitch/components/avatar';
|
||||||
import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
|
import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
|
||||||
import DisplayName from 'flavours/glitch/components/display_name';
|
import DisplayName from 'flavours/glitch/components/display_name';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Icon from 'flavours/glitch/components/icon';
|
import IconButton from 'flavours/glitch/components/icon_button';
|
||||||
import Link from 'flavours/glitch/components/link';
|
|
||||||
import Toggle from 'react-toggle';
|
|
||||||
|
|
||||||
export default class ActionsModal extends ImmutablePureComponent {
|
export default class ActionsModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
|
onClick: PropTypes.func,
|
||||||
actions: PropTypes.arrayOf(PropTypes.shape({
|
actions: PropTypes.arrayOf(PropTypes.shape({
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
href: PropTypes.string,
|
href: PropTypes.string,
|
||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
meta: PropTypes.node,
|
meta: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
on: PropTypes.bool,
|
text: PropTypes.string,
|
||||||
onPassiveClick: PropTypes.func,
|
|
||||||
text: PropTypes.node,
|
|
||||||
})),
|
})),
|
||||||
|
renderItemContents: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderAction = (action, i) => {
|
renderAction = (action, i) => {
|
||||||
|
@ -32,57 +30,26 @@ export default class ActionsModal extends ImmutablePureComponent {
|
||||||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { icon = null, text, meta = null, active = false, href = '#' } = action;
|
||||||
active,
|
let contents = this.props.renderItemContents && this.props.renderItemContents(action, i);
|
||||||
href,
|
|
||||||
icon,
|
if (!contents) {
|
||||||
meta,
|
contents = (
|
||||||
name,
|
<React.Fragment>
|
||||||
on,
|
{icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' inverted />}
|
||||||
onClick,
|
<div>
|
||||||
onPassiveClick,
|
<div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div>
|
||||||
text,
|
<div>{meta}</div>
|
||||||
} = action;
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={name || i}>
|
<li key={`${text}-${i}`}>
|
||||||
<Link
|
<a href={href} target='_blank' rel='noopener noreferrer' onClick={this.props.onClick} data-index={i} className={classNames('link', { active })}>
|
||||||
className={classNames('link', { active })}
|
{contents}
|
||||||
href={href}
|
</a>
|
||||||
onClick={on !== null && typeof on !== 'undefined' && onPassiveClick || onClick}
|
|
||||||
role={onClick ? 'button' : null}
|
|
||||||
>
|
|
||||||
{function () {
|
|
||||||
|
|
||||||
// We render a `<Toggle>` if we were provided an `on`
|
|
||||||
// property, and otherwise show an `<Icon>` if available.
|
|
||||||
switch (true) {
|
|
||||||
case on !== null && typeof on !== 'undefined':
|
|
||||||
return (
|
|
||||||
<Toggle
|
|
||||||
checked={on}
|
|
||||||
onChange={onPassiveClick || onClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case !!icon:
|
|
||||||
return (
|
|
||||||
<Icon
|
|
||||||
className='icon'
|
|
||||||
fixedWidth
|
|
||||||
id={icon}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}()}
|
|
||||||
{meta ? (
|
|
||||||
<div>
|
|
||||||
<strong>{text}</strong>
|
|
||||||
{meta}
|
|
||||||
</div>
|
|
||||||
) : <div>{text}</div>}
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue