diff options
Diffstat (limited to 'app/javascript/flavours/glitch/features')
22 files changed, 592 insertions, 503 deletions
diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js index c3e6c987c..d64bee7ee 100644 --- a/app/javascript/flavours/glitch/features/composer/index.js +++ b/app/javascript/flavours/glitch/features/composer/index.js @@ -52,6 +52,7 @@ function mapStateToProps (state) { focusDate: state.getIn(['compose', 'focusDate']), isSubmitting: state.getIn(['compose', 'is_submitting']), isUploading: state.getIn(['compose', 'is_uploading']), + layout: state.getIn(['local_settings', 'layout']), media: state.getIn(['compose', 'media_attachments']), preselectDate: state.getIn(['compose', 'preselectDate']), privacy: state.getIn(['compose', 'privacy']), @@ -71,132 +72,96 @@ function mapStateToProps (state) { }; // Dispatch mapping. -const mapDispatchToProps = dispatch => ({ - cancelReply () { - dispatch(cancelReplyCompose()); - }, - changeDescription (mediaId, description) { - dispatch(changeUploadCompose(mediaId, description)); - }, - changeSensitivity () { - dispatch(changeComposeSensitivity()); - }, - changeSpoilerText (checked) { - dispatch(changeComposeSpoilerText(checked)); - }, - changeSpoilerness () { - dispatch(changeComposeSpoilerness()); - }, - changeText (text) { - dispatch(changeCompose(text)); - }, - changeVisibility (value) { - dispatch(changeComposeVisibility(value)); - }, - clearSuggestions () { - dispatch(clearComposeSuggestions()); - }, - closeModal () { - dispatch(closeModal()); - }, - fetchSuggestions (token) { - dispatch(fetchComposeSuggestions(token)); - }, - insertEmoji (position, data) { - dispatch(insertEmojiCompose(position, data)); - }, - openActionsModal (data) { - dispatch(openModal('ACTIONS', data)); - }, - openDoodleModal () { - dispatch(openModal('DOODLE', { noEsc: true })); - }, - selectSuggestion (position, token, accountId) { - dispatch(selectComposeSuggestion(position, token, accountId)); - }, - submit () { - dispatch(submitCompose()); - }, - toggleAdvancedOption (option) { - dispatch(toggleComposeAdvancedOption(option)); - }, - undoUpload (mediaId) { - dispatch(undoUploadCompose(mediaId)); - }, - upload (files) { - dispatch(uploadCompose(files)); - }, -}); +const mapDispatchToProps = { + onCancelReply: cancelReplyCompose, + onChangeDescription: changeUploadCompose, + onChangeSensitivity: changeComposeSensitivity, + onChangeSpoilerText: changeComposeSpoilerText, + onChangeSpoilerness: changeComposeSpoilerness, + onChangeText: changeCompose, + onChangeVisibility: changeComposeVisibility, + onClearSuggestions: clearComposeSuggestions, + onCloseModal: closeModal, + onFetchSuggestions: fetchComposeSuggestions, + onInsertEmoji: insertEmojiCompose, + onOpenActionsModal: openModal.bind(null, 'ACTIONS'), + onOpenDoodleModal: openModal.bind(null, 'DOODLE', { noEsc: true }), + onSelectSuggestion: selectComposeSuggestion, + onSubmit: submitCompose, + onToggleAdvancedOption: toggleComposeAdvancedOption, + onUndoUpload: undoUploadCompose, + onUpload: uploadCompose, +}; // Handlers. const handlers = { // Changes the text value of the spoiler. - changeSpoiler ({ target: { value } }) { - const { dispatch: { changeSpoilerText } } = this.props; - if (changeSpoilerText) { - changeSpoilerText(value); + handleChangeSpoiler ({ target: { value } }) { + const { onChangeSpoilerText } = this.props; + if (onChangeSpoilerText) { + onChangeSpoilerText(value); } }, // Inserts an emoji at the caret. - emoji (data) { + handleEmoji (data) { const { textarea: { selectionStart } } = this; - const { dispatch: { insertEmoji } } = this.props; + const { onInsertEmoji } = this.props; this.caretPos = selectionStart + data.native.length + 1; - if (insertEmoji) { - insertEmoji(selectionStart, data); + if (onInsertEmoji) { + onInsertEmoji(selectionStart, data); } }, // Handles the secondary submit button. - secondarySubmit () { - const { submit } = this.handlers; + handleSecondarySubmit () { + const { handleSubmit } = this.handlers; const { - dispatch: { changeVisibility }, - side_arm, + onChangeVisibility, + sideArm, } = this.props; - if (changeVisibility) { - changeVisibility(side_arm); + if (sideArm !== 'none' && onChangeVisibility) { + onChangeVisibility(sideArm); } - submit(); + handleSubmit(); }, // Selects a suggestion from the autofill. - select (tokenStart, token, value) { - const { dispatch: { selectSuggestion } } = this.props; + handleSelect (tokenStart, token, value) { + const { onSelectSuggestion } = this.props; this.caretPos = null; - if (selectSuggestion) { - selectSuggestion(tokenStart, token, value); + if (onSelectSuggestion) { + onSelectSuggestion(tokenStart, token, value); } }, // Submits the status. - submit () { + handleSubmit () { const { textarea: { value } } = this; const { - dispatch: { - changeText, - submit, - }, - state: { text }, + onChangeText, + onSubmit, + text, } = this.props; // If something changes inside the textarea, then we update the // state before submitting. - if (changeText && text !== value) { - changeText(value); + if (onChangeText && text !== value) { + onChangeText(value); } // Submits the status. - if (submit) { - submit(); + if (onSubmit) { + onSubmit(); } }, // Sets a reference to the textarea. - refTextarea ({ textarea }) { - this.textarea = textarea; + handleRefTextarea (textareaComponent) { + if (textareaComponent) { + this.textarea = textareaComponent.textarea; + } }, }; @@ -216,10 +181,10 @@ class Composer extends React.Component { // If this is the update where we've finished uploading, // save the last caret position so we can restore it below! componentWillReceiveProps (nextProps) { - const { textarea: { selectionStart } } = this; - const { state: { isUploading } } = this.props; - if (isUploading && !nextProps.state.isUploading) { - this.caretPos = selectionStart; + const { textarea } = this; + const { isUploading } = this.props; + if (textarea && isUploading && !nextProps.isUploading) { + this.caretPos = textarea.selectionStart; } } @@ -239,20 +204,18 @@ class Composer extends React.Component { textarea, } = this; const { - state: { - focusDate, - isUploading, - isSubmitting, - preselectDate, - text, - }, + focusDate, + isUploading, + isSubmitting, + preselectDate, + text, } = this.props; let selectionEnd, selectionStart; // Caret/selection handling. - if (focusDate !== prevProps.state.focusDate || (prevProps.state.isUploading && !isUploading && !isNaN(caretPos) && caretPos !== null)) { + if (focusDate !== prevProps.focusDate || (prevProps.isUploading && !isUploading && !isNaN(caretPos) && caretPos !== null)) { switch (true) { - case preselectDate !== prevProps.state.preselectDate: + case preselectDate !== prevProps.preselectDate: selectionStart = text.search(/\s/) + 1; selectionEnd = text.length; break; @@ -262,71 +225,71 @@ class Composer extends React.Component { default: selectionStart = selectionEnd = text.length; } - textarea.setSelectionRange(selectionStart, selectionEnd); - textarea.focus(); + if (textarea) { + textarea.setSelectionRange(selectionStart, selectionEnd); + textarea.focus(); + } // Refocuses the textarea after submitting. - } else if (prevProps.state.isSubmitting && !isSubmitting) { + } else if (textarea && prevProps.isSubmitting && !isSubmitting) { textarea.focus(); } } render () { const { - changeSpoiler, - emoji, - secondarySubmit, - select, - submit, - refTextarea, + handleChangeSpoiler, + handleEmoji, + handleSecondarySubmit, + handleSelect, + handleSubmit, + handleRefTextarea, } = this.handlers; const { history } = this.context; const { - dispatch: { - cancelReply, - changeDescription, - changeSensitivity, - changeText, - changeVisibility, - clearSuggestions, - closeModal, - fetchSuggestions, - openActionsModal, - openDoodleModal, - toggleAdvancedOption, - undoUpload, - upload, - }, + acceptContentTypes, + amUnlocked, + doNotFederate, intl, - state: { - acceptContentTypes, - amUnlocked, - doNotFederate, - isSubmitting, - isUploading, - media, - privacy, - progress, - replyAccount, - replyContent, - resetFileKey, - sensitive, - showSearch, - sideArm, - spoiler, - spoilerText, - suggestions, - text, - }, + isSubmitting, + isUploading, + layout, + media, + onCancelReply, + onChangeDescription, + onChangeSensitivity, + onChangeSpoilerness, + onChangeText, + onChangeVisibility, + onClearSuggestions, + onCloseModal, + onFetchSuggestions, + onOpenActionsModal, + onOpenDoodleModal, + onToggleAdvancedOption, + onUndoUpload, + onUpload, + privacy, + progress, + replyAccount, + replyContent, + resetFileKey, + sensitive, + showSearch, + sideArm, + spoiler, + spoilerText, + suggestions, + text, } = this.props; return ( - <div className='compose'> + <div className='composer'> <ComposerSpoiler hidden={!spoiler} intl={intl} - onChange={changeSpoiler} - onSubmit={submit} + onChange={handleChangeSpoiler} + onSubmit={handleSubmit} text={spoilerText} /> {privacy === 'private' && amUnlocked ? <ComposerWarning /> : null} @@ -336,32 +299,32 @@ class Composer extends React.Component { content={replyContent} history={history} intl={intl} - onCancel={cancelReply} + onCancel={onCancelReply} /> ) : null} <ComposerTextarea - autoFocus={!showSearch && !isMobile(window.innerWidth)} + autoFocus={!showSearch && !isMobile(window.innerWidth, layout)} disabled={isSubmitting} intl={intl} - onChange={changeText} - onPaste={upload} - onPickEmoji={emoji} - onSubmit={submit} - onSuggestionsClearRequested={clearSuggestions} - onSuggestionsFetchRequested={fetchSuggestions} - onSuggestionSelected={select} - ref={refTextarea} + onChange={onChangeText} + onPaste={onUpload} + onPickEmoji={handleEmoji} + onSubmit={handleSubmit} + onSuggestionsClearRequested={onClearSuggestions} + onSuggestionsFetchRequested={onFetchSuggestions} + onSuggestionSelected={handleSelect} + ref={handleRefTextarea} suggestions={suggestions} value={text} /> - {media && media.size ? ( + {isUploading || media && media.size ? ( <ComposerUploadForm - active={isUploading} intl={intl} media={media} - onChangeDescription={changeDescription} - onRemove={undoUpload} + onChangeDescription={onChangeDescription} + onRemove={onUndoUpload} progress={progress} + uploading={isUploading} /> ) : null} <ComposerOptions @@ -373,13 +336,14 @@ class Composer extends React.Component { )} hasMedia={!!media.size} intl={intl} - onChangeSensitivity={changeSensitivity} - onChangeVisibility={changeVisibility} - onDoodleOpen={openDoodleModal} - onModalClose={closeModal} - onModalOpen={openActionsModal} - onToggleAdvancedOption={toggleAdvancedOption} - onUpload={upload} + onChangeSensitivity={onChangeSensitivity} + onChangeVisibility={onChangeVisibility} + onDoodleOpen={onOpenDoodleModal} + onModalClose={onCloseModal} + onModalOpen={onOpenActionsModal} + onToggleAdvancedOption={onToggleAdvancedOption} + onToggleSpoiler={onChangeSpoilerness} + onUpload={onUpload} privacy={privacy} resetFileKey={resetFileKey} sensitive={sensitive} @@ -387,10 +351,10 @@ class Composer extends React.Component { /> <ComposerPublisher countText={`${spoilerText}${countableText(text)}${doNotFederate ? ' 👁️' : ''}`} - disabled={isSubmitting || isUploading || text.length && text.trim().length === 0} + disabled={isSubmitting || isUploading || !!text.length && !text.trim().length} intl={intl} - onSecondarySubmit={secondarySubmit} - onSubmit={submit} + onSecondarySubmit={handleSecondarySubmit} + onSubmit={handleSubmit} privacy={privacy} sideArm={sideArm} /> @@ -407,37 +371,51 @@ Composer.contextTypes = { // Props. Composer.propTypes = { - dispatch: PropTypes.objectOf(PropTypes.func).isRequired, intl: PropTypes.object.isRequired, - state: PropTypes.shape({ - acceptContentTypes: PropTypes.string, - amUnlocked: PropTypes.bool, - doNotFederate: PropTypes.bool, - focusDate: PropTypes.instanceOf(Date), - isSubmitting: PropTypes.bool, - isUploading: PropTypes.bool, - media: PropTypes.list, - preselectDate: PropTypes.instanceOf(Date), - privacy: PropTypes.string, - progress: PropTypes.number, - replyAccount: ImmutablePropTypes.map, - replyContent: PropTypes.string, - resetFileKey: PropTypes.string, - sideArm: PropTypes.string, - sensitive: PropTypes.bool, - showSearch: PropTypes.bool, - spoiler: PropTypes.bool, - spoilerText: PropTypes.string, - suggestionToken: PropTypes.string, - suggestions: ImmutablePropTypes.list, - text: PropTypes.string, - }).isRequired, -}; -// Default props. -Composer.defaultProps = { - dispatch: {}, - state: {}, + // State props. + acceptContentTypes: PropTypes.string, + amUnlocked: PropTypes.bool, + doNotFederate: PropTypes.bool, + focusDate: PropTypes.instanceOf(Date), + isSubmitting: PropTypes.bool, + isUploading: PropTypes.bool, + layout: PropTypes.string, + media: ImmutablePropTypes.list, + preselectDate: PropTypes.instanceOf(Date), + privacy: PropTypes.string, + progress: PropTypes.number, + replyAccount: ImmutablePropTypes.map, + replyContent: PropTypes.string, + resetFileKey: PropTypes.number, + sideArm: PropTypes.string, + sensitive: PropTypes.bool, + showSearch: PropTypes.bool, + spoiler: PropTypes.bool, + spoilerText: PropTypes.string, + suggestionToken: PropTypes.string, + suggestions: ImmutablePropTypes.list, + text: PropTypes.string, + + // Dispatch props. + onCancelReply: PropTypes.func, + onChangeDescription: PropTypes.func, + onChangeSensitivity: PropTypes.func, + onChangeSpoilerText: PropTypes.func, + onChangeSpoilerness: PropTypes.func, + onChangeText: PropTypes.func, + onChangeVisibility: PropTypes.func, + onClearSuggestions: PropTypes.func, + onCloseModal: PropTypes.func, + onFetchSuggestions: PropTypes.func, + onInsertEmoji: PropTypes.func, + onOpenActionsModal: PropTypes.func, + onOpenDoodleModal: PropTypes.func, + onSelectSuggestion: PropTypes.func, + onSubmit: PropTypes.func, + onToggleAdvancedOption: PropTypes.func, + onUndoUpload: PropTypes.func, + onUpload: PropTypes.func, }; // Connecting and export. diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js new file mode 100644 index 000000000..28bdfc0db --- /dev/null +++ b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js @@ -0,0 +1,138 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import spring from 'react-motion/lib/spring'; + +// Components. +import ComposerOptionsDropdownContentItem from './item'; + +// Utils. +import { withPassive } from 'flavours/glitch/util/dom_helpers'; +import Motion from 'flavours/glitch/util/optional_motion'; +import { assignHandlers } from 'flavours/glitch/util/react_helpers'; + +// Handlers. +const handlers = { + + // When the document is clicked elsewhere, we close the dropdown. + handleDocumentClick ({ target }) { + const { node } = this; + const { onClose } = this.props; + if (onClose && node && !node.contains(target)) { + onClose(); + } + }, + + // Stores our node in `this.node`. + handleRef (node) { + this.node = node; + }, +}; + +// The spring to use with our motion. +const springMotion = spring(1, { + damping: 35, + stiffness: 400, +}); + +// The component. +export default class ComposerOptionsDropdownContent extends React.PureComponent { + + // Constructor. + constructor (props) { + super(props); + assignHandlers(this, handlers); + + // Instance variables. + this.node = null; + } + + // On mounting, we add our listeners. + componentDidMount () { + const { handleDocumentClick } = this.handlers; + document.addEventListener('click', handleDocumentClick, false); + document.addEventListener('touchend', handleDocumentClick, withPassive); + } + + // On unmounting, we remove our listeners. + componentWillUnmount () { + const { handleDocumentClick } = this.handlers; + document.removeEventListener('click', handleDocumentClick, false); + document.removeEventListener('touchend', handleDocumentClick, withPassive); + } + + // Rendering. + render () { + const { handleRef } = this.handlers; + const { + items, + onChange, + onClose, + style, + value, + } = this.props; + + // The result. + return ( + <Motion + defaultStyle={{ + opacity: 0, + scaleX: 0.85, + scaleY: 0.75, + }} + style={{ + opacity: springMotion, + scaleX: springMotion, + scaleY: springMotion, + }} + > + {({ opacity, scaleX, scaleY }) => ( + <div + className='composer--options--dropdown--content' + ref={handleRef} + style={{ + ...style, + opacity: opacity, + transform: `scale(${scaleX}, ${scaleY})`, + }} + > + {items.map( + ({ + name, + ...rest + }) => ( + <ComposerOptionsDropdownContentItem + active={name === value} + key={name} + name={name} + onChange={onChange} + onClose={onClose} + options={rest} + /> + ) + )} + </div> + )} + </Motion> + ); + } + +} + +// Props. +ComposerOptionsDropdownContent.propTypes = { + items: PropTypes.arrayOf(PropTypes.shape({ + icon: PropTypes.string, + meta: PropTypes.node, + name: PropTypes.string.isRequired, + on: PropTypes.bool, + text: PropTypes.node, + })).isRequired, + onChange: PropTypes.func, + onClose: PropTypes.func, + style: PropTypes.object, + value: PropTypes.string, +}; + +// Default props. +ComposerOptionsDropdownContent.defaultProps = { style: {} }; diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/item/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js index e9047dc50..605c945bd 100644 --- a/app/javascript/flavours/glitch/features/composer/options/dropdown/item/index.js +++ b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js @@ -14,7 +14,7 @@ import { assignHandlers } from 'flavours/glitch/util/react_helpers'; const handlers = { // This function activates the dropdown item. - activate (e) { + handleActivate (e) { const { name, onChange, @@ -35,11 +35,10 @@ const handlers = { onChange(name); } }, - }; // The component. -export default class ComposerOptionsDropdownItem extends React.PureComponent { +export default class ComposerOptionsDropdownContentItem extends React.PureComponent { // Constructor. constructor (props) { @@ -49,7 +48,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent { // Rendering. render () { - const { activate } = this.handlers; + const { handleActivate } = this.handlers; const { active, options: { @@ -59,7 +58,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent { text, }, } = this.props; - const computedClass = classNames('composer--options--dropdown_item', { + const computedClass = classNames('composer--options--dropdown--content--item', { active, lengthy: meta, 'toggled-off': !on && on !== null && typeof on !== 'undefined', @@ -71,8 +70,8 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent { return ( <div className={computedClass} - onClick={activate} - onKeyDown={activate} + onClick={handleActivate} + onKeyDown={handleActivate} role='button' tabIndex='0' > @@ -85,7 +84,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent { return ( <Toggle checked={on} - onChange={activate} + onChange={handleActivate} /> ); case !!icon: @@ -113,7 +112,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent { }; // Props. -ComposerOptionsDropdownItem.propTypes = { +ComposerOptionsDropdownContentItem.propTypes = { active: PropTypes.bool, name: PropTypes.string, onChange: PropTypes.func, diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js index daed4ec8a..d63d90a9f 100644 --- a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js +++ b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js @@ -2,108 +2,120 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; -import spring from 'react-motion/lib/spring'; import Overlay from 'react-overlays/lib/Overlay'; // Components. import IconButton from 'flavours/glitch/components/icon_button'; -import ComposerOptionsDropdownItem from './item'; +import ComposerOptionsDropdownContent from './content'; // Utils. -import { withPassive } from 'flavours/glitch/util/dom_helpers'; import { isUserTouching } from 'flavours/glitch/util/is_mobile'; -import Motion from 'flavours/glitch/util/optional_motion'; import { assignHandlers } from 'flavours/glitch/util/react_helpers'; -// We'll use this to define our various transitions. -const springMotion = spring(1, { - damping: 35, - stiffness: 400, -}); - // Handlers. const handlers = { // Closes the dropdown. - close () { + handleClose () { this.setState({ open: false }); }, - // When the document is clicked elsewhere, we close the dropdown. - documentClick ({ target }) { - const { node } = this; - const { onClose } = this.props; - if (onClose && node && !node.contains(target)) { - onClose(); - } - }, - // The enter key toggles the dropdown's open state, and the escape // key closes it. - keyDown ({ key }) { + handleKeyDown ({ key }) { const { - close, - toggle, + handleClose, + handleToggle, } = this.handlers; switch (key) { case 'Enter': - toggle(); + handleToggle(); break; case 'Escape': - close(); + handleClose(); break; } }, - // Toggles opening and closing the dropdown. - toggle () { + // Creates an action modal object. + handleMakeModal () { + const component = this; const { items, onChange, - onModalClose, onModalOpen, + onModalClose, value, } = this.props; + + // Required props. + if (!(onChange && onModalOpen && onModalClose && items)) { + return null; + } + + // The object. + return { + actions: items.map( + ({ + name, + ...rest + }) => ({ + ...rest, + active: value && name === value, + name, + onClick (e) { + e.preventDefault(); // Prevents focus from changing + onModalClose(); + onChange(name); + }, + onPassiveClick (e) { + e.preventDefault(); // Prevents focus from changing + onChange(name); + component.setState({ needsModalUpdate: true }); + }, + }) + ), + }; + }, + + // Toggles opening and closing the dropdown. + handleToggle () { + const { handleMakeModal } = this.handlers; + const { onModalOpen } = this.props; const { open } = this.state; // If this is a touch device, we open a modal instead of the // dropdown. - if (onModalClose && isUserTouching()) { - if (open) { - onModalClose(); - } else if (onChange && onModalOpen) { - onModalOpen({ - actions: items.map( - ({ - name, - ...rest - }) => ({ - ...rest, - active: value && name === value, - name, - onClick (e) { - e.preventDefault(); // Prevents focus from changing - onModalClose(); - onChange(name); - }, - onPassiveClick (e) { - e.preventDefault(); // Prevents focus from changing - onChange(name); - }, - }) - ), - }); + if (isUserTouching()) { + + // This gets the modal to open. + const modal = handleMakeModal(); + + // If we can, we then open the modal. + if (modal && onModalOpen) { + onModalOpen(modal); + return; } + } // Otherwise, we just set our state to open. - } else { - this.setState({ open: !open }); - } + this.setState({ open: !open }); }, - // Stores our node in `this.node`. - ref (node) { - this.node = node; + // If our modal is open and our props update, we need to also update + // the modal. + handleUpdate () { + const { handleMakeModal } = this.handlers; + const { onModalOpen } = this.props; + const { needsModalUpdate } = this.state; + + // Gets our modal object. + const modal = handleMakeModal(); + + // Reopens the modal with the new object. + if (needsModalUpdate && modal && onModalOpen) { + onModalOpen(modal); + } }, }; @@ -114,33 +126,31 @@ export default class ComposerOptionsDropdown extends React.PureComponent { constructor (props) { super(props); assignHandlers(this, handlers); - this.state = { open: false }; - - // Instance variables. - this.node = null; + this.state = { + needsModalUpdate: false, + open: false, + }; } - // On mounting, we add our listeners. - componentDidMount () { - const { documentClick } = this.handlers; - document.addEventListener('click', documentClick, false); - document.addEventListener('touchend', documentClick, withPassive); - } - - // On unmounting, we remove our listeners. - componentWillUnmount () { - const { documentClick } = this.handlers; - document.removeEventListener('click', documentClick, false); - document.removeEventListener('touchend', documentClick, withPassive); + // Updates our modal as necessary. + componentDidUpdate (prevProps) { + const { handleUpdate } = this.handlers; + const { items } = this.props; + const { needsModalUpdate } = this.state; + if (needsModalUpdate && items.find( + (item, i) => item.on !== prevProps.items[i].on + )) { + handleUpdate(); + this.setState({ needsModalUpdate: false }); + } } // Rendering. render () { const { - close, - keyDown, - ref, - toggle, + handleClose, + handleKeyDown, + handleToggle, } = this.handlers; const { active, @@ -154,22 +164,21 @@ export default class ComposerOptionsDropdown extends React.PureComponent { const { open } = this.state; const computedClass = classNames('composer--options--dropdown', { active, - open: open || active, + open, }); // The result. return ( <div className={computedClass} - onKeyDown={keyDown} - ref={ref} + onKeyDown={handleKeyDown} > <IconButton active={open || active} className='value' disabled={disabled} icon={icon} - onClick={toggle} + onClick={handleToggle} size={18} style={{ height: null, @@ -178,49 +187,17 @@ export default class ComposerOptionsDropdown extends React.PureComponent { title={title} /> <Overlay + containerPadding={20} placement='bottom' show={open} target={this} > - <Motion - defaultStyle={{ - opacity: 0, - scaleX: 0.85, - scaleY: 0.75, - }} - style={{ - opacity: springMotion, - scaleX: springMotion, - scaleY: springMotion, - }} - > - {({ opacity, scaleX, scaleY }) => ( - <div - className='composer--options--dropdown__dropdown' - ref={this.setRef} - style={{ - opacity: opacity, - transform: `scale(${scaleX}, ${scaleY})`, - }} - > - {items.map( - ({ - name, - ...rest - }) => ( - <ComposerOptionsDropdownItem - active={name === value} - key={name} - name={name} - onChange={onChange} - onClose={close} - options={rest} - /> - ) - )} - </div> - )} - </Motion> + <ComposerOptionsDropdownContent + items={items} + onChange={onChange} + onClose={handleClose} + value={value} + /> </Overlay> </div> ); diff --git a/app/javascript/flavours/glitch/features/composer/options/index.js b/app/javascript/flavours/glitch/features/composer/options/index.js index ea998a421..e805372ab 100644 --- a/app/javascript/flavours/glitch/features/composer/options/index.js +++ b/app/javascript/flavours/glitch/features/composer/options/index.js @@ -95,7 +95,7 @@ const messages = defineMessages({ const handlers = { // Handles file selection. - changeFiles ({ target: { files } }) { + handleChangeFiles ({ target: { files } }) { const { onUpload } = this.props; if (files.length && onUpload) { onUpload(files); @@ -103,7 +103,7 @@ const handlers = { }, // Handles attachment clicks. - clickAttach (name) { + handleClickAttach (name) { const { fileElement } = this; const { onDoodleOpen } = this.props; @@ -123,7 +123,7 @@ const handlers = { }, // Handles a ref to the file input. - refFileElement (fileElement) { + handleRefFileElement (fileElement) { this.fileElement = fileElement; }, }; @@ -143,9 +143,9 @@ export default class ComposerOptions extends React.PureComponent { // Rendering. render () { const { - changeFiles, - clickAttach, - refFileElement, + handleChangeFiles, + handleClickAttach, + handleRefFileElement, } = this.handlers; const { acceptContentTypes, @@ -159,6 +159,7 @@ export default class ComposerOptions extends React.PureComponent { onModalClose, onModalOpen, onToggleAdvancedOption, + onToggleSpoiler, privacy, resetFileKey, sensitive, @@ -201,8 +202,8 @@ export default class ComposerOptions extends React.PureComponent { accept={acceptContentTypes} disabled={disabled || full} key={resetFileKey} - onChange={changeFiles} - ref={refFileElement} + onChange={handleChangeFiles} + ref={handleRefFileElement} type='file' {...hiddenComponent} /> @@ -221,10 +222,10 @@ export default class ComposerOptions extends React.PureComponent { text: <FormattedMessage {...messages.doodle} />, }, ]} - onChange={clickAttach} + onChange={handleClickAttach} onModalClose={onModalClose} onModalOpen={onModalOpen} - title={messages.attach} + title={intl.formatMessage(messages.attach)} /> <Motion defaultStyle={{ scale: 0.87 }} @@ -279,6 +280,7 @@ export default class ComposerOptions extends React.PureComponent { active={spoiler} ariaControls='glitch.composer.spoiler.input' label='CW' + onClick={onToggleSpoiler} title={intl.formatMessage(messages.spoiler)} /> <Dropdown @@ -318,9 +320,10 @@ ComposerOptions.propTypes = { onModalClose: PropTypes.func, onModalOpen: PropTypes.func, onToggleAdvancedOption: PropTypes.func, + onToggleSpoiler: PropTypes.func, onUpload: PropTypes.func, privacy: PropTypes.string, - resetFileKey: PropTypes.string, + resetFileKey: PropTypes.number, sensitive: PropTypes.bool, spoiler: PropTypes.bool, }; diff --git a/app/javascript/flavours/glitch/features/composer/publisher/index.js b/app/javascript/flavours/glitch/features/composer/publisher/index.js index 79337100f..f54fd68b7 100644 --- a/app/javascript/flavours/glitch/features/composer/publisher/index.js +++ b/app/javascript/flavours/glitch/features/composer/publisher/index.js @@ -46,10 +46,13 @@ export default function ComposerPublisher ({ // The result. return ( <div className={computedClass}> - <span class='count'>{diff}</span> + <span className='count'>{diff}</span> {sideArm && sideArm !== 'none' ? ( <Button className='side_arm' + disabled={disabled || diff < 0} + onClick={onSecondarySubmit} + style={{ padding: null }} text={ <span> <Icon @@ -63,8 +66,6 @@ export default function ComposerPublisher ({ </span> } title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${sideArm}.short` })}`} - onClick={onSecondarySubmit} - disabled={disabled || diff < 0} /> ) : null} <Button diff --git a/app/javascript/flavours/glitch/features/composer/reply/index.js b/app/javascript/flavours/glitch/features/composer/reply/index.js index 4a52cddb4..568705aff 100644 --- a/app/javascript/flavours/glitch/features/composer/reply/index.js +++ b/app/javascript/flavours/glitch/features/composer/reply/index.js @@ -25,7 +25,7 @@ const messages = defineMessages({ const handlers = { // Handles a click on the "close" button. - click () { + handleClick () { const { onCancel } = this.props; if (onCancel) { onCancel(); @@ -33,7 +33,7 @@ const handlers = { }, // Handles a click on the status's account. - clickAccount () { + handleClickAccount () { const { account, history, @@ -56,8 +56,8 @@ export default class ComposerReply extends React.PureComponent { // Rendering. render () { const { - click, - clickAccount, + handleClick, + handleClickAccount, } = this.handlers; const { account, @@ -72,14 +72,14 @@ export default class ComposerReply extends React.PureComponent { <IconButton className='cancel' icon='times' - onClick={click} + onClick={handleClick} title={intl.formatMessage(messages.cancel)} /> {account ? ( <a className='account' href={account.get('url')} - onClick={clickAccount} + onClick={handleClickAccount} > <Avatar account={account} diff --git a/app/javascript/flavours/glitch/features/composer/spoiler/index.js b/app/javascript/flavours/glitch/features/composer/spoiler/index.js index 730ab2205..a49b0e10f 100644 --- a/app/javascript/flavours/glitch/features/composer/spoiler/index.js +++ b/app/javascript/flavours/glitch/features/composer/spoiler/index.js @@ -24,7 +24,7 @@ const messages = defineMessages({ const handlers = { // Handles a keypress. - keyDown ({ + handleKeyDown ({ ctrlKey, keyCode, metaKey, @@ -49,7 +49,7 @@ export default class ComposerSpoiler extends React.PureComponent { // Rendering. render () { - const { keyDown } = this.handlers; + const { handleKeyDown } = this.handlers; const { hidden, intl, @@ -70,7 +70,7 @@ export default class ComposerSpoiler extends React.PureComponent { <input id='glitch.composer.spoiler.input' onChange={onChange} - onKeyDown={keyDown} + onKeyDown={handleKeyDown} placeholder={intl.formatMessage(messages.placeholder)} type='text' value={text} diff --git a/app/javascript/flavours/glitch/features/composer/textarea/index.js b/app/javascript/flavours/glitch/features/composer/textarea/index.js index 2299757df..1b6f79bba 100644 --- a/app/javascript/flavours/glitch/features/composer/textarea/index.js +++ b/app/javascript/flavours/glitch/features/composer/textarea/index.js @@ -31,14 +31,14 @@ const messages = defineMessages({ const handlers = { // When blurring the textarea, suggestions are hidden. - blur () { + handleBlur () { this.setState({ suggestionsHidden: true }); }, // When the contents of the textarea change, we have to pull up new // autosuggest suggestions if applicable, and also change the value // of the textarea in our store. - change ({ + handleChange ({ target: { selectionStart, value, @@ -91,7 +91,7 @@ const handlers = { }, // Handles a click on an autosuggestion. - clickSuggestion (index) { + handleClickSuggestion (index) { const { textarea } = this; const { onSuggestionSelected, @@ -107,7 +107,7 @@ const handlers = { // Handles a keypress. If the autosuggestions are visible, we need // to allow keypresses to navigate and sleect them. - keyDown (e) { + handleKeyDown (e) { const { disabled, onSubmit, @@ -165,7 +165,7 @@ const handlers = { // When the escape key is released, we either close the suggestions // window or focus the UI. - keyUp ({ key }) { + handleKeyUp ({ key }) { const { suggestionsHidden } = this.state; if (key === 'Escape') { if (!suggestionsHidden) { @@ -177,7 +177,7 @@ const handlers = { }, // Handles the pasting of images into the composer. - paste (e) { + handlePaste (e) { const { onPaste } = this.props; let d; if (onPaste && (d = e.clipboardData) && (d = d.files).length === 1) { @@ -187,7 +187,7 @@ const handlers = { }, // Saves a reference to the textarea. - refTextarea (textarea) { + handleRefTextarea (textarea) { this.textarea = textarea; }, }; @@ -223,13 +223,13 @@ export default class ComposerTextarea extends React.Component { // Rendering. render () { const { - blur, - change, - clickSuggestion, - keyDown, - keyUp, - paste, - refTextarea, + handleBlur, + handleChange, + handleClickSuggestion, + handleKeyDown, + handleKeyUp, + handlePaste, + handleRefTextarea, } = this.handlers; const { autoFocus, @@ -254,12 +254,12 @@ export default class ComposerTextarea extends React.Component { autoFocus={autoFocus} className='textarea' disabled={disabled} - inputRef={refTextarea} - onBlur={blur} - onChange={change} - onKeyDown={keyDown} - onKeyUp={keyUp} - onPaste={paste} + inputRef={handleRefTextarea} + onBlur={handleBlur} + onChange={handleChange} + onKeyDown={handleKeyDown} + onKeyUp={handleKeyUp} + onPaste={handlePaste} placeholder={intl.formatMessage(messages.placeholder)} value={value} style={{ direction: isRtl(value) ? 'rtl' : 'ltr' }} @@ -268,7 +268,7 @@ export default class ComposerTextarea extends React.Component { <EmojiPicker onPickEmoji={onPickEmoji} /> <ComposerTextareaSuggestions hidden={suggestionsHidden} - onSuggestionClick={clickSuggestion} + onSuggestionClick={handleClickSuggestion} suggestions={suggestions} value={selectedSuggestion} /> diff --git a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js b/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js index 65ed2c18a..dc72585f2 100644 --- a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js +++ b/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js @@ -18,9 +18,9 @@ export default function ComposerTextareaSuggestions ({ return ( <div className='composer--textarea--suggestions' - hidden={hidden || suggestions.isEmpty()} + hidden={hidden || !suggestions || suggestions.isEmpty()} > - {!hidden ? suggestions.map( + {!hidden && suggestions ? suggestions.map( (suggestion, index) => ( <ComposerTextareaSuggestionsItem index={index} @@ -39,5 +39,5 @@ ComposerTextareaSuggestions.propTypes = { hidden: PropTypes.bool, onSuggestionClick: PropTypes.func, suggestions: ImmutablePropTypes.list, - value: PropTypes.string, + value: PropTypes.number, }; diff --git a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js b/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js index b78f99ee9..dc057e679 100644 --- a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js +++ b/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js @@ -17,7 +17,7 @@ const assetHost = ((process || {}).env || {}).CDN_HOST || ''; const handlers = { // Handles a click on a suggestion. - click (e) { + handleClick (e) { const { index, onClick, @@ -40,7 +40,7 @@ export default class ComposerTextareaSuggestionsItem extends React.Component { // Rendering. render () { - const { click } = this.handlers; + const { handleClick } = this.handlers; const { selected, suggestion, @@ -51,7 +51,7 @@ export default class ComposerTextareaSuggestionsItem extends React.Component { return ( <div className={computedClass} - onMouseDown={click} + onMouseDown={handleClick} role='button' tabIndex='0' > diff --git a/app/javascript/flavours/glitch/features/composer/upload_form/index.js b/app/javascript/flavours/glitch/features/composer/upload_form/index.js index ab46a3046..53b14acc7 100644 --- a/app/javascript/flavours/glitch/features/composer/upload_form/index.js +++ b/app/javascript/flavours/glitch/features/composer/upload_form/index.js @@ -10,45 +10,44 @@ import ComposerUploadFormProgress from './progress'; // The component. export default function ComposerUploadForm ({ - active, intl, media, onChangeDescription, onRemove, progress, + uploading, }) { - const computedClass = classNames('composer--upload_form', { uploading: active }); - - // We need `media` in order to be able to render. - if (!media) { - return null; - } + const computedClass = classNames('composer--upload_form', { uploading }); // The result. return ( <div className={computedClass}> - {active ? <ComposerUploadFormProgress progress={progress} /> : null} - {media.map(item => ( - <ComposerUploadFormItem - description={item.get('description')} - key={item.get('id')} - id={item.get('id')} - intl={intl} - preview={item.get('preview_url')} - onChangeDescription={onChangeDescription} - onRemove={onRemove} - /> - ))} + {uploading ? <ComposerUploadFormProgress progress={progress} /> : null} + {media ? ( + <div className='content'> + {media.map(item => ( + <ComposerUploadFormItem + description={item.get('description')} + key={item.get('id')} + id={item.get('id')} + intl={intl} + preview={item.get('preview_url')} + onChangeDescription={onChangeDescription} + onRemove={onRemove} + /> + ))} + </div> + ) : null} </div> ); } // Props. ComposerUploadForm.propTypes = { - active: PropTypes.bool, intl: PropTypes.object.isRequired, media: ImmutablePropTypes.list, onChangeDescription: PropTypes.func, onRemove: PropTypes.func, progress: PropTypes.number, + uploading: PropTypes.bool, }; diff --git a/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js b/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js index cbec5ecd9..ec67b8ef8 100644 --- a/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js +++ b/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js @@ -31,7 +31,7 @@ const messages = defineMessages({ const handlers = { // On blur, we save the description for the media item. - blur () { + handleBlur () { const { id, onChangeDescription, @@ -48,27 +48,27 @@ const handlers = { // When the value of our description changes, we store it in the // temp value `dirtyDescription` in our state. - change ({ target: { value } }) { + handleChange ({ target: { value } }) { this.setState({ dirtyDescription: value }); }, // Records focus on the media item. - focus () { + handleFocus () { this.setState({ focused: true }); }, // Records the start of a hover over the media item. - mouseEnter () { + handleMouseEnter () { this.setState({ hovered: true }); }, // Records the end of a hover over the media item. - mouseLeave () { + handleMouseLeave () { this.setState({ hovered: false }); }, // Removes the media item. - remove () { + handleRemove () { const { id, onRemove, @@ -85,7 +85,7 @@ export default class ComposerUploadFormItem extends React.PureComponent { // Constructor. constructor (props) { super(props); - assignHandlers(handlers); + assignHandlers(this, handlers); this.state = { hovered: false, focused: false, @@ -96,12 +96,12 @@ export default class ComposerUploadFormItem extends React.PureComponent { // Rendering. render () { const { - blur, - change, - focus, - mouseEnter, - mouseLeave, - remove, + handleBlur, + handleChange, + handleFocus, + handleMouseEnter, + handleMouseLeave, + handleRemove, } = this.handlers; const { description, @@ -119,8 +119,8 @@ export default class ComposerUploadFormItem extends React.PureComponent { return ( <div className={computedClass} - onMouseEnter={mouseEnter} - onMouseLeave={mouseLeave} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} > <Motion defaultStyle={{ scale: 0.8 }} @@ -141,7 +141,7 @@ export default class ComposerUploadFormItem extends React.PureComponent { <IconButton className='close' icon='times' - onClick={remove} + onClick={handleRemove} size={36} title={intl.formatMessage(messages.undo)} /> @@ -149,9 +149,9 @@ export default class ComposerUploadFormItem extends React.PureComponent { <span style={{ display: 'none' }}><FormattedMessage {...messages.description} /></span> <input maxLength={420} - onBlur={blur} - onChange={change} - onFocus={focus} + onBlur={handleBlur} + onChange={handleChange} + onFocus={handleFocus} placeholder={intl.formatMessage(messages.description)} type='text' value={dirtyDescription || description || ''} @@ -169,7 +169,7 @@ export default class ComposerUploadFormItem extends React.PureComponent { // Props. ComposerUploadFormItem.propTypes = { description: PropTypes.string, - id: PropTypes.number, + id: PropTypes.string, intl: PropTypes.object.isRequired, onChangeDescription: PropTypes.func, onRemove: PropTypes.func, diff --git a/app/javascript/flavours/glitch/features/drawer/account/index.js b/app/javascript/flavours/glitch/features/drawer/account/index.js index 9afe8ba3e..168d0c2cf 100644 --- a/app/javascript/flavours/glitch/features/drawer/account/index.js +++ b/app/javascript/flavours/glitch/features/drawer/account/index.js @@ -27,7 +27,7 @@ export default function DrawerAccount ({ account }) { // We need an account to render. if (!account) { return ( - <div className='drawer--pager--account'> + <div className='drawer--account'> <a className='edit' href='/settings/profile' @@ -40,7 +40,7 @@ export default function DrawerAccount ({ account }) { // The result. return ( - <div className='drawer--pager--account'> + <div className='drawer--account'> <Permalink className='avatar' href={account.get('url')} @@ -67,4 +67,5 @@ export default function DrawerAccount ({ account }) { ); } +// Props. DrawerAccount.propTypes = { account: ImmutablePropTypes.map }; diff --git a/app/javascript/flavours/glitch/features/drawer/header/index.js b/app/javascript/flavours/glitch/features/drawer/header/index.js index fd79b6e18..6949cd028 100644 --- a/app/javascript/flavours/glitch/features/drawer/header/index.js +++ b/app/javascript/flavours/glitch/features/drawer/header/index.js @@ -51,7 +51,7 @@ export default function DrawerHeader ({ }) { // Only renders the component if the column isn't being shown. - const renderForColumn = conditionalRender.bind( + const renderForColumn = conditionalRender.bind(null, columnId => !columns || !columns.some( column => column.get('id') === columnId ) @@ -110,6 +110,7 @@ export default function DrawerHeader ({ ); } +// Props. DrawerHeader.propTypes = { columns: ImmutablePropTypes.list, intl: PropTypes.object, diff --git a/app/javascript/flavours/glitch/features/drawer/index.js b/app/javascript/flavours/glitch/features/drawer/index.js index d184dfd9b..9ade1f87a 100644 --- a/app/javascript/flavours/glitch/features/drawer/index.js +++ b/app/javascript/flavours/glitch/features/drawer/index.js @@ -34,23 +34,13 @@ const mapStateToProps = state => ({ }); // Dispatch mapping. -const mapDispatchToProps = dispatch => ({ - change (value) { - dispatch(changeSearch(value)); - }, - clear () { - dispatch(clearSearch()); - }, - show () { - dispatch(showSearch()); - }, - submit () { - dispatch(submitSearch()); - }, - openSettings () { - dispatch(openModal('SETTINGS', {})); - }, -}); +const mapDispatchToProps = { + onChange: changeSearch, + onClear: clearSearch, + onShow: showSearch, + onSubmit: submitSearch, + onOpenSettings: openModal.bind(null, 'SETTINGS', {}), +}; // The component. class Drawer extends React.Component { @@ -63,23 +53,19 @@ class Drawer extends React.Component { // Rendering. render () { const { - dispatch: { - change, - clear, - openSettings, - show, - submit, - }, + account, + columns, intl, multiColumn, - state: { - account, - columns, - results, - searchHidden, - searchValue, - submitted, - }, + onChange, + onClear, + onOpenSettings, + onShow, + onSubmit, + results, + searchHidden, + searchValue, + submitted, } = this.props; // The result. @@ -89,15 +75,15 @@ class Drawer extends React.Component { <DrawerHeader columns={columns} intl={intl} - onSettingsClick={openSettings} + onSettingsClick={onOpenSettings} /> ) : null} <DrawerSearch intl={intl} - onChange={change} - onClear={clear} - onShow={show} - onSubmit={submit} + onChange={onChange} + onClear={onClear} + onShow={onShow} + onSubmit={onSubmit} submitted={submitted} value={searchValue} /> @@ -117,23 +103,23 @@ class Drawer extends React.Component { // Props. Drawer.propTypes = { - dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, multiColumn: PropTypes.bool, - state: PropTypes.shape({ - account: ImmutablePropTypes.map, - columns: ImmutablePropTypes.list, - results: ImmutablePropTypes.map, - searchHidden: PropTypes.bool, - searchValue: PropTypes.string, - submitted: PropTypes.bool, - }).isRequired, -}; -// Default props. -Drawer.defaultProps = { - dispatch: {}, - state: {}, + // State props. + account: ImmutablePropTypes.map, + columns: ImmutablePropTypes.list, + results: ImmutablePropTypes.map, + searchHidden: PropTypes.bool, + searchValue: PropTypes.string, + submitted: PropTypes.bool, + + // Dispatch props. + onChange: PropTypes.func, + onClear: PropTypes.func, + onShow: PropTypes.func, + onSubmit: PropTypes.func, + onOpenSettings: PropTypes.func, }; // Connecting and export. diff --git a/app/javascript/flavours/glitch/features/drawer/results/index.js b/app/javascript/flavours/glitch/features/drawer/results/index.js index 559d56da5..f2a79eb59 100644 --- a/app/javascript/flavours/glitch/features/drawer/results/index.js +++ b/app/javascript/flavours/glitch/features/drawer/results/index.js @@ -25,7 +25,7 @@ const messages = defineMessages({ }); // The component. -export default function DrawerPager ({ +export default function DrawerResults ({ results, visible, }) { @@ -33,6 +33,7 @@ export default function DrawerPager ({ const statuses = results ? results.get('statuses') : null; const hashtags = results ? results.get('hashtags') : null; + // This gets the total number of items. const count = [accounts, statuses, hashtags].reduce(function (size, item) { if (item && item.size) { return size + item.size; @@ -108,7 +109,8 @@ export default function DrawerPager ({ ); } -DrawerPager.propTypes = { +// Props. +DrawerResults.propTypes = { results: ImmutablePropTypes.map, visible: PropTypes.bool, }; diff --git a/app/javascript/flavours/glitch/features/drawer/search/index.js b/app/javascript/flavours/glitch/features/drawer/search/index.js index ed69f71ed..2d739349c 100644 --- a/app/javascript/flavours/glitch/features/drawer/search/index.js +++ b/app/javascript/flavours/glitch/features/drawer/search/index.js @@ -30,18 +30,18 @@ const messages = defineMessages({ // Handlers. const handlers = { - blur () { + handleBlur () { this.setState({ expanded: false }); }, - change ({ target: { value } }) { + handleChange ({ target: { value } }) { const { onChange } = this.props; if (onChange) { onChange(value); } }, - clear (e) { + handleClear (e) { const { onClear, submitted, @@ -53,7 +53,7 @@ const handlers = { } }, - focus () { + handleFocus () { const { onShow } = this.props; this.setState({ expanded: true }); if (onShow) { @@ -61,7 +61,7 @@ const handlers = { } }, - keyUp (e) { + handleKeyUp (e) { const { onSubmit } = this.props; switch (e.key) { case 'Enter': @@ -78,19 +78,21 @@ const handlers = { // The component. export default class DrawerSearch extends React.PureComponent { + // Constructor. constructor (props) { super(props); assignHandlers(this, handlers); this.state = { expanded: false }; } + // Rendering. render () { const { - blur, - change, - clear, - focus, - keyUp, + handleBlur, + handleChange, + handleClear, + handleFocus, + handleKeyUp, } = this.handlers; const { intl, @@ -110,23 +112,22 @@ export default class DrawerSearch extends React.PureComponent { type='text' placeholder={intl.formatMessage(messages.placeholder)} value={value || ''} - onChange={change} - onKeyUp={keyUp} - onFocus={focus} - onBlur={blur} + onChange={handleChange} + onKeyUp={handleKeyUp} + onFocus={handleFocus} + onBlur={handleBlur} /> </label> <div aria-label={intl.formatMessage(messages.placeholder)} className='icon' - onClick={clear} + onClick={handleClear} role='button' tabIndex='0' > <Icon icon='search' /> <Icon icon='fa-times-circle' /> </div> - <Overlay placement='bottom' show={expanded && !(value || '').length && !submitted} @@ -138,6 +139,7 @@ export default class DrawerSearch extends React.PureComponent { } +// Props. DrawerSearch.propTypes = { value: PropTypes.string, submitted: PropTypes.bool, diff --git a/app/javascript/flavours/glitch/features/drawer/search/popout/index.js b/app/javascript/flavours/glitch/features/drawer/search/popout/index.js index bd36275f5..b5ea86ff1 100644 --- a/app/javascript/flavours/glitch/features/drawer/search/popout/index.js +++ b/app/javascript/flavours/glitch/features/drawer/search/popout/index.js @@ -34,9 +34,13 @@ const messages = defineMessages({ }, }); +// The spring used by our motion. const motionSpring = spring(1, { damping: 35, stiffness: 400 }); +// The component. export default function DrawerSearchPopout ({ style }) { + + // The result. return ( <Motion defaultStyle={{ diff --git a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js b/app/javascript/flavours/glitch/features/ui/components/actions_modal.js index 020cc0dd6..c8b040f95 100644 --- a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/actions_modal.js @@ -50,7 +50,7 @@ export default class ActionsModal extends ImmutablePureComponent { <Link className={classNames('link', { active })} href={href} - onClick={onClick} + onClick={on !== null && typeof on !== 'undefined' && onPassiveClick || onClick} role={onClick ? 'button' : null} > {function () { diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js index 91d4df93f..e4556899d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -11,13 +11,13 @@ import BundleContainer from '../containers/bundle_container'; import ColumnLoading from './column_loading'; import DrawerLoading from './drawer_loading'; import BundleColumnError from './bundle_column_error'; -import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from 'flavours/glitch/util/async-components'; +import { Drawer, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from 'flavours/glitch/util/async-components'; import detectPassiveEvents from 'detect-passive-events'; import { scrollRight } from 'flavours/glitch/util/scroll'; const componentMap = { - 'COMPOSE': Compose, + 'COMPOSE': Drawer, 'HOME': HomeTimeline, 'NOTIFICATIONS': Notifications, 'PUBLIC': PublicTimeline, diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index 5c80ea07b..fae705deb 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -17,7 +17,7 @@ import UploadArea from './components/upload_area'; import ColumnsAreaContainer from './containers/columns_area_container'; import classNames from 'classnames'; import { - Compose, + Drawer, Status, GettingStarted, KeyboardShortcuts, @@ -56,7 +56,6 @@ const messages = defineMessages({ }); const mapStateToProps = state => ({ - isComposing: state.getIn(['compose', 'is_composing']), hasComposingText: state.getIn(['compose', 'text']) !== '', layout: state.getIn(['local_settings', 'layout']), isWide: state.getIn(['local_settings', 'stretch']), @@ -120,9 +119,9 @@ export default class UI extends React.Component { }; handleBeforeUnload = (e) => { - const { intl, isComposing, hasComposingText } = this.props; + const { intl, hasComposingText } = this.props; - if (isComposing && hasComposingText) { + if (hasComposingText) { // Setting returnValue to any string causes confirmation dialog. // Many browsers no longer display this text to users, // but we set user-friendly message for other browsers, e.g. Edge. @@ -227,9 +226,8 @@ export default class UI extends React.Component { } shouldComponentUpdate (nextProps) { - if (nextProps.isComposing !== this.props.isComposing) { + if (nextProps.navbarUnder !== this.props.navbarUnder) { // Avoid expensive update just to toggle a class - this.node.classList.toggle('is-composing', nextProps.isComposing); this.node.classList.toggle('navbar-under', nextProps.navbarUnder); return false; @@ -427,7 +425,7 @@ export default class UI extends React.Component { <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} /> <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} /> - <WrappedRoute path='/statuses/new' component={Compose} content={children} /> + <WrappedRoute path='/statuses/new' component={Drawer} content={children} /> <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} /> <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} /> <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} /> |