changed verifier bot to marksbot
This commit is contained in:
parent
6ade34970c
commit
fa5cfaf14d
16 changed files with 302 additions and 30 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
cmd/telesrv-admin/web/dist/assets/index-EkAGiEK9.css
vendored
Normal file
1
cmd/telesrv-admin/web/dist/assets/index-EkAGiEK9.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
cmd/telesrv-admin/web/dist/index.html
vendored
4
cmd/telesrv-admin/web/dist/index.html
vendored
|
|
@ -23,8 +23,8 @@
|
|||
})();
|
||||
</script>
|
||||
|
||||
<script type="module" crossorigin src="/assets/index-65rEwtSD.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CKoIcj6p.css">
|
||||
<script type="module" crossorigin src="/assets/index-D0qBFBU-.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-EkAGiEK9.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
130
cmd/telesrv-admin/web/src/components/EmojiPicker.tsx
Normal file
130
cmd/telesrv-admin/web/src/components/EmojiPicker.tsx
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import { Check, Loader2, Search, X } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { api, errorMessage } from "../api";
|
||||
import type { EmojiRow } from "../types";
|
||||
import { StaticLottie } from "./StaticLottie";
|
||||
|
||||
function isAnimated(mime: string): boolean {
|
||||
const m = mime.toLowerCase();
|
||||
return m.includes("tgsticker") || m.includes("lottie") || m.includes("json");
|
||||
}
|
||||
|
||||
function EmojiThumb({ row }: { row: EmojiRow }) {
|
||||
const [failed, setFailed] = useState(!isAnimated(row.MimeType));
|
||||
|
||||
useEffect(() => {
|
||||
setFailed(!isAnimated(row.MimeType));
|
||||
}, [row.DocumentID, row.MimeType]);
|
||||
|
||||
if (failed) {
|
||||
return <div className="emoji-picker-glyph">{row.Alt || "🙂"}</div>;
|
||||
}
|
||||
return (
|
||||
<StaticLottie
|
||||
className="emoji-picker-anim"
|
||||
cacheKey={row.DocumentID}
|
||||
loader={() => api.emojiAnimation(row.DocumentID)}
|
||||
onError={() => setFailed(true)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// EmojiPicker searches every custom-emoji document already on this
|
||||
// deployment -- unlike the Emoji admin page's per-pack browsing, this is a
|
||||
// flat document search with no system/non-system distinction, so a bundled
|
||||
// icon from a default pack (e.g. Topics' ✅) is just as findable as a
|
||||
// hand-uploaded one. Lets the caller select a document id by clicking it
|
||||
// instead of typing one from memory; used wherever a raw document id field
|
||||
// otherwise has nothing else in the panel to pick from (e.g. a
|
||||
// bot-verification icon).
|
||||
export function EmojiPicker({
|
||||
label,
|
||||
value,
|
||||
onChange
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: (documentID: string) => void;
|
||||
}) {
|
||||
const [query, setQuery] = useState("");
|
||||
const [rows, setRows] = useState<EmojiRow[]>([]);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const selected = rows.find((row) => row.DocumentID === value) ?? null;
|
||||
|
||||
async function search() {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
const params = new URLSearchParams({ limit: "24" });
|
||||
if (query.trim()) params.set("q", query.trim());
|
||||
try {
|
||||
const result = await api.emoji(params);
|
||||
setRows(result.rows ?? []);
|
||||
} catch (err) {
|
||||
setError(errorMessage(err));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void search();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="entity-picker">
|
||||
<div className="picker-head">
|
||||
<span>{label}</span>
|
||||
{value ? (
|
||||
<button className="link-button" type="button" onClick={() => onChange("")}>
|
||||
<X size={13} /> {"Clear"}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{value ? (
|
||||
<div className="selected-entity">
|
||||
<Check size={15} />
|
||||
<div>
|
||||
<strong>{selected?.Alt || "—"}</strong>
|
||||
<span className="mono">{value}</span>
|
||||
</div>
|
||||
<span>{selected?.SetTitle || "-"}</span>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="picker-search">
|
||||
<Search size={15} />
|
||||
<input
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
void search();
|
||||
}
|
||||
}}
|
||||
placeholder={"Search document ID or emoji"}
|
||||
/>
|
||||
<button className="btn compact-btn" type="button" onClick={search} disabled={busy}>
|
||||
{busy ? <Loader2 size={14} className="spin" /> : "Search"}
|
||||
</button>
|
||||
</div>
|
||||
{error && <div className="picker-error">{error}</div>}
|
||||
<div className="picker-results emoji-picker-results">
|
||||
{rows.map((row) => (
|
||||
<button
|
||||
key={row.DocumentID}
|
||||
className={`picker-row emoji-picker-row ${value === row.DocumentID ? "selected" : ""}`}
|
||||
type="button"
|
||||
onClick={() => onChange(row.DocumentID)}
|
||||
>
|
||||
<EmojiThumb row={row} />
|
||||
<span className="mono">{row.DocumentID}</span>
|
||||
<span>{row.SetTitle || "—"}</span>
|
||||
</button>
|
||||
))}
|
||||
{rows.length === 0 && !busy ? <div className="picker-empty">{"No results"}</div> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import {
|
|||
import { useEffect, useState, type ReactNode } from "react";
|
||||
import { api, APIError, errorMessage } from "../api";
|
||||
import { ActionButton } from "../components/ActionButton";
|
||||
import { EmojiPicker } from "../components/EmojiPicker";
|
||||
import { BotPicker } from "../components/EntityPicker";
|
||||
import { Alert, Badge, EmptyRow, Metric, PageFrame, QueryPanel, SectionHead } from "../components/ui";
|
||||
import { displayUsername, formatDate } from "../lib/format";
|
||||
|
|
@ -623,17 +624,9 @@ function IconsBlock({
|
|||
<>
|
||||
{canManage && (
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Add or rename an icon"} text={"The document id has to name a real custom emoji document on this deployment; the Emoji section lists them with their ids. Adding an id that already exists renames it instead of duplicating it."} />
|
||||
<SectionHead title={"Add or rename an icon"} text={"Search and pick any custom-emoji document already on this deployment (including bundled/system ones). Adding an id that already exists renames it instead of duplicating it."} />
|
||||
<EmojiPicker label={"Document"} value={documentID} onChange={setDocumentID} />
|
||||
<div className="bot-create-fields">
|
||||
<label className="duration-field">
|
||||
<span>{"Document ID"}</span>
|
||||
<input
|
||||
value={documentID}
|
||||
onChange={(event) => setDocumentID(event.target.value)}
|
||||
inputMode="numeric"
|
||||
placeholder="5361371319611781774"
|
||||
/>
|
||||
</label>
|
||||
<label className="duration-field">
|
||||
<span>{"Name"}</span>
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -11,9 +11,8 @@ import { StickerSetPreviewModal } from "./StickerSetPreviewModal";
|
|||
type StickerPageSize = 10 | 20 | 50 | 100 | "all";
|
||||
|
||||
// Shared list/manage view for one non-system sticker-set kind ("stickers" or
|
||||
// "emoji") — system packs (dice, animated emoji, premium/TON gifts, etc.) are
|
||||
// filtered out server-side and never reach this page; they aren't meant to be
|
||||
// hand-edited.
|
||||
// "emoji") — system packs (dice, animated emoji, gifts) aren't shown here,
|
||||
// they're not hand-edited.
|
||||
export function StickerSetsPage({ kind }: { kind: "stickers" | "emoji" }) {
|
||||
const [sets, setSets] = useState<StickerSetRow[]>([]);
|
||||
const [query, setQuery] = useState("");
|
||||
|
|
|
|||
|
|
@ -317,6 +317,26 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.emoji-picker-row {
|
||||
grid-template-columns: 36px minmax(140px, 1fr) minmax(100px, 1fr);
|
||||
}
|
||||
|
||||
.emoji-picker-glyph {
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.emoji-picker-anim {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.emoji-picker-anim canvas {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.picker-error {
|
||||
color: var(--danger);
|
||||
background: var(--danger-tint);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue