koishi-plugin-memesluna 0.2.6 → 0.2.8
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/lib/index.js +326 -232
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1288,7 +1288,7 @@ function buildAdminHtml(basePath) {
|
|
|
1288
1288
|
<head>
|
|
1289
1289
|
<meta charset="utf-8" />
|
|
1290
1290
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1291
|
-
<title>图床转发 -
|
|
1291
|
+
<title>图床转发 - 合集管理</title>
|
|
1292
1292
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
1293
1293
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css">
|
|
1294
1294
|
<style>
|
|
@@ -1323,36 +1323,102 @@ function buildAdminHtml(basePath) {
|
|
|
1323
1323
|
max-width: 1320px;
|
|
1324
1324
|
}
|
|
1325
1325
|
|
|
1326
|
-
.
|
|
1327
|
-
.main-panel,
|
|
1328
|
-
.sub-card {
|
|
1326
|
+
.panel {
|
|
1329
1327
|
border-radius: 14px;
|
|
1330
1328
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
|
1331
1329
|
background: rgba(255, 255, 255, 0.72);
|
|
1332
1330
|
-webkit-backdrop-filter: blur(10px) saturate(130%);
|
|
1333
1331
|
backdrop-filter: blur(10px) saturate(130%);
|
|
1334
1332
|
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
|
|
1333
|
+
padding: 1rem;
|
|
1335
1334
|
}
|
|
1336
1335
|
|
|
1337
|
-
.
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1336
|
+
.sub-card {
|
|
1337
|
+
border-radius: 12px;
|
|
1338
|
+
border: 1px solid rgba(255, 255, 255, 0.5);
|
|
1339
|
+
background: rgba(255, 255, 255, 0.72);
|
|
1340
|
+
-webkit-backdrop-filter: blur(10px) saturate(130%);
|
|
1341
|
+
backdrop-filter: blur(10px) saturate(130%);
|
|
1342
|
+
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
|
|
1343
|
+
padding: 0.9rem;
|
|
1341
1344
|
}
|
|
1342
1345
|
|
|
1343
|
-
.
|
|
1344
|
-
|
|
1346
|
+
.folder-grid {
|
|
1347
|
+
display: grid;
|
|
1348
|
+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
1349
|
+
gap: 1rem;
|
|
1345
1350
|
}
|
|
1346
1351
|
|
|
1347
|
-
.
|
|
1352
|
+
.folder-card {
|
|
1353
|
+
border-radius: 12px;
|
|
1354
|
+
overflow: hidden;
|
|
1355
|
+
border: 1px solid rgba(148, 163, 184, 0.3);
|
|
1356
|
+
background: rgba(255, 255, 255, 0.88);
|
|
1348
1357
|
cursor: pointer;
|
|
1358
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
.folder-card:hover {
|
|
1362
|
+
transform: translateY(-3px);
|
|
1363
|
+
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.12);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
.folder-card .folder-icon {
|
|
1367
|
+
display: flex;
|
|
1368
|
+
align-items: center;
|
|
1369
|
+
justify-content: center;
|
|
1370
|
+
height: 100px;
|
|
1371
|
+
background: linear-gradient(135deg, #e0e7ff 0%, #f0f4ff 100%);
|
|
1372
|
+
font-size: 2.5rem;
|
|
1373
|
+
color: #6366f1;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
.folder-card .folder-info {
|
|
1377
|
+
padding: 0.6rem 0.8rem;
|
|
1349
1378
|
}
|
|
1350
1379
|
|
|
1351
|
-
.
|
|
1352
|
-
background: rgba(59, 130, 246, 0.14);
|
|
1353
|
-
border-color: rgba(59, 130, 246, 0.35);
|
|
1354
|
-
color: #1d4ed8;
|
|
1380
|
+
.folder-card .folder-name {
|
|
1355
1381
|
font-weight: 700;
|
|
1382
|
+
font-size: 0.95rem;
|
|
1383
|
+
color: #334155;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
.folder-card .folder-meta {
|
|
1387
|
+
font-size: 0.78rem;
|
|
1388
|
+
color: #64748b;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
.folder-card .folder-desc {
|
|
1392
|
+
font-size: 0.78rem;
|
|
1393
|
+
color: #94a3b8;
|
|
1394
|
+
margin-top: 2px;
|
|
1395
|
+
overflow: hidden;
|
|
1396
|
+
text-overflow: ellipsis;
|
|
1397
|
+
white-space: nowrap;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
.folder-card .folder-path {
|
|
1401
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
1402
|
+
font-size: 0.72rem;
|
|
1403
|
+
color: #a5b4fc;
|
|
1404
|
+
margin-top: 2px;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
.desc-editor {
|
|
1408
|
+
display: flex;
|
|
1409
|
+
align-items: center;
|
|
1410
|
+
gap: 0.5rem;
|
|
1411
|
+
margin-bottom: 0.8rem;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
.desc-editor input {
|
|
1415
|
+
flex: 1;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
.desc-display {
|
|
1419
|
+
font-size: 0.88rem;
|
|
1420
|
+
color: #64748b;
|
|
1421
|
+
margin-bottom: 0.5rem;
|
|
1356
1422
|
}
|
|
1357
1423
|
|
|
1358
1424
|
.image-grid {
|
|
@@ -1376,10 +1442,6 @@ function buildAdminHtml(basePath) {
|
|
|
1376
1442
|
background: #f1f5f9;
|
|
1377
1443
|
}
|
|
1378
1444
|
|
|
1379
|
-
.sub-card {
|
|
1380
|
-
padding: 0.9rem;
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
1445
|
.code-url {
|
|
1384
1446
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
1385
1447
|
font-size: 0.8rem;
|
|
@@ -1396,6 +1458,21 @@ function buildAdminHtml(basePath) {
|
|
|
1396
1458
|
color: #64748b;
|
|
1397
1459
|
font-size: 0.9rem;
|
|
1398
1460
|
}
|
|
1461
|
+
|
|
1462
|
+
.drop-zone {
|
|
1463
|
+
border: 2px dashed #94a3b8;
|
|
1464
|
+
border-radius: 12px;
|
|
1465
|
+
padding: 2rem;
|
|
1466
|
+
text-align: center;
|
|
1467
|
+
color: #64748b;
|
|
1468
|
+
transition: border-color 0.2s, background 0.2s;
|
|
1469
|
+
cursor: pointer;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
.drop-zone.drag-over {
|
|
1473
|
+
border-color: #6366f1;
|
|
1474
|
+
background: rgba(99, 102, 241, 0.06);
|
|
1475
|
+
}
|
|
1399
1476
|
</style>
|
|
1400
1477
|
</head>
|
|
1401
1478
|
<body>
|
|
@@ -1416,69 +1493,70 @@ function buildAdminHtml(basePath) {
|
|
|
1416
1493
|
</nav>
|
|
1417
1494
|
|
|
1418
1495
|
<div class="container admin-shell mt-3 pb-4">
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
<
|
|
1424
|
-
|
|
1425
|
-
<
|
|
1426
|
-
|
|
1427
|
-
<div id="collection-list" class="list-group mb-3"></div>
|
|
1428
|
-
<button id="delete-collection" class="btn btn-outline-danger w-100" disabled>删除当前合集</button>
|
|
1429
|
-
|
|
1430
|
-
<hr>
|
|
1431
|
-
<h6 class="mb-2">合集描述</h6>
|
|
1432
|
-
<div class="input-group mb-3">
|
|
1433
|
-
<input id="collection-description" class="form-control" placeholder="为当前合集添加描述" disabled />
|
|
1434
|
-
<button id="save-description" class="btn btn-primary" disabled>保存</button>
|
|
1435
|
-
</div>
|
|
1436
|
-
|
|
1437
|
-
<hr>
|
|
1438
|
-
<h6 class="mb-2">快捷信息</h6>
|
|
1439
|
-
<div class="small text-muted">
|
|
1440
|
-
<div>管理链接:<code>${basePath}/admin</code></div>
|
|
1441
|
-
<div class="mt-1">随机访问:<code id="collection-random-url">-</code></div>
|
|
1496
|
+
<!-- View: folder list (main) -->
|
|
1497
|
+
<div id="view-folders">
|
|
1498
|
+
<div class="panel mb-3">
|
|
1499
|
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
1500
|
+
<h5 class="mb-0">合集管理</h5>
|
|
1501
|
+
<div class="d-flex gap-2">
|
|
1502
|
+
<input id="new-collection-name" class="form-control form-control-sm" style="width:180px" placeholder="新合集名称" />
|
|
1503
|
+
<button id="create-collection" class="btn btn-sm btn-primary">创建</button>
|
|
1442
1504
|
</div>
|
|
1443
1505
|
</div>
|
|
1506
|
+
<div id="folder-grid" class="folder-grid"></div>
|
|
1507
|
+
<div id="folders-empty" class="empty-tip mt-2">暂无合集,请先创建</div>
|
|
1444
1508
|
</div>
|
|
1509
|
+
</div>
|
|
1445
1510
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1511
|
+
<!-- View: collection detail -->
|
|
1512
|
+
<div id="view-detail" style="display:none">
|
|
1513
|
+
<div class="panel mb-3">
|
|
1514
|
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
1515
|
+
<div class="d-flex align-items-center gap-2">
|
|
1516
|
+
<button id="back-to-folders" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i> 返回</button>
|
|
1517
|
+
<h5 class="mb-0" id="detail-title"></h5>
|
|
1451
1518
|
</div>
|
|
1519
|
+
<div class="d-flex gap-2">
|
|
1520
|
+
<button id="refresh-resources" class="btn btn-sm btn-outline-primary"><i class="bi bi-arrow-clockwise"></i> 刷新缓存</button>
|
|
1521
|
+
<button id="delete-collection" class="btn btn-sm btn-outline-danger">删除合集</button>
|
|
1522
|
+
</div>
|
|
1523
|
+
</div>
|
|
1524
|
+
<div id="detail-path" class="desc-display" style="font-family:monospace;color:#a5b4fc;font-size:0.82rem"></div>
|
|
1525
|
+
<div class="desc-editor">
|
|
1526
|
+
<input id="desc-input" class="form-control form-control-sm" placeholder="为这个合集添加描述,例如:千恋万花的丛雨表情包" />
|
|
1527
|
+
<button id="save-desc" class="btn btn-sm btn-outline-primary">保存描述</button>
|
|
1528
|
+
</div>
|
|
1452
1529
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
<
|
|
1459
|
-
<div
|
|
1530
|
+
<div class="row g-3 mb-3">
|
|
1531
|
+
<div class="col-md-6">
|
|
1532
|
+
<div class="sub-card h-100">
|
|
1533
|
+
<div class="section-title mb-2">上传图片</div>
|
|
1534
|
+
<div id="drop-zone" class="drop-zone mb-2">
|
|
1535
|
+
<i class="bi bi-cloud-arrow-up" style="font-size:1.5rem"></i>
|
|
1536
|
+
<div>拖放图片到此处,或点击选择文件</div>
|
|
1537
|
+
<input id="upload-files" type="file" class="d-none" multiple accept="image/*" />
|
|
1460
1538
|
</div>
|
|
1461
1539
|
</div>
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1540
|
+
</div>
|
|
1541
|
+
<div class="col-md-6">
|
|
1542
|
+
<div class="sub-card h-100">
|
|
1543
|
+
<div class="section-title mb-2">添加外链</div>
|
|
1544
|
+
<textarea id="links-input" class="form-control mb-2" rows="3" placeholder="每行一个 http/https 链接"></textarea>
|
|
1545
|
+
<button id="add-links" class="btn btn-primary btn-sm w-100">添加外链</button>
|
|
1468
1546
|
</div>
|
|
1469
1547
|
</div>
|
|
1548
|
+
</div>
|
|
1470
1549
|
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1550
|
+
<div class="sub-card mb-3">
|
|
1551
|
+
<div class="section-title mb-2">本地图片</div>
|
|
1552
|
+
<div id="images-grid" class="image-grid"></div>
|
|
1553
|
+
<div id="images-empty" class="empty-tip mt-2">暂无本地图片</div>
|
|
1554
|
+
</div>
|
|
1476
1555
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
</div>
|
|
1556
|
+
<div class="sub-card">
|
|
1557
|
+
<div class="section-title mb-2">外链列表</div>
|
|
1558
|
+
<div id="links-list" class="list-group"></div>
|
|
1559
|
+
<div id="links-empty" class="empty-tip mt-2">暂无外链</div>
|
|
1482
1560
|
</div>
|
|
1483
1561
|
</div>
|
|
1484
1562
|
</div>
|
|
@@ -1491,7 +1569,6 @@ function buildAdminHtml(basePath) {
|
|
|
1491
1569
|
const state = {
|
|
1492
1570
|
collectionNames: [],
|
|
1493
1571
|
collections: [],
|
|
1494
|
-
endpoints: [],
|
|
1495
1572
|
selectedCollection: '',
|
|
1496
1573
|
images: [],
|
|
1497
1574
|
links: [],
|
|
@@ -1499,173 +1576,184 @@ function buildAdminHtml(basePath) {
|
|
|
1499
1576
|
|
|
1500
1577
|
const byId = (id) => document.getElementById(id)
|
|
1501
1578
|
|
|
1502
|
-
function showAlert(message, type
|
|
1503
|
-
|
|
1579
|
+
function showAlert(message, type) {
|
|
1580
|
+
type = type || 'info'
|
|
1581
|
+
var el = byId('admin-alert')
|
|
1504
1582
|
el.className = 'alert alert-' + type + ' mt-3'
|
|
1505
1583
|
el.textContent = message
|
|
1506
1584
|
el.classList.remove('d-none')
|
|
1507
|
-
setTimeout(()
|
|
1585
|
+
setTimeout(function() { el.classList.add('d-none') }, 2200)
|
|
1508
1586
|
}
|
|
1509
1587
|
|
|
1510
|
-
async function request(url, options
|
|
1511
|
-
|
|
1512
|
-
|
|
1588
|
+
async function request(url, options) {
|
|
1589
|
+
options = options || {}
|
|
1590
|
+
var headers = Object.assign({}, options.headers || {})
|
|
1513
1591
|
if (options.body && !headers['Content-Type']) {
|
|
1514
1592
|
headers['Content-Type'] = 'application/json'
|
|
1515
1593
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
try {
|
|
1520
|
-
data = await res.json()
|
|
1521
|
-
console.log('Response data:', data)
|
|
1522
|
-
} catch (e) {
|
|
1523
|
-
console.log('JSON parse error:', e)
|
|
1524
|
-
data = null
|
|
1525
|
-
}
|
|
1594
|
+
var res = await fetch(url, Object.assign({}, options, { headers: headers }))
|
|
1595
|
+
var data = null
|
|
1596
|
+
try { data = await res.json() } catch(e) { data = null }
|
|
1526
1597
|
if (!res.ok) {
|
|
1527
1598
|
throw new Error(data && data.error ? data.error : String(res.status) + ' ' + String(res.statusText))
|
|
1528
1599
|
}
|
|
1529
1600
|
return data
|
|
1530
1601
|
}
|
|
1531
1602
|
|
|
1603
|
+
function readFileAsDataURL(file) {
|
|
1604
|
+
return new Promise(function(resolve, reject) {
|
|
1605
|
+
var reader = new FileReader()
|
|
1606
|
+
reader.onload = function() { resolve(reader.result) }
|
|
1607
|
+
reader.onerror = function() { reject(new Error('读取文件失败')) }
|
|
1608
|
+
reader.readAsDataURL(file)
|
|
1609
|
+
})
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/* --- Folder list view --- */
|
|
1532
1613
|
async function refreshState() {
|
|
1533
|
-
|
|
1614
|
+
var data = await request(BASE_PATH + '/api/admin/state')
|
|
1534
1615
|
state.collectionNames = Array.isArray(data.collectionNames) ? data.collectionNames : []
|
|
1535
1616
|
state.collections = Array.isArray(data.collections) ? data.collections : []
|
|
1536
|
-
|
|
1617
|
+
renderFolderGrid()
|
|
1618
|
+
}
|
|
1537
1619
|
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1620
|
+
function renderFolderGrid() {
|
|
1621
|
+
var grid = byId('folder-grid')
|
|
1622
|
+
var empty = byId('folders-empty')
|
|
1623
|
+
grid.textContent = ''
|
|
1624
|
+
empty.style.display = state.collections.length ? 'none' : 'block'
|
|
1625
|
+
|
|
1626
|
+
state.collections.forEach(function(col) {
|
|
1627
|
+
var card = document.createElement('div')
|
|
1628
|
+
card.className = 'folder-card'
|
|
1629
|
+
card.addEventListener('click', function() { enterCollection(col.name) })
|
|
1630
|
+
|
|
1631
|
+
var icon = document.createElement('div')
|
|
1632
|
+
icon.className = 'folder-icon'
|
|
1633
|
+
icon.innerHTML = '<i class="bi bi-folder-fill"></i>'
|
|
1634
|
+
card.appendChild(icon)
|
|
1635
|
+
|
|
1636
|
+
var info = document.createElement('div')
|
|
1637
|
+
info.className = 'folder-info'
|
|
1638
|
+
var nameEl = document.createElement('div')
|
|
1639
|
+
nameEl.className = 'folder-name'
|
|
1640
|
+
nameEl.textContent = col.name
|
|
1641
|
+
info.appendChild(nameEl)
|
|
1642
|
+
if (col.description) {
|
|
1643
|
+
var descEl = document.createElement('div')
|
|
1644
|
+
descEl.className = 'folder-desc'
|
|
1645
|
+
descEl.textContent = col.description
|
|
1646
|
+
descEl.title = col.description
|
|
1647
|
+
info.appendChild(descEl)
|
|
1648
|
+
}
|
|
1649
|
+
var pathEl = document.createElement('div')
|
|
1650
|
+
pathEl.className = 'folder-path'
|
|
1651
|
+
pathEl.textContent = BASE_PATH + '/' + col.name
|
|
1652
|
+
info.appendChild(pathEl)
|
|
1653
|
+
var meta = document.createElement('div')
|
|
1654
|
+
meta.className = 'folder-meta'
|
|
1655
|
+
meta.textContent = '本地 ' + (col.localCount || 0) + ' / 外链 ' + (col.linkCount || 0)
|
|
1656
|
+
info.appendChild(meta)
|
|
1657
|
+
card.appendChild(info)
|
|
1658
|
+
grid.appendChild(card)
|
|
1659
|
+
})
|
|
1660
|
+
}
|
|
1541
1661
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1662
|
+
function enterCollection(name) {
|
|
1663
|
+
state.selectedCollection = name
|
|
1664
|
+
byId('view-folders').style.display = 'none'
|
|
1665
|
+
byId('view-detail').style.display = 'block'
|
|
1666
|
+
byId('detail-title').textContent = name
|
|
1667
|
+
byId('detail-path').textContent = '端点路径: ' + BASE_PATH + '/' + name
|
|
1668
|
+
var col = state.collections.find(function(c) { return c.name === name })
|
|
1669
|
+
byId('desc-input').value = (col && col.description) ? col.description : ''
|
|
1670
|
+
refreshCollectionResources()
|
|
1545
1671
|
}
|
|
1546
1672
|
|
|
1673
|
+
function backToFolders() {
|
|
1674
|
+
state.selectedCollection = ''
|
|
1675
|
+
byId('view-detail').style.display = 'none'
|
|
1676
|
+
byId('view-folders').style.display = 'block'
|
|
1677
|
+
refreshState()
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
/* --- Detail view --- */
|
|
1547
1681
|
async function refreshCollectionResources() {
|
|
1548
|
-
if (!state.selectedCollection) {
|
|
1549
|
-
|
|
1550
|
-
state.links = []
|
|
1551
|
-
renderImages()
|
|
1552
|
-
renderLinks()
|
|
1553
|
-
return
|
|
1554
|
-
}
|
|
1555
|
-
const data = await request(BASE_PATH + '/api/collections/' + encodeURIComponent(state.selectedCollection) + '/resources')
|
|
1682
|
+
if (!state.selectedCollection) { state.images = []; state.links = []; renderImages(); renderLinks(); return }
|
|
1683
|
+
var data = await request(BASE_PATH + '/api/collections/' + encodeURIComponent(state.selectedCollection) + '/resources')
|
|
1556
1684
|
state.images = Array.isArray(data.images) ? data.images : []
|
|
1557
1685
|
state.links = Array.isArray(data.links) ? data.links : []
|
|
1558
1686
|
renderImages()
|
|
1559
1687
|
renderLinks()
|
|
1560
1688
|
}
|
|
1561
1689
|
|
|
1562
|
-
function syncSelectedCollectionUi() {
|
|
1563
|
-
const selected = state.selectedCollection
|
|
1564
|
-
byId('selected-collection-badge').textContent = selected || '未选择'
|
|
1565
|
-
byId('delete-collection').disabled = !selected
|
|
1566
|
-
byId('upload-images').disabled = !selected
|
|
1567
|
-
byId('add-links').disabled = !selected
|
|
1568
|
-
byId('collection-random-url').textContent = selected ? BASE_PATH + '/' + selected : '-'
|
|
1569
|
-
byId('collection-description').disabled = !selected
|
|
1570
|
-
byId('save-description').disabled = !selected
|
|
1571
|
-
|
|
1572
|
-
const collectionInfo = state.collections.find((c) => c.name === selected)
|
|
1573
|
-
byId('collection-description').value = collectionInfo ? (collectionInfo.description || '') : ''
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
function renderCollectionList() {
|
|
1577
|
-
const list = byId('collection-list')
|
|
1578
|
-
list.textContent = ''
|
|
1579
|
-
if (!state.collectionNames.length) {
|
|
1580
|
-
const empty = document.createElement('div')
|
|
1581
|
-
empty.className = 'text-muted small'
|
|
1582
|
-
empty.textContent = '暂无合集'
|
|
1583
|
-
list.appendChild(empty)
|
|
1584
|
-
return
|
|
1585
|
-
}
|
|
1586
|
-
state.collectionNames.forEach((name) => {
|
|
1587
|
-
const button = document.createElement('button')
|
|
1588
|
-
button.type = 'button'
|
|
1589
|
-
button.className = 'list-group-item list-group-item-action collection-item' + (name === state.selectedCollection ? ' active' : '')
|
|
1590
|
-
button.textContent = name
|
|
1591
|
-
button.addEventListener('click', async () => {
|
|
1592
|
-
state.selectedCollection = name
|
|
1593
|
-
renderCollectionList()
|
|
1594
|
-
syncSelectedCollectionUi()
|
|
1595
|
-
await refreshCollectionResources()
|
|
1596
|
-
})
|
|
1597
|
-
list.appendChild(button)
|
|
1598
|
-
})
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
1690
|
function imagePreviewUrl(filename) {
|
|
1602
1691
|
return BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images/' + encodeURIComponent(filename)
|
|
1603
1692
|
}
|
|
1604
1693
|
|
|
1605
1694
|
function renderImages() {
|
|
1606
|
-
|
|
1607
|
-
|
|
1695
|
+
var grid = byId('images-grid')
|
|
1696
|
+
var empty = byId('images-empty')
|
|
1608
1697
|
grid.textContent = ''
|
|
1609
1698
|
empty.style.display = state.images.length ? 'none' : 'block'
|
|
1610
1699
|
|
|
1611
|
-
state.images.forEach((name)
|
|
1612
|
-
|
|
1700
|
+
state.images.forEach(function(name) {
|
|
1701
|
+
var card = document.createElement('div')
|
|
1613
1702
|
card.className = 'image-card'
|
|
1614
1703
|
|
|
1615
|
-
|
|
1704
|
+
var img = document.createElement('img')
|
|
1616
1705
|
img.src = imagePreviewUrl(name)
|
|
1617
1706
|
img.alt = name
|
|
1618
1707
|
card.appendChild(img)
|
|
1619
1708
|
|
|
1620
|
-
|
|
1709
|
+
var body = document.createElement('div')
|
|
1621
1710
|
body.className = 'p-2'
|
|
1622
|
-
|
|
1711
|
+
var title = document.createElement('div')
|
|
1623
1712
|
title.className = 'small text-truncate mb-2'
|
|
1624
1713
|
title.textContent = name
|
|
1625
1714
|
body.appendChild(title)
|
|
1626
1715
|
|
|
1627
|
-
|
|
1716
|
+
var row = document.createElement('div')
|
|
1628
1717
|
row.className = 'd-flex gap-1'
|
|
1629
1718
|
|
|
1630
|
-
|
|
1719
|
+
var moveSelect = document.createElement('select')
|
|
1631
1720
|
moveSelect.className = 'form-select form-select-sm'
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
moveSelect.appendChild(
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
opt.value =
|
|
1640
|
-
opt.textContent = target
|
|
1721
|
+
var others = state.collectionNames.filter(function(c) { return c !== state.selectedCollection })
|
|
1722
|
+
var ph = document.createElement('option')
|
|
1723
|
+
ph.value = ''
|
|
1724
|
+
ph.textContent = '移动到...'
|
|
1725
|
+
moveSelect.appendChild(ph)
|
|
1726
|
+
others.forEach(function(t) {
|
|
1727
|
+
var opt = document.createElement('option')
|
|
1728
|
+
opt.value = t; opt.textContent = t
|
|
1641
1729
|
moveSelect.appendChild(opt)
|
|
1642
1730
|
})
|
|
1643
1731
|
|
|
1644
|
-
|
|
1732
|
+
var moveBtn = document.createElement('button')
|
|
1645
1733
|
moveBtn.className = 'btn btn-sm btn-outline-primary'
|
|
1646
1734
|
moveBtn.textContent = '移动'
|
|
1647
|
-
moveBtn.disabled =
|
|
1648
|
-
moveBtn.addEventListener('click', async ()
|
|
1649
|
-
|
|
1650
|
-
if (!
|
|
1735
|
+
moveBtn.disabled = others.length === 0
|
|
1736
|
+
moveBtn.addEventListener('click', async function() {
|
|
1737
|
+
var tc = moveSelect.value
|
|
1738
|
+
if (!tc) return
|
|
1651
1739
|
await request(
|
|
1652
1740
|
BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images/' + encodeURIComponent(name) + '/move',
|
|
1653
|
-
{ method: 'POST', body: JSON.stringify({ targetCollection }) }
|
|
1741
|
+
{ method: 'POST', body: JSON.stringify({ targetCollection: tc }) }
|
|
1654
1742
|
)
|
|
1655
1743
|
showAlert('图片已移动', 'success')
|
|
1656
|
-
|
|
1744
|
+
refreshCollectionResources()
|
|
1657
1745
|
})
|
|
1658
1746
|
|
|
1659
|
-
|
|
1747
|
+
var delBtn = document.createElement('button')
|
|
1660
1748
|
delBtn.className = 'btn btn-sm btn-outline-danger'
|
|
1661
1749
|
delBtn.textContent = '删除'
|
|
1662
|
-
delBtn.addEventListener('click', async ()
|
|
1750
|
+
delBtn.addEventListener('click', async function() {
|
|
1663
1751
|
await request(
|
|
1664
1752
|
BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images/' + encodeURIComponent(name),
|
|
1665
1753
|
{ method: 'DELETE' }
|
|
1666
1754
|
)
|
|
1667
1755
|
showAlert('图片已删除', 'success')
|
|
1668
|
-
|
|
1756
|
+
refreshCollectionResources()
|
|
1669
1757
|
})
|
|
1670
1758
|
|
|
1671
1759
|
row.appendChild(moveSelect)
|
|
@@ -1678,40 +1766,40 @@ function buildAdminHtml(basePath) {
|
|
|
1678
1766
|
}
|
|
1679
1767
|
|
|
1680
1768
|
function renderLinks() {
|
|
1681
|
-
|
|
1682
|
-
|
|
1769
|
+
var wrap = byId('links-list')
|
|
1770
|
+
var empty = byId('links-empty')
|
|
1683
1771
|
wrap.textContent = ''
|
|
1684
1772
|
empty.style.display = state.links.length ? 'none' : 'block'
|
|
1685
|
-
state.links.forEach((link)
|
|
1686
|
-
|
|
1773
|
+
state.links.forEach(function(link) {
|
|
1774
|
+
var row = document.createElement('div')
|
|
1687
1775
|
row.className = 'list-group-item d-flex justify-content-between align-items-center gap-2'
|
|
1688
1776
|
|
|
1689
|
-
|
|
1777
|
+
var text = document.createElement('div')
|
|
1690
1778
|
text.className = 'code-url flex-grow-1'
|
|
1691
1779
|
text.textContent = link
|
|
1692
1780
|
|
|
1693
|
-
|
|
1781
|
+
var actions = document.createElement('div')
|
|
1694
1782
|
actions.className = 'd-flex gap-1'
|
|
1695
1783
|
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1784
|
+
var openEl = document.createElement('a')
|
|
1785
|
+
openEl.className = 'btn btn-sm btn-outline-primary'
|
|
1786
|
+
openEl.textContent = '查看'
|
|
1787
|
+
openEl.href = link
|
|
1788
|
+
openEl.target = '_blank'
|
|
1701
1789
|
|
|
1702
|
-
|
|
1790
|
+
var del = document.createElement('button')
|
|
1703
1791
|
del.className = 'btn btn-sm btn-outline-danger'
|
|
1704
1792
|
del.textContent = '删除'
|
|
1705
|
-
del.addEventListener('click', async ()
|
|
1793
|
+
del.addEventListener('click', async function() {
|
|
1706
1794
|
await request(
|
|
1707
1795
|
BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/links',
|
|
1708
|
-
{ method: 'DELETE', body: JSON.stringify({ link }) }
|
|
1796
|
+
{ method: 'DELETE', body: JSON.stringify({ link: link }) }
|
|
1709
1797
|
)
|
|
1710
1798
|
showAlert('外链已删除', 'success')
|
|
1711
|
-
|
|
1799
|
+
refreshCollectionResources()
|
|
1712
1800
|
})
|
|
1713
1801
|
|
|
1714
|
-
actions.appendChild(
|
|
1802
|
+
actions.appendChild(openEl)
|
|
1715
1803
|
actions.appendChild(del)
|
|
1716
1804
|
row.appendChild(text)
|
|
1717
1805
|
row.appendChild(actions)
|
|
@@ -1719,87 +1807,93 @@ function buildAdminHtml(basePath) {
|
|
|
1719
1807
|
})
|
|
1720
1808
|
}
|
|
1721
1809
|
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1810
|
+
/* --- Upload with drag/drop --- */
|
|
1811
|
+
async function uploadFiles(files) {
|
|
1812
|
+
if (!state.selectedCollection || !files || !files.length) return
|
|
1813
|
+
var images = []
|
|
1814
|
+
for (var i = 0; i < files.length; i++) {
|
|
1815
|
+
var b64 = await readFileAsDataURL(files[i])
|
|
1816
|
+
images.push({ base64: b64, originalName: files[i].name })
|
|
1817
|
+
}
|
|
1818
|
+
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images', {
|
|
1819
|
+
method: 'POST',
|
|
1820
|
+
body: JSON.stringify({ images: images }),
|
|
1728
1821
|
})
|
|
1822
|
+
showAlert('图片上传成功', 'success')
|
|
1823
|
+
refreshCollectionResources()
|
|
1729
1824
|
}
|
|
1730
1825
|
|
|
1731
|
-
byId('
|
|
1732
|
-
|
|
1826
|
+
var dropZone = byId('drop-zone')
|
|
1827
|
+
var fileInput = byId('upload-files')
|
|
1828
|
+
|
|
1829
|
+
dropZone.addEventListener('click', function() { fileInput.click() })
|
|
1830
|
+
fileInput.addEventListener('change', function() { uploadFiles(fileInput.files); fileInput.value = '' })
|
|
1831
|
+
|
|
1832
|
+
dropZone.addEventListener('dragover', function(e) { e.preventDefault(); dropZone.classList.add('drag-over') })
|
|
1833
|
+
dropZone.addEventListener('dragleave', function(e) { e.preventDefault(); dropZone.classList.remove('drag-over') })
|
|
1834
|
+
dropZone.addEventListener('drop', function(e) {
|
|
1835
|
+
e.preventDefault()
|
|
1836
|
+
dropZone.classList.remove('drag-over')
|
|
1837
|
+
if (e.dataTransfer && e.dataTransfer.files) uploadFiles(e.dataTransfer.files)
|
|
1838
|
+
})
|
|
1839
|
+
|
|
1840
|
+
/* --- Buttons --- */
|
|
1841
|
+
byId('create-collection').addEventListener('click', async function() {
|
|
1842
|
+
var name = byId('new-collection-name').value.trim()
|
|
1733
1843
|
if (!name) return
|
|
1734
1844
|
await request(BASE_PATH + '/api/admin/collections', {
|
|
1735
1845
|
method: 'POST',
|
|
1736
|
-
body: JSON.stringify({ name }),
|
|
1846
|
+
body: JSON.stringify({ name: name }),
|
|
1737
1847
|
})
|
|
1738
1848
|
byId('new-collection-name').value = ''
|
|
1739
1849
|
showAlert('合集创建成功', 'success')
|
|
1740
|
-
|
|
1850
|
+
refreshState()
|
|
1741
1851
|
})
|
|
1742
1852
|
|
|
1743
|
-
byId('delete-collection').addEventListener('click', async ()
|
|
1853
|
+
byId('delete-collection').addEventListener('click', async function() {
|
|
1744
1854
|
if (!state.selectedCollection) return
|
|
1855
|
+
if (!confirm('确定删除合集 ' + state.selectedCollection + ' ?')) return
|
|
1745
1856
|
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection), {
|
|
1746
1857
|
method: 'DELETE',
|
|
1747
1858
|
})
|
|
1748
1859
|
showAlert('合集已删除', 'success')
|
|
1749
|
-
|
|
1860
|
+
backToFolders()
|
|
1750
1861
|
})
|
|
1751
1862
|
|
|
1752
|
-
byId('
|
|
1863
|
+
byId('back-to-folders').addEventListener('click', backToFolders)
|
|
1864
|
+
|
|
1865
|
+
byId('save-desc').addEventListener('click', async function() {
|
|
1753
1866
|
if (!state.selectedCollection) return
|
|
1754
|
-
|
|
1867
|
+
var desc = byId('desc-input').value.trim()
|
|
1755
1868
|
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/description', {
|
|
1756
1869
|
method: 'PATCH',
|
|
1757
|
-
body: JSON.stringify({ description }),
|
|
1870
|
+
body: JSON.stringify({ description: desc }),
|
|
1758
1871
|
})
|
|
1872
|
+
var col = state.collections.find(function(c) { return c.name === state.selectedCollection })
|
|
1873
|
+
if (col) col.description = desc
|
|
1759
1874
|
showAlert('描述已保存', 'success')
|
|
1760
|
-
await refreshState()
|
|
1761
1875
|
})
|
|
1762
1876
|
|
|
1763
|
-
byId('
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
if (!files || !files.length) return
|
|
1767
|
-
const images = []
|
|
1768
|
-
for (const file of files) {
|
|
1769
|
-
const base64 = await readFileAsDataURL(file)
|
|
1770
|
-
images.push({ base64, originalName: file.name })
|
|
1771
|
-
}
|
|
1772
|
-
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images', {
|
|
1773
|
-
method: 'POST',
|
|
1774
|
-
body: JSON.stringify({ images }),
|
|
1775
|
-
})
|
|
1776
|
-
byId('upload-files').value = ''
|
|
1777
|
-
showAlert('图片上传成功', 'success')
|
|
1778
|
-
await refreshCollectionResources()
|
|
1779
|
-
await refreshState()
|
|
1877
|
+
byId('refresh-resources').addEventListener('click', function() {
|
|
1878
|
+
refreshCollectionResources()
|
|
1879
|
+
showAlert('已刷新', 'success')
|
|
1780
1880
|
})
|
|
1781
1881
|
|
|
1782
|
-
byId('add-links').addEventListener('click', async ()
|
|
1882
|
+
byId('add-links').addEventListener('click', async function() {
|
|
1783
1883
|
if (!state.selectedCollection) return
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
if (!links.length) return
|
|
1884
|
+
var text = byId('links-input').value
|
|
1885
|
+
var lines = text.split(/\\r?\\n/g).map(function(l) { return l.trim() }).filter(Boolean)
|
|
1886
|
+
if (!lines.length) return
|
|
1788
1887
|
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/links', {
|
|
1789
1888
|
method: 'POST',
|
|
1790
|
-
body: JSON.stringify({ links }),
|
|
1889
|
+
body: JSON.stringify({ links: lines }),
|
|
1791
1890
|
})
|
|
1792
1891
|
byId('links-input').value = ''
|
|
1793
1892
|
showAlert('外链添加成功', 'success')
|
|
1794
|
-
|
|
1795
|
-
await refreshState()
|
|
1796
|
-
})
|
|
1797
|
-
|
|
1798
|
-
byId('upload-files').addEventListener('change', () => {
|
|
1799
|
-
byId('upload-images').disabled = !state.selectedCollection
|
|
1893
|
+
refreshCollectionResources()
|
|
1800
1894
|
})
|
|
1801
1895
|
|
|
1802
|
-
refreshState().catch((error)
|
|
1896
|
+
refreshState().catch(function(error) {
|
|
1803
1897
|
showAlert(error instanceof Error ? error.message : String(error), 'danger')
|
|
1804
1898
|
})
|
|
1805
1899
|
</script>
|
package/package.json
CHANGED