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);
+ });
+ }
+
})();