diff --git a/plugins/y-clipboard/CHANGELOG.md b/plugins/y-clipboard/CHANGELOG.md new file mode 100644 index 000000000..02c68a52e --- /dev/null +++ b/plugins/y-clipboard/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +## 0.1.0 - 2026-09-30 + +- 重写为剪贴板历史管理插件:文本 / 链接 / 代码 / 颜色值 / 图片 / 文件 六类自动分类 +- 本地即时过滤 + 宿主端补充搜索 +- 收藏:以快照形式独立持久化,清空历史后不丢 +- 键盘流:↑↓ 选择 / Enter 复制并粘贴 / Delete 删除 / Esc 退出 +- 虚拟滚动,千条记录保持流畅 +- 跟随 ZTools 深浅色主题与主题强调色 +- 新增插件 logo(闪电 + 剪贴板) + +## 1.0.0 - 2026-09-30 + +- 初次发布,将插件提交至 ZTools 插件仓库。 diff --git a/plugins/y-clipboard/README.md b/plugins/y-clipboard/README.md new file mode 100644 index 000000000..48de706f8 --- /dev/null +++ b/plugins/y-clipboard/README.md @@ -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 +``` diff --git a/plugins/y-clipboard/design/index.html b/plugins/y-clipboard/design/index.html new file mode 100644 index 000000000..b7d1ad831 --- /dev/null +++ b/plugins/y-clipboard/design/index.html @@ -0,0 +1,134 @@ + + + + +y-clipboard logo design + + + +
+ + + diff --git a/plugins/y-clipboard/design/logo-a.png b/plugins/y-clipboard/design/logo-a.png new file mode 100644 index 000000000..6d1985d64 Binary files /dev/null and b/plugins/y-clipboard/design/logo-a.png differ diff --git a/plugins/y-clipboard/design/logo-b.png b/plugins/y-clipboard/design/logo-b.png new file mode 100644 index 000000000..67d613a07 Binary files /dev/null and b/plugins/y-clipboard/design/logo-b.png differ diff --git a/plugins/y-clipboard/design/logo-c.png b/plugins/y-clipboard/design/logo-c.png new file mode 100644 index 000000000..0c7f6c5e4 Binary files /dev/null and b/plugins/y-clipboard/design/logo-c.png differ diff --git a/plugins/y-clipboard/design/preview.png b/plugins/y-clipboard/design/preview.png new file mode 100644 index 000000000..c752b525f Binary files /dev/null and b/plugins/y-clipboard/design/preview.png differ diff --git a/plugins/y-clipboard/design/render.cjs b/plugins/y-clipboard/design/render.cjs new file mode 100644 index 000000000..101572dd9 --- /dev/null +++ b/plugins/y-clipboard/design/render.cjs @@ -0,0 +1,292 @@ +/** + * y-clipboard logo 渲染器(无第三方依赖) + * + * 逐像素求值 + 3x3 超采样:先把 sample 点逆变换回形状的用户坐标系, + * 再依次做 roundRect / polygon 命中测试,最后 source-over 合成。 + * 输出 512x512 RGBA PNG(圆角区域真透明)。 + */ +const fs = require('fs') +const zlib = require('zlib') + +const W = 512 +const H = 512 +const SS = 3 + +/* ---------------- 基础数学 ---------------- */ + +const lerp = (a, b, t) => a + (b - a) * t + +function grad(p, stops) { + // userSpaceOnUse: (40,24) -> (472,488),与 SVG 定义一致 + const x0 = 40, y0 = 24, x1 = 472, y1 = 488 + const dx = x1 - x0, dy = y1 - y0 + let t = ((p[0] - x0) * dx + (p[1] - y0) * dy) / (dx * dx + dy * dy) + t = Math.max(0, Math.min(1, t)) + return [ + lerp(stops[0][0], stops[1][0], t), + lerp(stops[0][1], stops[1][1], t), + lerp(stops[0][2], stops[1][2], t) + ] +} + +function inRect(p, x, y, w, h, r) { + r = r || 0 + const dx = Math.abs(p[0] - (x + w / 2)) - (w / 2 - r) + const dy = Math.abs(p[1] - (y + h / 2)) - (h / 2 - r) + const mx = Math.max(dx, 0), my = Math.max(dy, 0) + return mx * mx + my * my <= r * r +} + +function inPoly(p, pts) { + let inside = false + for (let i = 0, j = pts.length - 1; i < pts.length; j = i++) { + const xi = pts[i][0], yi = pts[i][1], xj = pts[j][0], yj = pts[j][1] + if ((yi > p[1]) !== (yj > p[1]) && p[0] < ((xj - xi) * (p[1] - yi)) / (yj - yi) + xi) { + inside = !inside + } + } + return inside +} + +function rot(p, deg, c) { + const a = (deg * Math.PI) / 180, s = Math.sin(a), co = Math.cos(a) + const dx = p[0] - c[0], dy = p[1] - c[1] + return [c[0] + dx * co - dy * s, c[1] + dx * s + dy * co] +} + +function over(dst, r, g, b, a) { + if (a <= 0) return dst + const da = dst[3] + const oa = a + da * (1 - a) + if (oa <= 0) { dst[0] = dst[1] = dst[2] = dst[3] = 0; return dst } + for (let i = 0; i < 3; i++) { + const s = [r, g, b][i] + dst[i] = (s * a + dst[i] * da * (1 - a)) / oa + } + dst[3] = oa + return dst +} + +const white = () => [255, 255, 255, 1] + +/* ---------------- 三个方案 ---------------- */ + +const STOPS = { + a: [[78, 141, 247], [47, 91, 232]], + b: [[56, 189, 248], [37, 99, 235]], + c: [[42, 59, 95], [20, 30, 56]] +} + +const BOLT_A = [[265, 167], [170, 281], [256, 281], [246, 357], [341, 243], [256, 243]] +const BOLT_C = [[272, 112], [232, 158], [252, 158], [236, 194], [284, 146], [262, 146]] + +function sceneA(p) { + let out = [0, 0, 0, 0] + if (inRect(p, 0, 0, 512, 512, 116)) { + const c = grad(p, STOPS.a); over(out, c[0], c[1], c[2], 1) + } + if (inRect(p, 158, 118, 196, 282, 30)) { const w = white(); over(out, w[0], w[1], w[2], 1) } + if (inRect(p, 222, 92, 68, 52, 16)) { const w = white(); over(out, w[0], w[1], w[2], 1) } + if (inPoly(p, BOLT_A)) { const c = grad(p, STOPS.a); over(out, c[0], c[1], c[2], 1) } + return out +} + +function sceneB(p) { + let out = [0, 0, 0, 0] + if (inRect(p, 0, 0, 512, 512, 116)) { + const c = grad(p, STOPS.b); over(out, c[0], c[1], c[2], 1) + } + const m = rot(p, 10, [268, 256]) // 逆变换:SVG 里是 rotate(-10 268 256) + if (inRect(m, 180, 112, 180, 258, 26)) { const w = white(); over(out, w[0], w[1], w[2], 1) } + for (const l of [[206, 176, 128], [206, 210, 128], [206, 244, 86]]) { + if (inRect(m, l[0], l[1], l[2], 13, 6.5)) { + const c = grad(m, STOPS.b); over(out, c[0], c[1], c[2], 1) + } + } + if (inRect(m, 238, 88, 64, 48, 14)) { const w = white(); over(out, w[0], w[1], w[2], 1) } + for (const s of [[44, 176, 72, 0.95], [28, 230, 104, 0.8], [52, 284, 64, 0.65]]) { + if (inRect(p, s[0], s[1], s[2], 15, 7.5)) over(out, 255, 255, 255, s[3]) + } + return out +} + +function sceneC(p) { + let out = [0, 0, 0, 0] + if (inRect(p, 0, 0, 512, 512, 116)) { + const c = grad(p, STOPS.c); over(out, c[0], c[1], c[2], 1) + } + if (inRect(p, 160, 140, 192, 268, 28)) { const w = white(); over(out, w[0], w[1], w[2], 1) } + for (const l of [[194, 204, 124], [194, 238, 124], [194, 272, 82]]) { + if (inRect(p, l[0], l[1], l[2], 13, 6.5)) { + const c = grad(p, STOPS.c); over(out, c[0], c[1], c[2], 1) + } + } + if (inPoly(p, BOLT_C)) over(out, 255, 176, 32, 1) + return out +} + +const SCENES = { a: sceneA, b: sceneB, c: sceneC } + +/* ---------------- 渲染 ---------------- */ + +function render(scene) { + const buf = Buffer.alloc(W * H * 4) + for (let y = 0; y < H; y++) { + for (let x = 0; x < W; x++) { + let r = 0, g = 0, b = 0, a = 0 + for (let sy = 0; sy < SS; sy++) { + for (let sx = 0; sx < SS; sx++) { + const c = scene([x + (sx + 0.5) / SS, y + (sy + 0.5) / SS]) + r += c[0]; g += c[1]; b += c[2]; a += c[3] + } + } + const n = SS * SS + const i = (y * W + x) * 4 + buf[i] = Math.round(r / n) + buf[i + 1] = Math.round(g / n) + buf[i + 2] = Math.round(b / n) + buf[i + 3] = Math.round((a / n) * 255) + } + } + return buf +} + +/* ---------------- PNG 编码 ---------------- */ + +const CRC_TABLE = (() => { + const t = new Int32Array(256) + for (let n = 0; n < 256; n++) { + let c = n + for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1 + t[n] = c + } + return t +})() + +function crc32(buf) { + let c = 0xffffffff + for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8) + return (c ^ 0xffffffff) >>> 0 +} + +function chunk(type, data) { + const len = Buffer.alloc(4) + len.writeUInt32BE(data.length) + const body = Buffer.concat([Buffer.from(type, 'ascii'), data]) + const crc = Buffer.alloc(4) + crc.writeUInt32BE(crc32(body)) + return Buffer.concat([len, body, crc]) +} + +function writePng(path, buf) { + const ihdr = Buffer.alloc(13) + ihdr.writeUInt32BE(W, 0) + ihdr.writeUInt32BE(H, 4) + ihdr[8] = 8 // bit depth + ihdr[9] = 6 // RGBA + ihdr[10] = 0 + ihdr[11] = 0 + ihdr[12] = 0 + const raw = Buffer.alloc((W * 4 + 1) * H) + for (let y = 0; y < H; y++) { + raw[y * (W * 4 + 1)] = 0 // filter: none + buf.copy(raw, y * (W * 4 + 1) + 1, y * W * 4, (y + 1) * W * 4) + } + const png = Buffer.concat([ + Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), + chunk('IHDR', ihdr), + chunk('IDAT', zlib.deflateSync(raw, { level: 9 })), + chunk('IEND', Buffer.alloc(0)) + ]) + fs.writeFileSync(path, png) + return png.length +} + +/* ---------------- 自检:ASCII 预览 + 像素探针 ---------------- */ + +function ascii(buf) { + const step = 16 + let s = '' + for (let y = 8; y < H; y += step) { + for (let x = 8; x < W; x += step) { + const i = (y * W + x) * 4 + const a = buf[i + 3] + if (a < 96) { s += ' '; continue } + const lum = 0.299 * buf[i] + 0.587 * buf[i + 1] + 0.114 * buf[i + 2] + const warm = buf[i] - buf[i + 2] + if (warm > 60) s += 'o' // 琥珀 + else if (lum > 210) s += '#' // 白 + else s += '.' // 蓝/深底 + } + s += '\n' + } + return s +} + +function probe(buf, label, points) { + const out = [] + for (const [name, x, y] of points) { + const i = (y * W + x) * 4 + out.push(`${name}(${x},${y})=rgba(${buf[i]},${buf[i + 1]},${buf[i + 2]},${buf[i + 3]})`) + } + console.log(` ${label}: ${out.join(' ')}`) +} + +const POINTS = [ + ['corner', 4, 4], + ['bgTopL', 60, 40], + ['board', 200, 160], + ['boltMid', 240, 280], + ['clip', 256, 110] +] + +/* ---------------- PNG 结构校验 ---------------- */ + +function verifyPng(path) { + const b = fs.readFileSync(path) + const sigOk = Buffer.compare(b.slice(0, 8), Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) === 0 + let off = 8 + let idat = null + const types = [] + while (off < b.length) { + const len = b.readUInt32BE(off) + const type = b.slice(off + 4, off + 8).toString('ascii') + const body = b.slice(off + 4, off + 8 + len) + const stored = b.readUInt32BE(off + 8 + len) + if (crc32(body) !== stored) return { ok: false, why: `CRC 错误 @ ${type}` } + types.push(type) + if (type === 'IHDR') { + if (b.readUInt32BE(off + 8) !== W || b.readUInt32BE(off + 12) !== H) { + return { ok: false, why: 'IHDR 尺寸不符' } + } + } + if (type === 'IDAT') idat = b.slice(off + 8, off + 8 + len) + off += 12 + len + } + const raw = zlib.inflateSync(idat) + const stride = W * 4 + 1 // 每行 1 字节过滤器 + const px = (x, y) => y * stride + 1 + x * 4 + return { + ok: true, + sigOk, + chunks: types.join(','), + rawSize: raw.length === stride * H, + cornerAlpha: raw[px(4, 4) + 3], + centerAlpha: raw[px(256, 256) + 3], + centerRGB: [raw[px(256, 256)], raw[px(256, 256) + 1], raw[px(256, 256) + 2]] + } +} + +const dir = __dirname +for (const key of Object.keys(SCENES)) { + const buf = render(SCENES[key]) + const size = writePng(require('path').join(dir, `logo-${key}.png`), buf) + console.log(`\n=== Logo ${key.toUpperCase()} === ${size} bytes`) + console.log(ascii(buf)) + probe(buf, 'probe', POINTS) + const v = verifyPng(require('path').join(dir, `logo-${key}.png`)) + console.log( + ` png: ok=${v.ok} sig=${v.sigOk} chunks=${v.chunks} rawOK=${v.rawSize} ` + + `cornerA=${v.cornerAlpha} centerA=${v.centerAlpha}${v.why ? ' ' + v.why : ''}` + ) +} diff --git a/plugins/y-clipboard/index.html b/plugins/y-clipboard/index.html new file mode 100644 index 000000000..eaa17316c --- /dev/null +++ b/plugins/y-clipboard/index.html @@ -0,0 +1,11 @@ + + + + + + + +
+ + + diff --git a/plugins/y-clipboard/package-lock.json b/plugins/y-clipboard/package-lock.json new file mode 100644 index 000000000..ba8065751 --- /dev/null +++ b/plugins/y-clipboard/package-lock.json @@ -0,0 +1,1524 @@ +{ + "name": "y-clipboard", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "y-clipboard", + "version": "1.0.0", + "dependencies": { + "vue": "^3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "@ztools-center/ztools-api-types": "^1.0.1", + "typescript": "^5.3.0", + "vite": "^6.0.11", + "vue-tsc": "^2.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.5.tgz", + "integrity": "sha512-J25QJU+B78T4FhhBsNpLJyVWOi31mwtpcMwywHmOKH65Q9IWGA81gPj+dnwlhU8wktVriYE+tFAaQgrnJRzAZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.5.tgz", + "integrity": "sha512-LDopB3zuZM5Ux9TT2luNEBJW/tYbGU2g1d+VpKk6I+gSKDb+/7sYE6M225gRQt4RbMX6MSwMsVR/phdjVUgRLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.5.tgz", + "integrity": "sha512-wlJEERGfeuHeBavCL2qVnNacOK43NDoZM4sjkeRPymd04OAE9T1zBqDJgmZ+CIsPTYKwdzpUC8vmOw84dwY4Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.5.tgz", + "integrity": "sha512-4nJJGg5jbo2wwPP4JP+LfEBA3bvP8rU9CLuhp7jWvq9sxEyhjQFTFdrqi+/dHEin/pd8jpT0vcehIpnZtmEdcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.5.tgz", + "integrity": "sha512-DrZbyCDF1hneuO6jRbvZ2D7+PIBM6yIwYnJpg2vIk58T+wuFpiaGZrfUr59lDWw45bg+IrpTGLPiNi/Fk4w3Cg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.5.tgz", + "integrity": "sha512-gqfUVMJMB3mehqywxp6hTBFfgtMQykZY19+cfiaYP0toIJLb/1DZRJHVkQQGP13W4TAwfZDWeg1qBcheTRioXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.5.tgz", + "integrity": "sha512-CFmhpvAwzSaWMlN3VN7UtmoTihlZNzoP0juQib5TQRnYUyDV8dXeWOp29sobWAT6gXl/hQgAClLlEiYozQG3OQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.5.tgz", + "integrity": "sha512-Uc9H8eXCOayV6JLTH5bXKMId6qbhNHa818/BgYjm4jrlq3vZquC9cqyvHBw17xy5Mnj5f+I3gFK5JcEf3hSqrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.5.tgz", + "integrity": "sha512-VcPr/szv/1BFw112Kt//fxulXt/JPqzzidU84iW68L2DdjnOO8QFUv2zTSYBEPHD6movBD4z+bbr5y60GYM7Jw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.5.tgz", + "integrity": "sha512-BnxtJ5/91BrIHYIkGrmjz/lbMhqEHt1dPFqIxIFR+jPn0xVc/oUSCtIT089zfp5ufwGDlYz2UC+Fe1SRBpYFbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.5.tgz", + "integrity": "sha512-LrYcHZwF+fAMNKHYTOQ5osWM4AZF7YF6D+XtsjDyEvljtt11twc+zHVXBLNEjxVSUnKYsOhvVz4Z213eW02COQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.5.tgz", + "integrity": "sha512-nj7QKQePAAUpCpJHtg0pR0W/b92A9NO17JS3BAQmHDn/yhmkir2p8llrKY9TOhleKIaSzy1JhxS3T9FVld6coA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.5.tgz", + "integrity": "sha512-5ylkX6dWMeBKge9nTU+Rxfb+ZfaCIJ9lRqIFaK0eAMcWp7OJbYnLveLgXmm0VrvuLKb8qIK+mHyH0qu88RM+iA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.5.tgz", + "integrity": "sha512-oHK4ZHYFDKjZviK34I+NwgfbGxgI7ztrNxj2hPTSSNFgeq1a/lEd7dHV2fdGAuTH4Iym3RHJg+vAbWaWG4B7Zg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.5.tgz", + "integrity": "sha512-UcetmHZ6XOXuUByiKZyQmb55ZPr0LABr3Ec/HB9wKZn6CEAFWZkE+hsJErJ9hbPBC7nI0dKuELx7CoV6IM7TMg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.5.tgz", + "integrity": "sha512-C5CmDPQBtvjVo8cgQsBs+w6WB0JLkiixhgi6hVLV11hERWdn/p0XcPU2OUcZzac9BPOFq7SbaHFa8r3SWEysCQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.5.tgz", + "integrity": "sha512-lHVQHJFKsuuxLMi3MQO9XVL8Tje3JR82CzB+QDKC5NWBcsIWuwsn9uIM5e3lBhI+fF1/s63qnyYqsg65+8rV/w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.5.tgz", + "integrity": "sha512-3W9bTFcQNJn71cSJVM9RKIiZOy8DO/XLDii8Uv/Pm6WKqDRj7JV3ZfuXIEfyuy5LXpIzAbB/1M4Ukp9GKNa7nA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.5.tgz", + "integrity": "sha512-VDC7rRJlee/scpki96GZ27Omf6yU87s1YXwVTpjE5841faVlDYYT565rgfmoR1U0sqL7z5ivQSDjcsF6VRXyBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.5.tgz", + "integrity": "sha512-z86Ok2p4pTdv5xqCKZsTooO7yBEiaJR/HzU3Wx8RmWsPoLppnMKROhJusQob8B3IE1ghC343kUW9rC2r+Wf3ig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.5.tgz", + "integrity": "sha512-IzQmj+xXwQFGhMAMKMQVXkMwMZN3TqkJgAE0nSsqvVwWWciP4AIPMmWRqOQ2GfX7TUDZr+xqGFcBS36CRPGw0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.5.tgz", + "integrity": "sha512-F6qpTaPc9bwBH85kjy0/BLmLSW1uv7AoOXCoRIkg2arlgCYlWYcAbiMkvZuAcaWk9TpCRG//okznLAqLGshkMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.5.tgz", + "integrity": "sha512-igoDsTFhhwECBeGbUuLeIk7t8Y1apa+cs6mDWpx2EZ0ch7oEQgzHbFUXN9euoHekCAQzXdXApAGkV6jznS7tWw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.5.tgz", + "integrity": "sha512-U3teMeMbXFmaM5D+OTJpsOXd+wV/qftIeYF9kBKL4v73641qyJmoXFtA28DQLsnmlyayEsTe72xpLHrArq6vHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.5.tgz", + "integrity": "sha512-ypfC34F3RKXvCXBglGqGMsUSMKlgwd1HX9AOAlx9RoZZ6GaI42YHVeKpzg3JG+wpBUJYTG+NNZhqbDWL8tBZkw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.43.tgz", + "integrity": "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.43", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.43.tgz", + "integrity": "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.43.tgz", + "integrity": "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.43", + "@vue/compiler-dom": "3.5.43", + "@vue/compiler-ssr": "3.5.43", + "@vue/shared": "3.5.43", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.28", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.43.tgz", + "integrity": "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.43.tgz", + "integrity": "sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.43.tgz", + "integrity": "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.43.tgz", + "integrity": "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.43", + "@vue/runtime-core": "3.5.43", + "@vue/shared": "3.5.43", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.43.tgz", + "integrity": "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.43", + "@vue/runtime-dom": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.43.tgz", + "integrity": "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q==", + "license": "MIT" + }, + "node_modules/@ztools-center/ztools-api-types": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@ztools-center/ztools-api-types/-/ztools-api-types-1.0.3.tgz", + "integrity": "sha512-dv1eOAIasAupqKaQL/gESk1i2+RebdM/1gvZhrvH2D/bo3enCUsAGJ8nrHnlCLBSOGB81eC/SU0IH8BNsUlmvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.63.5", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.63.5.tgz", + "integrity": "sha512-KRWwmNLlPw5M7HcdYfm15oBv9n9LPtjzpzCIxS/phwqvPyxHSoKX6Y2YU3pxSPfy0CLquVgsx/j/hBi6OvH1Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.5", + "@rollup/rollup-android-arm64": "4.63.5", + "@rollup/rollup-darwin-arm64": "4.63.5", + "@rollup/rollup-darwin-x64": "4.63.5", + "@rollup/rollup-freebsd-arm64": "4.63.5", + "@rollup/rollup-freebsd-x64": "4.63.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.5", + "@rollup/rollup-linux-arm-musleabihf": "4.63.5", + "@rollup/rollup-linux-arm64-gnu": "4.63.5", + "@rollup/rollup-linux-arm64-musl": "4.63.5", + "@rollup/rollup-linux-loong64-gnu": "4.63.5", + "@rollup/rollup-linux-loong64-musl": "4.63.5", + "@rollup/rollup-linux-ppc64-gnu": "4.63.5", + "@rollup/rollup-linux-ppc64-musl": "4.63.5", + "@rollup/rollup-linux-riscv64-gnu": "4.63.5", + "@rollup/rollup-linux-riscv64-musl": "4.63.5", + "@rollup/rollup-linux-s390x-gnu": "4.63.5", + "@rollup/rollup-linux-x64-gnu": "4.63.5", + "@rollup/rollup-linux-x64-musl": "4.63.5", + "@rollup/rollup-openbsd-x64": "4.63.5", + "@rollup/rollup-openharmony-arm64": "4.63.5", + "@rollup/rollup-win32-arm64-msvc": "4.63.5", + "@rollup/rollup-win32-ia32-msvc": "4.63.5", + "@rollup/rollup-win32-x64-gnu": "4.63.5", + "@rollup/rollup-win32-x64-msvc": "4.63.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.43", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.43.tgz", + "integrity": "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.43", + "@vue/compiler-sfc": "3.5.43", + "@vue/runtime-dom": "3.5.43", + "@vue/server-renderer": "3.5.43", + "@vue/shared": "3.5.43" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + } + } +} diff --git a/plugins/y-clipboard/package.json b/plugins/y-clipboard/package.json new file mode 100644 index 000000000..f78a0dec4 --- /dev/null +++ b/plugins/y-clipboard/package.json @@ -0,0 +1,20 @@ +{ + "name": "y-clipboard", + "version": "1.0.0", + "description": "A ZTools plugin", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build" + }, + "dependencies": { + "vue": "^3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "@ztools-center/ztools-api-types": "^1.0.1", + "typescript": "^5.3.0", + "vite": "^6.0.11", + "vue-tsc": "^2.0.0" + } +} diff --git a/plugins/y-clipboard/src-ztools/logo.png b/plugins/y-clipboard/src-ztools/logo.png new file mode 100644 index 000000000..0c7f6c5e4 Binary files /dev/null and b/plugins/y-clipboard/src-ztools/logo.png differ diff --git a/plugins/y-clipboard/src-ztools/plugin.json b/plugins/y-clipboard/src-ztools/plugin.json new file mode 100644 index 000000000..8c3fca0ab --- /dev/null +++ b/plugins/y-clipboard/src-ztools/plugin.json @@ -0,0 +1,22 @@ +{ + "$schema": "node_modules/@ztools-center/ztools-api-types/resource/ztools.schema.json", + "name": "y-clipboard", + "title": "Y剪贴板", + "description": "分类清晰、响应飞快的剪贴板历史管理:文本 / 链接 / 代码 / 图片 / 文件,支持收藏、搜索与键盘流操作", + "author": "ysysyss", + "version": "1.0.0", + "main": "dist/index.html", + "preload": "preload/services.js", + "logo": "logo.png", + "development": { + "main": "http://localhost:5173" + }, + "features": [ + { + "code": "clipboard", + "explain": "剪贴板历史管理", + "icon": "logo.png", + "cmds": ["剪贴板", "clipboard", "clip", "历史"] + } + ] +} diff --git a/plugins/y-clipboard/src-ztools/preload/package.json b/plugins/y-clipboard/src-ztools/preload/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/plugins/y-clipboard/src-ztools/preload/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/plugins/y-clipboard/src-ztools/preload/services.js b/plugins/y-clipboard/src-ztools/preload/services.js new file mode 100644 index 000000000..5d5f2db55 --- /dev/null +++ b/plugins/y-clipboard/src-ztools/preload/services.js @@ -0,0 +1,14 @@ +/** + * Y剪贴板 preload + * + * 本插件的核心能力(剪贴板历史、搜索、写入)均由宿主提供的 + * window.ztools.clipboard / window.ztools.dbStorage 完成,无需额外 + * Node.js 能力。此文件保留最小实现,仅做一次宿主环境探测, + * 方便在控制台快速确认 preload 是否生效。 + */ + +window.services = { + ready() { + return typeof window.ztools !== 'undefined' + } +} diff --git a/plugins/y-clipboard/src/App.vue b/plugins/y-clipboard/src/App.vue new file mode 100644 index 000000000..7d329fff7 --- /dev/null +++ b/plugins/y-clipboard/src/App.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/plugins/y-clipboard/src/api.ts b/plugins/y-clipboard/src/api.ts new file mode 100644 index 000000000..213c1aee5 --- /dev/null +++ b/plugins/y-clipboard/src/api.ts @@ -0,0 +1,371 @@ +import type { ClipboardRecord, FavoriteSnapshot, PageResult } from './types' + +/** + * 宿主 API 封装层。 + * + * 宿主 getHistory / search 的返回结构文档未给出精确字段, + * 这里做一层防御性归一化:无论返回 {list} / {rows} / {items} / {data} + * 还是裸数组,统一收敛成 ClipboardRecord[]。 + * + * 浏览器直接访问 localhost:5173 时无 window.ztools, + * 自动切换到 mock 数据,保证 UI 可以独立开发调试。 + */ + +const isHost = () => typeof window !== 'undefined' && typeof window.ztools !== 'undefined' +export const isDevMock = () => !isHost() + +/* ------------------------------------------------------------------ */ +/* 工具 */ +/* ------------------------------------------------------------------ */ + +function djb2(str: string): string { + let h = 5381 + for (let i = 0; i < str.length; i++) h = ((h << 5) + h + str.charCodeAt(i)) >>> 0 + return 'h' + h.toString(36) + str.length.toString(36) +} + +export function contentId(kind: string, content: string): string { + return djb2(kind + '\u0000' + content) +} + +function toNum(v: unknown): number { + if (typeof v === 'number') return v + if (typeof v === 'string') { + const n = Number(v) + if (!Number.isNaN(n)) return n + const d = new Date(v).getTime() + if (!Number.isNaN(d)) return d + } + return 0 +} + +function pick(obj: Record, keys: string[]): unknown { + for (const k of keys) { + const v = obj[k] + if (v !== undefined && v !== null && v !== '') return v + } + return undefined +} + +/** 图片路径 → 可显示 src(data: 原样;绝对路径转 file://) */ +export function toImgSrc(p: string): string { + if (!p) return '' + if (/^(data:|https?:|file:)/i.test(p)) return p + const norm = p.replace(/\\/g, '/') + return 'file://' + (norm.startsWith('/') ? '' : '/') + encodeURI(norm) +} + +/* ------------------------------------------------------------------ */ +/* 分类判定 */ +/* ------------------------------------------------------------------ */ + +const CODE_HINTS = [ + 'function ', 'const ', 'let ', '=>', 'import ', 'export ', 'class ', + 'return ', 'console.', ' { + const t = text.trim() + if (!t) return 'text' + if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(t)) return 'color' + if (/^(https?:\/\/|www\.)\S+$/i.test(t)) return 'link' + if (t.length >= 24) { + const hits = CODE_HINTS.reduce((n, h) => (t.includes(h) ? n + 1 : n), 0) + if (hits >= 2) return 'code' + } + return 'text' +} + +/* ------------------------------------------------------------------ */ +/* 归一化 */ +/* ------------------------------------------------------------------ */ + +function normalizeRecord(raw: unknown): ClipboardRecord | null { + if (!raw || typeof raw !== 'object') return null + const o = raw as Record + + const rawType = String( + pick(o, ['type', 'kind', 'contentType']) ?? 'text' + ).toLowerCase() + + const filesRaw = pick(o, ['files', 'paths', 'fileList', 'file']) + let files: string[] | undefined + if (Array.isArray(filesRaw)) { + files = filesRaw.map(String).filter(Boolean) + } else if (typeof filesRaw === 'string' && filesRaw) { + files = [filesRaw] + } + + let content = '' + let kind: ClipboardRecord['kind'] + + if (rawType.includes('image')) { + kind = 'image' + content = String( + pick(o, ['thumbnail', 'image', 'dataUrl', 'dataURL', 'content', 'data', 'path', 'url', 'src']) ?? '' + ) + if (!content) return null + } else if (rawType.includes('file') || (files && files.length > 0)) { + kind = 'file' + if (!files || files.length === 0) return null + content = files[0] + } else { + content = String(pick(o, ['content', 'text', 'value', 'data']) ?? '') + if (!content) return null + kind = detectKind(content) + } + + const id = String(pick(o, ['id', '_id', 'recordId']) ?? djb2(kind + '\u0000' + content)) + + return { + id, + kind, + rawType, + content, + files, + createdAt: toNum(pick(o, ['createdAt', 'createAt', 'time', 'timestamp', 'date', 'updated_at'])), + raw + } +} + +function extractList(res: unknown): unknown[] { + if (Array.isArray(res)) return res + if (res && typeof res === 'object') { + const o = res as Record + for (const k of ['list', 'rows', 'items', 'data', 'records', 'result']) { + if (Array.isArray(o[k])) return o[k] + } + // { success, data: { list } } 双层结构 + if (o.data && typeof o.data === 'object') return extractList(o.data) + } + return [] +} + +function toRecords(res: unknown): ClipboardRecord[] { + return extractList(res) + .map(normalizeRecord) + .filter((r): r is ClipboardRecord => r !== null) +} + +/* ------------------------------------------------------------------ */ +/* 宿主 API */ +/* ------------------------------------------------------------------ */ + +export const host = { + async getHistory(page: number, pageSize: number): Promise { + const res = await (window.ztools as any).clipboard.getHistory(page, pageSize) + const items = toRecords(res) + return { items, hasMore: items.length >= pageSize } + }, + + async search(keyword: string): Promise { + const res = await (window.ztools as any).clipboard.search(keyword) + return toRecords(res) + }, + + async remove(id: string): Promise { + const res = await (window.ztools as any).clipboard.delete(id) + return !res || (res as any).success !== false + }, + + /** 写回剪贴板(默认同时模拟粘贴到之前焦点窗口) */ + async write(id: string, paste = true): Promise { + await (window.ztools as any).clipboard.write(id, paste) + }, + + /** 按内容写剪贴板(收藏快照等无宿主 id 的场景) */ + async writeContent( + type: 'text' | 'image' | 'file', + content: string | string[], + paste = true + ): Promise { + await (window.ztools as any).clipboard.writeContent({ type, content }, paste) + }, + + async clear(): Promise { + const res = await (window.ztools as any).clipboard.clear() + return Number((res as any)?.count ?? 0) + }, + + onChange(cb: () => void): void { + ;(window.ztools as any).clipboard.onChange(cb) + }, + + outPlugin(): void { + void (window.ztools as any).outPlugin?.() + }, + + setSubInput(onChange: (text: string) => void, placeholder: string): void { + ;(window.ztools as any).setSubInput({ text: onChange }, placeholder, true) + }, + + toast(message: string): void { + void (window.ztools as any).showToast?.(message) + }, + + theme(): { dark: boolean; accent: string } { + try { + const info = (window.ztools as any).getThemeInfo?.() ?? {} + return { + dark: window.ztools.isDarkColors?.() ?? false, + accent: String(info.primaryColor || '') + } + } catch { + return { dark: false, accent: '' } + } + }, + + onThemeChange(cb: () => void): void { + ;(window.ztools as any).onThemeChange?.(cb) + } +} + +/* ------------------------------------------------------------------ */ +/* 浏览器开发 mock */ +/* ------------------------------------------------------------------ */ + +const MOCK_TEXTS: Array<[string, string]> = [ + ['text', '会议纪要:周三评审剪贴板插件交互稿,重点看键盘流是否顺'], + ['text', '发票抬头:北京某某科技有限公司 税号 91110108MA01XXXXXX'], + ['text', '地铁 10 号线换乘 6 号线,C 口出直走 200 米'], + ['link', 'https://vuejs.org/guide/introduction.html'], + ['link', 'https://github.com/vuejs/core/releases'], + ['link', 'https://ztoolscenter.github.io/ZTools-doc/first-plugin.html'], + ['code', "const debounce = (fn, ms) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms) } }"], + ['code', 'import { createApp } from \'vue\'\nimport App from \'./App.vue\'\ncreateApp(App).mount(\'#app\')'], + ['code', 'SELECT id, content, created_at FROM clipboard ORDER BY created_at DESC LIMIT 50;'], + ['color', '#5B8DEF'], + ['color', '#0F6E56'], + ['text', 'ssh -p 2222 dev@10.0.0.12'], + ['text', '取件码 8-2-3069,丰巢快递柜'], +] + +const MOCK_FILES = [ + ['E:\\A_YSYSYSS_workspace\\y-clipboard\\src-ztools\\plugin.json'], + ['D:\\Downloads\\设计稿-首页-v3.fig', 'D:\\Downloads\\设计稿-详情-v3.fig'], +] + +function mockImage(i: number): string { + const hue = (i * 47) % 360 + const svg = `` + return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg) +} + +function buildMockData(): any[] { + const out: any[] = [] + const now = Date.now() + for (let i = 0; i < 72; i++) { + const [kind, content] = MOCK_TEXTS[i % MOCK_TEXTS.length] + const age = (i * 23 + (i % 7) * 41) * 60 * 1000 // 分布在过去约 48h + if (kind === 'text' || kind === 'link' || kind === 'code' || kind === 'color') { + out.push({ id: 'mock-' + i, type: 'text', content, createdAt: now - age }) + } else if (i % 11 === 5) { + out.push({ id: 'mock-img-' + i, type: 'image', thumbnail: mockImage(i), createdAt: now - age }) + } else if (i % 11 === 8) { + const [f] = MOCK_FILES[i % MOCK_FILES.length] + const files = i % 2 === 0 ? [f] : MOCK_FILES[i % MOCK_FILES.length] + out.push({ id: 'mock-file-' + i, type: 'file', files, createdAt: now - age }) + } + } + return out +} + +let mockDb = buildMockData() + +const mock = { + async getHistory(page: number, pageSize: number): Promise { + await sleep(60) + const start = (page - 1) * pageSize + const items = toRecords(mockDb.slice(start, start + pageSize)) + return { items, hasMore: start + pageSize < mockDb.length } + }, + + async search(keyword: string): Promise { + await sleep(80) + const kw = keyword.toLowerCase() + return toRecords( + mockDb.filter((r: any) => String(r.content ?? '').toLowerCase().includes(kw)) + ) + }, + + async remove(id: string): Promise { + mockDb = mockDb.filter((r: any) => r.id !== id) + return true + }, + + async write(_id: string): Promise { + mock.toast('已复制(开发模式不执行真实粘贴)') + }, + + async writeContent(): Promise { + mock.toast('已复制(开发模式不执行真实粘贴)') + }, + + async clear(): Promise { + const n = mockDb.length + mockDb = [] + return n + }, + + onChange(_cb: () => void): void { + /* 开发模式不监听 */ + }, + + outPlugin(): void { + mock.toast('开发模式:此处会退出插件') + }, + + setSubInput(_onChange: (t: string) => void, _ph: string): void { + /* 开发模式用页面内搜索框 */ + }, + + toast(message: string): void { + console.info('[toast]', message) + }, + + theme(): { dark: boolean; accent: string } { + return { dark: window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false, accent: '#5B8DEF' } + }, + + onThemeChange(_cb: () => void): void { + /* noop */ + } +} + +function sleep(ms: number): Promise { + return new Promise((r) => setTimeout(r, ms)) +} + +/** 统一出口:真实宿主或 mock */ +export const api = isHost() ? host : mock + +/* ------------------------------------------------------------------ */ +/* 收藏持久化(宿主 dbStorage / mock localStorage) */ +/* ------------------------------------------------------------------ */ + +const FAV_KEY = 'y-clipboard:favorites' +const FAV_LIMIT = 200 + +function rawStorage(): Pick { + if (isHost()) return window.ztools.dbStorage as any + return localStorage +} + +export const favoritesStore = { + load(): FavoriteSnapshot[] { + try { + const raw = rawStorage().getItem(FAV_KEY) + const parsed = raw ? JSON.parse(raw) : [] + return Array.isArray(parsed) ? parsed : [] + } catch { + return [] + } + }, + + save(list: FavoriteSnapshot[]): void { + try { + rawStorage().setItem(FAV_KEY, JSON.stringify(list.slice(0, FAV_LIMIT))) + } catch { /* 存储失败静默 */ } + } +} diff --git a/plugins/y-clipboard/src/components/ClipboardItem.vue b/plugins/y-clipboard/src/components/ClipboardItem.vue new file mode 100644 index 000000000..47b369487 --- /dev/null +++ b/plugins/y-clipboard/src/components/ClipboardItem.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/plugins/y-clipboard/src/components/Sidebar.vue b/plugins/y-clipboard/src/components/Sidebar.vue new file mode 100644 index 000000000..0d468bcb9 --- /dev/null +++ b/plugins/y-clipboard/src/components/Sidebar.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/plugins/y-clipboard/src/components/Toolbar.vue b/plugins/y-clipboard/src/components/Toolbar.vue new file mode 100644 index 000000000..bb95524fb --- /dev/null +++ b/plugins/y-clipboard/src/components/Toolbar.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/plugins/y-clipboard/src/components/VirtualList.vue b/plugins/y-clipboard/src/components/VirtualList.vue new file mode 100644 index 000000000..44300ed16 --- /dev/null +++ b/plugins/y-clipboard/src/components/VirtualList.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/plugins/y-clipboard/src/composables/useStore.ts b/plugins/y-clipboard/src/composables/useStore.ts new file mode 100644 index 000000000..39f85ab2e --- /dev/null +++ b/plugins/y-clipboard/src/composables/useStore.ts @@ -0,0 +1,299 @@ +import { computed, reactive, ref, watch } from 'vue' +import { api, favoritesStore, isDevMock, contentId, toImgSrc } from '../api' +import type { Category, ClipboardRecord, FavoriteSnapshot, ItemKind } from '../types' + +const PAGE_SIZE = 60 +const SEARCH_DEBOUNCE = 100 + +/* ------------------------------------------------------------------ */ +/* 单例状态 */ +/* ------------------------------------------------------------------ */ + +const records = ref([]) +const favorites = ref([]) +const activeCat = ref('all') +const query = ref('') +const selectedIndex = ref(0) +const page = ref(0) +const hasMore = ref(false) +const loading = ref(false) +const remoteSearching = ref(false) + +const favIds = computed(() => new Set(favorites.value.map((f) => f.id))) + +/* ------------------------------------------------------------------ */ +/* 过滤 */ +/* ------------------------------------------------------------------ */ + +function matchCat(r: ClipboardRecord): boolean { + if (activeCat.value === 'all') return true + if (activeCat.value === 'star') return favIds.value.has(favKey(r)) + return r.kind === activeCat.value +} + +function matchQuery(r: ClipboardRecord): boolean { + const q = query.value.trim().toLowerCase() + if (!q) return true + if (r.kind === 'file' && r.files) { + return r.files.some((f) => f.toLowerCase().includes(q)) + } + return r.content.toLowerCase().includes(q) +} + +/** 收藏在记录列表中的 key(同内容同分类视为同一条) */ +function favKey(r: ClipboardRecord): string { + return contentId(r.kind, r.files ? r.files.join('|') : r.content) +} + +/** star 分类下的"记录":直接由收藏快照构造,删除原记录也不丢 */ +const favoriteRecords = computed(() => + favorites.value.map((f) => ({ + id: f.id, + kind: f.kind, + rawType: f.kind, + content: f.content, + files: f.files, + createdAt: f.createdAt, + raw: null + })) +) + +const filtered = computed(() => { + if (activeCat.value === 'star') { + const q = query.value.trim().toLowerCase() + if (!q) return favoriteRecords.value + return favoriteRecords.value.filter(matchQuery) + } + return records.value.filter((r) => matchCat(r) && matchQuery(r)) +}) + +const counts = reactive>({ + all: 0, text: 0, link: 0, code: 0, color: 0, image: 0, file: 0, star: 0 +}) + +function recount(): void { + const c: Record = { all: records.value.length, star: favorites.value.length } + for (const r of records.value) c[r.kind] = (c[r.kind] ?? 0) + 1 + for (const k of Object.keys(counts) as Category[]) { + counts[k] = c[k] ?? 0 + } +} + +/* ------------------------------------------------------------------ */ +/* 加载 */ +/* ------------------------------------------------------------------ */ + +let loadSeq = 0 + +async function loadFirst(): Promise { + const seq = ++loadSeq + loading.value = true + try { + const res = await api.getHistory(1, PAGE_SIZE) + if (seq !== loadSeq) return + records.value = res.items + page.value = 1 + hasMore.value = res.hasMore + selectedIndex.value = 0 + recount() + } finally { + if (seq === loadSeq) loading.value = false + } +} + +async function loadMore(): Promise { + if (loading.value || !hasMore.value || query.value.trim()) return + const seq = ++loadSeq + loading.value = true + try { + const res = await api.getHistory(page.value + 1, PAGE_SIZE) + if (seq !== loadSeq) return + const seen = new Set(records.value.map((r) => r.id)) + records.value = records.value.concat(res.items.filter((r) => !seen.has(r.id))) + page.value += 1 + hasMore.value = res.hasMore + recount() + } finally { + if (seq === loadSeq) loading.value = false + } +} + +/* ------------------------------------------------------------------ */ +/* 搜索:本地即时过滤 + 远端防抖补充 */ +/* ------------------------------------------------------------------ */ + +let searchTimer: ReturnType | null = null + +function setQuery(q: string): void { + query.value = q + selectedIndex.value = 0 + if (searchTimer) clearTimeout(searchTimer) + const kw = q.trim() + if (!kw) { + remoteSearching.value = false + if (records.value.length === 0) void loadFirst() + return + } + searchTimer = setTimeout(async () => { + remoteSearching.value = true + try { + const remote = await api.search(kw) + if (query.value.trim() !== kw) return + const seen = new Set(records.value.map((r) => r.id)) + const fresh = remote.filter((r) => !seen.has(r.id)) + if (fresh.length) { + records.value = fresh.concat(records.value) + recount() + } + } finally { + remoteSearching.value = false + } + }, SEARCH_DEBOUNCE) +} + +/* ------------------------------------------------------------------ */ +/* 动作 */ +/* ------------------------------------------------------------------ */ + +async function copyRecord(r: ClipboardRecord, exit = true): Promise { + try { + const isSnapshot = activeCat.value === 'star' || !r.raw + if (isSnapshot) { + if (r.kind === 'file' && r.files) await api.writeContent('file', r.files) + else if (r.kind === 'image') await api.writeContent('image', r.content) + else await api.writeContent('text', r.content) + } else { + await api.write(r.id) + } + if (exit) api.outPlugin() + } catch (e) { + api.toast('复制失败:' + (e instanceof Error ? e.message : String(e))) + } +} + +async function deleteRecord(r: ClipboardRecord): Promise { + const idx = filtered.value.findIndex((x) => x.id === r.id) + const ok = await api.remove(r.id) + if (!ok) { + api.toast('删除失败') + return + } + if (activeCat.value === 'star') { + favorites.value = favorites.value.filter((f) => f.id !== r.id) + favoritesStore.save(favorites.value) + recount() + } else { + records.value = records.value.filter((x) => x.id !== r.id) + recount() + } + if (idx >= 0 && idx < filtered.value.length) { + selectedIndex.value = Math.min(idx, filtered.value.length - 1) + } else if (filtered.value.length > 0) { + selectedIndex.value = filtered.value.length - 1 + } else { + selectedIndex.value = 0 + } +} + +function toggleStar(r: ClipboardRecord): void { + const key = favKey(r) + if (favIds.value.has(key)) { + favorites.value = favorites.value.filter((f) => f.id !== key) + } else { + const snap: FavoriteSnapshot = { + id: key, + kind: r.kind, + content: r.content, + files: r.files, + createdAt: r.createdAt || Date.now() + } + favorites.value = [snap, ...favorites.value].slice(0, 200) + } + favoritesStore.save(favorites.value) + recount() +} + +async function clearAll(): Promise { + const n = await api.clear() + if (n > 0 || isDevMock()) { + records.value = [] + hasMore.value = false + recount() + api.toast(`已清空 ${n} 条记录`) + } +} + +function setCat(cat: Category): void { + activeCat.value = cat + selectedIndex.value = 0 +} + +function moveSelection(delta: number): void { + const len = filtered.value.length + if (len === 0) return + selectedIndex.value = Math.min(len - 1, Math.max(0, selectedIndex.value + delta)) +} + +/* 过滤结果变化时收拢选中下标,避免越界空选 */ +watch(filtered, (list) => { + if (selectedIndex.value >= list.length) selectedIndex.value = Math.max(0, list.length - 1) +}) + +/* ------------------------------------------------------------------ */ +/* 初始化 */ +/* ------------------------------------------------------------------ */ + +let inited = false + +function init(): void { + if (inited) return + inited = true + + favorites.value = favoritesStore.load() + + api.setSubInput((payload: unknown) => { + const text = typeof payload === 'string' ? payload : String((payload as any)?.text ?? '') + setQuery(text) + }, '搜索剪贴板历史') + + api.onChange(async (payload?: unknown) => { + // 变化项结构未知:能归一化就 prepend,否则保守重载第一页 + const maybe = Array.isArray(payload) ? payload[0] : payload + if (maybe && typeof maybe === 'object') { + try { + const res = await api.getHistory(1, 5) + const seen = new Set(records.value.map((r) => r.id)) + const fresh = res.items.filter((r) => !seen.has(r.id)) + if (fresh.length) { + records.value = fresh.concat(records.value) + recount() + return + } + if (res.items.length) { + records.value = res.items + recount() + return + } + } catch { /* fallthrough */ } + } + void loadFirst() + }) + + void loadFirst() +} + +export function useStore() { + return { + records, favorites, activeCat, query, selectedIndex, + loading, remoteSearching, hasMore, counts, filtered, + init, loadFirst, loadMore, setQuery, setCat, + copyRecord, deleteRecord, toggleStar, clearAll, moveSelection, + favKey + } +} + +export type Store = ReturnType + +/** 供 ClipboardItem 使用的图片 src 转换(避免组件直接依赖 api 内部) */ +export { toImgSrc } +export type { Category, ItemKind } diff --git a/plugins/y-clipboard/src/env.d.ts b/plugins/y-clipboard/src/env.d.ts new file mode 100644 index 000000000..a0b822a7e --- /dev/null +++ b/plugins/y-clipboard/src/env.d.ts @@ -0,0 +1,21 @@ +/// +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent, Record, unknown> + export default component +} + +// Preload services 类型声明(对应 src-ztools/preload/services.js) +interface Services { + ready: () => boolean +} + +declare global { + interface Window { + services: Services + } +} + +export {} diff --git a/plugins/y-clipboard/src/main.css b/plugins/y-clipboard/src/main.css new file mode 100644 index 000000000..15bcd2a0a --- /dev/null +++ b/plugins/y-clipboard/src/main.css @@ -0,0 +1,102 @@ +/* ===== 主题变量 ===== */ +:root { + --bg: #f6f7f9; + --panel: #ffffff; + --border: rgba(15, 20, 32, 0.08); + --border-strong: rgba(15, 20, 32, 0.16); + --text: #1f2329; + --text-2: #5f6570; + --text-3: #949aa3; + --hover: rgba(15, 20, 32, 0.045); + --active: rgba(15, 20, 32, 0.09); + --accent: #3377ff; + --accent-weak: rgba(51, 119, 255, 0.12); + --danger: #e5484d; + --danger-weak: rgba(229, 72, 77, 0.1); + --star: #f5a623; + + /* 分类色 */ + --c-text: #6b7280; + --c-link: #2563eb; + --c-code: #7c3aed; + --c-color: #db2777; + --c-image: #0d9488; + --c-file: #d97706; + + --row-h: 72px; + --radius: 8px; +} + +.dark { + --bg: #1b1c1f; + --panel: #232428; + --border: rgba(235, 240, 250, 0.09); + --border-strong: rgba(235, 240, 250, 0.18); + --text: #e6e8eb; + --text-2: #a8adb5; + --text-3: #6f757e; + --hover: rgba(235, 240, 250, 0.06); + --active: rgba(235, 240, 250, 0.12); + --accent-weak: rgba(120, 168, 255, 0.16); + + --c-text: #9aa1ab; + --c-link: #6ea8ff; + --c-code: #b493f5; + --c-color: #f286b5; + --c-image: #4fc9b8; + --c-file: #eeb25f; +} + +* { box-sizing: border-box; margin: 0; padding: 0; } + +html, body, #app { height: 100%; } + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', + 'Microsoft YaHei', 'Noto Sans SC', sans-serif; + font-size: 13px; + color: var(--text); + background: var(--bg); + -webkit-font-smoothing: antialiased; + user-select: none; + overflow: hidden; +} + +button { + font: inherit; + color: inherit; + background: none; + border: none; + cursor: pointer; +} + +/* 滚动条 */ +::-webkit-scrollbar { width: 8px; } +::-webkit-scrollbar-thumb { + background: var(--border-strong); + border-radius: 4px; +} +::-webkit-scrollbar-track { background: transparent; } + +/* ===== 布局骨架 ===== */ +.app { + display: flex; + flex-direction: column; + height: 100vh; + background: var(--panel); +} + +.app-body { + display: flex; + flex: 1; + min-height: 0; +} + +/* ===== 图标 ===== */ +.icon-svg { width: 16px; height: 16px; flex: none; } + +/* ===== 动画 ===== */ +@keyframes fade-in { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: none; } +} diff --git a/plugins/y-clipboard/src/main.ts b/plugins/y-clipboard/src/main.ts new file mode 100644 index 000000000..8a3c77bc5 --- /dev/null +++ b/plugins/y-clipboard/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import './main.css' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/plugins/y-clipboard/src/types.ts b/plugins/y-clipboard/src/types.ts new file mode 100644 index 000000000..26af53c52 --- /dev/null +++ b/plugins/y-clipboard/src/types.ts @@ -0,0 +1,36 @@ +/** 内容分类 */ +export type ItemKind = 'text' | 'link' | 'code' | 'color' | 'image' | 'file' + +export type Category = 'all' | ItemKind | 'star' + +/** 归一化后的剪贴板记录(无论宿主返回什么结构,UI 只面对这个) */ +export interface ClipboardRecord { + /** 宿主记录 id(无 id 时用内容 hash 代替) */ + id: string + /** 分类结果 */ + kind: ItemKind + /** 原始类型标记 text / image / file ... */ + rawType: string + /** 文本内容;图片为 dataURL/路径;文件为主路径 */ + content: string + /** 文件类记录的完整路径列表 */ + files?: string[] + /** 创建时间戳(ms),未知为 0 */ + createdAt: number + /** 原始记录,写回宿主时可能需要 */ + raw?: unknown +} + +export interface PageResult { + items: ClipboardRecord[] + hasMore: boolean +} + +export interface FavoriteSnapshot { + /** 快照 id:生成时基于内容 */ + id: string + kind: ItemKind + content: string + files?: string[] + createdAt: number +} diff --git a/plugins/y-clipboard/src/utils/format.ts b/plugins/y-clipboard/src/utils/format.ts new file mode 100644 index 000000000..2cab95b48 --- /dev/null +++ b/plugins/y-clipboard/src/utils/format.ts @@ -0,0 +1,71 @@ +import type { ClipboardRecord, ItemKind } from '../types' + +/** 相对时间:刚刚 / n 分钟前 / n 小时前 / 昨天 / MM-DD / YYYY-MM-DD */ +export function relTime(ts: number): string { + if (!ts) return '' + const diff = Date.now() - ts + if (diff < 60_000) return '刚刚' + if (diff < 3_600_000) return `${Math.floor(diff / 60_000)} 分钟前` + if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)} 小时前` + if (diff < 172_800_000) return '昨天' + const d = new Date(ts) + const md = `${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` + if (d.getFullYear() === new Date().getFullYear()) return md + return `${d.getFullYear()}-${md}` +} + +/** 文本预览:压掉多余空白,折叠换行 */ +export function previewText(text: string, max = 240): string { + const t = text.replace(/\s+/g, ' ').trim() + return t.length > max ? t.slice(0, max) + '…' : t +} + +/** 文件路径列表 → 文件名列表 */ +export function fileNames(files: string[]): string { + return files.map((f) => f.replace(/[\\/]+/g, '/').split('/').pop() || f).join('、') +} + +/** 字节数描述(文件/图片大小如有) */ +export function humanSize(bytes?: number): string { + if (!bytes || bytes <= 0) return '' + const units = ['B', 'KB', 'MB', 'GB'] + let i = 0 + let n = bytes + while (n >= 1024 && i < units.length - 1) { + n /= 1024 + i++ + } + return `${n.toFixed(n >= 100 || i === 0 ? 0 : 1)} ${units[i]}` +} + +export const KIND_META: Record = { + text: { label: '文本', color: 'var(--c-text)', icon: 'M4 7h16M4 12h16M4 17h10' }, + link: { label: '链接', color: 'var(--c-link)', icon: 'M10 14a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07L11.5 5.4M14 10a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.5-1.47' }, + code: { label: '代码', color: 'var(--c-code)', icon: 'M8 6l-5 6 5 6M16 6l5 6-5 6M13 4l-2 16' }, + color: { label: '颜色', color: 'var(--c-color)', icon: 'M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6l2.1 2.1m0-12.8l-2.1 2.1M7.7 16.3l-2.1 2.1' }, + image: { label: '图片', color: 'var(--c-image)', icon: 'M3 5h18v14H3zM3 15l5-4 4 3 3-2 6 5M15.5 9.5a1 1 0 1 0 0-.01' }, + file: { label: '文件', color: 'var(--c-file)', icon: 'M6 2h8l4 4v16H6zM14 2v5h4' } +} + +/** 颜色色板展示(#hex 记录) */ +export function isHexColor(t: string): boolean { + return /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(t.trim()) +} + +/** 记录的次级说明文案(空串表示不显示) */ +export function recordMeta(r: ClipboardRecord): string { + if (r.kind === 'image') return '' + if (r.kind === 'file' && r.files) return `${r.files.length} 个文件` + if (r.kind === 'color') return '颜色值' + if (r.kind === 'link') return hostname(r.content) + if (r.kind === 'code') return '代码片段' + return `${r.content.length} 字符` +} + +function hostname(url: string): string { + try { + return new URL(url).hostname + } catch { + return url.split('/')[2] || url + } +} diff --git a/plugins/y-clipboard/tsconfig.json b/plugins/y-clipboard/tsconfig.json new file mode 100644 index 000000000..39b73bc6f --- /dev/null +++ b/plugins/y-clipboard/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": false, + "noImplicitAny": false, + "types": ["@ztools-center/ztools-api-types"] + }, + "include": ["src"] +} diff --git a/plugins/y-clipboard/vite.config.js b/plugins/y-clipboard/vite.config.js new file mode 100644 index 000000000..ce331609e --- /dev/null +++ b/plugins/y-clipboard/vite.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + base: './', + build: { + outDir: 'src-ztools/dist', + emptyOutDir: true + } +})