--- /dev/null
+import React from 'react';
+import PropTypes from 'prop-types';
+import unicodeMapping from 'flavours/glitch/util/emoji/emoji_unicode_mapping_light';
+
+const assetHost = process.env.CDN_HOST || '';
+
+export default class AutosuggestEmoji extends React.PureComponent {
+
+ static propTypes = {
+ emoji: PropTypes.object.isRequired,
+ };
+
+ render () {
+ const { emoji } = this.props;
+ let url;
+
+ if (emoji.custom) {
+ url = emoji.imageUrl;
+ } else {
+ const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')];
+
+ if (!mapping) {
+ return null;
+ }
+
+ url = `${assetHost}/emoji/${mapping.filename}.svg`;
+ }
+
+ return (
+ <div className='emoji'>
+ <img
+ className='emojione'
+ src={url}
+ alt={emoji.native || emoji.colons}
+ />
+
+ {emoji.colons}
+ </div>
+ );
+ }
+
+}
--- /dev/null
+import React from 'react';
+import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
+import AutosuggestEmoji from './autosuggest_emoji';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { isRtl } from 'flavours/glitch/util/rtl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import Textarea from 'react-textarea-autosize';
+import classNames from 'classnames';
+
+const textAtCursorMatchesToken = (str, caretPosition) => {
+ let word;
+
+ let left = str.slice(0, caretPosition).search(/[^\s\u200B]+$/);
+ let right = str.slice(caretPosition).search(/[\s\u200B]/);
+
+ if (right < 0) {
+ word = str.slice(left);
+ } else {
+ word = str.slice(left, right + caretPosition);
+ }
+
+ if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) {
+ return [null, null];
+ }
+
+ word = word.trim().toLowerCase();
+
+ if (word.length > 0) {
+ return [left, word];
+ } else {
+ return [null, null];
+ }
+};
+
+export default class AutosuggestTextarea extends ImmutablePureComponent {
+
+ static propTypes = {
+ value: PropTypes.string,
+ suggestions: ImmutablePropTypes.list,
+ disabled: PropTypes.bool,
+ placeholder: PropTypes.string,
+ onSuggestionSelected: PropTypes.func.isRequired,
+ onSuggestionsClearRequested: PropTypes.func.isRequired,
+ onSuggestionsFetchRequested: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onKeyUp: PropTypes.func,
+ onKeyDown: PropTypes.func,
+ onPaste: PropTypes.func.isRequired,
+ autoFocus: PropTypes.bool,
+ };
+
+ static defaultProps = {
+ autoFocus: true,
+ };
+
+ state = {
+ suggestionsHidden: false,
+ selectedSuggestion: 0,
+ lastToken: null,
+ tokenStart: 0,
+ };
+
+ onChange = (e) => {
+ const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
+
+ if (token !== null && this.state.lastToken !== token) {
+ this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
+ this.props.onSuggestionsFetchRequested(token);
+ } else if (token === null) {
+ this.setState({ lastToken: null });
+ this.props.onSuggestionsClearRequested();
+ }
+
+ this.props.onChange(e);
+ }
+
+ onKeyDown = (e) => {
+ const { suggestions, disabled } = this.props;
+ const { selectedSuggestion, suggestionsHidden } = this.state;
+
+ if (disabled) {
+ e.preventDefault();
+ return;
+ }
+
+ if (e.which === 229 || e.isComposing) {
+ // Ignore key events during text composition
+ // e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac)
+ return;
+ }
+
+ switch(e.key) {
+ case 'Escape':
+ if (suggestions.size === 0 || suggestionsHidden) {
+ document.querySelector('.ui').parentElement.focus();
+ } else {
+ e.preventDefault();
+ this.setState({ suggestionsHidden: true });
+ }
+
+ break;
+ case 'ArrowDown':
+ if (suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
+ }
+
+ break;
+ case 'ArrowUp':
+ if (suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
+ }
+
+ break;
+ case 'Enter':
+ case 'Tab':
+ // Select suggestion
+ if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ e.stopPropagation();
+ this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
+ }
+
+ break;
+ }
+
+ if (e.defaultPrevented || !this.props.onKeyDown) {
+ return;
+ }
+
+ this.props.onKeyDown(e);
+ }
+
+ onBlur = () => {
+ this.setState({ suggestionsHidden: true });
+ }
+
+ onSuggestionClick = (e) => {
+ const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
+ e.preventDefault();
+ this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
+ this.textarea.focus();
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
+ this.setState({ suggestionsHidden: false });
+ }
+ }
+
+ setTextarea = (c) => {
+ this.textarea = c;
+ }
+
+ onPaste = (e) => {
+ if (e.clipboardData && e.clipboardData.files.length === 1) {
+ this.props.onPaste(e.clipboardData.files);
+ e.preventDefault();
+ }
+ }
+
+ renderSuggestion = (suggestion, i) => {
+ const { selectedSuggestion } = this.state;
+ let inner, key;
+
+ if (typeof suggestion === 'object') {
+ inner = <AutosuggestEmoji emoji={suggestion} />;
+ key = suggestion.id;
+ } else if (suggestion[0] === '#') {
+ inner = suggestion;
+ key = suggestion;
+ } else {
+ inner = <AutosuggestAccountContainer id={suggestion} />;
+ key = suggestion;
+ }
+
+ return (
+ <div role='button' tabIndex='0' key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
+ {inner}
+ </div>
+ );
+ }
+
+ render () {
+ const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
+ const { suggestionsHidden } = this.state;
+ const style = { direction: 'ltr' };
+
+ if (isRtl(value)) {
+ style.direction = 'rtl';
+ }
+
+ return (
+ <div className='autosuggest-textarea'>
+ <label>
+ <span style={{ display: 'none' }}>{placeholder}</span>
+
+ <Textarea
+ inputRef={this.setTextarea}
+ className='autosuggest-textarea__textarea'
+ disabled={disabled}
+ placeholder={placeholder}
+ autoFocus={autoFocus}
+ value={value}
+ onChange={this.onChange}
+ onKeyDown={this.onKeyDown}
+ onKeyUp={onKeyUp}
+ onBlur={this.onBlur}
+ onPaste={this.onPaste}
+ style={style}
+ aria-autocomplete='list'
+ />
+ </label>
+
+ <div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
+ {suggestions.map(this.renderSuggestion)}
+ </div>
+ </div>
+ );
+ }
+
+}
--- /dev/null
+import React from 'react';
+import Avatar from 'flavours/glitch/components/avatar';
+import DisplayName from 'flavours/glitch/components/display_name';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+export default class AutosuggestAccount extends ImmutablePureComponent {
+
+ static propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ };
+
+ render () {
+ const { account } = this.props;
+
+ return (
+ <div className='account small' title={account.get('acct')}>
+ <div className='account__avatar-wrapper'><Avatar account={account} size={24} /></div>
+ <DisplayName account={account} inline />
+ </div>
+ );
+ }
+
+}
// Components.
import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
-import ComposerTextarea from '../../composer/textarea';
+import ComposerTextareaIcons from '../../composer/textarea/icons';
import UploadFormContainer from '../containers/upload_form_container';
import PollFormContainer from '../containers/poll_form_container';
import WarningContainer from '../containers/warning_container';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
+import EmojiPicker from 'flavours/glitch/features/emoji_picker';
+import AutosuggestTextarea from '../../../components/autosuggest_textarea';
// Utils.
import { countableText } from 'flavours/glitch/util/counter';
import { isMobile } from 'flavours/glitch/util/is_mobile';
const messages = defineMessages({
+ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
missingDescriptionMessage: { id: 'confirmations.missing_media_description.message',
defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm',
}
// Sets a reference to the textarea.
- handleRefTextarea = (textareaComponent) => {
+ setAutosuggestTextarea = (textareaComponent) => {
if (textareaComponent) {
this.textarea = textareaComponent.textarea;
}
}
}
+ handleChange = (e) => {
+ this.props.onChangeText(e.target.value);
+ }
+
render () {
const {
handleEmoji,
</label>
</div>
- <ComposerTextarea
- advancedOptions={advancedOptions}
- autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
- disabled={isSubmitting}
- intl={intl}
- onChange={onChangeText}
- onPaste={onUpload}
- onPickEmoji={handleEmoji}
- onSubmit={handleSubmit}
- onSecondarySubmit={handleSecondarySubmit}
- onSuggestionsClearRequested={onClearSuggestions}
- onSuggestionsFetchRequested={onFetchSuggestions}
- onSuggestionSelected={handleSelect}
- ref={handleRefTextarea}
- suggestions={suggestions}
- value={text}
- />
+ <div className='composer--textarea'>
+ <ComposerTextareaIcons
+ advancedOptions={advancedOptions}
+ intl={intl}
+ />
+
+ <AutosuggestTextarea
+ ref={this.setAutosuggestTextarea}
+ placeholder={intl.formatMessage(messages.placeholder)}
+ disabled={isSubmitting}
+ value={this.props.text}
+ onChange={this.handleChange}
+ suggestions={this.props.suggestions}
+ onKeyDown={this.handleKeyDown}
+ onSuggestionsFetchRequested={onFetchSuggestions}
+ onSuggestionsClearRequested={onClearSuggestions}
+ onSuggestionSelected={this.handleSelect}
+ onPaste={onUpload}
+ autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
+ />
+
+ <EmojiPicker onPickEmoji={handleEmoji} />
+ </div>
+
<div className='compose-form__modifiers'>
<UploadFormContainer />
<PollFormContainer />
</div>
+
<ComposerOptions
acceptContentTypes={acceptContentTypes}
advancedOptions={advancedOptions}
sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
/>
+
<ComposerPublisher
countText={`${spoilerText}${countableText(text)}${advancedOptions && advancedOptions.get('do_not_federate') ? ' 👁️' : ''}`}
disabled={disabledButton}
--- /dev/null
+import { connect } from 'react-redux';
+import AutosuggestAccount from '../components/autosuggest_account';
+import { makeGetAccount } from 'flavours/glitch/selectors';
+
+const makeMapStateToProps = () => {
+ const getAccount = makeGetAccount();
+
+ const mapStateToProps = (state, { id }) => ({
+ account: getAccount(state, id),
+ });
+
+ return mapStateToProps;
+};
+
+export default connect(makeMapStateToProps)(AutosuggestAccount);
+++ /dev/null
-// 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 };
+++ /dev/null
-// 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,
-};
+++ /dev/null
-// 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]),
-};
.composer--textarea {
position: relative;
- & > label {
- .textarea {
+ label {
+ .autosuggest-textarea__textarea {
display: block;
box-sizing: border-box;
margin: 0;
}
}
-.composer--textarea--suggestions {
+.autosuggest-textarea__suggestions {
display: block;
position: absolute;
box-sizing: border-box;
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
font-size: 14px;
z-index: 99;
+ display: none;
+}
- &[hidden] { display: none }
+.autosuggest-textarea__suggestions--visible {
+ display: block;
}
-.composer--textarea--suggestions--item {
+.autosuggest-textarea__suggestions__item {
display: flex;
flex-direction: row;
align-items: center;
border-color: $ui-base-color;
}
-.composer--textarea--suggestions {
+.autosuggest-textarea__suggestions {
background: lighten($ui-base-color, 10%)
}
-.composer--textarea--suggestions--item {
+.autosuggest-textarea__suggestions__item {
&:hover,
&:focus,
&:active,