mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
7 Commits
96f5a028b8
...
0d2df6db95
Author | SHA1 | Date | |
---|---|---|---|
0d2df6db95 | |||
ffd62a936f | |||
12a7f4f78a | |||
417675cc4c | |||
f5c5b0bfb1 | |||
01f7f6eabb | |||
7eff539df5 |
@ -369,6 +369,7 @@ input[type=file]:focus+.file-dropper,
|
|||||||
a
|
a
|
||||||
display: block
|
display: block
|
||||||
padding: 0.1em 0.5em
|
padding: 0.1em 0.5em
|
||||||
|
transition: none
|
||||||
&.active a, a:hover
|
&.active a, a:hover
|
||||||
background: $button-enabled-background-color
|
background: $button-enabled-background-color
|
||||||
color: $button-enabled-text-color
|
color: $button-enabled-text-color
|
||||||
|
@ -102,7 +102,8 @@ class PoolListController {
|
|||||||
this._ctx.parameters.query,
|
this._ctx.parameters.query,
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
fields
|
fields,
|
||||||
|
{}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
pageRenderer: (pageCtx) => {
|
pageRenderer: (pageCtx) => {
|
||||||
|
@ -78,7 +78,8 @@ class TagListController {
|
|||||||
this._ctx.parameters.query,
|
this._ctx.parameters.query,
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
fields
|
fields,
|
||||||
|
{}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
pageRenderer: (pageCtx) => {
|
pageRenderer: (pageCtx) => {
|
||||||
|
@ -70,11 +70,18 @@ class AutoCompleteControl {
|
|||||||
prefix = this._sourceInputNode.value.substring(0, index + 1);
|
prefix = this._sourceInputNode.value.substring(0, index + 1);
|
||||||
middle = this._sourceInputNode.value.substring(index + 1);
|
middle = this._sourceInputNode.value.substring(index + 1);
|
||||||
}
|
}
|
||||||
|
suffix = spaceIndex < commaIndex ? suffix.replace(/^[^,]+/, "") : suffix.replace(/^\S+/, "");
|
||||||
|
suffix = suffix.trimLeft();
|
||||||
this._sourceInputNode.value =
|
this._sourceInputNode.value =
|
||||||
prefix + result.toString() + delimiter + suffix.trimLeft();
|
prefix + result.toString() + delimiter + suffix;
|
||||||
if (!addSpace) {
|
if (!addSpace) {
|
||||||
this._sourceInputNode.value = this._sourceInputNode.value.trim();
|
this._sourceInputNode.value = this._sourceInputNode.value.trimLeft();
|
||||||
}
|
}
|
||||||
|
const selection = this._sourceInputNode.value.length - suffix.length;
|
||||||
|
if (!addSpace) {
|
||||||
|
this._sourceInputNode.value = this._sourceInputNode.value.trimRight();
|
||||||
|
}
|
||||||
|
this._sourceInputNode.setSelectionRange(selection, selection);
|
||||||
this._sourceInputNode.focus();
|
this._sourceInputNode.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,6 +235,13 @@ class AutoCompleteControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_updateResults(textToFind) {
|
_updateResults(textToFind) {
|
||||||
|
if (this._options.isNegationAllowed && textToFind === "-") {
|
||||||
|
this._results = [];
|
||||||
|
this._activeResult = -1;
|
||||||
|
this._refreshList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._options.getMatches(textToFind).then((matches) => {
|
this._options.getMatches(textToFind).then((matches) => {
|
||||||
const oldResults = this._results.slice();
|
const oldResults = this._results.slice();
|
||||||
this._results = matches.slice(0, this._options.maxResults);
|
this._results = matches.slice(0, this._options.maxResults);
|
||||||
|
@ -4,18 +4,19 @@ const misc = require("../util/misc.js");
|
|||||||
const PoolList = require("../models/pool_list.js");
|
const PoolList = require("../models/pool_list.js");
|
||||||
const AutoCompleteControl = require("./auto_complete_control.js");
|
const AutoCompleteControl = require("./auto_complete_control.js");
|
||||||
|
|
||||||
function _poolListToMatches(pools, options) {
|
function _poolListToMatches(text, pools, options) {
|
||||||
return [...pools]
|
return [...pools]
|
||||||
.sort((pool1, pool2) => {
|
.sort((pool1, pool2) => {
|
||||||
return pool2.postCount - pool1.postCount;
|
return pool2.postCount - pool1.postCount;
|
||||||
})
|
})
|
||||||
.map((pool) => {
|
.map((pool) => {
|
||||||
|
pool.matchingNames = misc.matchingNames(text, pool.names);
|
||||||
let cssName = misc.makeCssName(pool.category, "pool");
|
let cssName = misc.makeCssName(pool.category, "pool");
|
||||||
const caption =
|
const caption =
|
||||||
'<span class="' +
|
'<span class="' +
|
||||||
cssName +
|
cssName +
|
||||||
'">' +
|
'">' +
|
||||||
misc.escapeHtml(pool.names[0] + " (" + pool.postCount + ")") +
|
misc.escapeHtml(pool.matchingNames[0] + " (" + pool.postCount + ")") +
|
||||||
"</span>";
|
"</span>";
|
||||||
return {
|
return {
|
||||||
caption: caption,
|
caption: caption,
|
||||||
@ -31,9 +32,9 @@ class PoolAutoCompleteControl extends AutoCompleteControl {
|
|||||||
options.getMatches = (text) => {
|
options.getMatches = (text) => {
|
||||||
const term = misc.escapeSearchTerm(text);
|
const term = misc.escapeSearchTerm(text);
|
||||||
const query =
|
const query =
|
||||||
(text.length < minLengthForPartialSearch
|
(text.length >= minLengthForPartialSearch
|
||||||
? term + "*"
|
? "*" + term + "*"
|
||||||
: "*" + term + "*") + " sort:post-count";
|
: term + "*") + " sort:post-count";
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
PoolList.search(query, 0, this._options.maxResults, [
|
PoolList.search(query, 0, this._options.maxResults, [
|
||||||
@ -42,10 +43,11 @@ class PoolAutoCompleteControl extends AutoCompleteControl {
|
|||||||
"category",
|
"category",
|
||||||
"postCount",
|
"postCount",
|
||||||
"version",
|
"version",
|
||||||
]).then(
|
],
|
||||||
|
{ noProgress: true }).then(
|
||||||
(response) =>
|
(response) =>
|
||||||
resolve(
|
resolve(
|
||||||
_poolListToMatches(response.results, this._options)
|
_poolListToMatches(text, response.results, this._options)
|
||||||
),
|
),
|
||||||
reject
|
reject
|
||||||
);
|
);
|
||||||
@ -54,6 +56,16 @@ class PoolAutoCompleteControl extends AutoCompleteControl {
|
|||||||
|
|
||||||
super(input, options);
|
super(input, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getActiveSuggestion() {
|
||||||
|
if (this._activeResult === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const result = this._results[this._activeResult].value;
|
||||||
|
const textToFind = this._options.getTextToFind();
|
||||||
|
result.matchingNames = misc.matchingNames(textToFind, result.names);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PoolAutoCompleteControl;
|
module.exports = PoolAutoCompleteControl;
|
||||||
|
@ -5,21 +5,26 @@ const views = require("../util/views.js");
|
|||||||
const TagList = require("../models/tag_list.js");
|
const TagList = require("../models/tag_list.js");
|
||||||
const AutoCompleteControl = require("./auto_complete_control.js");
|
const AutoCompleteControl = require("./auto_complete_control.js");
|
||||||
|
|
||||||
function _tagListToMatches(tags, options) {
|
function _tagListToMatches(text, tags, options, negated) {
|
||||||
return [...tags]
|
return [...tags]
|
||||||
.sort((tag1, tag2) => {
|
.sort((tag1, tag2) => {
|
||||||
return tag2.usages - tag1.usages;
|
return tag2.usages - tag1.usages;
|
||||||
})
|
})
|
||||||
.map((tag) => {
|
.map((tag) => {
|
||||||
|
tag.matchingNames = misc.matchingNames(text, tag.names);
|
||||||
let cssName = misc.makeCssName(tag.category, "tag");
|
let cssName = misc.makeCssName(tag.category, "tag");
|
||||||
if (options.isTaggedWith(tag.names[0])) {
|
if (options.isTaggedWith(tag.names[0])) {
|
||||||
cssName += " disabled";
|
cssName += " disabled";
|
||||||
}
|
}
|
||||||
|
if (negated) {
|
||||||
|
tag.names = tag.names.map((tagName) => "-"+tagName);
|
||||||
|
tag.matchingNames = tag.matchingNames.map((tagName) => "-"+tagName);
|
||||||
|
}
|
||||||
const caption =
|
const caption =
|
||||||
'<span class="' +
|
'<span class="' +
|
||||||
cssName +
|
cssName +
|
||||||
'">' +
|
'">' +
|
||||||
misc.escapeHtml(tag.names[0] + " (" + tag.postCount + ")") +
|
misc.escapeHtml(tag.matchingNames[0] + " (" + tag.postCount + ")") +
|
||||||
"</span>";
|
"</span>";
|
||||||
return {
|
return {
|
||||||
caption: caption,
|
caption: caption,
|
||||||
@ -35,26 +40,37 @@ class TagAutoCompleteControl extends AutoCompleteControl {
|
|||||||
options = Object.assign(
|
options = Object.assign(
|
||||||
{
|
{
|
||||||
isTaggedWith: (tag) => false,
|
isTaggedWith: (tag) => false,
|
||||||
|
isNegationAllowed: false,
|
||||||
},
|
},
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
options.getMatches = (text) => {
|
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 term = misc.escapeSearchTerm(text);
|
||||||
const query =
|
const query =
|
||||||
(text.length < minLengthForPartialSearch
|
(text.length >= minLengthForPartialSearch || (!options.isNegationAllowed && text[0] === "-")
|
||||||
? term + "*"
|
? "*" + term + "*"
|
||||||
: "*" + term + "*") + " sort:usages";
|
: term + "*") + " sort:usages";
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
TagList.search(query, 0, this._options.maxResults, [
|
TagList.search(query, 0, this._options.maxResults, [
|
||||||
"names",
|
"names",
|
||||||
"category",
|
"category",
|
||||||
"usages",
|
"usages",
|
||||||
]).then(
|
],
|
||||||
|
{ noProgress: true }).then(
|
||||||
(response) =>
|
(response) =>
|
||||||
resolve(
|
resolve(
|
||||||
_tagListToMatches(response.results, this._options)
|
_tagListToMatches(text, response.results, this._options, negated)
|
||||||
),
|
),
|
||||||
reject
|
reject
|
||||||
);
|
);
|
||||||
@ -63,6 +79,16 @@ class TagAutoCompleteControl extends AutoCompleteControl {
|
|||||||
|
|
||||||
super(input, options);
|
super(input, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getActiveSuggestion() {
|
||||||
|
if (this._activeResult === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const result = this._results[this._activeResult].value;
|
||||||
|
const textToFind = this._options.getTextToFind();
|
||||||
|
result.matchingNames = misc.matchingNames(textToFind, result.names);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TagAutoCompleteControl;
|
module.exports = TagAutoCompleteControl;
|
||||||
|
@ -114,6 +114,7 @@ class TagInputControl extends events.EventTarget {
|
|||||||
},
|
},
|
||||||
verticalShift: -2,
|
verticalShift: -2,
|
||||||
isTaggedWith: (tagName) => this.tags.isTaggedWith(tagName),
|
isTaggedWith: (tagName) => this.tags.isTaggedWith(tagName),
|
||||||
|
isNegationAllowed: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ const AbstractList = require("./abstract_list.js");
|
|||||||
const Pool = require("./pool.js");
|
const Pool = require("./pool.js");
|
||||||
|
|
||||||
class PoolList extends AbstractList {
|
class PoolList extends AbstractList {
|
||||||
static search(text, offset, limit, fields) {
|
static search(text, offset, limit, fields, options) {
|
||||||
return api
|
return api
|
||||||
.get(
|
.get(
|
||||||
uri.formatApiLink("pools", {
|
uri.formatApiLink("pools", {
|
||||||
@ -14,7 +14,8 @@ class PoolList extends AbstractList {
|
|||||||
offset: offset,
|
offset: offset,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
fields: fields.join(","),
|
fields: fields.join(","),
|
||||||
})
|
}),
|
||||||
|
options
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
|
@ -6,7 +6,7 @@ const AbstractList = require("./abstract_list.js");
|
|||||||
const Tag = require("./tag.js");
|
const Tag = require("./tag.js");
|
||||||
|
|
||||||
class TagList extends AbstractList {
|
class TagList extends AbstractList {
|
||||||
static search(text, offset, limit, fields) {
|
static search(text, offset, limit, fields, options) {
|
||||||
return api
|
return api
|
||||||
.get(
|
.get(
|
||||||
uri.formatApiLink("tags", {
|
uri.formatApiLink("tags", {
|
||||||
@ -14,7 +14,8 @@ class TagList extends AbstractList {
|
|||||||
offset: offset,
|
offset: offset,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
fields: fields.join(","),
|
fields: fields.join(","),
|
||||||
})
|
}),
|
||||||
|
options
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
|
@ -211,6 +211,24 @@ function getPrettyName(tag) {
|
|||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wildcardMatch(pattern, str, sensitive = false) {
|
||||||
|
let w = pattern.replace(/[.+^${}()|[\]\\?]/g, "\\$&");
|
||||||
|
const re = new RegExp(`^${w.replace(/\(--wildcard--\)|\*/g, ".*")}$`, sensitive ? "" : "i");
|
||||||
|
return re.test(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchingNames(text, names) {
|
||||||
|
const minLengthForPartialSearch = 3;
|
||||||
|
let matches = names.filter((name) => wildcardMatch(text + "*", name, false));
|
||||||
|
|
||||||
|
if (!matches.length && text.length >= minLengthForPartialSearch) {
|
||||||
|
matches = names.filter((name) => wildcardMatch("*" + text + "*", name, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
matches = matches.length ? matches : names;
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
range: range,
|
range: range,
|
||||||
formatRelativeTime: formatRelativeTime,
|
formatRelativeTime: formatRelativeTime,
|
||||||
@ -229,4 +247,6 @@ module.exports = {
|
|||||||
escapeSearchTerm: escapeSearchTerm,
|
escapeSearchTerm: escapeSearchTerm,
|
||||||
dataURItoBlob: dataURItoBlob,
|
dataURItoBlob: dataURItoBlob,
|
||||||
getPrettyName: getPrettyName,
|
getPrettyName: getPrettyName,
|
||||||
|
wildcardMatch: wildcardMatch,
|
||||||
|
matchingNames: matchingNames,
|
||||||
};
|
};
|
||||||
|
@ -27,9 +27,10 @@ class HomeView {
|
|||||||
{
|
{
|
||||||
confirm: (tag) =>
|
confirm: (tag) =>
|
||||||
this._autoCompleteControl.replaceSelectedText(
|
this._autoCompleteControl.replaceSelectedText(
|
||||||
misc.escapeSearchTerm(tag.names[0]),
|
misc.escapeSearchTerm(tag.matchingNames[0]),
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
|
isNegationAllowed: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this._formNode.addEventListener("submit", (e) =>
|
this._formNode.addEventListener("submit", (e) =>
|
||||||
|
@ -25,7 +25,7 @@ class PoolMergeView extends events.EventTarget {
|
|||||||
confirm: (pool) => {
|
confirm: (pool) => {
|
||||||
this._targetPoolId = pool.id;
|
this._targetPoolId = pool.id;
|
||||||
this._autoCompleteControl.replaceSelectedText(
|
this._autoCompleteControl.replaceSelectedText(
|
||||||
pool.names[0],
|
pool.matchingNames[0],
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,7 @@ class PoolsHeaderView extends events.EventTarget {
|
|||||||
{
|
{
|
||||||
confirm: (pool) =>
|
confirm: (pool) =>
|
||||||
this._autoCompleteControl.replaceSelectedText(
|
this._autoCompleteControl.replaceSelectedText(
|
||||||
misc.escapeSearchTerm(pool.names[0]),
|
misc.escapeSearchTerm(pool.matchingNames[0]),
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -183,9 +183,10 @@ class PostsHeaderView extends events.EventTarget {
|
|||||||
{
|
{
|
||||||
confirm: (tag) =>
|
confirm: (tag) =>
|
||||||
this._autoCompleteControl.replaceSelectedText(
|
this._autoCompleteControl.replaceSelectedText(
|
||||||
misc.escapeSearchTerm(tag.names[0]),
|
misc.escapeSearchTerm(tag.matchingNames[0]),
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
|
isNegationAllowed: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ class TagMergeView extends events.EventTarget {
|
|||||||
{
|
{
|
||||||
confirm: (tag) =>
|
confirm: (tag) =>
|
||||||
this._autoCompleteControl.replaceSelectedText(
|
this._autoCompleteControl.replaceSelectedText(
|
||||||
tag.names[0],
|
tag.matchingNames[0],
|
||||||
false
|
false
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ class TagsHeaderView extends events.EventTarget {
|
|||||||
{
|
{
|
||||||
confirm: (tag) =>
|
confirm: (tag) =>
|
||||||
this._autoCompleteControl.replaceSelectedText(
|
this._autoCompleteControl.replaceSelectedText(
|
||||||
misc.escapeSearchTerm(tag.names[0]),
|
misc.escapeSearchTerm(tag.matchingNames[0]),
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user