REFACTOR: reworks all the search-advanced-options panel (#10661)

* REFACTOR: reworks all the search-advanced-options panel

This commit includes the following changes:
- prevents any mutation of external (to the component) values
- get rid of observers
- uses @action
- minor UI tweaks
- dropped the unecessary debounce
- drops all the legacy code for badges/groups which is not being used
- replaces user-selector by user-chooser and improves multi-select to not show `search` if maximum has been reached

Most importantly this refactor should fix multiple bugs due to _update() being called multiple times if searchTerm was empty and other various bugs where some changes in searchTerm was not applied to the sidebar.
This commit is contained in:
Joffrey JAFFEUX 2020-09-15 09:39:12 +02:00 committed by GitHub
parent bbddce4d3a
commit 324aa3eb61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 307 additions and 311 deletions

View File

@ -1,19 +1,13 @@
import I18n from "I18n"; import I18n from "I18n";
import { debounce, scheduleOnce } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import { observes } from "discourse-common/utils/decorators"; import { action } from "@ember/object";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
import Group from "discourse/models/group";
import Badge from "discourse/models/badge";
import Category from "discourse/models/category"; import Category from "discourse/models/category";
import { INPUT_DELAY } from "discourse-common/config/environment";
const REGEXP_BLOCKS = /(([^" \t\n\x0B\f\r]+)?(("[^"]+")?))/g; const REGEXP_BLOCKS = /(([^" \t\n\x0B\f\r]+)?(("[^"]+")?))/g;
const REGEXP_USERNAME_PREFIX = /^(user:|@)/gi; const REGEXP_USERNAME_PREFIX = /^(user:|@)/gi;
const REGEXP_CATEGORY_PREFIX = /^(category:|#)/gi; const REGEXP_CATEGORY_PREFIX = /^(category:|#)/gi;
const REGEXP_GROUP_PREFIX = /^group:/gi;
const REGEXP_BADGE_PREFIX = /^badge:/gi;
const REGEXP_TAGS_PREFIX = /^(tags?:|#(?=[a-z0-9\-]+::tag))/gi; const REGEXP_TAGS_PREFIX = /^(tags?:|#(?=[a-z0-9\-]+::tag))/gi;
const REGEXP_IN_PREFIX = /^(in|with):/gi; const REGEXP_IN_PREFIX = /^(in|with):/gi;
const REGEXP_STATUS_PREFIX = /^status:/gi; const REGEXP_STATUS_PREFIX = /^status:/gi;
@ -77,30 +71,17 @@ function addAdvancedSearchOptions(options) {
export default Component.extend({ export default Component.extend({
classNames: ["search-advanced-options"], classNames: ["search-advanced-options"],
category: null,
init() { init() {
this._super(...arguments); this._super(...arguments);
this._init();
scheduleOnce("afterRender", this, this._update);
},
@observes("searchTerm")
_updateOptions() {
this._update();
debounce(this, this._update, INPUT_DELAY);
},
_init() {
this.setProperties({ this.setProperties({
searchedTerms: { searchedTerms: {
username: "", username: null,
category: "", category: null,
group: [], tags: null,
badge: [], in: null,
tags: [],
in: "",
special: { special: {
in: { in: {
title: false, title: false,
@ -110,11 +91,11 @@ export default Component.extend({
}, },
all_tags: false, all_tags: false,
}, },
status: "", status: null,
min_post_count: "", min_post_count: null,
time: { time: {
when: "before", when: "before",
days: "", days: null,
}, },
}, },
inOptions: this.currentUser inOptions: this.currentUser
@ -125,16 +106,11 @@ export default Component.extend({
}); });
}, },
_update() { didReceiveAttrs() {
if (!this.searchTerm) { this._super(...arguments);
this._init();
return;
}
this.setSearchedTermValue("searchedTerms.username", REGEXP_USERNAME_PREFIX); this.setSearchedTermValue("searchedTerms.username", REGEXP_USERNAME_PREFIX);
this.setSearchedTermValueForCategory(); this.setSearchedTermValueForCategory();
this.setSearchedTermValueForGroup();
this.setSearchedTermValueForBadge();
this.setSearchedTermValueForTags(); this.setSearchedTermValueForTags();
let regExpInMatch = this.inOptions.map((option) => option.value).join("|"); let regExpInMatch = this.inOptions.map((option) => option.value).join("|");
@ -216,14 +192,14 @@ export default Component.extend({
const match = this.filterBlocks(matchRegEx); const match = this.filterBlocks(matchRegEx);
let val = this.get(key); let val = this.get(key);
if (match.length !== 0) { if (match.length !== 0) {
const userInput = match[0].replace(replaceRegEx, ""); const userInput = match[0].replace(replaceRegEx, "");
if (val !== userInput) {
if (val !== userInput && userInput.length) {
this.set(key, userInput); this.set(key, userInput);
} }
} else if (val && val.length !== 0) { } else if (val && val.length !== 0) {
this.set(key, ""); this.set(key, null);
} }
}, },
@ -239,11 +215,6 @@ export default Component.extend({
} }
}, },
setCategory(category) {
this.set("searchedTerms.category", category);
this.set("category", category);
},
setSearchedTermValueForCategory() { setSearchedTermValueForCategory() {
const match = this.filterBlocks(REGEXP_CATEGORY_PREFIX); const match = this.filterBlocks(REGEXP_CATEGORY_PREFIX);
if (match.length !== 0) { if (match.length !== 0) {
@ -259,61 +230,28 @@ export default Component.extend({
if ( if (
(!existingInput && userInput) || (!existingInput && userInput) ||
(existingInput && userInput && existingInput.id !== userInput.id) (existingInput && userInput && existingInput.id !== userInput.id)
) ) {
this.setCategory(userInput); this.set("searchedTerms.category", userInput);
}
} else if (isNaN(subcategories)) { } else if (isNaN(subcategories)) {
const userInput = Category.findSingleBySlug(subcategories[0]); const userInput = Category.findSingleBySlug(subcategories[0]);
if ( if (
(!existingInput && userInput) || (!existingInput && userInput) ||
(existingInput && userInput && existingInput.id !== userInput.id) (existingInput && userInput && existingInput.id !== userInput.id)
) ) {
this.setCategory(userInput); this.set("searchedTerms.category", userInput);
}
} else { } else {
const userInput = Category.findById(subcategories[0]); const userInput = Category.findById(subcategories[0]);
if ( if (
(!existingInput && userInput) || (!existingInput && userInput) ||
(existingInput && userInput && existingInput.id !== userInput.id) (existingInput && userInput && existingInput.id !== userInput.id)
) ) {
this.setCategory(userInput); this.set("searchedTerms.category", userInput);
}
} }
} else this.set("searchedTerms.category", ""); } else {
}, this.set("searchedTerms.category", null);
setSearchedTermValueForGroup() {
const match = this.filterBlocks(REGEXP_GROUP_PREFIX);
const group = this.get("searchedTerms.group");
if (match.length !== 0) {
const existingInput = Array.isArray(group) ? group[0] : group;
const userInput = match[0].replace(REGEXP_GROUP_PREFIX, "");
if (existingInput !== userInput) {
this.set(
"searchedTerms.group",
userInput.length !== 0 ? [userInput] : []
);
}
} else if (group.length !== 0) {
this.set("searchedTerms.group", []);
}
},
setSearchedTermValueForBadge() {
const match = this.filterBlocks(REGEXP_BADGE_PREFIX);
const badge = this.get("searchedTerms.badge");
if (match.length !== 0) {
const existingInput = Array.isArray(badge) ? badge[0] : badge;
const userInput = match[0].replace(REGEXP_BADGE_PREFIX, "");
if (existingInput !== userInput) {
this.set(
"searchedTerms.badge",
userInput.length !== 0 ? [userInput] : []
);
}
} else if (badge.length !== 0) {
this.set("searchedTerms.badge", []);
} }
}, },
@ -322,21 +260,24 @@ export default Component.extend({
const match = this.filterBlocks(REGEXP_TAGS_PREFIX); const match = this.filterBlocks(REGEXP_TAGS_PREFIX);
const tags = this.get("searchedTerms.tags"); const tags = this.get("searchedTerms.tags");
const contain_all_tags = this.get("searchedTerms.special.all_tags"); if (match.length) {
this.set("searchedTerms.special.all_tags", match[0].includes("+"));
}
const containAllTags = this.get("searchedTerms.special.all_tags");
if (match.length !== 0) { if (match.length !== 0) {
const join_char = contain_all_tags ? "+" : ","; const joinChar = containAllTags ? "+" : ",";
const existingInput = Array.isArray(tags) ? tags.join(join_char) : tags; const existingInput = Array.isArray(tags) ? tags.join(joinChar) : tags;
const userInput = match[0].replace(REGEXP_TAGS_REPLACE, ""); const userInput = match[0].replace(REGEXP_TAGS_REPLACE, "");
if (existingInput !== userInput) { if (existingInput !== userInput) {
this.set( this.set(
"searchedTerms.tags", "searchedTerms.tags",
userInput.length !== 0 ? userInput.split(join_char) : [] userInput.length !== 0 ? userInput.split(joinChar) : null
); );
} }
} else if (tags.length !== 0) { } else if (!tags) {
this.set("searchedTerms.tags", []); this.set("searchedTerms.tags", null);
} }
}, },
@ -360,32 +301,141 @@ export default Component.extend({
this.setProperties(properties); this.setProperties(properties);
} else { } else {
this.set("searchedTerms.time.days", ""); this.set("searchedTerms.time.when", "before");
this.set("searchedTerms.time.days", null);
} }
}, },
@observes("searchedTerms.username") updateInRegex(regex, filter) {
updateSearchTermForUsername() { const match = this.filterBlocks(regex);
const match = this.filterBlocks(REGEXP_USERNAME_PREFIX); const inFilter = this.get("searchedTerms.special.in." + filter);
const userFilter = this.get("searchedTerms.username");
let searchTerm = this.searchTerm || ""; let searchTerm = this.searchTerm || "";
if (userFilter && userFilter.length !== 0) { if (inFilter) {
if (match.length !== 0) { if (match.length === 0) {
searchTerm = searchTerm.replace(match[0], `@${userFilter}`); searchTerm += ` in:${filter}`;
} else { this._updateSearchTerm(searchTerm);
searchTerm += ` @${userFilter}`;
} }
this.set("searchTerm", searchTerm.trim());
} else if (match.length !== 0) { } else if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], ""); searchTerm = searchTerm.replace(match, "");
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} }
}, },
@observes("searchedTerms.category") @action
updateSearchTermForCategory() { onChangeSearchTermMinPostCount(value) {
this.set("searchedTerms.min_post_count", value.length ? value : null);
this._updateSearchTermForMinPostCount();
},
@action
onChangeSearchTermForIn(value) {
this.set("searchedTerms.in", value);
this._updateSearchTermForIn();
},
@action
onChangeSearchTermForStatus(value) {
this.set("searchedTerms.status", value);
this._updateSearchTermForStatus();
},
@action
onChangeWhenTime(time) {
if (time) {
this.set("searchedTerms.time.when", time);
this._updateSearchTermForPostTime();
}
},
@action
onChangeWhenDate(date) {
if (date) {
this.set("searchedTerms.time.days", date.format("YYYY-MM-DD"));
this._updateSearchTermForPostTime();
}
},
@action
onChangeSearchTermForCategory(categoryId) {
if (categoryId) {
const category = Category.findById(categoryId);
this.onChangeCategory && this.onChangeCategory(category);
this.set("searchedTerms.category", category);
} else {
this.onChangeCategory && this.onChangeCategory(null);
this.set("searchedTerms.category", null);
}
this._updateSearchTermForCategory();
},
@action
onChangeSearchTermForUsername(username) {
this.set("searchedTerms.username", username.length ? username : null);
this._updateSearchTermForUsername();
},
@action
onChangeSearchTermForTags(tags) {
this.set("searchedTerms.tags", tags.length ? tags : null);
this._updateSearchTermForTags();
},
@action
onChangeSearchTermForAllTags(checked) {
this.set("searchedTerms.special.all_tags", checked);
this._updateSearchTermForTags();
},
@action
onChangeSearchTermForSpecialInLikes(checked) {
this.set("searchedTerms.special.in.likes", checked);
this.updateInRegex(REGEXP_SPECIAL_IN_LIKES_MATCH, "likes");
},
@action
onChangeSearchTermForSpecialInPersonal(checked) {
this.set("searchedTerms.special.in.personal", checked);
this.updateInRegex(REGEXP_SPECIAL_IN_PERSONAL_MATCH, "personal");
},
@action
onChangeSearchTermForSpecialInSeen(checked) {
this.set("searchedTerms.special.in.seen", checked);
this.updateInRegex(REGEXP_SPECIAL_IN_SEEN_MATCH, "seen");
},
@action
onChangeSearchTermForSpecialInTitle(checked) {
this.set("searchedTerms.special.in.title", checked);
this.updateInRegex(REGEXP_SPECIAL_IN_TITLE_MATCH, "title");
},
_updateSearchTermForTags() {
const match = this.filterBlocks(REGEXP_TAGS_PREFIX);
const tagFilter = this.get("searchedTerms.tags");
let searchTerm = this.searchTerm || "";
const containAllTags = this.get("searchedTerms.special.all_tags");
if (tagFilter && tagFilter.length !== 0) {
const joinChar = containAllTags ? "+" : ",";
const tags = tagFilter.join(joinChar);
if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], `tags:${tags}`);
} else {
searchTerm += ` tags:${tags}`;
}
this._updateSearchTerm(searchTerm);
} else if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], "");
this._updateSearchTerm(searchTerm);
}
},
_updateSearchTermForCategory() {
const match = this.filterBlocks(REGEXP_CATEGORY_PREFIX); const match = this.filterBlocks(REGEXP_CATEGORY_PREFIX);
const categoryFilter = this.get("searchedTerms.category"); const categoryFilter = this.get("searchedTerms.category");
let searchTerm = this.searchTerm || ""; let searchTerm = this.searchTerm || "";
@ -411,7 +461,7 @@ export default Component.extend({
); );
else searchTerm += ` #${parentSlug}:${slug}`; else searchTerm += ` #${parentSlug}:${slug}`;
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} else { } else {
if (slugCategoryMatches) if (slugCategoryMatches)
searchTerm = searchTerm.replace(slugCategoryMatches[0], `#${slug}`); searchTerm = searchTerm.replace(slugCategoryMatches[0], `#${slug}`);
@ -422,7 +472,7 @@ export default Component.extend({
); );
else searchTerm += ` #${slug}`; else searchTerm += ` #${slug}`;
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} }
} else { } else {
if (slugCategoryMatches) if (slugCategoryMatches)
@ -430,76 +480,50 @@ export default Component.extend({
if (idCategoryMatches) if (idCategoryMatches)
searchTerm = searchTerm.replace(idCategoryMatches[0], ""); searchTerm = searchTerm.replace(idCategoryMatches[0], "");
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} }
}, },
@observes("searchedTerms.group") _updateSearchTermForUsername() {
updateSearchTermForGroup() { const match = this.filterBlocks(REGEXP_USERNAME_PREFIX);
const match = this.filterBlocks(REGEXP_GROUP_PREFIX); const userFilter = this.get("searchedTerms.username");
const groupFilter = this.get("searchedTerms.group");
let searchTerm = this.searchTerm || ""; let searchTerm = this.searchTerm || "";
if (groupFilter && groupFilter.length !== 0) { if (userFilter && userFilter.length !== 0) {
if (match.length !== 0) { if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], ` group:${groupFilter}`); searchTerm = searchTerm.replace(match[0], `@${userFilter}`);
} else { } else {
searchTerm += ` group:${groupFilter}`; searchTerm += ` @${userFilter}`;
} }
this.set("searchTerm", searchTerm); this._updateSearchTerm(searchTerm);
} else if (match.length !== 0) { } else if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], ""); searchTerm = searchTerm.replace(match[0], "");
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} }
}, },
@observes("searchedTerms.badge") _updateSearchTermForPostTime() {
updateSearchTermForBadge() { const match = this.filterBlocks(REGEXP_POST_TIME_PREFIX);
const match = this.filterBlocks(REGEXP_BADGE_PREFIX); const timeDaysFilter = this.get("searchedTerms.time.days");
const badgeFilter = this.get("searchedTerms.badge");
let searchTerm = this.searchTerm || ""; let searchTerm = this.searchTerm || "";
if (badgeFilter && badgeFilter.length !== 0) { if (timeDaysFilter) {
const when = this.get("searchedTerms.time.when");
if (match.length !== 0) { if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], ` badge:${badgeFilter}`); searchTerm = searchTerm.replace(match[0], `${when}:${timeDaysFilter}`);
} else { } else {
searchTerm += ` badge:${badgeFilter}`; searchTerm += ` ${when}:${timeDaysFilter}`;
} }
this.set("searchTerm", searchTerm); this._updateSearchTerm(searchTerm);
} else if (match.length !== 0) { } else if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], ""); searchTerm = searchTerm.replace(match[0], "");
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} }
}, },
@observes("searchedTerms.tags", "searchedTerms.special.all_tags") _updateSearchTermForIn() {
updateSearchTermForTags() {
const match = this.filterBlocks(REGEXP_TAGS_PREFIX);
const tagFilter = this.get("searchedTerms.tags");
let searchTerm = this.searchTerm || "";
const contain_all_tags = this.get("searchedTerms.special.all_tags");
if (tagFilter && tagFilter.length !== 0) {
const join_char = contain_all_tags ? "+" : ",";
const tags = tagFilter.join(join_char);
if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], `tags:${tags}`);
} else {
searchTerm += ` tags:${tags}`;
}
this.set("searchTerm", searchTerm.trim());
} else if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], "");
this.set("searchTerm", searchTerm.trim());
}
},
@observes("searchedTerms.in")
updateSearchTermForIn() {
let regExpInMatch = this.inOptions.map((option) => option.value).join("|"); let regExpInMatch = this.inOptions.map((option) => option.value).join("|");
const REGEXP_IN_MATCH = new RegExp(`(in|with):(${regExpInMatch})`); const REGEXP_IN_MATCH = new RegExp(`(in|with):(${regExpInMatch})`);
@ -518,51 +542,14 @@ export default Component.extend({
searchTerm += ` ${keyword}:${inFilter}`; searchTerm += ` ${keyword}:${inFilter}`;
} }
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} else if (match.length !== 0) { } else if (match.length !== 0) {
searchTerm = searchTerm.replace(match, ""); searchTerm = searchTerm.replace(match, "");
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} }
}, },
updateInRegex(regex, filter) { _updateSearchTermForStatus() {
const match = this.filterBlocks(regex);
const inFilter = this.get("searchedTerms.special.in." + filter);
let searchTerm = this.searchTerm || "";
if (inFilter) {
if (match.length === 0) {
searchTerm += ` in:${filter}`;
this.set("searchTerm", searchTerm.trim());
}
} else if (match.length !== 0) {
searchTerm = searchTerm.replace(match, "");
this.set("searchTerm", searchTerm.trim());
}
},
@observes("searchedTerms.special.in.likes")
updateSearchTermForSpecialInLikes() {
this.updateInRegex(REGEXP_SPECIAL_IN_LIKES_MATCH, "likes");
},
@observes("searchedTerms.special.in.personal")
updateSearchTermForSpecialInPersonal() {
this.updateInRegex(REGEXP_SPECIAL_IN_PERSONAL_MATCH, "personal");
},
@observes("searchedTerms.special.in.seen")
updateSearchTermForSpecialInSeen() {
this.updateInRegex(REGEXP_SPECIAL_IN_SEEN_MATCH, "seen");
},
@observes("searchedTerms.special.in.title")
updateSearchTermForSpecialInTitle() {
this.updateInRegex(REGEXP_SPECIAL_IN_TITLE_MATCH, "title");
},
@observes("searchedTerms.status")
updateSearchTermForStatus() {
let regExpStatusMatch = this.statusOptions let regExpStatusMatch = this.statusOptions
.map((status) => status.value) .map((status) => status.value)
.join("|"); .join("|");
@ -579,35 +566,14 @@ export default Component.extend({
searchTerm += ` status:${statusFilter}`; searchTerm += ` status:${statusFilter}`;
} }
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} else if (match.length !== 0) { } else if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], ""); searchTerm = searchTerm.replace(match[0], "");
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} }
}, },
updateSearchTermForPostTime() { _updateSearchTermForMinPostCount() {
const match = this.filterBlocks(REGEXP_POST_TIME_PREFIX);
const timeDaysFilter = this.get("searchedTerms.time.days");
let searchTerm = this.searchTerm || "";
if (timeDaysFilter) {
const when = this.get("searchedTerms.time.when");
if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], `${when}:${timeDaysFilter}`);
} else {
searchTerm += ` ${when}:${timeDaysFilter}`;
}
this.set("searchTerm", searchTerm.trim());
} else if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], "");
this.set("searchTerm", searchTerm.trim());
}
},
@observes("searchedTerms.min_post_count")
updateSearchTermForMinPostCount() {
const match = this.filterBlocks(REGEXP_MIN_POST_COUNT_PREFIX); const match = this.filterBlocks(REGEXP_MIN_POST_COUNT_PREFIX);
const postsCountFilter = this.get("searchedTerms.min_post_count"); const postsCountFilter = this.get("searchedTerms.min_post_count");
let searchTerm = this.searchTerm || ""; let searchTerm = this.searchTerm || "";
@ -622,42 +588,15 @@ export default Component.extend({
searchTerm += ` min_post_count:${postsCountFilter}`; searchTerm += ` min_post_count:${postsCountFilter}`;
} }
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} else if (match.length !== 0) { } else if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], ""); searchTerm = searchTerm.replace(match[0], "");
this.set("searchTerm", searchTerm.trim()); this._updateSearchTerm(searchTerm);
} }
}, },
groupFinder(term) { _updateSearchTerm(searchTerm) {
return Group.findAll({ term: term, ignore_automatic: false }); this.onChangeSearchTerm(searchTerm.trim());
},
badgeFinder(term) {
return Badge.findAll({ search: term });
},
actions: {
onChangeWhenTime(time) {
if (time) {
this.set("searchedTerms.time.when", time);
this.updateSearchTermForPostTime();
}
},
onChangeWhenDate(date) {
if (date) {
this.set("searchedTerms.time.days", moment(date).format("YYYY-MM-DD"));
this.updateSearchTermForPostTime();
}
},
onChangeCategory(categoryId) {
if (categoryId) {
this.set("searchedTerms.category", Category.findById(categoryId));
} else {
this.set("searchedTerms.category", null);
}
},
}, },
}); });

View File

@ -2,14 +2,17 @@
<div class="container advanced-search-posted-by-group"> <div class="container advanced-search-posted-by-group">
<div class="control-group pull-left"> <div class="control-group pull-left">
<label class="control-label" for="search-posted-by">{{i18n "search.advanced.posted_by.label"}}</label> <label class="control-label" for="search-posted-by">
{{i18n "search.advanced.posted_by.label"}}
</label>
<div class="controls"> <div class="controls">
{{user-selector {{user-chooser
excludeCurrentUser=false value=searchedTerms.username
usernames=searchedTerms.username onChange=(action "onChangeSearchTermForUsername")
single=true options=(hash
canReceiveUpdates=true maximum=1
class="user-selector" excludeCurrentUser=false
)
}} }}
</div> </div>
</div> </div>
@ -18,28 +21,12 @@
<div class="controls"> <div class="controls">
{{search-advanced-category-chooser {{search-advanced-category-chooser
value=searchedTerms.category.id value=searchedTerms.category.id
onChange=(action "onChangeCategory") onChange=(action "onChangeSearchTermForCategory")
}} }}
</div> </div>
</div> </div>
</div> </div>
{{!-- disable these super-advanced searches for now
<div class="container">
<div class="control-group pull-left">
<label class="control-label" for="search-in-group">{{i18n "search.advanced.in_group.label"}}</label>
<div class="controls">
{{group-selector groupFinder=groupFinder groupNames=searchedTerms.group single="true" canReceiveUpdates="true"}}
</div>
</div>
<div class="control-group pull-left">
<label class="control-label" for="search-with-badge">{{i18n "search.advanced.with_badge.label"}}</label>
<div class="controls">
{{badge-selector badgeFinder=badgeFinder badgeNames=searchedTerms.badge single="true" canReceiveUpdates="true"}}
</div>
</div>
</div> --}}
{{#if siteSettings.tagging_enabled}} {{#if siteSettings.tagging_enabled}}
<div class="container advanced-search-tag-group"> <div class="container advanced-search-tag-group">
<div class="control-group"> <div class="control-group">
@ -50,10 +37,18 @@
allowCreate=false allowCreate=false
everyTag=true everyTag=true
unlimitedTagCount=true unlimitedTagCount=true
onChange=(action (mut searchedTerms.tags)) onChange=(action "onChangeSearchTermForTags")
}} }}
<section class="field"> <section class="field">
<label>{{ input type="checkbox" class="all-tags" checked=searchedTerms.special.all_tags}} {{i18n "search.advanced.filters.all_tags"}} </label> <label>
{{input
type="checkbox"
class="all-tags"
checked=searchedTerms.special.all_tags
click=(action "onChangeSearchTermForAllTags" value="target.checked")
}}
{{i18n "search.advanced.filters.all_tags"}}
</label>
</section> </section>
</div> </div>
</div> </div>
@ -66,10 +61,42 @@
<div class="controls"> <div class="controls">
{{#if currentUser}} {{#if currentUser}}
<section class="field"> <section class="field">
<label>{{input type="checkbox" class="in-title" checked=searchedTerms.special.in.title}} {{i18n "search.advanced.filters.title"}}</label> <label>
<label>{{input type="checkbox" class="in-likes" checked=searchedTerms.special.in.likes}} {{i18n "search.advanced.filters.likes"}}</label> {{input
<label>{{input type="checkbox" class="in-private" checked=searchedTerms.special.in.personal}} {{i18n "search.advanced.filters.private"}}</label> type="checkbox"
<label>{{input type="checkbox" class="in-seen" checked=searchedTerms.special.in.seen}} {{i18n "search.advanced.filters.seen"}}</label> class="in-title"
checked=searchedTerms.special.in.title
click=(action "onChangeSearchTermForSpecialInTitle" value="target.checked")
}}
{{i18n "search.advanced.filters.title"}}
</label>
<label>
{{input
type="checkbox"
class="in-likes"
checked=searchedTerms.special.in.likes
click=(action "onChangeSearchTermForSpecialInLikes" value="target.checked")
}}
{{i18n "search.advanced.filters.likes"}}
</label>
<label>
{{input
type="checkbox"
class="in-private"
checked=searchedTerms.special.in.personal
click=(action "onChangeSearchTermForSpecialInPersonal" value="target.checked")
}}
{{i18n "search.advanced.filters.private"}}
</label>
<label>
{{input
type="checkbox"
class="in-seen"
checked=searchedTerms.special.in.seen
click=(action "onChangeSearchTermForSpecialInSeen" value="target.checked")
}}
{{i18n "search.advanced.filters.seen"}}
</label>
</section> </section>
{{/if}} {{/if}}
{{combo-box {{combo-box
@ -77,8 +104,11 @@
valueProperty="value" valueProperty="value"
content=inOptions content=inOptions
value=searchedTerms.in value=searchedTerms.in
none="user.locale.any" onChange=(action "onChangeSearchTermForIn")
onChange=(action (mut searchedTerms.in)) options=(hash
none="user.locale.any"
clearable=true
)
}} }}
</div> </div>
</div> </div>
@ -90,8 +120,11 @@
valueProperty="value" valueProperty="value"
content=statusOptions content=statusOptions
value=searchedTerms.status value=searchedTerms.status
none="user.locale.any" onChange=(action "onChangeSearchTermForStatus")
onChange=(action (mut searchedTerms.status)) options=(hash
none="user.locale.any"
clearable=true
)
}} }}
</div> </div>
</div> </div>
@ -118,7 +151,13 @@
<div class="control-group pull-left"> <div class="control-group pull-left">
<label class="control-label" for="search-min-post-count">{{i18n "search.advanced.post.count.label"}}</label> <label class="control-label" for="search-min-post-count">{{i18n "search.advanced.post.count.label"}}</label>
<div class="controls"> <div class="controls">
{{input type="number" value=searchedTerms.min_post_count class="input-small" id="search-min-post-count"}} {{input
type="number"
value=(readonly searchedTerms.min_post_count)
class="input-small"
id="search-min-post-count"
input=(action "onChangeSearchTermMinPostCount" value="target.value")
}}
</div> </div>
</div> </div>
</div> </div>

View File

@ -198,11 +198,10 @@
{{/if}} {{/if}}
{{#if site.mobileView}} {{#if site.mobileView}}
{{d-button <div role="button" class="search-advanced-title" {{on "click" (action "toggleAdvancedSearch")}}>
class="search-advanced-title" {{d-icon (if expanded "caret-down" "caret-right")}}
action=(action "toggleAdvancedSearch") <span>{{i18n "search.advanced.title"}}</span>
icon=(if expanded "caret-down" "caret-right") </div>
label="search.advanced.title"}}
{{else}} {{else}}
<span class="search-advanced-title"> <span class="search-advanced-title">
{{i18n "search.advanced.title"}} {{i18n "search.advanced.title"}}
@ -213,17 +212,17 @@
{{#if expanded}} {{#if expanded}}
<div class="search-advanced-filters"> <div class="search-advanced-filters">
{{search-advanced-options {{search-advanced-options
searchTerm=searchTerm searchTerm=(readonly searchTerm)
isExpanded=expanded onChangeSearchTerm=(action (mut searchTerm))
}} }}
</div> </div>
{{/if}} {{/if}}
{{else}} {{else}}
<div class="search-advanced-filters"> <div class="search-advanced-filters">
{{search-advanced-options {{search-advanced-options
searchTerm=searchTerm searchTerm=(readonly searchTerm)
isExpanded=true onChangeSearchTerm=(action (mut searchTerm))
category=category onChangeCategory=(action (mut category))
}} }}
{{d-button {{d-button

View File

@ -11,6 +11,14 @@ export default SelectKitHeaderComponent.extend({
return makeArray(this.selectedContent).map((c) => this.getName(c)); return makeArray(this.selectedContent).map((c) => this.getName(c));
}), }),
hasReachedMaximumSelection: computed("selectedValue", function () {
if (!this.selectKit.options.maximum) {
return false;
}
return this.selectedValue.length >= this.selectKit.options.maximum;
}),
selectedValue: computed("selectedContent", function () { selectedValue: computed("selectedContent", function () {
return makeArray(this.selectedContent) return makeArray(this.selectedContent)
.map((c) => { .map((c) => {

View File

@ -7,9 +7,11 @@
}} }}
{{/each}} {{/each}}
<div class="choice input-wrapper"> {{#unless hasReachedMaximumSelection}}
{{component selectKit.options.filterComponent <div class="choice input-wrapper">
selectKit=selectKit {{component selectKit.options.filterComponent
}} selectKit=selectKit
</div> }}
</div>
{{/unless}}
</div> </div>

View File

@ -12,7 +12,6 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
justify-content: space-between;
margin-bottom: 1em; margin-bottom: 1em;
.search-query { .search-query {
@ -133,11 +132,7 @@
} }
font-weight: 700; font-weight: 700;
text-align: left; text-align: left;
font-weight: bold; cursor: pointer;
&.btn {
background: var(--primary-low);
}
.d-icon { .d-icon {
margin: 0; margin: 0;

View File

@ -34,7 +34,8 @@
width: 100%; width: 100%;
margin: 0; margin: 0;
.tag-chooser { .tag-chooser,
.user-chooser {
width: 100%; width: 100%;
} }
} }

View File

@ -1,5 +1,5 @@
import selectKit from "helpers/select-kit-helper"; import selectKit from "helpers/select-kit-helper";
import { acceptance, waitFor } from "helpers/qunit-helpers"; import { selectDate, acceptance, waitFor } from "helpers/qunit-helpers";
acceptance("Search - Full Page", { acceptance("Search - Full Page", {
settings: { tagging_enabled: true }, settings: { tagging_enabled: true },
@ -109,7 +109,7 @@ QUnit.test("escape search term", async (assert) => {
assert.ok( assert.ok(
exists( exists(
'.search-advanced-options span:contains("<script>prompt(1337)</script>gmail.com")' '.search-advanced-options span:contains("&lt;script&gt;prompt(1337)&lt;/script&gt;gmail.com")'
), ),
"it escapes search term" "it escapes search term"
); );
@ -355,7 +355,7 @@ QUnit.test("update post time through advanced search ui", async (assert) => {
await visit("/search"); await visit("/search");
await fillIn(".search-query", "none"); await fillIn(".search-query", "none");
await fillIn("#search-post-date .date-picker", "2016-10-05"); await selectDate("#search-post-date .date-picker", "2016-10-05");
const postTimeSelector = selectKit( const postTimeSelector = selectKit(
".search-advanced-options .select-kit#postTime" ".search-advanced-options .select-kit#postTime"

View File

@ -1,3 +1,4 @@
import { Promise } from "rsvp";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { later } from "@ember/runloop"; import { later } from "@ember/runloop";
/* global QUnit, resetSite */ /* global QUnit, resetSite */
@ -283,3 +284,15 @@ export function waitFor(assert, callback, timeout) {
done(); done();
}, timeout); }, timeout);
} }
export async function selectDate(selector, date) {
return new Promise((resolve) => {
const elem = document.querySelector(selector);
elem.value = date;
const evt = new Event("input", { bubbles: true, cancelable: false });
elem.dispatchEvent(evt);
elem.blur();
resolve();
});
}