This commit is contained in:
mgabdev
2020-04-01 23:17:21 -04:00
parent 1a33759e19
commit 80d41b8d94
50 changed files with 1771 additions and 610 deletions

View File

@@ -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>

View File

@@ -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>
)
}
}

View File

@@ -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
/>
)
}
}
}

View File

@@ -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>

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>
);
}

View 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>
)
}
}

View File

@@ -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 }),

View File

@@ -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,

View File

@@ -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 (

View File

@@ -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>

View File

@@ -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,

View File

@@ -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(' ')}>

View 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>
)
}
}

View File

@@ -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>
);
}
}

View File

@@ -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>
);
}
}

View File

@@ -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,

View File

@@ -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>
)

View File

@@ -488,7 +488,6 @@ class Video extends PureComponent {
return (
<div
role='menuitem'
className={[_s.default].join(' ')}
style={playerStyle}
ref={this.setPlayerRef}