feat(web): add account workspaces and signed content flows

This commit is contained in:
justme committed 2026-10-14 04:06:03 +08:00
1 parent 7b8d61d1ba
commit b5cf3d5958
117 files changed
+2789 -1674

No files matched your search

+16 -46
View File
@@ -18,9 +18,8 @@ export interface WorkspaceData {
devices: Device[];
threads: ThreadInfo[];
groups: ChannelGroupInfo[];
threadGroups: Record<string, string | null>;
folders: FolderInfo[];
locations: Record<string, { threadId: string; folderId?: string }>;
locations: Record<string, { threadId: string; folderId?: string; authorAccount?: string }>;
byId: Record<string, Item>;
authors: Record<string, SenderInfo>;
/** 新的在前 */
@@ -36,7 +35,6 @@ export const initialData: WorkspaceData = {
devices: [],
threads: [],
groups: [],
threadGroups: {},
folders: [],
locations: {},
byId: {},
@@ -55,6 +53,20 @@ function insertByTime(order: string[], byId: Record<string, Item>, item: Item):
export function reduce(s: WorkspaceData, e: WorkspaceEvent): WorkspaceData {
switch (e.t) {
case 'structure': {
const people = new Map(e.people.map(person => [person.username, person]));
const locations = Object.fromEntries(e.state.locations.map(location => [location.itemId, { threadId: location.threadId, folderId: location.folderId ?? undefined, authorAccount: location.authorAccount ?? undefined }]));
const authors = { ...s.authors };
for (const location of e.state.locations) {
const person = location.authorAccount ? people.get(location.authorAccount) : undefined;
if (person) authors[location.itemId] = { username: person.username, displayName: person.displayName || person.username, avatar: person.avatar ?? undefined };
}
const byId = Object.fromEntries(Object.entries(s.byId).map(([id, item]) => [id, {
...item, ...(locations[id] ?? {}), ...(authors[id] ? { author: authors[id] } : {}),
}]));
return { ...s, threads: e.state.channels.map(c => ({ ...c, groupId: c.groupId ?? undefined })), groups: e.state.groups,
folders: e.state.folders.map(f => ({ ...f, parentId: f.parentId ?? undefined })), locations, authors, byId };
}
case 'reset':
return { ...initialData, connection: s.connection };
case 'error':
@@ -67,56 +79,14 @@ export function reduce(s: WorkspaceData, e: WorkspaceEvent): WorkspaceData {
return { ...s, channel: e.info };
case 'devices':
return { ...s, devices: e.devices };
case 'thread-created': {
const thread = { ...e.thread };
if (thread.id in s.threadGroups) {
const groupId = s.threadGroups[thread.id];
if (groupId) thread.groupId = groupId;
else delete thread.groupId;
}
return { ...s, threads: [thread, ...s.threads.filter((entry) => entry.id !== thread.id)] };
}
case 'thread-renamed':
return { ...s, threads: s.threads.map((thread) => thread.id === e.id ? { ...thread, name: e.name } : thread) };
case 'group-created':
return { ...s, groups: [...s.groups.filter((group) => group.id !== e.group.id), e.group] };
case 'group-renamed':
return { ...s, groups: s.groups.map((group) => group.id === e.id ? { ...group, name: e.name } : group) };
case 'thread-grouped':
return { ...s, threadGroups: { ...s.threadGroups, [e.id]: e.groupId ?? null },
threads: s.threads.map((thread) => thread.id === e.id ? { ...thread, groupId: e.groupId } : thread) };
case 'folder-created':
return { ...s, folders: [e.folder, ...s.folders.filter((folder) => folder.id !== e.folder.id)] };
case 'folder-renamed':
return { ...s, folders: s.folders.map((folder) => folder.id === e.id ? { ...folder, name: e.name } : folder) };
case 'items-moved': {
const byId = { ...s.byId };
const locations = { ...s.locations };
for (const id of e.itemIds) {
locations[id] = { threadId: e.threadId, ...(e.folderId ? { folderId: e.folderId } : {}) };
const item = byId[id];
if (!item) continue;
if (item.kind === 'file' && item.metadataHidden) continue;
const moved = { ...item, threadId: e.threadId };
if (e.folderId === undefined) delete moved.folderId;
else moved.folderId = e.folderId;
byId[id] = moved;
}
return { ...s, byId, locations };
}
case 'snapshot':
return { ...s, snapshot: e.snapshot };
case 'item-author': {
const item = s.byId[e.id];
return { ...s, authors: { ...s.authors, [e.id]: e.author },
byId: item ? { ...s.byId, [e.id]: { ...item, author: e.author } } : s.byId };
}
case 'file-catalog':
case 'item': {
const known = e.item.id in s.byId;
let item = e.item;
const current = s.byId[item.id];
const author = item.author ?? s.authors[item.id] ?? current?.author;
const author = s.authors[item.id] ?? item.author ?? current?.author;
if (author) item = { ...item, author };
if (e.t === 'item' && item.kind === 'file' && item.metadataHidden && current?.kind === 'file' && !current.metadataHidden) return s;
const location = s.locations[item.id];