.
This commit is contained in:
+66
-3
@@ -53,6 +53,8 @@ export function App() {
|
|||||||
const [model, setModel] = useState("");
|
const [model, setModel] = useState("");
|
||||||
const [convs, setConvs] = useState<Conversation[]>([]);
|
const [convs, setConvs] = useState<Conversation[]>([]);
|
||||||
const [activeId, setActiveId] = useState<string | null>(null);
|
const [activeId, setActiveId] = useState<string | null>(null);
|
||||||
|
// Conversation opened from "Archived chats" (read-only, not in the sidebar list).
|
||||||
|
const [archivedConv, setArchivedConv] = useState<Conversation | null>(null);
|
||||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||||
const [streaming, setStreaming] = useState("");
|
const [streaming, setStreaming] = useState("");
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
@@ -162,12 +164,14 @@ export function App() {
|
|||||||
setConvs([]);
|
setConvs([]);
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
setActiveId(null);
|
setActiveId(null);
|
||||||
|
setArchivedConv(null);
|
||||||
setAttachments([]);
|
setAttachments([]);
|
||||||
setFilePreview(null);
|
setFilePreview(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function newChat() {
|
async function newChat() {
|
||||||
try {
|
try {
|
||||||
|
setArchivedConv(null);
|
||||||
const res = await api.createConv({ model });
|
const res = await api.createConv({ model });
|
||||||
await refreshConvs(res.conversation.id);
|
await refreshConvs(res.conversation.id);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -176,7 +180,7 @@ export function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function send(text: string) {
|
async function send(text: string) {
|
||||||
if (sending || !model) return;
|
if (sending || !model || archivedConv) return;
|
||||||
let convId = activeId;
|
let convId = activeId;
|
||||||
if (!convId) {
|
if (!convId) {
|
||||||
try {
|
try {
|
||||||
@@ -271,12 +275,65 @@ export function App() {
|
|||||||
setActiveId(null);
|
setActiveId(null);
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
}
|
}
|
||||||
|
if (archivedConv?.id === c.id) {
|
||||||
|
setArchivedConv(null);
|
||||||
|
setMessages([]);
|
||||||
|
}
|
||||||
await refreshConvs();
|
await refreshConvs();
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Archive from the sidebar menu: the chat leaves the side list. */
|
||||||
|
async function archiveConv(c: Conversation) {
|
||||||
|
try {
|
||||||
|
await api.archiveConv(c.id);
|
||||||
|
if (activeId === c.id) {
|
||||||
|
setActiveId(null);
|
||||||
|
setMessages([]);
|
||||||
|
}
|
||||||
|
await refreshConvs();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Open an archived chat (from settings) read-only in the main view. */
|
||||||
|
function openArchived(c: Conversation) {
|
||||||
|
setSettingsOpen(false);
|
||||||
|
setMobileNav(false);
|
||||||
|
setArchivedConv(c);
|
||||||
|
setActiveId(null);
|
||||||
|
setMessages([]);
|
||||||
|
api.getConv(c.id).then((res) => setMessages(res.messages)).catch(() => setMessages([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unarchive the currently viewed archived chat and select it. */
|
||||||
|
async function unarchiveActive() {
|
||||||
|
if (!archivedConv) return;
|
||||||
|
try {
|
||||||
|
await api.unarchiveConv(archivedConv.id);
|
||||||
|
const id = archivedConv.id;
|
||||||
|
setArchivedConv(null);
|
||||||
|
await refreshConvs(id);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings "Archived chats" changed something: refresh the sidebar, and
|
||||||
|
* drop the read-only view if its conversation was deleted underneath us.
|
||||||
|
*/
|
||||||
|
async function handleArchivedChanged(deletedId?: string) {
|
||||||
|
if (deletedId && archivedConv?.id === deletedId) {
|
||||||
|
setArchivedConv(null);
|
||||||
|
setMessages([]);
|
||||||
|
}
|
||||||
|
await refreshConvs();
|
||||||
|
}
|
||||||
|
|
||||||
if (checking) {
|
if (checking) {
|
||||||
return <LoadingScreen />;
|
return <LoadingScreen />;
|
||||||
}
|
}
|
||||||
@@ -285,7 +342,8 @@ export function App() {
|
|||||||
return <Login onLogin={handleLogin} />;
|
return <Login onLogin={handleLogin} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const active = convs.find((c) => c.id === activeId) ?? null;
|
const active = convs.find((c) => c.id === activeId) ?? archivedConv;
|
||||||
|
const readOnly = (active?.archived_at ?? 0) !== 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full">
|
<div className="flex h-full">
|
||||||
@@ -296,10 +354,11 @@ export function App() {
|
|||||||
collapsed={sidebarCollapsed}
|
collapsed={sidebarCollapsed}
|
||||||
onToggle={() => setSidebarCollapsed(true)}
|
onToggle={() => setSidebarCollapsed(true)}
|
||||||
onNew={() => { newChat(); setMobileNav(false); }}
|
onNew={() => { newChat(); setMobileNav(false); }}
|
||||||
onSelect={(id) => { setActiveId(id); setMobileNav(false); }}
|
onSelect={(id) => { setArchivedConv(null); setActiveId(id); setMobileNav(false); }}
|
||||||
onRename={setEditConv}
|
onRename={setEditConv}
|
||||||
onTopic={setEditConv}
|
onTopic={setEditConv}
|
||||||
onShare={setShareConv}
|
onShare={setShareConv}
|
||||||
|
onArchive={archiveConv}
|
||||||
onDelete={setDeleteConv}
|
onDelete={setDeleteConv}
|
||||||
onOpenSettings={() => { setSettingsOpen(true); setMobileNav(false); }}
|
onOpenSettings={() => { setSettingsOpen(true); setMobileNav(false); }}
|
||||||
onOpenAdmin={() => { window.location.href = "/admin"; }}
|
onOpenAdmin={() => { window.location.href = "/admin"; }}
|
||||||
@@ -318,6 +377,8 @@ export function App() {
|
|||||||
onModelChange={setModel}
|
onModelChange={setModel}
|
||||||
onSend={send}
|
onSend={send}
|
||||||
onShare={() => active && setShareConv(active)}
|
onShare={() => active && setShareConv(active)}
|
||||||
|
readOnly={readOnly}
|
||||||
|
onUnarchive={unarchiveActive}
|
||||||
sidebarCollapsed={sidebarCollapsed}
|
sidebarCollapsed={sidebarCollapsed}
|
||||||
onExpandSidebar={() => setSidebarCollapsed(false)}
|
onExpandSidebar={() => setSidebarCollapsed(false)}
|
||||||
onOpenNav={() => setMobileNav(true)}
|
onOpenNav={() => setMobileNav(true)}
|
||||||
@@ -358,6 +419,8 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
// Key rotated or account deleted: sessions are dead server-side.
|
// Key rotated or account deleted: sessions are dead server-side.
|
||||||
onKeyRotated={() => { setSettingsOpen(false); logout(); }}
|
onKeyRotated={() => { setSettingsOpen(false); logout(); }}
|
||||||
|
onViewArchived={openArchived}
|
||||||
|
onArchivedChanged={handleArchivedChanged}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -243,6 +243,22 @@ export function Chat({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{readOnly ? (
|
||||||
|
<div className="mx-auto flex w-full max-w-3xl items-center gap-3 rounded-[1.75rem] border border-stone-200 bg-stone-100 p-2 pl-4 shadow-lg dark:border-zinc-700 dark:bg-zinc-800/60">
|
||||||
|
<span className="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-accent-600/10 text-accent-600 dark:text-accent-400">
|
||||||
|
<Archive size={17} />
|
||||||
|
</span>
|
||||||
|
<p className="min-w-0 flex-1 text-sm opacity-70">{t("archived.readonly")}</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onUnarchive}
|
||||||
|
className="inline-flex shrink-0 items-center gap-1.5 rounded-full bg-accent-600 px-4 py-2.5 text-sm font-medium text-white transition hover:bg-accent-500 dark:bg-accent-500 dark:text-zinc-950 dark:hover:bg-accent-400"
|
||||||
|
>
|
||||||
|
<ArchiveRestore size={15} />
|
||||||
|
{t("archived.unarchive")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<form onSubmit={submit} className={`mx-auto flex w-full max-w-3xl items-end gap-2 rounded-[1.75rem] border border-stone-200 bg-white p-2 shadow-lg dark:border-zinc-700 dark:bg-zinc-900 ${features.attachments ? "pl-2" : "pl-5"}`}>
|
<form onSubmit={submit} className={`mx-auto flex w-full max-w-3xl items-end gap-2 rounded-[1.75rem] border border-stone-200 bg-white p-2 shadow-lg dark:border-zinc-700 dark:bg-zinc-900 ${features.attachments ? "pl-2" : "pl-5"}`}>
|
||||||
{features.attachments && (
|
{features.attachments && (
|
||||||
<button
|
<button
|
||||||
@@ -281,6 +297,7 @@ export function Chat({
|
|||||||
<SendHorizontal size={17} />
|
<SendHorizontal size={17} />
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
)}
|
||||||
<p className="py-2 text-center text-xs opacity-50">
|
<p className="py-2 text-center text-xs opacity-50">
|
||||||
<a href="/wiki" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 underline-offset-2 hover:underline">
|
<a href="/wiki" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 underline-offset-2 hover:underline">
|
||||||
<BookOpen size={12} />
|
<BookOpen size={12} />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ArrowRight, KeyRound, MessageSquareText, ShieldCheck, User, UserPlus, Z
|
|||||||
import { api, setToken } from "../lib/api.ts";
|
import { api, setToken } from "../lib/api.ts";
|
||||||
import { useT } from "../lib/i18n.ts";
|
import { useT } from "../lib/i18n.ts";
|
||||||
import type { User as UserType } from "../lib/types.ts";
|
import type { User as UserType } from "../lib/types.ts";
|
||||||
import { Button, FlowerMark, Input, Spinner } from "./ui.tsx";
|
import { Button, FlowerMark, Input, LangPicker, Spinner } from "./ui.tsx";
|
||||||
import { RecoverDialog, RegisterDialog } from "./dialogs.tsx";
|
import { RecoverDialog, RegisterDialog } from "./dialogs.tsx";
|
||||||
import { AccessRequestModal } from "./ReviewPage.tsx";
|
import { AccessRequestModal } from "./ReviewPage.tsx";
|
||||||
|
|
||||||
@@ -202,6 +202,10 @@ export function Login({ onLogin }: { onLogin: (user: UserType) => void }) {
|
|||||||
)}
|
)}
|
||||||
<RecoverDialog open={recoverOpen} onClose={() => setRecoverOpen(false)} from={recoveryFrom} />
|
<RecoverDialog open={recoverOpen} onClose={() => setRecoverOpen(false)} from={recoveryFrom} />
|
||||||
|
|
||||||
|
<div className="mt-4 flex justify-center">
|
||||||
|
<LangPicker />
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul className="mt-6 space-y-2 border-t border-stone-200/70 pt-5 dark:border-zinc-800">
|
<ul className="mt-6 space-y-2 border-t border-stone-200/70 pt-5 dark:border-zinc-800">
|
||||||
{PERKS.map((p) => (
|
{PERKS.map((p) => (
|
||||||
<li key={p.text} className="flex items-center gap-2.5 text-[13px] opacity-70">
|
<li key={p.text} className="flex items-center gap-2.5 text-[13px] opacity-70">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Fingerprint, Globe, FileText, ImagePlus, KeyRound, Link2, Mail, Palette, Pencil, ScanText, Search, Settings2, ShieldCheck, Sparkles, Tag, Trash2, Unplug, User as UserIcon, UserPlus, X } from "lucide-react";
|
import { Archive, ArchiveRestore, Eye, Fingerprint, Globe, FileText, ImagePlus, KeyRound, Link2, Mail, Palette, Pencil, ScanText, Search, Settings2, ShieldCheck, Sparkles, Tag, Trash2, Unplug, User as UserIcon, UserPlus, X } from "lucide-react";
|
||||||
import { api } from "../lib/api.ts";
|
import { api } from "../lib/api.ts";
|
||||||
import { cn } from "../lib/cn.ts";
|
import { cn } from "../lib/cn.ts";
|
||||||
import { ACCENT_PRESETS, applyAccent, currentAccent, previewAccent, type AccentState } from "../lib/accent.ts";
|
import { ACCENT_PRESETS, applyAccent, currentAccent, previewAccent, type AccentState } from "../lib/accent.ts";
|
||||||
@@ -345,11 +345,15 @@ export function SettingsModal({
|
|||||||
onClose,
|
onClose,
|
||||||
onSaved,
|
onSaved,
|
||||||
onKeyRotated,
|
onKeyRotated,
|
||||||
|
onViewArchived,
|
||||||
|
onArchivedChanged,
|
||||||
}: {
|
}: {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSaved: (u: User) => void;
|
onSaved: (u: User) => void;
|
||||||
onKeyRotated?: () => void;
|
onKeyRotated?: () => void;
|
||||||
|
onViewArchived: (c: Conversation) => void;
|
||||||
|
onArchivedChanged: (deletedId?: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useT();
|
const { t } = useT();
|
||||||
const [displayName, setDisplayName] = useState("");
|
const [displayName, setDisplayName] = useState("");
|
||||||
@@ -382,6 +386,7 @@ export function SettingsModal({
|
|||||||
{ id: "appearance", title: t("settings.appearance"), icon: Palette, keywords: ["appearance", "theme", "dark", "light", "color", "colour", "accent", "palette", "language", "langue", "idioma", "lingua", "язык"] },
|
{ id: "appearance", title: t("settings.appearance"), icon: Palette, keywords: ["appearance", "theme", "dark", "light", "color", "colour", "accent", "palette", "language", "langue", "idioma", "lingua", "язык"] },
|
||||||
{ id: "features", title: t("settings.features"), icon: Sparkles, keywords: ["features", "thinking", "attachments", "search", "deep", "upload", "ocr", "capabilities", "enable", "disable"] },
|
{ id: "features", title: t("settings.features"), icon: Sparkles, keywords: ["features", "thinking", "attachments", "search", "deep", "upload", "ocr", "capabilities", "enable", "disable"] },
|
||||||
{ id: "security", title: t("settings.security"), icon: ShieldCheck, keywords: ["security", "key", "rotate", "password", "totp", "2fa", "two", "factor", "authenticator", "passkey", "webauthn"] },
|
{ id: "security", title: t("settings.security"), icon: ShieldCheck, keywords: ["security", "key", "rotate", "password", "totp", "2fa", "two", "factor", "authenticator", "passkey", "webauthn"] },
|
||||||
|
{ id: "archived", title: t("archived.title"), icon: Archive, keywords: ["archived", "archive", "old", "hidden", "read-only", "readonly", "archiv"] },
|
||||||
] as const;
|
] as const;
|
||||||
type CatId = (typeof CATS)[number]["id"];
|
type CatId = (typeof CATS)[number]["id"];
|
||||||
const [cat, setCat] = useState<CatId>("profile");
|
const [cat, setCat] = useState<CatId>("profile");
|
||||||
@@ -622,6 +627,12 @@ export function SettingsModal({
|
|||||||
<SecuritySection onKeyRotated={onKeyRotated} />
|
<SecuritySection onKeyRotated={onKeyRotated} />
|
||||||
</SettingsPane>
|
</SettingsPane>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{activeCat === "archived" && (
|
||||||
|
<SettingsPane>
|
||||||
|
<ArchivedSection onView={onViewArchived} onChanged={onArchivedChanged} />
|
||||||
|
</SettingsPane>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -651,6 +662,126 @@ function SettingsPane({ children }: { children: React.ReactNode }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- Archived chats (settings): view, unarchive, delete ---------- */
|
||||||
|
|
||||||
|
function ArchivedSection({ onView, onChanged }: { onView: (c: Conversation) => void; onChanged: (deletedId?: string) => void }) {
|
||||||
|
const { t } = useT();
|
||||||
|
const [list, setList] = useState<Conversation[] | null>(null);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [busyId, setBusyId] = useState<string | null>(null);
|
||||||
|
const [deleteTarget, setDeleteTarget] = useState<Conversation | null>(null);
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
try {
|
||||||
|
const res = await api.archivedConvs();
|
||||||
|
setList(res.conversations);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : t("common.loadFailed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refresh();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function unarchive(c: Conversation) {
|
||||||
|
setBusyId(c.id);
|
||||||
|
setError("");
|
||||||
|
try {
|
||||||
|
await api.unarchiveConv(c.id);
|
||||||
|
await refresh();
|
||||||
|
onChanged();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : t("common.saveFailed"));
|
||||||
|
} finally {
|
||||||
|
setBusyId(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove(c: Conversation) {
|
||||||
|
setBusyId(c.id);
|
||||||
|
setError("");
|
||||||
|
try {
|
||||||
|
await api.deleteConv(c.id);
|
||||||
|
setDeleteTarget(null);
|
||||||
|
await refresh();
|
||||||
|
onChanged(c.id);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : t("common.deleteFailed"));
|
||||||
|
} finally {
|
||||||
|
setBusyId(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list === null && !error) {
|
||||||
|
return <p className="flex items-center gap-2 py-4 text-sm opacity-60"><Spinner size={15} /> {t("common.loading")}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p className="-mb-2 text-xs opacity-60">{t("archived.readonly")}</p>
|
||||||
|
{error && <p className="text-sm text-red-600 dark:text-red-400">{error}</p>}
|
||||||
|
{(list ?? []).length === 0 ? (
|
||||||
|
<p className="py-4 text-center text-sm opacity-50">{t("archived.empty")}</p>
|
||||||
|
) : (
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{(list ?? []).map((c) => (
|
||||||
|
<li key={c.id} className="flex items-center gap-2 rounded-2xl border border-stone-200/70 px-3.5 py-2.5 dark:border-zinc-800">
|
||||||
|
<span className="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-accent-600/10 text-accent-600 dark:text-accent-400">
|
||||||
|
<Archive size={15} />
|
||||||
|
</span>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="truncate text-sm font-medium">{c.title}</p>
|
||||||
|
<p className="truncate text-xs opacity-50">{new Date(c.updated_at).toLocaleDateString()}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex shrink-0 gap-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title={t("archived.view")}
|
||||||
|
aria-label={`${t("archived.view")}: ${c.title}`}
|
||||||
|
onClick={() => onView(c)}
|
||||||
|
className="rounded-full p-2 transition hover:bg-stone-100 dark:hover:bg-zinc-800"
|
||||||
|
>
|
||||||
|
<Eye size={15} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title={t("archived.unarchive")}
|
||||||
|
aria-label={`${t("archived.unarchive")}: ${c.title}`}
|
||||||
|
disabled={busyId === c.id}
|
||||||
|
onClick={() => unarchive(c)}
|
||||||
|
className="rounded-full p-2 transition hover:bg-stone-100 disabled:opacity-50 dark:hover:bg-zinc-800"
|
||||||
|
>
|
||||||
|
<ArchiveRestore size={15} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title={t("common.delete")}
|
||||||
|
aria-label={`${t("common.delete")}: ${c.title}`}
|
||||||
|
disabled={busyId === c.id}
|
||||||
|
onClick={() => setDeleteTarget(c)}
|
||||||
|
className="rounded-full p-2 text-red-600 transition hover:bg-red-50 disabled:opacity-50 dark:text-red-400 dark:hover:bg-red-950/40"
|
||||||
|
>
|
||||||
|
<Trash2 size={15} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
<ConfirmDialog
|
||||||
|
open={deleteTarget !== null}
|
||||||
|
onClose={() => setDeleteTarget(null)}
|
||||||
|
title={t("archived.deleteTitle")}
|
||||||
|
message={t("archived.deleteMsg")}
|
||||||
|
confirmLabel={t("common.delete")}
|
||||||
|
onConfirm={() => deleteTarget && remove(deleteTarget)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Accent color (settings) ---------- */
|
/* ---------- Accent color (settings) ---------- */
|
||||||
|
|
||||||
function AccentSection() {
|
function AccentSection() {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ const en = {
|
|||||||
"sidebar.adminCenter": "Admin Center",
|
"sidebar.adminCenter": "Admin Center",
|
||||||
"sidebar.adminCenterTip": "Open the Admin Center",
|
"sidebar.adminCenterTip": "Open the Admin Center",
|
||||||
"sidebar.settingsTip": "Profile & settings",
|
"sidebar.settingsTip": "Profile & settings",
|
||||||
"sidebar.logout": "Log out",
|
"sidebar.archive": "Archive",
|
||||||
|
|
||||||
"chat.expand": "Expand sidebar",
|
"chat.expand": "Expand sidebar",
|
||||||
"chat.openNav": "Open chats",
|
"chat.openNav": "Open chats",
|
||||||
@@ -457,6 +457,9 @@ const en = {
|
|||||||
|
|
||||||
"admin.editUserTitle": "Edit @{user}",
|
"admin.editUserTitle": "Edit @{user}",
|
||||||
"admin.avatarUrl": "Avatar URL",
|
"admin.avatarUrl": "Avatar URL",
|
||||||
|
|
||||||
|
"archived.readonly": "This chat is archived and read-only.",
|
||||||
|
"archived.unarchive": "Unarchive",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type StringKey = keyof typeof en;
|
export type StringKey = keyof typeof en;
|
||||||
@@ -527,7 +530,7 @@ const es: Dict = {
|
|||||||
"sidebar.adminCenter": "Centro de admin",
|
"sidebar.adminCenter": "Centro de admin",
|
||||||
"sidebar.adminCenterTip": "Abrir el Centro de admin",
|
"sidebar.adminCenterTip": "Abrir el Centro de admin",
|
||||||
"sidebar.settingsTip": "Perfil y ajustes",
|
"sidebar.settingsTip": "Perfil y ajustes",
|
||||||
"sidebar.logout": "Cerrar sesión",
|
"sidebar.archive": "Archivar",
|
||||||
|
|
||||||
"chat.expand": "Mostrar barra lateral",
|
"chat.expand": "Mostrar barra lateral",
|
||||||
"chat.openNav": "Abrir chats",
|
"chat.openNav": "Abrir chats",
|
||||||
@@ -896,6 +899,9 @@ const es: Dict = {
|
|||||||
|
|
||||||
"admin.editUserTitle": "Editar @{user}",
|
"admin.editUserTitle": "Editar @{user}",
|
||||||
"admin.avatarUrl": "URL del avatar",
|
"admin.avatarUrl": "URL del avatar",
|
||||||
|
|
||||||
|
"archived.readonly": "Este chat está archivado y es de solo lectura.",
|
||||||
|
"archived.unarchive": "Desarchivar",
|
||||||
};
|
};
|
||||||
|
|
||||||
const fr: Dict = {
|
const fr: Dict = {
|
||||||
@@ -963,7 +969,7 @@ const fr: Dict = {
|
|||||||
"sidebar.adminCenter": "Centre admin",
|
"sidebar.adminCenter": "Centre admin",
|
||||||
"sidebar.adminCenterTip": "Ouvrir le Centre admin",
|
"sidebar.adminCenterTip": "Ouvrir le Centre admin",
|
||||||
"sidebar.settingsTip": "Profil et réglages",
|
"sidebar.settingsTip": "Profil et réglages",
|
||||||
"sidebar.logout": "Déconnexion",
|
"sidebar.archive": "Archiver",
|
||||||
|
|
||||||
"chat.expand": "Afficher la barre latérale",
|
"chat.expand": "Afficher la barre latérale",
|
||||||
"chat.openNav": "Ouvrir les chats",
|
"chat.openNav": "Ouvrir les chats",
|
||||||
@@ -1332,6 +1338,9 @@ const fr: Dict = {
|
|||||||
|
|
||||||
"admin.editUserTitle": "Modifier @{user}",
|
"admin.editUserTitle": "Modifier @{user}",
|
||||||
"admin.avatarUrl": "URL de l'avatar",
|
"admin.avatarUrl": "URL de l'avatar",
|
||||||
|
|
||||||
|
"archived.readonly": "Ce chat est archivé en lecture seule.",
|
||||||
|
"archived.unarchive": "Désarchiver",
|
||||||
};
|
};
|
||||||
|
|
||||||
const ru: Dict = {
|
const ru: Dict = {
|
||||||
@@ -1399,7 +1408,7 @@ const ru: Dict = {
|
|||||||
"sidebar.adminCenter": "Админ-центр",
|
"sidebar.adminCenter": "Админ-центр",
|
||||||
"sidebar.adminCenterTip": "Открыть Админ-центр",
|
"sidebar.adminCenterTip": "Открыть Админ-центр",
|
||||||
"sidebar.settingsTip": "Профиль и настройки",
|
"sidebar.settingsTip": "Профиль и настройки",
|
||||||
"sidebar.logout": "Выйти",
|
"sidebar.archive": "В архив",
|
||||||
|
|
||||||
"chat.expand": "Показать панель",
|
"chat.expand": "Показать панель",
|
||||||
"chat.openNav": "Открыть чаты",
|
"chat.openNav": "Открыть чаты",
|
||||||
@@ -1755,6 +1764,9 @@ const ru: Dict = {
|
|||||||
|
|
||||||
"admin.editUserTitle": "Править @{user}",
|
"admin.editUserTitle": "Править @{user}",
|
||||||
"admin.avatarUrl": "URL аватара",
|
"admin.avatarUrl": "URL аватара",
|
||||||
|
|
||||||
|
"archived.readonly": "Чат в архиве, только чтение.",
|
||||||
|
"archived.unarchive": "Вернуть",
|
||||||
};
|
};
|
||||||
|
|
||||||
const it: Dict = {
|
const it: Dict = {
|
||||||
@@ -1822,7 +1834,7 @@ const it: Dict = {
|
|||||||
"sidebar.adminCenter": "Centro admin",
|
"sidebar.adminCenter": "Centro admin",
|
||||||
"sidebar.adminCenterTip": "Apri il Centro admin",
|
"sidebar.adminCenterTip": "Apri il Centro admin",
|
||||||
"sidebar.settingsTip": "Profilo e impostazioni",
|
"sidebar.settingsTip": "Profilo e impostazioni",
|
||||||
"sidebar.logout": "Esci",
|
"sidebar.archive": "Archivia",
|
||||||
|
|
||||||
"chat.expand": "Mostra barra laterale",
|
"chat.expand": "Mostra barra laterale",
|
||||||
"chat.openNav": "Apri chat",
|
"chat.openNav": "Apri chat",
|
||||||
@@ -2184,6 +2196,9 @@ const it: Dict = {
|
|||||||
|
|
||||||
"admin.editUserTitle": "Modifica @{user}",
|
"admin.editUserTitle": "Modifica @{user}",
|
||||||
"admin.avatarUrl": "URL avatar",
|
"admin.avatarUrl": "URL avatar",
|
||||||
|
|
||||||
|
"archived.readonly": "Questa chat è archiviata e in sola lettura.",
|
||||||
|
"archived.unarchive": "Ripristina",
|
||||||
};
|
};
|
||||||
|
|
||||||
const pt: Dict = {
|
const pt: Dict = {
|
||||||
@@ -2251,7 +2266,7 @@ const pt: Dict = {
|
|||||||
"sidebar.adminCenter": "Centro admin",
|
"sidebar.adminCenter": "Centro admin",
|
||||||
"sidebar.adminCenterTip": "Abrir o Centro admin",
|
"sidebar.adminCenterTip": "Abrir o Centro admin",
|
||||||
"sidebar.settingsTip": "Perfil e definições",
|
"sidebar.settingsTip": "Perfil e definições",
|
||||||
"sidebar.logout": "Sair",
|
"sidebar.archive": "Arquivar",
|
||||||
|
|
||||||
"chat.expand": "Mostrar barra lateral",
|
"chat.expand": "Mostrar barra lateral",
|
||||||
"chat.openNav": "Abrir chats",
|
"chat.openNav": "Abrir chats",
|
||||||
@@ -2604,6 +2619,9 @@ const pt: Dict = {
|
|||||||
|
|
||||||
"admin.editUserTitle": "Editar @{user}",
|
"admin.editUserTitle": "Editar @{user}",
|
||||||
"admin.avatarUrl": "URL do avatar",
|
"admin.avatarUrl": "URL do avatar",
|
||||||
|
|
||||||
|
"archived.readonly": "Este chat está arquivado e é só de leitura.",
|
||||||
|
"archived.unarchive": "Desarquivar",
|
||||||
};
|
};
|
||||||
|
|
||||||
const dicts: Record<LangId, Dict> = { en, es, fr, ru, it, pt };
|
const dicts: Record<LangId, Dict> = { en, es, fr, ru, it, pt };
|
||||||
|
|||||||
Reference in New Issue
Block a user