cdrop — 跨 OS 剪贴板与文件传输服务

Commilitia Drop:自托管的跨设备剪贴板同步与点对点文件传输。

- 后端 Go(chi / SQLite WAL / SSE Hub / WebRTC signaling + 状态机 / Relay ring buffer),编译进单个 distroless 镜像(前端 go:embed)。
- 前端 React + TanStack Router + Zustand,自实现 SSE + WebRTC P2P,NAT 受阻时回退服务端中继;聚珍(Juzhen)CJK 综合排版。
- 桌面端 Wails v2(macOS / Windows),瘦客户端复用 web。
- 鉴权 OIDC PKCE(自建 Casdoor 等),refresh_token 信封加密存系统密钥库;iOS Shortcut 用 HS256 scoped token。

架构文档与变更记录见 docs 分支(PROJECT_BRIEF / FRONTEND_DESIGN / CHANGELOG)。

本次为公开发布初始提交:完整开发历史(含部署细节)留存于私有归档,公开仓库自此干净起步。
This commit is contained in:
2026-06-15 21:38:28 +08:00
commit f21fa5b5e8
239 changed files with 29010 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
import type { ReactNode } from "react";
import { IconButton, Tooltip } from "../../ui/primitives";
import { Wordmark } from "../../ui/brand";
import { Monitor, Moon, Sun } from "lucide-react";
import { useAppStore, type ThemeMode } from "../../store";
import { t } from "../../i18n";
import s from "./AuthShell.module.css";
export interface AuthShellProps
{
title: ReactNode;
subtitle?: ReactNode;
children: ReactNode;
footer?: ReactNode;
/** 卡内品牌字标。默认不渲染——顶栏已有 logo,卡内再放会一屏两 logo;
* 需要时显式传入一个节点即可。 */
cardBrand?: ReactNode | null;
/** 隐藏顶栏 logo。过渡型认证屏(OAuth 回调 / 首次设置)用:它们会 SPA
* 跳转到主界面,跳转帧里顶栏 logo 会与主界面 header 的 logo 重叠成“两个
* logo”,隐藏即可消除。登录页是整页跳 IdP、无此重叠,保留 logo。 */
hideBrand?: boolean;
}
// 全屏外壳:登录、首次设置、OAuth 回调共用。带轻量品牌 + 主题切换,
// 让用户在登录前也能调主题。
export function AuthShell(props: AuthShellProps)
{
const { title, subtitle, children, footer, cardBrand, hideBrand } = props;
const theme = useAppStore((s) => s.theme);
const setTheme = useAppStore((s) => s.setTheme);
const next: ThemeMode = theme === "system" ? "light" : theme === "light" ? "dark" : "system";
const icon = theme === "light" ? <Sun size={16} />
: theme === "dark" ? <Moon size={16} />
: <Monitor size={16} />;
// 默认不在卡内重复 logo(顶栏已有),避免一屏两 logo;需要时显式传入。
const brandNode = cardBrand === undefined ? null : cardBrand;
return (
<div className={s.shell}>
<div className={s.topbar}>
{/* 空 span 占位保持 space-between,主题切换仍靠右。 */}
{hideBrand ? <span /> : <span className={s.brand}><Wordmark /></span>}
<Tooltip label={t("nav.themeToggle")}>
<IconButton aria-label={t("nav.themeToggle")} size="sm" onClick={() => setTheme(next)}>
{icon}
</IconButton>
</Tooltip>
</div>
<div className={s.center}>
<div className={s.card}>
{brandNode && <div className={s.cardBrand}>{brandNode}</div>}
<div className={s.hero}>
<h1 className={s.title}>{title}</h1>
{subtitle && <p className={s.subtitle} data-jz-level="paragraph">{subtitle}</p>}
</div>
<div className={s.body}>
{children}
</div>
</div>
</div>
{footer && <div className={s.footer}>{footer}</div>}
</div>
);
}
// 公共 footer 链接组件:登录页用,setup / oauth 不一定要。
export function AuthFooterLinks(props: { items: { label: string; href: string }[] })
{
const { items } = props;
return (
<div className={s.footerLinks}>
{items.map((it, i) => (
<span key={it.href} style={{ display: "inline-flex", gap: "var(--space-3)", alignItems: "center" }}>
{i > 0 && <span className={s.sep}>·</span>}
<a href={it.href} target="_blank" rel="noreferrer noopener">{it.label}</a>
</span>
))}
</div>
);
}