feat(web): tidy the channel sidebar and let owners delete groups
This commit is contained in:
1 parent
6310f93469
commit
6d6490055e
12 files changed
+66
-13
No files matched your search
@@ -27,6 +27,7 @@ export interface WorkspaceApi {
|
|||||||
createThread(name: string, groupId?: string): Promise<string>;
|
createThread(name: string, groupId?: string): Promise<string>;
|
||||||
createGroup(name: string): Promise<string>;
|
createGroup(name: string): Promise<string>;
|
||||||
renameGroup(id: string, name: string): Promise<void>;
|
renameGroup(id: string, name: string): Promise<void>;
|
||||||
|
deleteGroup(id: string): Promise<void>;
|
||||||
groupThread(id: string, groupId?: string): Promise<void>;
|
groupThread(id: string, groupId?: string): Promise<void>;
|
||||||
renameThread(threadId: string, name: string): Promise<void>;
|
renameThread(threadId: string, name: string): Promise<void>;
|
||||||
setChannelHistory(threadId: string, chatHistory: boolean, filesHistory: boolean): Promise<void>;
|
setChannelHistory(threadId: string, chatHistory: boolean, filesHistory: boolean): Promise<void>;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export const realApi: WorkspaceApi = {
|
|||||||
sendFiles: (files, options, threadId, folderId, localIds) => callWorker<void>('sendFiles', { id: currentChannelId(), files, options, threadId, folderId, localIds, apiBase: API_BASE }),
|
sendFiles: (files, options, threadId, folderId, localIds) => callWorker<void>('sendFiles', { id: currentChannelId(), files, options, threadId, folderId, localIds, apiBase: API_BASE }),
|
||||||
createThread: (name, groupId) => callWorker<string>('createThread', { id: currentChannelId(), name, groupId, apiBase: API_BASE }),
|
createThread: (name, groupId) => callWorker<string>('createThread', { id: currentChannelId(), name, groupId, apiBase: API_BASE }),
|
||||||
createGroup: (name) => callWorker<string>('createGroup', { id: currentChannelId(), name, apiBase: API_BASE }),
|
createGroup: (name) => callWorker<string>('createGroup', { id: currentChannelId(), name, apiBase: API_BASE }),
|
||||||
|
deleteGroup: (groupId) => callWorker<void>('deleteGroup', { id: currentChannelId(), groupId, apiBase: API_BASE }),
|
||||||
renameGroup: (groupId, name) => callWorker<void>('renameGroup', { id: currentChannelId(), groupId, name, apiBase: API_BASE }),
|
renameGroup: (groupId, name) => callWorker<void>('renameGroup', { id: currentChannelId(), groupId, name, apiBase: API_BASE }),
|
||||||
groupThread: (threadId, groupId) => callWorker<void>('groupThread', { id: currentChannelId(), threadId, groupId, apiBase: API_BASE }),
|
groupThread: (threadId, groupId) => callWorker<void>('groupThread', { id: currentChannelId(), threadId, groupId, apiBase: API_BASE }),
|
||||||
renameThread: (threadId, name) => callWorker<void>('renameThread', { id: currentChannelId(), threadId, name, apiBase: API_BASE }),
|
renameThread: (threadId, name) => callWorker<void>('renameThread', { id: currentChannelId(), threadId, name, apiBase: API_BASE }),
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { setEventSink } from './worker/port';
|
|||||||
import { serial } from './worker/serial';
|
import { serial } from './worker/serial';
|
||||||
import { errorMessage } from './errors';
|
import { errorMessage } from './errors';
|
||||||
import { accountStatus, loginAccount, logoutAccount, registerAccount, syncAccount, listAccountInvitations, acceptInvitation, inviteChannelUser, listChannelInvitations, revokeChannelInvitation } from './worker/account';
|
import { accountStatus, loginAccount, logoutAccount, registerAccount, syncAccount, listAccountInvitations, acceptInvitation, inviteChannelUser, listChannelInvitations, revokeChannelInvitation } from './worker/account';
|
||||||
import { createFolder, createThread, createGroup, renameGroup, groupThread, moveItems, renameFolder, renameThread, setChannelHistory } from './worker/channelThreads';
|
import { createFolder, createThread, createGroup, renameGroup, deleteGroup, groupThread, moveItems, renameFolder, renameThread, setChannelHistory } from './worker/channelThreads';
|
||||||
import { restoreAccount, updatePersonal, listAccountDevices, renameAccountDevice, revokeAccountDevice, getSavedLogin, clearSavedLogin } from './worker/account';
|
import { restoreAccount, updatePersonal, listAccountDevices, renameAccountDevice, revokeAccountDevice, getSavedLogin, clearSavedLogin } from './worker/account';
|
||||||
|
|
||||||
interface WorkerRequest { requestId: number; operation: string; payload: unknown }
|
interface WorkerRequest { requestId: number; operation: string; payload: unknown }
|
||||||
@@ -62,6 +62,7 @@ async function dispatch(operation: string, input: unknown): Promise<unknown> {
|
|||||||
case 'updateChannelSettings': return updateChannelSettings(id, payload.patch as Parameters<typeof updateChannelSettings>[1]);
|
case 'updateChannelSettings': return updateChannelSettings(id, payload.patch as Parameters<typeof updateChannelSettings>[1]);
|
||||||
case 'createThread': return createThread(id, String(payload.name ?? ''), typeof payload.groupId === 'string' ? payload.groupId : undefined);
|
case 'createThread': return createThread(id, String(payload.name ?? ''), typeof payload.groupId === 'string' ? payload.groupId : undefined);
|
||||||
case 'createGroup': return createGroup(id, String(payload.name ?? ''));
|
case 'createGroup': return createGroup(id, String(payload.name ?? ''));
|
||||||
|
case 'deleteGroup': return deleteGroup(id, String(payload.groupId ?? ''));
|
||||||
case 'renameGroup': return renameGroup(id, String(payload.groupId ?? ''), String(payload.name ?? ''));
|
case 'renameGroup': return renameGroup(id, String(payload.groupId ?? ''), String(payload.name ?? ''));
|
||||||
case 'groupThread': return groupThread(id, String(payload.threadId ?? ''), typeof payload.groupId === 'string' ? payload.groupId : undefined);
|
case 'groupThread': return groupThread(id, String(payload.threadId ?? ''), typeof payload.groupId === 'string' ? payload.groupId : undefined);
|
||||||
case 'renameThread': return renameThread(id, String(payload.threadId ?? ''), String(payload.name ?? ''));
|
case 'renameThread': return renameThread(id, String(payload.threadId ?? ''), String(payload.name ?? ''));
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ export async function createGroup(resourceId: string, name: string): Promise<str
|
|||||||
export async function renameGroup(resourceId: string, id: string, name: string): Promise<void> {
|
export async function renameGroup(resourceId: string, id: string, name: string): Promise<void> {
|
||||||
await changeWorkspace(resourceId, 'groups/' + id, 'PATCH', { name } satisfies WorkspaceNameRequest);
|
await changeWorkspace(resourceId, 'groups/' + id, 'PATCH', { name } satisfies WorkspaceNameRequest);
|
||||||
}
|
}
|
||||||
|
export async function deleteGroup(resourceId: string, id: string): Promise<void> {
|
||||||
|
await changeWorkspace(resourceId, 'groups/' + id, 'DELETE');
|
||||||
|
}
|
||||||
export async function groupThread(resourceId: string, id: string, groupId?: string): Promise<void> {
|
export async function groupThread(resourceId: string, id: string, groupId?: string): Promise<void> {
|
||||||
await changeWorkspace(resourceId, 'channels/' + id + '/group', 'PUT', { groupId: groupId ?? null } satisfies WorkspaceGroupRequest);
|
await changeWorkspace(resourceId, 'channels/' + id + '/group', 'PUT', { groupId: groupId ?? null } satisfies WorkspaceGroupRequest);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export async function loadWorkspace(id: string): Promise<WorkspaceBody> {
|
|||||||
const body = await apiFetch(`/spaces/${id}/structure`, { headers: session.authHeaders('read') }).then(r => r.json() as Promise<WorkspaceBody>);
|
const body = await apiFetch(`/spaces/${id}/structure`, { headers: session.authHeaders('read') }).then(r => r.json() as Promise<WorkspaceBody>);
|
||||||
return publish(id, body);
|
return publish(id, body);
|
||||||
}
|
}
|
||||||
export async function changeWorkspace(id: string, path: string, method: 'POST' | 'PATCH' | 'PUT', value: unknown): Promise<WorkspaceBody> {
|
export async function changeWorkspace(id: string, path: string, method: 'POST' | 'PATCH' | 'PUT' | 'DELETE', value?: unknown): Promise<WorkspaceBody> {
|
||||||
const { session } = await getSession(id);
|
const { session } = await getSession(id);
|
||||||
const body = await apiFetch(`/spaces/${id}/${path}`, {
|
const body = await apiFetch(`/spaces/${id}/${path}`, {
|
||||||
method, headers: { ...session.authHeaders(session.can('owner') ? 'owner' : 'write'), 'Content-Type': 'application/json' }, body: JSON.stringify(value),
|
method, headers: { ...session.authHeaders(session.can('owner') ? 'owner' : 'write'), 'Content-Type': 'application/json' }, body: JSON.stringify(value),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.pad {
|
.pad {
|
||||||
margin-top: var(--space-2);
|
margin-top: var(--space-2);
|
||||||
padding: var(--space-1) var(--space-2);
|
padding: var(--space-1) var(--space-3);
|
||||||
border-top: 1px solid var(--line);
|
border-top: 1px solid var(--line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,15 @@
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dangerZone {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-2);
|
||||||
|
align-items: flex-start;
|
||||||
|
padding-top: var(--space-2);
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
.modalActions {
|
.modalActions {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: var(--space-2);
|
margin-top: var(--space-2);
|
||||||
@@ -147,11 +156,19 @@
|
|||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding-inline: var(--space-1);
|
padding-inline: var(--space-1) 0;
|
||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heading button {
|
||||||
|
width: 26px;
|
||||||
|
min-width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
min-height: 26px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.folderHeading {
|
.folderHeading {
|
||||||
padding-top: var(--space-2);
|
padding-top: var(--space-2);
|
||||||
border-top: 1px solid var(--line);
|
border-top: 1px solid var(--line);
|
||||||
@@ -167,9 +184,8 @@
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 频道行缩进到分组标题文字之下 */
|
|
||||||
.threads {
|
.threads {
|
||||||
padding-inline-start: var(--space-4);
|
padding-inline-start: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nestedThreads {
|
.nestedThreads {
|
||||||
@@ -219,11 +235,13 @@
|
|||||||
gap: var(--space-2);
|
gap: var(--space-2);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding: 0 var(--space-2);
|
padding: 0 var(--space-2) 0 var(--space-1);
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
|
font: inherit;
|
||||||
|
font-size: 14px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -259,12 +277,14 @@
|
|||||||
.thread.selected {
|
.thread.selected {
|
||||||
background: var(--accent-soft);
|
background: var(--accent-soft);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread > span:first-child {
|
.thread > span:first-child {
|
||||||
|
width: 14px;
|
||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty,
|
.empty,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ interface Props {
|
|||||||
onRenameThread: (id: string, name: string) => Promise<void>;
|
onRenameThread: (id: string, name: string) => Promise<void>;
|
||||||
onCreateGroup: (name: string) => Promise<string>;
|
onCreateGroup: (name: string) => Promise<string>;
|
||||||
onRenameGroup: (id: string, name: string) => Promise<void>;
|
onRenameGroup: (id: string, name: string) => Promise<void>;
|
||||||
|
onDeleteGroup: (id: string) => Promise<void>;
|
||||||
onGroupThread: (id: string, groupId?: string) => Promise<void>;
|
onGroupThread: (id: string, groupId?: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
type Editor = { kind: 'channel' | 'group'; id?: string; groupId?: string };
|
type Editor = { kind: 'channel' | 'group'; id?: string; groupId?: string };
|
||||||
@@ -29,16 +30,24 @@ export function ThreadSidebar(props: Props) {
|
|||||||
const [chatHistory, setChatHistory] = useState(true);
|
const [chatHistory, setChatHistory] = useState(true);
|
||||||
const [filesHistory, setFilesHistory] = useState(true);
|
const [filesHistory, setFilesHistory] = useState(true);
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [collapsed, setCollapsed] = useState<ReadonlySet<string>>(new Set());
|
const [collapsed, setCollapsed] = useState<ReadonlySet<string>>(new Set());
|
||||||
const knownGroups = new Set(props.groups.map((group) => group.id));
|
const knownGroups = new Set(props.groups.map((group) => group.id));
|
||||||
const ungrouped = props.threads.filter((thread) => !thread.groupId || !knownGroups.has(thread.groupId));
|
const ungrouped = props.threads.filter((thread) => !thread.groupId || !knownGroups.has(thread.groupId));
|
||||||
function begin(target: Editor, initial = '') {
|
function begin(target: Editor, initial = '') {
|
||||||
setEdit(target); setName(initial); setGroupId(target.groupId ?? ''); setError('');
|
setEdit(target); setName(initial); setGroupId(target.groupId ?? ''); setError(''); setConfirmDelete(false);
|
||||||
const channel = props.threads.find(t => t.id === target.id); setChatHistory(channel?.chatHistory ?? true); setFilesHistory(channel?.filesHistory ?? true);
|
const channel = props.threads.find(t => t.id === target.id); setChatHistory(channel?.chatHistory ?? true); setFilesHistory(channel?.filesHistory ?? true);
|
||||||
}
|
}
|
||||||
function toggle(id: string) { setCollapsed((previous) => { const next = new Set(previous); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }
|
function toggle(id: string) { setCollapsed((previous) => { const next = new Set(previous); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }
|
||||||
function select(id: string) { props.onSelectThread(id); setEdit(undefined); }
|
function select(id: string) { props.onSelectThread(id); setEdit(undefined); }
|
||||||
|
async function removeGroup() {
|
||||||
|
if (!edit?.id || busy || !props.enabled) return;
|
||||||
|
setBusy(true); setError('');
|
||||||
|
try { await props.onDeleteGroup(edit.id); setEdit(undefined); }
|
||||||
|
catch (cause) { setError(cause instanceof Error ? cause.message : '删除失败'); }
|
||||||
|
finally { setBusy(false); }
|
||||||
|
}
|
||||||
async function submit(event: FormEvent) {
|
async function submit(event: FormEvent) {
|
||||||
event.preventDefault(); if (!edit || !name.trim() || busy || !props.enabled) return;
|
event.preventDefault(); if (!edit || !name.trim() || busy || !props.enabled) return;
|
||||||
setBusy(true); setError('');
|
setBusy(true); setError('');
|
||||||
@@ -57,7 +66,7 @@ export function ThreadSidebar(props: Props) {
|
|||||||
finally { setBusy(false); }
|
finally { setBusy(false); }
|
||||||
}
|
}
|
||||||
return <section className={styles.panel} aria-label="空间频道导航">
|
return <section className={styles.panel} aria-label="空间频道导航">
|
||||||
<div className={styles.heading}><strong>频道</strong><Button variant="ghost" icon="folder" label="新建频道分组" disabled={!props.enabled || busy} onClick={() => begin({ kind: 'group' })} /></div>
|
<div className={styles.heading}><strong>频道</strong><Button variant="ghost" icon="plus" label="新建频道分组" disabled={!props.enabled || busy} onClick={() => begin({ kind: 'group' })} /></div>
|
||||||
{(ungrouped.length > 0 || props.groups.length === 0) ? <section className={styles.group} aria-label="未分组频道">
|
{(ungrouped.length > 0 || props.groups.length === 0) ? <section className={styles.group} aria-label="未分组频道">
|
||||||
<div className={styles.groupHeading}>
|
<div className={styles.groupHeading}>
|
||||||
<button className={styles.groupToggle} type="button" aria-label="未分组频道" aria-expanded={!collapsed.has(UNGROUPED)} onClick={() => toggle(UNGROUPED)}>
|
<button className={styles.groupToggle} type="button" aria-label="未分组频道" aria-expanded={!collapsed.has(UNGROUPED)} onClick={() => toggle(UNGROUPED)}>
|
||||||
@@ -94,6 +103,15 @@ export function ThreadSidebar(props: Props) {
|
|||||||
<label className={styles.check}><input type="checkbox" checked={chatHistory} onChange={event => setChatHistory(event.target.checked)} />新成员可读历史聊天</label>
|
<label className={styles.check}><input type="checkbox" checked={chatHistory} onChange={event => setChatHistory(event.target.checked)} />新成员可读历史聊天</label>
|
||||||
<label className={styles.check}><input type="checkbox" checked={filesHistory} onChange={event => setFilesHistory(event.target.checked)} />新成员可读历史文件</label>
|
<label className={styles.check}><input type="checkbox" checked={filesHistory} onChange={event => setFilesHistory(event.target.checked)} />新成员可读历史文件</label>
|
||||||
</> : null}
|
</> : null}
|
||||||
|
{edit.kind === 'group' && edit.id ? <div className={styles.dangerZone}>
|
||||||
|
{confirmDelete ? <>
|
||||||
|
<p className={styles.hint}>分组会被删除,其中的频道回到「未分组」,频道和消息都不会丢。</p>
|
||||||
|
<div className={styles.groupActions}>
|
||||||
|
<Button variant="ghost" disabled={busy} onClick={() => setConfirmDelete(false)}>先不删</Button>
|
||||||
|
<Button variant="danger" disabled={busy} onClick={() => { void removeGroup(); }}>确认删除分组</Button>
|
||||||
|
</div>
|
||||||
|
</> : <Button variant="ghost" icon="trash" disabled={busy} onClick={() => setConfirmDelete(true)}>删除分组</Button>}
|
||||||
|
</div> : null}
|
||||||
{error ? <p className={styles.error} role="alert">{error}</p> : null}
|
{error ? <p className={styles.error} role="alert">{error}</p> : null}
|
||||||
<div className={`${styles.groupActions} ${styles.modalActions}`}>
|
<div className={`${styles.groupActions} ${styles.modalActions}`}>
|
||||||
<Button variant="ghost" disabled={busy} onClick={() => setEdit(undefined)}>取消</Button>
|
<Button variant="ghost" disabled={busy} onClick={() => setEdit(undefined)}>取消</Button>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export function Workspace({ onCreateChannel }: { onCreateChannel: () => void })
|
|||||||
const setChannelHistory = useWorkspace(s => s.setChannelHistory);
|
const setChannelHistory = useWorkspace(s => s.setChannelHistory);
|
||||||
const createGroup = useWorkspace((s) => s.createGroup);
|
const createGroup = useWorkspace((s) => s.createGroup);
|
||||||
const renameGroup = useWorkspace((s) => s.renameGroup);
|
const renameGroup = useWorkspace((s) => s.renameGroup);
|
||||||
|
const deleteGroup = useWorkspace((s) => s.deleteGroup);
|
||||||
const groupThread = useWorkspace((s) => s.groupThread);
|
const groupThread = useWorkspace((s) => s.groupThread);
|
||||||
const folders = useWorkspace((s) => s.folders);
|
const folders = useWorkspace((s) => s.folders);
|
||||||
const sendText = useWorkspace((s) => s.sendText);
|
const sendText = useWorkspace((s) => s.sendText);
|
||||||
@@ -127,7 +128,7 @@ export function Workspace({ onCreateChannel }: { onCreateChannel: () => void })
|
|||||||
const rail = <SpaceRail id={channel?.id} onCreate={onCreateChannel} onJoin={() => setScanOpen(true)} />;
|
const rail = <SpaceRail id={channel?.id} onCreate={onCreateChannel} onJoin={() => setScanOpen(true)} />;
|
||||||
const threadSidebar = <ThreadSidebar threads={threads} groups={groups} selectedThreadId={activeThreadId} enabled={Boolean(channel?.canManageStructure)} owner={channel?.role === 'owner'} onHistory={setChannelHistory}
|
const threadSidebar = <ThreadSidebar threads={threads} groups={groups} selectedThreadId={activeThreadId} enabled={Boolean(channel?.canManageStructure)} owner={channel?.role === 'owner'} onHistory={setChannelHistory}
|
||||||
onSelectThread={(id) => { selectThread(id); setDrawerOpen(false); setMobileView('chat'); }} onCreateThread={createThread} onRenameThread={renameThread}
|
onSelectThread={(id) => { selectThread(id); setDrawerOpen(false); setMobileView('chat'); }} onCreateThread={createThread} onRenameThread={renameThread}
|
||||||
onCreateGroup={createGroup} onRenameGroup={renameGroup} onGroupThread={groupThread} />;
|
onCreateGroup={createGroup} onRenameGroup={renameGroup} onDeleteGroup={deleteGroup} onGroupThread={groupThread} />;
|
||||||
const navigation = <><SpaceHeader />{threadSidebar}{desktop ? <Notepad /> : null}</>;
|
const navigation = <><SpaceHeader />{threadSidebar}{desktop ? <Notepad /> : null}</>;
|
||||||
const chat = <ChatPane threadName={activeThread?.name ?? '选择频道'} ids={messages} enabled={enabled} onSend={(text) => sendText(text, activeThreadId)} onDelete={deleteItem} onOpenFile={setPreviewId} onFiles={requestFiles} />;
|
const chat = <ChatPane threadName={activeThread?.name ?? '选择频道'} ids={messages} enabled={enabled} onSend={(text) => sendText(text, activeThreadId)} onDelete={deleteItem} onOpenFile={setPreviewId} onFiles={requestFiles} />;
|
||||||
const explorer = <>
|
const explorer = <>
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ interface Actions {
|
|||||||
createThread: (name: string, groupId?: string) => Promise<string>;
|
createThread: (name: string, groupId?: string) => Promise<string>;
|
||||||
createGroup: typeof api.createGroup;
|
createGroup: typeof api.createGroup;
|
||||||
renameGroup: typeof api.renameGroup;
|
renameGroup: typeof api.renameGroup;
|
||||||
|
deleteGroup: typeof api.deleteGroup;
|
||||||
groupThread: typeof api.groupThread;
|
groupThread: typeof api.groupThread;
|
||||||
renameThread: (threadId: string, name: string) => Promise<void>;
|
renameThread: (threadId: string, name: string) => Promise<void>;
|
||||||
setChannelHistory: typeof api.setChannelHistory;
|
setChannelHistory: typeof api.setChannelHistory;
|
||||||
@@ -112,6 +113,7 @@ export const useWorkspace = create<WorkspaceData & Actions>()(() => ({
|
|||||||
createThread: (name, groupId) => api.createThread(name, groupId),
|
createThread: (name, groupId) => api.createThread(name, groupId),
|
||||||
createGroup: (name) => api.createGroup(name),
|
createGroup: (name) => api.createGroup(name),
|
||||||
renameGroup: (id, name) => api.renameGroup(id, name),
|
renameGroup: (id, name) => api.renameGroup(id, name),
|
||||||
|
deleteGroup: (id) => api.deleteGroup(id),
|
||||||
groupThread: (id, groupId) => api.groupThread(id, groupId),
|
groupThread: (id, groupId) => api.groupThread(id, groupId),
|
||||||
renameThread: (threadId, name) => api.renameThread(threadId, name),
|
renameThread: (threadId, name) => api.renameThread(threadId, name),
|
||||||
setChannelHistory: (threadId, chatHistory, filesHistory) => api.setChannelHistory(threadId, chatHistory, filesHistory),
|
setChannelHistory: (threadId, chatHistory, filesHistory) => api.setChannelHistory(threadId, chatHistory, filesHistory),
|
||||||
|
|||||||
@@ -34,6 +34,12 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
background: var(--danger);
|
||||||
|
color: var(--on-accent);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.quiet {
|
.quiet {
|
||||||
border-color: var(--line-strong);
|
border-color: var(--line-strong);
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Icon, type IconName } from './Icon';
|
|||||||
import styles from './Button.module.css';
|
import styles from './Button.module.css';
|
||||||
|
|
||||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
variant?: 'primary' | 'quiet' | 'ghost';
|
variant?: 'primary' | 'quiet' | 'ghost' | 'danger';
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
/** 仅图标时必填,作为无障碍名称 */
|
/** 仅图标时必填,作为无障碍名称 */
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|||||||
Reference in new issue
Block a user