Progress
This commit is contained in:
@@ -36,6 +36,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||
onBlur: PropTypes.func,
|
||||
textarea: PropTypes.bool,
|
||||
small: PropTypes.bool,
|
||||
prependIcon: PropTypes.string,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
@@ -208,7 +209,8 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||
className,
|
||||
id,
|
||||
maxLength,
|
||||
textarea
|
||||
textarea,
|
||||
prependIcon
|
||||
} = this.props
|
||||
|
||||
const { suggestionsHidden } = this.state
|
||||
@@ -232,26 +234,11 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||
mr5: small,
|
||||
})
|
||||
|
||||
// <div aria-activedescendant="typeaheadFocus-0.35973815699338085"
|
||||
// aria-autocomplete="list"
|
||||
// aria-controls="typeaheadDropdownWrapped-0"
|
||||
// aria-describedby="placeholder-7g4r6"
|
||||
// aria-label="Tweet text"
|
||||
// aria-multiline="true"
|
||||
// class="notranslate public-DraftEditor-content"
|
||||
// contenteditable="true"
|
||||
// data-testid="tweetTextarea_0"
|
||||
// role="textbox"
|
||||
// spellcheck="true"
|
||||
// tabindex="0"
|
||||
// no-focuscontainer-refocus="true"
|
||||
// style="outline: none; user-select: text; white-space: pre-wrap; overflow-wrap: break-word;">
|
||||
|
||||
if (textarea) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={[_s.default, _s.flexGrow1].join(' ')}>
|
||||
<div className={[_s.default, _s.ml5].join(' ')}>
|
||||
<div className={[_s.default].join(' ')}>
|
||||
|
||||
<Composer
|
||||
inputRef={this.setTextbox}
|
||||
@@ -265,6 +252,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onPaste={this.onPaste}
|
||||
small={small}
|
||||
/>
|
||||
|
||||
{ /* <Textarea
|
||||
@@ -350,6 +338,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||
id={id}
|
||||
className={className}
|
||||
maxLength={maxLength}
|
||||
prependIcon={prependIcon}
|
||||
/>
|
||||
</label>
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import Text from './text'
|
||||
|
||||
export default class Badge extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
}
|
||||
|
||||
state = {
|
||||
hovering: false,
|
||||
}
|
||||
|
||||
handleOnMouseEnter = () => {
|
||||
this.setState({ hovering: true })
|
||||
}
|
||||
|
||||
handleOnMouseLeave = () => {
|
||||
this.setState({ hovering: false })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, description } = this.props
|
||||
const { hovering } = this.state // : todo : tooltip
|
||||
|
||||
return (
|
||||
<Text
|
||||
color='white'
|
||||
size='extraSmall'
|
||||
className={[_s.backgroundColorBrand, _s.px5, _s.lineHeight125, _s.radiusSmall].join(' ')}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,12 @@ import {
|
||||
} from 'draft-js'
|
||||
import { urlRegex } from '../features/compose/util/url_regex'
|
||||
import classNames from 'classnames/bind'
|
||||
import { me } from '../initial_state'
|
||||
import { makeGetAccount } from '../selectors'
|
||||
import Button from './button'
|
||||
|
||||
import 'draft-js/dist/Draft.css'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
const getBlockStyle = (block) => {
|
||||
@@ -57,55 +61,61 @@ const RTE_ITEMS = [
|
||||
label: 'Bold',
|
||||
style: 'BOLD',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
icon: 'bold',
|
||||
},
|
||||
{
|
||||
label: 'Italic',
|
||||
style: 'ITALIC',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
icon: 'italic',
|
||||
},
|
||||
{
|
||||
label: 'Underline',
|
||||
style: 'UNDERLINE',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
icon: 'underline',
|
||||
},
|
||||
{
|
||||
label: 'Monospace',
|
||||
style: 'CODE',
|
||||
label: 'Strikethrough',
|
||||
style: 'STRIKETHROUGH',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
icon: 'strikethrough',
|
||||
},
|
||||
// {
|
||||
// label: 'Monospace',
|
||||
// style: 'CODE',
|
||||
// type: 'style',
|
||||
// icon: 'circle',
|
||||
// },
|
||||
{
|
||||
label: 'H1',
|
||||
style: 'header-one',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
icon: 'text-size',
|
||||
},
|
||||
{
|
||||
label: 'Blockquote',
|
||||
style: 'blockquote',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'UL',
|
||||
style: 'unordered-list-item',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'OL',
|
||||
style: 'ordered-list-item',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
icon: 'blockquote',
|
||||
},
|
||||
{
|
||||
label: 'Code Block',
|
||||
style: 'code-block',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
icon: 'code',
|
||||
},
|
||||
{
|
||||
label: 'UL',
|
||||
style: 'unordered-list-item',
|
||||
type: 'block',
|
||||
icon: 'ul-list',
|
||||
},
|
||||
{
|
||||
label: 'OL',
|
||||
style: 'ordered-list-item',
|
||||
type: 'block',
|
||||
icon: 'ol-list',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -127,7 +137,26 @@ const compositeDecorator = new CompositeDecorator([
|
||||
const HANDLE_REGEX = /\@[\w]+/g;
|
||||
const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;
|
||||
|
||||
export default class Composer extends PureComponent {
|
||||
const mapStateToProps = state => {
|
||||
const getAccount = makeGetAccount()
|
||||
const account = getAccount(state, me)
|
||||
const isPro = account.get('is_pro')
|
||||
|
||||
return {
|
||||
isPro,
|
||||
rteControlsVisible: state.getIn(['compose', 'rte_controls_visible']),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class Composer extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
inputRef: PropTypes.func,
|
||||
@@ -141,6 +170,9 @@ export default class Composer extends PureComponent {
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
onPaste: PropTypes.func,
|
||||
small: PropTypes.bool,
|
||||
isPro: PropTypes.bool.isRequired,
|
||||
rteControlsVisible: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
@@ -176,22 +208,21 @@ export default class Composer extends PureComponent {
|
||||
this.onChange(RichUtils.onTab(e, this.state.editorState, maxDepth))
|
||||
}
|
||||
|
||||
toggleBlockType = (blockType) => {
|
||||
this.onChange(
|
||||
RichUtils.toggleBlockType(
|
||||
this.state.editorState,
|
||||
blockType
|
||||
toggleEditorStyle = (style, type) => {
|
||||
console.log("toggleEditorStyle:", style, type)
|
||||
if (type === 'style') {
|
||||
this.onChange(
|
||||
RichUtils.toggleInlineStyle(this.state.editorState, style)
|
||||
)
|
||||
)
|
||||
} else if (type === 'block') {
|
||||
this.onChange(
|
||||
RichUtils.toggleBlockType(this.state.editorState, style)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
toggleInlineStyle = (inlineStyle) => {
|
||||
this.onChange(
|
||||
RichUtils.toggleInlineStyle(
|
||||
this.state.editorState,
|
||||
inlineStyle
|
||||
)
|
||||
)
|
||||
handleOnTogglePopoutEditor = () => {
|
||||
//
|
||||
}
|
||||
|
||||
setRef = (n) => {
|
||||
@@ -210,28 +241,61 @@ export default class Composer extends PureComponent {
|
||||
onKeyUp,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onPaste
|
||||
onPaste,
|
||||
small,
|
||||
isPro,
|
||||
rteControlsVisible
|
||||
} = this.props
|
||||
const { editorState } = this.state
|
||||
|
||||
const editorContainerClasses = cx({
|
||||
default: 1,
|
||||
RTE: 1,
|
||||
cursorText: 1,
|
||||
text: 1,
|
||||
fontSize16PX: !small,
|
||||
fontSize14PX: small,
|
||||
pt15: !small,
|
||||
px15: !small,
|
||||
px10: small,
|
||||
pt10: small,
|
||||
pb10: 1,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={[_s.default].join(' ')}>
|
||||
|
||||
<div className={[_s.default, _s.backgroundColorPrimary, _s.borderBottom1PX, _s.borderColorSecondary, _s.py5, _s.px15, _s.alignItemsCenter, _s.flexRow].join(' ')}>
|
||||
{
|
||||
RTE_ITEMS.map((item, i) => (
|
||||
<StyleButton
|
||||
key={`rte-button-${i}`}
|
||||
editorState={editorState}
|
||||
{...item}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
{
|
||||
rteControlsVisible && isPro &&
|
||||
<div className={[_s.default, _s.backgroundColorPrimary, _s.borderBottom1PX, _s.borderColorSecondary, _s.py5, _s.px15, _s.alignItemsCenter, _s.flexRow].join(' ')}>
|
||||
{
|
||||
RTE_ITEMS.map((item, i) => (
|
||||
<StyleButton
|
||||
key={`rte-button-${i}`}
|
||||
editorState={editorState}
|
||||
onClick={this.toggleEditorStyle}
|
||||
{...item}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<Button
|
||||
backgroundColor='none'
|
||||
color='secondary'
|
||||
onClick={this.handleOnTogglePopoutEditor}
|
||||
title='Fullscreen'
|
||||
className={[_s.px10, _s.noSelect, _s.marginLeftAuto].join(' ')}
|
||||
icon='fullscreen'
|
||||
iconClassName={_s.inheritFill}
|
||||
iconWidth='12px'
|
||||
iconHeight='12px'
|
||||
radiusSmall
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div
|
||||
onClick={this.focus}
|
||||
className={[_s.text, _s.fontSize16PX].join(' ')}
|
||||
className={editorContainerClasses}
|
||||
>
|
||||
<Editor
|
||||
blockStyleFn={getBlockStyle}
|
||||
@@ -240,7 +304,7 @@ export default class Composer extends PureComponent {
|
||||
handleKeyCommand={this.handleKeyCommand}
|
||||
onChange={this.onChange}
|
||||
onTab={this.onTab}
|
||||
placeholder={placeholder}
|
||||
// placeholder={placeholder}
|
||||
ref={this.setRef}
|
||||
/>
|
||||
</div>
|
||||
@@ -252,17 +316,17 @@ export default class Composer extends PureComponent {
|
||||
|
||||
class StyleButton extends PureComponent {
|
||||
static propTypes = {
|
||||
onToggle: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
label: PropTypes.string,
|
||||
style: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
}
|
||||
|
||||
handleOnToggle = (e) => {
|
||||
const { onToggle, style } = this.props
|
||||
handleOnClick
|
||||
= (e) => {
|
||||
e.preventDefault()
|
||||
onToggle(style)
|
||||
this.props.onClick(this.props.style, this.props.type)
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -279,13 +343,13 @@ class StyleButton extends PureComponent {
|
||||
const currentStyle = editorState.getCurrentInlineStyle()
|
||||
const blockType = editorState.getCurrentContent().getBlockForKey(selection.getStartKey()).getType()
|
||||
|
||||
let active
|
||||
// active={type.style === blockType}
|
||||
// active={currentStyle.has(type.style)}
|
||||
|
||||
const active = type === 'block' ? style === blockType : currentStyle.has(style)
|
||||
const color = active ? 'white' : 'secondary'
|
||||
|
||||
const btnClasses = cx({
|
||||
px10: 1,
|
||||
mr5: 1,
|
||||
noSelect: 1,
|
||||
backgroundSubtle2Dark_onHover: 1,
|
||||
backgroundColorBrandLight: active,
|
||||
// py10: !small,
|
||||
@@ -293,23 +357,20 @@ class StyleButton extends PureComponent {
|
||||
// px5: small,
|
||||
})
|
||||
|
||||
const iconClasses = cx({
|
||||
fillColorSecondary: !active,
|
||||
fillColorWhite: active,
|
||||
})
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={btnClasses}
|
||||
backgroundColor='none'
|
||||
onClick={this.handleOnToggle}
|
||||
color={color}
|
||||
onClick={this.handleOnClick}
|
||||
title={label}
|
||||
icon={'rich-text'}
|
||||
iconClassName={iconClasses}
|
||||
iconWidth='10px'
|
||||
iconHeight='10px'
|
||||
icon={icon}
|
||||
iconClassName={_s.inheritFill}
|
||||
iconWidth='12px'
|
||||
iconHeight='12px'
|
||||
radiusSmall
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,18 @@ import { NavLink } from 'react-router-dom'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import classNames from 'classnames/bind'
|
||||
import { shortNumberFormat } from '../utils/numbers'
|
||||
import Image from './image'
|
||||
import Text from './text'
|
||||
import Button from './button'
|
||||
import DotTextSeperator from './dot_text_seperator'
|
||||
import Image from './image'
|
||||
import Text from './text'
|
||||
|
||||
const messages = defineMessages({
|
||||
members: { id: 'groups.card.members', defaultMessage: 'Members' },
|
||||
new_statuses: { id: 'groups.sidebar-panel.item.view', defaultMessage: 'new gabs' },
|
||||
no_recent_activity: { id: 'groups.sidebar-panel.item.no_recent_activity', defaultMessage: 'No recent activity' },
|
||||
viewGroup: { id: 'view_group', defaultMessage: 'View Group' },
|
||||
member: { id: 'member', defaultMessage: 'Member' },
|
||||
admin: { id: 'admin', defaultMessage: 'Admin' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
@@ -49,8 +52,7 @@ class GroupCollectionItem extends ImmutablePureComponent {
|
||||
|
||||
const imageHeight = '200px'
|
||||
|
||||
// : todo :
|
||||
const isMember = false
|
||||
const isMember = relationships.get('member')
|
||||
|
||||
const outsideClasses = cx({
|
||||
default: 1,
|
||||
@@ -83,6 +85,25 @@ class GroupCollectionItem extends ImmutablePureComponent {
|
||||
height={imageHeight}
|
||||
/>
|
||||
|
||||
<div className={[_s.default, _s.flexRow, _s.positionAbsolute, _s.top0, _s.right0, _s.pt10, _s.mr10].join(' ')}>
|
||||
<Text
|
||||
badge
|
||||
className={_s.backgroundColorWhite}
|
||||
size='extraSmall'
|
||||
color='brand'
|
||||
>
|
||||
{intl.formatMessage(messages.member)}
|
||||
</Text>
|
||||
<Text
|
||||
badge
|
||||
className={[_s.backgroundColorBlack, _s.ml5].join(' ')}
|
||||
size='extraSmall'
|
||||
color='white'
|
||||
>
|
||||
{intl.formatMessage(messages.admin)}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className={[_s.default, _s.px10, _s.my10].join(' ')}>
|
||||
<Text color='primary' size='medium' weight='bold'>
|
||||
{group.get('title')}
|
||||
@@ -101,20 +122,17 @@ class GroupCollectionItem extends ImmutablePureComponent {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
!isMember &&
|
||||
<div className={[_s.default, _s.px10, _s.mb10].join(' ')}>
|
||||
<Button
|
||||
color='primary'
|
||||
backgroundColor='tertiary'
|
||||
radiusSmall
|
||||
>
|
||||
<Text color='inherit' weight='bold'>
|
||||
Join
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
<div className={[_s.default, _s.px10, _s.mb10].join(' ')}>
|
||||
<Button
|
||||
color='primary'
|
||||
backgroundColor='tertiary'
|
||||
radiusSmall
|
||||
>
|
||||
<Text color='inherit' weight='bold'>
|
||||
{intl.formatMessage(messages.viewGroup)}
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,8 @@ import AppsIcon from '../assets/apps_icon'
|
||||
import AudioIcon from '../assets/audio_icon'
|
||||
import AudioMuteIcon from '../assets/audio_mute_icon'
|
||||
import BackIcon from '../assets/back_icon'
|
||||
import BlockquoteIcon from '../assets/blockquote_icon'
|
||||
import BoldIcon from '../assets/bold_icon'
|
||||
import CalendarIcon from '../assets/calendar_icon'
|
||||
import ChatIcon from '../assets/chat_icon'
|
||||
import CircleIcon from '../assets/circle_icon'
|
||||
@@ -22,6 +24,7 @@ import GroupAddIcon from '../assets/group_add_icon'
|
||||
import HappyIcon from '../assets/happy_icon'
|
||||
import HomeIcon from '../assets/home_icon'
|
||||
import InvestorIcon from '../assets/investor_icon'
|
||||
import ItalicIcon from '../assets/italic_icon'
|
||||
import LikeIcon from '../assets/like_icon'
|
||||
import LinkIcon from '../assets/link_icon'
|
||||
import ListIcon from '../assets/list_icon'
|
||||
@@ -32,6 +35,7 @@ import MinimizeFullscreenIcon from '../assets/minimize_fullscreen_icon'
|
||||
import MissingIcon from '../assets/missing_icon'
|
||||
import MoreIcon from '../assets/more_icon'
|
||||
import NotificationsIcon from '../assets/notifications_icon'
|
||||
import OLListIcon from '../assets/ol_list_icon'
|
||||
import PauseIcon from '../assets/pause_icon'
|
||||
import PinIcon from '../assets/pin_icon'
|
||||
import PlayIcon from '../assets/play_icon'
|
||||
@@ -43,8 +47,12 @@ import SearchIcon from '../assets/search_icon'
|
||||
import SearchAltIcon from '../assets/search_alt_icon'
|
||||
import ShareIcon from '../assets/share_icon'
|
||||
import ShopIcon from '../assets/shop_icon'
|
||||
import StrikethroughIcon from '../assets/strikethrough_icon'
|
||||
import SubtractIcon from '../assets/subtract_icon'
|
||||
import TextSizeIcon from '../assets/text_size_icon'
|
||||
import TrendsIcon from '../assets/trends_icon'
|
||||
import ULListIcon from '../assets/ul_list_icon'
|
||||
import UnderlineIcon from '../assets/underline_icon'
|
||||
import VerifiedIcon from '../assets/verified_icon'
|
||||
import WarningIcon from '../assets/warning_icon'
|
||||
|
||||
@@ -55,6 +63,8 @@ const ICONS = {
|
||||
'audio': AudioIcon,
|
||||
'audio-mute': AudioMuteIcon,
|
||||
'back': BackIcon,
|
||||
'blockquote': BlockquoteIcon,
|
||||
'bold': BoldIcon,
|
||||
'calendar': CalendarIcon,
|
||||
'chat': ChatIcon,
|
||||
'close': CloseIcon,
|
||||
@@ -72,6 +82,7 @@ const ICONS = {
|
||||
'happy': HappyIcon,
|
||||
'home': HomeIcon,
|
||||
'investor': InvestorIcon,
|
||||
'italic': ItalicIcon,
|
||||
'like': LikeIcon,
|
||||
'link': LinkIcon,
|
||||
'list': ListIcon,
|
||||
@@ -82,6 +93,7 @@ const ICONS = {
|
||||
'missing': MissingIcon,
|
||||
'more': MoreIcon,
|
||||
'notifications': NotificationsIcon,
|
||||
'ol-list': OLListIcon,
|
||||
'pause': PauseIcon,
|
||||
'pin': PinIcon,
|
||||
'play': PlayIcon,
|
||||
@@ -93,8 +105,12 @@ const ICONS = {
|
||||
'search-alt': SearchAltIcon,
|
||||
'share': ShareIcon,
|
||||
'shop': ShopIcon,
|
||||
'strikethrough': StrikethroughIcon,
|
||||
'subtract': SubtractIcon,
|
||||
'text-size': TextSizeIcon,
|
||||
'trends': TrendsIcon,
|
||||
'ul-list': ULListIcon,
|
||||
'underline': UnderlineIcon,
|
||||
'verified': VerifiedIcon,
|
||||
'warning': WarningIcon,
|
||||
'': CircleIcon,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Fragment } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import Button from './button'
|
||||
import Icon from './icon'
|
||||
import Text from './text'
|
||||
|
||||
@@ -71,6 +72,12 @@ export default class Input extends PureComponent {
|
||||
displayNone: hideLabel,
|
||||
})
|
||||
|
||||
const btnClasses = cx({
|
||||
displayNone: value.length === 0,
|
||||
px10: 1,
|
||||
mr5: 1,
|
||||
})
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{
|
||||
@@ -103,9 +110,16 @@ export default class Input extends PureComponent {
|
||||
|
||||
{
|
||||
hasClear &&
|
||||
<div role='button' tabIndex='0' className={'btnClasses'} onClick={onClear}>
|
||||
<Icon id='close' width='10px' height='10px' className={_s.fillColorWhite} aria-label='Clear' />
|
||||
</div>
|
||||
<Button
|
||||
className={btnClasses}
|
||||
tabIndex='0'
|
||||
title='Clear'
|
||||
onClick={onClear}
|
||||
icon='close'
|
||||
iconClassName={_s.inheritFill}
|
||||
iconHeight='10px'
|
||||
iconWidth='10px'
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</Fragment>
|
||||
|
||||
@@ -124,6 +124,7 @@ class Item extends ImmutablePureComponent {
|
||||
let right = 'auto';
|
||||
let float = 'left';
|
||||
let position = 'relative';
|
||||
let borderRadius = '0 0 0 0';
|
||||
|
||||
if (dimensions) {
|
||||
width = dimensions.w;
|
||||
@@ -134,13 +135,20 @@ class Item extends ImmutablePureComponent {
|
||||
left = dimensions.l || 'auto';
|
||||
float = dimensions.float || 'left';
|
||||
position = dimensions.pos || 'relative';
|
||||
|
||||
const br = dimensions.br || []
|
||||
const hasTL = br.indexOf('tl') > -1
|
||||
const hasTR = br.indexOf('tr') > -1
|
||||
const hasBR = br.indexOf('br') > -1
|
||||
const hasBL = br.indexOf('bl') > -1
|
||||
borderRadius = `${hasTL ? '8px' : '0'} ${hasTR ? '8px' : '0'} ${hasBR ? '8px' : '0'} ${hasBL ? '8px' : '0'}`
|
||||
}
|
||||
|
||||
let thumbnail = '';
|
||||
|
||||
if (attachment.get('type') === 'unknown') {
|
||||
return (
|
||||
<div className={[_s.default].join(' ')} key={attachment.get('id')} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
|
||||
<div className={[_s.default].join(' ')} key={attachment.get('id')} style={{ position, float, left, top, right, bottom, height, borderRadius, width: `${width}%` }}>
|
||||
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }}>
|
||||
<canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' />
|
||||
</a>
|
||||
@@ -169,6 +177,7 @@ class Item extends ImmutablePureComponent {
|
||||
href={attachment.get('remote_url') || originalUrl}
|
||||
onClick={this.handleClick}
|
||||
target='_blank'
|
||||
style={{ borderRadius }}
|
||||
>
|
||||
<img
|
||||
src={previewUrl}
|
||||
@@ -235,6 +244,7 @@ class MediaGallery extends PureComponent {
|
||||
cacheWidth: PropTypes.func,
|
||||
visible: PropTypes.bool,
|
||||
onToggleVisibility: PropTypes.func,
|
||||
reduced: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -278,8 +288,15 @@ class MediaGallery extends PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { media, intl, sensitive, height, defaultWidth } = this.props;
|
||||
const { visible } = this.state;
|
||||
const {
|
||||
media,
|
||||
intl,
|
||||
sensitive,
|
||||
height,
|
||||
defaultWidth,
|
||||
reduced
|
||||
} = this.props
|
||||
const { visible } = this.state
|
||||
|
||||
const width = this.state.width || defaultWidth;
|
||||
|
||||
@@ -331,34 +348,34 @@ class MediaGallery extends PureComponent {
|
||||
|
||||
if (isPortrait(ar1) && isPortrait(ar2)) {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: '100%', r: '2px' },
|
||||
{ w: 50, h: '100%', l: '2px' }
|
||||
{ w: 50, h: '100%', r: '2px', br: ['tl', 'bl'] },
|
||||
{ w: 50, h: '100%', l: '2px', br: ['tr', 'br'] },
|
||||
];
|
||||
} else if (isPanoramic(ar1) && isPanoramic(ar2)) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: panoSize_px, b: '2px' },
|
||||
{ w: 100, h: panoSize_px, t: '2px' }
|
||||
{ w: 100, h: panoSize_px, b: '2px', br: ['tl', 'tr'] },
|
||||
{ w: 100, h: panoSize_px, t: '2px', br: ['bl', 'br'] },
|
||||
];
|
||||
} else if (
|
||||
(isPanoramic(ar1) && isPortrait(ar2)) ||
|
||||
(isPanoramic(ar1) && isNonConformingRatio(ar2))
|
||||
) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: `${(width / maximumAspectRatio)}px`, b: '2px' },
|
||||
{ w: 100, h: `${(width * 0.6)}px`, t: '2px' },
|
||||
{ w: 100, h: `${(width / maximumAspectRatio)}px`, b: '2px', br: ['tl', 'tr'] },
|
||||
{ w: 100, h: `${(width * 0.6)}px`, t: '2px', br: ['bl', 'br'] },
|
||||
];
|
||||
} else if (
|
||||
(isPortrait(ar1) && isPanoramic(ar2)) ||
|
||||
(isNonConformingRatio(ar1) && isPanoramic(ar2))
|
||||
) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: `${(width * 0.6)}px`, b: '2px' },
|
||||
{ w: 100, h: `${(width / maximumAspectRatio)}px`, t: '2px' },
|
||||
{ w: 100, h: `${(width * 0.6)}px`, b: '2px', br: ['tl', 'tr'] },
|
||||
{ w: 100, h: `${(width / maximumAspectRatio)}px`, t: '2px', br: ['bl', 'br'] },
|
||||
];
|
||||
} else {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: '100%', r: '2px' },
|
||||
{ w: 50, h: '100%', l: '2px' }
|
||||
{ w: 50, h: '100%', r: '2px', br: ['tl', 'bl'] },
|
||||
{ w: 50, h: '100%', l: '2px', br: ['tr', 'br'] },
|
||||
];
|
||||
}
|
||||
} else if (size == 3) {
|
||||
@@ -374,60 +391,60 @@ class MediaGallery extends PureComponent {
|
||||
|
||||
if (isPanoramic(ar1) && isNonConformingRatio(ar2) && isNonConformingRatio(ar3)) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: `50%`, b: '2px' },
|
||||
{ w: 50, h: '50%', t: '2px', r: '2px' },
|
||||
{ w: 50, h: '50%', t: '2px', l: '2px' }
|
||||
{ w: 100, h: `50%`, b: '2px', br: ['tl', 'tr'] },
|
||||
{ w: 50, h: '50%', t: '2px', r: '2px', br: ['bl'] },
|
||||
{ w: 50, h: '50%', t: '2px', l: '2px', br: ['br'] },
|
||||
];
|
||||
} else if (isPanoramic(ar1) && isPanoramic(ar2) && isPanoramic(ar3)) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: panoSize_px, b: '4px' },
|
||||
{ w: 100, h: panoSize_px, b: '4px', br: ['tl', 'tr'] },
|
||||
{ w: 100, h: panoSize_px },
|
||||
{ w: 100, h: panoSize_px, t: '4px' }
|
||||
{ w: 100, h: panoSize_px, t: '4px', br: ['bl', 'br'] },
|
||||
];
|
||||
} else if (isPortrait(ar1) && isNonConformingRatio(ar2) && isNonConformingRatio(ar3)) {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: `100%`, r: '2px' },
|
||||
{ w: 50, h: '50%', b: '2px', l: '2px' },
|
||||
{ w: 50, h: '50%', t: '2px', l: '2px' },
|
||||
{ w: 50, h: `100%`, r: '2px', br: ['tl', 'bl'] },
|
||||
{ w: 50, h: '50%', b: '2px', l: '2px', br: ['tr'] },
|
||||
{ w: 50, h: '50%', t: '2px', l: '2px', br: ['br'] },
|
||||
];
|
||||
} else if (isNonConformingRatio(ar1) && isNonConformingRatio(ar2) && isPortrait(ar3)) {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px' },
|
||||
{ w: 50, h: '50%', l: '-2px', b: '-2px', pos: 'absolute', float: 'none' },
|
||||
{ w: 50, h: `100%`, r: '-2px', t: '0px', b: '0px', pos: 'absolute', float: 'none' }
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px', br: ['tl'] },
|
||||
{ w: 50, h: '50%', l: '-2px', b: '-2px', pos: 'absolute', float: 'none', br: ['bl'] },
|
||||
{ w: 50, h: `100%`, r: '-2px', t: '0px', b: '0px', pos: 'absolute', float: 'none', br: ['tr', 'br'] },
|
||||
];
|
||||
} else if (
|
||||
(isNonConformingRatio(ar1) && isPortrait(ar2) && isNonConformingRatio(ar3)) ||
|
||||
(isPortrait(ar1) && isPortrait(ar2) && isPortrait(ar3))
|
||||
) {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px' },
|
||||
{ w: 50, h: `100%`, l: '2px', float: 'right' },
|
||||
{ w: 50, h: '50%', t: '2px', r: '2px' }
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px', br: ['tl'] },
|
||||
{ w: 50, h: `100%`, l: '2px', float: 'right', br: ['tr', 'br'] },
|
||||
{ w: 50, h: '50%', t: '2px', r: '2px', br: ['bl'] },
|
||||
];
|
||||
} else if (
|
||||
(isPanoramic(ar1) && isPanoramic(ar2) && isNonConformingRatio(ar3)) ||
|
||||
(isPanoramic(ar1) && isPanoramic(ar2) && isPortrait(ar3))
|
||||
) {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: panoSize_px, b: '2px', r: '2px' },
|
||||
{ w: 50, h: panoSize_px, b: '2px', l: '2px' },
|
||||
{ w: 100, h: `${width - panoSize}px`, t: '2px' }
|
||||
{ w: 50, h: panoSize_px, b: '2px', r: '2px', br: ['tl'] },
|
||||
{ w: 50, h: panoSize_px, b: '2px', l: '2px', br: ['tr'] },
|
||||
{ w: 100, h: `${width - panoSize}px`, t: '2px', br: ['bl', 'br'] },
|
||||
];
|
||||
} else if (
|
||||
(isNonConformingRatio(ar1) && isPanoramic(ar2) && isPanoramic(ar3)) ||
|
||||
(isPortrait(ar1) && isPanoramic(ar2) && isPanoramic(ar3))
|
||||
) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: `${width - panoSize}px`, b: '2px' },
|
||||
{ w: 50, h: panoSize_px, t: '2px', r: '2px' },
|
||||
{ w: 50, h: panoSize_px, t: '2px', l: '2px' },
|
||||
{ w: 100, h: `${width - panoSize}px`, b: '2px', br: ['tl', 'tr'] },
|
||||
{ w: 50, h: panoSize_px, t: '2px', r: '2px', br: ['bl'] },
|
||||
{ w: 50, h: panoSize_px, t: '2px', l: '2px', br: ['br'] },
|
||||
];
|
||||
} else {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px' },
|
||||
{ w: 50, h: '50%', b: '2px', l: '2px' },
|
||||
{ w: 100, h: `50%`, t: '2px' }
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px', br: ['tl'] },
|
||||
{ w: 50, h: '50%', b: '2px', l: '2px', br: ['tr'] },
|
||||
{ w: 100, h: `50%`, t: '2px', br: ['bl', 'br'] },
|
||||
];
|
||||
}
|
||||
} else if (size == 4) {
|
||||
@@ -489,6 +506,12 @@ class MediaGallery extends PureComponent {
|
||||
style.height = height;
|
||||
}
|
||||
|
||||
//If reduced (i.e. like in a quoted post)
|
||||
//then we need to make media smaller
|
||||
if (reduced) {
|
||||
style.height = width / 2
|
||||
}
|
||||
|
||||
children = media.take(4).map((attachment, i) => (
|
||||
<Item
|
||||
key={attachment.get('id')}
|
||||
@@ -530,12 +553,15 @@ class MediaGallery extends PureComponent {
|
||||
style={style}
|
||||
ref={this.handleRef}
|
||||
>
|
||||
{ /*
|
||||
|
||||
{ /* : todo :
|
||||
<div className={classNames('spoiler-button', { 'spoiler-button--minified': visible })}>
|
||||
{spoilerButton}
|
||||
</div> */ }
|
||||
|
||||
{children}
|
||||
<div className={[_s.default, _s.displayBlock, _s.width100PC, _s.height100PC, _s.overflowHidden].join(' ')}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
275
app/javascript/gabsocial/components/modal/gif_picker_modal.js
Normal file
275
app/javascript/gabsocial/components/modal/gif_picker_modal.js
Normal file
@@ -0,0 +1,275 @@
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import {
|
||||
fetchGifCategories,
|
||||
fetchGifResults,
|
||||
clearGifResults,
|
||||
setSelectedGif,
|
||||
changeGifSearchText
|
||||
} from '../../actions/tenor'
|
||||
import { closeModal } from '../../actions/modal'
|
||||
import Block from '../block'
|
||||
import Button from '../button'
|
||||
import ColumnIndicator from '../column_indicator'
|
||||
import Image from '../image'
|
||||
import Input from '../input'
|
||||
import Text from '../text'
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
title: { id: 'pick_gif', defaultMessage: 'Select a GIF' },
|
||||
searchGifs: { id: 'search_gifs', defaultMessage: 'Search for GIFs' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
categories: state.getIn(['tenor', 'categories']),
|
||||
suggestions: state.getIn(['tenor', 'suggestions']),
|
||||
results: state.getIn(['tenor', 'results']),
|
||||
loading: state.getIn(['tenor', 'loading']),
|
||||
error: state.getIn(['tenor', 'error']),
|
||||
searchText: state.getIn(['tenor', 'searchText']),
|
||||
})
|
||||
|
||||
export const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
handleCloseModal() {
|
||||
dispatch(changeGifSearchText(''))
|
||||
dispatch(clearGifResults())
|
||||
dispatch(closeModal())
|
||||
},
|
||||
|
||||
handleFetchCategories: () => {
|
||||
dispatch(fetchGifCategories())
|
||||
},
|
||||
|
||||
handleOnChange: (value) => {
|
||||
dispatch(changeGifSearchText(value))
|
||||
if (value.length >= 3) {
|
||||
dispatch(fetchGifResults())
|
||||
} else if (value.length === 0) {
|
||||
dispatch(clearGifResults())
|
||||
}
|
||||
},
|
||||
|
||||
handleSelectResult: (resultId) => {
|
||||
|
||||
},
|
||||
|
||||
// dispatchSubmit: (e) => {
|
||||
// e.preventDefault();
|
||||
// dispatch(getGifs());
|
||||
// },
|
||||
|
||||
})
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class GifPickerModal extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
handleCloseModal: PropTypes.func.isRequired,
|
||||
handleFetchCategories: PropTypes.func.isRequired,
|
||||
handleOnChange: PropTypes.func.isRequired,
|
||||
categories: PropTypes.array.isRequired,
|
||||
results: PropTypes.array.isRequired,
|
||||
loading: PropTypes.bool,
|
||||
error: PropTypes.bool,
|
||||
chosenUrl: PropTypes.string,
|
||||
searchText: PropTypes.string,
|
||||
}
|
||||
|
||||
state = {
|
||||
row: 0,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.handleFetchCategories()
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
this.props.handleOnChange(e.target.value)
|
||||
}
|
||||
|
||||
onHandleCloseModal = () => {
|
||||
this.props.handleCloseModal()
|
||||
}
|
||||
|
||||
handleSelectCategory = (category) => {
|
||||
this.props.handleOnChange(category)
|
||||
}
|
||||
|
||||
handleSelectGifResult = (resultId) => {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
categories,
|
||||
results,
|
||||
loading,
|
||||
error,
|
||||
searchText
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div style={{ width: '560px' }}>
|
||||
<Block>
|
||||
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.justifyContentCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.height53PX, _s.px15].join(' ')}>
|
||||
<div className={[_s.default, _s.flexGrow1, _s.mr5].join(' ')}>
|
||||
<Input
|
||||
onChange={this.onChange}
|
||||
value={searchText}
|
||||
prependIcon='search'
|
||||
placeholder={intl.formatMessage(messages.searchGifs)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
backgroundColor='none'
|
||||
title={intl.formatMessage(messages.close)}
|
||||
className={_s.marginLeftAuto}
|
||||
onClick={this.onHandleCloseModal}
|
||||
color='secondary'
|
||||
icon='close'
|
||||
iconWidth='10px'
|
||||
iconWidth='10px'
|
||||
/>
|
||||
</div>
|
||||
<div className={[_s.default, _s.heightMin50VH, _s.heightMax80VH, _s.overflowYScroll].join(' ')}>
|
||||
{
|
||||
error &&
|
||||
<ColumnIndicator type='error' />
|
||||
}
|
||||
{
|
||||
(loading && results.length === 0 && categories.length === 0) &&
|
||||
<ColumnIndicator type='loading' />
|
||||
}
|
||||
|
||||
{
|
||||
(results.length > 0 || categories.length > 0) &&
|
||||
<div className={[_s.default, _s.width100PC, _s.height100PC].join(' ')}>
|
||||
{
|
||||
results.length === 0 && categories.length > 0 &&
|
||||
<GifCategoriesCollection categories={categories} handleSelectCategory={this.handleSelectCategory} />
|
||||
}
|
||||
{
|
||||
results.length > 0 &&
|
||||
<GifResultsCollection results={results} handleSelectGifResult={this.handleSelectGifResult} />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Block>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class GifResultsCollectionColumn extends PureComponent {
|
||||
static propTypes = {
|
||||
results: PropTypes.array.isRequired,
|
||||
handleSelectGifResult: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
onClick = (resultId) => {
|
||||
this.props.handleSelectGifResult(resultId)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { results } = this.props
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.flexNormal].join(' ')}>
|
||||
{
|
||||
results.map((result, i) => (
|
||||
<button
|
||||
key={`gif-result-item-${i}`}
|
||||
onClick={() => this.onClick(result.id)}
|
||||
className={[_s.default, _s.cursorPointer, _s.px2, _s.py2].join(' ')}
|
||||
>
|
||||
<Image
|
||||
height={result.media[0].tinygif.dims[1]}
|
||||
src={result.media[0].tinygif.url}
|
||||
/>
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class GifResultsCollection extends PureComponent {
|
||||
static propTypes = {
|
||||
results: PropTypes.array.isRequired,
|
||||
handleSelectGifResult: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { results, handleSelectGifResult } = this.props
|
||||
|
||||
const count = results.length
|
||||
const columnIndex = 10
|
||||
|
||||
console.log("results:", results)
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.height100PC, _s.flexRow, _s.width100PC].join(' ')}>
|
||||
<GifResultsCollectionColumn
|
||||
results={results.slice(0, columnIndex)}
|
||||
handleSelectGifResult={handleSelectGifResult}
|
||||
/>
|
||||
<GifResultsCollectionColumn
|
||||
results={results.slice(columnIndex, columnIndex * 2)}
|
||||
handleSelectGifResult={handleSelectGifResult}
|
||||
/>
|
||||
<GifResultsCollectionColumn
|
||||
results={results.slice(columnIndex * 2, count)}
|
||||
handleSelectGifResult={handleSelectGifResult}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class GifCategoriesCollection extends PureComponent {
|
||||
static propTypes = {
|
||||
categories: PropTypes.array.isRequired,
|
||||
handleSelectCategory: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
onClick = (term) => {
|
||||
this.props.handleSelectCategory(term)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { categories } = this.props
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.height100PC, _s.width100PC, _s.flexRow, _s.flexWrap].join(' ')}>
|
||||
{
|
||||
categories.map((category, i) => (
|
||||
<button
|
||||
key={`gif-category-${i}`}
|
||||
onClick={() => this.onClick(category.searchterm)}
|
||||
className={[_s.default, _s.px2, _s.py2, _s.width50PC].join(' ')}
|
||||
>
|
||||
<div className={[_s.default, _s.cursorPointer].join(' ')}>
|
||||
<Image
|
||||
height={150}
|
||||
src={category.image}
|
||||
/>
|
||||
<div className={[_s.default, _s.positionAbsolute, _s.videoPlayerControlsBackground, _s.right0, _s.bottom0, _s.left0, _s.py10, _s.px10].join(' ')}>
|
||||
<Text color='white' weight='bold' size='large' align='left'>
|
||||
{category.searchterm}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import BoostModal from './boost_modal'
|
||||
import CommunityTimelineSettingsModal from './community_timeline_settings_modal'
|
||||
import ComposeModal from './compose_modal'
|
||||
import ConfirmationModal from './confirmation_modal'
|
||||
import GifPickerModal from './gif_picker_modal'
|
||||
import GroupCreateModal from './group_create_modal'
|
||||
import GroupDeleteModal from './group_delete_modal'
|
||||
import GroupEditorModal from './group_editor_modal'
|
||||
@@ -46,6 +47,7 @@ const MODAL_COMPONENTS = {
|
||||
COMPOSE: () => Promise.resolve({ default: ComposeModal }),
|
||||
CONFIRM: () => Promise.resolve({ default: ConfirmationModal }),
|
||||
EMBED: () => Promise.resolve({ default: EmbedModal }),
|
||||
GIF_PICKER: () => Promise.resolve({ default: GifPickerModal }),
|
||||
GROUP_CREATE: () => Promise.resolve({ default: GroupCreateModal }),
|
||||
GROUP_DELETE: () => Promise.resolve({ default: GroupDeleteModal }),
|
||||
GROUP_EDITOR: () => Promise.resolve({ default: GroupEditorModal }),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import DatePicker from 'react-datepicker'
|
||||
import { changeScheduledAt } from '../../actions/compose'
|
||||
import { openModal } from '../../actions/modal'
|
||||
import { me } from '../../initial_state'
|
||||
import { isMobile } from '../../utils/is_mobile'
|
||||
import PopoverLayout from './popover_layout'
|
||||
@@ -16,22 +15,15 @@ const mapDispatchToProps = dispatch => ({
|
||||
setScheduledAt (date) {
|
||||
dispatch(changeScheduledAt(date))
|
||||
},
|
||||
|
||||
onOpenProUpgradeModal() {
|
||||
dispatch(openModal('PRO_UPGRADE'))
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class DatePickerPopover extends PureComponent {
|
||||
static propTypes = {
|
||||
date: PropTypes.instanceOf(Date),
|
||||
setScheduledAt: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
isPro: PropTypes.bool,
|
||||
onOpenProUpgradeModal: PropTypes.func.isRequired,
|
||||
position: PropTypes.string,
|
||||
small: PropTypes.bool,
|
||||
}
|
||||
@@ -46,7 +38,6 @@ class DatePickerPopover extends PureComponent {
|
||||
const open = !!date
|
||||
const datePickerDisabled = !isPro
|
||||
const withPortal = isMobile(window.innerWidth)
|
||||
const popperPlacement = position || undefined
|
||||
|
||||
return (
|
||||
<PopoverLayout>
|
||||
@@ -63,7 +54,6 @@ class DatePickerPopover extends PureComponent {
|
||||
disabled={datePickerDisabled}
|
||||
showTimeSelect
|
||||
withPortal={withPortal}
|
||||
popperPlacement={popperPlacement}
|
||||
popperModifiers={{
|
||||
offset: {
|
||||
enabled: true,
|
||||
|
||||
@@ -38,8 +38,7 @@ const mapStateToProps = state => ({
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onClose(optionalType) {
|
||||
//
|
||||
dispatch(closePopover())
|
||||
dispatch(closePopover(optionalType))
|
||||
},
|
||||
})
|
||||
|
||||
@@ -62,17 +61,6 @@ class PopoverRoot extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
style: PropTypes.object,
|
||||
placement: PropTypes.string,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
style: {},
|
||||
placement: 'bottom',
|
||||
}
|
||||
|
||||
state = {
|
||||
mounted: false,
|
||||
}
|
||||
|
||||
handleDocumentClick = e => {
|
||||
@@ -85,8 +73,6 @@ class PopoverRoot extends PureComponent {
|
||||
document.addEventListener('click', this.handleDocumentClick, false)
|
||||
document.addEventListener('keydown', this.handleKeyDown, false)
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions)
|
||||
|
||||
this.setState({ mounted: true })
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -160,10 +146,8 @@ class PopoverRoot extends PureComponent {
|
||||
render() {
|
||||
const {
|
||||
type,
|
||||
style,
|
||||
props,
|
||||
} = this.props
|
||||
const { mounted } = this.state
|
||||
const visible = !!type
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Fragment } from 'react'
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
@@ -9,14 +7,10 @@ import { displayMedia } from '../../initial_state';
|
||||
import StatusCard from '../status_card'
|
||||
import { MediaGallery, Video } from '../../features/ui/util/async_components';
|
||||
import ComposeFormContainer from '../../features/compose/containers/compose_form_container'
|
||||
import Avatar from '../avatar';
|
||||
import StatusQuote from '../status_quote';
|
||||
import RelativeTimestamp from '../relative_timestamp';
|
||||
import DisplayName from '../display_name';
|
||||
import RecursiveStatusContainer from '../../containers/recursive_status_container'
|
||||
import StatusContent from '../status_content'
|
||||
import StatusPrepend from '../status_prepend'
|
||||
import StatusActionBar from '../status_action_bar';
|
||||
import Block from '../block';
|
||||
import Icon from '../icon';
|
||||
import Poll from '../poll';
|
||||
import StatusHeader from '../status_header'
|
||||
import Text from '../text'
|
||||
@@ -30,13 +24,14 @@ const cx = classNames.bind(_s)
|
||||
export const textForScreenReader = (intl, status, rebloggedByText = false) => {
|
||||
const displayName = status.getIn(['account', 'display_name']);
|
||||
|
||||
// : todo :
|
||||
const values = [
|
||||
displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
|
||||
status.get('spoiler_text') && status.get('hidden')
|
||||
? status.get('spoiler_text')
|
||||
: status.get('search_index').slice(status.get('spoiler_text').length),
|
||||
intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
|
||||
status.getIn(['account', 'acct']),
|
||||
// displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
|
||||
// status.get('spoiler_text') && status.get('hidden')
|
||||
// ? status.get('spoiler_text')
|
||||
// : status.get('search_index').slice(status.get('spoiler_text').length),
|
||||
// intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
|
||||
// status.getIn(['account', 'acct']),
|
||||
];
|
||||
|
||||
if (rebloggedByText) {
|
||||
@@ -47,19 +42,17 @@ export const textForScreenReader = (intl, status, rebloggedByText = false) => {
|
||||
};
|
||||
|
||||
export const defaultMediaVisibility = status => {
|
||||
if (!status) return undefined;
|
||||
if (!status) return undefined
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
status = status.get('reblog');
|
||||
status = status.get('reblog')
|
||||
}
|
||||
|
||||
return (displayMedia !== 'hide_all' && !status.get('sensitive')) || displayMedia === 'show_all';
|
||||
};
|
||||
return (displayMedia !== 'hide_all' && !status.get('sensitive')) || displayMedia === 'show_all'
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
filtered: { id: 'status.filtered', defaultMessage: 'Filtered' },
|
||||
promoted: { id:'status.promoted', defaultMessage: 'Promoted gab' },
|
||||
pinned: { id: 'status.pinned', defaultMessage: 'Pinned gab' },
|
||||
})
|
||||
|
||||
export default
|
||||
@@ -72,7 +65,6 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.map,
|
||||
onClick: PropTypes.func,
|
||||
onReply: PropTypes.func,
|
||||
onShowRevisions: PropTypes.func,
|
||||
@@ -91,7 +83,6 @@ class Status extends ImmutablePureComponent {
|
||||
onToggleHidden: PropTypes.func,
|
||||
muted: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
unread: PropTypes.bool,
|
||||
onMoveUp: PropTypes.func,
|
||||
onMoveDown: PropTypes.func,
|
||||
showThread: PropTypes.bool,
|
||||
@@ -104,16 +95,17 @@ class Status extends ImmutablePureComponent {
|
||||
onOpenProUpgradeModal: PropTypes.func,
|
||||
intl: PropTypes.object.isRequired,
|
||||
borderless: PropTypes.bool,
|
||||
};
|
||||
isChild: PropTypes.bool,
|
||||
}
|
||||
|
||||
// Avoid checking props that are functions (and whose equality will always
|
||||
// evaluate to false. See react-immutable-pure-component for usage.
|
||||
updateOnProps = ['status', 'account', 'muted', 'hidden'];
|
||||
updateOnProps = ['status', 'account', 'muted', 'hidden']
|
||||
|
||||
state = {
|
||||
showMedia: defaultMediaVisibility(this.props.status),
|
||||
statusId: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// Track height changes we know about to compensate scrolling
|
||||
componentDidMount() {
|
||||
@@ -122,10 +114,10 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
getSnapshotBeforeUpdate() {
|
||||
if (this.props.getScrollPosition) {
|
||||
return this.props.getScrollPosition();
|
||||
return this.props.getScrollPosition()
|
||||
}
|
||||
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
@@ -237,34 +229,34 @@ class Status extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
handleHotkeyMoveUp = e => {
|
||||
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'));
|
||||
};
|
||||
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'))
|
||||
}
|
||||
|
||||
handleHotkeyMoveDown = e => {
|
||||
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'));
|
||||
};
|
||||
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'))
|
||||
}
|
||||
|
||||
handleHotkeyToggleHidden = () => {
|
||||
this.props.onToggleHidden(this._properStatus());
|
||||
};
|
||||
this.props.onToggleHidden(this._properStatus())
|
||||
}
|
||||
|
||||
handleHotkeyToggleSensitive = () => {
|
||||
this.handleToggleMediaVisibility();
|
||||
};
|
||||
this.handleToggleMediaVisibility()
|
||||
}
|
||||
|
||||
_properStatus() {
|
||||
const { status } = this.props;
|
||||
const { status } = this.props
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
return status.get('reblog');
|
||||
return status.get('reblog')
|
||||
}
|
||||
|
||||
return status;
|
||||
return status
|
||||
}
|
||||
|
||||
handleRef = c => {
|
||||
this.node = c;
|
||||
};
|
||||
this.node = c
|
||||
}
|
||||
|
||||
|
||||
handleOpenProUpgradeModal = () => {
|
||||
@@ -276,21 +268,30 @@ class Status extends ImmutablePureComponent {
|
||||
intl,
|
||||
hidden,
|
||||
featured,
|
||||
unread,
|
||||
showThread,
|
||||
group,
|
||||
promoted,
|
||||
borderless
|
||||
borderless,
|
||||
isChild
|
||||
} = this.props
|
||||
|
||||
let media = null
|
||||
let prepend, rebloggedByText, reblogContent
|
||||
let rebloggedByText, reblogContent
|
||||
|
||||
// rebloggedByText = intl.formatMessage(
|
||||
// { id: 'status.reposted_by', defaultMessage: '{name} reposted' },
|
||||
// { name: status.getIn(['account', 'acct']) }
|
||||
// );
|
||||
|
||||
// reblogContent = status.get('contentHtml');
|
||||
// status = status.get('reblog');
|
||||
// }
|
||||
|
||||
let { status, ...other } = this.props;
|
||||
|
||||
// console.log("replies:", this.props.replies)
|
||||
|
||||
let { status, account, ...other } = this.props;
|
||||
|
||||
if (status === null) return null;
|
||||
if (!status) return null
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
@@ -302,12 +303,10 @@ class Status extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
if (status.get('filtered') || status.getIn(['reblog', 'filtered'])) {
|
||||
const minHandlers = this.props.muted
|
||||
? {}
|
||||
: {
|
||||
moveUp: this.handleHotkeyMoveUp,
|
||||
moveDown: this.handleHotkeyMoveDown,
|
||||
};
|
||||
const minHandlers = this.props.muted ? {} : {
|
||||
moveUp: this.handleHotkeyMoveUp,
|
||||
moveDown: this.handleHotkeyMoveDown,
|
||||
}
|
||||
|
||||
return (
|
||||
<HotKeys handlers={minHandlers}>
|
||||
@@ -315,73 +314,14 @@ class Status extends ImmutablePureComponent {
|
||||
<Text>{intl.formatMessage(messages.filtered)}</Text>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
if (promoted) {
|
||||
prepend = (
|
||||
<button className='status__prepend status__prepend--promoted' onClick={this.handleOpenProUpgradeModal}>
|
||||
<div className='status__prepend-icon-wrapper'>
|
||||
<Icon id='star' className='status__prepend-icon' fixedWidth />
|
||||
</div>
|
||||
<Text>{intl.formatMessage(messages.promoted)}</Text>
|
||||
</button>
|
||||
);
|
||||
} else if (featured) {
|
||||
prepend = (
|
||||
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.py5, _s.px15].join(' ')}>
|
||||
<Icon
|
||||
id='pin'
|
||||
width='10px'
|
||||
height='10px'
|
||||
className={_s.fillColorSecondary}
|
||||
/>
|
||||
<Text size='small' color='secondary' className={_s.ml5}>
|
||||
{intl.formatMessage(messages.pinned)}
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
} else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
||||
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'>
|
||||
<Icon id='repost' className='status__prepend-icon' fixedWidth />
|
||||
</div>
|
||||
{/*<FormattedMessage
|
||||
id='status.reposted_by'
|
||||
defaultMessage='{name} reposted'
|
||||
values={{
|
||||
name: (
|
||||
<NavLink to={`/${status.getIn(['account', 'acct'])}`} className='status__display-name muted'>
|
||||
<bdi>
|
||||
<strong dangerouslySetInnerHTML={display_name_html} />
|
||||
</bdi>
|
||||
</NavLink>
|
||||
),
|
||||
}}
|
||||
/> */ }
|
||||
</div>
|
||||
);
|
||||
|
||||
rebloggedByText = intl.formatMessage(
|
||||
{ id: 'status.reposted_by', defaultMessage: '{name} reposted' },
|
||||
{ name: status.getIn(['account', 'acct']) }
|
||||
);
|
||||
|
||||
account = status.get('account');
|
||||
reblogContent = status.get('contentHtml');
|
||||
status = status.get('reblog');
|
||||
)
|
||||
}
|
||||
|
||||
if (status.get('poll')) {
|
||||
media = <Poll pollId={status.get('poll')} />
|
||||
} else if (status.get('media_attachments').size > 0) {
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
const video = status.getIn(['media_attachments', 0]);
|
||||
|
||||
// console.log("VIDEO HERE")
|
||||
const video = status.getIn(['media_attachments', 0])
|
||||
|
||||
media = (
|
||||
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer}>
|
||||
@@ -409,6 +349,7 @@ class Status extends ImmutablePureComponent {
|
||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
|
||||
{Component => (
|
||||
<Component
|
||||
reduced={isChild}
|
||||
media={status.get('media_attachments')}
|
||||
sensitive={status.get('sensitive')}
|
||||
height={110}
|
||||
@@ -423,7 +364,6 @@ class Status extends ImmutablePureComponent {
|
||||
)
|
||||
}
|
||||
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
|
||||
// console.log("card:", status.get('card'))
|
||||
media = (
|
||||
<StatusCard
|
||||
onOpenMedia={this.props.onOpenMedia}
|
||||
@@ -434,10 +374,6 @@ class Status extends ImmutablePureComponent {
|
||||
)
|
||||
}
|
||||
|
||||
// console.log("da status:", status)
|
||||
let quotedStatus = status.get('quotedStatus');
|
||||
// console.log("quotedStatus:", quotedStatus)
|
||||
|
||||
const handlers = this.props.muted ? {} : {
|
||||
reply: this.handleHotkeyReply,
|
||||
favorite: this.handleHotkeyFavorite,
|
||||
@@ -451,15 +387,13 @@ class Status extends ImmutablePureComponent {
|
||||
toggleSensitive: this.handleHotkeyToggleSensitive,
|
||||
}
|
||||
|
||||
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`;
|
||||
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
mb15: !borderless,
|
||||
mb15: !borderless && !isChild,
|
||||
backgroundColorPrimary: 1,
|
||||
pb15: featured,
|
||||
borderBottom1PX: featured,
|
||||
borderColorSecondary: featured,
|
||||
borderBottom1PX: featured && !isChild,
|
||||
borderColorSecondary: featured && !isChild,
|
||||
})
|
||||
|
||||
const innerContainerClasses = cx({
|
||||
@@ -468,6 +402,10 @@ class Status extends ImmutablePureComponent {
|
||||
radiusSmall: !borderless,
|
||||
borderColorSecondary: !borderless,
|
||||
border1PX: !borderless,
|
||||
pb10: isChild && status.get('media_attachments').size === 0,
|
||||
pb5: isChild && status.get('media_attachments').size > 1,
|
||||
cursorPointer: isChild,
|
||||
backgroundSubtle_onHover: isChild,
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -478,14 +416,15 @@ class Status extends ImmutablePureComponent {
|
||||
data-featured={featured ? 'true' : null}
|
||||
aria-label={textForScreenReader(intl, status, rebloggedByText)}
|
||||
ref={this.handleRef}
|
||||
// onClick={this.handleClick}
|
||||
>
|
||||
<div className={innerContainerClasses}>
|
||||
|
||||
{prepend}
|
||||
|
||||
<div data-id={status.get('id')}>
|
||||
|
||||
<StatusHeader status={status} />
|
||||
<StatusPrepend status={status} promoted={promoted} featured={featured} />
|
||||
|
||||
<StatusHeader status={status} reduced={isChild} />
|
||||
|
||||
<div className={_s.default}>
|
||||
<StatusContent
|
||||
@@ -500,21 +439,24 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
{media}
|
||||
|
||||
{ /* status.get('quote') && <StatusQuote
|
||||
id={status.get('quote')}
|
||||
/> */ }
|
||||
{
|
||||
!!status.get('quote') && !isChild &&
|
||||
<div className={[_s.default, _s.mt10, _s.px10].join(' ')}>
|
||||
<Status status={status.get('quoted_status')} isChild />
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isChild &&
|
||||
<StatusActionBar status={status} {...other} />
|
||||
}
|
||||
|
||||
{ /* showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && (
|
||||
<button className='status__content__read-more-button' onClick={this.handleClick}>
|
||||
<FormattedMessage id='status.show_thread' defaultMessage='Show thread' />
|
||||
</button>
|
||||
) */ }
|
||||
|
||||
<StatusActionBar status={status} account={account} {...other} />
|
||||
|
||||
<div className={[_s.default, _s.borderTop1PX, _s.borderColorSecondary, _s.pt10, _s.px15, _s.mb10].join(' ')}>
|
||||
{ /* <ComposeFormContainer replyToId={status.get('id')} shouldCondense /> */ }
|
||||
</div>
|
||||
{
|
||||
!isChild &&
|
||||
<div className={[_s.default, _s.borderTop1PX, _s.borderColorSecondary, _s.pt10, _s.px15, _s.mb10].join(' ')}>
|
||||
<ComposeFormContainer replyToId={status.get('id')} shouldCondense />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -154,7 +154,11 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
)
|
||||
|
||||
const hasInteractions = favoriteCount > 0 || replyCount > 0 || repostCount > 0
|
||||
const shouldCondense = (!!status.get('card') || status.get('media_attachments').size > 0) && !hasInteractions
|
||||
const shouldCondense = (
|
||||
!!status.get('card') ||
|
||||
status.get('media_attachments').size > 0 ||
|
||||
!!status.get('quote')
|
||||
) && !hasInteractions
|
||||
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Fragment } from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import classNames from 'classnames/bind'
|
||||
import { openPopover } from '../actions/popover'
|
||||
import { openModal } from '../actions/modal'
|
||||
import RelativeTimestamp from './relative_timestamp'
|
||||
@@ -12,6 +13,8 @@ import Icon from './icon'
|
||||
import Button from './button'
|
||||
import Avatar from './avatar'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onOpenStatusRevisionsPopover(status) {
|
||||
dispatch(openModal('STATUS_REVISIONS', {
|
||||
@@ -36,6 +39,7 @@ class StatusHeader extends ImmutablePureComponent {
|
||||
status: ImmutablePropTypes.map,
|
||||
onOpenStatusRevisionsPopover: PropTypes.func.isRequired,
|
||||
onOpenStatusOptionsPopover: PropTypes.func.isRequired,
|
||||
reduced: PropTypes.bool,
|
||||
}
|
||||
|
||||
handleOpenStatusOptionsPopover = () => {
|
||||
@@ -122,21 +126,33 @@ class StatusHeader extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { status } = this.props
|
||||
const { status, reduced } = this.props
|
||||
|
||||
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`;
|
||||
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
px15: 1,
|
||||
py10: !reduced,
|
||||
pb10: reduced,
|
||||
})
|
||||
|
||||
const avatarSize = reduced ? 20 : 46
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.px15, _s.py10].join(' ')}>
|
||||
<div className={containerClasses}>
|
||||
<div className={[_s.default, _s.flexRow, _s.mt5].join(' ')}>
|
||||
|
||||
<NavLink
|
||||
to={`/${status.getIn(['account', 'acct'])}`}
|
||||
title={status.getIn(['account', 'acct'])}
|
||||
className={[_s.default, _s.mr10].join(' ')}
|
||||
>
|
||||
<Avatar account={status.get('account')} size={50} />
|
||||
</NavLink>
|
||||
{
|
||||
!reduced &&
|
||||
<NavLink
|
||||
to={`/${status.getIn(['account', 'acct'])}`}
|
||||
title={status.getIn(['account', 'acct'])}
|
||||
className={[_s.default, _s.mr10].join(' ')}
|
||||
>
|
||||
<Avatar account={status.get('account')} size={avatarSize} />
|
||||
</NavLink>
|
||||
}
|
||||
|
||||
<div className={[_s.default, _s.alignItemsStart, _s.flexGrow1, _s.mt5].join(' ')}>
|
||||
|
||||
@@ -149,18 +165,21 @@ class StatusHeader extends ImmutablePureComponent {
|
||||
<DisplayName account={status.get('account')} />
|
||||
</NavLink>
|
||||
|
||||
<Button
|
||||
text
|
||||
backgroundColor='none'
|
||||
color='none'
|
||||
icon='ellipsis'
|
||||
iconWidth='20px'
|
||||
iconHeight='20px'
|
||||
iconClassName={_s.fillColorSecondary}
|
||||
className={_s.marginLeftAuto}
|
||||
onClick={this.handleOpenStatusOptionsPopover}
|
||||
buttonRef={this.setStatusOptionsButton}
|
||||
/>
|
||||
{
|
||||
!reduced &&
|
||||
<Button
|
||||
text
|
||||
backgroundColor='none'
|
||||
color='none'
|
||||
icon='ellipsis'
|
||||
iconWidth='20px'
|
||||
iconHeight='20px'
|
||||
iconClassName={_s.fillColorSecondary}
|
||||
className={_s.marginLeftAuto}
|
||||
onClick={this.handleOpenStatusOptionsPopover}
|
||||
buttonRef={this.setStatusOptionsButton}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.lineHeight15].join(' ')}>
|
||||
|
||||
82
app/javascript/gabsocial/components/status_prepend.js
Normal file
82
app/javascript/gabsocial/components/status_prepend.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import Icon from './icon'
|
||||
import Text from './text'
|
||||
|
||||
const messages = defineMessages({
|
||||
filtered: { id: 'status.filtered', defaultMessage: 'Filtered' },
|
||||
promoted: { id: 'status.promoted', defaultMessage: 'Promoted gab' },
|
||||
pinned: { id: 'status.pinned', defaultMessage: 'Pinned gab' },
|
||||
reposted: { id: 'status.reposted_by', defaultMessage: '{name} reposted' },
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class StatusPrepend extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
status: ImmutablePropTypes.map,
|
||||
featured: PropTypes.bool,
|
||||
promoted: PropTypes.bool,
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
status,
|
||||
featured,
|
||||
promoted,
|
||||
} = this.props
|
||||
|
||||
if (!status) return null
|
||||
|
||||
const isRepost = (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object')
|
||||
|
||||
if (!featured && !promoted && !isRepost) return null
|
||||
|
||||
const iconId = featured ? 'pin' : promoted ? 'star' : 'repost'
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.width100PC, _s.alignItemsStart, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||
<div className={[_s.default, _s.width100PC, _s.flexRow, _s.alignItemsCenter, _s.py5, _s.px15].join(' ')}>
|
||||
<Icon id={iconId} width='12px' height='12px' className={[_s.fillColorSecondary, _s.mr5].join(' ')} />
|
||||
{
|
||||
isRepost &&
|
||||
<div className={[_s.default, _s.flexRow].join(' ')}>
|
||||
<Text size='small' color='secondary'>
|
||||
<FormattedMessage
|
||||
id='status.reposted_by'
|
||||
defaultMessage='{name} reposted'
|
||||
values={{
|
||||
name: (
|
||||
<NavLink
|
||||
className={[_s.noUnderline, _s.underline_onHover].join(' ')}
|
||||
to={`/${status.getIn(['account', 'acct'])}`}
|
||||
>
|
||||
<Text size='small' color='secondary'>
|
||||
<bdi>
|
||||
<span dangerouslySetInnerHTML={{ __html: status.getIn(['account', 'display_name_html']) }} />
|
||||
</bdi>
|
||||
</Text>
|
||||
</NavLink>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
!isRepost &&
|
||||
<Text color='secondary' size='small'>
|
||||
{intl.formatMessage(featured ? messages.pinned : messages.promoted)}
|
||||
</Text>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import StatusContent from './status_content';
|
||||
import DisplayName from './display_name';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
status: state.getIn(['statuses', id]),
|
||||
account: state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
|
||||
});
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export default class StatusQuote extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { status, account } = this.props;
|
||||
|
||||
const statusUrl = `/${account.get('acct')}/posts/${status.get('id')}`;
|
||||
|
||||
return (
|
||||
<NavLink to={statusUrl} className="status__quote">
|
||||
<DisplayName account={account} />
|
||||
<StatusContent
|
||||
status={status}
|
||||
expanded={false}
|
||||
onClick
|
||||
collapsable
|
||||
/>
|
||||
</NavLink>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import Avatar from '../../../../components/avatar';
|
||||
import Button from '../../../../components/button';
|
||||
import DisplayName from '../../../../components/display_name';
|
||||
import { isRtl } from '../../../../utils/rtl';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
|
||||
});
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class ReplyIndicator extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onCancel();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { status, intl } = this.props;
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
const style = {
|
||||
direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='reply-indicator'>
|
||||
<div className='reply-indicator__header'>
|
||||
<div className='reply-indicator__cancel'>
|
||||
<Button title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} inverted />
|
||||
</div>
|
||||
|
||||
<NavLink to={`/${status.getIn(['account', 'acct'])}`} className='reply-indicator__display-name'>
|
||||
<div className='reply-indicator__display-avatar'>
|
||||
<Avatar account={status.get('account')} size={24} />
|
||||
</div>
|
||||
<DisplayName account={status.get('account')} />
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div className='reply-indicator-content' style={style} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,6 +43,7 @@ export default class Text extends PureComponent {
|
||||
weight: PropTypes.oneOf(Object.keys(WEIGHTS)),
|
||||
align: PropTypes.oneOf(Object.keys(ALIGNMENTS)),
|
||||
underline: PropTypes.bool,
|
||||
badge: PropTypes.bool,
|
||||
htmlFor: PropTypes.string,
|
||||
}
|
||||
|
||||
@@ -63,13 +64,18 @@ export default class Text extends PureComponent {
|
||||
weight,
|
||||
underline,
|
||||
align,
|
||||
htmlFor
|
||||
htmlFor,
|
||||
badge
|
||||
} = this.props
|
||||
|
||||
const classes = cx(className, {
|
||||
default: 1,
|
||||
text: 1,
|
||||
|
||||
radiusSmall: badge,
|
||||
lineHeight15: badge,
|
||||
px5: badge,
|
||||
|
||||
colorPrimary: color === COLORS.primary,
|
||||
colorSecondary: color === COLORS.secondary,
|
||||
colorBrand: color === COLORS.brand,
|
||||
|
||||
@@ -63,9 +63,7 @@ class TimelineComposeBlock extends ImmutablePureComponent {
|
||||
{intl.formatMessage(messages.createPost)}
|
||||
</Heading>
|
||||
</div>
|
||||
<div className={[_s.default, _s.flexRow, _s.px15, _s.pt15, _s.pb10].join(' ')}>
|
||||
<ComposeFormContainer {...rest} />
|
||||
</div>
|
||||
<ComposeFormContainer {...rest} />
|
||||
</Block>
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -488,7 +488,6 @@ class Video extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
role='menuitem'
|
||||
className={[_s.default].join(' ')}
|
||||
style={playerStyle}
|
||||
ref={this.setPlayerRef}
|
||||
|
||||
Reference in New Issue
Block a user