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){
|
function media_sort_files($dir, $files){
|
||||||
return list_files($dir);
|
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){
|
function input_dir_date_from_media($dir){
|
||||||
$dates = [];
|
$dates = [];
|
||||||
foreach (list_files($dir) as $file) {
|
foreach (list_files($dir) as $file) {
|
||||||
|
|
@ -762,6 +807,29 @@ try {
|
||||||
}
|
}
|
||||||
header('Location: new.php?tab=edit');
|
header('Location: new.php?tab=edit');
|
||||||
exit;
|
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') {
|
} elseif ($action === 'delete_video') {
|
||||||
$file = basename((string)($_POST['video'] ?? ''));
|
$file = basename((string)($_POST['video'] ?? ''));
|
||||||
$path = $config['videos_dir'] . '/' . $file;
|
$path = $config['videos_dir'] . '/' . $file;
|
||||||
|
|
@ -939,11 +1007,138 @@ function deleteFile(name){
|
||||||
.then(function(json){
|
.then(function(json){
|
||||||
if(!json || !json.ok) throw new Error(json && (json.error || json.message) ? (json.error || json.message) : 'Delete failed');
|
if(!json || !json.ok) throw new Error(json && (json.error || json.message) ? (json.error || json.message) : 'Delete failed');
|
||||||
if(target) target.remove();
|
if(target) target.remove();
|
||||||
|
refreshFileOrderButtons();
|
||||||
showToast(json.message || ('Removed ' + name), true);
|
showToast(json.message || ('Removed ' + name), true);
|
||||||
}).catch(function(err){
|
}).catch(function(err){
|
||||||
showToast(err.message || String(err), false);
|
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>
|
||||||
<script>
|
<script>
|
||||||
function showToast(message, ok, details){
|
function showToast(message, ok, details){
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue