Progress
This commit is contained in:
parent
3d0a85cde4
commit
2bd344a594
|
@ -1,3 +1,4 @@
|
|||
// : todo : fill, stroke colors
|
||||
const GlobeIcon = ({
|
||||
className = '',
|
||||
width = '12px',
|
||||
|
|
|
@ -139,6 +139,7 @@ export default class Button extends PureComponent {
|
|||
backgroundColorBrand_onHover: color === COLORS.brand && outline,
|
||||
colorWhite_onHover: !!children && color === COLORS.brand && outline,
|
||||
|
||||
fillColorSecondary: !!icon && color === COLORS.secondary,
|
||||
fillColorWhite: !!icon && color === COLORS.white,
|
||||
fillColorBrand: !!icon && color === COLORS.brand,
|
||||
fillColorWhite_onHover: !!icon && color === COLORS.brand && outline,
|
||||
|
|
|
@ -58,7 +58,7 @@ class ModalLayout extends PureComponent {
|
|||
const childrenContainerClasses = cx({
|
||||
default: 1,
|
||||
heightMax80VH: 1,
|
||||
overflowScroll: 1,
|
||||
overflowYScroll: 1,
|
||||
px15: !noPadding,
|
||||
py10: !noPadding,
|
||||
})
|
||||
|
@ -77,6 +77,7 @@ class ModalLayout extends PureComponent {
|
|||
title={intl.formatMessage(messages.close)}
|
||||
className={_s.marginLeftAuto}
|
||||
onClick={this.onHandleCloseModal}
|
||||
color='secondary'
|
||||
icon='close'
|
||||
iconWidth='10px'
|
||||
iconWidth='10px'
|
||||
|
|
|
@ -2,8 +2,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import DisplayName from '../../../../components/display_name';
|
||||
import Icon from '../../../../components/icon';
|
||||
import DisplayName from './display_name';
|
||||
import Icon from './icon';
|
||||
|
||||
export default class MovedNote extends ImmutablePureComponent {
|
||||
|
|
@ -8,6 +8,8 @@ import Button from '../button'
|
|||
import Divider from '../divider'
|
||||
import Heading from '../heading'
|
||||
import Icon from '../icon'
|
||||
import Input from '../input'
|
||||
import Switch from '../switch'
|
||||
import Text from '../text'
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -33,11 +35,26 @@ class NotificationFilterPanel extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<PanelLayout title={intl.formatMessage(messages.title)}>
|
||||
<Text>Date</Text>
|
||||
<Text>Verified</Text>
|
||||
<Text>Users</Text>
|
||||
<Text>Status Id</Text>
|
||||
<Text>Only People I Follow</Text>
|
||||
<Text>Start Date</Text>
|
||||
<Text>End Date</Text>
|
||||
|
||||
<Divider small />
|
||||
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -54,10 +54,10 @@ class TrendsPanel extends ImmutablePureComponent {
|
|||
{ /* trends && trends.map(hashtag => (
|
||||
<TrendingItem key={hashtag.get('name')} hashtag={hashtag} />
|
||||
)) */ }
|
||||
<TrendingItem />
|
||||
<TrendingItem />
|
||||
<TrendingItem />
|
||||
<TrendingItem />
|
||||
<TrendingItem index='1' />
|
||||
<TrendingItem index='2' />
|
||||
<TrendingItem index='3' />
|
||||
<TrendingItem index='4' />
|
||||
</div>
|
||||
</PanelLayout>
|
||||
)
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import detectPassiveEvents from 'detect-passive-events';
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
import { EmojiPicker as EmojiPickerAsync } from '../../../ui/util/async_components';
|
||||
import { buildCustomEmojis } from '../../../../components/emoji/emoji';
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { Map as ImmutableMap } from 'immutable'
|
||||
import classNames from 'classnames'
|
||||
import { createSelector } from 'reselect'
|
||||
import detectPassiveEvents from 'detect-passive-events'
|
||||
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({
|
||||
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
|
||||
|
@ -22,13 +25,13 @@ const messages = defineMessages({
|
|||
objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' },
|
||||
symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' },
|
||||
flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' },
|
||||
});
|
||||
})
|
||||
|
||||
const assetHost = process.env.CDN_HOST || '';
|
||||
let EmojiPicker, Emoji; // load asynchronously
|
||||
const assetHost = process.env.CDN_HOST || ''
|
||||
let EmojiPicker, Emoji // load asynchronously
|
||||
|
||||
const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`;
|
||||
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
|
||||
const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`
|
||||
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false
|
||||
|
||||
const categoriesSort = [
|
||||
'recent',
|
||||
|
@ -93,9 +96,9 @@ class ModifierPickerMenu extends PureComponent {
|
|||
return (
|
||||
<div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
|
||||
<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 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} />
|
||||
</button>
|
||||
<button onClick={this.handleClick} data-index={3}>
|
||||
|
@ -109,7 +112,7 @@ class ModifierPickerMenu extends PureComponent {
|
|||
</button>
|
||||
<button onClick={this.handleClick} data-index={6}>
|
||||
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} />
|
||||
</button>
|
||||
</button>*/}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -144,7 +147,7 @@ class ModifierPicker extends PureComponent {
|
|||
|
||||
return (
|
||||
<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} />
|
||||
</div>
|
||||
);
|
||||
|
@ -162,7 +165,6 @@ class EmojiPickerMenu extends ImmutablePureComponent {
|
|||
onClose: PropTypes.func.isRequired,
|
||||
onPick: PropTypes.func.isRequired,
|
||||
style: PropTypes.object,
|
||||
placement: PropTypes.string,
|
||||
arrowOffsetLeft: PropTypes.string,
|
||||
arrowOffsetTop: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
@ -178,7 +180,6 @@ class EmojiPickerMenu extends ImmutablePureComponent {
|
|||
|
||||
state = {
|
||||
modifierOpen: false,
|
||||
placement: null,
|
||||
};
|
||||
|
||||
handleDocumentClick = e => {
|
||||
|
@ -252,7 +253,7 @@ class EmojiPickerMenu extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
const title = intl.formatMessage(messages.emoji);
|
||||
const { modifierOpen } = this.state;
|
||||
const { modifierOpen } = this.state
|
||||
|
||||
return (
|
||||
<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
|
||||
@injectIntl
|
||||
class EmojiPickerDropdown extends ImmutablePureComponent {
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class EmojiPickerPopover extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
custom_emojis: ImmutablePropTypes.list,
|
||||
|
@ -307,11 +384,7 @@ class EmojiPickerDropdown extends ImmutablePureComponent {
|
|||
loading: false,
|
||||
};
|
||||
|
||||
setRef = (c) => {
|
||||
this.dropdown = c;
|
||||
}
|
||||
|
||||
onShowDropdown = ({ target }) => {
|
||||
componentWillMount = () => {
|
||||
this.setState({ active: true });
|
||||
|
||||
if (!EmojiPicker) {
|
||||
|
@ -326,9 +399,6 @@ class EmojiPickerDropdown extends ImmutablePureComponent {
|
|||
this.setState({ loading: false });
|
||||
});
|
||||
}
|
||||
|
||||
const { top } = target.getBoundingClientRect();
|
||||
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
|
||||
}
|
||||
|
||||
onHideDropdown = () => {
|
||||
|
@ -351,40 +421,28 @@ class EmojiPickerDropdown extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setTargetRef = c => {
|
||||
this.target = c;
|
||||
}
|
||||
|
||||
findTarget = () => {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props;
|
||||
const title = intl.formatMessage(messages.emoji);
|
||||
const { active, loading, placement } = this.state;
|
||||
const {
|
||||
intl,
|
||||
onPickEmoji,
|
||||
onSkinTone,
|
||||
skinTone,
|
||||
frequentlyUsedEmojis
|
||||
} = this.props
|
||||
|
||||
const { active, loading } = this.state;
|
||||
|
||||
return (
|
||||
<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
|
||||
custom_emojis={this.props.custom_emojis}
|
||||
loading={loading}
|
||||
onClose={this.onHideDropdown}
|
||||
onPick={onPickEmoji}
|
||||
onSkinTone={onSkinTone}
|
||||
skinTone={skinTone}
|
||||
frequentlyUsedEmojis={frequentlyUsedEmojis}
|
||||
/>
|
||||
</Overlay>
|
||||
<EmojiPickerMenu
|
||||
custom_emojis={this.props.custom_emojis}
|
||||
loading={loading}
|
||||
onClose={this.onHideDropdown}
|
||||
onPick={onPickEmoji}
|
||||
onSkinTone={onSkinTone}
|
||||
skinTone={skinTone}
|
||||
frequentlyUsedEmojis={frequentlyUsedEmojis}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -5,6 +5,7 @@ import BundleModalError from '../bundle_modal_error'
|
|||
import PopoverBase from './popover_base'
|
||||
import ContentWarningPopover from './content_warning_popover'
|
||||
import DatePickerPopover from './date_picker_popover'
|
||||
import EmojiPickerPopover from './emoji_picker_popover'
|
||||
import GroupInfoPopover from './group_info_popover'
|
||||
import ProfileOptionsPopover from './profile_options_popover'
|
||||
import SearchPopover from './search_popover'
|
||||
|
@ -19,6 +20,7 @@ const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : fal
|
|||
const POPOVER_COMPONENTS = {
|
||||
CONTENT_WARNING: () => Promise.resolve({ default: ContentWarningPopover }),
|
||||
DATE_PICKER: () => Promise.resolve({ default: DatePickerPopover }),
|
||||
EMOJI_PICKER: () => Promise.resolve({ default: EmojiPickerPopover }),
|
||||
GROUP_INFO: () => GroupInfoPopover,
|
||||
PROFILE_OPTIONS: () => Promise.resolve({ default: ProfileOptionsPopover }),
|
||||
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 {
|
||||
render() {
|
||||
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 () {
|
||||
const { value } = this.props
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ /* */ }
|
||||
</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>
|
||||
</PopoverLayout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -242,6 +242,8 @@ class ProfileHeader extends ImmutablePureComponent {
|
|||
console.log("buttonOptions:", buttonText, buttonOptions)
|
||||
|
||||
|
||||
// : todo : "follows you", "mutual follow"
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.z1, _s.width100PC].join(' ')}>
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ export default class SidebarSectionItem extends PureComponent {
|
|||
buttonRef={buttonRef}
|
||||
onMouseEnter={() => this.handleOnMouseEnter()}
|
||||
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={[_s.default]}>
|
||||
|
|
|
@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import punycode from 'punycode';
|
||||
import classnames from 'classnames';
|
||||
import Icon from '../../../../components/icon';
|
||||
import Icon from './icon';
|
||||
|
||||
const IDNA_PREFIX = 'xn--';
|
||||
|
|
@ -285,12 +285,18 @@ class StatusContent extends ImmutablePureComponent {
|
|||
/>
|
||||
{
|
||||
this.state.collapsed &&
|
||||
<button
|
||||
className={[_s.default, _s.displayFlex, _s.cursorPointer, _s.py2, _s.text, _s.colorPrimary, _s.fontWeightBold, _s.fontSize15PX].join(' ')}
|
||||
<Button
|
||||
text
|
||||
underlineOnHover
|
||||
color='primary'
|
||||
backgroundColor='none'
|
||||
className={[_s.py2].join(' ')}
|
||||
onClick={this.handleReadMore}
|
||||
>
|
||||
{intl.formatMessage(messages.readMore)}
|
||||
</button>
|
||||
<Text size='medium' color='inherit' weight='bold'>
|
||||
{intl.formatMessage(messages.readMore)}
|
||||
</Text>
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -15,6 +15,7 @@ const cx = classNames.bind(_s)
|
|||
export default class TrendingItem extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
index: PropTypes.number,
|
||||
trend: ImmutablePropTypes.map.isRequired,
|
||||
}
|
||||
|
||||
|
@ -31,7 +32,7 @@ export default class TrendingItem extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { trend } = this.props
|
||||
const { trend, index } = this.props
|
||||
const { hovering } = this.state
|
||||
|
||||
const subtitleClasses = cx({
|
||||
|
@ -44,7 +45,7 @@ export default class TrendingItem extends ImmutablePureComponent {
|
|||
underline: hovering,
|
||||
})
|
||||
|
||||
return null;
|
||||
// return null;
|
||||
|
||||
// : todo :
|
||||
|
||||
|
@ -56,7 +57,7 @@ export default class TrendingItem extends ImmutablePureComponent {
|
|||
onMouseLeave={() => this.handleOnMouseLeave()}
|
||||
>
|
||||
<div className={[_s.default, _s.flexRow, _s.mt5].join(' ')}>
|
||||
<Text size='small' color='secondary'>1</Text>
|
||||
<Text size='small' color='secondary'>{index}</Text>
|
||||
<DotTextSeperator />
|
||||
<Text size='small' color='secondary' className={_s.ml5}>Politics</Text>
|
||||
</div>
|
||||
|
|
|
@ -58,7 +58,7 @@ export default class TrendingItemCard extends ImmutablePureComponent {
|
|||
The best flower subscription services: BloomsyBox, Bouqs...
|
||||
</Text>
|
||||
</div>
|
||||
<Image width='92px' height='92px' />
|
||||
<Image width='92px' height='96px' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -452,6 +452,8 @@ class Video extends PureComponent {
|
|||
// fullscreen
|
||||
// })}
|
||||
|
||||
// : todo spoiler :
|
||||
|
||||
const seekHandleClasses = cx({
|
||||
default: 1,
|
||||
positionAbsolute: 1,
|
|
@ -2,9 +2,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { List as ImmutableList } from 'immutable'
|
||||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines'
|
||||
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'
|
||||
import StatusList from '../../components/status_list'
|
||||
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../actions/timelines'
|
||||
import { fetchAccountIdentityProofs } from '../actions/identity_proofs'
|
||||
import StatusList from '../components/status_list'
|
||||
|
||||
const messages = defineMessages({
|
||||
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,
|
||||
small: PropTypes.bool,
|
||||
active: PropTypes.bool,
|
||||
buttonRef: PropTypes.func,
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -21,16 +22,12 @@ export default class ComposeExtraButton extends PureComponent {
|
|||
icon,
|
||||
children,
|
||||
small,
|
||||
active
|
||||
active,
|
||||
buttonRef
|
||||
} = this.props
|
||||
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
mr10: !small,
|
||||
mr2: small,
|
||||
})
|
||||
|
||||
const btnClasses = cx({
|
||||
backgroundSubtle_onHover: 1,
|
||||
backgroundColorBrandLight: active,
|
||||
py10: !small,
|
||||
px10: !small,
|
||||
|
@ -43,16 +40,16 @@ export default class ComposeExtraButton extends PureComponent {
|
|||
fillColorWhite: active,
|
||||
})
|
||||
|
||||
const iconSize = !!small ? '12px' : '18px'
|
||||
const iconSize = !!small ? '12px' : '16px'
|
||||
|
||||
return (
|
||||
<div className={containerClasses} data-tip={title}>
|
||||
<div className={[_s.default, _s.mr2].join(' ')} ref={buttonRef}>
|
||||
<Button
|
||||
className={btnClasses}
|
||||
title={title}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
backgroundColor='secondary'
|
||||
backgroundColor='none'
|
||||
iconClassName={iconClasses}
|
||||
icon={icon}
|
||||
iconWidth={iconSize}
|
||||
|
|
|
@ -1,29 +1,30 @@
|
|||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { length } from 'stringz';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { length } from 'stringz'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import classNames from 'classnames/bind'
|
||||
import CharacterCounter from '../character_counter';
|
||||
import UploadForm from '../upload_form';
|
||||
import ReplyIndicatorContainer from '../../containers/reply_indicator_container';
|
||||
import AutosuggestTextbox from '../../../../components/autosuggest_textbox';
|
||||
import PollButton from '../../components/poll_button';
|
||||
import UploadButton from '../../components/upload_button';
|
||||
import SpoilerButton from '../../components/spoiler_button';
|
||||
import PostPrivacyButton from '../../../../components/post_privacy_button';
|
||||
import CharacterCounter from '../character_counter'
|
||||
import UploadForm from '../upload_form'
|
||||
import ReplyIndicatorContainer from '../../containers/reply_indicator_container'
|
||||
import AutosuggestTextbox from '../../../../components/autosuggest_textbox'
|
||||
import PollButton from '../../components/poll_button'
|
||||
import UploadButton from '../../components/upload_button'
|
||||
import SpoilerButton from '../../components/spoiler_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 EmojiPickerDropdown from '../../containers/emoji_picker_dropdown_container';
|
||||
import PollFormContainer from '../../containers/poll_form_container';
|
||||
import SchedulePostDropdown from '../../components/schedule_post_dropdown';
|
||||
import QuotedStatusPreviewContainer from '../../containers/quoted_status_preview_container';
|
||||
import Icon from '../../../../components/icon';
|
||||
import Button from '../../../../components/button';
|
||||
import PollFormContainer from '../../containers/poll_form_container'
|
||||
import SchedulePostDropdown from '../../components/schedule_post_dropdown'
|
||||
import QuotedStatusPreviewContainer from '../../containers/quoted_status_preview_container'
|
||||
import Icon from '../../../../components/icon'
|
||||
import Button from '../../../../components/button'
|
||||
import Avatar from '../../../../components/avatar'
|
||||
import { isMobile } from '../../../../utils/is_mobile';
|
||||
import { countableText } from '../../util/counter';
|
||||
import { isMobile } from '../../../../utils/is_mobile'
|
||||
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 maxPostCharacterCount = 3000;
|
||||
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 messages = defineMessages({
|
||||
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
|
||||
|
@ -266,6 +267,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
pb10: 1,
|
||||
borderBottom1PX: 1,
|
||||
borderColorSecondary: 1,
|
||||
mb10: 1,
|
||||
displayNone: !spoiler
|
||||
})
|
||||
|
||||
|
@ -303,11 +305,6 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{ /*
|
||||
<div className='emoji-picker-wrapper'>
|
||||
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
||||
</div> */ }
|
||||
|
||||
<AutosuggestTextbox
|
||||
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
|
@ -340,16 +337,18 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
|
||||
<div className={actionsContainerClasses}>
|
||||
<div className={[_s.default, _s.flexRow, _s.marginRightAuto].join(' ')}>
|
||||
<RichTextEditorButton small={shouldCondense} />
|
||||
<UploadButton small={shouldCondense} />
|
||||
{
|
||||
!edit && <PollButton small={shouldCondense} />
|
||||
}
|
||||
{
|
||||
!shouldCondense &&
|
||||
<PostPrivacyButton />
|
||||
<StatusVisibilityButton small={shouldCondense} />
|
||||
}
|
||||
<SpoilerButton small={shouldCondense} />
|
||||
<SchedulePostDropdown small={shouldCondense} position={isModalOpen ? 'top' : undefined} />
|
||||
<GifSelectorButton small={shouldCondense} />
|
||||
<EmojiPickerButton small={shouldCondense} />
|
||||
</div>
|
||||
<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'
|
||||
|
||||
const messages = defineMessages({
|
||||
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
|
||||
})
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
// unavailable: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 0),
|
||||
// active: state.getIn(['compose', 'poll']) !== null,
|
||||
|
@ -8,46 +13,47 @@ const mapStateToProps = state => ({
|
|||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onClick() {
|
||||
dispatch((_, getState) => {
|
||||
if (getState().getIn(['compose', 'poll'])) {
|
||||
dispatch(removePoll())
|
||||
} else {
|
||||
dispatch(addPoll())
|
||||
}
|
||||
})
|
||||
onClick(targetRef) {
|
||||
dispatch(openPopover('EMOJI_PICKER', {
|
||||
targetRef,
|
||||
}))
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class EmojiPickerButton extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
unavailable: PropTypes.bool,
|
||||
active: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
active: PropTypes.bool,
|
||||
small: PropTypes.bool,
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onClick()
|
||||
this.props.onClick(this.button)
|
||||
}
|
||||
|
||||
setButton = (n) => {
|
||||
this.button = n
|
||||
}
|
||||
|
||||
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 (
|
||||
<ComposeExtraButton
|
||||
disabled={disabled}
|
||||
onClick={this.handleClick}
|
||||
icon='happy'
|
||||
title={title}
|
||||
small={small}
|
||||
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);
|
|
@ -30,19 +30,14 @@ class GroupsCollection extends ImmutablePureComponent {
|
|||
render() {
|
||||
const { groupIds } = this.props
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.flexRow, _s.flexWrap].join(' ')}>
|
||||
<ScrollableList
|
||||
scrollKey='groups_collection'
|
||||
emptyMessage={''}
|
||||
>
|
||||
{
|
||||
groupIds.map((groupId, i) => (
|
||||
<GroupCollectionItem key={`group-collection-item-${i}`} id={groupId} />
|
||||
))
|
||||
}
|
||||
</ScrollableList>
|
||||
{
|
||||
groupIds.map((groupId, i) => (
|
||||
<GroupCollectionItem key={`group-collection-item-${i}`} id={groupId} />
|
||||
))
|
||||
}
|
||||
</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() {
|
||||
return import(/* webpackChunkName: "features/blocks" */'../../blocks')
|
||||
return import(/* webpackChunkName: "features/blocks" */'../../blocked_accounts')
|
||||
}
|
||||
|
||||
export function CommunityTimeline() {
|
||||
|
@ -19,7 +19,7 @@ export function Compose() {
|
|||
}
|
||||
|
||||
export function DomainBlocks() {
|
||||
return import(/* webpackChunkName: "features/domain_blocks" */'../../domain_blocks')
|
||||
return import(/* webpackChunkName: "features/domain_blocks" */'../../blocked_domains')
|
||||
}
|
||||
|
||||
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 order = ['public', 'unlisted', 'private', 'direct'];
|
||||
const order = ['public', 'unlisted', 'private'];
|
||||
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%);
|
||||
|
||||
&:first-child {
|
||||
|
|
Loading…
Reference in New Issue