diff --git a/public/pdf.html b/public/pdf.html index bbea2fe..3907658 100644 --- a/public/pdf.html +++ b/public/pdf.html @@ -203,6 +203,17 @@ .page-thumb-check { position:absolute; top:4px; right:4px; width:18px; height:18px; border-radius:50%; border:2px solid rgba(255,255,255,.5); background:rgba(0,0,0,.3); display:none; align-items:center; justify-content:center; } .page-thumb.selected .page-thumb-check { display:flex; background:var(--accent); border-color:var(--accent); } .page-thumb-check svg { width:12px; height:12px; } + /* Merge sortable list */ + .merge-sort-list { display:flex; flex-direction:column; gap:6px; } + .merge-sort-item { display:flex; align-items:center; gap:10px; padding:10px 12px; background:var(--surface-700); border:1px solid var(--surface-600); border-radius:8px; cursor:grab; transition:all .15s; user-select:none; } + .merge-sort-item:active { cursor:grabbing; } + .merge-sort-item.dragging { opacity:0.4; } + .merge-sort-item.drag-over { border-color:var(--accent); background:var(--accent-bg); } + .merge-sort-item .merge-num { width:24px; height:24px; border-radius:6px; background:var(--accent); color:#fff; display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:700; font-family:'JetBrains Mono',monospace; flex-shrink:0; } + .merge-sort-item .merge-name { font-size:13px; font-weight:500; color:var(--text-primary); flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } + .merge-sort-item .merge-meta { font-size:11px; color:var(--text-muted); font-family:'JetBrains Mono',monospace; flex-shrink:0; } + .merge-sort-item .merge-grip { color:var(--text-muted); flex-shrink:0; } + .page-thumb.rotate-90 img { transform: rotate(90deg); } .page-thumb.rotate-180 img { transform: rotate(180deg); } .page-thumb.rotate-270 img { transform: rotate(270deg); } @@ -700,8 +711,9 @@
- Порядок файлов -
Файлы будут объединены в порядке списка выше. Нужно минимум 2 файла.
+ Перетащите файлы для изменения порядка объединения +
+
Нужно минимум 2 файла
@@ -1145,6 +1157,7 @@ }); renderSingleFileSelectors(); + renderMergeList(); } // Render file selector for single-file operations @@ -1520,7 +1533,7 @@ switch (op) { case 'merge': url = '/pdf/merge'; - body = JSON.stringify({ fileIds: state.files.map(f => f.id) }); + body = JSON.stringify({ fileIds: state.mergeOrder || state.files.map(f => f.id) }); break; case 'split': url = '/pdf/split'; @@ -1952,6 +1965,62 @@ }, 300); } + + // ─── Merge sortable file list ───────────────────────────────────────────── + function renderMergeList() { + const list = document.getElementById('mergeFileList'); + const hint = document.getElementById('mergeHint'); + if (!list) return; + list.innerHTML = ''; + + if (state.files.length < 2) { + if (hint) hint.textContent = 'Нужно минимум 2 файла'; + return; + } + if (hint) hint.textContent = state.files.length + ' файлов — перетащите для изменения порядка'; + + // Use mergeOrder or default + if (!state.mergeOrder || state.mergeOrder.length !== state.files.length) { + state.mergeOrder = state.files.map(f => f.id); + } + + state.mergeOrder.forEach((fileId, idx) => { + const f = state.files.find(x => x.id === fileId); + if (!f) return; + const item = document.createElement('div'); + item.className = 'merge-sort-item'; + item.dataset.id = f.id; + item.draggable = true; + item.innerHTML = '
' + + '
' + (idx + 1) + '
' + + '
' + escHtml(f.name) + '
' + + '
' + f.pages + ' стр. · ' + formatSize(f.size) + '
'; + + item.addEventListener('dragstart', e => { + e.dataTransfer.setData('text/plain', f.id); + item.classList.add('dragging'); + }); + item.addEventListener('dragend', () => item.classList.remove('dragging')); + item.addEventListener('dragover', e => { e.preventDefault(); item.classList.add('drag-over'); }); + item.addEventListener('dragleave', () => item.classList.remove('drag-over')); + item.addEventListener('drop', e => { + e.preventDefault(); + item.classList.remove('drag-over'); + const fromId = e.dataTransfer.getData('text/plain'); + if (fromId === f.id) return; + // Reorder mergeOrder array + const fromIdx = state.mergeOrder.indexOf(fromId); + const toIdx = state.mergeOrder.indexOf(f.id); + if (fromIdx < 0 || toIdx < 0) return; + state.mergeOrder.splice(fromIdx, 1); + state.mergeOrder.splice(toIdx, 0, fromId); + renderMergeList(); + }); + + list.appendChild(item); + }); + } + })();