chore(lint): remove dead eslint-disable directives
CI / check (push) Successful in 30s
CI / tests-imaging-browsers (push) Successful in 1m31s
CI / tests-unit (push) Successful in 1m45s
CI / tests-integration (push) Successful in 1m49s
CI / tests-quality (push) Successful in 47s
CI / tests-ui (push) Successful in 4m24s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m39s
CI / check (push) Successful in 30s
CI / tests-imaging-browsers (push) Successful in 1m31s
CI / tests-unit (push) Successful in 1m45s
CI / tests-integration (push) Successful in 1m49s
CI / tests-quality (push) Successful in 47s
CI / tests-ui (push) Successful in 4m24s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m39s
The project lints with Biome only, so none of these 78 directives across 57 files ever suppressed anything. Directives that carried the rationale for a flagged dynamic index access keep that rationale as a plain comment.
This commit is contained in:
1 parent
07c5904879
commit
2249303dbd
57 files changed
+29
-78
No files matched your search
@@ -60,7 +60,6 @@ export async function uploadBadge(formData: FormData): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
const buffer = Buffer.from(await file.arrayBuffer());
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||||||
const gif = await toBadgeGif(buffer);
|
const gif = await toBadgeGif(buffer);
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
await writeFile(target, gif);
|
await writeFile(target, gif);
|
||||||
} catch {
|
} catch {
|
||||||
back("error", "Could not process or write the badge file");
|
back("error", "Could not process or write the badge file");
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ async function upsertSetting(key: string, value: string): Promise<void> {
|
|||||||
.values({
|
.values({
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
// eslint-disable-next-line security/detect-object-injection -- key is one of 3 known const values
|
// key is one of 3 known const values
|
||||||
comment: COMMENTS[key] ?? null,
|
comment: COMMENTS[key] ?? null,
|
||||||
})
|
})
|
||||||
.onDuplicateKeyUpdate({ set: { value } });
|
.onDuplicateKeyUpdate({ set: { value } });
|
||||||
|
|||||||
@@ -71,10 +71,10 @@ export async function savePoints(formData: FormData): Promise<void> {
|
|||||||
POINTS_KEYS.map((key) =>
|
POINTS_KEYS.map((key) =>
|
||||||
db
|
db
|
||||||
.insert(WebsiteSetting)
|
.insert(WebsiteSetting)
|
||||||
// eslint-disable-next-line security/detect-object-injection -- key from POINTS_KEYS const
|
// key from POINTS_KEYS const
|
||||||
.values({ key, value: values[key], comment: "Radio points" })
|
.values({ key, value: values[key], comment: "Radio points" })
|
||||||
.onDuplicateKeyUpdate({
|
.onDuplicateKeyUpdate({
|
||||||
// eslint-disable-next-line security/detect-object-injection -- key from POINTS_KEYS const
|
// key from POINTS_KEYS const
|
||||||
set: { value: values[key] },
|
set: { value: values[key] },
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export async function saveTheme(formData: FormData): Promise<void> {
|
|||||||
export async function applyPreset(formData: FormData): Promise<void> {
|
export async function applyPreset(formData: FormData): Promise<void> {
|
||||||
const staff = await requirePermission(PERMS.SETTINGS_EDIT);
|
const staff = await requirePermission(PERMS.SETTINGS_EDIT);
|
||||||
const name = String(formData.get("preset") ?? "").normalize("NFC");
|
const name = String(formData.get("preset") ?? "").normalize("NFC");
|
||||||
// eslint-disable-next-line security/detect-object-injection -- guarded by null check below
|
// guarded by null check below
|
||||||
const preset = PRESETS[name];
|
const preset = PRESETS[name];
|
||||||
if (!preset) redirect("/admin/theme");
|
if (!preset) redirect("/admin/theme");
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ export async function saveFavicon(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buffer = upload.bytes;
|
const buffer = upload.bytes;
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
await mkdir(baseDir, { recursive: true });
|
await mkdir(baseDir, { recursive: true });
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
await writeFile(filePath, buffer);
|
await writeFile(filePath, buffer);
|
||||||
|
|
||||||
const url = `/api/media/favicon/${filename}`;
|
const url = `/api/media/favicon/${filename}`;
|
||||||
@@ -46,7 +44,6 @@ export async function saveFavicon(
|
|||||||
const oldPath = path.resolve(baseDir, oldName);
|
const oldPath = path.resolve(baseDir, oldName);
|
||||||
if (oldPath.startsWith(baseDir + path.sep)) {
|
if (oldPath.startsWith(baseDir + path.sep)) {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
await unlink(oldPath);
|
await unlink(oldPath);
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore if file doesn't exist */
|
/* ignore if file doesn't exist */
|
||||||
@@ -85,7 +82,6 @@ export async function deleteFavicon(): Promise<{
|
|||||||
const oldPath = path.resolve(baseDir, oldName);
|
const oldPath = path.resolve(baseDir, oldName);
|
||||||
if (oldPath.startsWith(baseDir + path.sep)) {
|
if (oldPath.startsWith(baseDir + path.sep)) {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
await unlink(oldPath);
|
await unlink(oldPath);
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ export async function saveLogo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buffer = upload.bytes;
|
const buffer = upload.bytes;
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
await mkdir(baseDir, { recursive: true });
|
await mkdir(baseDir, { recursive: true });
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
await writeFile(filePath, buffer);
|
await writeFile(filePath, buffer);
|
||||||
|
|
||||||
const url = `/api/media/logo/${filename}`;
|
const url = `/api/media/logo/${filename}`;
|
||||||
|
|||||||
@@ -278,14 +278,12 @@ export async function uploadBackground(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
let uploaded = false;
|
let uploaded = false;
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
await mkdir(MEDIA_ROOT, { recursive: true });
|
await mkdir(MEDIA_ROOT, { recursive: true });
|
||||||
const ext = file.name.split(".").pop() ?? "png";
|
const ext = file.name.split(".").pop() ?? "png";
|
||||||
const name = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`;
|
const name = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`;
|
||||||
const bytes = await file.arrayBuffer();
|
const bytes = await file.arrayBuffer();
|
||||||
const filePath = resolveMediaPath(name);
|
const filePath = resolveMediaPath(name);
|
||||||
if (filePath.startsWith(MEDIA_ROOT + path.sep)) {
|
if (filePath.startsWith(MEDIA_ROOT + path.sep)) {
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
await writeFile(filePath, Buffer.from(bytes));
|
await writeFile(filePath, Buffer.from(bytes));
|
||||||
await writeSetting("background_image", `/api/media/${name}`);
|
await writeSetting("background_image", `/api/media/${name}`);
|
||||||
await writeSetting(
|
await writeSetting(
|
||||||
@@ -319,7 +317,7 @@ export async function applyPresetManager(formData: FormData): Promise<void> {
|
|||||||
const name = String(formData.get("preset") ?? "")
|
const name = String(formData.get("preset") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
.trim();
|
.trim();
|
||||||
// eslint-disable-next-line security/detect-object-injection -- guarded by null check below
|
// guarded by null check below
|
||||||
const preset = PRESETS[name];
|
const preset = PRESETS[name];
|
||||||
if (!preset) redirect(MANAGER_PATH);
|
if (!preset) redirect(MANAGER_PATH);
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ export default async function ApplyStaffPage({
|
|||||||
<article key={String(position.id)} className="content-card">
|
<article key={String(position.id)} className="content-card">
|
||||||
<div className="content-card-head">
|
<div className="content-card-head">
|
||||||
{team?.badge ? (
|
{team?.badge ? (
|
||||||
/* eslint-disable-next-line @next/next/no-img-element */
|
|
||||||
<img
|
<img
|
||||||
src={`${BADGE_IMG_BASE}=${encodeURIComponent(team.badge)}`}
|
src={`${BADGE_IMG_BASE}=${encodeURIComponent(team.badge)}`}
|
||||||
alt={rankName}
|
alt={rankName}
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ export default async function ApplyTeamPage({
|
|||||||
<article key={String(team.id)} className="content-card">
|
<article key={String(team.id)} className="content-card">
|
||||||
<div className="content-card-head">
|
<div className="content-card-head">
|
||||||
{team.badge ? (
|
{team.badge ? (
|
||||||
/* eslint-disable-next-line @next/next/no-img-element */
|
|
||||||
<img
|
<img
|
||||||
src={`${BADGE_IMG_BASE}=${encodeURIComponent(team.badge)}`}
|
src={`${BADGE_IMG_BASE}=${encodeURIComponent(team.badge)}`}
|
||||||
alt={team.rankName}
|
alt={team.rankName}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ export default async function BadgesPage() {
|
|||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={`${BADGE_IMG_BASE}=${encodeURIComponent(badge.badgeKey)}`}
|
src={`${BADGE_IMG_BASE}=${encodeURIComponent(badge.badgeKey)}`}
|
||||||
alt={badge.badgeName}
|
alt={badge.badgeName}
|
||||||
|
|||||||
@@ -190,7 +190,6 @@ export default async function DrawBadgePage({
|
|||||||
{b.badgeUrl ? (
|
{b.badgeUrl ? (
|
||||||
// Badge images are arbitrary external URLs stored by the
|
// Badge images are arbitrary external URLs stored by the
|
||||||
// draw-badge tool, so a plain <img> is correct here.
|
// draw-badge tool, so a plain <img> is correct here.
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={b.badgeUrl}
|
src={b.badgeUrl}
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@@ -149,7 +149,6 @@ export default async function GuildPage({
|
|||||||
style={{ display: "flex", gap: "1.25rem", alignItems: "flex-start" }}
|
style={{ display: "flex", gap: "1.25rem", alignItems: "flex-start" }}
|
||||||
>
|
>
|
||||||
{guild.badge ? (
|
{guild.badge ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${guild.badge}.gif`}
|
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${guild.badge}.gif`}
|
||||||
alt={`${guild.name} badge`}
|
alt={`${guild.name} badge`}
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ export default async function GuildsPage() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{g.badge ? (
|
{g.badge ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${g.badge}.gif`}
|
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${g.badge}.gif`}
|
||||||
alt={`${g.name} badge`}
|
alt={`${g.name} badge`}
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ export default async function HelpCategoryPage({
|
|||||||
|
|
||||||
<ContentCard icon="❓" title={title} subtitle={t("topicSubtitle")}>
|
<ContentCard icon="❓" title={title} subtitle={t("topicSubtitle")}>
|
||||||
{imageSrc ? (
|
{imageSrc ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={imageSrc}
|
src={imageSrc}
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@@ -159,7 +159,6 @@ export default async function HelpCenterPage() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{c.imageUrl ? (
|
{c.imageUrl ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={c.imageUrl}
|
src={c.imageUrl}
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ export default async function LeaderboardPage({
|
|||||||
const rows = await (active === "credits"
|
const rows = await (active === "credits"
|
||||||
? loadCreditsRows()
|
? loadCreditsRows()
|
||||||
: active === "diamonds" || active === "duckets"
|
: active === "diamonds" || active === "duckets"
|
||||||
? // eslint-disable-next-line security/detect-object-injection -- active validated as "diamonds"|"duckets" in this branch
|
? // active validated as "diamonds"|"duckets" in this branch
|
||||||
loadCurrencyRows(CURRENCY_TYPE[active])
|
loadCurrencyRows(CURRENCY_TYPE[active])
|
||||||
: loadSettingsRows(
|
: loadSettingsRows(
|
||||||
SETTINGS_FIELD[active as keyof typeof SETTINGS_FIELD],
|
SETTINGS_FIELD[active as keyof typeof SETTINGS_FIELD],
|
||||||
|
|||||||
@@ -191,7 +191,6 @@ async function ArticlePage({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{article.image ? (
|
{article.image ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={article.image}
|
src={article.image}
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ export default async function RareCategoryPage({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{badgeSrc ? (
|
{badgeSrc ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={badgeSrc}
|
src={badgeSrc}
|
||||||
alt={`${cat.name} badge`}
|
alt={`${cat.name} badge`}
|
||||||
@@ -150,7 +149,6 @@ export default async function RareCategoryPage({
|
|||||||
<tr key={String(r.id)}>
|
<tr key={String(r.id)}>
|
||||||
<td>
|
<td>
|
||||||
{iconSrc ? (
|
{iconSrc ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={iconSrc}
|
src={iconSrc}
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ export default async function RareValuesPage() {
|
|||||||
>
|
>
|
||||||
<div className="content-card-head">
|
<div className="content-card-head">
|
||||||
{badgeSrc ? (
|
{badgeSrc ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={badgeSrc}
|
src={badgeSrc}
|
||||||
alt={`${c.name} badge`}
|
alt={`${c.name} badge`}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const STATE_LABELS: Record<string, { label: string; icon: string }> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function describeState(state: string): { label: string; icon: string } {
|
function describeState(state: string): { label: string; icon: string } {
|
||||||
// eslint-disable-next-line security/detect-object-injection -- STATE_LABELS has known keys, fallback provided
|
// STATE_LABELS has known keys, fallback provided
|
||||||
return STATE_LABELS[state] ?? { label: state || "Unknown", icon: "🚪" };
|
return STATE_LABELS[state] ?? { label: state || "Unknown", icon: "🚪" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,7 +249,6 @@ export default async function ShopPage({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{a.iconUrl ? (
|
{a.iconUrl ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={a.iconUrl}
|
src={a.iconUrl}
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@@ -366,7 +366,6 @@ async function ProfilePage({
|
|||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<ProfileImage
|
<ProfileImage
|
||||||
src={avatar}
|
src={avatar}
|
||||||
alt={user.username}
|
alt={user.username}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ export function AdsTable({ ads }: { ads: AdRow[] }) {
|
|||||||
{ads.map((ad) => (
|
{ads.map((ad) => (
|
||||||
<tr key={ad.id}>
|
<tr key={ad.id}>
|
||||||
<td>
|
<td>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={ad.image}
|
src={ad.image}
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ export default async function AdminCalendarCampaign({
|
|||||||
|
|
||||||
<div className="admin-card flex items-center gap-5 mb-6">
|
<div className="admin-card flex items-center gap-5 mb-6">
|
||||||
{campaign.image ? (
|
{campaign.image ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
className="w-24 h-24 object-cover"
|
className="w-24 h-24 object-cover"
|
||||||
src={campaign.image}
|
src={campaign.image}
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ export default async function AdminCalendar() {
|
|||||||
className="admin-card hover article"
|
className="admin-card hover article"
|
||||||
>
|
>
|
||||||
{c.image ? (
|
{c.image ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img className="article-img" src={c.image} alt={c.name} />
|
<img className="article-img" src={c.image} alt={c.name} />
|
||||||
) : (
|
) : (
|
||||||
<div className="article-img" />
|
<div className="article-img" />
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ export default async function AdminGuildDetailPage({
|
|||||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{guild.badge ? (
|
{guild.badge ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${guild.badge}.gif`}
|
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${guild.badge}.gif`}
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ export default async function AdminGuildsPage({
|
|||||||
<td className="muted">{g.id}</td>
|
<td className="muted">{g.id}</td>
|
||||||
<td>
|
<td>
|
||||||
{g.badge ? (
|
{g.badge ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${g.badge}.gif`}
|
src={`https://habbo-stories-content.s3.amazonaws.com/badge-part-images/${g.badge}.gif`}
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ export default async function AdminPhotos({
|
|||||||
className="admin-card"
|
className="admin-card"
|
||||||
style={{ padding: 0, overflow: "hidden" }}
|
style={{ padding: 0, overflow: "hidden" }}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={p.url}
|
src={p.url}
|
||||||
alt={t("photoAlt", { id: String(p.id) })}
|
alt={t("photoAlt", { id: String(p.id) })}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ function isRecord(v: unknown): v is Record<string, unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pickString(obj: Record<string, unknown>, key: string): string | null {
|
function pickString(obj: Record<string, unknown>, key: string): string | null {
|
||||||
// eslint-disable-next-line security/detect-object-injection -- only called with hardcoded keys
|
// only called with hardcoded keys
|
||||||
const v = obj[key];
|
const v = obj[key];
|
||||||
return typeof v === "string" && v.trim() !== "" ? v.trim() : null;
|
return typeof v === "string" && v.trim() !== "" ? v.trim() : null;
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ function findNumberDeep(
|
|||||||
if (typeof value === "number" && Number.isFinite(value)) return value;
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
||||||
if (!isRecord(value)) return null;
|
if (!isRecord(value)) return null;
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
// eslint-disable-next-line security/detect-object-injection -- keys from hardcoded array
|
// keys from hardcoded array
|
||||||
const v = value[key];
|
const v = value[key];
|
||||||
if (typeof v === "number" && Number.isFinite(v)) return v;
|
if (typeof v === "number" && Number.isFinite(v)) return v;
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -577,7 +577,6 @@ export function FurniClient({ room, items = [], canEdit }: Props) {
|
|||||||
)}
|
)}
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{icon ? (
|
{icon ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={icon}
|
src={icon}
|
||||||
alt={item.name}
|
alt={item.name}
|
||||||
@@ -733,7 +732,6 @@ export function FurniClient({ room, items = [], canEdit }: Props) {
|
|||||||
>
|
>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{icon ? (
|
{icon ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={icon}
|
src={icon}
|
||||||
alt={g.base.name}
|
alt={g.base.name}
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ export default async function AdminTheme({
|
|||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
// eslint-disable-next-line security/detect-object-injection -- k from fixed theme color key list
|
// k from fixed theme color key list
|
||||||
background: theme.settings[k] ?? "#64748b",
|
background: theme.settings[k] ?? "#64748b",
|
||||||
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.2)",
|
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.2)",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ export async function GET(req: Request) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const rarity = Object.fromEntries(
|
const rarity = Object.fromEntries(
|
||||||
// eslint-disable-next-line security/detect-object-injection -- rk from rarityKeys const, i is array index
|
// rk from rarityKeys const, i is array index
|
||||||
rarityKeys.map((rk, i) => [rk, rarityBoards[i]]),
|
rarityKeys.map((rk, i) => [rk, rarityBoards[i]]),
|
||||||
) as Record<BadgeRarityKey, BoardCore>;
|
) as Record<BadgeRarityKey, BoardCore>;
|
||||||
|
|
||||||
@@ -398,7 +398,7 @@ export async function GET(req: Request) {
|
|||||||
totalBadgesViewer = tb;
|
totalBadgesViewer = tb;
|
||||||
achievementViewer = ach;
|
achievementViewer = ach;
|
||||||
rarityViewer = Object.fromEntries(
|
rarityViewer = Object.fromEntries(
|
||||||
// eslint-disable-next-line security/detect-object-injection -- rk from rarityKeys const, i is array index
|
// rk from rarityKeys const, i is array index
|
||||||
rarityKeys.map((rk, i) => [rk, rvs[i]]),
|
rarityKeys.map((rk, i) => [rk, rvs[i]]),
|
||||||
) as Record<BadgeRarityKey, Partial<BadgeLeaderboardEntry> | undefined>;
|
) as Record<BadgeRarityKey, Partial<BadgeLeaderboardEntry> | undefined>;
|
||||||
}
|
}
|
||||||
@@ -414,7 +414,7 @@ export async function GET(req: Request) {
|
|||||||
viewerEntry: achievementViewer,
|
viewerEntry: achievementViewer,
|
||||||
},
|
},
|
||||||
rarity: Object.fromEntries(
|
rarity: Object.fromEntries(
|
||||||
// eslint-disable-next-line security/detect-object-injection -- rk from rarityKeys const
|
// rk from rarityKeys const
|
||||||
rarityKeys.map((rk) => [
|
rarityKeys.map((rk) => [
|
||||||
rk,
|
rk,
|
||||||
{ ...global.rarity[rk], viewerEntry: rarityViewer[rk] },
|
{ ...global.rarity[rk], viewerEntry: rarityViewer[rk] },
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export async function GET(req: Request) {
|
|||||||
async () =>
|
async () =>
|
||||||
type === "credits"
|
type === "credits"
|
||||||
? await loadCreditsRows()
|
? await loadCreditsRows()
|
||||||
: // eslint-disable-next-line security/detect-object-injection -- type validated to "diamonds"|"duckets"
|
: // type validated to "diamonds"|"duckets"
|
||||||
await loadCurrencyRows(CURRENCY_TYPE[type]),
|
await loadCurrencyRows(CURRENCY_TYPE[type]),
|
||||||
// Rankings are re-sorted on every read; serving the previous order for a
|
// Rankings are re-sorted on every read; serving the previous order for a
|
||||||
// minute keeps a busy leaderboard off the database.
|
// minute keeps a busy leaderboard off the database.
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ export async function GET(
|
|||||||
headers: SECURITY_HEADERS,
|
headers: SECURITY_HEADERS,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
if (!existsSync(filePath)) {
|
if (!existsSync(filePath)) {
|
||||||
return new NextResponse("Not found", {
|
return new NextResponse("Not found", {
|
||||||
status: 404,
|
status: 404,
|
||||||
@@ -59,7 +58,6 @@ export async function GET(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
const bytes = await readFile(filePath);
|
const bytes = await readFile(filePath);
|
||||||
const mime: Record<string, string> = {
|
const mime: Record<string, string> = {
|
||||||
".png": "image/png",
|
".png": "image/png",
|
||||||
@@ -76,7 +74,7 @@ export async function GET(
|
|||||||
headers: {
|
headers: {
|
||||||
...SECURITY_HEADERS,
|
...SECURITY_HEADERS,
|
||||||
...(ext === ".svg" ? { "Content-Disposition": "attachment" } : {}),
|
...(ext === ".svg" ? { "Content-Disposition": "attachment" } : {}),
|
||||||
// eslint-disable-next-line security/detect-object-injection -- ext validated against ALLOWED_EXT
|
// ext validated against ALLOWED_EXT
|
||||||
"Content-Type": mime[ext] ?? "application/octet-stream",
|
"Content-Type": mime[ext] ?? "application/octet-stream",
|
||||||
"Cache-Control": "public, max-age=3600, must-revalidate",
|
"Cache-Control": "public, max-age=3600, must-revalidate",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import { PERMS } from "@/lib/permissions";
|
|||||||
*/
|
*/
|
||||||
export const GET = withAdmin({ permission: PERMS.PAGES_EDIT }, async () => {
|
export const GET = withAdmin({ permission: PERMS.PAGES_EDIT }, async () => {
|
||||||
const dir = MEDIA_ROOT;
|
const dir = MEDIA_ROOT;
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
if (!existsSync(dir)) {
|
if (!existsSync(dir)) {
|
||||||
return apiOk({ files: [] });
|
return apiOk({ files: [] });
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
const files = readdirSync(dir)
|
const files = readdirSync(dir)
|
||||||
.filter((f) => /\.(png|jpg|jpeg|gif|webp|svg|bmp)$/i.test(f))
|
.filter((f) => /\.(png|jpg|jpeg|gif|webp|svg|bmp)$/i.test(f))
|
||||||
.map((f) => {
|
.map((f) => {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function findCount(value: unknown, depth = 0): number | null {
|
|||||||
"count",
|
"count",
|
||||||
];
|
];
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
// eslint-disable-next-line security/detect-object-injection -- keys from hardcoded array
|
// keys from hardcoded array
|
||||||
const v = value[key];
|
const v = value[key];
|
||||||
if (typeof v === "number" && Number.isFinite(v)) return v;
|
if (typeof v === "number" && Number.isFinite(v)) return v;
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ export function ArticleCard({
|
|||||||
<div className="admin-card overflow-hidden flex flex-col">
|
<div className="admin-card overflow-hidden flex flex-col">
|
||||||
{confirmDialog}
|
{confirmDialog}
|
||||||
<div className="relative w-full aspect-[16/9] bg-[var(--admin-canvas)]">
|
<div className="relative w-full aspect-[16/9] bg-[var(--admin-canvas)]">
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={image || ""}
|
src={image || ""}
|
||||||
alt={title}
|
alt={title}
|
||||||
|
|||||||
@@ -296,7 +296,6 @@ export function ArticleForm({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{image && !imageError ? (
|
{image && !imageError ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={image}
|
src={image}
|
||||||
alt={t("imagePreview")}
|
alt={t("imagePreview")}
|
||||||
|
|||||||
@@ -715,7 +715,6 @@ function FurniThumb({
|
|||||||
className="flex items-center justify-center rounded bg-muted/50"
|
className="flex items-center justify-center rounded bg-muted/50"
|
||||||
style={{ width: size, height: size }}
|
style={{ width: size, height: size }}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={primary}
|
src={primary}
|
||||||
alt={item.itemName}
|
alt={item.itemName}
|
||||||
|
|||||||
@@ -893,7 +893,7 @@ export function SortableTree({
|
|||||||
handleToggleEnabled,
|
handleToggleEnabled,
|
||||||
handleAddSubpage,
|
handleAddSubpage,
|
||||||
],
|
],
|
||||||
); // eslint-disable-line react-hooks/exhaustive-deps
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export function FurniIcon({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-10 h-10 rounded bg-muted/50 flex items-center justify-center overflow-hidden">
|
<div className="w-10 h-10 rounded bg-muted/50 flex items-center justify-center overflow-hidden">
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={localUrl}
|
src={localUrl}
|
||||||
alt={classname}
|
alt={classname}
|
||||||
|
|||||||
@@ -231,7 +231,6 @@ function FurniThumb({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded bg-muted/50">
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded bg-muted/50">
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={primary}
|
src={primary}
|
||||||
alt={classname}
|
alt={classname}
|
||||||
|
|||||||
@@ -192,7 +192,6 @@ export function ShopFurniIcon({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={primary}
|
src={primary}
|
||||||
alt={classname}
|
alt={classname}
|
||||||
|
|||||||
@@ -226,7 +226,6 @@ export function AdminMediaGrid() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={validImageUrl(f.url)}
|
src={validImageUrl(f.url)}
|
||||||
alt={f.name}
|
alt={f.name}
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ export function MediaPicker({
|
|||||||
e.currentTarget.style.borderColor = "transparent";
|
e.currentTarget.style.borderColor = "transparent";
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={validImageUrl(f.url)}
|
src={validImageUrl(f.url)}
|
||||||
alt={f.name}
|
alt={f.name}
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ export function FurniThumb({
|
|||||||
className="flex items-center justify-center rounded bg-muted/50"
|
className="flex items-center justify-center rounded bg-muted/50"
|
||||||
style={{ width: size, height: size }}
|
style={{ width: size, height: size }}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={primary}
|
src={primary}
|
||||||
alt={item.itemName}
|
alt={item.itemName}
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ export function ThemeManagerPanel({
|
|||||||
width: 14,
|
width: 14,
|
||||||
height: 14,
|
height: 14,
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
// eslint-disable-next-line security/detect-object-injection -- k from fixed key list
|
// k from fixed key list
|
||||||
background: palette.light[k],
|
background: palette.light[k],
|
||||||
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.15)",
|
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.15)",
|
||||||
marginLeft: -3,
|
marginLeft: -3,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function NavbarColorPicker() {
|
|||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
function apply(index: number) {
|
function apply(index: number) {
|
||||||
// eslint-disable-next-line security/detect-object-injection -- index validated by findIndex + Math.max, guard below
|
// index validated by findIndex + Math.max, guard below
|
||||||
const c = COLORS[index];
|
const c = COLORS[index];
|
||||||
if (!c) return;
|
if (!c) return;
|
||||||
document.documentElement.style.setProperty("--color-navbar", c.value);
|
document.documentElement.style.setProperty("--color-navbar", c.value);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function PhotoLightbox({ photos }: { photos: LightboxPhoto[] }) {
|
|||||||
// has to hold the overlay itself.
|
// has to hold the overlay itself.
|
||||||
const [closing, setClosing] = useState(false);
|
const [closing, setClosing] = useState(false);
|
||||||
const exitTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const exitTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
// eslint-disable-next-line security/detect-object-injection -- openIndex is numeric array index, guarded by null check
|
// openIndex is numeric array index, guarded by null check
|
||||||
const active = openIndex !== null ? photos[openIndex] : null;
|
const active = openIndex !== null ? photos[openIndex] : null;
|
||||||
|
|
||||||
const close = useCallback(() => {
|
const close = useCallback(() => {
|
||||||
@@ -97,7 +97,6 @@ export function PhotoLightbox({ photos }: { photos: LightboxPhoto[] }) {
|
|||||||
}}
|
}}
|
||||||
aria-label={p.alt}
|
aria-label={p.alt}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={p.url}
|
src={p.url}
|
||||||
alt={p.alt}
|
alt={p.alt}
|
||||||
@@ -180,7 +179,6 @@ export function PhotoLightbox({ photos }: { photos: LightboxPhoto[] }) {
|
|||||||
cursor: "default",
|
cursor: "default",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
<img
|
||||||
src={active.url}
|
src={active.url}
|
||||||
alt={active.alt}
|
alt={active.alt}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ function scanCharWidths(
|
|||||||
|
|
||||||
const w = end - start;
|
const w = end - start;
|
||||||
if (w > 0) {
|
if (w > 0) {
|
||||||
// eslint-disable-next-line security/detect-object-injection -- ci increments within CHAR_ORDER bounds
|
// ci increments within CHAR_ORDER bounds
|
||||||
chars.push({ char: CHAR_ORDER[ci], x: start, w });
|
chars.push({ char: CHAR_ORDER[ci], x: start, w });
|
||||||
ci++;
|
ci++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export function AvatarImage({
|
|||||||
(options?.headOnly ? "l" : max <= 40 ? "s" : max <= 80 ? "m" : "l");
|
(options?.headOnly ? "l" : max <= 40 ? "s" : max <= 80 ? "m" : "l");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
src={getAvatarUrl(figure, { ...options, size: autoSize })}
|
src={getAvatarUrl(figure, { ...options, size: autoSize })}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ const LABELS: Record<CurrencyType, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function currencyLabel(type: CurrencyType): string {
|
export function currencyLabel(type: CurrencyType): string {
|
||||||
// eslint-disable-next-line security/detect-object-injection -- type is CurrencyType enum
|
// type is CurrencyType enum
|
||||||
return LABELS[type];
|
return LABELS[type];
|
||||||
}
|
}
|
||||||
@@ -55,12 +55,10 @@ async function writeToFile(
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const logDir = resolve(process.cwd(), "storage", "logs");
|
const logDir = resolve(process.cwd(), "storage", "logs");
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
await mkdir(logDir, { recursive: true });
|
await mkdir(logDir, { recursive: true });
|
||||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||||
const filename = `email-${timestamp}.html`;
|
const filename = `email-${timestamp}.html`;
|
||||||
const content = `<!-- To: ${to} | From: ${from} | Subject: ${subject} -->\n${html}`;
|
const content = `<!-- To: ${to} | From: ${from} | Subject: ${subject} -->\n${html}`;
|
||||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
||||||
await writeFile(resolve(logDir, filename), content, "utf-8");
|
await writeFile(resolve(logDir, filename), content, "utf-8");
|
||||||
logger.info("Email written to file", { module: "email", filename });
|
logger.info("Email written to file", { module: "email", filename });
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export async function checkVpn(ip: string): Promise<IpVerdict> {
|
|||||||
string,
|
string,
|
||||||
{ proxy?: string; type?: string }
|
{ proxy?: string; type?: string }
|
||||||
>;
|
>;
|
||||||
// eslint-disable-next-line security/detect-object-injection -- ip is the API response key from proxycheck
|
// ip is the API response key from proxycheck
|
||||||
const entry = d?.[ip];
|
const entry = d?.[ip];
|
||||||
if (entry?.proxy === "yes")
|
if (entry?.proxy === "yes")
|
||||||
return { blocked: true, reason: `${entry.type ?? "Proxy"} detected` };
|
return { blocked: true, reason: `${entry.type ?? "Proxy"} detected` };
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export async function sendCurrency(
|
|||||||
data: { credits: { increment: amount } },
|
data: { credits: { increment: amount } },
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line security/detect-object-injection -- type is CurrencyName union, not "credits"
|
// type is CurrencyName union, not "credits"
|
||||||
const t = TYPE_VALUE[type];
|
const t = TYPE_VALUE[type];
|
||||||
await deps.db.usersCurrency.upsert({
|
await deps.db.usersCurrency.upsert({
|
||||||
where: { userId_type: { userId, type: t } },
|
where: { userId_type: { userId, type: t } },
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class SiteSettings {
|
|||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
const map = await this.load();
|
const map = await this.load();
|
||||||
if (map.has(key)) return map.get(key) as string;
|
if (map.has(key)) return map.get(key) as string;
|
||||||
// eslint-disable-next-line security/detect-object-injection -- guarded by `key in DEFAULTS`
|
// guarded by `key in DEFAULTS`
|
||||||
if (key in DEFAULTS) return DEFAULTS[key] as string;
|
if (key in DEFAULTS) return DEFAULTS[key] as string;
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ function fallbackFor(key: string): string {
|
|||||||
const preset = PRESETS["Atom (golden)"];
|
const preset = PRESETS["Atom (golden)"];
|
||||||
if (key.endsWith("_dark")) {
|
if (key.endsWith("_dark")) {
|
||||||
const base = key.slice(0, -"_dark".length) as ThemeColorKey;
|
const base = key.slice(0, -"_dark".length) as ThemeColorKey;
|
||||||
return preset.dark[base] ?? ""; // eslint-disable-line security/detect-object-injection -- key derived from internal THEME_COLOR_KEYS
|
return preset.dark[base] ?? ""; // key derived from internal THEME_COLOR_KEYS
|
||||||
}
|
}
|
||||||
if (key in preset.light) {
|
if (key in preset.light) {
|
||||||
return preset.light[key as ThemeColorKey] ?? "";
|
return preset.light[key as ThemeColorKey] ?? "";
|
||||||
@@ -63,7 +63,7 @@ function fallbackFor(key: string): string {
|
|||||||
theme_preset: "Atom (golden)",
|
theme_preset: "Atom (golden)",
|
||||||
...BACKGROUND_DEFAULTS,
|
...BACKGROUND_DEFAULTS,
|
||||||
};
|
};
|
||||||
return extras[key] ?? ""; // eslint-disable-line security/detect-object-injection -- key derived from internal EXTRA_KEYS
|
return extras[key] ?? ""; // key derived from internal EXTRA_KEYS
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listCustomThemes(): Promise<CustomTheme[]> {
|
export async function listCustomThemes(): Promise<CustomTheme[]> {
|
||||||
@@ -106,9 +106,9 @@ export async function upsertCustomTheme(
|
|||||||
if (id) {
|
if (id) {
|
||||||
const idx = themes.findIndex((t) => t.id === id);
|
const idx = themes.findIndex((t) => t.id === id);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
themes[idx] = { ...themes[idx], name: trimmed, settings }; // eslint-disable-line security/detect-object-injection -- idx from findIndex over internal array
|
themes[idx] = { ...themes[idx], name: trimmed, settings }; // idx from findIndex over internal array
|
||||||
await persist(themes);
|
await persist(themes);
|
||||||
return themes[idx]; // eslint-disable-line security/detect-object-injection -- idx from findIndex over internal array
|
return themes[idx]; // idx from findIndex over internal array
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const theme: CustomTheme = {
|
const theme: CustomTheme = {
|
||||||
|
|||||||
Reference in new issue
Block a user