feat: 增加了预览功能
Browse files- app/static/index.html +83 -9
app/static/index.html
CHANGED
|
@@ -242,6 +242,24 @@
|
|
| 242 |
}
|
| 243 |
|
| 244 |
#toast.show { opacity: 1; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
</style>
|
| 246 |
</head>
|
| 247 |
<body>
|
|
@@ -323,7 +341,7 @@
|
|
| 323 |
<label class="bool-item"><input type="checkbox" id="append_type"> append_type</label>
|
| 324 |
<label class="bool-item"><input type="checkbox" id="scv"> scv</label>
|
| 325 |
<label class="bool-item"><input type="checkbox" id="append_info" checked> append_info</label>
|
| 326 |
-
<label class="bool-item"><input type="checkbox" id="new_name"
|
| 327 |
</div>
|
| 328 |
</div>
|
| 329 |
</div>
|
|
@@ -340,6 +358,10 @@
|
|
| 340 |
<div class="result-actions">
|
| 341 |
<button class="btn btn-sm" id="copy-btn" type="button">复制</button>
|
| 342 |
<button class="btn btn-sm" id="open-btn" type="button">在新标签打开</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
</div>
|
| 344 |
</div>
|
| 345 |
</div>
|
|
@@ -359,6 +381,20 @@ advancedToggle.addEventListener('click', () => {
|
|
| 359 |
toggleIcon.textContent = open ? '▼' : '▶';
|
| 360 |
});
|
| 361 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
// 生成链接
|
| 363 |
document.getElementById('gen-btn').addEventListener('click', async () => {
|
| 364 |
const url = document.getElementById('url').value.trim();
|
|
@@ -382,13 +418,12 @@ document.getElementById('gen-btn').addEventListener('click', async () => {
|
|
| 382 |
|
| 383 |
const extra = targetMap[targetVal] || { target: targetVal };
|
| 384 |
|
| 385 |
-
//
|
| 386 |
-
const boolFields =
|
| 387 |
const boolValues = {};
|
| 388 |
boolFields.forEach(id => {
|
| 389 |
-
const
|
| 390 |
-
|
| 391 |
-
boolValues[id] = el.checked;
|
| 392 |
});
|
| 393 |
|
| 394 |
// list 来自 extra(clash-list 模式)
|
|
@@ -408,10 +443,9 @@ document.getElementById('gen-btn').addEventListener('click', async () => {
|
|
| 408 |
...boolValues,
|
| 409 |
};
|
| 410 |
|
| 411 |
-
// 去掉 undefined/null 的 bool(后端会过滤 None,但前端先清理)
|
| 412 |
-
// bool 字段全部传,后端自己判断
|
| 413 |
-
|
| 414 |
hideError();
|
|
|
|
|
|
|
| 415 |
|
| 416 |
try {
|
| 417 |
const res = await fetch('/api/convert', {
|
|
@@ -434,6 +468,7 @@ document.getElementById('gen-btn').addEventListener('click', async () => {
|
|
| 434 |
// 绑定按钮(每次生成都重新绑,避免重复监听器)
|
| 435 |
const copyBtn = document.getElementById('copy-btn');
|
| 436 |
const openBtn = document.getElementById('open-btn');
|
|
|
|
| 437 |
|
| 438 |
copyBtn.onclick = () => {
|
| 439 |
navigator.clipboard.writeText(fullUrl).then(() => showToast());
|
|
@@ -441,11 +476,50 @@ document.getElementById('gen-btn').addEventListener('click', async () => {
|
|
| 441 |
|
| 442 |
openBtn.onclick = () => window.open(fullUrl, '_blank');
|
| 443 |
|
|
|
|
|
|
|
| 444 |
} catch (e) {
|
| 445 |
showError('网络错误:' + e.message);
|
| 446 |
}
|
| 447 |
});
|
| 448 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
function showError(msg) {
|
| 450 |
const el = document.getElementById('error-msg');
|
| 451 |
el.textContent = msg;
|
|
|
|
| 242 |
}
|
| 243 |
|
| 244 |
#toast.show { opacity: 1; }
|
| 245 |
+
|
| 246 |
+
/* 预览区域 */
|
| 247 |
+
#preview-section { margin-top: 0.75rem; display: none; }
|
| 248 |
+
#preview-section.show { display: block; }
|
| 249 |
+
#preview-content {
|
| 250 |
+
background: #0a0c10;
|
| 251 |
+
border: 1px solid #2d3348;
|
| 252 |
+
border-radius: 8px;
|
| 253 |
+
padding: 0.75rem 1rem;
|
| 254 |
+
font-family: monospace;
|
| 255 |
+
font-size: 0.75rem;
|
| 256 |
+
color: #94a3b8;
|
| 257 |
+
max-height: 400px;
|
| 258 |
+
overflow-y: auto;
|
| 259 |
+
white-space: pre;
|
| 260 |
+
line-height: 1.4;
|
| 261 |
+
}
|
| 262 |
+
#preview-content.error { color: #fca5a5; }
|
| 263 |
</style>
|
| 264 |
</head>
|
| 265 |
<body>
|
|
|
|
| 341 |
<label class="bool-item"><input type="checkbox" id="append_type"> append_type</label>
|
| 342 |
<label class="bool-item"><input type="checkbox" id="scv"> scv</label>
|
| 343 |
<label class="bool-item"><input type="checkbox" id="append_info" checked> append_info</label>
|
| 344 |
+
<label class="bool-item"><input type="checkbox" id="new_name"> new_name</label>
|
| 345 |
</div>
|
| 346 |
</div>
|
| 347 |
</div>
|
|
|
|
| 358 |
<div class="result-actions">
|
| 359 |
<button class="btn btn-sm" id="copy-btn" type="button">复制</button>
|
| 360 |
<button class="btn btn-sm" id="open-btn" type="button">在新标签打开</button>
|
| 361 |
+
<button class="btn btn-sm" id="preview-btn" type="button">预览内容</button>
|
| 362 |
+
</div>
|
| 363 |
+
<div id="preview-section">
|
| 364 |
+
<pre id="preview-content"></pre>
|
| 365 |
</div>
|
| 366 |
</div>
|
| 367 |
</div>
|
|
|
|
| 381 |
toggleIcon.textContent = open ? '▼' : '▶';
|
| 382 |
});
|
| 383 |
|
| 384 |
+
// subconverter 实际默认值,与之相同的 bool 不传(null = 后端跳过)
|
| 385 |
+
const BOOL_DEFAULTS = {
|
| 386 |
+
emoji: true,
|
| 387 |
+
udp: false,
|
| 388 |
+
tfo: false,
|
| 389 |
+
sort: false,
|
| 390 |
+
fdn: false,
|
| 391 |
+
expand: true,
|
| 392 |
+
append_type: false,
|
| 393 |
+
scv: false,
|
| 394 |
+
append_info: true,
|
| 395 |
+
new_name: false,
|
| 396 |
+
};
|
| 397 |
+
|
| 398 |
// 生成链接
|
| 399 |
document.getElementById('gen-btn').addEventListener('click', async () => {
|
| 400 |
const url = document.getElementById('url').value.trim();
|
|
|
|
| 418 |
|
| 419 |
const extra = targetMap[targetVal] || { target: targetVal };
|
| 420 |
|
| 421 |
+
// 只传与默认值不同的 bool,相同的传 null(后端跳过)
|
| 422 |
+
const boolFields = Object.keys(BOOL_DEFAULTS);
|
| 423 |
const boolValues = {};
|
| 424 |
boolFields.forEach(id => {
|
| 425 |
+
const val = document.getElementById(id).checked;
|
| 426 |
+
boolValues[id] = (val === BOOL_DEFAULTS[id]) ? null : val;
|
|
|
|
| 427 |
});
|
| 428 |
|
| 429 |
// list 来自 extra(clash-list 模式)
|
|
|
|
| 443 |
...boolValues,
|
| 444 |
};
|
| 445 |
|
|
|
|
|
|
|
|
|
|
| 446 |
hideError();
|
| 447 |
+
// 重置预览区
|
| 448 |
+
hidePreview();
|
| 449 |
|
| 450 |
try {
|
| 451 |
const res = await fetch('/api/convert', {
|
|
|
|
| 468 |
// 绑定按钮(每次生成都重新绑,避免重复监听器)
|
| 469 |
const copyBtn = document.getElementById('copy-btn');
|
| 470 |
const openBtn = document.getElementById('open-btn');
|
| 471 |
+
const previewBtn = document.getElementById('preview-btn');
|
| 472 |
|
| 473 |
copyBtn.onclick = () => {
|
| 474 |
navigator.clipboard.writeText(fullUrl).then(() => showToast());
|
|
|
|
| 476 |
|
| 477 |
openBtn.onclick = () => window.open(fullUrl, '_blank');
|
| 478 |
|
| 479 |
+
previewBtn.onclick = () => togglePreview(fullUrl, previewBtn);
|
| 480 |
+
|
| 481 |
} catch (e) {
|
| 482 |
showError('网络错误:' + e.message);
|
| 483 |
}
|
| 484 |
});
|
| 485 |
|
| 486 |
+
// 预览内容 toggle
|
| 487 |
+
async function togglePreview(fullUrl, btn) {
|
| 488 |
+
const section = document.getElementById('preview-section');
|
| 489 |
+
const content = document.getElementById('preview-content');
|
| 490 |
+
|
| 491 |
+
// 已展开则收起
|
| 492 |
+
if (section.classList.contains('show')) {
|
| 493 |
+
hidePreview();
|
| 494 |
+
return;
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
btn.textContent = '加载中…';
|
| 498 |
+
btn.disabled = true;
|
| 499 |
+
|
| 500 |
+
try {
|
| 501 |
+
const res = await fetch(fullUrl);
|
| 502 |
+
const text = await res.text();
|
| 503 |
+
const lines = text.split('\n');
|
| 504 |
+
content.textContent = lines.slice(0, 500).join('\n') + (lines.length > 500 ? '\n... (已截断)' : '');
|
| 505 |
+
content.classList.remove('error');
|
| 506 |
+
} catch (e) {
|
| 507 |
+
content.textContent = 'Error: ' + e.message;
|
| 508 |
+
content.classList.add('error');
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
section.classList.add('show');
|
| 512 |
+
btn.textContent = '预览内容';
|
| 513 |
+
btn.disabled = false;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
function hidePreview() {
|
| 517 |
+
document.getElementById('preview-section').classList.remove('show');
|
| 518 |
+
document.getElementById('preview-content').textContent = '';
|
| 519 |
+
const btn = document.getElementById('preview-btn');
|
| 520 |
+
if (btn) { btn.textContent = '预览内容'; btn.disabled = false; }
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
function showError(msg) {
|
| 524 |
const el = document.getElementById('error-msg');
|
| 525 |
el.textContent = msg;
|