Filtry, omezení, audio náhled

This commit is contained in:
2026-04-24 14:20:09 +02:00
parent 614919fbba
commit 4ec7338204
19 changed files with 72 additions and 76 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -0,0 +1,5 @@
ALTER TABLE restrictions
RENAME COLUMN reason TO note;
ALTER TABLE filters
RENAME COLUMN reason TO note;

View File

@@ -13,11 +13,11 @@ const createFilter = async (
filter_type: "replace" | "block" | "ban",
filter_value: string,
priority: number,
reason: string | null,
note: string | null,
created_by: string,
) => {
const result = await postgres.query<Filter>(
"INSERT INTO filters (board, target_type, target_value, filter_type, filter_value, priority, reason, created_by) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *",
"INSERT INTO filters (board, target_type, target_value, filter_type, filter_value, priority, note, created_by) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *",
[
board,
target_type,
@@ -25,7 +25,7 @@ const createFilter = async (
filter_type,
filter_value,
priority,
reason,
note,
created_by,
],
);

View File

@@ -12,18 +12,18 @@ const createRestriction = async (
target_mode: "blacklist" | "whitelist",
target_value: string[],
restrictions: string[],
reason: string | null,
note: string | null,
created_by: string,
) => {
const result = await postgres.query(
"INSERT INTO restrictions (board, target_type, target_mode, target_value, restrictions, reason, created_by) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *",
"INSERT INTO restrictions (board, target_type, target_mode, target_value, restrictions, note, created_by) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *",
[
board,
target_type,
target_mode,
JSON.stringify(target_value),
JSON.stringify(restrictions),
reason,
note,
created_by,
],
);

View File

@@ -85,7 +85,7 @@ const activeRestrictions = (
case "region":
active ||= isRestricted(
restriction.target_value,
req.ipInfo.region,
`${req.ipInfo.country}-${req.ipInfo.region}`,
restriction.target_mode,
);
break;

View File

@@ -96,7 +96,7 @@ type Restriction = {
target_mode: "blacklist" | "whitelist";
target_value: string[];
restrictions: string[];
reason: string | null;
note: string | null;
created_by: string;
created: Date;
};

View File

@@ -89,13 +89,13 @@ const zFilterValue = z
.string()
.max(2048, "Hodnota filtru musí mít maximálně 2048 znaků.");
const zFilterReason = z
const zFilterNote = z
.string()
.max(512, "Důvod filtru musí mít maximálně 512 znaků.");
.max(512, "Poznámka k filtru musí mít maximálně 512 znaků.");
const zBanReason = z
.string()
.max(512, "Důvod banu musí mít maximálně 512 znaků.");
.max(512, "Poznámka k banu musí mít maximálně 512 znaků.");
export {
zUrlEncodedValue,
@@ -113,6 +113,6 @@ export {
zFilterType,
zFilterTargetValue,
zFilterValue,
zFilterReason,
zFilterNote,
zBanReason,
};

View File

@@ -1,5 +1,5 @@
import {
zFilterReason,
zFilterNote,
zFilterTargetType,
zFilterTargetValue,
zFilterType,
@@ -29,7 +29,7 @@ const CreateFilterForm = z
filter_type: zRequiredField(zFilterType),
filter_value: zRequiredField(zFilterValue),
priority: zRequiredField(zPriority),
reason: zOptionalField(zFilterReason),
note: zOptionalField(zFilterNote),
})
.refine(
(form) =>
@@ -57,7 +57,7 @@ export {
zFilterType,
zFilterTargetValue,
zFilterValue,
zFilterReason,
zFilterNote,
CreateFilterForm,
FilterActionsForm,
UpdateFilterPriorityForm,

View File

@@ -3,7 +3,7 @@ import {
zBoardID,
zCheckbox,
zDuration,
zFilterReason,
zFilterNote,
zFilterType,
zFilterValue,
zMultiField,
@@ -114,7 +114,7 @@ const ZPostActionsForm = z.object({
filter_type: zOptionalField(zFilterType),
filter_value: zOptionalField(zFilterValue),
filter_priority: zOptionalField(zPriority).transform((value) => value ?? 0),
filter_reason: zOptionalField(zFilterReason),
filter_note: zOptionalField(zFilterNote),
set_sticky: zCheckbox, // Primary
sticky: zOptionalField(zPriority),
toggle_lock: zCheckbox, // Primary

View File

@@ -31,15 +31,15 @@ const zRestrictionTargetValues = z
.string()
.transform((value) =>
value
.split(/\s+/g)
.split(/[\s,]+/)
.map((segment) => segment.trim())
.filter((segment) => segment !== ""),
)
.refine((values) => values.length > 0, "Cíl omezení nesmí být prázdný.");
const zRestrictionReason = z
const zRestrictionNote = z
.string()
.max(512, "Důvod omezení musí mít maximálně 512 znaků.");
.max(512, "Poznámka k omezení musí mít maximálně 512 znaků.");
const zNullableBoardField = zOptionalField(zBoardID);
@@ -50,7 +50,7 @@ const CreateRestrictionForm = z.object({
target_mode: zRequiredField(zRestrictionTargetMode),
target_value: zRequiredField(zRestrictionTargetValues),
restrictions: zMultiField(zRestrictionFlag),
reason: zOptionalField(zRestrictionReason),
note: zOptionalField(zRestrictionNote),
});
const RestrictionActionsForm = z.object({
@@ -64,7 +64,7 @@ export {
zRestrictionTargetMode,
zRestrictionFlag,
zRestrictionTargetValues,
zRestrictionReason,
zRestrictionNote,
CreateRestrictionForm,
RestrictionActionsForm,
};

View File

@@ -137,8 +137,8 @@ const PostActionsForm = ({ ctx }: PropsWithChildren<{ ctx: TemplateCtx }>) => (
<FormField label="↳ Priorita filtru">
<input name="filter_priority" type="number" value="0" />
</FormField>
<FormField label="↳ Důvod filtru (poznámka)">
<input name="filter_reason" type="text" />
<FormField label="↳ Poznámka k filtru">
<input name="filter_note" type="text" />
</FormField>
</Form>
)}

View File

@@ -87,8 +87,8 @@ const uconfigMiddleware = (req: Request, res: Response, next: NextFunction) => {
if (req.cookies.non_public_overboard) {
req.uconfig.nonPublicOverboard = new Set(
req.cookies.non_public_overboard
.split(",")
.map((val: string) => val.trim()),
.split(/[\s,]+/)
.map((segment: string) => segment.trim()),
);
} else {
req.uconfig.nonPublicOverboard = new Set();

View File

@@ -360,7 +360,7 @@ const modActions = async (
form.filter_type as "replace" | "block" | "ban",
form.filter_value as string,
form.filter_priority,
form.filter_reason,
form.filter_note,
req.user?.username as string,
);
}

View File

@@ -21,13 +21,21 @@ export default async (req: Request, res: Response) => {
filter_type,
filter_value,
priority,
reason,
note,
} = CreateFilterForm.parse(fields);
if (board !== null) {
await readBoard(board);
}
if (target_type === "regex") {
try {
new RegExp(target_value, "gimv");
} catch (_) {
throw new CzchanError("Neplatný RegEx", 400);
}
}
await createFilter(
board,
target_type,
@@ -35,7 +43,7 @@ export default async (req: Request, res: Response) => {
filter_type,
filter_value,
priority,
reason,
note,
req.user?.username as string,
);

View File

@@ -14,14 +14,8 @@ export default async (req: Request, res: Response) => {
const parser = formidable({});
const [fields, _] = await parser.parse(req);
const {
board,
target_type,
target_mode,
target_value,
restrictions,
reason,
} = CreateRestrictionForm.parse(fields);
const { board, target_type, target_mode, target_value, restrictions, note } =
CreateRestrictionForm.parse(fields);
if (board !== null) {
await readBoard(board);
@@ -33,7 +27,7 @@ export default async (req: Request, res: Response) => {
target_mode,
target_value,
restrictions,
reason,
note,
req.user?.username as string,
);

View File

@@ -77,7 +77,7 @@ const FilterTable = ({
<th>Typ filtru</th>
<th>Hodnota</th>
<th>Priorita</th>
<th>Důvod (poznámka)</th>
<th>Poznámka</th>
<th>Vytvořil</th>
<th>Vytvořen</th>
</tr>
@@ -154,9 +154,9 @@ const CreateForm = ({ boards }: PropsWithChildren<{ boards: Board[] }>) => (
</FormField>
<FormField label="Typ cíle">
<select name="target_type">
<option value="text">text</option>
<option value="regex">regex</option>
<option value="sha1">sha1</option>
<option value="text">Text (s velikostí znaků)</option>
<option value="regex">RegEx (flagy: gimv)</option>
<option value="sha1">SHA1</option>
<option value="phash">phash</option>
</select>
</FormField>
@@ -165,9 +165,9 @@ const CreateForm = ({ boards }: PropsWithChildren<{ boards: Board[] }>) => (
</FormField>
<FormField label="Typ filtru">
<select name="filter_type">
<option value="replace">replace</option>
<option value="block">block</option>
<option value="ban">ban</option>
<option value="replace">Nahradit</option>
<option value="block">Zakázat</option>
<option value="ban">Zabanovat</option>
</select>
</FormField>
<FormField label="Hodnota filtru">
@@ -176,8 +176,8 @@ const CreateForm = ({ boards }: PropsWithChildren<{ boards: Board[] }>) => (
<FormField label="Priorita">
<input name="priority" type="number" value="0" />
</FormField>
<FormField label="Důvod">
<input name="reason" type="text" />
<FormField label="Poznámka">
<input name="note" type="text" />
</FormField>
<FormButton submit="Vytvořit" action="create_filter" />
</Form>

View File

@@ -66,7 +66,7 @@ const RestrictionTable = ({
<th>Režim</th>
<th>Hodnoty</th>
<th>Omezení</th>
<th>Důvod (poznámka)</th>
<th>Poznámka</th>
<th>Vytvořil</th>
<th>Vytvořeno</th>
</tr>
@@ -117,11 +117,7 @@ const RestrictionRow = ({
<td safe>{restriction.target_value.join(", ")}</td>
<td safe>{restriction.restrictions.join(", ")}</td>
<td>
{restriction.reason !== null ? (
<span safe>{restriction.reason}</span>
) : (
"-"
)}
{restriction.note !== null ? <span safe>{restriction.note}</span> : "-"}
</td>
<td safe>{restriction.created_by}</td>
<td>
@@ -148,16 +144,16 @@ const CreateForm = ({ boards }: PropsWithChildren<{ boards: Board[] }>) => (
</FormField>
<FormField label="Typ cíle">
<select name="target_type">
<option value="country">country</option>
<option value="region">region</option>
<option value="asn">asn</option>
<option value="list">list</option>
<option value="country">Země (ISO)</option>
<option value="region">Region (ISO země)-(ISO regionu)</option>
<option value="asn">ASN</option>
<option value="list">Seznam</option>
</select>
</FormField>
<FormField label="Režim">
<select name="target_mode">
<option value="blacklist">blacklist</option>
<option value="whitelist">whitelist</option>
<option value="blacklist">Blokovat hodnoty</option>
<option value="whitelist">Blokovat nehodnoty</option>
</select>
</FormField>
<FormField label="Hodnoty cíle">
@@ -165,24 +161,24 @@ const CreateForm = ({ boards }: PropsWithChildren<{ boards: Board[] }>) => (
</FormField>
<FormField label="Omezení" wrapper>
<label>
<input name="restrictions" type="checkbox" value="post" /> post
<input name="restrictions" type="checkbox" value="post" /> Příspěvky
</label>{" "}
<label>
<input name="restrictions" type="checkbox" value="thread" /> thread
<input name="restrictions" type="checkbox" value="thread" /> Vlákna
</label>{" "}
<label>
<input name="restrictions" type="checkbox" value="reply" /> reply
<input name="restrictions" type="checkbox" value="reply" /> Odpovědi
</label>{" "}
<label>
<input name="restrictions" type="checkbox" value="file" /> file
<input name="restrictions" type="checkbox" value="file" /> Soubory
</label>{" "}
<label>
<input name="restrictions" type="checkbox" value="captcha" />{" "}
captcha
<input name="restrictions" type="checkbox" value="captcha" /> Vnutit
CAPTCHA
</label>
</FormField>
<FormField label="Důvod">
<input name="reason" type="text" />
<FormField label="Poznámka">
<input name="note" type="text" />
</FormField>
<FormButton submit="Vytvořit" action="create_restriction" />
</Form>

View File

@@ -58,15 +58,7 @@ const Template = (ctx: TemplateCtx) => (
checked={ctx.uconfig.reltime}
/>
</FormField>
<FormField
label={
<>
Neveřejné nástěnky na overboardu
<br />
<small>(dělěny čárkami)</small>
</>
}
>
<FormField label="Neveřejné nástěnky na overboardu">
<input
name="non_public_overboard"
type="text"

View File

@@ -6,6 +6,7 @@ body {
}
header {
position: sticky; // On ancient browsers it just does nothing as it used to
padding: 4px;
}