Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions plugins/y-clipboard/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Changelog

## 0.1.0 - 2026-09-30

- 重写为剪贴板历史管理插件:文本 / 链接 / 代码 / 颜色值 / 图片 / 文件 六类自动分类
- 本地即时过滤 + 宿主端补充搜索
- 收藏:以快照形式独立持久化,清空历史后不丢
- 键盘流:↑↓ 选择 / Enter 复制并粘贴 / Delete 删除 / Esc 退出
- 虚拟滚动,千条记录保持流畅
- 跟随 ZTools 深浅色主题与主题强调色
- 新增插件 logo(闪电 + 剪贴板)

## 1.0.0 - 2026-09-30

- 初次发布,将插件提交至 ZTools 插件仓库。
45 changes: 45 additions & 0 deletions plugins/y-clipboard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Y剪贴板

> ZTools 剪贴板历史管理插件 · Vue 3 + TypeScript + Vite

分类清晰、响应飞快的剪贴板管理:文本 / 链接 / 代码 / 颜色 / 图片 / 文件自动分类,支持收藏、搜索与全键盘操作。

## 功能

- **自动分类**:复制的内容按 文本 / 链接 / 代码 / 颜色值 / 图片 / 文件 自动归类,侧栏一键切换
- **即搜即得**:本地过滤即时出结果,同时防抖调用宿主搜索补充全量历史
- **收藏**:星标记录存入插件独立存储(dbStorage),原历史被清空也不丢
- **键盘流**:`↑` `↓` 选择 · `Enter` 复制并粘贴到原窗口 · `Delete` 删除 · `Esc` 退出
- **主题适配**:跟随 ZTools 浅色 / 深色主题与主题强调色
- **虚拟滚动**:固定行高窗口化渲染,千条记录依然流畅

## 开发

```bash
npm install
npm run dev # 浏览器打开 http://localhost:5173(自动使用 mock 数据)
npm run build # 构建到 src-ztools/dist/
```

- 真实环境:ZTools 主窗口输入 `剪贴板` / `clipboard` / `clip` 触发
- 开发模式:`plugin.json` 的 `development.main` 指向 dev server,改代码即时热更新

## 结构

```
├── plugin.json # 插件配置(功能入口、触发指令)
├── preload/services.js # 预加载脚本(CommonJS,不可打包压缩)
└── src/
├── api.ts # 宿主 API 封装 + 字段归一化 + 开发 mock
├── types.ts # 类型定义
├── utils/format.ts # 分类判定、时间格式化
├── composables/useStore.ts # 状态:分页加载 / 搜索 / 收藏 / 键盘导航
└── components/ # Sidebar / Toolbar / VirtualList / ClipboardItem
```

## 发布

```bash
git add . && git commit -m "feat: clipboard plugin"
ztools publish
```
134 changes: 134 additions & 0 deletions plugins/y-clipboard/design/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>y-clipboard logo design</title>
<style>
body { margin: 0; font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif; background: #fff; }
.grid { display: flex; gap: 48px; padding: 36px 40px; align-items: flex-start; }
.col { display: flex; flex-direction: column; align-items: center; gap: 14px; }
.row { display: flex; gap: 10px; align-items: flex-end; }
.lbl { font-size: 13px; color: #333; font-weight: 500; }
.hint { font-size: 11px; color: #999; margin-top: -8px; }
</style>
</head>
<body>
<div class="grid" id="grid"></div>
<script>
const SVGS = {
a: `<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<defs>
<linearGradient id="ga" gradientUnits="userSpaceOnUse" x1="40" y1="24" x2="472" y2="488">
<stop offset="0" stop-color="#4E8DF7"/>
<stop offset="1" stop-color="#2F5BE8"/>
</linearGradient>
</defs>
<rect width="512" height="512" rx="116" fill="url(#ga)"/>
<rect x="158" y="118" width="196" height="282" rx="30" fill="#FFFFFF"/>
<rect x="222" y="92" width="68" height="52" rx="16" fill="#FFFFFF"/>
<path d="M265 167 L170 281 L256 281 L246 357 L341 243 L256 243 Z" fill="url(#ga)"/>
</svg>`,
b: `<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<defs>
<linearGradient id="gb" gradientUnits="userSpaceOnUse" x1="40" y1="24" x2="472" y2="488">
<stop offset="0" stop-color="#38BDF8"/>
<stop offset="1" stop-color="#2563EB"/>
</linearGradient>
</defs>
<rect width="512" height="512" rx="116" fill="url(#gb)"/>
<g transform="rotate(-10 268 256)">
<rect x="180" y="112" width="180" height="258" rx="26" fill="#FFFFFF"/>
<rect x="238" y="88" width="64" height="48" rx="14" fill="#FFFFFF"/>
<rect x="206" y="176" width="128" height="13" rx="6.5" fill="url(#gb)"/>
<rect x="206" y="210" width="128" height="13" rx="6.5" fill="url(#gb)"/>
<rect x="206" y="244" width="86" height="13" rx="6.5" fill="url(#gb)"/>
</g>
<rect x="44" y="176" width="72" height="15" rx="7.5" fill="#FFFFFF" opacity="0.95"/>
<rect x="28" y="230" width="104" height="15" rx="7.5" fill="#FFFFFF" opacity="0.8"/>
<rect x="52" y="284" width="64" height="15" rx="7.5" fill="#FFFFFF" opacity="0.65"/>
</svg>`,
c: `<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<defs>
<linearGradient id="gc" gradientUnits="userSpaceOnUse" x1="40" y1="24" x2="472" y2="488">
<stop offset="0" stop-color="#2A3B5F"/>
<stop offset="1" stop-color="#141E38"/>
</linearGradient>
</defs>
<rect width="512" height="512" rx="116" fill="url(#gc)"/>
<rect x="160" y="140" width="192" height="268" rx="28" fill="#FFFFFF"/>
<rect x="194" y="204" width="124" height="13" rx="6.5" fill="url(#gc)"/>
<rect x="194" y="238" width="124" height="13" rx="6.5" fill="url(#gc)"/>
<rect x="194" y="272" width="82" height="13" rx="6.5" fill="url(#gc)"/>
<path d="M272 112 L232 158 L252 158 L236 194 L284 146 L262 146 Z" fill="#FFB020"/>
</svg>`
}

const key = new URLSearchParams(location.search).get('src')

if (key && SVGS[key]) {
// 单个导出模式:SVG → 512px 透明背景 PNG 下载
const url = URL.createObjectURL(new Blob([SVGS[key]], { type: 'image/svg+xml' }))
const img = new Image()
img.onload = () => {
const c = document.createElement('canvas')
c.width = 512
c.height = 512
const ctx = c.getContext('2d')
ctx.drawImage(img, 0, 0, 512, 512)
const corner = ctx.getImageData(4, 4, 1, 1).data
const middle = ctx.getImageData(256, 256, 1, 1).data
const clip = ctx.getImageData(256, 118, 1, 1).data
window.__probe = {
cornerAlpha: corner[3],
centerRGBA: [middle[0], middle[1], middle[2], middle[3]],
clipRGBA: [clip[0], clip[1], clip[2], clip[3]]
}
c.toBlob((b) => {
const a = document.createElement('a')
a.href = URL.createObjectURL(b)
a.download = 'logo-' + key + '.png'
document.body.appendChild(a)
a.click()
}, 'image/png')
}
img.src = url
} else {
// 预览模式:三方案大图 + 小尺寸模拟
const labels = {
a: 'A · 闪电速贴(推荐)',
b: 'B · 疾影',
c: 'C · 闪电夹'
}
const grid = document.getElementById('grid')
for (const k of Object.keys(SVGS)) {
const col = document.createElement('div')
col.className = 'col'
const big = document.createElement('div')
big.innerHTML = SVGS[k]
big.firstElementChild.setAttribute('width', 180)
big.firstElementChild.setAttribute('height', 180)
const row = document.createElement('div')
row.className = 'row'
for (const s of [44, 28]) {
const w = document.createElement('div')
w.innerHTML = SVGS[k]
w.firstElementChild.setAttribute('width', s)
w.firstElementChild.setAttribute('height', s)
row.appendChild(w)
}
const lbl = document.createElement('div')
lbl.className = 'lbl'
lbl.textContent = labels[k]
const hint = document.createElement('div')
hint.className = 'hint'
hint.textContent = '180px / 44px / 28px'
col.appendChild(big)
col.appendChild(row)
col.appendChild(lbl)
col.appendChild(hint)
grid.appendChild(col)
}
}
</script>
</body>
</html>
Binary file added plugins/y-clipboard/design/logo-a.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added plugins/y-clipboard/design/logo-b.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added plugins/y-clipboard/design/logo-c.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added plugins/y-clipboard/design/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading