Progress
This commit is contained in:
parent
3d0a85cde4
commit
2bd344a594
@ -1,3 +1,4 @@
|
|||||||
|
// : todo : fill, stroke colors
|
||||||
const GlobeIcon = ({
|
const GlobeIcon = ({
|
||||||
className = '',
|
className = '',
|
||||||
width = '12px',
|
width = '12px',
|
||||||
|
@ -139,6 +139,7 @@ export default class Button extends PureComponent {
|
|||||||
backgroundColorBrand_onHover: color === COLORS.brand && outline,
|
backgroundColorBrand_onHover: color === COLORS.brand && outline,
|
||||||
colorWhite_onHover: !!children && color === COLORS.brand && outline,
|
colorWhite_onHover: !!children && color === COLORS.brand && outline,
|
||||||
|
|
||||||
|
fillColorSecondary: !!icon && color === COLORS.secondary,
|
||||||
fillColorWhite: !!icon && color === COLORS.white,
|
fillColorWhite: !!icon && color === COLORS.white,
|
||||||
fillColorBrand: !!icon && color === COLORS.brand,
|
fillColorBrand: !!icon && color === COLORS.brand,
|
||||||
fillColorWhite_onHover: !!icon && color === COLORS.brand && outline,
|
fillColorWhite_onHover: !!icon && color === COLORS.brand && outline,
|
||||||
|
@ -58,7 +58,7 @@ class ModalLayout extends PureComponent {
|
|||||||
const childrenContainerClasses = cx({
|
const childrenContainerClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
heightMax80VH: 1,
|
heightMax80VH: 1,
|
||||||
overflowScroll: 1,
|
overflowYScroll: 1,
|
||||||
px15: !noPadding,
|
px15: !noPadding,
|
||||||
py10: !noPadding,
|
py10: !noPadding,
|
||||||
})
|
})
|
||||||
@ -77,6 +77,7 @@ class ModalLayout extends PureComponent {
|
|||||||
title={intl.formatMessage(messages.close)}
|
title={intl.formatMessage(messages.close)}
|
||||||
className={_s.marginLeftAuto}
|
className={_s.marginLeftAuto}
|
||||||
onClick={this.onHandleCloseModal}
|
onClick={this.onHandleCloseModal}
|
||||||
|
color='secondary'
|
||||||
icon='close'
|
icon='close'
|
||||||
iconWidth='10px'
|
iconWidth='10px'
|
||||||
iconWidth='10px'
|
iconWidth='10px'
|
||||||
|
@ -2,8 +2,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import DisplayName from '../../../../components/display_name';
|
import DisplayName from './display_name';
|
||||||
import Icon from '../../../../components/icon';
|
import Icon from './icon';
|
||||||
|
|
||||||
export default class MovedNote extends ImmutablePureComponent {
|
export default class MovedNote extends ImmutablePureComponent {
|
||||||
|
|
@ -8,6 +8,8 @@ import Button from '../button'
|
|||||||
import Divider from '../divider'
|
import Divider from '../divider'
|
||||||
import Heading from '../heading'
|
import Heading from '../heading'
|
||||||
import Icon from '../icon'
|
import Icon from '../icon'
|
||||||
|
import Input from '../input'
|
||||||
|
import Switch from '../switch'
|
||||||
import Text from '../text'
|
import Text from '../text'
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@ -33,11 +35,26 @@ class NotificationFilterPanel extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout title={intl.formatMessage(messages.title)}>
|
<PanelLayout title={intl.formatMessage(messages.title)}>
|
||||||
<Text>Date</Text>
|
<Text>Start Date</Text>
|
||||||
<Text>Verified</Text>
|
<Text>End Date</Text>
|
||||||
<Text>Users</Text>
|
|
||||||
<Text>Status Id</Text>
|
<Divider small />
|
||||||
<Text>Only People I Follow</Text>
|
|
||||||
|
<Input title='From specific user(s)' small />
|
||||||
|
|
||||||
|
<Input title='From a specific status' small />
|
||||||
|
|
||||||
|
<Divider small />
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
id='notifications-verified'
|
||||||
|
label='Only Verified Users'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
id='notifications-verified'
|
||||||
|
label='Only People I Follow'
|
||||||
|
/>
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -54,10 +54,10 @@ class TrendsPanel extends ImmutablePureComponent {
|
|||||||
{ /* trends && trends.map(hashtag => (
|
{ /* trends && trends.map(hashtag => (
|
||||||
<TrendingItem key={hashtag.get('name')} hashtag={hashtag} />
|
<TrendingItem key={hashtag.get('name')} hashtag={hashtag} />
|
||||||
)) */ }
|
)) */ }
|
||||||
<TrendingItem />
|
<TrendingItem index='1' />
|
||||||
<TrendingItem />
|
<TrendingItem index='2' />
|
||||||
<TrendingItem />
|
<TrendingItem index='3' />
|
||||||
<TrendingItem />
|
<TrendingItem index='4' />
|
||||||
</div>
|
</div>
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl'
|
||||||
import classNames from 'classnames';
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import { Map as ImmutableMap } from 'immutable'
|
||||||
import detectPassiveEvents from 'detect-passive-events';
|
import classNames from 'classnames'
|
||||||
import Overlay from 'react-overlays/lib/Overlay';
|
import { createSelector } from 'reselect'
|
||||||
import { EmojiPicker as EmojiPickerAsync } from '../../../ui/util/async_components';
|
import detectPassiveEvents from 'detect-passive-events'
|
||||||
import { buildCustomEmojis } from '../../../../components/emoji/emoji';
|
import { changeSetting } from '../../actions/settings'
|
||||||
|
import { useEmoji } from '../../actions/emojis'
|
||||||
|
import { EmojiPicker as EmojiPickerAsync } from '../../features/ui/util/async_components'
|
||||||
|
import { buildCustomEmojis } from '../emoji/emoji'
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
|
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
|
||||||
@ -22,13 +25,13 @@ const messages = defineMessages({
|
|||||||
objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' },
|
objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' },
|
||||||
symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' },
|
symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' },
|
||||||
flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' },
|
flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' },
|
||||||
});
|
})
|
||||||
|
|
||||||
const assetHost = process.env.CDN_HOST || '';
|
const assetHost = process.env.CDN_HOST || ''
|
||||||
let EmojiPicker, Emoji; // load asynchronously
|
let EmojiPicker, Emoji // load asynchronously
|
||||||
|
|
||||||
const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`;
|
const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`
|
||||||
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
|
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false
|
||||||
|
|
||||||
const categoriesSort = [
|
const categoriesSort = [
|
||||||
'recent',
|
'recent',
|
||||||
@ -93,9 +96,9 @@ class ModifierPickerMenu extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
|
<div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
|
||||||
<button onClick={this.handleClick} data-index={1}>
|
<button onClick={this.handleClick} data-index={1}>
|
||||||
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} />
|
{/*<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} />*/}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={this.handleClick} data-index={2}>
|
{/*<button onClick={this.handleClick} data-index={2}>
|
||||||
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} />
|
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={this.handleClick} data-index={3}>
|
<button onClick={this.handleClick} data-index={3}>
|
||||||
@ -109,7 +112,7 @@ class ModifierPickerMenu extends PureComponent {
|
|||||||
</button>
|
</button>
|
||||||
<button onClick={this.handleClick} data-index={6}>
|
<button onClick={this.handleClick} data-index={6}>
|
||||||
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} />
|
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} />
|
||||||
</button>
|
</button>*/}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -144,7 +147,7 @@ class ModifierPicker extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='emoji-picker-dropdown__modifiers'>
|
<div className='emoji-picker-dropdown__modifiers'>
|
||||||
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
|
{ /* <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} /> */ }
|
||||||
<ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />
|
<ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -162,7 +165,6 @@ class EmojiPickerMenu extends ImmutablePureComponent {
|
|||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
onPick: PropTypes.func.isRequired,
|
onPick: PropTypes.func.isRequired,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
placement: PropTypes.string,
|
|
||||||
arrowOffsetLeft: PropTypes.string,
|
arrowOffsetLeft: PropTypes.string,
|
||||||
arrowOffsetTop: PropTypes.string,
|
arrowOffsetTop: PropTypes.string,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
@ -178,7 +180,6 @@ class EmojiPickerMenu extends ImmutablePureComponent {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
modifierOpen: false,
|
modifierOpen: false,
|
||||||
placement: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleDocumentClick = e => {
|
handleDocumentClick = e => {
|
||||||
@ -252,7 +253,7 @@ class EmojiPickerMenu extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const title = intl.formatMessage(messages.emoji);
|
const title = intl.formatMessage(messages.emoji);
|
||||||
const { modifierOpen } = this.state;
|
const { modifierOpen } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
|
<div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
|
||||||
@ -289,9 +290,85 @@ class EmojiPickerMenu extends ImmutablePureComponent {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const perLine = 8
|
||||||
|
const lines = 2
|
||||||
|
|
||||||
|
const DEFAULTS = [
|
||||||
|
'+1',
|
||||||
|
'grinning',
|
||||||
|
'kissing_heart',
|
||||||
|
'heart_eyes',
|
||||||
|
'laughing',
|
||||||
|
'stuck_out_tongue_winking_eye',
|
||||||
|
'sweat_smile',
|
||||||
|
'joy',
|
||||||
|
'yum',
|
||||||
|
'disappointed',
|
||||||
|
'thinking_face',
|
||||||
|
'weary',
|
||||||
|
'sob',
|
||||||
|
'sunglasses',
|
||||||
|
'heart',
|
||||||
|
'ok_hand',
|
||||||
|
];
|
||||||
|
|
||||||
|
const getFrequentlyUsedEmojis = createSelector([
|
||||||
|
state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()),
|
||||||
|
], emojiCounters => {
|
||||||
|
let emojis = emojiCounters
|
||||||
|
.keySeq()
|
||||||
|
.sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b))
|
||||||
|
.reverse()
|
||||||
|
.slice(0, perLine * lines)
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
if (emojis.length < DEFAULTS.length) {
|
||||||
|
let uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji));
|
||||||
|
emojis = emojis.concat(uniqueDefaults.slice(0, DEFAULTS.length - emojis.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return emojis;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCustomEmojis = createSelector([
|
||||||
|
state => state.get('custom_emojis'),
|
||||||
|
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
|
||||||
|
const aShort = a.get('shortcode').toLowerCase();
|
||||||
|
const bShort = b.get('shortcode').toLowerCase();
|
||||||
|
|
||||||
|
if (aShort < bShort) {
|
||||||
|
return -1;
|
||||||
|
} else if (aShort > bShort ) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
custom_emojis: getCustomEmojis(state),
|
||||||
|
skinTone: state.getIn(['settings', 'skinTone']),
|
||||||
|
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
|
||||||
|
onSkinTone: skinTone => {
|
||||||
|
dispatch(changeSetting(['skinTone'], skinTone));
|
||||||
|
},
|
||||||
|
|
||||||
|
onPickEmoji: emoji => {
|
||||||
|
dispatch(useEmoji(emoji));
|
||||||
|
|
||||||
|
if (onPickEmoji) {
|
||||||
|
onPickEmoji(emoji);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default
|
export default
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class EmojiPickerDropdown extends ImmutablePureComponent {
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
class EmojiPickerPopover extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
custom_emojis: ImmutablePropTypes.list,
|
custom_emojis: ImmutablePropTypes.list,
|
||||||
@ -307,11 +384,7 @@ class EmojiPickerDropdown extends ImmutablePureComponent {
|
|||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
setRef = (c) => {
|
componentWillMount = () => {
|
||||||
this.dropdown = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
onShowDropdown = ({ target }) => {
|
|
||||||
this.setState({ active: true });
|
this.setState({ active: true });
|
||||||
|
|
||||||
if (!EmojiPicker) {
|
if (!EmojiPicker) {
|
||||||
@ -326,9 +399,6 @@ class EmojiPickerDropdown extends ImmutablePureComponent {
|
|||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { top } = target.getBoundingClientRect();
|
|
||||||
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onHideDropdown = () => {
|
onHideDropdown = () => {
|
||||||
@ -351,30 +421,19 @@ class EmojiPickerDropdown extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTargetRef = c => {
|
|
||||||
this.target = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
findTarget = () => {
|
|
||||||
return this.target;
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props;
|
const {
|
||||||
const title = intl.formatMessage(messages.emoji);
|
intl,
|
||||||
const { active, loading, placement } = this.state;
|
onPickEmoji,
|
||||||
|
onSkinTone,
|
||||||
|
skinTone,
|
||||||
|
frequentlyUsedEmojis
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const { active, loading } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
|
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
|
||||||
<div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
|
|
||||||
<img
|
|
||||||
className={classNames('emojione', { 'pulse-loading': active && loading })}
|
|
||||||
alt='🙂'
|
|
||||||
src={`${assetHost}/emoji/1f602.svg`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Overlay show={active} placement={placement} target={this.findTarget}>
|
|
||||||
<EmojiPickerMenu
|
<EmojiPickerMenu
|
||||||
custom_emojis={this.props.custom_emojis}
|
custom_emojis={this.props.custom_emojis}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@ -384,7 +443,6 @@ class EmojiPickerDropdown extends ImmutablePureComponent {
|
|||||||
skinTone={skinTone}
|
skinTone={skinTone}
|
||||||
frequentlyUsedEmojis={frequentlyUsedEmojis}
|
frequentlyUsedEmojis={frequentlyUsedEmojis}
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ import BundleModalError from '../bundle_modal_error'
|
|||||||
import PopoverBase from './popover_base'
|
import PopoverBase from './popover_base'
|
||||||
import ContentWarningPopover from './content_warning_popover'
|
import ContentWarningPopover from './content_warning_popover'
|
||||||
import DatePickerPopover from './date_picker_popover'
|
import DatePickerPopover from './date_picker_popover'
|
||||||
|
import EmojiPickerPopover from './emoji_picker_popover'
|
||||||
import GroupInfoPopover from './group_info_popover'
|
import GroupInfoPopover from './group_info_popover'
|
||||||
import ProfileOptionsPopover from './profile_options_popover'
|
import ProfileOptionsPopover from './profile_options_popover'
|
||||||
import SearchPopover from './search_popover'
|
import SearchPopover from './search_popover'
|
||||||
@ -19,6 +20,7 @@ const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : fal
|
|||||||
const POPOVER_COMPONENTS = {
|
const POPOVER_COMPONENTS = {
|
||||||
CONTENT_WARNING: () => Promise.resolve({ default: ContentWarningPopover }),
|
CONTENT_WARNING: () => Promise.resolve({ default: ContentWarningPopover }),
|
||||||
DATE_PICKER: () => Promise.resolve({ default: DatePickerPopover }),
|
DATE_PICKER: () => Promise.resolve({ default: DatePickerPopover }),
|
||||||
|
EMOJI_PICKER: () => Promise.resolve({ default: EmojiPickerPopover }),
|
||||||
GROUP_INFO: () => GroupInfoPopover,
|
GROUP_INFO: () => GroupInfoPopover,
|
||||||
PROFILE_OPTIONS: () => Promise.resolve({ default: ProfileOptionsPopover }),
|
PROFILE_OPTIONS: () => Promise.resolve({ default: ProfileOptionsPopover }),
|
||||||
SEARCH: () => Promise.resolve({ default: SearchPopover }),
|
SEARCH: () => Promise.resolve({ default: SearchPopover }),
|
||||||
|
@ -1,259 +0,0 @@
|
|||||||
import { injectIntl, defineMessages } from 'react-intl'
|
|
||||||
import spring from 'react-motion/lib/spring'
|
|
||||||
import detectPassiveEvents from 'detect-passive-events'
|
|
||||||
import classNames from 'classnames'
|
|
||||||
import Overlay from 'react-overlays/lib/Overlay'
|
|
||||||
import { changeComposeVisibility } from '../../actions/compose'
|
|
||||||
import { openModal, closeModal } from '../../actions/modal'
|
|
||||||
import { isUserTouching } from '../../utils/is_mobile'
|
|
||||||
import Motion from '../../features/ui/util/optional_motion'
|
|
||||||
import Icon from '../icon'
|
|
||||||
import ComposeExtraButton from '../../features/compose/components/compose_extra_button'
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
|
||||||
public_long: { id: 'privacy.public.long', defaultMessage: 'Post to public timelines' },
|
|
||||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
|
||||||
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' },
|
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
|
||||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Post to followers only' },
|
|
||||||
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
|
||||||
visibility: { id: 'privacy.visibility', defaultMessage: 'Visibility' },
|
|
||||||
})
|
|
||||||
|
|
||||||
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false
|
|
||||||
|
|
||||||
class PrivacyDropdownMenu extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
style: PropTypes.object,
|
|
||||||
items: PropTypes.array.isRequired,
|
|
||||||
value: PropTypes.string.isRequired,
|
|
||||||
placement: PropTypes.string.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
mounted: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDocumentClick = e => {
|
|
||||||
if (this.node && !this.node.contains(e.target)) {
|
|
||||||
this.props.onClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyDown = e => {
|
|
||||||
const { items } = this.props
|
|
||||||
const value = e.currentTarget.getAttribute('data-index')
|
|
||||||
const index = items.findIndex(item => {
|
|
||||||
return (item.value === value)
|
|
||||||
})
|
|
||||||
let element
|
|
||||||
|
|
||||||
switch(e.key) {
|
|
||||||
case 'Escape':
|
|
||||||
this.props.onClose()
|
|
||||||
break
|
|
||||||
case 'Enter':
|
|
||||||
this.handleClick(e)
|
|
||||||
break
|
|
||||||
case 'ArrowDown':
|
|
||||||
element = this.node.childNodes[index + 1]
|
|
||||||
if (element) {
|
|
||||||
element.focus()
|
|
||||||
this.props.onChange(element.getAttribute('data-index'))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'ArrowUp':
|
|
||||||
element = this.node.childNodes[index - 1]
|
|
||||||
if (element) {
|
|
||||||
element.focus()
|
|
||||||
this.props.onChange(element.getAttribute('data-index'))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'Home':
|
|
||||||
element = this.node.firstChild
|
|
||||||
if (element) {
|
|
||||||
element.focus()
|
|
||||||
this.props.onChange(element.getAttribute('data-index'))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'End':
|
|
||||||
element = this.node.lastChild
|
|
||||||
if (element) {
|
|
||||||
element.focus()
|
|
||||||
this.props.onChange(element.getAttribute('data-index'))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick = e => {
|
|
||||||
const value = e.currentTarget.getAttribute('data-index')
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
this.props.onClose()
|
|
||||||
this.props.onChange(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
document.addEventListener('click', this.handleDocumentClick, false)
|
|
||||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions)
|
|
||||||
if (this.focusedItem) this.focusedItem.focus()
|
|
||||||
this.setState({ mounted: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
document.removeEventListener('click', this.handleDocumentClick, false)
|
|
||||||
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef = c => {
|
|
||||||
this.node = c
|
|
||||||
}
|
|
||||||
|
|
||||||
setFocusRef = c => {
|
|
||||||
this.focusedItem = c
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { mounted } = this.state
|
|
||||||
const { style, items, placement, value } = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
|
|
||||||
{({ opacity, scaleX, scaleY }) => (
|
|
||||||
// It should not be transformed when mounting because the resulting
|
|
||||||
// size will be used to determine the coordinate of the menu by
|
|
||||||
// react-overlays
|
|
||||||
<div className={`privacy-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} role='listbox' ref={this.setRef}>
|
|
||||||
{items.map(item => (
|
|
||||||
<div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
|
|
||||||
<div className='privacy-dropdown__option__icon'>
|
|
||||||
<Icon id={item.icon} fixedWidth />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='privacy-dropdown__option__content'>
|
|
||||||
<strong>{item.text}</strong>
|
|
||||||
{item.meta}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Motion>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
isModalOpen: state.get('modal').modalType === 'ACTIONS',
|
|
||||||
value: state.getIn(['compose', 'privacy']),
|
|
||||||
})
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
|
|
||||||
onChange (value) {
|
|
||||||
dispatch(changeComposeVisibility(value))
|
|
||||||
},
|
|
||||||
|
|
||||||
isUserTouching,
|
|
||||||
onModalOpen: props => dispatch(openModal('ACTIONS', props)),
|
|
||||||
onModalClose: () => dispatch(closeModal()),
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
export default
|
|
||||||
@connect(mapStateToProps, mapDispatchToProps)
|
|
||||||
@injectIntl
|
|
||||||
class PrivacyDropdown extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
isUserTouching: PropTypes.func,
|
|
||||||
isModalOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalOpen: PropTypes.func,
|
|
||||||
onModalClose: PropTypes.func,
|
|
||||||
value: PropTypes.string.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
open: false,
|
|
||||||
placement: 'bottom',
|
|
||||||
}
|
|
||||||
|
|
||||||
handleToggle = ({ target }) => {
|
|
||||||
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 {
|
|
||||||
const { top } = target.getBoundingClientRect()
|
|
||||||
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' })
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyDown = e => {
|
|
||||||
switch(e.key) {
|
|
||||||
case 'Escape':
|
|
||||||
this.handleClose()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClose = () => {
|
|
||||||
this.setState({ open: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange = value => {
|
|
||||||
this.props.onChange(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount () {
|
|
||||||
const { intl: { formatMessage } } = this.props
|
|
||||||
|
|
||||||
this.options = [
|
|
||||||
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
|
||||||
{ icon: 'unlock', 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) },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { value, intl } = this.props
|
|
||||||
const { open, placement } = this.state
|
|
||||||
|
|
||||||
const valueOption = this.options.find(item => item.value === value)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PrivacyDropdownMenu
|
|
||||||
items={this.options}
|
|
||||||
value={value}
|
|
||||||
onClose={this.handleClose}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
placement={placement}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,9 +1,129 @@
|
|||||||
export default class StatusVisibilityPopover extends PureComponent {
|
import { injectIntl, defineMessages } from 'react-intl'
|
||||||
|
import classNames from 'classnames/bind'
|
||||||
|
import { changeComposeVisibility } from '../../actions/compose'
|
||||||
|
import { closePopover } from '../../actions/popover'
|
||||||
|
import PopoverLayout from './popover_layout'
|
||||||
|
import Icon from '../icon'
|
||||||
|
import Text from '../text'
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||||
|
public_long: { id: 'privacy.public.long', defaultMessage: 'Post to public timelines' },
|
||||||
|
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||||
|
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' },
|
||||||
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||||
|
private_long: { id: 'privacy.private.long', defaultMessage: 'Post to followers only' },
|
||||||
|
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
||||||
|
visibility: { id: 'privacy.visibility', defaultMessage: 'Visibility' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
value: state.getIn(['compose', 'privacy']),
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
||||||
|
onChange (value) {
|
||||||
|
dispatch(changeComposeVisibility(value))
|
||||||
|
dispatch(closePopover())
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
export default
|
||||||
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
@injectIntl
|
||||||
|
class StatusVisibilityDropdown extends PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = value => {
|
||||||
|
this.props.onChange(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
const { intl } = this.props
|
||||||
|
|
||||||
|
this.options = [
|
||||||
|
{
|
||||||
|
icon: 'globe',
|
||||||
|
value: 'public',
|
||||||
|
title: intl.formatMessage(messages.public_short),
|
||||||
|
subtitle: intl.formatMessage(messages.public_long)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'unlock',
|
||||||
|
value: 'unlisted',
|
||||||
|
title: intl.formatMessage(messages.unlisted_short),
|
||||||
|
subtitle: intl.formatMessage(messages.unlisted_long)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'lock',
|
||||||
|
value: 'private',
|
||||||
|
title: intl.formatMessage(messages.private_short),
|
||||||
|
subtitle: intl.formatMessage(messages.private_long)
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
const { value } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<PopoverLayout className={_s.width240PX}>
|
||||||
{ /* */ }
|
<div className={[_s.default].join(' ')}>
|
||||||
|
{
|
||||||
|
this.options.map((option, i) => {
|
||||||
|
const isActive = option.value === value
|
||||||
|
const isLast = i === this.options.length - 1
|
||||||
|
|
||||||
|
const containerClasses = cx({
|
||||||
|
default: 1,
|
||||||
|
flexRow: 1,
|
||||||
|
py10: 1,
|
||||||
|
cursorPointer: 1,
|
||||||
|
borderBottom1PX: !isLast,
|
||||||
|
borderColorSecondary: !isLast,
|
||||||
|
backgroundSubtle_onHover: !isActive,
|
||||||
|
backgroundColorBrand: isActive,
|
||||||
|
})
|
||||||
|
|
||||||
|
const iconClasses = cx({
|
||||||
|
ml10: 1,
|
||||||
|
mt2: 1,
|
||||||
|
fillColorWhite: isActive,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role='button'
|
||||||
|
onClick={() => this.handleChange(option.value)}
|
||||||
|
className={containerClasses}
|
||||||
|
>
|
||||||
|
<Icon id={option.icon} height='16px' width='16px' className={iconClasses} />
|
||||||
|
<div className={[_s.default, _s.px10, _s.pt2].join(' ')}>
|
||||||
|
<Text size='medium' color={isActive ? 'white' : 'primary'}>
|
||||||
|
{option.title}
|
||||||
|
</Text>
|
||||||
|
<Text size='small' weight='medium' color={isActive ? 'white' : 'secondary'}>
|
||||||
|
{option.subtitle}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
</PopoverLayout>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -242,6 +242,8 @@ class ProfileHeader extends ImmutablePureComponent {
|
|||||||
console.log("buttonOptions:", buttonText, buttonOptions)
|
console.log("buttonOptions:", buttonText, buttonOptions)
|
||||||
|
|
||||||
|
|
||||||
|
// : todo : "follows you", "mutual follow"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[_s.default, _s.z1, _s.width100PC].join(' ')}>
|
<div className={[_s.default, _s.z1, _s.width100PC].join(' ')}>
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ export default class SidebarSectionItem extends PureComponent {
|
|||||||
buttonRef={buttonRef}
|
buttonRef={buttonRef}
|
||||||
onMouseEnter={() => this.handleOnMouseEnter()}
|
onMouseEnter={() => this.handleOnMouseEnter()}
|
||||||
onMouseLeave={() => this.handleOnMouseLeave()}
|
onMouseLeave={() => this.handleOnMouseLeave()}
|
||||||
className={[_s.default, _s.noUnderline, _s.cursorPointer, _s.width100PC, _s.alignItemsStart, _s.backgroundTransparent].join(' ')}
|
className={[_s.default, _s.noUnderline, _s.cursorPointer, _s.width100PC, _s.backgroundTransparent].join(' ')}
|
||||||
>
|
>
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
<div className={[_s.default]}>
|
<div className={[_s.default]}>
|
||||||
|
@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import punycode from 'punycode';
|
import punycode from 'punycode';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Icon from '../../../../components/icon';
|
import Icon from './icon';
|
||||||
|
|
||||||
const IDNA_PREFIX = 'xn--';
|
const IDNA_PREFIX = 'xn--';
|
||||||
|
|
@ -285,12 +285,18 @@ class StatusContent extends ImmutablePureComponent {
|
|||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
this.state.collapsed &&
|
this.state.collapsed &&
|
||||||
<button
|
<Button
|
||||||
className={[_s.default, _s.displayFlex, _s.cursorPointer, _s.py2, _s.text, _s.colorPrimary, _s.fontWeightBold, _s.fontSize15PX].join(' ')}
|
text
|
||||||
|
underlineOnHover
|
||||||
|
color='primary'
|
||||||
|
backgroundColor='none'
|
||||||
|
className={[_s.py2].join(' ')}
|
||||||
onClick={this.handleReadMore}
|
onClick={this.handleReadMore}
|
||||||
>
|
>
|
||||||
|
<Text size='medium' color='inherit' weight='bold'>
|
||||||
{intl.formatMessage(messages.readMore)}
|
{intl.formatMessage(messages.readMore)}
|
||||||
</button>
|
</Text>
|
||||||
|
</Button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -15,6 +15,7 @@ const cx = classNames.bind(_s)
|
|||||||
export default class TrendingItem extends ImmutablePureComponent {
|
export default class TrendingItem extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
index: PropTypes.number,
|
||||||
trend: ImmutablePropTypes.map.isRequired,
|
trend: ImmutablePropTypes.map.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ export default class TrendingItem extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { trend } = this.props
|
const { trend, index } = this.props
|
||||||
const { hovering } = this.state
|
const { hovering } = this.state
|
||||||
|
|
||||||
const subtitleClasses = cx({
|
const subtitleClasses = cx({
|
||||||
@ -44,7 +45,7 @@ export default class TrendingItem extends ImmutablePureComponent {
|
|||||||
underline: hovering,
|
underline: hovering,
|
||||||
})
|
})
|
||||||
|
|
||||||
return null;
|
// return null;
|
||||||
|
|
||||||
// : todo :
|
// : todo :
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ export default class TrendingItem extends ImmutablePureComponent {
|
|||||||
onMouseLeave={() => this.handleOnMouseLeave()}
|
onMouseLeave={() => this.handleOnMouseLeave()}
|
||||||
>
|
>
|
||||||
<div className={[_s.default, _s.flexRow, _s.mt5].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.mt5].join(' ')}>
|
||||||
<Text size='small' color='secondary'>1</Text>
|
<Text size='small' color='secondary'>{index}</Text>
|
||||||
<DotTextSeperator />
|
<DotTextSeperator />
|
||||||
<Text size='small' color='secondary' className={_s.ml5}>Politics</Text>
|
<Text size='small' color='secondary' className={_s.ml5}>Politics</Text>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,7 +58,7 @@ export default class TrendingItemCard extends ImmutablePureComponent {
|
|||||||
The best flower subscription services: BloomsyBox, Bouqs...
|
The best flower subscription services: BloomsyBox, Bouqs...
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<Image width='92px' height='92px' />
|
<Image width='92px' height='96px' />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -452,6 +452,8 @@ class Video extends PureComponent {
|
|||||||
// fullscreen
|
// fullscreen
|
||||||
// })}
|
// })}
|
||||||
|
|
||||||
|
// : todo spoiler :
|
||||||
|
|
||||||
const seekHandleClasses = cx({
|
const seekHandleClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
positionAbsolute: 1,
|
positionAbsolute: 1,
|
@ -2,9 +2,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
|||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import { List as ImmutableList } from 'immutable'
|
import { List as ImmutableList } from 'immutable'
|
||||||
import { injectIntl, defineMessages } from 'react-intl'
|
import { injectIntl, defineMessages } from 'react-intl'
|
||||||
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines'
|
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../actions/timelines'
|
||||||
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'
|
import { fetchAccountIdentityProofs } from '../actions/identity_proofs'
|
||||||
import StatusList from '../../components/status_list'
|
import StatusList from '../components/status_list'
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
empty: { id: 'empty_column.account_timeline', defaultMessage: 'No gabs here!' },
|
empty: { id: 'empty_column.account_timeline', defaultMessage: 'No gabs here!' },
|
@ -1,105 +0,0 @@
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import InnerHeader from '../inner_header';
|
|
||||||
import MovedNote from '../moved_note';
|
|
||||||
|
|
||||||
export default class Header extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.map,
|
|
||||||
identity_proofs: ImmutablePropTypes.list,
|
|
||||||
onFollow: PropTypes.func.isRequired,
|
|
||||||
onBlock: PropTypes.func.isRequired,
|
|
||||||
onMention: PropTypes.func.isRequired,
|
|
||||||
onRepostToggle: PropTypes.func.isRequired,
|
|
||||||
onReport: PropTypes.func.isRequired,
|
|
||||||
onMute: PropTypes.func.isRequired,
|
|
||||||
onBlockDomain: PropTypes.func.isRequired,
|
|
||||||
onUnblockDomain: PropTypes.func.isRequired,
|
|
||||||
onEndorseToggle: PropTypes.func.isRequired,
|
|
||||||
onAddToList: PropTypes.func.isRequired,
|
|
||||||
domain: PropTypes.string.isRequired,
|
|
||||||
username: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
static contextTypes = {
|
|
||||||
router: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleFollow = () => {
|
|
||||||
this.props.onFollow(this.props.account);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleBlock = () => {
|
|
||||||
this.props.onBlock(this.props.account);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMention = () => {
|
|
||||||
this.props.onMention(this.props.account, this.context.router.history);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleReport = () => {
|
|
||||||
this.props.onReport(this.props.account);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRepostToggle = () => {
|
|
||||||
this.props.onRepostToggle(this.props.account);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMute = () => {
|
|
||||||
this.props.onMute(this.props.account);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleBlockDomain = () => {
|
|
||||||
const domain = this.props.account.get('acct').split('@')[1];
|
|
||||||
|
|
||||||
if (!domain) return;
|
|
||||||
|
|
||||||
this.props.onBlockDomain(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUnblockDomain = () => {
|
|
||||||
const domain = this.props.account.get('acct').split('@')[1];
|
|
||||||
|
|
||||||
if (!domain) return;
|
|
||||||
|
|
||||||
this.props.onUnblockDomain(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEndorseToggle = () => {
|
|
||||||
this.props.onEndorseToggle(this.props.account);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleAddToList = () => {
|
|
||||||
this.props.onAddToList(this.props.account);
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { account, identity_proofs } = this.props;
|
|
||||||
const moved = (account) ? account.get('moved') : false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='account-timeline__header'>
|
|
||||||
{ moved && <MovedNote from={account} to={account.get('moved')} /> }
|
|
||||||
|
|
||||||
<InnerHeader
|
|
||||||
account={account}
|
|
||||||
identity_proofs={identity_proofs}
|
|
||||||
onFollow={this.handleFollow}
|
|
||||||
onBlock={this.handleBlock}
|
|
||||||
onMention={this.handleMention}
|
|
||||||
onRepostToggle={this.handleRepostToggle}
|
|
||||||
onReport={this.handleReport}
|
|
||||||
onMute={this.handleMute}
|
|
||||||
onBlockDomain={this.handleBlockDomain}
|
|
||||||
onUnblockDomain={this.handleUnblockDomain}
|
|
||||||
onEndorseToggle={this.handleEndorseToggle}
|
|
||||||
onAddToList={this.handleAddToList}
|
|
||||||
domain={this.props.domain}
|
|
||||||
username={this.props.username}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
.account-timeline {
|
|
||||||
&__header {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './header'
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './inner_header'
|
|
@ -1,337 +0,0 @@
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { NavLink } from 'react-router-dom';
|
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import Button from '../../../components/button';
|
|
||||||
import { autoPlayGif, me, isStaff } from '../../../initial_state';
|
|
||||||
import Avatar from '../../../components/avatar';
|
|
||||||
import { shortNumberFormat } from '../../../utils/numbers';
|
|
||||||
import ProfileInfoPanel from './profile_info_panel/profile_info_panel';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
|
||||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
|
|
||||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
|
||||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
|
||||||
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
|
|
||||||
account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
|
|
||||||
mention: { id: 'account.mention', defaultMessage: 'Mention' },
|
|
||||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
|
||||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
|
||||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
|
||||||
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
|
||||||
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
|
||||||
media: { id: 'account.media', defaultMessage: 'Media' },
|
|
||||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
|
||||||
hideReposts: { id: 'account.hide_reblogs', defaultMessage: 'Hide reposts from @{name}' },
|
|
||||||
showReposts: { id: 'account.show_reblogs', defaultMessage: 'Show reposts from @{name}' },
|
|
||||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
|
||||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
|
||||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
|
||||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
|
|
||||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
|
||||||
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
|
||||||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
|
||||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
|
||||||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
|
||||||
accountFollowsYou: { id: 'account.follows_you', defaultMessage: 'Follows you' },
|
|
||||||
accountBlocked: { id: 'account.blocked', defaultMessage: 'Blocked' },
|
|
||||||
accountMuted: { id: 'account.muted', defaultMessage: 'Muted' },
|
|
||||||
domainBlocked: { id: 'account.domain_blocked', defaultMessage: 'Domain hidden' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default
|
|
||||||
@injectIntl
|
|
||||||
class Header extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.map,
|
|
||||||
identity_props: ImmutablePropTypes.list,
|
|
||||||
onFollow: PropTypes.func.isRequired,
|
|
||||||
onBlock: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
domain: PropTypes.string.isRequired,
|
|
||||||
username: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
isSmallScreen: (window.innerWidth <= 895),
|
|
||||||
}
|
|
||||||
|
|
||||||
openEditProfile = () => {
|
|
||||||
window.open('/settings/profile', '_blank');
|
|
||||||
}
|
|
||||||
|
|
||||||
isStatusesPageActive = (match, location) => {
|
|
||||||
if (!match) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !location.pathname.match(/\/(followers|following|favorites|pins)\/?$/);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount () {
|
|
||||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
window.removeEventListener('resize', this.handleResize);
|
|
||||||
}
|
|
||||||
|
|
||||||
onChat = () => {
|
|
||||||
const { account } = this.props;
|
|
||||||
|
|
||||||
axios.post('https://chat.gab.com/private-message', {
|
|
||||||
username: account.get('username'),
|
|
||||||
})
|
|
||||||
.then(function (response) {
|
|
||||||
console.log(response);
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleResize = debounce(() => {
|
|
||||||
this.setState({ isSmallScreen: (window.innerWidth <= 895) });
|
|
||||||
}, 5, {
|
|
||||||
trailing: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
makeMenu() {
|
|
||||||
const { account, intl, domain } = this.props;
|
|
||||||
|
|
||||||
let menu = [];
|
|
||||||
|
|
||||||
if (!account || !me) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('share' in navigator) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
|
||||||
menu.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.get('id') === me) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
|
|
||||||
menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
|
|
||||||
menu.push(null);
|
|
||||||
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
|
|
||||||
menu.push(null);
|
|
||||||
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
|
|
||||||
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
|
|
||||||
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
|
|
||||||
} else {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('acct') }), action: this.props.onMention });
|
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'following'])) {
|
|
||||||
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.hideReposts, { name: account.get('username') }), action: this.props.onRepostToggle });
|
|
||||||
} else {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.showReposts, { name: account.get('username') }), action: this.props.onRepostToggle });
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
|
|
||||||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
|
||||||
menu.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'muting'])) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
|
|
||||||
} else {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'blocking'])) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
|
|
||||||
} else {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock });
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.get('acct') !== account.get('username')) {
|
|
||||||
const domain = account.get('acct').split('@')[1];
|
|
||||||
|
|
||||||
menu.push(null);
|
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'domain_blocking'])) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
|
|
||||||
} else {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.get('id') !== me && isStaff) {
|
|
||||||
menu.push(null);
|
|
||||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
makeInfo() {
|
|
||||||
const { account, intl } = this.props;
|
|
||||||
|
|
||||||
let info = [];
|
|
||||||
|
|
||||||
if (!account || !me) return info;
|
|
||||||
|
|
||||||
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
|
|
||||||
info.push(<span key='followed_by' className='relationship-tag'>{intl.formatMessage(messages.accountFollowsYou)}</span>);
|
|
||||||
} else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
|
|
||||||
info.push(<span key='blocked' className='relationship-tag'>{intl.formatMessage(messages.accountBlocked)}</span>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
|
|
||||||
info.push(<span key='muted' className='relationship-tag'>{intl.formatMessage(messages.accountMuted)}</span>);
|
|
||||||
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
|
|
||||||
info.push(<span key='domain_blocked' className='relationship-tag'>{intl.formatMessage(messages.domainBlocked)}</span>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
};
|
|
||||||
|
|
||||||
getActionBtn() {
|
|
||||||
const { account, intl } = this.props;
|
|
||||||
|
|
||||||
let actionBtn = null;
|
|
||||||
|
|
||||||
if (!account || !me) return actionBtn;
|
|
||||||
|
|
||||||
if (me !== account.get('id')) {
|
|
||||||
if (!account.get('relationship')) { // Wait until the relationship is loaded
|
|
||||||
//
|
|
||||||
} else if (account.getIn(['relationship', 'requested'])) {
|
|
||||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
|
|
||||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
|
||||||
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
|
|
||||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
|
||||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return actionBtn;
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { account, intl, domain, username } = this.props;
|
|
||||||
const { isSmallScreen } = this.state;
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
return (
|
|
||||||
<div className='account__header'>
|
|
||||||
<div className='account__header__image account__header__image--none'></div>
|
|
||||||
<div className='account__header__bar'>
|
|
||||||
<div className='account__header__extra'>
|
|
||||||
<div className='account__header__avatar'></div>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
isSmallScreen &&
|
|
||||||
<div className='account-mobile-container account-mobile-container--nonuser'>
|
|
||||||
<ProfileInfoPanel username={username} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const info = this.makeInfo();
|
|
||||||
const actionBtn = this.getActionBtn();
|
|
||||||
const menu = this.makeMenu();
|
|
||||||
|
|
||||||
const content = { __html: account.get('note_emojified') };
|
|
||||||
const fields = account.get('fields');
|
|
||||||
const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
|
|
||||||
|
|
||||||
const headerImgSrc = autoPlayGif ? account.get('header') : account.get('header_static');
|
|
||||||
const headerMissing = (headerImgSrc.indexOf('/headers/original/missing.png') > -1);
|
|
||||||
|
|
||||||
const avatarSize = isSmallScreen ? 90 : 200;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })}>
|
|
||||||
<div className={classNames('account__header__image', { 'account__header__image--none': headerMissing })}>
|
|
||||||
<div className='account__header__info'>
|
|
||||||
{info}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<img src={headerImgSrc} alt='' className='parallax' />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account__header__bar'>
|
|
||||||
<div className='account__header__extra'>
|
|
||||||
|
|
||||||
<div className='account__header__avatar'>
|
|
||||||
<Avatar account={account} size={avatarSize} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account__header__extra__links'>
|
|
||||||
|
|
||||||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/${account.get('acct')}`} title={intl.formatNumber(account.get('statuses_count'))}>
|
|
||||||
{shortNumberFormat(account.get('statuses_count'))}
|
|
||||||
<FormattedMessage id='account.posts' defaultMessage='Gabs' />
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
<NavLink exact activeClassName='active' to={`/${account.get('acct')}/following`} title={intl.formatNumber(account.get('following_count'))}>
|
|
||||||
{shortNumberFormat(account.get('following_count'))}
|
|
||||||
<FormattedMessage id='account.follows' defaultMessage='Follows' />
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
<NavLink exact activeClassName='active' to={`/${account.get('acct')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
|
|
||||||
{shortNumberFormat(account.get('followers_count'))}
|
|
||||||
<FormattedMessage id='account.followers' defaultMessage='Followers' />
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
{
|
|
||||||
account.get('id') === me &&
|
|
||||||
<div>
|
|
||||||
<NavLink exact activeClassName='active' to={`/${account.get('acct')}/favorites`} title={intl.formatNumber(account.get('favourite_count'))}>
|
|
||||||
{ /* : TODO : shortNumberFormat(account.get('favourite_count')) */ }
|
|
||||||
<span>•</span>
|
|
||||||
<FormattedMessage id='navigation_bar.favorites' defaultMessage='Favorites' />
|
|
||||||
</NavLink>
|
|
||||||
<NavLink exact activeClassName='active' to={`/${account.get('acct')}/pins`} title={intl.formatNumber(account.get('pinned_count'))}>
|
|
||||||
{ /* : TODO : shortNumberFormat(account.get('pinned_count')) */ }
|
|
||||||
<span>•</span>
|
|
||||||
<FormattedMessage id='navigation_bar.pins' defaultMessage='Pins' />
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
isSmallScreen &&
|
|
||||||
<div className='account-mobile-container'>
|
|
||||||
<ProfileInfoPanel username={username} account={account} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
me &&
|
|
||||||
<div className='account__header__extra__buttons'>
|
|
||||||
{actionBtn}
|
|
||||||
{account.get('id') !== me &&
|
|
||||||
<Button className='button button--alternative-2' onClick={this.props.onMention}>
|
|
||||||
<FormattedMessage id='account.mention' defaultMessage='Mention' values={{
|
|
||||||
name: account.get('acct')
|
|
||||||
}} />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
{/*<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />*/}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
.relationship-tag {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
vertical-align: top;
|
|
||||||
color: $primary-text-color;
|
|
||||||
background-color: $base-overlay-background;
|
|
||||||
text-transform: uppercase;
|
|
||||||
padding: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
opacity: 0.7;
|
|
||||||
|
|
||||||
@include text-sizing(11px, 500);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './moved_note'
|
|
@ -1,55 +0,0 @@
|
|||||||
.moved-note {
|
|
||||||
padding: 14px 10px 16px 10px;
|
|
||||||
background: lighten($ui-base-color, 4%);
|
|
||||||
border-top: 1px solid lighten($ui-base-color, 8%);
|
|
||||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
|
||||||
|
|
||||||
&__message {
|
|
||||||
position: relative;
|
|
||||||
margin-left: 58px;
|
|
||||||
color: $dark-text-color;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
>span {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
@include text-overflow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__icon-wrapper {
|
|
||||||
left: -26px;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__display-name {
|
|
||||||
display: block;
|
|
||||||
color: $secondary-text-color;
|
|
||||||
margin-bottom: 0;
|
|
||||||
line-height: 24px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
strong,
|
|
||||||
span {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
@include text-overflow;
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font-size: 16px;
|
|
||||||
color: $primary-text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover strong {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__display-avatar {
|
|
||||||
position: relative;
|
|
||||||
float: left;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
import { List as ImmutableList } from 'immutable';
|
|
||||||
import {
|
|
||||||
followAccount,
|
|
||||||
unfollowAccount,
|
|
||||||
blockAccount,
|
|
||||||
unblockAccount,
|
|
||||||
unmuteAccount,
|
|
||||||
pinAccount,
|
|
||||||
unpinAccount,
|
|
||||||
} from '../../../actions/accounts';
|
|
||||||
import {
|
|
||||||
mentionCompose,
|
|
||||||
} from '../../../actions/compose';
|
|
||||||
import { initMuteModal } from '../../../actions/mutes';
|
|
||||||
import { initReport } from '../../../actions/reports';
|
|
||||||
import { openModal } from '../../../actions/modal';
|
|
||||||
import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
|
|
||||||
import { unfollowModal } from '../../../initial_state';
|
|
||||||
import { makeGetAccount } from '../../../selectors';
|
|
||||||
import Header from '../components/header';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
|
||||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
|
|
||||||
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
|
||||||
const getAccount = makeGetAccount();
|
|
||||||
|
|
||||||
const mapStateToProps = (state, { accountId }) => ({
|
|
||||||
account: getAccount(state, accountId),
|
|
||||||
domain: state.getIn(['meta', 'domain']),
|
|
||||||
identity_proofs: state.getIn(['identity_proofs', accountId], ImmutableList()),
|
|
||||||
});
|
|
||||||
|
|
||||||
return mapStateToProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
|
||||||
|
|
||||||
onFollow (account) {
|
|
||||||
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
|
||||||
if (unfollowModal) {
|
|
||||||
dispatch(openModal('UNFOLLOW', {
|
|
||||||
accountId: account.get('id'),
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
dispatch(unfollowAccount(account.get('id')))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dispatch(followAccount(account.get('id')))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onBlock (account) {
|
|
||||||
if (account.getIn(['relationship', 'blocking'])) {
|
|
||||||
dispatch(unblockAccount(account.get('id')));
|
|
||||||
} else {
|
|
||||||
dispatch(openModal('BLOCK_ACCOUNT', {
|
|
||||||
accountId: account.get('id'),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onMention (account, router) {
|
|
||||||
dispatch(mentionCompose(account, router));
|
|
||||||
},
|
|
||||||
|
|
||||||
onRepostToggle (account) {
|
|
||||||
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
|
||||||
dispatch(followAccount(account.get('id'), false));
|
|
||||||
} else {
|
|
||||||
dispatch(followAccount(account.get('id'), true));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onEndorseToggle (account) {
|
|
||||||
if (account.getIn(['relationship', 'endorsed'])) {
|
|
||||||
dispatch(unpinAccount(account.get('id')));
|
|
||||||
} else {
|
|
||||||
dispatch(pinAccount(account.get('id')));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onReport (account) {
|
|
||||||
dispatch(initReport(account));
|
|
||||||
},
|
|
||||||
|
|
||||||
onMute (account) {
|
|
||||||
if (account.getIn(['relationship', 'muting'])) {
|
|
||||||
dispatch(unmuteAccount(account.get('id')));
|
|
||||||
} else {
|
|
||||||
dispatch(initMuteModal(account));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onBlockDomain (domain) {
|
|
||||||
dispatch(openModal('BLOCK_DOMAIN', {
|
|
||||||
domain,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
onUnblockDomain (domain) {
|
|
||||||
dispatch(unblockDomain(domain));
|
|
||||||
},
|
|
||||||
|
|
||||||
onAddToList(account){
|
|
||||||
dispatch(openModal('LIST_ADDER', {
|
|
||||||
accountId: account.get('id'),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './account_timeline'
|
|
@ -11,6 +11,7 @@ export default class ComposeExtraButton extends PureComponent {
|
|||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
small: PropTypes.bool,
|
small: PropTypes.bool,
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
|
buttonRef: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -21,16 +22,12 @@ export default class ComposeExtraButton extends PureComponent {
|
|||||||
icon,
|
icon,
|
||||||
children,
|
children,
|
||||||
small,
|
small,
|
||||||
active
|
active,
|
||||||
|
buttonRef
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const containerClasses = cx({
|
|
||||||
default: 1,
|
|
||||||
mr10: !small,
|
|
||||||
mr2: small,
|
|
||||||
})
|
|
||||||
|
|
||||||
const btnClasses = cx({
|
const btnClasses = cx({
|
||||||
|
backgroundSubtle_onHover: 1,
|
||||||
backgroundColorBrandLight: active,
|
backgroundColorBrandLight: active,
|
||||||
py10: !small,
|
py10: !small,
|
||||||
px10: !small,
|
px10: !small,
|
||||||
@ -43,16 +40,16 @@ export default class ComposeExtraButton extends PureComponent {
|
|||||||
fillColorWhite: active,
|
fillColorWhite: active,
|
||||||
})
|
})
|
||||||
|
|
||||||
const iconSize = !!small ? '12px' : '18px'
|
const iconSize = !!small ? '12px' : '16px'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClasses} data-tip={title}>
|
<div className={[_s.default, _s.mr2].join(' ')} ref={buttonRef}>
|
||||||
<Button
|
<Button
|
||||||
className={btnClasses}
|
className={btnClasses}
|
||||||
title={title}
|
title={title}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
backgroundColor='secondary'
|
backgroundColor='none'
|
||||||
iconClassName={iconClasses}
|
iconClassName={iconClasses}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
iconWidth={iconSize}
|
iconWidth={iconSize}
|
||||||
|
@ -1,29 +1,30 @@
|
|||||||
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'
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz'
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import classNames from 'classnames/bind'
|
import classNames from 'classnames/bind'
|
||||||
import CharacterCounter from '../character_counter';
|
import CharacterCounter from '../character_counter'
|
||||||
import UploadForm from '../upload_form';
|
import UploadForm from '../upload_form'
|
||||||
import ReplyIndicatorContainer from '../../containers/reply_indicator_container';
|
import ReplyIndicatorContainer from '../../containers/reply_indicator_container'
|
||||||
import AutosuggestTextbox from '../../../../components/autosuggest_textbox';
|
import AutosuggestTextbox from '../../../../components/autosuggest_textbox'
|
||||||
import PollButton from '../../components/poll_button';
|
import PollButton from '../../components/poll_button'
|
||||||
import UploadButton from '../../components/upload_button';
|
import UploadButton from '../../components/upload_button'
|
||||||
import SpoilerButton from '../../components/spoiler_button';
|
import SpoilerButton from '../../components/spoiler_button'
|
||||||
import PostPrivacyButton from '../../../../components/post_privacy_button';
|
import RichTextEditorButton from '../../components/rich_text_editor_button'
|
||||||
|
import GifSelectorButton from '../../components/gif_selector_button'
|
||||||
|
import StatusVisibilityButton from '../../components/status_visibility_button'
|
||||||
import EmojiPickerButton from '../../components/emoji_picker_button'
|
import EmojiPickerButton from '../../components/emoji_picker_button'
|
||||||
import EmojiPickerDropdown from '../../containers/emoji_picker_dropdown_container';
|
import PollFormContainer from '../../containers/poll_form_container'
|
||||||
import PollFormContainer from '../../containers/poll_form_container';
|
import SchedulePostDropdown from '../../components/schedule_post_dropdown'
|
||||||
import SchedulePostDropdown from '../../components/schedule_post_dropdown';
|
import QuotedStatusPreviewContainer from '../../containers/quoted_status_preview_container'
|
||||||
import QuotedStatusPreviewContainer from '../../containers/quoted_status_preview_container';
|
import Icon from '../../../../components/icon'
|
||||||
import Icon from '../../../../components/icon';
|
import Button from '../../../../components/button'
|
||||||
import Button from '../../../../components/button';
|
|
||||||
import Avatar from '../../../../components/avatar'
|
import Avatar from '../../../../components/avatar'
|
||||||
import { isMobile } from '../../../../utils/is_mobile';
|
import { isMobile } from '../../../../utils/is_mobile'
|
||||||
import { countableText } from '../../util/counter';
|
import { countableText } from '../../util/counter'
|
||||||
|
|
||||||
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d'
|
||||||
const maxPostCharacterCount = 3000;
|
const maxPostCharacterCount = 3000
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
|
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
|
||||||
@ -266,6 +267,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
pb10: 1,
|
pb10: 1,
|
||||||
borderBottom1PX: 1,
|
borderBottom1PX: 1,
|
||||||
borderColorSecondary: 1,
|
borderColorSecondary: 1,
|
||||||
|
mb10: 1,
|
||||||
displayNone: !spoiler
|
displayNone: !spoiler
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -303,11 +305,6 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ /*
|
|
||||||
<div className='emoji-picker-wrapper'>
|
|
||||||
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
|
||||||
</div> */ }
|
|
||||||
|
|
||||||
<AutosuggestTextbox
|
<AutosuggestTextbox
|
||||||
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
|
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
|
||||||
placeholder={intl.formatMessage(messages.placeholder)}
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
@ -340,16 +337,18 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
|
|
||||||
<div className={actionsContainerClasses}>
|
<div className={actionsContainerClasses}>
|
||||||
<div className={[_s.default, _s.flexRow, _s.marginRightAuto].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.marginRightAuto].join(' ')}>
|
||||||
|
<RichTextEditorButton small={shouldCondense} />
|
||||||
<UploadButton small={shouldCondense} />
|
<UploadButton small={shouldCondense} />
|
||||||
{
|
{
|
||||||
!edit && <PollButton small={shouldCondense} />
|
!edit && <PollButton small={shouldCondense} />
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!shouldCondense &&
|
!shouldCondense &&
|
||||||
<PostPrivacyButton />
|
<StatusVisibilityButton small={shouldCondense} />
|
||||||
}
|
}
|
||||||
<SpoilerButton small={shouldCondense} />
|
<SpoilerButton small={shouldCondense} />
|
||||||
<SchedulePostDropdown small={shouldCondense} position={isModalOpen ? 'top' : undefined} />
|
<SchedulePostDropdown small={shouldCondense} position={isModalOpen ? 'top' : undefined} />
|
||||||
|
<GifSelectorButton small={shouldCondense} />
|
||||||
<EmojiPickerButton small={shouldCondense} />
|
<EmojiPickerButton small={shouldCondense} />
|
||||||
</div>
|
</div>
|
||||||
<CharacterCounter max={maxPostCharacterCount} text={text} small={shouldCondense} />
|
<CharacterCounter max={maxPostCharacterCount} text={text} small={shouldCondense} />
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { addPoll, removePoll } from '../../../actions/compose'
|
import { defineMessages, injectIntl } from 'react-intl'
|
||||||
|
import { openPopover } from '../../../actions/popover'
|
||||||
import ComposeExtraButton from './compose_extra_button'
|
import ComposeExtraButton from './compose_extra_button'
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
|
||||||
|
})
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
// unavailable: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 0),
|
// unavailable: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 0),
|
||||||
// active: state.getIn(['compose', 'poll']) !== null,
|
// active: state.getIn(['compose', 'poll']) !== null,
|
||||||
@ -8,46 +13,47 @@ const mapStateToProps = state => ({
|
|||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
||||||
onClick() {
|
onClick(targetRef) {
|
||||||
dispatch((_, getState) => {
|
dispatch(openPopover('EMOJI_PICKER', {
|
||||||
if (getState().getIn(['compose', 'poll'])) {
|
targetRef,
|
||||||
dispatch(removePoll())
|
}))
|
||||||
} else {
|
|
||||||
dispatch(addPoll())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export default
|
export default
|
||||||
|
@injectIntl
|
||||||
@connect(mapStateToProps, mapDispatchToProps)
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
class EmojiPickerButton extends PureComponent {
|
class EmojiPickerButton extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
disabled: PropTypes.bool,
|
intl: PropTypes.object.isRequired,
|
||||||
unavailable: PropTypes.bool,
|
|
||||||
active: PropTypes.bool,
|
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
|
active: PropTypes.bool,
|
||||||
small: PropTypes.bool,
|
small: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = () => {
|
handleClick = () => {
|
||||||
this.props.onClick()
|
this.props.onClick(this.button)
|
||||||
|
}
|
||||||
|
|
||||||
|
setButton = (n) => {
|
||||||
|
this.button = n
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { active, unavailable, disabled, small } = this.props
|
const { active, small, intl } = this.props
|
||||||
|
|
||||||
if (unavailable) return null
|
const title = intl.formatMessage(messages.emoji)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ComposeExtraButton
|
<ComposeExtraButton
|
||||||
disabled={disabled}
|
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
icon='happy'
|
icon='happy'
|
||||||
|
title={title}
|
||||||
small={small}
|
small={small}
|
||||||
active={active}
|
active={active}
|
||||||
|
buttonRef={this.setButton}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
import { injectIntl, defineMessages } from 'react-intl'
|
||||||
|
import { changeComposeSpoilerness } from '../../../actions/compose'
|
||||||
|
import ComposeExtraButton from './compose_extra_button'
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
marked: { id: 'compose_form.spoiler.marked', defaultMessage: 'Text is hidden behind warning' },
|
||||||
|
unmarked: { id: 'compose_form.spoiler.unmarked', defaultMessage: 'Text is not hidden' },
|
||||||
|
title: { id: 'compose_form.spoiler.title', defaultMessage: 'Warning' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
active: state.getIn(['compose', 'spoiler']),
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
||||||
|
onClick () {
|
||||||
|
dispatch(changeComposeSpoilerness())
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
export default
|
||||||
|
@injectIntl
|
||||||
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
class SpoilerButton extends PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
active: PropTypes.bool,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
small: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.props.onClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { active, intl, small } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ComposeExtraButton
|
||||||
|
title={intl.formatMessage(messages.title)}
|
||||||
|
icon='gif'
|
||||||
|
onClick={this.handleClick}
|
||||||
|
small={small}
|
||||||
|
active={active}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,65 +0,0 @@
|
|||||||
import { defineMessages, injectIntl } from 'react-intl'
|
|
||||||
import { addPoll, removePoll } from '../../../actions/compose'
|
|
||||||
import ComposeExtraButton from './compose_extra_button'
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
add_poll: { id: 'poll_button.add_poll', defaultMessage: 'Add poll' },
|
|
||||||
title: { id: 'poll_button.title', defaultMessage: 'Poll' },
|
|
||||||
remove_poll: { id: 'poll_button.remove_poll', defaultMessage: 'Remove poll' },
|
|
||||||
})
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
unavailable: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 0),
|
|
||||||
active: state.getIn(['compose', 'poll']) !== null,
|
|
||||||
})
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
|
|
||||||
onClick() {
|
|
||||||
dispatch((_, getState) => {
|
|
||||||
if (getState().getIn(['compose', 'poll'])) {
|
|
||||||
dispatch(removePoll())
|
|
||||||
} else {
|
|
||||||
dispatch(addPoll())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
export default
|
|
||||||
@connect(mapStateToProps, mapDispatchToProps)
|
|
||||||
@injectIntl
|
|
||||||
class PollButton extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
unavailable: PropTypes.bool,
|
|
||||||
active: PropTypes.bool,
|
|
||||||
onClick: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
small: PropTypes.bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
this.props.onClick()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { intl, active, unavailable, disabled, small } = this.props
|
|
||||||
|
|
||||||
if (unavailable) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ComposeExtraButton
|
|
||||||
title={intl.formatMessage(active ? messages.remove_poll : messages.title)}
|
|
||||||
disabled={disabled}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
icon='poll'
|
|
||||||
small={small}
|
|
||||||
active={active}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,53 @@
|
|||||||
|
import { injectIntl, defineMessages } from 'react-intl'
|
||||||
|
import { changeComposeSpoilerness } from '../../../actions/compose'
|
||||||
|
import ComposeExtraButton from './compose_extra_button'
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
marked: { id: 'compose_form.spoiler.marked', defaultMessage: 'Text is hidden behind warning' },
|
||||||
|
unmarked: { id: 'compose_form.spoiler.unmarked', defaultMessage: 'Text is not hidden' },
|
||||||
|
title: { id: 'compose_form.spoiler.title', defaultMessage: 'Warning' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
active: state.getIn(['compose', 'spoiler']),
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
||||||
|
onClick () {
|
||||||
|
dispatch(changeComposeSpoilerness())
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
export default
|
||||||
|
@injectIntl
|
||||||
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
class SpoilerButton extends PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
active: PropTypes.bool,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
small: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.props.onClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { active, intl, small } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ComposeExtraButton
|
||||||
|
title={intl.formatMessage(messages.title)}
|
||||||
|
icon='letter-t'
|
||||||
|
onClick={this.handleClick}
|
||||||
|
small={small}
|
||||||
|
active={active}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
import { defineMessages, injectIntl } from 'react-intl'
|
||||||
|
import { openPopover } from '../../../actions/popover'
|
||||||
|
import ComposeExtraButton from './compose_extra_button'
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
visibility: { id: 'privacy.visibility', defaultMessage: 'Visibility' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
value: state.getIn(['compose', 'privacy']),
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
onOpenPopover(targetRef) {
|
||||||
|
dispatch(openPopover('STATUS_VISIBILITY', {
|
||||||
|
targetRef,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default
|
||||||
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
@injectIntl
|
||||||
|
class StatusVisibilityButton extends PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
small: PropTypes.bool,
|
||||||
|
onOpenPopover: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnClick = () => {
|
||||||
|
this.props.onOpenPopover(this.button)
|
||||||
|
}
|
||||||
|
|
||||||
|
setButton = (n) => {
|
||||||
|
this.button = n
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
intl,
|
||||||
|
small,
|
||||||
|
value
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
let icon;
|
||||||
|
switch (value) {
|
||||||
|
case 'unlisted':
|
||||||
|
icon = 'unlock'
|
||||||
|
break;
|
||||||
|
case 'private':
|
||||||
|
icon = 'lock'
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
icon = 'globe'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ComposeExtraButton
|
||||||
|
icon={icon}
|
||||||
|
title={intl.formatMessage(messages.visibility)}
|
||||||
|
onClick={this.handleOnClick}
|
||||||
|
small={small}
|
||||||
|
buttonRef={this.setButton}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,82 +0,0 @@
|
|||||||
import EmojiPickerDropdown from '../components/emoji_picker_dropdown';
|
|
||||||
import { changeSetting } from '../../../actions/settings';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
|
||||||
import { useEmoji } from '../../../actions/emojis';
|
|
||||||
|
|
||||||
const perLine = 8;
|
|
||||||
const lines = 2;
|
|
||||||
|
|
||||||
const DEFAULTS = [
|
|
||||||
'+1',
|
|
||||||
'grinning',
|
|
||||||
'kissing_heart',
|
|
||||||
'heart_eyes',
|
|
||||||
'laughing',
|
|
||||||
'stuck_out_tongue_winking_eye',
|
|
||||||
'sweat_smile',
|
|
||||||
'joy',
|
|
||||||
'yum',
|
|
||||||
'disappointed',
|
|
||||||
'thinking_face',
|
|
||||||
'weary',
|
|
||||||
'sob',
|
|
||||||
'sunglasses',
|
|
||||||
'heart',
|
|
||||||
'ok_hand',
|
|
||||||
];
|
|
||||||
|
|
||||||
const getFrequentlyUsedEmojis = createSelector([
|
|
||||||
state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()),
|
|
||||||
], emojiCounters => {
|
|
||||||
let emojis = emojiCounters
|
|
||||||
.keySeq()
|
|
||||||
.sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b))
|
|
||||||
.reverse()
|
|
||||||
.slice(0, perLine * lines)
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
if (emojis.length < DEFAULTS.length) {
|
|
||||||
let uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji));
|
|
||||||
emojis = emojis.concat(uniqueDefaults.slice(0, DEFAULTS.length - emojis.length));
|
|
||||||
}
|
|
||||||
|
|
||||||
return emojis;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getCustomEmojis = createSelector([
|
|
||||||
state => state.get('custom_emojis'),
|
|
||||||
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
|
|
||||||
const aShort = a.get('shortcode').toLowerCase();
|
|
||||||
const bShort = b.get('shortcode').toLowerCase();
|
|
||||||
|
|
||||||
if (aShort < bShort) {
|
|
||||||
return -1;
|
|
||||||
} else if (aShort > bShort ) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
custom_emojis: getCustomEmojis(state),
|
|
||||||
skinTone: state.getIn(['settings', 'skinTone']),
|
|
||||||
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
|
|
||||||
onSkinTone: skinTone => {
|
|
||||||
dispatch(changeSetting(['skinTone'], skinTone));
|
|
||||||
},
|
|
||||||
|
|
||||||
onPickEmoji: emoji => {
|
|
||||||
dispatch(useEmoji(emoji));
|
|
||||||
|
|
||||||
if (onPickEmoji) {
|
|
||||||
onPickEmoji(emoji);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(EmojiPickerDropdown);
|
|
@ -33,16 +33,11 @@ class GroupsCollection extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[_s.default, _s.flexRow, _s.flexWrap].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.flexWrap].join(' ')}>
|
||||||
<ScrollableList
|
|
||||||
scrollKey='groups_collection'
|
|
||||||
emptyMessage={''}
|
|
||||||
>
|
|
||||||
{
|
{
|
||||||
groupIds.map((groupId, i) => (
|
groupIds.map((groupId, i) => (
|
||||||
<GroupCollectionItem key={`group-collection-item-${i}`} id={groupId} />
|
<GroupCollectionItem key={`group-collection-item-${i}`} id={groupId} />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</ScrollableList>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,169 +0,0 @@
|
|||||||
.status-card {
|
|
||||||
display: flex;
|
|
||||||
font-size: 14px;
|
|
||||||
color: $dark-text-color;
|
|
||||||
margin-top: 14px;
|
|
||||||
text-decoration: none;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
@include border-design(lighten($ui-base-color, 8%), 1px, 4px);
|
|
||||||
|
|
||||||
&__actions {
|
|
||||||
@include flex(center, center);
|
|
||||||
@include abs-position(0, 0, 0, 0);
|
|
||||||
|
|
||||||
&>div {
|
|
||||||
background: rgba($base-shadow-color, 0.6);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px 9px;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
|
|
||||||
@include flex(center, center);
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
a {
|
|
||||||
display: inline;
|
|
||||||
color: $secondary-text-color;
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
padding: 0 8px;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 18px;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:active,
|
|
||||||
&:focus {
|
|
||||||
color: $primary-text-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: 19px;
|
|
||||||
position: relative;
|
|
||||||
bottom: -1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
display: block;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
color: $darker-text-color;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
@include text-overflow(nowrap);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 14px 14px 14px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__description {
|
|
||||||
color: $darker-text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__host {
|
|
||||||
display: block;
|
|
||||||
margin-top: 5px;
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
@include text-overflow(nowrap);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__image {
|
|
||||||
flex: 0 0 100px;
|
|
||||||
background: lighten($ui-base-color, 8%);
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&>.fa {
|
|
||||||
font-size: 21px;
|
|
||||||
position: absolute;
|
|
||||||
transform-origin: 50% 50%;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__image-image {
|
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
object-fit: cover;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center center;
|
|
||||||
|
|
||||||
@include size(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.horizontal {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.horizontal & {
|
|
||||||
&__image {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__image-image {
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
white-space: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.compact {
|
|
||||||
border-color: lighten($ui-base-color, 4%);
|
|
||||||
|
|
||||||
&.interactive {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.compact & {
|
|
||||||
&__content {
|
|
||||||
padding: 10px 8px 8px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__image {
|
|
||||||
flex: 0 0 60px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a.status-card {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: lighten($ui-base-color, 8%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.compact:hover {
|
|
||||||
background-color: lighten($ui-base-color, 4%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-card-photo {
|
|
||||||
cursor: zoom-in;
|
|
||||||
display: block;
|
|
||||||
text-decoration: none;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
@include size(100%, auto);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-card-video {
|
|
||||||
iframe {
|
|
||||||
@include size(100%);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './card'
|
|
@ -7,7 +7,7 @@ export function AccountGallery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Blocks() {
|
export function Blocks() {
|
||||||
return import(/* webpackChunkName: "features/blocks" */'../../blocks')
|
return import(/* webpackChunkName: "features/blocks" */'../../blocked_accounts')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CommunityTimeline() {
|
export function CommunityTimeline() {
|
||||||
@ -19,7 +19,7 @@ export function Compose() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function DomainBlocks() {
|
export function DomainBlocks() {
|
||||||
return import(/* webpackChunkName: "features/domain_blocks" */'../../domain_blocks')
|
return import(/* webpackChunkName: "features/domain_blocks" */'../../blocked_domains')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EmbedModal() {
|
export function EmbedModal() {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export { default } from './video'
|
|
@ -1,71 +0,0 @@
|
|||||||
.video-player {
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
background: $base-shadow-color;
|
|
||||||
max-width: 100%;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
@include max-size(100vw, 80vh);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.fullscreen {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
@include size(100% !important);
|
|
||||||
|
|
||||||
video {
|
|
||||||
@include max-size(100% !important);
|
|
||||||
@include size(100% !important);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.inactive {
|
|
||||||
|
|
||||||
video,
|
|
||||||
.video-player__controls {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__spoiler {
|
|
||||||
display: none;
|
|
||||||
z-index: 4;
|
|
||||||
border: 0;
|
|
||||||
background: $base-overlay-background;
|
|
||||||
color: $darker-text-color;
|
|
||||||
transition: none;
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
@include abs-position(0, auto, auto, 0);
|
|
||||||
@include size(100%);
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
display: block;
|
|
||||||
pointer-events: auto;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:active,
|
|
||||||
&:focus {
|
|
||||||
color: lighten($darker-text-color, 7%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
display: block;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__subtitle {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
@include text-sizing(11px, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -176,7 +176,7 @@ const insertEmoji = (state, position, emojiData, needsSpace) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const privacyPreference = (a, b) => {
|
const privacyPreference = (a, b) => {
|
||||||
const order = ['public', 'unlisted', 'private', 'direct'];
|
const order = ['public', 'unlisted', 'private'];
|
||||||
return order[Math.max(order.indexOf(a), order.indexOf(b), 0)];
|
return order[Math.max(order.indexOf(a), order.indexOf(b), 0)];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-mart-bar {
|
.emoji-mart-bar {{
|
||||||
border: 0 solid darken($ui-secondary-color, 8%);
|
border: 0 solid darken($ui-secondary-color, 8%);
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user