feat: integrate client and server sharing workflows

This commit is contained in:
justme committed 2026-10-02 10:49:45 +08:00
1 parent 42b93ae2d8
commit 7406e2fe37
101 files changed
+3153 -253

No files matched your search

+71
View File
@@ -0,0 +1,71 @@
import { removeChannelItem } from './channelItems';
import { saveResource } from '../database';
import { previewKindOf } from '@/lib/fileKind';
import type { FileItem, Item } from '../types';
import { blobs, getSession } from './sessions';
import { deviceIdentity } from './presentation';
import { flushOutbox, persistPlan } from './transfer';
import { publishWorkspace } from './presentation';
export async function sendText(resourceId: string, text: string): Promise<{ itemId: string; uploaded: boolean }> {
const { session, resource } = await getSession(resourceId);
if (resource.kind !== 'channel' || (!session.can('owner') && (!session.can('write') || !resource.membersCanUpload))) throw new Error('此链接没有上传权限');
const device = deviceIdentity();
session.deviceName = device.name;
const ts = Date.now();
const plan = session.encryptText(text, { dev: device.name, ts });
const item: Item = { id: plan.itemId, kind: 'text', ts, deviceId: device.name, state: { kind: 'receiving', progress: 0 }, text };
blobs.set(`${resourceId}:${item.id}`, new Blob([text], { type: 'text/plain' }));
publishWorkspace(resourceId, { t: 'item', item });
await persistPlan(resource, plan);
const uploaded = await flushOutbox(resource, session);
publishWorkspace(resourceId, { t: 'item', item: { ...item, state: uploaded ? { kind: 'complete' } : { kind: 'incomplete' } } });
if (!uploaded) publishWorkspace(resourceId, { t: 'connection', state: 'offline' });
return { itemId: plan.itemId, uploaded };
}
export async function sendFiles(resourceId: string, files: File[]): Promise<void> {
const { session, resource } = await getSession(resourceId);
if (resource.kind !== 'channel' || (!session.can('owner') && (!session.can('write') || !resource.membersCanUpload))) throw new Error('此链接没有上传权限');
const device = deviceIdentity();
session.deviceName = device.name;
for (const file of files) {
const ts = Date.now();
const plan = session.encryptFile(file, {}, { maxDownloads: resource.maxDownloads ?? 10, dev: device.name, ts });
let item: FileItem = {
id: plan.itemId, kind: 'file', ts, deviceId: device.name, state: { kind: 'receiving', progress: 0 },
name: file.name, mime: file.type || 'application/octet-stream', size: file.size,
remaining: resource.maxDownloads || null, preview: previewKindOf(file.type, file.name), gated: true,
};
resource.localItems = [...(resource.localItems ?? []).filter((entry) => entry.id !== item.id), item];
await saveResource(resource);
blobs.set(`${resourceId}:${item.id}`, file);
publishWorkspace(resourceId, { t: 'item', item });
await persistPlan(resource, plan);
const uploaded = await flushOutbox(resource, session);
item = { ...item, state: uploaded ? { kind: 'complete' } : { kind: 'incomplete' } };
resource.localItems = resource.localItems.map((entry) => entry.id === item.id ? item : entry);
await saveResource(resource);
publishWorkspace(resourceId, { t: 'item', item });
}
}
export async function updateSnapshot(resourceId: string, text: string): Promise<void> {
const { session, resource } = await getSession(resourceId);
const plan = session.encryptJson({ text, updatedBy: deviceIdentity().name, updatedAt: Date.now() }, { snapshot: true, dev: deviceIdentity().name });
await persistPlan(resource, plan);
await flushOutbox(resource, session);
}
export async function editChannelText(resourceId: string, itemId: string, text: string): Promise<void> {
const created = await sendText(resourceId, text);
if (!created.uploaded) throw new Error('新版本已排队,网络恢复前保留旧内容。');
await removeChannelItem(resourceId, itemId);
}