diff --git a/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx b/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx
index 9ea7f190f..5591b45cf 100644
--- a/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx
+++ b/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx
@@ -1,11 +1,16 @@
import Avatar from '../../../components/avatar';
import DisplayName from '../../../components/display_name';
+import ImmutablePropTypes from 'react-immutable-proptypes';
const AutosuggestAccount = ({ account }) => (
-
+
);
+AutosuggestAccount.propTypes = {
+ account: ImmutablePropTypes.map.isRequired
+};
+
export default AutosuggestAccount;
diff --git a/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx b/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx
new file mode 100644
index 000000000..086488649
--- /dev/null
+++ b/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx
@@ -0,0 +1,15 @@
+import { FormattedMessage } from 'react-intl';
+import DisplayName from '../../../components/display_name';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+
+const AutosuggestStatus = ({ status }) => (
+
+ @{status.getIn(['account', 'acct'])} }} />
+
+);
+
+AutosuggestStatus.propTypes = {
+ status: ImmutablePropTypes.map.isRequired
+};
+
+export default AutosuggestStatus;
diff --git a/app/assets/javascripts/components/features/compose/components/search.jsx b/app/assets/javascripts/components/features/compose/components/search.jsx
index c1f23939d..a0e8f82fb 100644
--- a/app/assets/javascripts/components/features/compose/components/search.jsx
+++ b/app/assets/javascripts/components/features/compose/components/search.jsx
@@ -2,6 +2,7 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Autosuggest from 'react-autosuggest';
import AutosuggestAccountContainer from '../containers/autosuggest_account_container';
+import AutosuggestStatusContainer from '../containers/autosuggest_status_container';
import { debounce } from 'react-decoration';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
@@ -14,8 +15,10 @@ const getSuggestionValue = suggestion => suggestion.value;
const renderSuggestion = suggestion => {
if (suggestion.type === 'account') {
return
;
+ } else if (suggestion.type === 'hashtag') {
+ return
#{suggestion.id};
} else {
- return
#{suggestion.id}
+ return
;
}
};
@@ -78,8 +81,10 @@ const Search = React.createClass({
onSuggestionSelected (_, { suggestion }) {
if (suggestion.type === 'account') {
this.context.router.push(`/accounts/${suggestion.id}`);
- } else {
+ } else if(suggestion.type === 'hashtag') {
this.context.router.push(`/timelines/tag/${suggestion.id}`);
+ } else {
+ this.context.router.push(`/statuses/${suggestion.id}`);
}
},
diff --git a/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx b/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx
new file mode 100644
index 000000000..ef46eb09c
--- /dev/null
+++ b/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx
@@ -0,0 +1,15 @@
+import { connect } from 'react-redux';
+import AutosuggestStatus from '../components/autosuggest_status';
+import { makeGetStatus } from '../../../selectors';
+
+const makeMapStateToProps = () => {
+ const getStatus = makeGetStatus();
+
+ const mapStateToProps = (state, { id }) => ({
+ status: getStatus(state, id)
+ });
+
+ return mapStateToProps;
+};
+
+export default connect(makeMapStateToProps)(AutosuggestStatus);
diff --git a/app/assets/javascripts/components/reducers/accounts.jsx b/app/assets/javascripts/components/reducers/accounts.jsx
index f3938cee1..6ce41670d 100644
--- a/app/assets/javascripts/components/reducers/accounts.jsx
+++ b/app/assets/javascripts/components/reducers/accounts.jsx
@@ -90,7 +90,6 @@ export default function accounts(state = initialState, action) {
case REBLOGS_FETCH_SUCCESS:
case FAVOURITES_FETCH_SUCCESS:
case COMPOSE_SUGGESTIONS_READY:
- case SEARCH_SUGGESTIONS_READY:
case FOLLOW_REQUESTS_FETCH_SUCCESS:
case FOLLOW_REQUESTS_EXPAND_SUCCESS:
case BLOCKS_FETCH_SUCCESS:
@@ -98,6 +97,7 @@ export default function accounts(state = initialState, action) {
return normalizeAccounts(state, action.accounts);
case NOTIFICATIONS_REFRESH_SUCCESS:
case NOTIFICATIONS_EXPAND_SUCCESS:
+ case SEARCH_SUGGESTIONS_READY:
return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses);
case TIMELINE_REFRESH_SUCCESS:
case TIMELINE_EXPAND_SUCCESS:
diff --git a/app/assets/javascripts/components/reducers/search.jsx b/app/assets/javascripts/components/reducers/search.jsx
index 1767be2c6..e95f9ed79 100644
--- a/app/assets/javascripts/components/reducers/search.jsx
+++ b/app/assets/javascripts/components/reducers/search.jsx
@@ -32,7 +32,7 @@ const normalizeSuggestions = (state, value, accounts, hashtags, statuses) => {
value: `#${item}`
}));
- if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 && hashtags.indexOf(value) === -1) {
+ if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 && !value.startsWith('http://') && !value.startsWith('https://') && hashtags.indexOf(value) === -1) {
hashtagItems.unshift({
type: 'hashtag',
id: value,
@@ -40,9 +40,22 @@ const normalizeSuggestions = (state, value, accounts, hashtags, statuses) => {
});
}
+ if (hashtagItems.length > 0) {
+ newSuggestions.push({
+ title: 'hashtag',
+ items: hashtagItems
+ });
+ }
+ }
+
+ if (statuses.length > 0) {
newSuggestions.push({
- title: 'hashtag',
- items: hashtagItems
+ title: 'status',
+ items: statuses.map(item => ({
+ type: 'status',
+ id: item.id,
+ value: item.id
+ }))
});
}
diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss
index 4b1e86aca..057c61f91 100644
--- a/app/assets/stylesheets/components.scss
+++ b/app/assets/stylesheets/components.scss
@@ -1421,3 +1421,13 @@ button.active i.fa-retweet {
}
}
}
+
+.autosuggest-status {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+ strong {
+ font-weight: 500;
+ }
+}
diff --git a/app/models/account.rb b/app/models/account.rb
index c0cd2ff64..6968607a2 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -222,8 +222,9 @@ SQL
end
def search_for(terms, limit = 10)
+ terms = Arel.sql(connection.quote(terms.gsub(/['?\\:]/, ' ')))
textsearch = '(setweight(to_tsvector(\'simple\', accounts.display_name), \'A\') || setweight(to_tsvector(\'simple\', accounts.username), \'B\') || setweight(to_tsvector(\'simple\', coalesce(accounts.domain, \'\')), \'C\'))'
- query = 'to_tsquery(\'simple\', \'\'\' \' || ? || \' \'\'\' || \':*\')'
+ query = 'to_tsquery(\'simple\', \'\'\' \' || ' + terms + ' || \' \'\'\' || \':*\')'
sql = <