copyhub-cli 1.0.1 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -1
- package/README.md +73 -8
- package/package.json +1 -1
- package/src/cli.js +36 -3
- package/src/config.js +20 -0
- package/src/load-env.js +39 -0
- package/src/oauth.js +270 -3
- package/src/sheet-overlay-history.js +79 -0
- package/src/sheets.js +1 -1
- package/src/start-daemon-logic.js +4 -1
- package/src/storage.js +11 -0
- package/src/wipe-data.js +10 -0
- package/ui/main.mjs +272 -20
- package/ui/preload.cjs +4 -1
- package/ui/renderer/index.html +256 -7
package/ui/renderer/index.html
CHANGED
|
@@ -58,6 +58,53 @@
|
|
|
58
58
|
cursor: grabbing;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
#list-wrap {
|
|
62
|
+
flex: 1;
|
|
63
|
+
min-height: 0;
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
position: relative;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#list-loading {
|
|
70
|
+
flex-shrink: 0;
|
|
71
|
+
display: none;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 10px;
|
|
74
|
+
padding: 8px 14px;
|
|
75
|
+
font-size: 12px;
|
|
76
|
+
font-weight: 600;
|
|
77
|
+
color: var(--accent);
|
|
78
|
+
background: linear-gradient(90deg, var(--accent-soft), rgba(61, 90, 254, 0.02));
|
|
79
|
+
border-bottom: 1px solid var(--border);
|
|
80
|
+
-webkit-app-region: no-drag;
|
|
81
|
+
app-region: no-drag;
|
|
82
|
+
animation: hint-pulse 1.4s ease-in-out infinite;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#list-loading.visible {
|
|
86
|
+
display: flex;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#list-loading .spinner {
|
|
90
|
+
width: 16px;
|
|
91
|
+
height: 16px;
|
|
92
|
+
flex-shrink: 0;
|
|
93
|
+
border: 2px solid rgba(61, 90, 254, 0.35);
|
|
94
|
+
border-top-color: var(--accent);
|
|
95
|
+
border-radius: 50%;
|
|
96
|
+
animation: spin 0.65s linear infinite;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@keyframes spin {
|
|
100
|
+
to { transform: rotate(360deg); }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@keyframes hint-pulse {
|
|
104
|
+
0%, 100% { opacity: 1; }
|
|
105
|
+
50% { opacity: 0.82; }
|
|
106
|
+
}
|
|
107
|
+
|
|
61
108
|
#list {
|
|
62
109
|
flex: 1;
|
|
63
110
|
min-height: 0;
|
|
@@ -173,33 +220,173 @@
|
|
|
173
220
|
border-radius: var(--radius);
|
|
174
221
|
font-size: 13px;
|
|
175
222
|
}
|
|
223
|
+
|
|
224
|
+
#pager {
|
|
225
|
+
flex-shrink: 0;
|
|
226
|
+
display: none;
|
|
227
|
+
align-items: center;
|
|
228
|
+
justify-content: center;
|
|
229
|
+
gap: 12px;
|
|
230
|
+
padding: 8px 12px 12px;
|
|
231
|
+
border-top: 1px solid var(--border);
|
|
232
|
+
background: linear-gradient(180deg, var(--bg) 0%, #fafbfc 100%);
|
|
233
|
+
-webkit-app-region: no-drag;
|
|
234
|
+
app-region: no-drag;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#pager.visible {
|
|
238
|
+
display: flex;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#pager button {
|
|
242
|
+
font: inherit;
|
|
243
|
+
font-size: 13px;
|
|
244
|
+
font-weight: 600;
|
|
245
|
+
padding: 6px 14px;
|
|
246
|
+
border-radius: 8px;
|
|
247
|
+
border: 1px solid var(--border);
|
|
248
|
+
background: var(--surface);
|
|
249
|
+
color: var(--text);
|
|
250
|
+
cursor: pointer;
|
|
251
|
+
transition: background 0.15s, border-color 0.15s;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#pager button:hover:not(:disabled) {
|
|
255
|
+
background: var(--surface-hover);
|
|
256
|
+
border-color: rgba(61, 90, 254, 0.35);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#pager button:disabled {
|
|
260
|
+
opacity: 0.45;
|
|
261
|
+
cursor: default;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#pg-info {
|
|
265
|
+
font-size: 12px;
|
|
266
|
+
color: var(--text-muted);
|
|
267
|
+
min-width: 0;
|
|
268
|
+
text-align: center;
|
|
269
|
+
flex: 1;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.sheet-hint {
|
|
273
|
+
flex-shrink: 0;
|
|
274
|
+
font-size: 11px;
|
|
275
|
+
line-height: 1.45;
|
|
276
|
+
color: var(--text-muted);
|
|
277
|
+
padding: 6px 14px 4px;
|
|
278
|
+
text-align: center;
|
|
279
|
+
border-top: 1px solid var(--border);
|
|
280
|
+
background: var(--bg);
|
|
281
|
+
-webkit-app-region: no-drag;
|
|
282
|
+
app-region: no-drag;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.sheet-hint[hidden] {
|
|
286
|
+
display: none !important;
|
|
287
|
+
}
|
|
176
288
|
</style>
|
|
177
289
|
</head>
|
|
178
290
|
<body>
|
|
179
291
|
<div id="drag-strip" title="Drag to move window"></div>
|
|
180
|
-
<div id="list"
|
|
292
|
+
<div id="list-wrap">
|
|
293
|
+
<div id="list-loading" aria-live="polite">
|
|
294
|
+
<div class="spinner" aria-hidden="true"></div>
|
|
295
|
+
<span>Syncing Google Sheet…</span>
|
|
296
|
+
</div>
|
|
297
|
+
<div id="list"></div>
|
|
298
|
+
</div>
|
|
299
|
+
<div id="sheet-hint" class="sheet-hint" hidden></div>
|
|
300
|
+
<div id="pager" aria-label="Pagination">
|
|
301
|
+
<button type="button" id="pg-prev">Previous</button>
|
|
302
|
+
<span id="pg-info"></span>
|
|
303
|
+
<button type="button" id="pg-next">Next</button>
|
|
304
|
+
</div>
|
|
181
305
|
<script>
|
|
182
306
|
const listEl = document.getElementById('list');
|
|
307
|
+
const listLoadingEl = document.getElementById('list-loading');
|
|
308
|
+
const pagerEl = document.getElementById('pager');
|
|
309
|
+
const pgPrev = document.getElementById('pg-prev');
|
|
310
|
+
const pgNext = document.getElementById('pg-next');
|
|
311
|
+
const pgInfo = document.getElementById('pg-info');
|
|
312
|
+
const sheetHintEl = document.getElementById('sheet-hint');
|
|
313
|
+
|
|
314
|
+
const pageSize = 10;
|
|
315
|
+
let currentPage = 1;
|
|
316
|
+
/** @type {{ total: number, totalPages: number }} */
|
|
317
|
+
let lastPaging = { total: 0, totalPages: 1 };
|
|
318
|
+
let lastSheetHasMore = false;
|
|
319
|
+
let sheetSyncInFlight = false;
|
|
183
320
|
|
|
184
|
-
|
|
321
|
+
function setLoadingUi(on) {
|
|
322
|
+
listLoadingEl.classList.toggle('visible', on);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function syncPagerBar() {
|
|
326
|
+
const { total, totalPages } = lastPaging;
|
|
327
|
+
if (!total) return;
|
|
328
|
+
pgPrev.disabled = sheetSyncInFlight || currentPage <= 1;
|
|
329
|
+
pgNext.disabled =
|
|
330
|
+
sheetSyncInFlight ||
|
|
331
|
+
(currentPage * pageSize >= total && !lastSheetHasMore);
|
|
332
|
+
const from = (currentPage - 1) * pageSize + 1;
|
|
333
|
+
const to = Math.min(currentPage * pageSize, total);
|
|
334
|
+
const moreMark = lastSheetHasMore && currentPage * pageSize >= total ? '+' : '';
|
|
335
|
+
const pagesShown =
|
|
336
|
+
lastSheetHasMore && currentPage * pageSize >= total
|
|
337
|
+
? Math.max(totalPages, currentPage + 1)
|
|
338
|
+
: totalPages;
|
|
339
|
+
pgInfo.textContent = `Page ${currentPage} / ${pagesShown} · ${from}–${to} of ${total}${moreMark}`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function renderHistoryResponse(res) {
|
|
185
343
|
listEl.innerHTML = '';
|
|
186
|
-
|
|
344
|
+
|
|
187
345
|
if (res && res.error) {
|
|
346
|
+
pagerEl.classList.remove('visible');
|
|
347
|
+
sheetHintEl.hidden = true;
|
|
188
348
|
const e = document.createElement('div');
|
|
189
349
|
e.id = 'err';
|
|
190
350
|
e.textContent = res.error;
|
|
191
351
|
listEl.appendChild(e);
|
|
352
|
+
lastPaging = { total: 0, totalPages: 1 };
|
|
353
|
+
lastSheetHasMore = false;
|
|
192
354
|
return;
|
|
193
355
|
}
|
|
356
|
+
|
|
357
|
+
lastSheetHasMore = Boolean(res.sheetHasMore);
|
|
358
|
+
currentPage = res.page ?? currentPage;
|
|
194
359
|
const rows = res && Array.isArray(res.items) ? res.items : [];
|
|
195
|
-
|
|
360
|
+
const total = res.total ?? 0;
|
|
361
|
+
const totalPages = res.totalPages ?? 1;
|
|
362
|
+
lastPaging = { total, totalPages };
|
|
363
|
+
|
|
364
|
+
if (!total) {
|
|
365
|
+
pagerEl.classList.remove('visible');
|
|
366
|
+
if (res.sheetHint) {
|
|
367
|
+
sheetHintEl.hidden = false;
|
|
368
|
+
sheetHintEl.textContent = res.sheetHint;
|
|
369
|
+
} else {
|
|
370
|
+
sheetHintEl.hidden = true;
|
|
371
|
+
}
|
|
196
372
|
const d = document.createElement('div');
|
|
197
373
|
d.id = 'empty';
|
|
198
374
|
d.textContent =
|
|
199
|
-
'No history yet — run copyhub start and copy something. Click a row to copy again; click outside or Esc to close.';
|
|
375
|
+
'No local history yet — run copyhub start and copy something. Click a row to copy again; click outside or Esc to close.';
|
|
200
376
|
listEl.appendChild(d);
|
|
201
377
|
return;
|
|
202
378
|
}
|
|
379
|
+
|
|
380
|
+
if (res.sheetHint) {
|
|
381
|
+
sheetHintEl.hidden = false;
|
|
382
|
+
sheetHintEl.textContent = res.sheetHint;
|
|
383
|
+
} else {
|
|
384
|
+
sheetHintEl.hidden = true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
pagerEl.classList.add('visible');
|
|
388
|
+
syncPagerBar();
|
|
389
|
+
|
|
203
390
|
for (const r of rows) {
|
|
204
391
|
const div = document.createElement('div');
|
|
205
392
|
div.className = 'row' + (r.synced ? ' synced' : '');
|
|
@@ -215,10 +402,72 @@
|
|
|
215
402
|
}
|
|
216
403
|
}
|
|
217
404
|
|
|
405
|
+
async function loadHistory(opt = {}) {
|
|
406
|
+
const refreshCache = Boolean(opt.refreshCache);
|
|
407
|
+
if (refreshCache) currentPage = 1;
|
|
408
|
+
if (typeof opt.page === 'number' && opt.page >= 1) {
|
|
409
|
+
currentPage = opt.page;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (refreshCache) {
|
|
413
|
+
sheetSyncInFlight = true;
|
|
414
|
+
setLoadingUi(true);
|
|
415
|
+
pgPrev.disabled = true;
|
|
416
|
+
pgNext.disabled = true;
|
|
417
|
+
|
|
418
|
+
const quick = await window.copyhub.getHistoryLocal({
|
|
419
|
+
page: currentPage,
|
|
420
|
+
pageSize,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
if (quick.error) {
|
|
424
|
+
sheetSyncInFlight = false;
|
|
425
|
+
setLoadingUi(false);
|
|
426
|
+
renderHistoryResponse(quick);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
currentPage = quick.page ?? currentPage;
|
|
431
|
+
renderHistoryResponse(quick);
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
const full = await window.copyhub.getHistory({
|
|
435
|
+
page: currentPage,
|
|
436
|
+
pageSize,
|
|
437
|
+
refresh: true,
|
|
438
|
+
});
|
|
439
|
+
currentPage = full.page ?? currentPage;
|
|
440
|
+
renderHistoryResponse(full);
|
|
441
|
+
} finally {
|
|
442
|
+
sheetSyncInFlight = false;
|
|
443
|
+
setLoadingUi(false);
|
|
444
|
+
syncPagerBar();
|
|
445
|
+
}
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const res = await window.copyhub.getHistory({
|
|
450
|
+
page: currentPage,
|
|
451
|
+
pageSize,
|
|
452
|
+
refresh: false,
|
|
453
|
+
});
|
|
454
|
+
renderHistoryResponse(res);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
pgPrev.addEventListener('click', () => {
|
|
458
|
+
if (sheetSyncInFlight || currentPage <= 1) return;
|
|
459
|
+
void loadHistory({ page: currentPage - 1 });
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
pgNext.addEventListener('click', () => {
|
|
463
|
+
if (sheetSyncInFlight) return;
|
|
464
|
+
void loadHistory({ page: currentPage + 1 });
|
|
465
|
+
});
|
|
466
|
+
|
|
218
467
|
window.copyhub.onOpen(() => {
|
|
219
|
-
void
|
|
468
|
+
void loadHistory({ refreshCache: true });
|
|
220
469
|
});
|
|
221
|
-
void refresh();
|
|
222
470
|
</script>
|
|
223
471
|
</body>
|
|
224
472
|
</html>
|
|
473
|
+
|