Progress on new composer

Updated composer to work with comments, groups, timelines, comments in groups, etc.
This commit is contained in:
mgabdev 2020-12-20 20:37:53 -05:00
parent 1a8ecc672c
commit a2ffbadedb
11 changed files with 84 additions and 96 deletions

View File

@ -307,16 +307,14 @@ export const handleComposeSubmit = (dispatch, getState, response, status) => {
/** /**
* *
*/ */
export const submitCompose = (groupId, replyToId = null, router, isStandalone, autoJoinGroup) => (dispatch, getState) => { export const submitCompose = (options = {}) => (dispatch, getState) => {
if (!me) return if (!me) return
if (autoJoinGroup) dispatch(joinGroup(groupId)) if (options.autoJoinGroup) dispatch(joinGroup(groupId))
let status = getState().getIn(['compose', 'text'], '') let status = getState().getIn(['compose', 'text'], '')
let markdown = getState().getIn(['compose', 'markdown'], '') let markdown = getState().getIn(['compose', 'markdown'], '')
const media = getState().getIn(['compose', 'media_attachments'])
const isPrivateGroup = !!groupId ? getState().getIn(['groups', groupId, 'is_private'], false) : false
const replacer = (match) => { const replacer = (match) => {
const hasProtocol = match.startsWith('https://') || match.startsWith('http://') const hasProtocol = match.startsWith('https://') || match.startsWith('http://')
//Make sure not a remote mention like @someone@somewhere.com //Make sure not a remote mention like @someone@somewhere.com
@ -331,24 +329,27 @@ export const submitCompose = (groupId, replyToId = null, router, isStandalone, a
status = `${status}`.replace(urlRegex, replacer) status = `${status}`.replace(urlRegex, replacer)
markdown = !!markdown ? `${markdown}`.replace(urlRegex, replacer) : undefined markdown = !!markdown ? `${markdown}`.replace(urlRegex, replacer) : undefined
const inReplyToId = getState().getIn(['compose', 'in_reply_to'], null) || replyToId const inReplyToId = getState().getIn(['compose', 'in_reply_to'], null)
const groupId = getState().getIn(['compose', 'group_id'], null)
dispatch(submitComposeRequest()) const media = getState().getIn(['compose', 'media_attachments'])
dispatch(closeModal()) const isPrivateGroup = !!groupId ? getState().getIn(['groups', groupId, 'is_private'], false) : false
const expires_at = getState().getIn(['compose', 'expires_at'], null)
const id = getState().getIn(['compose', 'id'])
const endpoint = id === null
? '/api/v1/statuses'
: `/api/v1/statuses/${id}`
const method = id === null ? 'post' : 'put'
let scheduled_at = getState().getIn(['compose', 'scheduled_at'], null) let scheduled_at = getState().getIn(['compose', 'scheduled_at'], null)
if (scheduled_at !== null) scheduled_at = moment.utc(scheduled_at).toDate() if (scheduled_at !== null) scheduled_at = moment.utc(scheduled_at).toDate()
const expires_at = getState().getIn(['compose', 'expires_at'], null) //
if (isMobile(window.innerWidth) && router && isStandalone) { const id = getState().getIn(['compose', 'id'])
router.history.goBack() const endpoint = id === null ? '/api/v1/statuses' : `/api/v1/statuses/${id}`
const method = id === null ? 'post' : 'put'
dispatch(submitComposeRequest())
dispatch(closeModal())
if (isMobile(window.innerWidth) && options.router && options.isStandalone) {
options.router.history.goBack()
} }
api(getState)[method](endpoint, { api(getState)[method](endpoint, {
@ -356,7 +357,7 @@ export const submitCompose = (groupId, replyToId = null, router, isStandalone, a
markdown, markdown,
expires_at, expires_at,
scheduled_at, scheduled_at,
autoJoinGroup, autoJoinGroup: options.autoJoinGroup,
isPrivateGroup, isPrivateGroup,
in_reply_to_id: inReplyToId, in_reply_to_id: inReplyToId,
quote_of_id: getState().getIn(['compose', 'quote_of_id'], null), quote_of_id: getState().getIn(['compose', 'quote_of_id'], null),

View File

@ -499,11 +499,7 @@ export const debouncedFetchGroupMembersAdminSearch = debounce((groupId, query, d
if (!groupId || !query) return if (!groupId || !query) return
api(getState).get(`/api/v1/groups/${groupId}/member_search`, { api(getState).get(`/api/v1/groups/${groupId}/member_search`, {
params: { params: { q: query },
q: query,
resolve: false,
limit: 4,
},
}).then((response) => { }).then((response) => {
dispatch(importFetchedAccounts(response.data)) dispatch(importFetchedAccounts(response.data))
dispatch(fetchGroupMembersAdminSearchSuccess(response.data)) dispatch(fetchGroupMembersAdminSearchSuccess(response.data))
@ -621,11 +617,7 @@ export const debouncedFetchGroupRemovedAccountsAdminSearch = debounce((groupId,
if (!groupId || !query) return if (!groupId || !query) return
api(getState).get(`/api/v1/groups/${groupId}/removed_accounts_search`, { api(getState).get(`/api/v1/groups/${groupId}/removed_accounts_search`, {
params: { params: { q: query },
q: query,
resolve: false,
limit: 4,
},
}).then((response) => { }).then((response) => {
dispatch(importFetchedAccounts(response.data)) dispatch(importFetchedAccounts(response.data))
dispatch(fetchGroupRemovedAccountsAdminSearchSuccess(response.data)) dispatch(fetchGroupRemovedAccountsAdminSearchSuccess(response.data))

View File

@ -10,26 +10,17 @@ import {
import Heading from '../heading' import Heading from '../heading'
import Button from '../button' import Button from '../button'
import BackButton from '../back_button' import BackButton from '../back_button'
import Text from '../text'
import ComposeFormSubmitButton from '../../features/compose/components/compose_form_submit_button' import ComposeFormSubmitButton from '../../features/compose/components/compose_form_submit_button'
class ComposeNavigationBar extends React.PureComponent { class ComposeNavigationBar extends React.PureComponent {
handleOnPost = () => { static contextTypes = {
// router: PropTypes.object,
} }
render() { render() {
const { const { isXS } = this.props
isUploading,
isChangingUpload,
isSubmitting,
anyMedia,
text,
isXS,
} = this.props
const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia)
const innerClasses = CX({ const innerClasses = CX({
d: 1, d: 1,
flexRow: 1, flexRow: 1,
@ -64,7 +55,7 @@ class ComposeNavigationBar extends React.PureComponent {
</div> </div>
<div className={[_s.d, _s.h53PX, _s.flexRow, _s.mlAuto, _s.aiCenter, _s.jcCenter, _s.mr15].join(' ')}> <div className={[_s.d, _s.h53PX, _s.flexRow, _s.mlAuto, _s.aiCenter, _s.jcCenter, _s.mr15].join(' ')}>
<ComposeFormSubmitButton type='navigation' /> <ComposeFormSubmitButton type='navigation' router={this.context.router} />
</div> </div>
</div> </div>
@ -76,27 +67,8 @@ class ComposeNavigationBar extends React.PureComponent {
} }
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 = { ComposeNavigationBar.propTypes = {
isUploading: PropTypes.bool,
isChangingUpload: PropTypes.bool,
isSubmitting: PropTypes.bool,
anyMedia: PropTypes.bool,
text: PropTypes.string,
isXS: PropTypes.bool, isXS: PropTypes.bool,
} }
export default connect(mapStateToProps, mapDispatchToProps)(ComposeNavigationBar) export default ComposeNavigationBar

View File

@ -32,6 +32,7 @@ class ComposeDestinationHeader extends ImmutablePureComponent {
render() { render() {
const { const {
account, account,
isReply,
isModal, isModal,
composeGroup, composeGroup,
formLocation, formLocation,
@ -41,7 +42,18 @@ class ComposeDestinationHeader extends ImmutablePureComponent {
let groupTitle = !!composeGroup ? composeGroup.get('title') : '' let groupTitle = !!composeGroup ? composeGroup.get('title') : ''
groupTitle = groupTitle.length > 32 ? `${groupTitle.substring(0, 32).trim()}...` : groupTitle groupTitle = groupTitle.length > 32 ? `${groupTitle.substring(0, 32).trim()}...` : groupTitle
const title = !!composeGroup ? `Post to ${groupTitle}` : 'Post to timeline' let title = 'Post to timeline'
if (!!composeGroup) {
if (isReply) {
title = `Comment in ${groupTitle}`
} else {
title = `Post to ${groupTitle}`
}
} else {
if (isReply) {
title = 'Post as comment'
}
}
return ( return (
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.bgPrimary, _s.w100PC, _s.h40PX, _s.pr15].join(' ')}> <div className={[_s.d, _s.flexRow, _s.aiCenter, _s.bgPrimary, _s.w100PC, _s.h40PX, _s.pr15].join(' ')}>
@ -57,12 +69,12 @@ class ComposeDestinationHeader extends ImmutablePureComponent {
buttonRef={this.setDestinationBtn} buttonRef={this.setDestinationBtn}
backgroundColor='secondary' backgroundColor='secondary'
color='primary' color='primary'
onClick={this.handleOnClick} onClick={isReply ? undefined : this.handleOnClick}
className={[_s.border1PX, _s.borderColorPrimary].join(' ')} className={[_s.border1PX, _s.borderColorPrimary].join(' ')}
> >
<Text color='inherit' size='small' className={_s.jcCenter}> <Text color='inherit' size='small' className={_s.jcCenter}>
{title} {title}
<Icon id='caret-down' size='8px' className={_s.ml5} /> { !isReply && <Icon id='caret-down' size='8px' className={_s.ml5} /> }
</Text> </Text>
</Button> </Button>
</div> </div>
@ -89,6 +101,7 @@ const mapStateToProps = (state) => {
return { return {
composeGroupId, composeGroupId,
isReply: !!state.getIn(['compose', 'in_reply_to']),
composeGroup: state.getIn(['groups', composeGroupId]), composeGroup: state.getIn(['groups', composeGroupId]),
} }
} }
@ -111,6 +124,7 @@ ComposeDestinationHeader.propTypes = {
onOpenModal: PropTypes.func.isRequired, onOpenModal: PropTypes.func.isRequired,
onOpenPopover: PropTypes.func.isRequired, onOpenPopover: PropTypes.func.isRequired,
formLocation: PropTypes.string, formLocation: PropTypes.string,
isReply: PropTypes.bool.isRequired,
} }
export default connect(mapStateToProps, mapDispatchToProps)(ComposeDestinationHeader) export default connect(mapStateToProps, mapDispatchToProps)(ComposeDestinationHeader)

View File

@ -60,8 +60,6 @@ class ComposeExtraButtonList extends React.PureComponent {
const isIntroduction = formLocation === 'introduction' const isIntroduction = formLocation === 'introduction'
const small = (!isModal && isXS && !isStandalone) || isTimeline const small = (!isModal && isXS && !isStandalone) || isTimeline
console.log("small, formLocation:", small, formLocation)
const containerClasses = CX({ const containerClasses = CX({
d: 1, d: 1,
w100PC: 1, w100PC: 1,

View File

@ -102,14 +102,19 @@ class ComposeForm extends ImmutablePureComponent {
// } // }
// Submit disabled: // Submit disabled:
const { isSubmitting, isChangingUpload, isUploading, anyMedia, groupId, autoJoinGroup } = this.props const { isSubmitting, formLocation, isChangingUpload, isUploading, anyMedia, groupId, autoJoinGroup } = this.props
const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('') const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('')
const isStandalone = formLocation === 'standalone'
if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > MAX_POST_CHARACTER_COUNT || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) { if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > MAX_POST_CHARACTER_COUNT || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
return return
} }
this.props.onSubmit(groupId, this.props.replyToId, this.context.router, autoJoinGroup) this.props.onSubmit({
router: this.context.router,
isStandalone,
autoJoinGroup,
})
} }
onSuggestionsClearRequested = () => { onSuggestionsClearRequested = () => {
@ -183,6 +188,7 @@ class ComposeForm extends ImmutablePureComponent {
render() { render() {
const { const {
intl, intl,
autoJoinGroup,
account, account,
onPaste, onPaste,
anyMedia, anyMedia,
@ -277,9 +283,7 @@ class ComposeForm extends ImmutablePureComponent {
<div className={[_s.d, _s.w100PC, _s.flexGrow1, _s.bgPrimary].join(' ')}> <div className={[_s.d, _s.w100PC, _s.flexGrow1, _s.bgPrimary].join(' ')}>
<div className={[_s.d, _s.calcMaxH410PX, _s.overflowYScroll].join(' ')}> <div className={[_s.d, _s.calcMaxH410PX, _s.overflowYScroll].join(' ')}>
<Responsive min={BREAKPOINT_EXTRA_SMALL}> <ComposeDestinationHeader formLocation={formLocation} account={account} isModal={isModalOpen} />
<ComposeDestinationHeader formLocation={formLocation} account={account} isModal={isModalOpen} />
</Responsive>
<div className={containerClasses} ref={this.setForm} onClick={this.handleClick}> <div className={containerClasses} ref={this.setForm} onClick={this.handleClick}>
{ {
@ -337,7 +341,12 @@ class ComposeForm extends ImmutablePureComponent {
{ {
(!disabledButton || isModalOpen) && isMatch && (!disabledButton || isModalOpen) && isMatch &&
<div className={[_s.d, _s.mb10, _s.px10].join(' ')}> <div className={[_s.d, _s.mb10, _s.px10].join(' ')}>
<ComposeFormSubmitButton type='block' /> <ComposeFormSubmitButton
type='block'
formLocation={formLocation}
autoJoinGroup={autoJoinGroup}
router={this.context.router}
/>
</div> </div>
} }

View File

@ -15,7 +15,9 @@ import Text from '../../../components/text'
class ComposeFormSubmitButton extends React.PureComponent { class ComposeFormSubmitButton extends React.PureComponent {
handleSubmit = () => { handleSubmit = () => {
this.props.onSubmit() const { formLocation, autoJoinGroup, router, type } = this.props
const isStandalone = formLocation === 'standalone' || type === 'navigation'
this.props.onSubmit(router, isStandalone, autoJoinGroup)
} }
render() { render() {
@ -25,7 +27,7 @@ class ComposeFormSubmitButton extends React.PureComponent {
active, active,
small, small,
type, type,
//
edit, edit,
text, text,
isSubmitting, isSubmitting,
@ -35,6 +37,7 @@ class ComposeFormSubmitButton extends React.PureComponent {
quoteOfId, quoteOfId,
scheduledAt, scheduledAt,
hasPoll, hasPoll,
replyToId,
} = this.props } = this.props
const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia) const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia)
@ -66,12 +69,6 @@ class ComposeFormSubmitButton extends React.PureComponent {
) )
} }
const containerClasses = CX({
d: 1,
jcCenter: 1,
h40PX: 1,
})
const btnClasses = CX({ const btnClasses = CX({
d: 1, d: 1,
radiusSmall: 1, radiusSmall: 1,
@ -101,7 +98,7 @@ class ComposeFormSubmitButton extends React.PureComponent {
} }
return ( return (
<div className={containerClasses}> <div className={[_s.d, _s.jcCenter, _s.h40PX].join(' ')}>
<div className={[_s.d, _s.w100PC].join(' ')}> <div className={[_s.d, _s.w100PC].join(' ')}>
<Button <Button
isBlock isBlock
@ -139,16 +136,24 @@ const mapStateToProps = (state) => ({
quoteOfId: state.getIn(['compose', 'quote_of_id']), quoteOfId: state.getIn(['compose', 'quote_of_id']),
scheduledAt: state.getIn(['compose', 'scheduled_at']), scheduledAt: state.getIn(['compose', 'scheduled_at']),
hasPoll: state.getIn(['compose', 'poll']), hasPoll: state.getIn(['compose', 'poll']),
replyToId: state.getIn(['compose', 'in_reply_to']),
}) })
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
onSubmit(groupId, replyToId = null, router, isStandalone, autoJoinGroup) { onSubmit(router, isStandalone, autoJoinGroup) {
dispatch(submitCompose(groupId, replyToId, router, isStandalone, autoJoinGroup)) dispatch(submitCompose({
router,
isStandalone,
autoJoinGroup,
}))
} }
}) })
ComposeFormSubmitButton.propTypes = { ComposeFormSubmitButton.propTypes = {
type: PropTypes.oneOf(['header', 'navigation', 'block', 'comment']) type: PropTypes.oneOf(['header', 'navigation', 'block', 'comment']),
formLocation: PropTypes.string,
autoJoinGroup: PropTypes.bool,
router: PropTypes.object,
} }
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ComposeFormSubmitButton)) export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ComposeFormSubmitButton))

View File

@ -5,7 +5,6 @@ import { List as ImmutableList } from 'immutable'
import ComposeForm from '../components/compose_form' import ComposeForm from '../components/compose_form'
import { import {
changeCompose, changeCompose,
submitCompose,
clearComposeSuggestions, clearComposeSuggestions,
fetchComposeSuggestions, fetchComposeSuggestions,
selectComposeSuggestion, selectComposeSuggestion,
@ -21,11 +20,12 @@ import { me } from '../../../initial_state'
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const { const {
replyToId, replyToId,
isStandalone, formLocation,
shouldCondense, shouldCondense,
isModal, isModal,
} = props } = props
const isStandalone = formLocation === 'standalone'
const reduxReplyToId = state.getIn(['compose', 'in_reply_to']) const reduxReplyToId = state.getIn(['compose', 'in_reply_to'])
const isModalOpen = state.getIn(['modal', 'modalType']) === 'COMPOSE' || isStandalone const isModalOpen = state.getIn(['modal', 'modalType']) === 'COMPOSE' || isStandalone
let isMatch; let isMatch;
@ -44,6 +44,7 @@ const mapStateToProps = (state, props) => {
if (!isMatch) { if (!isMatch) {
return { return {
isStandalone,
isMatch, isMatch,
isModalOpen, isModalOpen,
reduxReplyToId, reduxReplyToId,
@ -73,6 +74,7 @@ const mapStateToProps = (state, props) => {
isMatch, isMatch,
isModalOpen, isModalOpen,
reduxReplyToId, reduxReplyToId,
isStandalone,
edit: state.getIn(['compose', 'id']) !== null, edit: state.getIn(['compose', 'id']) !== null,
text: state.getIn(['compose', 'text']), text: state.getIn(['compose', 'text']),
markdown: state.getIn(['compose', 'markdown']), markdown: state.getIn(['compose', 'markdown']),
@ -96,16 +98,13 @@ const mapStateToProps = (state, props) => {
} }
} }
const mapDispatchToProps = (dispatch, { isStandalone }) => ({ const mapDispatchToProps = (dispatch, { formLocation }) => ({
onChange(text, markdown, newReplyToId, position) { onChange(text, markdown, newReplyToId, position) {
const isStandalone = formLocation === 'standalone'
dispatch(changeCompose(text, markdown, newReplyToId, isStandalone, position)) dispatch(changeCompose(text, markdown, newReplyToId, isStandalone, position))
}, },
onSubmit(groupId, replyToId, router, autoJoinGroup) {
dispatch(submitCompose(groupId, replyToId, router, isStandalone, autoJoinGroup))
},
onClearSuggestions() { onClearSuggestions() {
dispatch(clearComposeSuggestions()) dispatch(clearComposeSuggestions())
}, },

View File

@ -186,8 +186,6 @@ class Deck extends React.PureComponent {
const isEmpty = gabDeckOrder.size === 0 const isEmpty = gabDeckOrder.size === 0
console.log("gabDeckOrder:", gabDeckOrder)
return ( return (
<SortableContainer <SortableContainer
axis='x' axis='x'

View File

@ -115,7 +115,6 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
if (!this.scrollContainerRef) return if (!this.scrollContainerRef) return
if (this.scrollContainerRef.scrollTop !== newScrollTop) { if (this.scrollContainerRef.scrollTop !== newScrollTop) {
this.lastScrollWasSynthetic = true this.lastScrollWasSynthetic = true
console.log("newScrollTop:", newScrollTop)
this.scrollContainerRef.scrollTop = newScrollTop this.scrollContainerRef.scrollTop = newScrollTop
} }
} }

View File

@ -273,6 +273,7 @@ export default function compose(state = initialState, action) {
case COMPOSE_REPLY: case COMPOSE_REPLY:
return state.withMutations(map => { return state.withMutations(map => {
map.set('in_reply_to', action.status.get('id')); map.set('in_reply_to', action.status.get('id'));
map.set('group_id', action.status.getIn(['group', 'id'], null));
map.set('quote_of_id', null); map.set('quote_of_id', null);
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
map.set('focusDate', new Date()); map.set('focusDate', new Date());