Skip to content

Commit

Permalink
Validate blacklist entries and show errors
Browse files Browse the repository at this point in the history
  • Loading branch information
WithoutPants committed Nov 21, 2024
1 parent e28bb7f commit 41554dc
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 64 deletions.
160 changes: 96 additions & 64 deletions ui/v2.5/src/components/Tagger/scenes/Config.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -14,41 +14,110 @@ import { Icon } from "src/components/Shared/Icon";
import { ParseMode, TagOperation } from "../constants";
import { TaggerStateContext } from "../context";

interface IConfigProps {
show: boolean;
}

const Config: React.FC<IConfigProps> = ({ show }) => {
const { config, setConfig } = useContext(TaggerStateContext);
const Blacklist: React.FC<{
list: string[];
setList: (blacklist: string[]) => void;
}> = ({ list, setList }) => {
const intl = useIntl();
const blacklistRef = useRef<HTMLInputElement | null>(null);

function addBlacklistItem() {
if (!blacklistRef.current) return;
const [currentValue, setCurrentValue] = useState("");
const [error, setError] = useState<string>();

const input = blacklistRef.current.value;
if (!input) return;
function addBlacklistItem() {
if (!currentValue) return;

// don't add duplicate items
if (!config.blacklist.includes(input)) {
setConfig({
...config,
blacklist: [...config.blacklist, input],
});
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;
}

blacklistRef.current.value = "";
setList([...list, currentValue]);

setCurrentValue("");
}

function removeBlacklistItem(index: number) {
const newBlacklist = [...config.blacklist];
const newBlacklist = [...list];
newBlacklist.splice(index, 1);
setConfig({
...config,
blacklist: newBlacklist,
});
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;
}

const Config: React.FC<IConfigProps> = ({ show }) => {
const { config, setConfig } = useContext(TaggerStateContext);
const intl = useIntl();

return (
<Collapse in={show}>
<Card>
Expand Down Expand Up @@ -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>
Expand Down
3 changes: 3 additions & 0 deletions ui/v2.5/src/locales/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 41554dc

Please sign in to comment.