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
@@ -0,0 +1,186 @@
.row
{
display: flex;
flex-direction: column;
gap: var(--space-2);
padding: var(--space-3) var(--space-4);
background: var(--surface);
border-radius: var(--radius-card);
/* 描边:1px divider 全包,左侧 3px 状态色覆盖。 */
border: 1px solid var(--divider);
border-left: 3px solid var(--divider);
box-shadow: var(--shadow-card);
transition: border-color var(--dur-fast) var(--ease-out);
}
.rowSunken
{
background: var(--surface-sunken);
box-shadow: none;
}
/* 状态色编码:左色条按当前状态着色。
- rowActive 传输中 → info (cyan)
- rowStalled 卡住 → warn (yellow)
- rowFailed 失败 / 取消 → error (red)
- rowDone 完成 → success (green) */
.rowActive { border-left-color: var(--info); }
.rowStalled { border-left-color: var(--warn); }
.rowFailed { border-left-color: var(--error); }
.rowDone { border-left-color: var(--success); }
.head
{
display: flex;
align-items: center;
gap: var(--space-2);
min-width: 0;
}
.arrow
{
color: var(--text-muted);
font-family: var(--font-mono);
flex-shrink: 0;
}
.fileName
{
font-size: var(--fs-13);
font-weight: 500;
color: var(--text);
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.peer
{
font-size: var(--fs-12);
color: var(--text-muted);
flex-shrink: 0;
}
.barRow
{
display: flex;
align-items: center;
gap: var(--space-3);
}
.bar
{
flex: 1;
height: 3px;
background: var(--divider);
border-radius: 999px;
overflow: hidden;
position: relative;
}
.barFill
{
position: absolute;
inset: 0 auto 0 0;
background: var(--accent);
border-radius: 999px;
transition: width 220ms linear;
}
.barFillStalled { background: var(--state-warn); }
.barFillError { background: var(--state-error); }
.barFillDone { background: var(--state-online); }
.pct
{
font-family: var(--font-mono);
font-size: var(--fs-12);
color: var(--text-muted);
min-width: 40px;
text-align: right;
}
.meta
{
display: flex;
align-items: center;
gap: var(--space-2);
font-family: var(--font-mono);
font-size: var(--fs-12);
color: var(--text-muted);
flex-wrap: wrap;
}
.metaItem { white-space: nowrap; }
.dot { color: var(--divider); }
.footer
{
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
}
.actions
{
display: flex;
align-items: center;
gap: 4px;
}
.actionBtn
{
background: transparent;
border: 0;
color: var(--text-muted);
font-size: var(--fs-11);
cursor: pointer;
padding: 2px 6px;
border-radius: var(--radius-control);
transition: color var(--dur-fast) var(--ease-out);
}
.actionBtn:hover { color: var(--accent); }
.actionDanger:hover { color: var(--state-error); }
.detailToggle
{
background: transparent;
border: 0;
color: var(--text-muted);
font-size: var(--fs-11);
cursor: pointer;
padding: 2px 4px;
border-radius: var(--radius-control);
display: inline-flex;
align-items: center;
gap: 4px;
transition: color var(--dur-fast) var(--ease-out);
}
.detailToggle:hover { color: var(--accent); }
.detail
{
background: var(--surface-sunken);
border-radius: var(--radius-control);
padding: var(--space-3);
font-family: var(--font-mono);
font-size: var(--fs-11);
color: var(--text-muted);
display: grid;
grid-template-columns: max-content 1fr;
gap: 4px var(--space-3);
line-height: var(--lh-ui);
}
.detail strong { color: var(--text); font-weight: 500; }
.errMsg
{
color: var(--state-error);
font-size: var(--fs-12);
}
.stalled .meta { color: var(--state-warn); }