Progress on Gab Deck, Updates on Compose

• Progress on Gab Deck, Updates on Compose
This commit is contained in:
mgabdev
2020-12-08 23:15:33 -05:00
parent 6950f67520
commit 998f00ae48
26 changed files with 895 additions and 381 deletions

View File

@@ -1,9 +1,12 @@
import { me } from '../initial_state'
import { saveSettings } from './settings'
export const DECK_CONNECT = 'DECK_CONNECT'
export const DECK_DISCONNECT = 'DECK_DISCONNECT'
export const DECK_SET_COLUMN_AT_INDEX = 'DECK_SET_COLUMN_AT_INDEX'
export const DECK_DELETE_COLUMN_AT_INDEX = 'DECK_DELETE_COLUMN_AT_INDEX'
export const DECK_CHANGE_COLUMN_AT_INDEX = 'DECK_CHANGE_COLUMN_AT_INDEX'
export const deckConnect = () => ({
type: DECK_CONNECT,
@@ -13,8 +16,28 @@ export const deckDisconnect = () => ({
type: DECK_DISCONNECT,
})
export const setDeckColumnAtIndex = (column, index) => ({
type: DECK_SET_COLUMN_AT_INDEX,
column,
index,
})
export const setDeckColumnAtIndex = (column, index) => (dispatch) => {
dispatch({
type: DECK_SET_COLUMN_AT_INDEX,
column,
index,
})
dispatch(saveSettings())
}
export const deleteDeckColumnAtIndex = (index) => (dispatch) => {
dispatch({
type: DECK_DELETE_COLUMN_AT_INDEX,
index,
})
dispatch(saveSettings())
}
export const updateDeckColumnAtIndex = (oldIndex, newIndex) => (dispatch) => {
dispatch({
type: DECK_CHANGE_COLUMN_AT_INDEX,
oldIndex,
newIndex,
})
dispatch(saveSettings())
}

View File

@@ -261,7 +261,7 @@ class Composer extends React.PureComponent {
<div className={_s.d}>
{
!small && isPro &&
isPro &&
<RichTextEditorBar
editorState={editorState}
onChange={this.onChange}

View File

@@ -10,12 +10,13 @@ class DeckColumn extends React.PureComponent {
subtitle,
icon,
children,
index,
} = this.props
return (
<div className={[_s.d, _s.w360PX, _s.px2, _s.bgSecondary, _s.h100VH].join(' ')}>
<div className={[_s.d, _s.w100PC, _s.bgPrimary, _s.h100VH].join(' ')}>
<DeckColumnHeader title={title} subtitle={subtitle} icon={icon} />
<DeckColumnHeader title={title} subtitle={subtitle} icon={icon} index={index} />
<div className={[_s.d, _s.w100PC, _s.overflowYScroll, _s.boxShadowNone, _s.posAbs, _s.top60PX, _s.left0, _s.right0, _s.bottom0].join(' ')}>
{children}
</div>
@@ -30,6 +31,7 @@ DeckColumn.propTypes = {
title: PropTypes.string,
subtitle: PropTypes.string,
icon: PropTypes.string,
index: PropTypes.number,
}
export default DeckColumn

View File

@@ -1,11 +1,21 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { deleteDeckColumnAtIndex } from '../actions/deck'
import Icon from './icon'
import Text from './text'
import Button from './button'
class DeckColumnHeader extends React.PureComponent {
handleClickDelete = () => {
this.props.dispatch(deleteDeckColumnAtIndex(this.props.index))
}
handleClickRefresh = () => {
}
render() {
const {
title,
@@ -15,22 +25,41 @@ class DeckColumnHeader extends React.PureComponent {
} = this.props
return (
<div className={[_s.d, _s.w100PC, _s.flexRow, _s.aiCenter, _s.h60PX, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary, _s.bgPrimary].join(' ')}>
{ !!icon && <Icon id={icon} className={_s.cPrimary} size='20px' /> }
<div data-sort-header className={[_s.d, _s.w100PC, _s.flexRow, _s.aiCenter, _s.h60PX, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary, _s.bgPrimary].join(' ')}>
{
!!icon &&
<div data-sort-header className={[_s.d, _s.flexRow, _s.mr15, _s.cursorEWResize].join(' ')}>
<span className={[_s.d, _s.w1PX, _s.h24PX, _s.mr2, _s.bgSecondary].join(' ')} />
<span className={[_s.d, _s.w1PX, _s.h24PX, _s.mr2, _s.bgSecondary].join(' ')} />
<span className={[_s.d, _s.w1PX, _s.h24PX, _s.bgSecondary].join(' ')} />
</div>
}
{ !!icon && <Icon id={icon} className={_s.cPrimary} size='18px' /> }
<div className={[_s.d, _s.flexRow, _s.aiEnd, _s.ml15].join(' ')}>
{ !!title && <Text size='large' weight='medium'>{title}</Text> }
{ !!subtitle && <Text className={_s.ml5} size='small' color='secondary'>{subtitle}</Text> }
{ !!title && <Text size='extraLarge' weight='medium'>{title}</Text> }
{ !!subtitle && <Text className={_s.ml5} color='secondary'>{subtitle}</Text> }
</div>
<div className={[_s.d, _s.aiCenter, _s.mlAuto, _s.jcCenter].join(' ')}>
{
!!title &&
{
!!title &&
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.mlAuto, _s.jcCenter].join(' ')}>
<Button
color='primary'
backgroundColor='none'
icon='list'
isNarrow
noClasses
onClick={this.handleClickRefresh}
className={[_s.d, _s.mr5, _s.cursorPointer, _s.outlineNone, _s.bgTransparent, _s.px5, _s.py5].join(' ')}
iconClassName={_s.cSecondary}
icon='repost'
/>
}
</div>
<Button
isNarrow
noClasses
onClick={this.handleClickDelete}
className={[_s.d, _s.mr5, _s.cursorPointer, _s.outlineNone, _s.bgTransparent, _s.px5, _s.py5].join(' ')}
iconClassName={_s.cSecondary}
icon='trash'
/>
</div>
}
</div>
)
}
@@ -41,6 +70,7 @@ DeckColumnHeader.propTypes = {
title: PropTypes.string,
subtitle: PropTypes.string,
icon: PropTypes.string,
index: PropTypes.number,
}
export default DeckColumnHeader
export default connect()(DeckColumnHeader)

View File

@@ -2,7 +2,6 @@ import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { setDeckColumnAtIndex } from '../../actions/deck'
import { saveSettings } from '../../actions/settings'
import { openModal } from '../../actions/modal'
import ModalLayout from './modal_layout'
import Button from '../button'
@@ -11,6 +10,7 @@ import Text from '../text'
class DeckColumnAddModal extends React.PureComponent {
onAdd = (column) => {
console.log("onAdd column: ", column)
switch (column) {
case 'user':
//
@@ -18,15 +18,12 @@ class DeckColumnAddModal extends React.PureComponent {
case 'list':
//
break
case 'groups':
//
break
case 'group':
//
break
default:
this.props.dispatch(setDeckColumnAtIndex(column))
this.props.dispatch(saveSettings())
this.props.onClose()
break
}
}
@@ -47,7 +44,7 @@ class DeckColumnAddModal extends React.PureComponent {
<div className={[_s.d, _s.pl10, _s.borderBottom1PX, _s.borderColorSecondary, _s.flexRow, _s.aiCenter, _s.jcCenter].join(' ')}>
<DeckColumnAddModalButton icon='home' type='Home' onClick={() => this.onAdd('home')} />
<DeckColumnAddModalButton icon='group' type='User' onClick={() => this.onAdd('user')} />
<DeckColumnAddModalButton icon='notifications' type='Notifications' onClick={() => this.onAdd('home')} />
<DeckColumnAddModalButton icon='notifications' type='Notifications' onClick={() => this.onAdd('notifications')} />
</div>
<div className={[_s.d, _s.pl10, _s.borderBottom1PX, _s.borderColorSecondary, _s.flexRow, _s.aiCenter, _s.jcCenter].join(' ')}>
<DeckColumnAddModalButton icon='list' type='List' onClick={() => this.onAdd('list')} />
@@ -56,7 +53,7 @@ class DeckColumnAddModal extends React.PureComponent {
</div>
<div className={[_s.d, _s.pl10, _s.pb10, _s.flexRow, _s.aiCenter, _s.jcCenter].join(' ')}>
<DeckColumnAddModalButton icon='pro' type='PRO Timeline' onClick={() => this.onAdd('pro')} />
<DeckColumnAddModalButton icon='group' type='Groups Timeline' onClick={() => this.onAdd('groups')} />
<DeckColumnAddModalButton icon='pencil' type='Compose' onClick={() => this.onAdd('compose')} />
<DeckColumnAddModalButton icon='group' type='Group Timeline' onClick={() => this.onAdd('group')} />
</div>
</div>
@@ -85,7 +82,7 @@ const DeckColumnAddModalButton = ({ icon, type, onClick }) => (
DeckColumnAddModalButton.propTypes = {
onSetDeckColumnAtIndex: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
}
export default connect()(DeckColumnAddModal)

View File

@@ -0,0 +1,96 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { length } from 'stringz'
import { openPopover } from '../../actions/popover'
import { MAX_POST_CHARACTER_COUNT } from '../../constants'
import Heading from '../heading'
import Button from '../button'
import BackButton from '../back_button'
import Text from '../text'
import CharacterCounter from '../character_counter'
class ComposeNavigationBar extends React.PureComponent {
handleOnPost = () => {
//
}
render() {
const {
isUploading,
isChangingUpload,
isSubmitting,
anyMedia,
text,
} = this.props
const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia)
const buttonOptions = {
backgroundColor: disabledButton ? 'tertiary' : 'brand',
color: disabledButton ? 'tertiary' : 'white',
isDisabled: disabledButton,
onClick: this.handleOnPost,
}
return (
<div className={[_s.d, _s.z4, _s.h53PX, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.h53PX, _s.bgNavigation, _s.aiCenter, _s.z3, _s.top0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
<div className={[_s.d, _s.flexRow, _s.saveAreaInsetPT, _s.saveAreaInsetPL, _s.saveAreaInsetPR, _s.w100PC].join(' ')}>
<BackButton
toHome
className={[_s.h53PX, _s.pl10, _s.pr10].join(' ')}
iconSize='18px'
iconClassName={[_s.mr5, _s.fillNavigation].join(' ')}
/>
<div className={[_s.d, _s.h53PX, _s.flexRow, _s.jcCenter, _s.aiCenter, _s.mrAuto].join(' ')}>
<Heading size='h1'>
Compose
</Heading>
</div>
<div className={[_s.d, _s.h53PX, _s.flexRow, _s.mlAuto, _s.aiCenter, _s.jcCenter, _s.mr15].join(' ')}>
<CharacterCounter max={MAX_POST_CHARACTER_COUNT} text={text} />
<Button {...buttonOptions}>
<Text color='inherit' weight='bold' size='medium' className={_s.px5}>
POST
</Text>
</Button>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state, props) => ({
isUploading: state.getIn(['compose', 'is_uploading']),
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
isSubmitting: state.getIn(['compose', 'is_submitting']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
text: state.getIn(['compose', 'text']),
})
const mapDispatchToProps = (dispatch) => ({
onSubmitCompose() {
//
},
})
ComposeNavigationBar.propTypes = {
isUploading: PropTypes.bool,
isChangingUpload: PropTypes.bool,
isSubmitting: PropTypes.bool,
anyMedia: PropTypes.bool,
text: PropTypes.string,
}
export default connect(mapStateToProps, mapDispatchToProps)(ComposeNavigationBar)

View File

@@ -11,6 +11,7 @@ import { makeGetAccount } from '../../selectors'
import {
THEMES,
DEFAULT_THEME,
MODAL_COMPOSE,
POPOVER_NAV_SETTINGS,
MODAL_DECK_COLUMN_ADD,
} from '../../constants'
@@ -38,6 +39,10 @@ class DeckSidebar extends ImmutablePureComponent {
this.props.onOpenNewColumnModal()
}
handleOnOpenComposeModal = () => {
this.props.onOpenComposeModal()
}
setAvatarNode = (c) => {
this.avatarNode = c
}
@@ -70,9 +75,9 @@ class DeckSidebar extends ImmutablePureComponent {
<div className={[_s.d, _s.px10].join(' ')}>
<NavigationBarButton icon='pencil' />
<NavigationBarButton icon='pencil' onClick={this.handleOnOpenComposeModal} />
<NavigationBarButton icon='search' />
<NavigationBarButton icon='search' to='/search' />
<Divider isSmall />
@@ -117,12 +122,15 @@ const mapDispatchToProps = (dispatch) => ({
onOpenNavSettingsPopover(targetRef) {
dispatch(openPopover(POPOVER_NAV_SETTINGS, {
targetRef,
position: 'right-start',
position: 'top-start',
}))
},
onOpenNewColumnModal() {
dispatch(openModal(MODAL_DECK_COLUMN_ADD))
},
onOpenComposeModal() {
dispatch(openModal(MODAL_COMPOSE))
},
onChange(key, value) {
dispatch(changeSetting(['displayOptions', key], value))
dispatch(saveSettings())
@@ -133,6 +141,7 @@ DeckSidebar.propTypes = {
account: ImmutablePropTypes.map,
onOpenNavSettingsPopover: PropTypes.func.isRequired,
onOpenNewColumnModal: PropTypes.func.isRequired,
onOpenComposeModal: PropTypes.func.isRequired,
theme: PropTypes.string,
logoDisabled: PropTypes.bool,
}

View File

@@ -10,6 +10,7 @@ export const BREAKPOINT_LARGE = 1280
export const BREAKPOINT_MEDIUM = 1160
export const BREAKPOINT_SMALL = 1080
export const BREAKPOINT_EXTRA_SMALL = 992
export const BREAKPOINT_EXTRA_EXTRA_SMALL = 767
export const MOUSE_IDLE_DELAY = 300
@@ -165,6 +166,8 @@ export const TIMELINE_INJECTION_WEIGHT_MULTIPLIER = 100
export const TIMELINE_INJECTION_WEIGHT_SUBTRACTOR = 0.005
export const TIMELINE_INJECTION_WEIGHT_MIN = 0.01
export const GAB_DECK_OPTIONS = ['home', 'user.id', 'notifications', 'list.id', 'likes', 'bookmarks', 'pro', 'compose', 'group.id']
export const TRENDS_RSS_SOURCES = [
{'id':'5daf64b18e955e2433b0f5ce','title':'Breitbart'},
{'id':'5daf66772fea4d3ba000883b','title':'Gateway Pundit'},

View File

@@ -0,0 +1,75 @@
import React from 'react'
import PropTypes from 'prop-types'
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { length } from 'stringz'
import { isMobile } from '../../../utils/is_mobile'
import { countableText } from '../../ui/util/counter'
import {
CX,
MAX_POST_CHARACTER_COUNT,
ALLOWED_AROUND_SHORT_CODE,
BREAKPOINT_EXTRA_SMALL,
BREAKPOINT_EXTRA_EXTRA_SMALL,
BREAKPOINT_MEDIUM,
} from '../../../constants'
import AutosuggestTextbox from '../../../components/autosuggest_textbox'
import Responsive from '../../ui/util/responsive_component'
import ResponsiveClassesComponent from '../../ui/util/responsive_classes_component'
import Avatar from '../../../components/avatar'
import Button from '../../../components/button'
import EmojiPickerButton from './emoji_picker_button'
import PollButton from './poll_button'
import PollForm from './poll_form'
import SchedulePostButton from './schedule_post_button'
import SpoilerButton from './spoiler_button'
import ExpiresPostButton from './expires_post_button'
import RichTextEditorButton from './rich_text_editor_button'
import StatusContainer from '../../../containers/status_container'
import StatusVisibilityButton from './status_visibility_button'
import UploadButton from './media_upload_button'
import UploadForm from './upload_form'
import Input from '../../../components/input'
import Text from '../../../components/text'
import Icon from '../../../components/icon'
import ComposeExtraButtonList from './compose_extra_button_list'
class ComposeDestinationHeader extends ImmutablePureComponent {
handleOnClick = () => {
}
render() {
const { account } = this.props
const title = 'Post to timeline'
return (
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.bgPrimary, _s.borderBottom1PX, _s.borderTop1PX, _s.borderColorSecondary, _s.mb5, _s.mt5, _s.px15, _s.w100PC, _s.h40PX].join(' ')}>
<Avatar account={account} size={28} />
<div className={[_s.ml15].join(' ')}>
<Button
isNarrow
radiusSmall
backgroundColor='tertiary'
color='primary'
onClick={this.handleOnClick}
>
<Text color='inherit' size='small' className={_s.jcCenter}>
{title}
<Icon id='caret-down' size='8px' className={_s.ml5} />
</Text>
</Button>
</div>
</div>
)
}
}
ComposeDestinationHeader.propTypes = {
account: ImmutablePropTypes.map,
}
export default ComposeDestinationHeader

View File

@@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { CX } from '../../../constants'
import Button from '../../../components/button'
import Text from '../../../components/text'
class ComposeExtraButton extends React.PureComponent {
@@ -12,11 +13,19 @@ class ComposeExtraButton extends React.PureComponent {
onClick,
icon,
children,
small,
active,
buttonRef
buttonRef,
isLast,
small,
} = this.props
const containerClasses = CX({
d: 1,
mr5: 1,
jcCenter: 1,
h40PX: 1,
})
const btnClasses = CX({
d: 1,
circle: 1,
@@ -26,45 +35,46 @@ class ComposeExtraButton extends React.PureComponent {
textAlignCenter: 1,
outlineNone: 1,
bgTransparent: 1,
flexRow: 1,
bgSubtle_onHover: !active,
bgBrandLight: active,
py10: !small,
px10: !small,
py5: small,
px5: small,
mr2: !children,
py10: 1,
px10: 1,
})
const iconClasses = CX({
cSecondary: !active,
cWhite: active,
mr10: 1,
py2: small,
ml10: small,
})
const iconSize = !!small ? '14px' : '16px'
const button = (
<Button
noClasses
className={btnClasses}
title={title}
isDisabled={disabled}
onClick={onClick}
backgroundColor='none'
iconClassName={iconClasses}
icon={icon}
iconSize={iconSize}
buttonRef={!children ? buttonRef : undefined}
/>
)
if (!children) {
return button
}
const iconSize = !small ? '18px' : '16px'
const textColor = !active ? 'primary' : 'white'
return (
<div className={[_s.d, _s.mr2].join(' ')} ref={buttonRef}>
{button}
{children}
<div className={containerClasses} ref={buttonRef}>
<Button
noClasses
className={btnClasses}
title={title}
isDisabled={disabled}
onClick={onClick}
backgroundColor='none'
iconClassName={iconClasses}
icon={icon}
iconSize={iconSize}
buttonRef={!children ? buttonRef : undefined}
>
{ children }
{
!small &&
<Text color={textColor} weight='medium' className={[_s.pr5].join(' ')}>
{title}
</Text>
}
</Button>
</div>
)
}
@@ -76,9 +86,9 @@ ComposeExtraButton.propTypes = {
disabled: PropTypes.bool,
onClick: PropTypes.func,
icon: PropTypes.string,
small: PropTypes.bool,
active: PropTypes.bool,
buttonRef: PropTypes.func,
small: PropTypes.bool,
}
export default ComposeExtraButton

View File

@@ -0,0 +1,88 @@
import React from 'react'
import PropTypes from 'prop-types'
import {
CX,
BREAKPOINT_EXTRA_SMALL,
} from '../../../constants'
import Responsive from '../../ui/util/responsive_component'
import ResponsiveClassesComponent from '../../ui/util/responsive_classes_component'
import EmojiPickerButton from './emoji_picker_button'
import PollButton from './poll_button'
import SchedulePostButton from './schedule_post_button'
import SpoilerButton from './spoiler_button'
import ExpiresPostButton from './expires_post_button'
import RichTextEditorButton from './rich_text_editor_button'
import StatusVisibilityButton from './status_visibility_button'
import UploadButton from './media_upload_button'
import { getWindowDimension } from '../../../utils/is_mobile'
const initialState = getWindowDimension()
class ComposeExtraButtonList extends React.PureComponent {
state = {
height: initialState.height,
}
componentDidMount() {
this.handleResize()
window.addEventListener('keyup', this.handleKeyUp, false)
window.addEventListener('resize', this.handleResize, false)
}
handleResize = () => {
const { height } = getWindowDimension()
this.setState({ height })
}
componentWillUnmount() {
window.removeEventListener('keyup', this.handleKeyUp)
window.removeEventListener('resize', this.handleResize, false)
}
render() {
const { isMatch, edit, hidePro } = this.props
const { height } = this.state
const small = height <= 660
const containerClasses = CX({
d: 1,
w100PC: 1,
bgPrimary: 1,
px15: 1,
py10: 1,
mtAuto: 1,
boxShadowBlockY: 1,
topLeftRadiusSmall: 1,
borderColorSecondary: 1,
topRightRadiusSmall: 1,
flexRow: small,
overflowXScroll: small,
noScrollbar: small,
})
return (
<div className={containerClasses}>
<UploadButton small={small} />
<EmojiPickerButton isMatch={isMatch} small={small} />
{ !edit && <PollButton small={small} /> }
<StatusVisibilityButton small={small} />
<SpoilerButton small={small} />
{ !hidePro && !edit && <SchedulePostButton small={small} /> }
{ !hidePro && !edit && <ExpiresPostButton small={small} /> }
{ !hidePro && <RichTextEditorButton small={small} /> }
</div>
)
}
}
ComposeExtraButtonList.propTypes = {
hidePro: PropTypes.bool,
edit: PropTypes.bool,
isMatch: PropTypes.bool,
}
export default ComposeExtraButtonList

View File

@@ -11,13 +11,14 @@ import {
MAX_POST_CHARACTER_COUNT,
ALLOWED_AROUND_SHORT_CODE,
BREAKPOINT_EXTRA_SMALL,
BREAKPOINT_EXTRA_EXTRA_SMALL,
BREAKPOINT_MEDIUM,
} from '../../../constants'
import AutosuggestTextbox from '../../../components/autosuggest_textbox'
import Responsive from '../../ui/util/responsive_component'
import ResponsiveClassesComponent from '../../ui/util/responsive_classes_component'
import Avatar from '../../../components/avatar'
import Button from '../../../components/button'
import CharacterCounter from '../../../components/character_counter'
import EmojiPickerButton from './emoji_picker_button'
import PollButton from './poll_button'
import PollForm from './poll_form'
@@ -30,6 +31,10 @@ import StatusVisibilityButton from './status_visibility_button'
import UploadButton from './media_upload_button'
import UploadForm from './upload_form'
import Input from '../../../components/input'
import Text from '../../../components/text'
import Icon from '../../../components/icon'
import ComposeExtraButtonList from './compose_extra_button_list'
import ComposeDestinationHeader from './compose_destination_header'
const messages = defineMessages({
placeholder: { id: 'compose_form.placeholder', defaultMessage: "What's on your mind?" },
@@ -193,11 +198,12 @@ class ComposeForm extends ImmutablePureComponent {
isSubmitting,
isPro,
hidePro,
isStandalone,
} = this.props
const disabled = isSubmitting
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
const disabledButton = disabled || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text) !== 0 && length(text.trim()) === 0 && !anyMedia);
const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia)
const shouldAutoFocus = autoFocus && !showSearch && !isMobile(window.innerWidth)
const parentContainerClasses = CX({
@@ -238,220 +244,213 @@ class ComposeForm extends ImmutablePureComponent {
displayNone: length(this.props.text) === 0,
})
return (
<div className={[_s.d, _s.w100PC].join(' ')}>
{
shouldCondense &&
<div className={parentContainerClasses}>
<div className={[_s.d, _s.w100PC].join(' ')}>
if (shouldCondense) {
return (
<div className={[_s.d, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.mr10].join(' ')}>
<Avatar account={account} size={28} noHover />
</div>
<div className={[_s.d, _s.flexRow, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.mr10].join(' ')}>
<Avatar account={account} size={28} noHover />
</div>
<div
className={[_s.d, _s.flexWrap, _s.overflowHidden, _s.flex1, _s.minH28PX, _s.py2, _s.aiEnd, _s.flexRow, _s.radiusSmall, _s.bgSubtle, _s.px5].join(' ')}
ref={this.setForm}
onClick={this.handleClick}
>
<div
className={childContainerClasses}
ref={this.setForm}
onClick={this.handleClick}
>
<AutosuggestTextbox
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
placeholder={intl.formatMessage(messages.commentPlaceholder)}
disabled={disabled}
value={this.props.text}
onChange={this.handleChange}
suggestions={this.props.suggestions}
onKeyDown={this.handleKeyDown}
onFocus={this.handleComposeFocus}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={shouldAutoFocus}
small={shouldCondense}
isPro={isPro}
isEdit={!!edit}
id='comment-composer'
/>
<AutosuggestTextbox
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
placeholder={intl.formatMessage(messages.commentPlaceholder)}
disabled={disabled}
value={this.props.text}
onChange={this.handleChange}
suggestions={this.props.suggestions}
onKeyDown={this.handleKeyDown}
onFocus={this.handleComposeFocus}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={shouldAutoFocus}
small={shouldCondense}
isPro={isPro}
isEdit={!!edit}
id='comment-composer'
/>
<div className={actionsContainerClasses}>
<div className={[_s.d, _s.flexRow, _s.mrAuto].join(' ')}>
{ /* <EmojiPickerButton small={shouldCondense} isMatch={isMatch} /> */}
{ /* <UploadButton small={shouldCondense} /> */}
<div className={commentPublishBtnClasses}>
<Button
isNarrow
onClick={this.handleSubmit}
isDisabled={disabledButton}
className={_s.px10}
>
{intl.formatMessage(scheduledAt ? messages.schedulePost : messages.publish)}
</Button>
</div>
</div>
<div className={[_s.d, _s.flexRow, _s.aiStart, _s.mlAuto].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.mrAuto].join(' ')}>
<div className={commentPublishBtnClasses}>
<Button
isNarrow
onClick={this.handleSubmit}
isDisabled={disabledButton}
className={_s.px10}
>
{intl.formatMessage(scheduledAt ? messages.schedulePost : messages.publish)}
</Button>
</div>
</div>
</div>
</div>
</div>
{
(isUploading || anyMedia) &&
<div className={[_s.d, _s.w100PC, _s.pl35, _s.mt5].join(' ')}>
<UploadForm replyToId={replyToId} isModalOpen={isModalOpen} />
</div>
}
</div>
)
}
if (isModalOpen) {
//
}
if (isStandalone || isModalOpen) {
return (
<div className={[_s.d, _s.w100PC, _s.flexGrow1, _s.bgTertiary].join(' ')}>
<div className={[_s.d, _s.calcMaxH370PX, _s.overflowYScroll, _s.boxShadowBlock, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<ComposeDestinationHeader account={account} />
<div
className={[_s.d, _s.bgPrimary, _s.boxShadowBlock, _s.w100PC, _s.minH200PX, _s.pb10, _s.borderBottom1PX, _s.borderTop1PX, _s.borderColorSecondary].join(' ')}
ref={this.setForm}
onClick={this.handleClick}
>
{
!!reduxReplyToId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer
contextType='compose'
id={reduxReplyToId}
isChild
/>
</div>
}
{
!!spoiler &&
<div className={[_s.d, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<Input
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
value={this.props.spoilerText}
onChange={this.handleChangeSpoilerText}
disabled={!this.props.spoiler}
prependIcon='warning'
maxLength={256}
id='cw-spoiler-input'
/>
</div>
}
<AutosuggestTextbox
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
placeholder={intl.formatMessage((shouldCondense || !!reduxReplyToId) && isMatch ? messages.commentPlaceholder : messages.placeholder)}
disabled={disabled}
value={this.props.text}
valueMarkdown={this.props.markdown}
onChange={this.handleChange}
suggestions={this.props.suggestions}
onKeyDown={this.handleKeyDown}
onFocus={this.handleComposeFocus}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={shouldAutoFocus}
small={shouldCondense}
isPro={isPro}
isEdit={!!edit}
id='main-composer'
/>
{
(isUploading || anyMedia) &&
<div className={[_s.d, _s.w100PC, _s.pl35, _s.mt5].join(' ')}>
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<UploadForm
replyToId={replyToId}
isModalOpen={isModalOpen}
/>
</div>
}
</div>
</div>
}
{
!shouldCondense &&
<div className={parentContainerClasses}>
<div className={[_s.d, _s.flexRow, _s.w100PC].join(' ')}>
<div
className={childContainerClasses}
ref={this.setForm}
onClick={this.handleClick}
>
{
!!reduxReplyToId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer
contextType='compose'
id={reduxReplyToId}
isChild
/>
</div>
}
{
!!spoiler &&
<div className={[_s.d, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<Input
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
value={this.props.spoilerText}
onChange={this.handleChangeSpoilerText}
disabled={!this.props.spoiler}
prependIcon='warning'
maxLength={256}
id='cw-spoiler-input'
/>
</div>
}
<AutosuggestTextbox
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
placeholder={intl.formatMessage((shouldCondense || !!reduxReplyToId) && isMatch ? messages.commentPlaceholder : messages.placeholder)}
disabled={disabled}
value={this.props.text}
valueMarkdown={this.props.markdown}
onChange={this.handleChange}
suggestions={this.props.suggestions}
onKeyDown={this.handleKeyDown}
onFocus={this.handleComposeFocus}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={shouldAutoFocus}
small={shouldCondense}
isPro={isPro}
isEdit={!!edit}
id='main-composer'
/>
{
(isUploading || anyMedia) &&
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<UploadForm
replyToId={replyToId}
isModalOpen={isModalOpen}
/>
</div>
}
{
!edit && hasPoll &&
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<PollForm replyToId={replyToId} />
</div>
}
{
!!quoteOfId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer
contextType='compose'
id={quoteOfId}
isChild
/>
</div>
}
<div className={actionsContainerClasses}>
<div className={[_s.d, _s.flexRow, _s.mrAuto].join(' ')}>
<UploadButton small={shouldCondense} />
<EmojiPickerButton small={shouldCondense} isMatch={isMatch} />
{ /* <GifSelectorButton small={shouldCondense} /> */}
{
!edit &&
<PollButton />
}
<StatusVisibilityButton />
<SpoilerButton />
{
!hidePro && !edit &&
<SchedulePostButton />
}
{
!hidePro && !edit &&
<ExpiresPostButton />
}
{
!hidePro &&
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
<RichTextEditorButton />
</Responsive>
}
</div>
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
<CharacterCounter max={MAX_POST_CHARACTER_COUNT} text={text} />
</Responsive>
<Button
isOutline
isDisabled={disabledButton}
backgroundColor='none'
color='brand'
className={[_s.fs15PX, _s.px15].join(' ')}
onClick={this.handleSubmit}
>
{intl.formatMessage(scheduledAt ? messages.schedulePost : edit ? messages.publishEdit : messages.publish)}
</Button>
{
!edit && hasPoll &&
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<PollForm replyToId={replyToId} />
</div>
}
{
!!quoteOfId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer
contextType='compose'
id={quoteOfId}
isChild
/>
</div>
}
</div>
</div>
</div>
}
<div className={[_s.d, _s.w100PC, _s.pt10, _s.px10].join(' ')}>
<Button
isBlock
isDisabled={disabledButton}
backgroundColor={disabledButton ? 'secondary' : 'brand'}
color={disabledButton ? 'tertiary' : 'white'}
className={[_s.fs15PX, _s.px15, _s.flexGrow1, _s.mlAuto].join(' ')}
onClick={this.handleSubmit}
>
<Text color='inherit' weight='medium' align='center'>
{intl.formatMessage(scheduledAt ? messages.schedulePost : edit ? messages.publishEdit : messages.publish)}
</Text>
</Button>
</div>
<ComposeExtraButtonList isMatch={isMatch} hidePro={hidePro} edit={edit} />
</div>
)
}
return (
<div
className={[_s.d, _s.w100PC, _s.pb10, _s.flexWrap, _s.overflowHidden, _s.flex1].join(' ')}
ref={this.setForm}
onClick={this.handleClick}
>
<Text className={[_s.d, _s.px15, _s.pt15, _s.pb10].join(' ')} size='medium' color='tertiary'>
{intl.formatMessage((shouldCondense || !!reduxReplyToId) && isMatch ? messages.commentPlaceholder : messages.placeholder)}
</Text>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.mt5, _s.px10, _s.flexWrap].join(' ')}>
<UploadButton />
<EmojiPickerButton isMatch={isMatch} />
<Responsive min={BREAKPOINT_EXTRA_EXTRA_SMALL}>
<PollButton />
</Responsive>
<Button
isOutline
isDisabled={disabledButton}
backgroundColor='none'
color='brand'
className={[_s.fs15PX, _s.px15, _s.flexGrow1, _s.maxW212PX, _s.mlAuto].join(' ')}
onClick={this.handleSubmit}
>
<Text color='inherit' weight='medium' align='center'>
{intl.formatMessage(scheduledAt ? messages.schedulePost : edit ? messages.publishEdit : messages.publish)}
</Text>
</Button>
</div>
</div>
)
}
@@ -497,6 +496,7 @@ ComposeForm.propTypes = {
isPro: PropTypes.bool,
hidePro: PropTypes.bool,
autoJoinGroup: PropTypes.bool,
isStandalone: PropTypes.bool,
}
ComposeForm.defaultProps = {

View File

@@ -29,9 +29,9 @@ class EmojiPickerButton extends React.PureComponent {
title={intl.formatMessage(messages.emoji)}
onClick={this.handleClick}
icon='happy'
small={small}
active={active && isMatch}
buttonRef={this.setButton}
small={small}
/>
)
}
@@ -47,21 +47,19 @@ const mapStateToProps = (state) => ({
})
const mapDispatchToProps = (dispatch) => ({
onClick(targetRef) {
dispatch(openPopover('EMOJI_PICKER', {
targetRef,
}))
},
})
EmojiPickerButton.propTypes = {
intl: PropTypes.object.isRequired,
onClick: PropTypes.func.isRequired,
active: PropTypes.bool,
small: PropTypes.bool,
isMatch: PropTypes.bool,
small: PropTypes.bool,
}
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(EmojiPickerButton))

View File

@@ -24,7 +24,14 @@ class UploadButton extends ImmutablePureComponent {
}
render() {
const { intl, resetFileKey, unavailable, disabled, acceptContentTypes, small } = this.props
const {
intl,
resetFileKey,
unavailable,
disabled,
acceptContentTypes,
small,
} = this.props
if (unavailable) return null
@@ -33,8 +40,8 @@ class UploadButton extends ImmutablePureComponent {
title={intl.formatMessage(messages.title)}
disabled={disabled}
onClick={this.handleClick}
icon='media'
small={small}
icon='media'
>
<label>
<span className={_s.displayNone}>{intl.formatMessage(messages.upload)}</span>

View File

@@ -12,13 +12,19 @@ class PollButton extends React.PureComponent {
}
render() {
const { intl, active, unavailable, disabled, small } = this.props
const {
intl,
active,
unavailable,
disabled,
small,
} = this.props
if (unavailable) return null
return (
<ComposeExtraButton
title={intl.formatMessage(active ? messages.remove_poll : messages.title)}
title={intl.formatMessage(active ? messages.remove_poll : messages.add_poll)}
disabled={disabled}
onClick={this.handleClick}
icon='poll'
@@ -42,7 +48,6 @@ const mapStateToProps = (state) => ({
})
const mapDispatchToProps = (dispatch) => ({
onClick() {
dispatch((_, getState) => {
if (getState().getIn(['compose', 'poll'])) {
@@ -52,7 +57,6 @@ const mapDispatchToProps = (dispatch) => ({
}
})
},
})
PollButton.propTypes = {

View File

@@ -46,11 +46,9 @@ const mapStateToProps = (state) => ({
})
const mapDispatchToProps = (dispatch) => ({
onChangeRichTextEditorControlsVisibility() {
dispatch(changeRichTextEditorControlsVisibility())
},
onOpenProUpgradeModal() {
dispatch(openModal('PRO_UPGRADE'))
},

View File

@@ -39,11 +39,9 @@ const mapStateToProps = (state) => ({
})
const mapDispatchToProps = (dispatch) => ({
onClick () {
dispatch(changeComposeSpoilerness())
},
})
SpoilerButton.propTypes = {

View File

@@ -7,25 +7,13 @@ import ComposeFormContainer from './containers/compose_form_container'
class Compose extends React.PureComponent {
componentWillUnmount() {
this.props.onClearCompose()
this.props.dispatch(clearCompose())
}
render () {
return (
<div className={[_s.d, _s.bgPrimary, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<ComposeFormContainer isStandalone />
</div>
)
return <ComposeFormContainer isStandalone />
}
}
const mapDispatchToProps = (dispatch) => ({
onClearCompose:() => dispatch(clearCompose())
})
Compose.propTypes = {
onClearCompose: PropTypes.func.isRequired,
}
export default connect(null, mapDispatchToProps)(Compose)
export default connect()(Compose)

View File

@@ -3,18 +3,30 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import {
sortableContainer,
sortableElement,
} from 'react-sortable-hoc'
import { me, meUsername} from '../initial_state'
import {
deckConnect,
deckDisconnect,
updateDeckColumnAtIndex,
} from '../actions/deck'
import {
saveSettings,
} from '../actions/settings'
import WrappedBundle from './ui/util/wrapped_bundle'
import DeckColumn from '../components/deck_column'
import {
AccountTimeline,
Compose,
LikedStatuses,
HomeTimeline,
Notifications,
HashtagTimeline,
BookmarkedStatuses,
ProTimeline,
} from './ui/util/async_components'
class Deck extends React.PureComponent {
@@ -39,36 +51,135 @@ class Deck extends React.PureComponent {
this.props.dispatch(deckDisconnect())
}
onSortEnd = ({oldIndex, newIndex}) => {
this.props.dispatch(updateDeckColumnAtIndex(oldIndex, newIndex))
}
onShouldCancelStart = (event) => {
if (event.target) {
if (!event.target.hasAttribute('data-sort-header')) {
return true
}
}
return false
}
getDeckColumn = (deckColumn, index) => {
if (!deckColumn) return null
let Component = null
let componentParams = {}
let title, icon = ''
switch (deckColumn) {
case 'notifications':
title = 'Notifications'
icon = 'notifications'
Component = Notifications
break
case 'home':
title = 'Home'
icon = 'home'
Component = HomeTimeline
break
case 'compose':
title = 'Compose'
icon = 'pencil'
Component = Compose
break
case 'likes':
title = 'Likes'
icon = 'like'
Component = LikedStatuses
componentParams = { params: { username: meUsername }}
break
case 'bookmarks':
title = 'Bookmarks'
icon = 'bookmark'
Component = BookmarkedStatuses
componentParams = { params: { username: meUsername }}
break
case 'pro':
title = 'Pro Timeline'
icon = 'pro'
Component = ProTimeline
break
default:
break
}
if (!Component) {
if (deckColumn.indexOf('user.') > -1) {
} else if (deckColumn.indexOf('list.') > -1) {
} else if (deckColumn.indexOf('group.') > -1) {
}
}
if (!Component) return null
return (
<SortableItem
key={`deck-column-${index}`}
index={index}
sortIndex={index}
>
<DeckColumn title={title} icon={icon} index={index}>
<WrappedBundle component={Component} componentParams={componentParams} />
</DeckColumn>
</SortableItem>
)
}
render () {
const { gabDeckOrder } = this.props
console.log("gabDeckOrder:", gabDeckOrder)
const isEmpty = gabDeckOrder.size === 0
// : todo : max: 12
return (
<div className={[_s.d, _s.flexRow].join(' ')}>
<DeckColumn title='Compose' icon='pencil'>
<WrappedBundle component={Compose} />
</DeckColumn>
<DeckColumn />
{/*<DeckColumn title='Home' icon='home'>
<WrappedBundle component={HomeTimeline} />
</DeckColumn>
<DeckColumn title='Notifications' icon='notifications'>
<WrappedBundle component={Notifications} />
</DeckColumn>
<DeckColumn title='Cashtag' icon='list' subtitle='$BTC'>
<WrappedBundle component={HashtagTimeline} componentParams={{ params: { id: 'btc' } }} />
</DeckColumn>
<DeckColumn title='Jonny' icon='group' subtitle='@admin'>
<WrappedBundle component={AccountTimeline} componentParams={{ account }} />
</DeckColumn>
</DeckColumn>*/}
</div>
<SortableContainer
axis='x'
lockAxis='x'
onSortEnd={this.onSortEnd}
shouldCancelStart={this.onShouldCancelStart}
>
{
isEmpty &&
<React.Fragment>
<DeckColumn title='Compose' icon='pencil'>
<WrappedBundle component={Compose} />
</DeckColumn>
<DeckColumn />
</React.Fragment>
}
{
!isEmpty &&
gabDeckOrder.map((deckOption, i) => this.getDeckColumn(deckOption, i))
}
</SortableContainer>
)
}
}
const SortableItem = sortableElement(({children}) => (
<div>
{children}
</div>
))
const SortableContainer = sortableContainer(({children}) => (
<div className={[_s.d, _s.flexRow, _s.listStyleNone].join(' ')}>
{children}
</div>
))
const mapStateToProps = (state) => ({
gabDeckOrder: state.getIn(['settings', 'gabDeckOrder']),
})

View File

@@ -336,6 +336,7 @@ class Introduction extends ImmutablePureComponent {
<Text color='white' className={_s.px5}>{nextTitle}</Text>
</Responsive>
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
<Text color='white' className={[_s.px5, _s.mr10].join(' ')}>Done</Text>
<Icon id='check' size='14px' className={_s.cWhite} />
</Responsive>
</React.Fragment>

View File

@@ -0,0 +1,38 @@
import React from 'react'
import PropTypes from 'prop-types'
import { me } from '../initial_state'
import DefaultSidebar from '../components/sidebar/default_sidebar'
import ComposeNavigationBar from '../components/navigation_bar/compose_navigation_bar_xs'
import Responsive from '../features/ui/util/responsive_component'
import WrappedBundle from '../features/ui/util/wrapped_bundle'
import {
SidebarXS,
} from '../features/ui/util/async_components'
class ComposeLayout extends React.PureComponent {
render() {
const { children, isXS } = this.props
if (!isXS) return null
return (
<div className={[_s.d, _s.w100PC, _s.minH100VH, _s.bgTertiary].join(' ')}>
<WrappedBundle component={SidebarXS} />
<ComposeNavigationBar />
<main role='main' className={[_s.d, _s.w100PC, _s.flexGrow1].join(' ')}>
{ children }
</main>
</div>
)
}
}
ComposeLayout.propTypes = {
children: PropTypes.node,
}
export default ComposeLayout

View File

@@ -1,33 +1,47 @@
import React from 'react'
import PropTypes from 'prop-types'
import PageTitle from '../features/ui/util/page_title'
import DefaultLayout from '../layouts/default_layout'
import ComposeLayout from '../layouts/compose_layout'
import { BREAKPOINT_EXTRA_SMALL } from '../constants'
import { getWindowDimension } from '../utils/is_mobile'
const initialState = getWindowDimension()
class ComposePage extends React.PureComponent {
state = {
width: initialState.width,
}
componentDidMount() {
this.handleResize()
window.addEventListener('keyup', this.handleKeyUp, false)
window.addEventListener('resize', this.handleResize, false)
}
handleResize = () => {
const { width } = getWindowDimension()
this.setState({ width })
}
componentWillUnmount() {
window.removeEventListener('keyup', this.handleKeyUp)
window.removeEventListener('resize', this.handleResize, false)
}
render() {
const {
children,
page,
title,
} = this.props
const { children } = this.props
const { width } = this.state
const isXS = width <= BREAKPOINT_EXTRA_SMALL
if (!isXS) throw 'This page does not exist'
return (
<DefaultLayout
noComposeButton
showBackBtn
title={title}
page={page}
actions={[
{
title: 'Post',
onClick: this.onOpenCommunityPageSettingsModal,
},
]}
>
<PageTitle path={title} />
<ComposeLayout title='Compose' isXS={isXS}>
<PageTitle path='Compose' />
{children}
</DefaultLayout>
</ComposeLayout>
)
}

View File

@@ -3,6 +3,11 @@ import { STORE_HYDRATE } from '../actions/store'
import { EMOJI_USE } from '../actions/emojis'
import { LIST_DELETE_SUCCESS, LIST_FETCH_FAIL } from '../actions/lists'
import { TIMELINE_INJECTION_HIDE } from '../actions/timeline_injections'
import {
DECK_SET_COLUMN_AT_INDEX,
DECK_DELETE_COLUMN_AT_INDEX,
DECK_CHANGE_COLUMN_AT_INDEX,
} from '../actions/deck'
import {
COMMENT_SORTING_TYPE_OLDEST,
TIMELINE_INJECTION_WEIGHT_DEFAULT,
@@ -14,7 +19,7 @@ import {
TIMELINE_INJECTION_SHOP,
TIMELINE_INJECTION_USER_SUGGESTIONS,
} from '../constants'
import { Map as ImmutableMap, fromJS } from 'immutable'
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
import uuid from '../utils/uuid'
const initialState = ImmutableMap({
@@ -23,6 +28,7 @@ const initialState = ImmutableMap({
skinTone: 1,
isCompact: false,
commentSorting: COMMENT_SORTING_TYPE_OLDEST,
gabDeckOrder: ImmutableList([]),
// every dismiss reduces by half or set to zero for pwa, shop, pro
injections: ImmutableMap({
@@ -55,37 +61,50 @@ const initialState = ImmutableMap({
onlyMedia: false,
}),
}),
});
})
const defaultColumns = fromJS([
{ id: 'COMPOSE', uuid: uuid(), params: {} },
{ id: 'HOME', uuid: uuid(), params: {} },
{ id: 'NOTIFICATIONS', uuid: uuid(), params: {} },
]);
])
const hydrate = (state, settings) => state.mergeDeep(settings).update('columns', (val = defaultColumns) => val);
const hydrate = (state, settings) => state.mergeDeep(settings).update('columns', (val = defaultColumns) => val)
const updateFrequentEmojis = (state, emoji) => state.update('frequentlyUsedEmojis', ImmutableMap(), map => map.update(emoji.id, 0, count => count + 1)).set('saved', false);
const updateFrequentEmojis = (state, emoji) => state.update('frequentlyUsedEmojis', ImmutableMap(), map => map.update(emoji.id, 0, count => count + 1)).set('saved', false)
const filterDeadListColumns = (state, listId) => state.update('columns', columns => columns.filterNot(column => column.get('id') === 'LIST' && column.get('params').get('id') === listId));
const filterDeadListColumns = (state, listId) => state.update('columns', columns => columns.filterNot(column => column.get('id') === 'LIST' && column.get('params').get('id') === listId))
export default function settings(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
return hydrate(state, action.state.get('settings'));
return hydrate(state, action.state.get('settings'))
case SETTING_CHANGE:
return state
.setIn(action.path, action.value)
.set('saved', false);
.set('saved', false)
case EMOJI_USE:
return updateFrequentEmojis(state, action.emoji);
return updateFrequentEmojis(state, action.emoji)
case SETTING_SAVE:
return state.set('saved', true);
return state.set('saved', true)
case LIST_FETCH_FAIL:
return action.error.response.status === 404 ? filterDeadListColumns(state, action.id) : state;
return action.error.response.status === 404 ? filterDeadListColumns(state, action.id) : state
case LIST_DELETE_SUCCESS:
return filterDeadListColumns(state, action.id);
return filterDeadListColumns(state, action.id)
case DECK_SET_COLUMN_AT_INDEX:
// : todo : max: 12
const sizeOfDeck = state.get('gabDeckOrder', ImmutableList()).size
const newIndex = Math.max(action.index || 0, sizeOfDeck)
return state.setIn(['gabDeckOrder', newIndex + 1], action.column).set('saved', false)
case DECK_DELETE_COLUMN_AT_INDEX:
return state.deleteIn(['gabDeckOrder', action.index])
case DECK_CHANGE_COLUMN_AT_INDEX:
return state.update('gabDeckOrder', idsList => idsList.withMutations((list) => {
let soruce = list.get(action.oldIndex)
let destination = list.get(action.newIndex)
return list.set(action.newIndex, soruce).set(action.oldIndex, destination)
}))
default:
return state;
return state
}
};
}

View File

@@ -395,6 +395,7 @@ pre {
.cursorText { cursor: text; }
.cursorPointer { cursor: pointer; }
.cursorEWResize { cursor: ew-resize; }
.cursorNotAllowed { cursor: not-allowed; }
.backgroundCandy {
@@ -536,6 +537,11 @@ pre {
.calcH53PX { height: calc(100vh - 53px); }
.calcH80VH106PX { height: calc(80vh - 106px); }
.calcMaxH370PX { max-height: calc(100vh - 370px); }
@media (min-height: 0px) and (max-height:660px) {
.calcMaxH370PX { max-height: calc(100vh - 140px); }
}
.minH100VH { min-height: 100vh; }
.minH50VH { min-height: 50vh; }
.minH200PX { min-height: 200px; }
@@ -908,6 +914,7 @@ pre {
.boxShadow1 { box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, .25); }
.boxShadowPopover { box-shadow: 0 0 15px -5px rgba(0,0,0,0.15); }
.boxShadowBlock { box-shadow: 0 1px 2px rgba(0,0,0,0.2); }
.boxShadowBlockY { box-shadow: 0 -1px 2px rgba(0,0,0,0.2); }
.boxShadowDot { box-shadow: inset 0 0 0 3px #fff, inset 0 0 0 6px #000; }
.boxShadowToast { box-shadow: 0px 0px 10px -2px rgba(0, 0, 0, .2), 0px 0px 2px -1px rgba(0, 0, 0, 0.4); }