feat(webdav): add WebDAV synchronization configuration and functionality
This commit is contained in:
@@ -341,7 +341,7 @@ function NodeContent(props: NodeContentRendererProps) {
|
||||
if (props.node.metadata?.status === "error") return <ErrorContent node={props.node} theme={props.theme} onRetry={props.onRetry} />;
|
||||
|
||||
const Renderer = nodeContentRenderers[props.node.type];
|
||||
return <Renderer {...props} />;
|
||||
return Renderer ? <Renderer {...props} /> : <UnknownNodeContent theme={props.theme} />;
|
||||
}
|
||||
|
||||
const nodeContentRenderers = {
|
||||
@@ -382,6 +382,14 @@ function ErrorContent({ node, theme, onRetry }: Pick<NodeContentRendererProps, "
|
||||
);
|
||||
}
|
||||
|
||||
function UnknownNodeContent({ theme }: Pick<NodeContentRendererProps, "theme">) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center text-sm" style={{ color: theme.node.placeholder }}>
|
||||
未知节点
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TextContent({ node, theme, isEditingContent, textareaRef, mentionReferences, onContentChange, onStopEditing, onGenerateImage }: NodeContentRendererProps) {
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col overflow-hidden pt-8">
|
||||
|
||||
@@ -28,6 +28,7 @@ type CanvasStore = {
|
||||
openProject: (id: string) => CanvasProject | null;
|
||||
renameProject: (id: string, title: string) => void;
|
||||
deleteProjects: (ids: string[]) => void;
|
||||
replaceProjects: (projects: CanvasProject[]) => void;
|
||||
updateProject: (id: string, patch: Partial<Pick<CanvasProject, "nodes" | "connections" | "chatSessions" | "activeChatId" | "backgroundMode" | "showImageInfo" | "viewport">>) => void;
|
||||
};
|
||||
|
||||
@@ -112,6 +113,7 @@ export const useCanvasStore = create<CanvasStore>()(
|
||||
const projects = state.projects.filter((project) => !ids.includes(project.id));
|
||||
return { projects };
|
||||
}),
|
||||
replaceProjects: (projects) => set({ projects }),
|
||||
updateProject: (id, patch) =>
|
||||
set((state) => ({
|
||||
projects: state.projects.map((project) => (project.id === id ? { ...project, ...patch, updatedAt: new Date().toISOString() } : project)),
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const WEBDAV_PROXY_TIMEOUT_MS = 120000;
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const target = request.headers.get("x-webdav-target") || "";
|
||||
const method = (request.headers.get("x-webdav-method") || "GET").toUpperCase();
|
||||
if (!target) return new Response("Missing x-webdav-target", { status: 400 });
|
||||
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(target);
|
||||
} catch {
|
||||
return new Response("Invalid x-webdav-target", { status: 400 });
|
||||
}
|
||||
if (url.protocol !== "http:" && url.protocol !== "https:") return new Response("Unsupported WebDAV target", { status: 400 });
|
||||
|
||||
const headers = new Headers();
|
||||
copyHeader(request, headers, "x-webdav-authorization", "Authorization");
|
||||
copyHeader(request, headers, "x-webdav-depth", "Depth");
|
||||
copyHeader(request, headers, "x-webdav-destination", "Destination");
|
||||
copyHeader(request, headers, "x-webdav-overwrite", "Overwrite");
|
||||
copyHeader(request, headers, "x-webdav-content-type", "Content-Type");
|
||||
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), WEBDAV_PROXY_TIMEOUT_MS);
|
||||
try {
|
||||
const body = method === "GET" || method === "HEAD" ? undefined : await request.arrayBuffer();
|
||||
console.log(`[webdav-proxy] ${method} ${url.href} ${body?.byteLength || 0}B`);
|
||||
const response = await fetch(url, { method, headers, body: body?.byteLength ? body : undefined, signal: controller.signal });
|
||||
console.log(`[webdav-proxy] ${method} ${url.href} -> ${response.status}`);
|
||||
return new Response(method === "HEAD" ? null : response.body, {
|
||||
status: response.status,
|
||||
headers: responseHeaders(response.headers),
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === "AbortError") return new Response("WebDAV proxy timeout", { status: 504 });
|
||||
return new Response(error instanceof Error ? error.message : "WebDAV proxy error", { status: 502 });
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
function copyHeader(request: NextRequest, headers: Headers, from: string, to: string) {
|
||||
const value = request.headers.get(from);
|
||||
if (value) headers.set(to, value);
|
||||
}
|
||||
|
||||
function responseHeaders(headers: Headers) {
|
||||
const result = new Headers();
|
||||
["content-type", "etag", "last-modified", "dav"].forEach((key) => {
|
||||
const value = headers.get(key);
|
||||
if (value) result.set(key, value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user