From 3cc2828bc015f3d987d433afcceddd1d77423699 Mon Sep 17 00:00:00 2001 From: hbrain Date: Sun, 31 May 2026 17:33:19 +0200 Subject: [PATCH] Add admin AJAX up/down media reordering synced to order.json --- new.php | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/new.php b/new.php index 41f1f22..f49d9f4 100644 --- a/new.php +++ b/new.php @@ -339,6 +339,51 @@ function list_files($dir){ function media_sort_files($dir, $files){ return list_files($dir); } +function visual_order_files($dir){ + $all = list_files($dir); + return array_values(array_filter($all, fn($name)=>is_visual_media_file($name))); +} +function write_visual_order($dir, $order){ + $clean = []; + $seen = []; + foreach ((array)$order as $name) { + if (!is_string($name)) continue; + $name = basename($name); + if (!is_visual_media_file($name) || isset($seen[$name])) continue; + if (!is_file($dir . '/' . $name)) continue; + $seen[$name] = true; + $clean[] = $name; + } + file_put_contents($dir . '/order.json', json_encode(array_values($clean), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + @chmod($dir . '/order.json', 0664); + return $clean; +} +function move_visual_file_order($dir, $file, $direction){ + $file = basename((string)$file); + if (!is_visual_media_file($file) || !is_file($dir . '/' . $file)) throw new RuntimeException('Invalid media file for reordering.'); + $direction = strtolower(trim((string)$direction)); + if (!in_array($direction, ['up','down'], true)) throw new RuntimeException('Invalid move direction.'); + + $order = visual_order_files($dir); + $idx = array_search($file, $order, true); + if ($idx === false) throw new RuntimeException('File not found in visual order.'); + + $newIdx = $idx; + if ($direction === 'up' && $idx > 0) { + [$order[$idx - 1], $order[$idx]] = [$order[$idx], $order[$idx - 1]]; + $newIdx = $idx - 1; + } elseif ($direction === 'down' && $idx < count($order) - 1) { + [$order[$idx], $order[$idx + 1]] = [$order[$idx + 1], $order[$idx]]; + $newIdx = $idx + 1; + } + + write_visual_order($dir, $order); + return [ + 'changed' => $newIdx !== $idx, + 'index' => $newIdx, + 'count' => count($order), + ]; +} function input_dir_date_from_media($dir){ $dates = []; foreach (list_files($dir) as $file) { @@ -762,6 +807,29 @@ try { } header('Location: new.php?tab=edit'); exit; + } elseif ($action === 'reorder_file') { + $dir = resolve_input_dir_from_request($config['uploads_dir'], $_POST['id'] ?? '', $_POST['dir'] ?? '', $articleIndex); + $articleId = ensure_state_article_id($dir); + if (input_dir_running($dir)) throw new RuntimeException('This input directory is being rendered. Editing is disabled until the job completes.'); + $file = basename((string)($_POST['file'] ?? '')); + $direction = strtolower(trim((string)($_POST['direction'] ?? ''))); + $result = move_visual_file_order($dir, $file, $direction); + $message = $result['changed'] ? ('Moved file ' . ($direction === 'up' ? 'up' : 'down') . ': ' . $file) : 'No move possible for file: ' . $file; + if (!empty($_POST['ajax'])) { + ajax_json([ + 'ok' => true, + 'message' => $message, + 'file' => $file, + 'direction' => $direction, + 'changed' => !empty($result['changed']), + 'index' => (int)($result['index'] ?? -1), + 'count' => (int)($result['count'] ?? 0), + 'dir' => basename($dir), + 'id' => $articleId, + ]); + } + header('Location: new.php?tab=edit&id=' . rawurlencode((string)$articleId) . '&edit=' . rawurlencode(basename($dir)) . '&msg=' . rawurlencode($message)); + exit; } elseif ($action === 'delete_video') { $file = basename((string)($_POST['video'] ?? '')); $path = $config['videos_dir'] . '/' . $file; @@ -939,11 +1007,138 @@ function deleteFile(name){ .then(function(json){ if(!json || !json.ok) throw new Error(json && (json.error || json.message) ? (json.error || json.message) : 'Delete failed'); if(target) target.remove(); + refreshFileOrderButtons(); showToast(json.message || ('Removed ' + name), true); }).catch(function(err){ showToast(err.message || String(err), false); }); } + +function getEditDirContext(){ + var form = document.getElementById('delete-file-form'); + if(!form) return {dir:'', id:''}; + var dirInput = form.querySelector('input[name=dir]'); + var idInput = form.querySelector('input[name=id]'); + return { + dir: dirInput ? dirInput.value : '', + id: idInput ? idInput.value : '' + }; +} + +function captionRowFileName(row){ + if(!row) return ''; + var strong = row.querySelector('.caption-fields strong') || row.querySelector('strong'); + return strong ? strong.textContent.trim() : ''; +} + +function isVisualMediaName(name){ + var lower = String(name || '').toLowerCase(); + return /\.(jpg|jpeg|png|webp|gif|mp4|mov|m4v|avi|mkv|webm)$/.test(lower); +} + +function getVisualCaptionRows(){ + return Array.prototype.slice.call(document.querySelectorAll('.caption-row')).filter(function(row){ + return isVisualMediaName(captionRowFileName(row)); + }); +} + +function moveFileOrder(name, direction, row, triggerBtn){ + var ctx = getEditDirContext(); + if(!ctx.dir){ showToast('Internal error: missing input dir', false); return; } + if(triggerBtn) triggerBtn.disabled = true; + + var body = 'action=reorder_file' + + '&dir=' + encodeURIComponent(ctx.dir) + + '&id=' + encodeURIComponent(ctx.id) + + '&file=' + encodeURIComponent(name) + + '&direction=' + encodeURIComponent(direction) + + '&ajax=1'; + + fetch('new.php', { + method: 'POST', + credentials: 'same-origin', + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + body: body + }).then(function(r){ return r.json().catch(function(){ return { ok:false, error:'Invalid server response' }; }); }) + .then(function(json){ + if(!json || !json.ok) throw new Error((json && (json.error || json.message)) ? (json.error || json.message) : 'Reorder failed'); + + if(row && json.changed){ + var rows = getVisualCaptionRows(); + var idx = rows.indexOf(row); + if(direction === 'up' && idx > 0){ + row.parentNode.insertBefore(row, rows[idx - 1]); + } else if(direction === 'down' && idx >= 0 && idx < rows.length - 1){ + row.parentNode.insertBefore(rows[idx + 1], row); + } + } + + refreshFileOrderButtons(); + showToast(json.message || 'Order updated', true); + }).catch(function(err){ + showToast(err.message || String(err), false); + }).finally(function(){ + if(triggerBtn) triggerBtn.disabled = false; + }); +} + +function ensureFileOrderButtons(){ + var rows = document.querySelectorAll('.caption-row'); + for(var i=0;i