FEATURE: Autocomplete support on advanced search

This commit is contained in:
cpradio 2017-02-20 21:49:24 -05:00 committed by cpradio
parent 1dda998a4e
commit 38d7234018
4 changed files with 113 additions and 22 deletions

View File

@ -3,24 +3,26 @@ import { escapeExpression } from 'discourse/lib/utilities';
const REGEXP_BLOCKS = /(([^" \t\n\x0B\f\r]+)?(("[^"]+")?))/g;
const REGEXP_USERNAME_PREFIX = /(user:|@)/ig;
const REGEXP_CATEGORY_PREFIX = /(category:|#)/ig;
const REGEXP_GROUP_PREFIX = /group:/ig;
const REGEXP_BADGE_PREFIX = /badge:/ig;
const REGEXP_TAGS_PREFIX = /tags?:/ig;
const REGEXP_IN_PREFIX = /in:/ig;
const REGEXP_STATUS_PREFIX = /status:/ig;
const REGEXP_MIN_POST_COUNT_PREFIX = /min_post_count:/ig;
const REGEXP_POST_TIME_PREFIX = /(before|after):/ig;
const REGEXP_USERNAME_PREFIX = /^(user:|@)/ig;
const REGEXP_CATEGORY_PREFIX = /^(category:|#)/ig;
const REGEXP_GROUP_PREFIX = /^group:/ig;
const REGEXP_BADGE_PREFIX = /^badge:/ig;
const REGEXP_TAGS_PREFIX = /^(tags?:|#(?=[a-z0-9\-]+::tag))/ig;
const REGEXP_IN_PREFIX = /^in:/ig;
const REGEXP_STATUS_PREFIX = /^status:/ig;
const REGEXP_MIN_POST_COUNT_PREFIX = /^min_post_count:/ig;
const REGEXP_POST_TIME_PREFIX = /^(before|after):/ig;
const REGEXP_TAGS_SUFFIX = /::tag\s?$/ig;
const REGEXP_IN_MATCH = /in:(posted|watching|tracking|bookmarks|first|pinned|unpinned)/ig;
const REGEXP_SPECIAL_IN_LIKES_MATCH = /in:likes/ig;
const REGEXP_SPECIAL_IN_PRIVATE_MATCH = /in:private/ig;
const REGEXP_SPECIAL_IN_WIKI_MATCH = /in:wiki/ig;
const REGEXP_CATEGORY_SLUG = /(\#[a-zA-Z0-9\-:]+)/ig;
const REGEXP_CATEGORY_ID = /(category:[0-9]+)/ig;
const REGEXP_POST_TIME_WHEN = /(before|after)/ig;
const REGEXP_IN_MATCH = /^in:(posted|watching|tracking|bookmarks|first|pinned|unpinned)/ig;
const REGEXP_SPECIAL_IN_LIKES_MATCH = /^in:likes/ig;
const REGEXP_SPECIAL_IN_PRIVATE_MATCH = /^in:private/ig;
const REGEXP_SPECIAL_IN_WIKI_MATCH = /^in:wiki/ig;
const REGEXP_CATEGORY_SLUG = /^(\#[a-zA-Z0-9\-:]+)/ig;
const REGEXP_CATEGORY_ID = /^(category:[0-9]+)/ig;
const REGEXP_POST_TIME_WHEN = /^(before|after)/ig;
export default Em.Component.extend({
classNames: ['search-advanced-options'],
@ -228,7 +230,7 @@ export default Em.Component.extend({
if (match.length !== 0) {
const existingInput = _.isArray(tags) ? tags.join(',') : tags;
const userInput = match[0].replace(REGEXP_TAGS_PREFIX, '');
const userInput = match[0].replace(REGEXP_TAGS_PREFIX, '').replace(REGEXP_TAGS_SUFFIX, '');
if (existingInput !== userInput) {
this.set('searchedTerms.tags', (userInput.length !== 0) ? userInput.split(',') : []);

View File

@ -1,6 +1,12 @@
import computed from 'ember-addons/ember-computed-decorators';
import { on } from 'ember-addons/ember-computed-decorators';
import TextField from 'discourse/components/text-field';
import { findRawTemplate } from 'discourse/lib/raw-templates';
import { TAG_HASHTAG_POSTFIX } from 'discourse/lib/tag-hashtags';
import { SEPARATOR } from 'discourse/lib/category-hashtags';
import Category from 'discourse/models/category';
import { search as searchCategoryTag } from 'discourse/lib/category-tag-search';
import userSearch from 'discourse/lib/user-search';
export default TextField.extend({
@computed('searchService.searchContextEnabled')
@ -15,5 +21,46 @@ export default TextField.extend({
// at the top of the page
$(window).scrollTop(0);
this.$().focus();
},
@on("didInsertElement")
applyAutoComplete() {
this._super();
const $searchInput = this.$();
this._applyCategoryHashtagAutocomplete($searchInput);
this._applyUsernameAutocomplete($searchInput);
},
_applyCategoryHashtagAutocomplete($searchInput) {
const siteSettings = this.siteSettings;
$searchInput.autocomplete({
template: findRawTemplate('category-tag-autocomplete'),
key: '#',
width: '100%',
treatAsTextarea: true,
transformComplete(obj) {
if (obj.model) {
return Category.slugFor(obj.model, SEPARATOR);
} else {
return `${obj.text}${TAG_HASHTAG_POSTFIX}`;
}
},
dataSource(term) {
return searchCategoryTag(term, siteSettings);
}
});
},
_applyUsernameAutocomplete($searchInput) {
$searchInput.autocomplete({
template: findRawTemplate('user-selector-autocomplete'),
dataSource: term => userSearch({ term, undefined, includeGroups: true }),
key: "@",
width: '100%',
treatAsTextarea: true,
transformComplete: v => v.username || v.name
});
}
});

View File

@ -82,7 +82,7 @@ export default function(options) {
let prevTerm = null;
// input is handled differently
const isInput = this[0].tagName === "INPUT";
const isInput = this[0].tagName === "INPUT" && !options.treatAsTextarea;
let inputSelectedItems = [];
function closeAutocomplete() {
@ -175,8 +175,10 @@ export default function(options) {
wrap.width(width);
}
if(options.single) {
if(options.single && !options.width) {
this.css("width", "100%");
} else if (options.width) {
this.css("width", options.width);
} else {
this.width(150);
}
@ -258,7 +260,7 @@ export default function(options) {
me.parent().append(div);
if (!isInput) {
if (!isInput && !options.treatAsTextarea) {
vOffset = div.height();
if ((window.innerHeight - me.outerHeight() - $("header.d-header").innerHeight()) < vOffset) {

View File

@ -3,6 +3,12 @@ import { iconNode } from 'discourse/helpers/fa-icon-node';
import { avatarImg } from 'discourse/widgets/post';
import DiscourseURL from 'discourse/lib/url';
import { wantsNewWindow } from 'discourse/lib/intercept-click';
import { findRawTemplate } from 'discourse/lib/raw-templates';
import { TAG_HASHTAG_POSTFIX } from 'discourse/lib/tag-hashtags';
import { SEPARATOR } from 'discourse/lib/category-hashtags';
import Category from 'discourse/models/category';
import { search as searchCategoryTag } from 'discourse/lib/category-tag-search';
import userSearch from 'discourse/lib/user-search';
import { h } from 'virtual-dom';
@ -256,7 +262,11 @@ export default createWidget('header', {
this.updateHighlight();
if (this.state.searchVisible) {
Ember.run.schedule('afterRender', () => $('#search-term').focus().select());
Ember.run.schedule('afterRender', () => {
const $searchInput = $('#search-term');
$searchInput.focus().select();
this.applyAutocomplete($searchInput);
});
}
},
@ -340,6 +350,36 @@ export default createWidget('header', {
return Ember.get(ctx, 'type');
}
}
},
applyAutocomplete($searchInput) {
const siteSettings = this.siteSettings;
$searchInput.autocomplete({
template: findRawTemplate('category-tag-autocomplete'),
key: '#',
width: '100%',
treatAsTextarea: true,
transformComplete(obj) {
if (obj.model) {
return Category.slugFor(obj.model, SEPARATOR);
} else {
return `${obj.text}${TAG_HASHTAG_POSTFIX}`;
}
},
dataSource(term) {
return searchCategoryTag(term, siteSettings);
}
});
$searchInput.autocomplete({
template: findRawTemplate('user-selector-autocomplete'),
dataSource: term => userSearch({ term, undefined, includeGroups: true }),
key: "@",
width: '100%',
treatAsTextarea: true,
transformComplete: v => v.username || v.name
});
}
});