mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Validate tagger blacklist entries (#5497)
* Don't let invalid tagger regex crash UI * Validate blacklist entries and show errors
This commit is contained in:
parent
6c5bf5f052
commit
f81202660c
3 changed files with 120 additions and 73 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
import React, { useRef, useContext } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
|
|
@ -14,6 +14,102 @@ import { Icon } from "src/components/Shared/Icon";
|
|||
import { ParseMode, TagOperation } from "../constants";
|
||||
import { TaggerStateContext } from "../context";
|
||||
|
||||
const Blacklist: React.FC<{
|
||||
list: string[];
|
||||
setList: (blacklist: string[]) => void;
|
||||
}> = ({ list, setList }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const [currentValue, setCurrentValue] = useState("");
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
function addBlacklistItem() {
|
||||
if (!currentValue) return;
|
||||
|
||||
// don't add duplicate items
|
||||
if (list.includes(currentValue)) {
|
||||
setError(
|
||||
intl.formatMessage({
|
||||
id: "component_tagger.config.errors.blacklist_duplicate",
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// validate regex
|
||||
try {
|
||||
new RegExp(currentValue);
|
||||
} catch (e) {
|
||||
setError((e as SyntaxError).message);
|
||||
return;
|
||||
}
|
||||
|
||||
setList([...list, currentValue]);
|
||||
|
||||
setCurrentValue("");
|
||||
}
|
||||
|
||||
function removeBlacklistItem(index: number) {
|
||||
const newBlacklist = [...list];
|
||||
newBlacklist.splice(index, 1);
|
||||
setList(newBlacklist);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h5>
|
||||
<FormattedMessage id="component_tagger.config.blacklist_label" />
|
||||
</h5>
|
||||
<Form.Group>
|
||||
<InputGroup hasValidation>
|
||||
<Form.Control
|
||||
className="text-input"
|
||||
value={currentValue}
|
||||
onChange={(e) => {
|
||||
setCurrentValue(e.currentTarget.value);
|
||||
setError(undefined);
|
||||
}}
|
||||
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
addBlacklistItem();
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
isInvalid={!!error}
|
||||
/>
|
||||
<InputGroup.Append>
|
||||
<Button onClick={() => addBlacklistItem()}>
|
||||
<FormattedMessage id="actions.add" />
|
||||
</Button>
|
||||
</InputGroup.Append>
|
||||
<Form.Control.Feedback type="invalid">{error}</Form.Control.Feedback>
|
||||
</InputGroup>
|
||||
</Form.Group>
|
||||
<div>
|
||||
{intl.formatMessage(
|
||||
{ id: "component_tagger.config.blacklist_desc" },
|
||||
{ chars_require_escape: <code>[\^$.|?*+()</code> }
|
||||
)}
|
||||
</div>
|
||||
{list.map((item, index) => (
|
||||
<Badge
|
||||
className="tag-item d-inline-block"
|
||||
variant="secondary"
|
||||
key={item}
|
||||
>
|
||||
{item.toString()}
|
||||
<Button
|
||||
className="minimal ml-2"
|
||||
onClick={() => removeBlacklistItem(index)}
|
||||
>
|
||||
<Icon icon={faTimes} />
|
||||
</Button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface IConfigProps {
|
||||
show: boolean;
|
||||
}
|
||||
|
|
@ -21,33 +117,6 @@ interface IConfigProps {
|
|||
const Config: React.FC<IConfigProps> = ({ show }) => {
|
||||
const { config, setConfig } = useContext(TaggerStateContext);
|
||||
const intl = useIntl();
|
||||
const blacklistRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
function addBlacklistItem() {
|
||||
if (!blacklistRef.current) return;
|
||||
|
||||
const input = blacklistRef.current.value;
|
||||
if (!input) return;
|
||||
|
||||
// don't add duplicate items
|
||||
if (!config.blacklist.includes(input)) {
|
||||
setConfig({
|
||||
...config,
|
||||
blacklist: [...config.blacklist, input],
|
||||
});
|
||||
}
|
||||
|
||||
blacklistRef.current.value = "";
|
||||
}
|
||||
|
||||
function removeBlacklistItem(index: number) {
|
||||
const newBlacklist = [...config.blacklist];
|
||||
newBlacklist.splice(index, 1);
|
||||
setConfig({
|
||||
...config,
|
||||
blacklist: newBlacklist,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapse in={show}>
|
||||
|
|
@ -198,47 +267,10 @@ const Config: React.FC<IConfigProps> = ({ show }) => {
|
|||
</Form.Group>
|
||||
</Form>
|
||||
<div className="col-md-6">
|
||||
<h5>
|
||||
<FormattedMessage id="component_tagger.config.blacklist_label" />
|
||||
</h5>
|
||||
<InputGroup>
|
||||
<Form.Control
|
||||
className="text-input"
|
||||
ref={blacklistRef}
|
||||
onKeyPress={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
addBlacklistItem();
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<InputGroup.Append>
|
||||
<Button onClick={() => addBlacklistItem()}>
|
||||
<FormattedMessage id="actions.add" />
|
||||
</Button>
|
||||
</InputGroup.Append>
|
||||
</InputGroup>
|
||||
<div>
|
||||
{intl.formatMessage(
|
||||
{ id: "component_tagger.config.blacklist_desc" },
|
||||
{ chars_require_escape: <code>[\^$.|?*+()</code> }
|
||||
)}
|
||||
</div>
|
||||
{config.blacklist.map((item, index) => (
|
||||
<Badge
|
||||
className="tag-item d-inline-block"
|
||||
variant="secondary"
|
||||
key={item}
|
||||
>
|
||||
{item.toString()}
|
||||
<Button
|
||||
className="minimal ml-2"
|
||||
onClick={() => removeBlacklistItem(index)}
|
||||
>
|
||||
<Icon icon={faTimes} />
|
||||
</Button>
|
||||
</Badge>
|
||||
))}
|
||||
<Blacklist
|
||||
list={config.blacklist}
|
||||
setList={(blacklist) => setConfig({ ...config, blacklist })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -83,6 +83,17 @@ export function prepareQueryString(
|
|||
mode: ParseMode,
|
||||
blacklist: string[]
|
||||
) {
|
||||
const regexs = blacklist
|
||||
.map((b) => {
|
||||
try {
|
||||
return new RegExp(b, "gi");
|
||||
} catch {
|
||||
// ignore
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((r) => r !== null) as RegExp[];
|
||||
|
||||
if ((mode === "auto" && scene.date && scene.studio) || mode === "metadata") {
|
||||
let str = [
|
||||
scene.date,
|
||||
|
|
@ -92,8 +103,8 @@ export function prepareQueryString(
|
|||
]
|
||||
.filter((s) => s !== "")
|
||||
.join(" ");
|
||||
blacklist.forEach((b) => {
|
||||
str = str.replace(new RegExp(b, "gi"), " ");
|
||||
regexs.forEach((re) => {
|
||||
str = str.replace(re, " ");
|
||||
});
|
||||
return str;
|
||||
}
|
||||
|
|
@ -106,8 +117,9 @@ export function prepareQueryString(
|
|||
} else if (mode === "dir" && paths.length) {
|
||||
s = paths[paths.length - 1];
|
||||
}
|
||||
blacklist.forEach((b) => {
|
||||
s = s.replace(new RegExp(b, "gi"), " ");
|
||||
|
||||
regexs.forEach((re) => {
|
||||
s = s.replace(re, " ");
|
||||
});
|
||||
s = parseDate(s);
|
||||
return s.replace(/\./g, " ").replace(/ +/g, " ");
|
||||
|
|
|
|||
|
|
@ -173,6 +173,9 @@
|
|||
"active_instance": "Active stash-box instance:",
|
||||
"blacklist_desc": "Blacklist items are excluded from queries. Note that they are regular expressions and also case-insensitive. Certain characters must be escaped with a backslash: {chars_require_escape}",
|
||||
"blacklist_label": "Blacklist",
|
||||
"errors": {
|
||||
"blacklist_duplicate": "Duplicate blacklist item"
|
||||
},
|
||||
"mark_organized_desc": "Immediately mark the scene as Organized after the Save button is clicked.",
|
||||
"mark_organized_label": "Mark as Organized on save",
|
||||
"query_mode_auto": "Auto",
|
||||
|
|
|
|||
Loading…
Reference in a new issue