Progress and testing status w comments

This commit is contained in:
mgabdev 2020-04-16 02:00:43 -04:00
parent 0c6837213d
commit 35852e7fee
22 changed files with 605 additions and 534 deletions

@ -1,5 +1,6 @@
import { Fragment } from 'react'
import ImmutablePropTypes from 'react-immutable-proptypes'
import isObject from 'lodash.isobject'
import classNames from 'classnames/bind'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { isRtl } from '../utils/rtl'
@ -51,8 +52,19 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
tokenStart: 0,
}
onChange = (e) => {
const [tokenStart, token] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart, this.props.searchTokens);
onChange = (e, value, selectionStart) => {
if (!isObject(e)) {
e = {
target: {
value,
selectionStart,
},
}
console.log("new e:", e)
}
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart, this.props.searchTokens);
// console.log('onChange', e.target.value, e.target, this.textbox, tokenStart, token)
@ -235,7 +247,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
if (textarea) {
return (
<Fragment>
<div className={[_s.default, _s.flexGrow1].join(' ')}>
<div className={[_s.default, _s.flexGrow1, _s.maxWidth100PC].join(' ')}>
<div className={[_s.default].join(' ')}>
<Composer
@ -253,59 +265,10 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
small={small}
/>
{ /* <Textarea
className={_s.default}
inputRef={this.setTextbox}
disabled={disabled}
placeholder={placeholder}
autoFocus={autoFocus}
value={value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
aria-autocomplete='list'
/> */ }
{ /*
<Textarea
className={_s.default}
inputRef={this.setTextbox}
disabled={disabled}
value={value}
aria-autocomplete='list'
/>
<ContentEditable
tabIndex='0'
aria-label='Gab text'
role='textbox'
aria-autocomplete='list'
style={{
userSelect: 'text',
'white-space': 'pre-wrap',
overflowWrap: 'break-word'
}}
className={textClasses}
disabled={disabled}
style={style}
html={value}
placeholder={placeholder}
autoFocus={autoFocus}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
/>
*/ }
</div>
{children}
</div>
{ /* : todo : */ }
{ /* : todo : put in popover */ }
<div className='autosuggest-textarea__suggestions-wrapper'>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
{suggestions.map(this.renderSuggestion)}

@ -5,15 +5,14 @@ export default class CharacterCounter extends PureComponent {
static propTypes = {
text: PropTypes.string.isRequired,
max: PropTypes.number.isRequired,
small: PropTypes.bool,
}
render () {
const { text, max, small } = this.props
const actualRadius = small ? '10' : '16'
const radius = small ? 8 : 12
const { text, max } = this.props
const actualRadius = '16'
const radius = 12
const circumference = 2 * Math.PI * radius
const diff = length(text) / max
const diff = Math.min(length(text), max) / max
const dashoffset = circumference * (1 - diff)
return (

@ -6,11 +6,9 @@ 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 RichTextEditorBar from './rich_text_editor_bar'
// import 'draft-js/dist/Draft.css'
import '!style-loader!css-loader!draft-js/dist/Draft.css'
const cx = classNames.bind(_s)
@ -45,7 +43,6 @@ const findWithRegex = (regex, contentBlock, callback) => {
}
const HighlightedSpan = (props) => {
console.log("HighlightedSpan:", props)
return (
<span
className={_s.colorBrand}
@ -56,69 +53,6 @@ const HighlightedSpan = (props) => {
)
}
const RTE_ITEMS = [
{
label: 'Bold',
style: 'BOLD',
type: 'style',
icon: 'bold',
},
{
label: 'Italic',
style: 'ITALIC',
type: 'style',
icon: 'italic',
},
{
label: 'Underline',
style: 'UNDERLINE',
type: 'style',
icon: 'underline',
},
{
label: 'Strikethrough',
style: 'STRIKETHROUGH',
type: 'style',
icon: 'strikethrough',
},
// {
// label: 'Monospace',
// style: 'CODE',
// type: 'style',
// icon: 'circle',
// },
{
label: 'H1',
style: 'header-one',
type: 'block',
icon: 'text-size',
},
{
label: 'Blockquote',
style: 'blockquote',
type: 'block',
icon: 'blockquote',
},
{
label: 'Code Block',
style: 'code-block',
type: 'block',
icon: 'code',
},
{
label: 'UL',
style: 'unordered-list-item',
type: 'block',
icon: 'ul-list',
},
{
label: 'OL',
style: 'ordered-list-item',
type: 'block',
icon: 'ol-list',
},
]
const compositeDecorator = new CompositeDecorator([
{
strategy: handleStrategy,
@ -137,23 +71,13 @@ const compositeDecorator = new CompositeDecorator([
const HANDLE_REGEX = /\@[\w]+/g;
const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;
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) => ({
})
export default
@connect(mapStateToProps, mapDispatchToProps)
@connect(null, mapDispatchToProps)
class Composer extends PureComponent {
static propTypes = {
@ -169,8 +93,6 @@ class Composer extends PureComponent {
onBlur: PropTypes.func,
onPaste: PropTypes.func,
small: PropTypes.bool,
isPro: PropTypes.bool.isRequired,
rteControlsVisible: PropTypes.bool.isRequired,
}
state = {
@ -180,11 +102,11 @@ class Composer extends PureComponent {
onChange = (editorState) => {
this.setState({ editorState })
const text = editorState.getCurrentContent().getPlainText('\u0001')
this.props.onChange(text)
}
const selectionState = editorState.getSelection()
const selectionStart = selectionState.getStartOffset()
onToggleInlineStyle = (style) => {
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, style))
this.props.onChange(null, text, selectionStart)
}
focus = () => {
@ -203,28 +125,15 @@ class Composer extends PureComponent {
return false
}
handleOnTogglePopoutEditor = () => {
//
}
onTab = (e) => {
const maxDepth = 4
this.onChange(RichUtils.onTab(e, this.state.editorState, maxDepth))
}
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)
)
}
}
handleOnTogglePopoutEditor = () => {
//
}
setRef = (n) => {
this.textbox = n
}
@ -243,14 +152,11 @@ class Composer extends PureComponent {
onBlur,
onPaste,
small,
isPro,
rteControlsVisible
} = this.props
const { editorState } = this.state
const editorContainerClasses = cx({
default: 1,
RTE: 1,
cursorText: 1,
text: 1,
fontSize16PX: !small,
@ -263,35 +169,12 @@ class Composer extends PureComponent {
})
return (
<div className={[_s.default].join(' ')}>
<div className={_s.default}>
{
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>
}
<RichTextEditorBar
editorState={editorState}
onChange={this.onChange}
/>
<div
onClick={this.focus}
@ -304,73 +187,13 @@ class Composer extends PureComponent {
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
onTab={this.onTab}
// placeholder={placeholder}
placeholder={placeholder}
ref={this.setRef}
readOnly={disabled}
/>
</div>
</div>
)
}
}
class StyleButton extends PureComponent {
static propTypes = {
onClick: PropTypes.func,
label: PropTypes.string,
style: PropTypes.string,
icon: PropTypes.string,
type: PropTypes.string,
}
handleOnClick
= (e) => {
e.preventDefault()
this.props.onClick(this.props.style, this.props.type)
}
render() {
const {
label,
style,
type,
icon,
editorState
} = this.props
const selection = editorState.getSelection()
const currentStyle = editorState.getCurrentInlineStyle()
const blockType = editorState.getCurrentContent().getBlockForKey(selection.getStartKey()).getType()
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,
// py5: small,
// px5: small,
})
return (
<Button
className={btnClasses}
backgroundColor='none'
color={color}
onClick={this.handleOnClick}
title={label}
icon={icon}
iconClassName={_s.inheritFill}
iconWidth='12px'
iconHeight='12px'
radiusSmall
/>
)
}
}
}

@ -4,7 +4,7 @@ import { me } from '../../initial_state'
import { isMobile } from '../../utils/is_mobile'
import PopoverLayout from './popover_layout'
// import 'react-datepicker/dist/react-datepicker.css'
import '!style-loader!css-loader!react-datepicker/dist/react-datepicker.css'
const mapStateToProps = (state) => ({
date: state.getIn(['compose', 'scheduled_at']),
@ -40,8 +40,9 @@ class DatePickerPopover extends PureComponent {
const withPortal = isMobile(window.innerWidth)
return (
<PopoverLayout>
<PopoverLayout width={331}>
<DatePicker
inline
target={this}
className='schedule-post-dropdown__datepicker'
minDate={new Date()}

@ -2,14 +2,15 @@ import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { Map as ImmutableMap } from 'immutable'
import classNames from 'classnames'
import { createSelector } from 'reselect'
import detectPassiveEvents from 'detect-passive-events'
import { changeSetting } from '../../actions/settings'
import { useEmoji } from '../../actions/emojis'
import { closePopover } from '../../actions/popover'
import { EmojiPicker as EmojiPickerAsync } from '../../features/ui/util/async_components'
import { buildCustomEmojis } from '../emoji/emoji'
import PopoverLayout from './popover_layout'
import ColumnIndicator from '../column_indicator'
import '!style-loader!css-loader!emoji-mart/css/emoji-mart.css'
@ -36,151 +37,6 @@ let EmojiPicker, Emoji // load asynchronously
const backgroundImageFn = () => `${assetHost}/emoji/sheet.png`
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false
const categoriesSort = [
'recent',
'custom',
'people',
'nature',
'foods',
'activity',
'places',
'objects',
'symbols',
'flags',
];
@injectIntl
class EmojiPickerMenu extends ImmutablePureComponent {
static propTypes = {
custom_emojis: ImmutablePropTypes.list,
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
loading: PropTypes.bool,
onClose: PropTypes.func.isRequired,
onPick: PropTypes.func.isRequired,
style: PropTypes.object,
arrowOffsetLeft: PropTypes.string,
arrowOffsetTop: PropTypes.string,
intl: PropTypes.object.isRequired,
skinTone: PropTypes.number.isRequired,
onSkinTone: PropTypes.func.isRequired,
};
static defaultProps = {
style: {},
loading: true,
frequentlyUsedEmojis: [],
};
state = {
modifierOpen: false,
};
handleDocumentClick = e => {
if (this.node && !this.node.contains(e.target)) {
this.props.onClose();
}
}
componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
}
componentWillUnmount () {
document.removeEventListener('click', this.handleDocumentClick, false);
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
}
setRef = c => {
this.node = c;
}
getI18n = () => {
const { intl } = this.props;
return {
search: intl.formatMessage(messages.emoji_search),
notfound: intl.formatMessage(messages.emoji_not_found),
categories: {
search: intl.formatMessage(messages.search_results),
recent: intl.formatMessage(messages.recent),
people: intl.formatMessage(messages.people),
nature: intl.formatMessage(messages.nature),
foods: intl.formatMessage(messages.food),
activity: intl.formatMessage(messages.activity),
places: intl.formatMessage(messages.travel),
objects: intl.formatMessage(messages.objects),
symbols: intl.formatMessage(messages.symbols),
flags: intl.formatMessage(messages.flags),
custom: intl.formatMessage(messages.custom),
},
};
}
handleClick = emoji => {
if (!emoji.native) {
emoji.native = emoji.colons;
}
this.props.onClose();
this.props.onPick(emoji);
}
handleModifierOpen = () => {
this.setState({ modifierOpen: true });
}
handleModifierClose = () => {
this.setState({ modifierOpen: false });
}
handleModifierChange = modifier => {
this.props.onSkinTone(modifier);
}
render () {
const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props;
if (loading) {
return <div style={{ width: 340 }} />;
}
const title = intl.formatMessage(messages.emoji);
const { modifierOpen } = this.state
return (
<div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
<EmojiPicker
backgroundImageFn={backgroundImageFn}
custom={buildCustomEmojis(custom_emojis)}
title={title}
i18n={this.getI18n()}
onClick={this.handleClick}
include={categoriesSort}
recent={frequentlyUsedEmojis}
skin={skinTone}
emojiSize={24}
set='twitter'
color='#30CE7D'
emoji=''
autoFocus
emojiTooltip
/>
{/*<ModifierPicker
active={modifierOpen}
modifier={skinTone}
onOpen={this.handleModifierOpen}
onClose={this.handleModifierClose}
onChange={this.handleModifierChange}
/>*/}
</div>
);
}
}
const perLine = 8
const lines = 2
@ -201,7 +57,20 @@ const DEFAULTS = [
'sunglasses',
'heart',
'ok_hand',
];
]
const categoriesSort = [
'recent',
'custom',
'people',
'nature',
'foods',
'activity',
'places',
'objects',
'symbols',
'flags',
]
const getFrequentlyUsedEmojis = createSelector([
state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()),
@ -219,7 +88,7 @@ const getFrequentlyUsedEmojis = createSelector([
}
return emojis;
});
})
const getCustomEmojis = createSelector([
state => state.get('custom_emojis'),
@ -236,106 +105,187 @@ const getCustomEmojis = createSelector([
return 0;
}));
@injectIntl
class EmojiPickerMenu extends ImmutablePureComponent {
static propTypes = {
customEmojis: ImmutablePropTypes.list,
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
loading: PropTypes.bool,
onClose: PropTypes.func.isRequired,
onPick: PropTypes.func.isRequired,
arrowOffsetLeft: PropTypes.string,
arrowOffsetTop: PropTypes.string,
intl: PropTypes.object.isRequired,
skinTone: PropTypes.number.isRequired,
onSkinTone: PropTypes.func.isRequired,
}
static defaultProps = {
loading: true,
frequentlyUsedEmojis: [],
}
getI18n = () => {
const { intl } = this.props
return {
search: intl.formatMessage(messages.emoji_search),
notfound: intl.formatMessage(messages.emoji_not_found),
categories: {
search: intl.formatMessage(messages.search_results),
recent: intl.formatMessage(messages.recent),
people: intl.formatMessage(messages.people),
nature: intl.formatMessage(messages.nature),
foods: intl.formatMessage(messages.food),
activity: intl.formatMessage(messages.activity),
places: intl.formatMessage(messages.travel),
objects: intl.formatMessage(messages.objects),
symbols: intl.formatMessage(messages.symbols),
flags: intl.formatMessage(messages.flags),
custom: intl.formatMessage(messages.custom),
},
}
}
handleClick = emoji => {
if (!emoji.native) {
emoji.native = emoji.colons
}
this.props.onClose()
this.props.onPick(emoji)
}
handleModifierChange = modifier => {
this.props.onSkinTone(modifier)
}
render () {
const {
loading,
intl,
customEmojis,
skinTone,
frequentlyUsedEmojis,
} = this.props
if (loading) {
return (
<div style={{ width: 340, height: 425 }}>
<ColumnIndicator type='loading' />
</div>
)
}
const title = intl.formatMessage(messages.emoji)
return (
<EmojiPicker
backgroundImageFn={backgroundImageFn}
custom={buildCustomEmojis(customEmojis)}
title={title}
i18n={this.getI18n()}
onClick={this.handleClick}
include={categoriesSort}
recent={frequentlyUsedEmojis}
skin={skinTone}
emojiSize={24}
set='twitter'
color='#30CE7D'
emoji=''
autoFocus
emojiTooltip
onSkinChange={this.handleModifierChange}
/>
)
}
}
const mapStateToProps = (state) => ({
custom_emojis: getCustomEmojis(state),
customEmojis: getCustomEmojis(state),
skinTone: state.getIn(['settings', 'skinTone']),
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
});
})
const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
onClosePopover() {
dispatch(closePopover())
},
onSkinTone: skinTone => {
dispatch(changeSetting(['skinTone'], skinTone));
dispatch(changeSetting(['skinTone'], skinTone))
},
onPickEmoji: emoji => {
dispatch(useEmoji(emoji));
dispatch(useEmoji(emoji))
if (onPickEmoji) {
onPickEmoji(emoji);
onPickEmoji(emoji)
}
},
});
})
export default
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
class EmojiPickerPopover extends ImmutablePureComponent {
static propTypes = {
custom_emojis: ImmutablePropTypes.list,
customEmojis: ImmutablePropTypes.list,
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
intl: PropTypes.object.isRequired,
onPickEmoji: PropTypes.func.isRequired,
onSkinTone: PropTypes.func.isRequired,
skinTone: PropTypes.number.isRequired,
};
onClosePopover: PropTypes.func.isRequired,
}
state = {
active: false,
loading: false,
};
}
componentWillMount = () => {
this.setState({ active: true });
if (!EmojiPicker) {
this.setState({ loading: true });
this.setState({ loading: true })
EmojiPickerAsync().then(EmojiMart => {
EmojiPicker = EmojiMart.Picker;
Emoji = EmojiMart.Emoji;
EmojiPickerAsync().then((EmojiMart) => {
EmojiPicker = EmojiMart.Picker
Emoji = EmojiMart.Emoji
this.setState({ loading: false });
this.setState({ loading: false })
}).catch(() => {
this.setState({ loading: false });
});
this.setState({ loading: false })
})
}
}
onHideDropdown = () => {
this.setState({ active: false });
}
onToggle = (e) => {
if (!this.state.loading && (!e.key || e.key === 'Enter')) {
if (this.state.active) {
this.onHideDropdown();
} else {
this.onShowDropdown(e);
}
}
}
handleKeyDown = e => {
if (e.key === 'Escape') {
this.onHideDropdown();
}
this.props.onClosePopover()
}
render () {
const {
intl,
onPickEmoji,
onSkinTone,
skinTone,
frequentlyUsedEmojis
frequentlyUsedEmojis,
customEmojis,
} = this.props
const { active, loading } = this.state
const { loading } = this.state
return (
<PopoverLayout width={340}>
<div onKeyDown={this.handleKeyDown}>
<EmojiPickerMenu
custom_emojis={this.props.custom_emojis}
loading={loading}
onClose={this.onHideDropdown}
onPick={onPickEmoji}
onSkinTone={onSkinTone}
skinTone={skinTone}
frequentlyUsedEmojis={frequentlyUsedEmojis}
/>
</div>
<EmojiPickerMenu
customEmojis={customEmojis}
loading={loading}
onClose={this.onHideDropdown}
onPick={onPickEmoji}
onSkinTone={onSkinTone}
skinTone={skinTone}
frequentlyUsedEmojis={frequentlyUsedEmojis}
/>
</PopoverLayout>
)
}

@ -151,6 +151,8 @@ class PopoverRoot extends PureComponent {
} = this.props
const visible = !!type
console.log("POPOVER_COMPONENTS[type]:", type, POPOVER_COMPONENTS[type]);
return (
<PopoverBase
visible={visible}

@ -0,0 +1,206 @@
import { RichUtils } from 'draft-js'
import { defineMessages, injectIntl } from 'react-intl'
import classNames from 'classnames/bind'
import { me } from '../initial_state'
import { makeGetAccount } from '../selectors'
import Button from './button'
const cx = classNames.bind(_s)
const RTE_ITEMS = [
{
label: 'Bold',
style: 'BOLD',
type: 'style',
icon: 'bold',
},
{
label: 'Italic',
style: 'ITALIC',
type: 'style',
icon: 'italic',
},
{
label: 'Underline',
style: 'UNDERLINE',
type: 'style',
icon: 'underline',
},
{
label: 'Strikethrough',
style: 'STRIKETHROUGH',
type: 'style',
icon: 'strikethrough',
},
// {
// label: 'Monospace',
// style: 'CODE',
// type: 'style',
// icon: 'circle',
// },
{
label: 'H1',
style: 'header-one',
type: 'block',
icon: 'text-size',
},
{
label: 'Blockquote',
style: 'blockquote',
type: 'block',
icon: 'blockquote',
},
{
label: 'Code Block',
style: 'code-block',
type: 'block',
icon: 'code',
},
{
label: 'UL',
style: 'unordered-list-item',
type: 'block',
icon: 'ul-list',
},
{
label: 'OL',
style: 'ordered-list-item',
type: 'block',
icon: 'ol-list',
},
]
const messages = defineMessages({
follow: { id: 'follow', defaultMessage: 'Follow' },
})
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']),
}
}
export default
@injectIntl
@connect(mapStateToProps)
class RichTextEditorBar extends PureComponent {
static propTypes = {
editorState: PropTypes.object.isRequired,
intl: PropTypes.object.isRequired,
isPro: PropTypes.bool.isRequired,
rteControlsVisible: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
}
toggleEditorStyle = (style, type) => {
if (type === 'style') {
this.props.onChange(
RichUtils.toggleInlineStyle(this.props.editorState, style)
)
} else if (type === 'block') {
this.props.onChange(
RichUtils.toggleBlockType(this.props.editorState, style)
)
}
}
render() {
const { isPro, rteControlsVisible, editorState } = this.props
if (!rteControlsVisible || !isPro) return null
return (
<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>
)
}
}
class StyleButton extends PureComponent {
static propTypes = {
onClick: PropTypes.func,
label: PropTypes.string,
style: PropTypes.string,
icon: PropTypes.string,
type: PropTypes.string,
}
handleOnClick = (e) => {
e.preventDefault()
this.props.onClick(this.props.style, this.props.type)
}
render() {
const {
label,
style,
type,
icon,
editorState
} = this.props
const selection = editorState.getSelection()
const currentStyle = editorState.getCurrentInlineStyle()
const blockType = editorState.getCurrentContent().getBlockForKey(selection.getStartKey()).getType()
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,
// py5: small,
// px5: small,
})
return (
<Button
className={btnClasses}
backgroundColor='none'
color={color}
onClick={this.handleOnClick}
title={label}
icon={icon}
iconClassName={_s.inheritFill}
iconWidth='12px'
iconHeight='12px'
radiusSmall
/>
)
}
}

@ -387,8 +387,8 @@ class Status extends ImmutablePureComponent {
const containerClasses = cx({
default: 1,
radiusSmall: !borderless && !isChild,
mb15: !borderless && !isChild,
backgroundColorPrimary: 1,
// mb15: !borderless && !isChild,
// backgroundColorPrimary: 1,
pb15: featured,
borderBottom1PX: featured && !isChild,
borderColorSecondary: featured && !isChild,

@ -9,7 +9,8 @@ import { me, promotions } from '../initial_state';
import { dequeueTimeline } from '../actions/timelines';
import { scrollTopTimeline } from '../actions/timelines';
import { fetchStatus } from '../actions/statuses';
import StatusContainer from '../containers/status_container';
// import StatusContainer from '../containers/status_container';
import Status from '../features/status';
import ScrollableList from './scrollable_list';
import TimelineQueueButtonHeader from './timeline_queue_button_header';
import ColumnIndicator from './column_indicator';
@ -173,7 +174,7 @@ class StatusList extends ImmutablePureComponent {
/>
) : (
<Fragment key={statusId}>
<StatusContainer
<Status
id={statusId}
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
@ -184,7 +185,7 @@ class StatusList extends ImmutablePureComponent {
/>
{
promotedStatus && index === promotion.position &&
<StatusContainer
<Status
id={promotion.status_id}
contextType={timelineId}
promoted
@ -197,7 +198,7 @@ class StatusList extends ImmutablePureComponent {
if (scrollableContent && featuredStatusIds) {
scrollableContent = featuredStatusIds.map(statusId => (
<StatusContainer
<Status
key={`f-${statusId}`}
id={statusId}
featured

@ -49,6 +49,7 @@ class ComposeForm extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
edit: PropTypes.bool,
isMatch: PropTypes.bool,
text: PropTypes.string.isRequired,
suggestions: ImmutablePropTypes.list,
account: ImmutablePropTypes.map.isRequired,
@ -234,6 +235,7 @@ class ComposeForm extends ImmutablePureComponent {
replyToId,
hasPoll,
isUploading,
isMatch,
} = this.props
const disabled = this.props.isSubmitting;
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
@ -248,7 +250,10 @@ class ComposeForm extends ImmutablePureComponent {
const childContainerClasses = cx({
default: 1,
flexNormal: 1,
flexWrap: 1,
overflowHidden: 1,
flex1: 1,
alignItemsEnd: shouldCondense,
flexRow: shouldCondense,
radiusSmall: shouldCondense,
backgroundSubtle: shouldCondense,
@ -262,6 +267,7 @@ class ComposeForm extends ImmutablePureComponent {
alignItemsStart: shouldCondense,
mt10: !shouldCondense,
px15: !shouldCondense,
marginLeftAuto: shouldCondense,
})
return (
@ -361,7 +367,21 @@ class ComposeForm extends ImmutablePureComponent {
<SchedulePostButton />
}
<GifSelectorButton small={shouldCondense} />
<EmojiPickerButton small={shouldCondense} />
<EmojiPickerButton small={shouldCondense} isMatch={isMatch} />
{
shouldCondense /* && (hasPoll || anyMedia || text) */ &&
<div className={[_s.default, _s.justifyContentCenter].join(' ')}>
<Button
narrow
onClick={this.handleSubmit}
disabled={disabledButton}
className={_s.px10}
>
{intl.formatMessage(scheduledAt ? messages.schedulePost : messages.publish)}
</Button>
</div>
}
</div>
{

@ -30,6 +30,7 @@ class EmojiPickerButton extends PureComponent {
onClick: PropTypes.func.isRequired,
active: PropTypes.bool,
small: PropTypes.bool,
isMatch: PropTypes.bool,
}
handleClick = (e) => {
@ -42,7 +43,12 @@ class EmojiPickerButton extends PureComponent {
}
render() {
const { active, small, intl } = this.props
const {
active,
intl,
isMatch,
small,
} = this.props
return (
<ComposeExtraButton
@ -50,7 +56,7 @@ class EmojiPickerButton extends PureComponent {
onClick={this.handleClick}
icon='happy'
small={small}
active={active}
active={active && isMatch}
buttonRef={this.setButton}
/>
)

@ -1,6 +1,6 @@
import { injectIntl, defineMessages } from 'react-intl'
import { openModal } from '../../../actions/modal'
import { openPopover } from '../../../actions/popover'
import { closePopover, openPopover } from '../../../actions/popover'
import { me } from '../../../initial_state'
import ComposeExtraButton from './compose_extra_button'
@ -11,6 +11,7 @@ const messages = defineMessages({
})
const mapStateToProps = (state) => ({
active: !!state.getIn(['compose', 'scheduled_at']) || state.getIn(['popover', 'popoverType']) === 'DATE_PICKER',
isPro: state.getIn(['accounts', me, 'is_pro']),
})
@ -21,6 +22,10 @@ const mapDispatchToProps = (dispatch) => ({
}))
},
onCloseDatePickerPopover() {
dispatch(closePopover())
},
onOpenProUpgradeModal() {
dispatch(openModal('PRO_UPGRADE'))
},
@ -32,10 +37,12 @@ export default
class SchedulePostDropdown extends PureComponent {
static propTypes = {
active: PropTypes.bool.isRequired,
intl: PropTypes.object.isRequired,
isPro: PropTypes.bool,
onOpenProUpgradeModal: PropTypes.func.isRequired,
onOpenDatePickerPopover: PropTypes.func.isRequired,
onCloseDatePickerPopover: PropTypes.func.isRequired,
small: PropTypes.bool,
}
@ -52,15 +59,20 @@ class SchedulePostDropdown extends PureComponent {
}
render () {
const { intl, small } = this.props
const {
active,
intl,
small,
} = this.props
return (
<ComposeExtraButton
active={active}
buttonRef={this.setButton}
icon='calendar'
title={intl.formatMessage(messages.schedule_status)}
onClick={this.handleToggle}
small={small}
buttonRef={this.setButton}
title={intl.formatMessage(messages.schedule_status)}
/>
)
}

@ -21,6 +21,7 @@ const mapStateToProps = (state, { replyToId }) => {
// console.log("isMatch:", isMatch, reduxReplyToId, replyToId)
return {
isMatch,
edit: !isMatch ? null : state.getIn(['compose', 'id']) !== null,
text: !isMatch ? '' : state.getIn(['compose', 'text']),
suggestions: !isMatch ? ImmutableList() : state.getIn(['compose', 'suggestions']),

@ -52,9 +52,12 @@ const makeMapStateToProps = () => {
const getStatus = makeGetStatus()
const mapStateToProps = (state, props) => {
const statusId = props.id || props.params.statusId
const username = props.params ? props.params.username : undefined
const status = getStatus(state, {
id: props.params.statusId,
username: props.params.username,
id: statusId,
username: username,
})
// : todo : if is comment (i.e. if any ancestorsIds) use comment not status
@ -101,7 +104,7 @@ const makeMapStateToProps = () => {
})
}
console.log("descendantsIds:", descendantsIds)
// console.log("descendantsIds:", descendantsIds)
return {
status,
@ -142,13 +145,18 @@ class Status extends ImmutablePureComponent {
};
componentWillMount() {
this.props.dispatch(fetchStatus(this.props.params.statusId));
const statusId = this.props.id || this.props.params.statusId
// console.log("statusId:", statusId)
this.props.dispatch(fetchStatus(statusId));
}
componentWillReceiveProps(nextProps) {
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
const statusId = this.props.id || this.props.params.statusId
const nextStatusId = nextProps.id || nextProps.params.statusId
if (nextStatusId !== statusId && nextStatusId) {
this._scrolledIntoView = false;
this.props.dispatch(fetchStatus(nextProps.params.statusId));
this.props.dispatch(fetchStatus(nextStatusId));
}
if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) {
@ -366,7 +374,7 @@ class Status extends ImmutablePureComponent {
}
renderChildren(list) {
console.log("list:", list)
// console.log("list:", list)
return null
// : todo : comments
return list.map(id => (
@ -434,7 +442,7 @@ class Status extends ImmutablePureComponent {
};
return (
<div ref={this.setRef}>
<div ref={this.setRef} className={_s.mb15}>
<Block>
{
/* ancestors */

@ -1,5 +1,6 @@
import { Fragment } from 'react'
import { openModal } from '../actions/modal'
import { defineMessages, injectIntl } from 'react-intl'
import PageTitle from '../features/ui/util/page_title'
import GroupsPanel from '../components/panel/groups_panel'
import ListsPanel from '../components/panel/lists_panel'
@ -13,6 +14,14 @@ import DefaultLayout from '../layouts/default_layout'
import TimelineComposeBlock from '../components/timeline_compose_block'
import Divider from '../components/divider'
const messages = defineMessages({
home: { id: 'home', defaultMessage: 'Home' },
})
const mapStateToProps = (state) => ({
totalQueuedItemsCount: state.getIn(['timelines', 'home', 'totalQueuedItemsCount']),
})
const mapDispatchToProps = (dispatch) => ({
onOpenHomePageSettingsModal() {
dispatch(openModal('HOME_TIMELINE_SETTINGS'))
@ -20,23 +29,28 @@ const mapDispatchToProps = (dispatch) => ({
})
export default
@connect(null, mapDispatchToProps)
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
class HomePage extends PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
children: PropTypes.node.isRequired,
totalQueuedItemsCount: PropTypes.number.isRequired,
onOpenHomePageSettingsModal: PropTypes.func.isRequired,
}
componentDidMount() {
document.title = '(1) Home - Gab'
}
render() {
const { children, onOpenHomePageSettingsModal } = this.props
const {
intl,
children,
totalQueuedItemsCount,
onOpenHomePageSettingsModal,
} = this.props
return (
<DefaultLayout
title='Home'
title={intl.formatMessage(messages.home)}
actions={[
{
icon: 'ellipsis',
@ -56,6 +70,10 @@ class HomePage extends PureComponent {
</Fragment>
)}
>
<PageTitle
path={intl.formatMessage(messages.home)}
badge={totalQueuedItemsCount}
/>
<TimelineComposeBlock autoFocus={false} />
<Divider />
{children}

@ -1,6 +1,7 @@
import { Fragment } from 'react'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'
import { openModal } from '../actions/modal'
import PageTitle from '../features/ui/util/page_title'
import LinkFooter from '../components/link_footer'
@ -9,6 +10,10 @@ import ListDetailsPanel from '../components/panel/list_details_panel'
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
import TrendsPanel from '../components/panel/trends_panel'
const messages = defineMessages({
list: { id: 'list', defaultMessage: 'List' },
})
const mapStateToProps = (state, props) => ({
list: state.getIn(['lists', props.params.id]),
})
@ -25,23 +30,21 @@ const mapDispatchToProps = (dispatch, { list }) => ({
})
export default
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
class ListPage extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
list: ImmutablePropTypes.map,
children: PropTypes.node.isRequired,
onOpenListEditModal: PropTypes.func.isRequired,
onOpenListTimelineSettingsModal: PropTypes.func.isRequired,
}
componentDidMount() {
const { list } = this.props
const listTitle = !list ? '...' : list.get('title')
document.title = `List / ${listTitle} - Gab`
}
render() {
const {
intl,
children,
list,
onOpenListEditModal,
@ -73,6 +76,7 @@ class ListPage extends ImmutablePureComponent {
)}
showBackBtn
>
<PageTitle path={[title, intl.formatMessage(messages.list)]} />
{ children }
</DefaultLayout>
)

@ -1,11 +1,16 @@
import { Fragment } from 'react'
import { openModal } from '../actions/modal'
import { defineMessages, injectIntl } from 'react-intl'
import PageTitle from '../features/ui/util/page_title'
import LinkFooter from '../components/link_footer'
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
import TrendsPanel from '../components/panel/trends_panel'
import DefaultLayout from '../layouts/default_layout'
const messages = defineMessages({
lists: { id: 'lists', defaultMessage: 'Lists' },
})
const mapDispatchToProps = (dispatch) => ({
onOpenListCreateModal() {
dispatch(openModal('LIST_CREATE'))
@ -13,23 +18,26 @@ const mapDispatchToProps = (dispatch) => ({
})
export default
@injectIntl
@connect(null, mapDispatchToProps)
class ListsPage extends PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
children: PropTypes.node.isRequired,
onOpenListCreateModal: PropTypes.func.isRequired,
}
componentDidMount() {
document.title = 'Lists - Gab'
}
render() {
const { children, onOpenListCreateModal } = this.props
const {
intl,
children,
onOpenListCreateModal,
} = this.props
return (
<DefaultLayout
title='Lists'
title={intl.formatMessage(messages.lists)}
actions={[
{
icon: 'add',
@ -45,6 +53,7 @@ class ListsPage extends PureComponent {
)}
showBackBtn
>
<PageTitle path={intl.formatMessage(messages.lists)} />
{children}
</DefaultLayout>
)

@ -2,7 +2,6 @@ import { Fragment } from 'react'
import PageTitle from '../features/ui/util/page_title'
import LinkFooter from '../components/link_footer'
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
// import TrendsPanel from '../components/panel/trends_panel'
import DefaultLayout from '../layouts/default_layout'
export default class ModalPage extends PureComponent {
@ -25,6 +24,7 @@ export default class ModalPage extends PureComponent {
)}
showBackBtn
>
<PageTitle path={title} />
{children}
</DefaultLayout>
)

@ -61,8 +61,6 @@ class ProfilePage extends ImmutablePureComponent {
params: { username },
} = this.props
console.log("acount:", account)
const title = !!account ? account.get('display_name') : username
return (

@ -1,15 +1,28 @@
import { Fragment } from 'react'
import { defineMessages, injectIntl } from 'react-intl'
import PageTitle from '../features/ui/util/page_title'
import LinkFooter from '../components/link_footer'
import SearchFilterPanel from '../components/panel/search_filter_panel'
import SearchLayout from '../layouts/search_layout'
export default class SearchPage extends PureComponent {
componentDidMount() {
document.title = `Search - Gab`
const messages = defineMessages({
search: { id: 'search', defaultMessage: 'Search' },
})
export default
@injectIntl
class SearchPage extends PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
children: PropTypes.node.isRequired,
}
render() {
const { children } = this.props
const {
intl,
children,
} = this.props
return (
<SearchLayout
@ -21,8 +34,10 @@ export default class SearchPage extends PureComponent {
</Fragment>
)}
>
<PageTitle path={intl.formatMessage(messages.search)} />
{children}
</SearchLayout>
)
}
}

@ -8,39 +8,17 @@ import UserPanel from '../components/panel/user_panel'
import TrendsPanel from '../components/panel/trends_panel'
import HashtagsPanel from '../components/panel/hashtags_panel'
import DefaultLayout from '../layouts/default_layout'
import TimelineComposeBlock from '../components/timeline_compose_block'
import Divider from '../components/divider'
const mapDispatchToProps = (dispatch) => ({
onOpenHomePageSettingsModal() {
dispatch(openModal('HOME_TIMELINE_SETTINGS'))
},
})
export default
@connect(null, mapDispatchToProps)
class ShortcutsPage extends PureComponent {
static propTypes = {
onOpenHomePageSettingsModal: PropTypes.func.isRequired,
}
componentDidMount() {
document.title = '(1) Home - Gab'
}
render() {
const { children, onOpenHomePageSettingsModal } = this.props
const { children } = this.props
return (
<DefaultLayout
title='Shortcuts'
actions={[
{
icon: 'ellipsis',
onClick: onOpenHomePageSettingsModal,
},
]}
actions={[]}
layout={(
<Fragment>
<UserPanel />
@ -53,8 +31,6 @@ class ShortcutsPage extends PureComponent {
</Fragment>
)}
>
<TimelineComposeBlock autoFocus={false} />
<Divider />
{children}
</DefaultLayout>
)

@ -93,6 +93,10 @@ body {
fill: inherit;
}
.flex1 {
flex: 1;
}
.flexNormal {
flex-basis: 0%;
flex-grow: 1;
@ -1047,7 +1051,7 @@ body {
appearance: none;
}
.emojione {
:global(.emojione) {
margin: -3px 0 0;
height: 20px;
width: 20px;
@ -1066,11 +1070,11 @@ body {
/**
* Rich Text Editor
*/
.RTE :global(.RichEditor-hidePlaceholder .public-DraftEditorPlaceholder-root) {
:global(.RichEditor-hidePlaceholder .public-DraftEditorPlaceholder-root) {
display: none;
}
.RTE :global(.RichEditor-editor .RichEditor-blockquote) {
:global(.RichEditor-blockquote) {
border-left: 5px solid #eee;
color: #666;
font-family: 'Hoefler Text', 'Georgia', serif;
@ -1079,9 +1083,64 @@ body {
padding: 10px 20px;
}
.RTE :global(.RichEditor-editor .public-DraftStyleDefault-pre) {
:global(.public-DraftStyleDefault-pre) {
background-color: rgba(0, 0, 0, 0.05);
font-family: 'Inconsolata', 'Menlo', 'Consolas', monospace;
font-size: 16px;
padding: 20px;
padding: 10px 20px;
}
/* */
:global(.emoji-mart-search input) {
border-radius: 9999px !important;
}
/* */
:global(.react-datepicker) {
border: 0 !important;
width: 100% !important;
}
:global(.react-datepicker__header) {
background-color: transparent !important;
border-color: #E5E9ED !important;
}
:global(.react-datepicker__month-container) {
border-color: #E5E9ED !important;
}
:global(.react-datepicker__time-container) {
border-color: #E5E9ED !important;
min-height: 230px !important;
}
:global(.react-datepicker__time-list-item) {
padding: 10px !important;
height: auto !important;
}
:global(.react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button)) {
right: 95px !important;
}
:global(.react-datepicker__current-month),
:global(.react-datepicker-time__header),
:global(.react-datepicker-year-header) {
font-weight: 500 !important;
}
:global(.react-datepicker__time-list-item--selected),
:global(.react-datepicker__day--selected),
:global(.react-datepicker__day--in-selecting-range),
:global(.react-datepicker__day--in-range),
:global(.react-datepicker__month-text--selected),
:global(.react-datepicker__month-text--in-selecting-range),
:global(.react-datepicker__month-text--in-range),
:global(.react-datepicker__quarter-text--selected),
:global(.react-datepicker__quarter-text--in-selecting-range),
:global(.react-datepicker__quarter-text--in-range) {
background-color: #30CE7D !important;
}