diff options
Diffstat (limited to 'app/javascript/flavours/glitch/features/composer/textarea')
4 files changed, 0 insertions, 533 deletions
diff --git a/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js b/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js deleted file mode 100644 index 049cdd5cd..000000000 --- a/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js +++ /dev/null @@ -1,60 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages } from 'react-intl'; - -// Components. -import Icon from 'flavours/glitch/components/icon'; - -// Messages. -const messages = defineMessages({ - localOnly: { - defaultMessage: 'This post is local-only', - id: 'advanced_options.local-only.tooltip', - }, - threadedMode: { - defaultMessage: 'Threaded mode enabled', - id: 'advanced_options.threaded_mode.tooltip', - }, -}); - -// We use an array of tuples here instead of an object because it -// preserves order. -const iconMap = [ - ['do_not_federate', 'home', messages.localOnly], - ['threaded_mode', 'comments', messages.threadedMode], -]; - -// The component. -export default function ComposerTextareaIcons ({ - advancedOptions, - intl, -}) { - - // The result. We just map every active option to its icon. - return ( - <div className='composer--textarea--icons'> - {advancedOptions ? iconMap.map( - ([key, icon, message]) => advancedOptions.get(key) ? ( - <span - className='textarea_icon' - key={key} - title={intl.formatMessage(message)} - > - <Icon - fullwidth - icon={icon} - /> - </span> - ) : null - ) : null} - </div> - ); -} - -// Props. -ComposerTextareaIcons.propTypes = { - advancedOptions: ImmutablePropTypes.map, - intl: PropTypes.object.isRequired, -}; diff --git a/app/javascript/flavours/glitch/features/composer/textarea/index.js b/app/javascript/flavours/glitch/features/composer/textarea/index.js deleted file mode 100644 index 50e46fc78..000000000 --- a/app/javascript/flavours/glitch/features/composer/textarea/index.js +++ /dev/null @@ -1,312 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { - defineMessages, - FormattedMessage, -} from 'react-intl'; -import Textarea from 'react-textarea-autosize'; - -// Components. -import EmojiPicker from 'flavours/glitch/features/emoji_picker'; -import ComposerTextareaIcons from './icons'; -import ComposerTextareaSuggestions from './suggestions'; - -// Utils. -import { isRtl } from 'flavours/glitch/util/rtl'; -import { - assignHandlers, - hiddenComponent, -} from 'flavours/glitch/util/react_helpers'; - -// Messages. -const messages = defineMessages({ - placeholder: { - defaultMessage: 'What is on your mind?', - id: 'compose_form.placeholder', - }, -}); - -// Handlers. -const handlers = { - - // When blurring the textarea, suggestions are hidden. - 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. - handleChange ({ - target: { - selectionStart, - value, - }, - }) { - const { - onChange, - onSuggestionsFetchRequested, - onSuggestionsClearRequested, - } = this.props; - const { lastToken } = this.state; - - // This gets the token at the caret location, if it begins with an - // `@` (mentions) or `:` (shortcodes). - const left = value.slice(0, selectionStart).search(/[^\s\u200B]+$/); - const right = value.slice(selectionStart).search(/[\s\u200B]/); - const token = function () { - switch (true) { - case left < 0 || !/[@:#]/.test(value[left]): - return null; - case right < 0: - return value.slice(left); - default: - return value.slice(left, right + selectionStart).trim().toLowerCase(); - } - }(); - - // We only request suggestions for tokens which are at least 3 - // characters long. - if (onSuggestionsFetchRequested && token && token.length >= 3) { - if (lastToken !== token) { - this.setState({ - lastToken: token, - selectedSuggestion: 0, - tokenStart: left, - }); - onSuggestionsFetchRequested(token); - } - } else { - this.setState({ lastToken: null }); - if (onSuggestionsClearRequested) { - onSuggestionsClearRequested(); - } - } - - // Updates the value of the textarea. - if (onChange) { - onChange(value); - } - }, - - // Handles a click on an autosuggestion. - handleClickSuggestion (index) { - const { textarea } = this; - const { - onSuggestionSelected, - suggestions, - } = this.props; - const { - lastToken, - tokenStart, - } = this.state; - onSuggestionSelected(tokenStart, lastToken, suggestions.get(index)); - textarea.focus(); - }, - - // Handles a keypress. If the autosuggestions are visible, we need - // to allow keypresses to navigate and sleect them. - handleKeyDown (e) { - const { - disabled, - onSubmit, - onSecondarySubmit, - onSuggestionSelected, - suggestions, - } = this.props; - const { - lastToken, - suggestionsHidden, - selectedSuggestion, - tokenStart, - } = this.state; - - // Keypresses do nothing if the composer is disabled. - if (disabled) { - e.preventDefault(); - return; - } - - // We submit the status on control/meta + enter. - if (onSubmit && e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { - onSubmit(); - } - - // Submit the status with secondary visibility on alt + enter. - if (onSecondarySubmit && e.keyCode === 13 && e.altKey) { - onSecondarySubmit(); - } - - // Switches over the pressed key. - switch(e.key) { - - // On arrow down, we pick the next suggestion. - case 'ArrowDown': - if (suggestions && suggestions.size > 0 && !suggestionsHidden) { - e.preventDefault(); - this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) }); - } - return; - - // On arrow up, we pick the previous suggestion. - case 'ArrowUp': - if (suggestions && suggestions.size > 0 && !suggestionsHidden) { - e.preventDefault(); - this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) }); - } - return; - - // On enter or tab, we select the suggestion. - case 'Enter': - case 'Tab': - if (onSuggestionSelected && lastToken !== null && suggestions && suggestions.size > 0 && !suggestionsHidden) { - e.preventDefault(); - e.stopPropagation(); - onSuggestionSelected(tokenStart, lastToken, suggestions.get(selectedSuggestion)); - } - return; - } - }, - - // When the escape key is released, we either close the suggestions - // window or focus the UI. - handleKeyUp ({ key }) { - const { suggestionsHidden } = this.state; - if (key === 'Escape') { - if (!suggestionsHidden) { - this.setState({ suggestionsHidden: true }); - } else { - document.querySelector('.ui').parentElement.focus(); - } - } - }, - - // Handles the pasting of images into the composer. - handlePaste (e) { - const { onPaste } = this.props; - let d; - if (onPaste && (d = e.clipboardData) && (d = d.files).length === 1) { - onPaste(d); - e.preventDefault(); - } - }, - - // Saves a reference to the textarea. - handleRefTextarea (textarea) { - this.textarea = textarea; - }, -}; - -// The component. -export default class ComposerTextarea extends React.Component { - - // Constructor. - constructor (props) { - super(props); - assignHandlers(this, handlers); - this.state = { - suggestionsHidden: false, - selectedSuggestion: 0, - lastToken: null, - tokenStart: 0, - }; - - // Instance variables. - this.textarea = null; - } - - // When we receive new suggestions, we unhide the suggestions window - // if we didn't have any suggestions before. - componentWillReceiveProps (nextProps) { - const { suggestions } = this.props; - const { suggestionsHidden } = this.state; - if (nextProps.suggestions && nextProps.suggestions !== suggestions && nextProps.suggestions.size > 0 && suggestionsHidden) { - this.setState({ suggestionsHidden: false }); - } - } - - // Rendering. - render () { - const { - handleBlur, - handleChange, - handleClickSuggestion, - handleKeyDown, - handleKeyUp, - handlePaste, - handleRefTextarea, - } = this.handlers; - const { - advancedOptions, - autoFocus, - disabled, - intl, - onPickEmoji, - suggestions, - value, - } = this.props; - const { - selectedSuggestion, - suggestionsHidden, - } = this.state; - - // The result. - return ( - <div className='composer--textarea'> - <label> - <span {...hiddenComponent}><FormattedMessage {...messages.placeholder} /></span> - <ComposerTextareaIcons - advancedOptions={advancedOptions} - intl={intl} - /> - <Textarea - aria-autocomplete='list' - autoFocus={autoFocus} - className='textarea' - disabled={disabled} - 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' }} - /> - </label> - <EmojiPicker onPickEmoji={onPickEmoji} /> - <ComposerTextareaSuggestions - hidden={suggestionsHidden} - onSuggestionClick={handleClickSuggestion} - suggestions={suggestions} - value={selectedSuggestion} - /> - </div> - ); - } - -} - -// Props. -ComposerTextarea.propTypes = { - advancedOptions: ImmutablePropTypes.map, - autoFocus: PropTypes.bool, - disabled: PropTypes.bool, - intl: PropTypes.object.isRequired, - onChange: PropTypes.func, - onPaste: PropTypes.func, - onPickEmoji: PropTypes.func, - onSubmit: PropTypes.func, - onSecondarySubmit: PropTypes.func, - onSuggestionsClearRequested: PropTypes.func, - onSuggestionsFetchRequested: PropTypes.func, - onSuggestionSelected: PropTypes.func, - suggestions: ImmutablePropTypes.list, - value: PropTypes.string, -}; - -// Default props. -ComposerTextarea.defaultProps = { autoFocus: true }; diff --git a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js b/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js deleted file mode 100644 index dc72585f2..000000000 --- a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js +++ /dev/null @@ -1,43 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; - -// Components. -import ComposerTextareaSuggestionsItem from './item'; - -// The component. -export default function ComposerTextareaSuggestions ({ - hidden, - onSuggestionClick, - suggestions, - value, -}) { - - // The result. - return ( - <div - className='composer--textarea--suggestions' - hidden={hidden || !suggestions || suggestions.isEmpty()} - > - {!hidden && suggestions ? suggestions.map( - (suggestion, index) => ( - <ComposerTextareaSuggestionsItem - index={index} - key={typeof suggestion === 'object' ? suggestion.id : suggestion} - onClick={onSuggestionClick} - selected={index === value} - suggestion={suggestion} - /> - ) - ) : null} - </div> - ); -} - -ComposerTextareaSuggestions.propTypes = { - hidden: PropTypes.bool, - onSuggestionClick: PropTypes.func, - suggestions: ImmutablePropTypes.list, - 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 deleted file mode 100644 index 1b7ae8904..000000000 --- a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js +++ /dev/null @@ -1,118 +0,0 @@ -// Package imports. -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; - -// Components. -import AccountContainer from 'flavours/glitch/containers/account_container'; - -// Utils. -import { unicodeMapping } from 'flavours/glitch/util/emoji'; -import { assignHandlers } from 'flavours/glitch/util/react_helpers'; - -// Gets our asset host from the environment, if available. -const assetHost = process.env.CDN_HOST || ''; - -// Handlers. -const handlers = { - - // Handles a click on a suggestion. - handleClick (e) { - const { - index, - onClick, - } = this.props; - if (onClick) { - e.preventDefault(); - e.stopPropagation(); // Prevents following account links - onClick(index); - } - }, - - // This prevents the focus from changing, which would mess with - // our suggestion code. - handleMouseDown (e) { - e.preventDefault(); - }, -}; - -// The component. -export default class ComposerTextareaSuggestionsItem extends React.Component { - - // Constructor. - constructor (props) { - super(props); - assignHandlers(this, handlers); - } - - // Rendering. - render () { - const { - handleMouseDown, - handleClick, - } = this.handlers; - const { - selected, - suggestion, - } = this.props; - const computedClass = classNames('composer--textarea--suggestions--item', { selected }); - - // If the suggestion is an object, then we render an emoji. - // Otherwise, we render a hashtag if it starts with #, or an account. - let inner; - if (typeof suggestion === 'object') { - let url; - if (suggestion.custom) { - url = suggestion.imageUrl; - } else { - const mapping = unicodeMapping[suggestion.native] || unicodeMapping[suggestion.native.replace(/\uFE0F$/, '')]; - if (mapping) { - url = `${assetHost}/emoji/${mapping.filename}.svg`; - } - } - if (url) { - inner = ( - <div className='emoji'> - <img - alt={suggestion.native || suggestion.colons} - className='emojione' - src={url} - /> - {suggestion.colons} - </div> - ); - } - } else if (suggestion[0] === '#') { - inner = suggestion; - } else { - inner = ( - <AccountContainer - id={suggestion} - small - /> - ); - } - - // The result. - return ( - <div - className={computedClass} - onMouseDown={handleMouseDown} - onClickCapture={handleClick} // Jumps in front of contents - role='button' - tabIndex='0' - > - { inner } - </div> - ); - } - -} - -// Props. -ComposerTextareaSuggestionsItem.propTypes = { - index: PropTypes.number, - onClick: PropTypes.func, - selected: PropTypes.bool, - suggestion: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), -}; |