client/search: autocomplete negated tags in search fields

This doesn't apply to tag inputs that aren't for searching, like the tag
input box when editing a post.
This commit is contained in:
Eva
2025-04-04 10:58:35 +02:00
parent 782f069031
commit 7eff539df5
6 changed files with 31 additions and 8 deletions

View File

@ -228,6 +228,13 @@ class AutoCompleteControl {
}
_updateResults(textToFind) {
if (this._options.isNegationAllowed && textToFind === "-") {
this._results = [];
this._activeResult = -1;
this._refreshList();
return;
}
this._options.getMatches(textToFind).then((matches) => {
const oldResults = this._results.slice();
this._results = matches.slice(0, this._options.maxResults);

View File

@ -31,9 +31,9 @@ class PoolAutoCompleteControl extends AutoCompleteControl {
options.getMatches = (text) => {
const term = misc.escapeSearchTerm(text);
const query =
(text.length < minLengthForPartialSearch
? term + "*"
: "*" + term + "*") + " sort:post-count";
(text.length >= minLengthForPartialSearch
? "*" + term + "*"
: term + "*") + " sort:post-count";
return new Promise((resolve, reject) => {
PoolList.search(query, 0, this._options.maxResults, [

View File

@ -5,7 +5,7 @@ const views = require("../util/views.js");
const TagList = require("../models/tag_list.js");
const AutoCompleteControl = require("./auto_complete_control.js");
function _tagListToMatches(tags, options) {
function _tagListToMatches(tags, options, negated) {
return [...tags]
.sort((tag1, tag2) => {
return tag2.usages - tag1.usages;
@ -15,6 +15,9 @@ function _tagListToMatches(tags, options) {
if (options.isTaggedWith(tag.names[0])) {
cssName += " disabled";
}
if (negated) {
tag.names = tag.names.map((tagName) => "-"+tagName);
}
const caption =
'<span class="' +
cssName +
@ -35,16 +38,26 @@ class TagAutoCompleteControl extends AutoCompleteControl {
options = Object.assign(
{
isTaggedWith: (tag) => false,
isNegationAllowed: false,
},
options
);
options.getMatches = (text) => {
const negated = options.isNegationAllowed && text[0] === "-";
if (negated) text = text.substring(1);
if (!text) {
return new Promise((resolve, reject) => {
(response) => resolve(null),
reject
});
}
const term = misc.escapeSearchTerm(text);
const query =
(text.length < minLengthForPartialSearch
? term + "*"
: "*" + term + "*") + " sort:usages";
(text.length >= minLengthForPartialSearch || (!options.isNegationAllowed && text[0] === "-")
? "*" + term + "*"
: term + "*") + " sort:usages";
return new Promise((resolve, reject) => {
TagList.search(query, 0, this._options.maxResults, [
@ -54,7 +67,7 @@ class TagAutoCompleteControl extends AutoCompleteControl {
]).then(
(response) =>
resolve(
_tagListToMatches(response.results, this._options)
_tagListToMatches(response.results, this._options, negated)
),
reject
);

View File

@ -114,6 +114,7 @@ class TagInputControl extends events.EventTarget {
},
verticalShift: -2,
isTaggedWith: (tagName) => this.tags.isTaggedWith(tagName),
isNegationAllowed: false,
}
);

View File

@ -30,6 +30,7 @@ class HomeView {
misc.escapeSearchTerm(tag.names[0]),
true
),
isNegationAllowed: true,
}
);
this._formNode.addEventListener("submit", (e) =>

View File

@ -186,6 +186,7 @@ class PostsHeaderView extends events.EventTarget {
misc.escapeSearchTerm(tag.names[0]),
true
),
isNegationAllowed: true,
}
);