From 96e6aea6161188b956e5397a35a97b6288cca4be Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 31 Oct 2021 22:41:59 -0500 Subject: [PATCH 1/8] Button: fix follow button icon padding --- app/styles/components/buttons.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/styles/components/buttons.scss b/app/styles/components/buttons.scss index b0f6dd308..51157bf31 100644 --- a/app/styles/components/buttons.scss +++ b/app/styles/components/buttons.scss @@ -159,7 +159,7 @@ a.button { justify-content: center; .svg-icon { - margin-left: 6px; + margin: 0 0 0 6px; width: 20px; height: 20px; } From b73b613064858272d512d20e4b1a4a64c8f64bb5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 31 Oct 2021 22:58:06 -0500 Subject: [PATCH 2/8] Search: integration suggestions better --- .../compose/components/search_results.js | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/app/soapbox/features/compose/components/search_results.js b/app/soapbox/features/compose/components/search_results.js index 6e144d55d..e6004e4a3 100644 --- a/app/soapbox/features/compose/components/search_results.js +++ b/app/soapbox/features/compose/components/search_results.js @@ -7,8 +7,6 @@ import StatusContainer from '../../../containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Hashtag from '../../../components/hashtag'; import FilterBar from '../../search/components/filter_bar'; -import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; -import { WhoToFollowPanel } from 'soapbox/features/ui/util/async-components'; import ScrollableList from 'soapbox/components/scrollable_list'; import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder_account'; import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder_hashtag'; @@ -24,6 +22,7 @@ export default class SearchResults extends ImmutablePureComponent { selectedFilter: PropTypes.string.isRequired, selectFilter: PropTypes.func.isRequired, features: PropTypes.object.isRequired, + suggestions: ImmutablePropTypes.list, }; handleLoadMore = () => this.props.expandSearch(this.props.selectedFilter); @@ -31,15 +30,7 @@ export default class SearchResults extends ImmutablePureComponent { handleSelectFilter = newActiveFilter => this.props.selectFilter(newActiveFilter); render() { - const { value, results, submitted, selectedFilter, features } = this.props; - - if (!submitted && features.suggestions && results.isEmpty()) { - return ( - - {Component => } - - ); - } + const { value, results, submitted, selectedFilter, suggestions } = this.props; let searchResults; let hasMore = false; @@ -47,14 +38,16 @@ export default class SearchResults extends ImmutablePureComponent { let noResultsMessage; let placeholderComponent = PlaceholderStatus; - if (selectedFilter === 'accounts' && results.get('accounts')) { + if (selectedFilter === 'accounts') { hasMore = results.get('accountsHasMore'); loaded = results.get('accountsLoaded'); placeholderComponent = PlaceholderAccount; - if (results.get('accounts').size > 0) { + if (results.get('accounts') && results.get('accounts').size > 0) { searchResults = results.get('accounts').map(accountId => ); - } else { + } else if (suggestions && !suggestions.isEmpty()) { + searchResults = suggestions.map(suggestion => ); + } else if (submitted) { noResultsMessage = (
- {submitted && } + {noResultsMessage || ( Date: Sun, 31 Oct 2021 22:59:29 -0500 Subject: [PATCH 3/8] Search: nuke the popout --- .../features/compose/components/search.js | 40 +------------------ 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/app/soapbox/features/compose/components/search.js b/app/soapbox/features/compose/components/search.js index ad42208b4..6489b4e32 100644 --- a/app/soapbox/features/compose/components/search.js +++ b/app/soapbox/features/compose/components/search.js @@ -1,9 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Overlay from 'react-overlays/lib/Overlay'; -import Motion from '../../ui/util/optional_motion'; -import spring from 'react-motion/lib/spring'; +import { defineMessages, injectIntl } from 'react-intl'; import Icon from 'soapbox/components/icon'; import classNames from 'classnames'; @@ -11,37 +8,6 @@ const messages = defineMessages({ placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, }); -class SearchPopout extends React.PureComponent { - - static propTypes = { - style: PropTypes.object, - }; - - render() { - const { style } = this.props; - const extraInformation = ; - return ( -
- - {({ opacity, scaleX, scaleY }) => ( -
-

-
    -
  • #example
  • -
  • @username
  • -
  • URL
  • -
  • URL
  • -
- {extraInformation} -
- )} -
-
- ); - } - -} - export default @injectIntl class Search extends React.PureComponent { @@ -106,7 +72,6 @@ class Search extends React.PureComponent { render() { const { intl, value, autoFocus, submitted } = this.props; - const { expanded } = this.state; const hasValue = value.length > 0 || submitted; return ( @@ -129,9 +94,6 @@ class Search extends React.PureComponent {
- - - ); } From a5bf105ccd7f66982ec9a0f80e67d3e079ef4369 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Nov 2021 12:25:18 -0500 Subject: [PATCH 4/8] Search: autosuggest accounts in search bar on desktop --- app/soapbox/components/autosuggest_input.js | 14 ++++++++--- .../features/compose/components/search.js | 24 +++++++++++++++---- .../compose/containers/search_container.js | 15 ++++++++++++ .../features/ui/components/tabs_bar.js | 2 +- app/styles/components/search.scss | 8 +++++-- app/styles/components/tabs-bar.scss | 1 - 6 files changed, 53 insertions(+), 11 deletions(-) diff --git a/app/soapbox/components/autosuggest_input.js b/app/soapbox/components/autosuggest_input.js index 8f750ab3f..8bf5e86fa 100644 --- a/app/soapbox/components/autosuggest_input.js +++ b/app/soapbox/components/autosuggest_input.js @@ -47,6 +47,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { onKeyUp: PropTypes.func, onKeyDown: PropTypes.func, autoFocus: PropTypes.bool, + autoSelect: PropTypes.bool, className: PropTypes.string, id: PropTypes.string, searchTokens: PropTypes.arrayOf(PropTypes.string), @@ -55,13 +56,18 @@ export default class AutosuggestInput extends ImmutablePureComponent { static defaultProps = { autoFocus: false, + autoSelect: true, searchTokens: ImmutableList(['@', ':', '#']), }; + getFirstIndex = () => { + return this.props.autoSelect ? 0 : -1; + } + state = { suggestionsHidden: true, focused: false, - selectedSuggestion: 0, + selectedSuggestion: this.getFirstIndex(), lastToken: null, tokenStart: 0, }; @@ -83,6 +89,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { onKeyDown = (e) => { const { suggestions, disabled } = this.props; const { selectedSuggestion, suggestionsHidden } = this.state; + const firstIndex = this.getFirstIndex(); if (disabled) { e.preventDefault(); @@ -115,16 +122,17 @@ export default class AutosuggestInput extends ImmutablePureComponent { case 'ArrowUp': if (suggestions.size > 0 && !suggestionsHidden) { e.preventDefault(); - this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) }); + this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, firstIndex) }); } break; case 'Enter': case 'Tab': // Select suggestion - if (suggestions.size > 0 && !suggestionsHidden) { + if (suggestions.size > 0 && !suggestionsHidden && selectedSuggestion > -1) { e.preventDefault(); e.stopPropagation(); + this.setState({ selectedSuggestion: firstIndex }); this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion)); } diff --git a/app/soapbox/features/compose/components/search.js b/app/soapbox/features/compose/components/search.js index 6489b4e32..73a7c035f 100644 --- a/app/soapbox/features/compose/components/search.js +++ b/app/soapbox/features/compose/components/search.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import Icon from 'soapbox/components/icon'; +import AutosuggestAccountInput from 'soapbox/components/autosuggest_account_input'; import classNames from 'classnames'; const messages = defineMessages({ @@ -22,13 +23,16 @@ class Search extends React.PureComponent { onSubmit: PropTypes.func.isRequired, onClear: PropTypes.func.isRequired, onShow: PropTypes.func.isRequired, + onSelected: PropTypes.func, openInRoute: PropTypes.bool, autoFocus: PropTypes.bool, + autosuggest: PropTypes.bool, intl: PropTypes.object.isRequired, }; static defaultProps = { autoFocus: false, + ausosuggest: false, } state = { @@ -47,7 +51,7 @@ class Search extends React.PureComponent { } } - handleKeyUp = (e) => { + handleKeyDown = (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -70,24 +74,36 @@ class Search extends React.PureComponent { this.setState({ expanded: false }); } + handleSelected = accountId => { + const { onSelected } = this.props; + + if (onSelected) { + onSelected(accountId, this.context.router.history); + } + } + render() { - const { intl, value, autoFocus, submitted } = this.props; + const { intl, value, autoFocus, autosuggest, submitted } = this.props; const hasValue = value.length > 0 || submitted; + const Component = autosuggest ? AutosuggestAccountInput : 'input'; + return (
diff --git a/app/soapbox/features/compose/containers/search_container.js b/app/soapbox/features/compose/containers/search_container.js index e6484216c..f11a6af05 100644 --- a/app/soapbox/features/compose/containers/search_container.js +++ b/app/soapbox/features/compose/containers/search_container.js @@ -13,6 +13,16 @@ const mapStateToProps = state => ({ submitted: state.getIn(['search', 'submitted']), }); +function redirectToAccount(accountId, routerHistory) { + return (dispatch, getState) => { + const acct = getState().getIn(['accounts', accountId, 'acct']); + + if (acct && routerHistory) { + routerHistory.push(`/@${acct}`); + } + }; +} + const mapDispatchToProps = (dispatch, { autoSubmit }) => { const debouncedSubmit = debounce(() => { @@ -40,6 +50,11 @@ const mapDispatchToProps = (dispatch, { autoSubmit }) => { dispatch(showSearch()); }, + onSelected(accountId, routerHistory) { + dispatch(clearSearch()); + dispatch(redirectToAccount(accountId, routerHistory)); + }, + }; }; diff --git a/app/soapbox/features/ui/components/tabs_bar.js b/app/soapbox/features/ui/components/tabs_bar.js index a4b1d5bb5..959ce245c 100644 --- a/app/soapbox/features/ui/components/tabs_bar.js +++ b/app/soapbox/features/ui/components/tabs_bar.js @@ -88,7 +88,7 @@ class TabsBar extends React.PureComponent { )}
- +
diff --git a/app/styles/components/search.scss b/app/styles/components/search.scss index 39c56a308..b3e56eaac 100644 --- a/app/styles/components/search.scss +++ b/app/styles/components/search.scss @@ -2,7 +2,7 @@ position: relative; } -.search__input { +input.search__input { @include search-input; display: block; padding: 7px 30px 6px 10px; @@ -177,7 +177,7 @@ border-bottom: 1px solid hsla(var(--primary-text-color_hsl), 0.2); } - .search__input { + input.search__input { background-color: var(--background-color); border-radius: 8px; padding: 12px 36px 12px 16px; @@ -202,3 +202,7 @@ } } } + +.search .autosuggest-textarea__suggestions { + border-radius: 4px; +} diff --git a/app/styles/components/tabs-bar.scss b/app/styles/components/tabs-bar.scss index 037f70251..2827206d5 100644 --- a/app/styles/components/tabs-bar.scss +++ b/app/styles/components/tabs-bar.scss @@ -3,7 +3,6 @@ box-sizing: border-box; background: var(--brand-color); flex: 0 0 auto; - overflow-y: auto; height: 50px; width: 100%; position: sticky; From 4ef9a88f721b03143cd65ee6b8230923d2b800c9 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Nov 2021 13:45:17 -0500 Subject: [PATCH 5/8] Search: add menu to AutosuggestInput --- app/soapbox/components/autosuggest_input.js | 61 ++++++++++++++++--- .../features/compose/components/search.js | 25 ++++++-- app/styles/autosuggest.scss | 14 +++++ 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/app/soapbox/components/autosuggest_input.js b/app/soapbox/components/autosuggest_input.js index 8bf5e86fa..54ad6d264 100644 --- a/app/soapbox/components/autosuggest_input.js +++ b/app/soapbox/components/autosuggest_input.js @@ -52,6 +52,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { id: PropTypes.string, searchTokens: PropTypes.arrayOf(PropTypes.string), maxLength: PropTypes.number, + menu: PropTypes.arrayOf(PropTypes.object), }; static defaultProps = { @@ -87,9 +88,10 @@ export default class AutosuggestInput extends ImmutablePureComponent { } onKeyDown = (e) => { - const { suggestions, disabled } = this.props; + const { suggestions, menu, disabled } = this.props; const { selectedSuggestion, suggestionsHidden } = this.state; const firstIndex = this.getFirstIndex(); + const lastIndex = suggestions.size + (menu || []).length - 1; if (disabled) { e.preventDefault(); @@ -113,14 +115,14 @@ export default class AutosuggestInput extends ImmutablePureComponent { break; case 'ArrowDown': - if (suggestions.size > 0 && !suggestionsHidden) { + if (!suggestionsHidden && (suggestions.size > 0 || menu)) { e.preventDefault(); - this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) }); + this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, lastIndex) }); } break; case 'ArrowUp': - if (suggestions.size > 0 && !suggestionsHidden) { + if (!suggestionsHidden && (suggestions.size > 0 || menu)) { e.preventDefault(); this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, firstIndex) }); } @@ -129,11 +131,17 @@ export default class AutosuggestInput extends ImmutablePureComponent { case 'Enter': case 'Tab': // Select suggestion - if (suggestions.size > 0 && !suggestionsHidden && selectedSuggestion > -1) { + if (!suggestionsHidden && selectedSuggestion > -1 && (suggestions.size > 0 || menu)) { e.preventDefault(); e.stopPropagation(); this.setState({ selectedSuggestion: firstIndex }); - this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion)); + + if (selectedSuggestion < suggestions.size) { + this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion)); + } else { + const item = menu[selectedSuggestion - suggestions.size]; + this.handleMenuItemAction(item); + } } break; @@ -194,11 +202,47 @@ export default class AutosuggestInput extends ImmutablePureComponent { ); } + handleMenuItemAction = item => { + this.onBlur(); + item.action(); + } + + handleMenuItemClick = item => { + return e => { + e.preventDefault(); + this.handleMenuItemAction(item); + }; + } + + renderMenu = () => { + const { menu, suggestions } = this.props; + const { selectedSuggestion } = this.state; + + if (!menu) { + return null; + } + + return menu.map((item, i) => ( + + {item.text} + + )); + }; + render() { - const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength } = this.props; + const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength, menu } = this.props; const { suggestionsHidden } = this.state; const style = { direction: 'ltr' }; + const visible = !suggestionsHidden && (!suggestions.isEmpty() || (menu && value)); + if (isRtl(value)) { style.direction = 'rtl'; } @@ -228,8 +272,9 @@ export default class AutosuggestInput extends ImmutablePureComponent { /> -
+
{suggestions.map(this.renderSuggestion)} + {this.renderMenu()}
); diff --git a/app/soapbox/features/compose/components/search.js b/app/soapbox/features/compose/components/search.js index 73a7c035f..063e3f939 100644 --- a/app/soapbox/features/compose/components/search.js +++ b/app/soapbox/features/compose/components/search.js @@ -7,6 +7,7 @@ import classNames from 'classnames'; const messages = defineMessages({ placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, + action: { id: 'search.action', defaultMessage: 'Search for “{query}”' }, }); export default @injectIntl @@ -51,15 +52,18 @@ class Search extends React.PureComponent { } } + handleSubmit = () => { + this.props.onSubmit(); + + if (this.props.openInRoute) { + this.context.router.history.push('/search'); + } + } + handleKeyDown = (e) => { if (e.key === 'Enter') { e.preventDefault(); - - this.props.onSubmit(); - - if (this.props.openInRoute) { - this.context.router.history.push('/search'); - } + this.handleSubmit(); } else if (e.key === 'Escape') { document.querySelector('.ui').parentElement.focus(); } @@ -82,6 +86,14 @@ class Search extends React.PureComponent { } } + makeMenu = () => { + const { intl, value } = this.props; + + return [ + { text: intl.formatMessage(messages.action, { query: value }), action: this.handleSubmit }, + ]; + } + render() { const { intl, value, autoFocus, autosuggest, submitted } = this.props; const hasValue = value.length > 0 || submitted; @@ -104,6 +116,7 @@ class Search extends React.PureComponent { onSelected={this.handleSelected} autoFocus={autoFocus} autoSelect={false} + menu={this.makeMenu()} />
diff --git a/app/styles/autosuggest.scss b/app/styles/autosuggest.scss index ea322e233..8bf4b39dd 100644 --- a/app/styles/autosuggest.scss +++ b/app/styles/autosuggest.scss @@ -102,3 +102,17 @@ .autosuggest-account .display-name__account { color: var(--primary-text-color--faint); } + +.autosuggest-input__action { + display: block; + padding: 10px; + border-radius: 4px; + font-weight: bold; + text-decoration: none; + color: var(--primary-text-color--faint); + + &:hover, + &.selected { + background-color: var(--brand-color--med); + } +} From 23393710c71514c983ba7d7a5dbcc4bdd6c091ce Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Nov 2021 13:53:30 -0500 Subject: [PATCH 6/8] AutosuggestInput: add icon to menu --- app/soapbox/components/autosuggest_input.js | 7 ++++++- app/soapbox/features/compose/components/search.js | 2 +- app/styles/autosuggest.scss | 8 +++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/soapbox/components/autosuggest_input.js b/app/soapbox/components/autosuggest_input.js index 54ad6d264..cf5c8f04b 100644 --- a/app/soapbox/components/autosuggest_input.js +++ b/app/soapbox/components/autosuggest_input.js @@ -7,6 +7,7 @@ import { isRtl } from '../rtl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import classNames from 'classnames'; import { List as ImmutableList } from 'immutable'; +import Icon from 'soapbox/components/icon'; const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => { let word; @@ -231,7 +232,11 @@ export default class AutosuggestInput extends ImmutablePureComponent { onMouseDown={this.handleMenuItemClick(item)} key={i} > - {item.text} + {item.icon && ( + + )} + + {item.text} )); }; diff --git a/app/soapbox/features/compose/components/search.js b/app/soapbox/features/compose/components/search.js index 063e3f939..f14f79a84 100644 --- a/app/soapbox/features/compose/components/search.js +++ b/app/soapbox/features/compose/components/search.js @@ -90,7 +90,7 @@ class Search extends React.PureComponent { const { intl, value } = this.props; return [ - { text: intl.formatMessage(messages.action, { query: value }), action: this.handleSubmit }, + { text: intl.formatMessage(messages.action, { query: value }), icon: require('@tabler/icons/icons/search.svg'), action: this.handleSubmit }, ]; } diff --git a/app/styles/autosuggest.scss b/app/styles/autosuggest.scss index 8bf4b39dd..33a8f062d 100644 --- a/app/styles/autosuggest.scss +++ b/app/styles/autosuggest.scss @@ -104,7 +104,8 @@ } .autosuggest-input__action { - display: block; + display: flex; + align-items: center; padding: 10px; border-radius: 4px; font-weight: bold; @@ -115,4 +116,9 @@ &.selected { background-color: var(--brand-color--med); } + + .svg-icon { + margin-right: 8px; + transform: translateY(-1px); + } } From b32745a36d7282ff95bcd8eaec448ab1fa4ce11c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Nov 2021 14:07:02 -0500 Subject: [PATCH 7/8] Search: only show suggestions when not submitted --- app/soapbox/features/compose/components/search_results.js | 4 ++-- app/styles/components/search.scss | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/soapbox/features/compose/components/search_results.js b/app/soapbox/features/compose/components/search_results.js index e6004e4a3..58198eac4 100644 --- a/app/soapbox/features/compose/components/search_results.js +++ b/app/soapbox/features/compose/components/search_results.js @@ -45,9 +45,9 @@ export default class SearchResults extends ImmutablePureComponent { if (results.get('accounts') && results.get('accounts').size > 0) { searchResults = results.get('accounts').map(accountId => ); - } else if (suggestions && !suggestions.isEmpty()) { + } else if (!submitted && suggestions && !suggestions.isEmpty()) { searchResults = suggestions.map(suggestion => ); - } else if (submitted) { + } else if (loaded) { noResultsMessage = (
Date: Mon, 1 Nov 2021 14:21:40 -0500 Subject: [PATCH 8/8] Chats: fix ChatPane z-index above AccountHeader --- app/styles/chats.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/styles/chats.scss b/app/styles/chats.scss index 9266a43e5..ba324be24 100644 --- a/app/styles/chats.scss +++ b/app/styles/chats.scss @@ -9,7 +9,7 @@ max-height: calc(100vh - 70px); display: flex; flex-direction: column; - z-index: 999; + z-index: 1000; // Above AccountHeader transition: 0.05s; &--main {