koishi-plugin-memesluna 0.2.6 → 0.2.7
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 +268 -238
- 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,69 @@ 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;
|
|
1356
1389
|
}
|
|
1357
1390
|
|
|
1358
1391
|
.image-grid {
|
|
@@ -1376,10 +1409,6 @@ function buildAdminHtml(basePath) {
|
|
|
1376
1409
|
background: #f1f5f9;
|
|
1377
1410
|
}
|
|
1378
1411
|
|
|
1379
|
-
.sub-card {
|
|
1380
|
-
padding: 0.9rem;
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
1412
|
.code-url {
|
|
1384
1413
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
1385
1414
|
font-size: 0.8rem;
|
|
@@ -1396,6 +1425,21 @@ function buildAdminHtml(basePath) {
|
|
|
1396
1425
|
color: #64748b;
|
|
1397
1426
|
font-size: 0.9rem;
|
|
1398
1427
|
}
|
|
1428
|
+
|
|
1429
|
+
.drop-zone {
|
|
1430
|
+
border: 2px dashed #94a3b8;
|
|
1431
|
+
border-radius: 12px;
|
|
1432
|
+
padding: 2rem;
|
|
1433
|
+
text-align: center;
|
|
1434
|
+
color: #64748b;
|
|
1435
|
+
transition: border-color 0.2s, background 0.2s;
|
|
1436
|
+
cursor: pointer;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
.drop-zone.drag-over {
|
|
1440
|
+
border-color: #6366f1;
|
|
1441
|
+
background: rgba(99, 102, 241, 0.06);
|
|
1442
|
+
}
|
|
1399
1443
|
</style>
|
|
1400
1444
|
</head>
|
|
1401
1445
|
<body>
|
|
@@ -1416,69 +1460,65 @@ function buildAdminHtml(basePath) {
|
|
|
1416
1460
|
</nav>
|
|
1417
1461
|
|
|
1418
1462
|
<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>
|
|
1463
|
+
<!-- View: folder list (main) -->
|
|
1464
|
+
<div id="view-folders">
|
|
1465
|
+
<div class="panel mb-3">
|
|
1466
|
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
1467
|
+
<h5 class="mb-0">合集管理</h5>
|
|
1468
|
+
<div class="d-flex gap-2">
|
|
1469
|
+
<input id="new-collection-name" class="form-control form-control-sm" style="width:180px" placeholder="新合集名称" />
|
|
1470
|
+
<button id="create-collection" class="btn btn-sm btn-primary">创建</button>
|
|
1442
1471
|
</div>
|
|
1443
1472
|
</div>
|
|
1473
|
+
<div id="folder-grid" class="folder-grid"></div>
|
|
1474
|
+
<div id="folders-empty" class="empty-tip mt-2">暂无合集,请先创建</div>
|
|
1444
1475
|
</div>
|
|
1476
|
+
</div>
|
|
1445
1477
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1478
|
+
<!-- View: collection detail -->
|
|
1479
|
+
<div id="view-detail" style="display:none">
|
|
1480
|
+
<div class="panel mb-3">
|
|
1481
|
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
1482
|
+
<div class="d-flex align-items-center gap-2">
|
|
1483
|
+
<button id="back-to-folders" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i> 返回</button>
|
|
1484
|
+
<h5 class="mb-0" id="detail-title"></h5>
|
|
1485
|
+
</div>
|
|
1486
|
+
<div class="d-flex gap-2">
|
|
1487
|
+
<button id="refresh-resources" class="btn btn-sm btn-outline-primary"><i class="bi bi-arrow-clockwise"></i> 刷新缓存</button>
|
|
1488
|
+
<button id="delete-collection" class="btn btn-sm btn-outline-danger">删除合集</button>
|
|
1451
1489
|
</div>
|
|
1490
|
+
</div>
|
|
1452
1491
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
<
|
|
1459
|
-
<div
|
|
1492
|
+
<div class="row g-3 mb-3">
|
|
1493
|
+
<div class="col-md-6">
|
|
1494
|
+
<div class="sub-card h-100">
|
|
1495
|
+
<div class="section-title mb-2">上传图片</div>
|
|
1496
|
+
<div id="drop-zone" class="drop-zone mb-2">
|
|
1497
|
+
<i class="bi bi-cloud-arrow-up" style="font-size:1.5rem"></i>
|
|
1498
|
+
<div>拖放图片到此处,或点击选择文件</div>
|
|
1499
|
+
<input id="upload-files" type="file" class="d-none" multiple accept="image/*" />
|
|
1460
1500
|
</div>
|
|
1461
1501
|
</div>
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1502
|
+
</div>
|
|
1503
|
+
<div class="col-md-6">
|
|
1504
|
+
<div class="sub-card h-100">
|
|
1505
|
+
<div class="section-title mb-2">添加外链</div>
|
|
1506
|
+
<textarea id="links-input" class="form-control mb-2" rows="3" placeholder="每行一个 http/https 链接"></textarea>
|
|
1507
|
+
<button id="add-links" class="btn btn-primary btn-sm w-100">添加外链</button>
|
|
1468
1508
|
</div>
|
|
1469
1509
|
</div>
|
|
1510
|
+
</div>
|
|
1470
1511
|
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1512
|
+
<div class="sub-card mb-3">
|
|
1513
|
+
<div class="section-title mb-2">本地图片</div>
|
|
1514
|
+
<div id="images-grid" class="image-grid"></div>
|
|
1515
|
+
<div id="images-empty" class="empty-tip mt-2">暂无本地图片</div>
|
|
1516
|
+
</div>
|
|
1476
1517
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
</div>
|
|
1518
|
+
<div class="sub-card">
|
|
1519
|
+
<div class="section-title mb-2">外链列表</div>
|
|
1520
|
+
<div id="links-list" class="list-group"></div>
|
|
1521
|
+
<div id="links-empty" class="empty-tip mt-2">暂无外链</div>
|
|
1482
1522
|
</div>
|
|
1483
1523
|
</div>
|
|
1484
1524
|
</div>
|
|
@@ -1491,7 +1531,6 @@ function buildAdminHtml(basePath) {
|
|
|
1491
1531
|
const state = {
|
|
1492
1532
|
collectionNames: [],
|
|
1493
1533
|
collections: [],
|
|
1494
|
-
endpoints: [],
|
|
1495
1534
|
selectedCollection: '',
|
|
1496
1535
|
images: [],
|
|
1497
1536
|
links: [],
|
|
@@ -1499,173 +1538,170 @@ function buildAdminHtml(basePath) {
|
|
|
1499
1538
|
|
|
1500
1539
|
const byId = (id) => document.getElementById(id)
|
|
1501
1540
|
|
|
1502
|
-
function showAlert(message, type
|
|
1503
|
-
|
|
1541
|
+
function showAlert(message, type) {
|
|
1542
|
+
type = type || 'info'
|
|
1543
|
+
var el = byId('admin-alert')
|
|
1504
1544
|
el.className = 'alert alert-' + type + ' mt-3'
|
|
1505
1545
|
el.textContent = message
|
|
1506
1546
|
el.classList.remove('d-none')
|
|
1507
|
-
setTimeout(()
|
|
1547
|
+
setTimeout(function() { el.classList.add('d-none') }, 2200)
|
|
1508
1548
|
}
|
|
1509
1549
|
|
|
1510
|
-
async function request(url, options
|
|
1511
|
-
|
|
1512
|
-
|
|
1550
|
+
async function request(url, options) {
|
|
1551
|
+
options = options || {}
|
|
1552
|
+
var headers = Object.assign({}, options.headers || {})
|
|
1513
1553
|
if (options.body && !headers['Content-Type']) {
|
|
1514
1554
|
headers['Content-Type'] = 'application/json'
|
|
1515
1555
|
}
|
|
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
|
-
}
|
|
1556
|
+
var res = await fetch(url, Object.assign({}, options, { headers: headers }))
|
|
1557
|
+
var data = null
|
|
1558
|
+
try { data = await res.json() } catch(e) { data = null }
|
|
1526
1559
|
if (!res.ok) {
|
|
1527
1560
|
throw new Error(data && data.error ? data.error : String(res.status) + ' ' + String(res.statusText))
|
|
1528
1561
|
}
|
|
1529
1562
|
return data
|
|
1530
1563
|
}
|
|
1531
1564
|
|
|
1565
|
+
function readFileAsDataURL(file) {
|
|
1566
|
+
return new Promise(function(resolve, reject) {
|
|
1567
|
+
var reader = new FileReader()
|
|
1568
|
+
reader.onload = function() { resolve(reader.result) }
|
|
1569
|
+
reader.onerror = function() { reject(new Error('读取文件失败')) }
|
|
1570
|
+
reader.readAsDataURL(file)
|
|
1571
|
+
})
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/* --- Folder list view --- */
|
|
1532
1575
|
async function refreshState() {
|
|
1533
|
-
|
|
1576
|
+
var data = await request(BASE_PATH + '/api/admin/state')
|
|
1534
1577
|
state.collectionNames = Array.isArray(data.collectionNames) ? data.collectionNames : []
|
|
1535
1578
|
state.collections = Array.isArray(data.collections) ? data.collections : []
|
|
1536
|
-
|
|
1579
|
+
renderFolderGrid()
|
|
1580
|
+
}
|
|
1537
1581
|
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1582
|
+
function renderFolderGrid() {
|
|
1583
|
+
var grid = byId('folder-grid')
|
|
1584
|
+
var empty = byId('folders-empty')
|
|
1585
|
+
grid.textContent = ''
|
|
1586
|
+
empty.style.display = state.collections.length ? 'none' : 'block'
|
|
1587
|
+
|
|
1588
|
+
state.collections.forEach(function(col) {
|
|
1589
|
+
var card = document.createElement('div')
|
|
1590
|
+
card.className = 'folder-card'
|
|
1591
|
+
card.addEventListener('click', function() { enterCollection(col.name) })
|
|
1592
|
+
|
|
1593
|
+
var icon = document.createElement('div')
|
|
1594
|
+
icon.className = 'folder-icon'
|
|
1595
|
+
icon.innerHTML = '<i class="bi bi-folder-fill"></i>'
|
|
1596
|
+
card.appendChild(icon)
|
|
1597
|
+
|
|
1598
|
+
var info = document.createElement('div')
|
|
1599
|
+
info.className = 'folder-info'
|
|
1600
|
+
var nameEl = document.createElement('div')
|
|
1601
|
+
nameEl.className = 'folder-name'
|
|
1602
|
+
nameEl.textContent = col.name
|
|
1603
|
+
info.appendChild(nameEl)
|
|
1604
|
+
var meta = document.createElement('div')
|
|
1605
|
+
meta.className = 'folder-meta'
|
|
1606
|
+
meta.textContent = '本地 ' + (col.localCount || 0) + ' / 外链 ' + (col.linkCount || 0)
|
|
1607
|
+
info.appendChild(meta)
|
|
1608
|
+
card.appendChild(info)
|
|
1609
|
+
grid.appendChild(card)
|
|
1610
|
+
})
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function enterCollection(name) {
|
|
1614
|
+
state.selectedCollection = name
|
|
1615
|
+
byId('view-folders').style.display = 'none'
|
|
1616
|
+
byId('view-detail').style.display = 'block'
|
|
1617
|
+
byId('detail-title').textContent = name
|
|
1618
|
+
refreshCollectionResources()
|
|
1619
|
+
}
|
|
1541
1620
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1621
|
+
function backToFolders() {
|
|
1622
|
+
state.selectedCollection = ''
|
|
1623
|
+
byId('view-detail').style.display = 'none'
|
|
1624
|
+
byId('view-folders').style.display = 'block'
|
|
1625
|
+
refreshState()
|
|
1545
1626
|
}
|
|
1546
1627
|
|
|
1628
|
+
/* --- Detail view --- */
|
|
1547
1629
|
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')
|
|
1630
|
+
if (!state.selectedCollection) { state.images = []; state.links = []; renderImages(); renderLinks(); return }
|
|
1631
|
+
var data = await request(BASE_PATH + '/api/collections/' + encodeURIComponent(state.selectedCollection) + '/resources')
|
|
1556
1632
|
state.images = Array.isArray(data.images) ? data.images : []
|
|
1557
1633
|
state.links = Array.isArray(data.links) ? data.links : []
|
|
1558
1634
|
renderImages()
|
|
1559
1635
|
renderLinks()
|
|
1560
1636
|
}
|
|
1561
1637
|
|
|
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
1638
|
function imagePreviewUrl(filename) {
|
|
1602
1639
|
return BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images/' + encodeURIComponent(filename)
|
|
1603
1640
|
}
|
|
1604
1641
|
|
|
1605
1642
|
function renderImages() {
|
|
1606
|
-
|
|
1607
|
-
|
|
1643
|
+
var grid = byId('images-grid')
|
|
1644
|
+
var empty = byId('images-empty')
|
|
1608
1645
|
grid.textContent = ''
|
|
1609
1646
|
empty.style.display = state.images.length ? 'none' : 'block'
|
|
1610
1647
|
|
|
1611
|
-
state.images.forEach((name)
|
|
1612
|
-
|
|
1648
|
+
state.images.forEach(function(name) {
|
|
1649
|
+
var card = document.createElement('div')
|
|
1613
1650
|
card.className = 'image-card'
|
|
1614
1651
|
|
|
1615
|
-
|
|
1652
|
+
var img = document.createElement('img')
|
|
1616
1653
|
img.src = imagePreviewUrl(name)
|
|
1617
1654
|
img.alt = name
|
|
1618
1655
|
card.appendChild(img)
|
|
1619
1656
|
|
|
1620
|
-
|
|
1657
|
+
var body = document.createElement('div')
|
|
1621
1658
|
body.className = 'p-2'
|
|
1622
|
-
|
|
1659
|
+
var title = document.createElement('div')
|
|
1623
1660
|
title.className = 'small text-truncate mb-2'
|
|
1624
1661
|
title.textContent = name
|
|
1625
1662
|
body.appendChild(title)
|
|
1626
1663
|
|
|
1627
|
-
|
|
1664
|
+
var row = document.createElement('div')
|
|
1628
1665
|
row.className = 'd-flex gap-1'
|
|
1629
1666
|
|
|
1630
|
-
|
|
1667
|
+
var moveSelect = document.createElement('select')
|
|
1631
1668
|
moveSelect.className = 'form-select form-select-sm'
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
moveSelect.appendChild(
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
opt.value =
|
|
1640
|
-
opt.textContent = target
|
|
1669
|
+
var others = state.collectionNames.filter(function(c) { return c !== state.selectedCollection })
|
|
1670
|
+
var ph = document.createElement('option')
|
|
1671
|
+
ph.value = ''
|
|
1672
|
+
ph.textContent = '移动到...'
|
|
1673
|
+
moveSelect.appendChild(ph)
|
|
1674
|
+
others.forEach(function(t) {
|
|
1675
|
+
var opt = document.createElement('option')
|
|
1676
|
+
opt.value = t; opt.textContent = t
|
|
1641
1677
|
moveSelect.appendChild(opt)
|
|
1642
1678
|
})
|
|
1643
1679
|
|
|
1644
|
-
|
|
1680
|
+
var moveBtn = document.createElement('button')
|
|
1645
1681
|
moveBtn.className = 'btn btn-sm btn-outline-primary'
|
|
1646
1682
|
moveBtn.textContent = '移动'
|
|
1647
|
-
moveBtn.disabled =
|
|
1648
|
-
moveBtn.addEventListener('click', async ()
|
|
1649
|
-
|
|
1650
|
-
if (!
|
|
1683
|
+
moveBtn.disabled = others.length === 0
|
|
1684
|
+
moveBtn.addEventListener('click', async function() {
|
|
1685
|
+
var tc = moveSelect.value
|
|
1686
|
+
if (!tc) return
|
|
1651
1687
|
await request(
|
|
1652
1688
|
BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images/' + encodeURIComponent(name) + '/move',
|
|
1653
|
-
{ method: 'POST', body: JSON.stringify({ targetCollection }) }
|
|
1689
|
+
{ method: 'POST', body: JSON.stringify({ targetCollection: tc }) }
|
|
1654
1690
|
)
|
|
1655
1691
|
showAlert('图片已移动', 'success')
|
|
1656
|
-
|
|
1692
|
+
refreshCollectionResources()
|
|
1657
1693
|
})
|
|
1658
1694
|
|
|
1659
|
-
|
|
1695
|
+
var delBtn = document.createElement('button')
|
|
1660
1696
|
delBtn.className = 'btn btn-sm btn-outline-danger'
|
|
1661
1697
|
delBtn.textContent = '删除'
|
|
1662
|
-
delBtn.addEventListener('click', async ()
|
|
1698
|
+
delBtn.addEventListener('click', async function() {
|
|
1663
1699
|
await request(
|
|
1664
1700
|
BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images/' + encodeURIComponent(name),
|
|
1665
1701
|
{ method: 'DELETE' }
|
|
1666
1702
|
)
|
|
1667
1703
|
showAlert('图片已删除', 'success')
|
|
1668
|
-
|
|
1704
|
+
refreshCollectionResources()
|
|
1669
1705
|
})
|
|
1670
1706
|
|
|
1671
1707
|
row.appendChild(moveSelect)
|
|
@@ -1678,40 +1714,40 @@ function buildAdminHtml(basePath) {
|
|
|
1678
1714
|
}
|
|
1679
1715
|
|
|
1680
1716
|
function renderLinks() {
|
|
1681
|
-
|
|
1682
|
-
|
|
1717
|
+
var wrap = byId('links-list')
|
|
1718
|
+
var empty = byId('links-empty')
|
|
1683
1719
|
wrap.textContent = ''
|
|
1684
1720
|
empty.style.display = state.links.length ? 'none' : 'block'
|
|
1685
|
-
state.links.forEach((link)
|
|
1686
|
-
|
|
1721
|
+
state.links.forEach(function(link) {
|
|
1722
|
+
var row = document.createElement('div')
|
|
1687
1723
|
row.className = 'list-group-item d-flex justify-content-between align-items-center gap-2'
|
|
1688
1724
|
|
|
1689
|
-
|
|
1725
|
+
var text = document.createElement('div')
|
|
1690
1726
|
text.className = 'code-url flex-grow-1'
|
|
1691
1727
|
text.textContent = link
|
|
1692
1728
|
|
|
1693
|
-
|
|
1729
|
+
var actions = document.createElement('div')
|
|
1694
1730
|
actions.className = 'd-flex gap-1'
|
|
1695
1731
|
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1732
|
+
var openEl = document.createElement('a')
|
|
1733
|
+
openEl.className = 'btn btn-sm btn-outline-primary'
|
|
1734
|
+
openEl.textContent = '查看'
|
|
1735
|
+
openEl.href = link
|
|
1736
|
+
openEl.target = '_blank'
|
|
1701
1737
|
|
|
1702
|
-
|
|
1738
|
+
var del = document.createElement('button')
|
|
1703
1739
|
del.className = 'btn btn-sm btn-outline-danger'
|
|
1704
1740
|
del.textContent = '删除'
|
|
1705
|
-
del.addEventListener('click', async ()
|
|
1741
|
+
del.addEventListener('click', async function() {
|
|
1706
1742
|
await request(
|
|
1707
1743
|
BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/links',
|
|
1708
|
-
{ method: 'DELETE', body: JSON.stringify({ link }) }
|
|
1744
|
+
{ method: 'DELETE', body: JSON.stringify({ link: link }) }
|
|
1709
1745
|
)
|
|
1710
1746
|
showAlert('外链已删除', 'success')
|
|
1711
|
-
|
|
1747
|
+
refreshCollectionResources()
|
|
1712
1748
|
})
|
|
1713
1749
|
|
|
1714
|
-
actions.appendChild(
|
|
1750
|
+
actions.appendChild(openEl)
|
|
1715
1751
|
actions.appendChild(del)
|
|
1716
1752
|
row.appendChild(text)
|
|
1717
1753
|
row.appendChild(actions)
|
|
@@ -1719,87 +1755,81 @@ function buildAdminHtml(basePath) {
|
|
|
1719
1755
|
})
|
|
1720
1756
|
}
|
|
1721
1757
|
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1758
|
+
/* --- Upload with drag/drop --- */
|
|
1759
|
+
async function uploadFiles(files) {
|
|
1760
|
+
if (!state.selectedCollection || !files || !files.length) return
|
|
1761
|
+
var images = []
|
|
1762
|
+
for (var i = 0; i < files.length; i++) {
|
|
1763
|
+
var b64 = await readFileAsDataURL(files[i])
|
|
1764
|
+
images.push({ base64: b64, originalName: files[i].name })
|
|
1765
|
+
}
|
|
1766
|
+
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images', {
|
|
1767
|
+
method: 'POST',
|
|
1768
|
+
body: JSON.stringify({ images: images }),
|
|
1728
1769
|
})
|
|
1770
|
+
showAlert('图片上传成功', 'success')
|
|
1771
|
+
refreshCollectionResources()
|
|
1729
1772
|
}
|
|
1730
1773
|
|
|
1731
|
-
byId('
|
|
1732
|
-
|
|
1774
|
+
var dropZone = byId('drop-zone')
|
|
1775
|
+
var fileInput = byId('upload-files')
|
|
1776
|
+
|
|
1777
|
+
dropZone.addEventListener('click', function() { fileInput.click() })
|
|
1778
|
+
fileInput.addEventListener('change', function() { uploadFiles(fileInput.files); fileInput.value = '' })
|
|
1779
|
+
|
|
1780
|
+
dropZone.addEventListener('dragover', function(e) { e.preventDefault(); dropZone.classList.add('drag-over') })
|
|
1781
|
+
dropZone.addEventListener('dragleave', function(e) { e.preventDefault(); dropZone.classList.remove('drag-over') })
|
|
1782
|
+
dropZone.addEventListener('drop', function(e) {
|
|
1783
|
+
e.preventDefault()
|
|
1784
|
+
dropZone.classList.remove('drag-over')
|
|
1785
|
+
if (e.dataTransfer && e.dataTransfer.files) uploadFiles(e.dataTransfer.files)
|
|
1786
|
+
})
|
|
1787
|
+
|
|
1788
|
+
/* --- Buttons --- */
|
|
1789
|
+
byId('create-collection').addEventListener('click', async function() {
|
|
1790
|
+
var name = byId('new-collection-name').value.trim()
|
|
1733
1791
|
if (!name) return
|
|
1734
1792
|
await request(BASE_PATH + '/api/admin/collections', {
|
|
1735
1793
|
method: 'POST',
|
|
1736
|
-
body: JSON.stringify({ name }),
|
|
1794
|
+
body: JSON.stringify({ name: name }),
|
|
1737
1795
|
})
|
|
1738
1796
|
byId('new-collection-name').value = ''
|
|
1739
1797
|
showAlert('合集创建成功', 'success')
|
|
1740
|
-
|
|
1798
|
+
refreshState()
|
|
1741
1799
|
})
|
|
1742
1800
|
|
|
1743
|
-
byId('delete-collection').addEventListener('click', async ()
|
|
1801
|
+
byId('delete-collection').addEventListener('click', async function() {
|
|
1744
1802
|
if (!state.selectedCollection) return
|
|
1803
|
+
if (!confirm('确定删除合集 ' + state.selectedCollection + ' ?')) return
|
|
1745
1804
|
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection), {
|
|
1746
1805
|
method: 'DELETE',
|
|
1747
1806
|
})
|
|
1748
1807
|
showAlert('合集已删除', 'success')
|
|
1749
|
-
|
|
1808
|
+
backToFolders()
|
|
1750
1809
|
})
|
|
1751
1810
|
|
|
1752
|
-
byId('
|
|
1753
|
-
if (!state.selectedCollection) return
|
|
1754
|
-
const description = byId('collection-description').value.trim()
|
|
1755
|
-
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/description', {
|
|
1756
|
-
method: 'PATCH',
|
|
1757
|
-
body: JSON.stringify({ description }),
|
|
1758
|
-
})
|
|
1759
|
-
showAlert('描述已保存', 'success')
|
|
1760
|
-
await refreshState()
|
|
1761
|
-
})
|
|
1811
|
+
byId('back-to-folders').addEventListener('click', backToFolders)
|
|
1762
1812
|
|
|
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()
|
|
1813
|
+
byId('refresh-resources').addEventListener('click', function() {
|
|
1814
|
+
refreshCollectionResources()
|
|
1815
|
+
showAlert('已刷新', 'success')
|
|
1780
1816
|
})
|
|
1781
1817
|
|
|
1782
|
-
byId('add-links').addEventListener('click', async ()
|
|
1818
|
+
byId('add-links').addEventListener('click', async function() {
|
|
1783
1819
|
if (!state.selectedCollection) return
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
if (!links.length) return
|
|
1820
|
+
var text = byId('links-input').value
|
|
1821
|
+
var lines = text.split(/\\r?\\n/g).map(function(l) { return l.trim() }).filter(Boolean)
|
|
1822
|
+
if (!lines.length) return
|
|
1788
1823
|
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/links', {
|
|
1789
1824
|
method: 'POST',
|
|
1790
|
-
body: JSON.stringify({ links }),
|
|
1825
|
+
body: JSON.stringify({ links: lines }),
|
|
1791
1826
|
})
|
|
1792
1827
|
byId('links-input').value = ''
|
|
1793
1828
|
showAlert('外链添加成功', 'success')
|
|
1794
|
-
|
|
1795
|
-
await refreshState()
|
|
1796
|
-
})
|
|
1797
|
-
|
|
1798
|
-
byId('upload-files').addEventListener('change', () => {
|
|
1799
|
-
byId('upload-images').disabled = !state.selectedCollection
|
|
1829
|
+
refreshCollectionResources()
|
|
1800
1830
|
})
|
|
1801
1831
|
|
|
1802
|
-
refreshState().catch((error)
|
|
1832
|
+
refreshState().catch(function(error) {
|
|
1803
1833
|
showAlert(error instanceof Error ? error.message : String(error), 'danger')
|
|
1804
1834
|
})
|
|
1805
1835
|
</script>
|
package/package.json
CHANGED