Added emoji support for composer

• Added:
- emoji support for composer
- new emoji sheet

• Updated:
- Textarea to accomodate
- Reducers and actions to retrieve caretPosition on text change when typing

• Removed:
- Unused input type on AutosuggestTextbox
- Old emoji sheet.png
This commit is contained in:
mgabdev 2020-06-05 21:43:08 -04:00
parent 85ec3060d9
commit 91a227913a
8 changed files with 65 additions and 90 deletions

View File

@ -81,7 +81,7 @@ export const ensureComposeIsVisible = (getState, routerHistory) => {
} }
}; };
export function changeCompose(text, markdown, replyId, isStandalone) { export function changeCompose(text, markdown, replyId, isStandalone, caretPosition) {
return function (dispatch, getState) { return function (dispatch, getState) {
const reduxReplyToId = getState().getIn(['compose', 'in_reply_to']) const reduxReplyToId = getState().getIn(['compose', 'in_reply_to'])
const existingText = getState().getIn(['compose', 'text']).trim() const existingText = getState().getIn(['compose', 'text']).trim()
@ -105,6 +105,7 @@ export function changeCompose(text, markdown, replyId, isStandalone) {
type: COMPOSE_CHANGE, type: COMPOSE_CHANGE,
text: text, text: text,
markdown: markdown, markdown: markdown,
caretPosition: caretPosition,
}) })
} else if (existingText.length > 0 && text.trim().length > 0) { } else if (existingText.length > 0 && text.trim().length > 0) {
dispatch(openModal('CONFIRM', { dispatch(openModal('CONFIRM', {
@ -119,6 +120,7 @@ export function changeCompose(text, markdown, replyId, isStandalone) {
type: COMPOSE_CHANGE, type: COMPOSE_CHANGE,
text: text, text: text,
markdown: markdown, markdown: markdown,
caretPosition: caretPosition,
}) })
} }
})) }))
@ -136,6 +138,7 @@ export function changeCompose(text, markdown, replyId, isStandalone) {
type: COMPOSE_CHANGE, type: COMPOSE_CHANGE,
text: text, text: text,
markdown: markdown, markdown: markdown,
caretPosition: caretPosition,
}) })
} else if (existingText.length > 0 && text.trim().length > 0) { } else if (existingText.length > 0 && text.trim().length > 0) {
dispatch(openModal('CONFIRM', { dispatch(openModal('CONFIRM', {
@ -149,6 +152,7 @@ export function changeCompose(text, markdown, replyId, isStandalone) {
type: COMPOSE_CHANGE, type: COMPOSE_CHANGE,
text: text, text: text,
markdown: markdown, markdown: markdown,
caretPosition: caretPosition,
}) })
}, },
})) }))
@ -160,6 +164,7 @@ export function changeCompose(text, markdown, replyId, isStandalone) {
type: COMPOSE_CHANGE, type: COMPOSE_CHANGE,
text: text, text: text,
markdown: markdown, markdown: markdown,
caretPosition: caretPosition,
}) })
} }
} }
@ -642,10 +647,9 @@ export function changeComposeVisibility(value) {
}; };
}; };
export function insertEmojiCompose(position, emoji, needsSpace) { export function insertEmojiCompose(emoji, needsSpace) {
return { return {
type: COMPOSE_EMOJI_INSERT, type: COMPOSE_EMOJI_INSERT,
position,
emoji, emoji,
needsSpace, needsSpace,
}; };

View File

@ -255,61 +255,27 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
justifyContentCenter: small, justifyContentCenter: small,
}) })
if (textarea) {
return (
<Fragment>
<div className={textareaContainerClasses}>
<Textarea
inputRef={this.setTextbox}
className={textareaClasses}
disabled={disabled}
placeholder={placeholder}
autoFocus={false}
value={value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
aria-autocomplete='list'
/>
{/*<Composer
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}
small={small}
/>*/}
{children}
</div>
{ /* : 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)}
</div>
</div>
</Fragment>
)
}
return ( return (
<div className={[_s.default, _s.flexGrow1].join(' ')}> <Fragment>
<label className={[_s.default].join(' ')}> <div className={textareaContainerClasses}>
<span style={{ display: 'none' }}>{placeholder}</span> <Textarea
inputRef={this.setTextbox}
className={textareaClasses}
disabled={disabled}
placeholder={placeholder}
autoFocus={false}
value={value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
aria-autocomplete='list'
/>
<Input {/*<Composer
type='text' inputRef={this.setTextbox}
ref={this.setTextbox}
disabled={disabled} disabled={disabled}
placeholder={placeholder} placeholder={placeholder}
autoFocus={autoFocus} autoFocus={autoFocus}
@ -319,20 +285,21 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
onKeyUp={onKeyUp} onKeyUp={onKeyUp}
onFocus={this.onFocus} onFocus={this.onFocus}
onBlur={this.onBlur} onBlur={this.onBlur}
style={style} onPaste={this.onPaste}
aria-autocomplete='list' small={small}
id={id} />*/}
className={className}
maxLength={maxLength}
prependIcon={prependIcon}
/>
</label>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}> {children}
{suggestions.map(this.renderSuggestion)}
</div> </div>
</div>
); { /* : 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)}
</div>
</div>
</Fragment>
)
} }
} }

View File

@ -35,7 +35,7 @@ const messages = defineMessages({
const assetHost = process.env.CDN_HOST || '' const assetHost = process.env.CDN_HOST || ''
let EmojiPicker, Emoji // load asynchronously let EmojiPicker, Emoji // load asynchronously
const backgroundImageFn = () => `${assetHost}/emoji/sheet.png` const backgroundImageFn = () => `${assetHost}/emoji/sheet_1.png`
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false
const perLine = 8 const perLine = 8
@ -223,7 +223,7 @@ const mapDispatchToProps = (dispatch) => ({
onPickEmoji: (emoji) => { onPickEmoji: (emoji) => {
dispatch(useEmoji(emoji)) dispatch(useEmoji(emoji))
dispatch(insertEmojiCompose(0, emoji, false)) dispatch(insertEmojiCompose(emoji, false))
}, },
}) })

View File

@ -93,7 +93,13 @@ class ComposeForm extends ImmutablePureComponent {
}; };
handleChange = (e, markdown) => { handleChange = (e, markdown) => {
this.props.onChange(e.target.value, markdown, this.props.replyToId) let position = null
try {
position = this.autosuggestTextarea.textbox.selectionStart
} catch (error) {
//
}
this.props.onChange(e.target.value, markdown, this.props.replyToId, position)
} }
handleComposeFocus = () => { handleComposeFocus = () => {
@ -131,11 +137,11 @@ class ComposeForm extends ImmutablePureComponent {
} }
handleSubmit = () => { handleSubmit = () => {
// if (this.props.text !== this.autosuggestTextarea.textbox.value) { if (this.props.text !== this.autosuggestTextarea.textbox.value) {
// // Something changed the text inside the textarea (e.g. browser extensions like Grammarly) // Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
// // Update the state to match the current text // Update the state to match the current text
// this.props.onChange(this.autosuggestTextarea.textbox.value); this.props.onChange(this.autosuggestTextarea.textbox.value);
// } }
// Submit disabled: // Submit disabled:
const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props; const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
@ -198,8 +204,8 @@ class ComposeForm extends ImmutablePureComponent {
selectionStart = selectionEnd; selectionStart = selectionEnd;
} }
// this.autosuggestTextarea.textbox.setSelectionRange(selectionStart, selectionEnd); this.autosuggestTextarea.textbox.setSelectionRange(selectionStart, selectionEnd);
// this.autosuggestTextarea.textbox.focus(); this.autosuggestTextarea.textbox.focus();
} }
} }
@ -318,7 +324,6 @@ class ComposeForm extends ImmutablePureComponent {
onPaste={onPaste} onPaste={onPaste}
autoFocus={shouldAutoFocus} autoFocus={shouldAutoFocus}
small={shouldCondense} small={shouldCondense}
textarea
/> />
<div className={actionsContainerClasses}> <div className={actionsContainerClasses}>
@ -409,7 +414,6 @@ class ComposeForm extends ImmutablePureComponent {
onPaste={onPaste} onPaste={onPaste}
autoFocus={shouldAutoFocus} autoFocus={shouldAutoFocus}
small={shouldCondense} small={shouldCondense}
textarea
/> />
{ {
@ -456,7 +460,7 @@ class ComposeForm extends ImmutablePureComponent {
<div className={actionsContainerClasses}> <div className={actionsContainerClasses}>
<div className={[_s.default, _s.flexRow, _s.mrAuto].join(' ')}> <div className={[_s.default, _s.flexRow, _s.mrAuto].join(' ')}>
{ /* <EmojiPickerButton small={shouldCondense} isMatch={isMatch} /> */ } <EmojiPickerButton small={shouldCondense} isMatch={isMatch} />
<UploadButton small={shouldCondense} /> <UploadButton small={shouldCondense} />
{ /* <GifSelectorButton small={shouldCondense} /> */} { /* <GifSelectorButton small={shouldCondense} /> */}

View File

@ -86,8 +86,8 @@ const mapStateToProps = (state, { replyToId, isStandalone }) => {
const mapDispatchToProps = (dispatch, { reduxReplyToId, replyToId, isStandalone }) => ({ const mapDispatchToProps = (dispatch, { reduxReplyToId, replyToId, isStandalone }) => ({
onChange(text, markdown, newReplyToId) { onChange(text, markdown, newReplyToId, position) {
dispatch(changeCompose(text, markdown, newReplyToId, isStandalone)) dispatch(changeCompose(text, markdown, newReplyToId, isStandalone, position))
}, },
onSubmit(group, replyToId, router) { onSubmit(group, replyToId, router) {

View File

@ -169,14 +169,13 @@ const updateSuggestionTags = (state, token) => {
}); });
}; };
const insertEmoji = (state, position, emojiData, needsSpace) => { const insertEmoji = (state, emojiData, needsSpace) => {
const oldText = state.get('text') const position = state.get('caretPosition')
const emoji = needsSpace ? ' ' + emojiData.native : emojiData.native const oldText = state.get('text');
const text = `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}` const emoji = needsSpace ? ' ' + emojiData.native : emojiData.native;
// console.log("insertEmoji reducer:", emoji, position, emojiData, needsSpace, text)
return state.merge({ return state.merge({
text, text: `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`,
focusDate: new Date(), focusDate: new Date(),
caretPosition: position + emoji.length + 1, caretPosition: position + emoji.length + 1,
idempotencyKey: uuid(), idempotencyKey: uuid(),
@ -254,6 +253,7 @@ export default function compose(state = initialState, action) {
map.set('text', action.text) map.set('text', action.text)
map.set('markdown', action.markdown) map.set('markdown', action.markdown)
map.set('idempotencyKey', uuid()) map.set('idempotencyKey', uuid())
map.set('caretPosition', action.caretPosition)
if (action.replyId) { if (action.replyId) {
map.set('in_reply_to', action.replyId) map.set('in_reply_to', action.replyId)
} }
@ -345,7 +345,7 @@ export default function compose(state = initialState, action) {
return state; return state;
} }
case COMPOSE_EMOJI_INSERT: case COMPOSE_EMOJI_INSERT:
return insertEmoji(state, action.position, action.emoji, action.needsSpace); return insertEmoji(state, action.emoji, action.needsSpace);
case COMPOSE_UPLOAD_CHANGE_SUCCESS: case COMPOSE_UPLOAD_CHANGE_SUCCESS:
return state return state
.set('is_changing_upload', false) .set('is_changing_upload', false)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

BIN
public/emoji/sheet_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 KiB