Add admin AJAX up/down media reordering synced to order.json
This commit is contained in:
parent
7296c5cef4
commit
3cc2828bc0
1 changed files with 195 additions and 0 deletions
195
new.php
195
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<rows.length;i++){
|
||||
var row = rows[i];
|
||||
if(row.dataset.orderButtons === '1') continue;
|
||||
|
||||
var fileName = captionRowFileName(row);
|
||||
if(!isVisualMediaName(fileName)) { row.dataset.orderButtons = '1'; continue; }
|
||||
|
||||
var removeBtn = row.querySelector('button[onclick*="deleteFile"]');
|
||||
if(!removeBtn) { row.dataset.orderButtons = '1'; continue; }
|
||||
|
||||
var wrap = document.createElement('span');
|
||||
wrap.className = 'file-order-controls';
|
||||
wrap.style.display = 'inline-flex';
|
||||
wrap.style.gap = '6px';
|
||||
wrap.style.marginRight = '8px';
|
||||
|
||||
var upBtn = document.createElement('button');
|
||||
upBtn.type = 'button';
|
||||
upBtn.className = 'button order-btn-up';
|
||||
upBtn.textContent = '↑';
|
||||
upBtn.title = 'Move up';
|
||||
upBtn.style.padding = '0.2rem 0.45rem';
|
||||
|
||||
var downBtn = document.createElement('button');
|
||||
downBtn.type = 'button';
|
||||
downBtn.className = 'button order-btn-down';
|
||||
downBtn.textContent = '↓';
|
||||
downBtn.title = 'Move down';
|
||||
downBtn.style.padding = '0.2rem 0.45rem';
|
||||
|
||||
(function(r, file, up, down){
|
||||
up.addEventListener('click', function(){ moveFileOrder(file, 'up', r, up); });
|
||||
down.addEventListener('click', function(){ moveFileOrder(file, 'down', r, down); });
|
||||
})(row, fileName, upBtn, downBtn);
|
||||
|
||||
wrap.appendChild(upBtn);
|
||||
wrap.appendChild(downBtn);
|
||||
row.insertBefore(wrap, removeBtn);
|
||||
row.dataset.orderButtons = '1';
|
||||
}
|
||||
}
|
||||
|
||||
function refreshFileOrderButtons(){
|
||||
ensureFileOrderButtons();
|
||||
var rows = getVisualCaptionRows();
|
||||
for(var i=0;i<rows.length;i++){
|
||||
var row = rows[i];
|
||||
var up = row.querySelector('.order-btn-up');
|
||||
var down = row.querySelector('.order-btn-down');
|
||||
if(up) up.disabled = (i === 0);
|
||||
if(down) down.disabled = (i === rows.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', refreshFileOrderButtons);
|
||||
</script>
|
||||
<script>
|
||||
function showToast(message, ok, details){
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue