이미지 삭제 기능을 추가하고 NCue 로고를 제거한다.
썸네일에서 서버의 실제 이미지 파일을 안전하게 삭제하고 갤러리 상태를 동기화한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
52
api/delete_image.php
Normal file
52
api/delete_image.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
// Delete handler: removes an image file from /img and returns JSON.
|
||||
require_once __DIR__ . '/../auth/auth.php';
|
||||
dreamgirl_require_login_json();
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['ok' => false, 'error' => 'Method not allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$filename = isset($_POST['filename']) ? (string)$_POST['filename'] : '';
|
||||
$filename = basename($filename);
|
||||
|
||||
if ($filename === '' || $filename === '.' || $filename === '..') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok' => false, 'error' => 'Invalid filename']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$allowedExt = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, $allowedExt, true)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok' => false, 'error' => 'Unsupported file type']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$imgDir = realpath(__DIR__ . '/../img');
|
||||
if ($imgDir === false || !is_dir($imgDir)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['ok' => false, 'error' => 'img directory not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$path = $imgDir . DIRECTORY_SEPARATOR . $filename;
|
||||
$realPath = realpath($path);
|
||||
|
||||
if ($realPath === false || !is_file($realPath) || dirname($realPath) !== $imgDir) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['ok' => false, 'error' => 'File not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!@unlink($realPath)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['ok' => false, 'error' => 'Failed to delete file (check permissions)']);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => true, 'filename' => $filename], JSON_UNESCAPED_UNICODE);
|
||||
@@ -275,6 +275,32 @@ ul.thumbs li {
|
||||
padding: 0;
|
||||
margin: 5px 10px 5px 0;
|
||||
list-style: none;
|
||||
position: relative;
|
||||
}
|
||||
.thumb-delete {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: rgba(128, 0, 200, 0.9);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
opacity: 0.85;
|
||||
}
|
||||
.thumb-delete:hover,
|
||||
.thumb-delete:focus {
|
||||
opacity: 1;
|
||||
background: rgba(100, 0, 160, 1);
|
||||
outline: none;
|
||||
}
|
||||
a.thumb {
|
||||
padding: 2px;
|
||||
|
||||
@@ -33,8 +33,7 @@ dreamgirl_require_login_page();
|
||||
|
||||
<div id="page">
|
||||
<div id="container">
|
||||
<div style="display:flex; align-items:baseline; justify-content:space-between; gap:12px;">
|
||||
<h1 style="margin:0;"><a href="https://ncue.net" target="_blank" rel="noopener noreferrer">NCue</a></h1>
|
||||
<div style="display:flex; align-items:baseline; justify-content:flex-end; gap:12px;">
|
||||
<div style="font-size:12px;"><a href="<?php echo htmlspecialchars(dreamgirl_url('logout.php'), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>">Logout</a></div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,6 +2,95 @@ var refreshIntervalId = null;
|
||||
// Prefer same-origin relative paths so HTTPS doesn't break due to mixed-content / upgrade rules.
|
||||
var imgurl = "img/";
|
||||
|
||||
function buildThumbLi(filename) {
|
||||
var imgfile = imgurl + encodeURIComponent(filename);
|
||||
return (
|
||||
"<li data-filename='" + filename.replace(/'/g, "'") + "'>" +
|
||||
" <button type='button' class='thumb-delete' title='삭제' aria-label='삭제'>×</button>" +
|
||||
" <a class='thumb' name='leaf' href='" + imgfile + "' title='" + filename.replace(/'/g, "'") + "'>" +
|
||||
" <img src='" + imgfile + "' alt='" + filename.replace(/'/g, "'") + "' width='75' height='75'/>" +
|
||||
" </a>" +
|
||||
" <div class='caption'></div>" +
|
||||
"</li>"
|
||||
);
|
||||
}
|
||||
|
||||
function removeFromImageList(filename) {
|
||||
if (!window.DREAMGIRL_IMAGES || !window.DREAMGIRL_IMAGES.length) return;
|
||||
var idx = window.DREAMGIRL_IMAGES.indexOf(filename);
|
||||
if (idx >= 0) window.DREAMGIRL_IMAGES.splice(idx, 1);
|
||||
}
|
||||
|
||||
function deleteImageFile(filename, onSuccess, onError) {
|
||||
var fd = new FormData();
|
||||
fd.append('filename', filename);
|
||||
|
||||
fetch('api/delete_image.php', {
|
||||
method: 'POST',
|
||||
body: fd,
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
if (!data || !data.ok) {
|
||||
throw new Error((data && data.error) ? data.error : '삭제 실패');
|
||||
}
|
||||
if (typeof onSuccess === 'function') onSuccess(data);
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (typeof onError === 'function') {
|
||||
onError(err);
|
||||
} else {
|
||||
alert('에러: ' + (err && err.message ? err.message : '삭제 실패'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bindThumbDeleteHandler() {
|
||||
$('#thumbs').off('click.thumbDelete', '.thumb-delete');
|
||||
$('#thumbs').on('click.thumbDelete', '.thumb-delete', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var $li = $(this).closest('li');
|
||||
var filename = $li.attr('data-filename');
|
||||
if (!filename) return;
|
||||
|
||||
if (!window.confirm('이 이미지를 삭제하시겠습니까?\n' + filename)) return;
|
||||
|
||||
var $btn = $(this);
|
||||
$btn.prop('disabled', true);
|
||||
|
||||
deleteImageFile(filename, function () {
|
||||
removeFromImageList(filename);
|
||||
|
||||
if (window.dreamgirlGallery) {
|
||||
var gallery = window.dreamgirlGallery;
|
||||
var index = $li.index();
|
||||
var wasCurrent = gallery.currentImage && gallery.currentImage.index === index;
|
||||
|
||||
if (typeof gallery.removeImageByIndex === 'function') {
|
||||
gallery.removeImageByIndex(index);
|
||||
} else {
|
||||
$li.remove();
|
||||
}
|
||||
|
||||
if (wasCurrent && gallery.data && gallery.data.length) {
|
||||
var nextIndex = Math.min(index, gallery.data.length - 1);
|
||||
if (typeof gallery.gotoIndex === 'function') {
|
||||
gallery.gotoIndex(nextIndex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$li.remove();
|
||||
}
|
||||
}, function (err) {
|
||||
$btn.prop('disabled', false);
|
||||
alert('에러: ' + (err && err.message ? err.message : '삭제 실패'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getApiUrl() {
|
||||
// Use scheme-relative URL so it matches http/https of the current page.
|
||||
// NOTE: if the API does not support https, consider proxying it via Apache and pointing to a same-origin endpoint.
|
||||
@@ -87,17 +176,12 @@ function onLoad() {
|
||||
|
||||
for (var i=0; i<rid.length; i++) {
|
||||
var fname = fileNames[rid[i]];
|
||||
var imgfile = imgurl + encodeURIComponent(fname);
|
||||
text += "<li>";
|
||||
text += " <a class='thumb' name='leaf' href='" + imgfile + "' title='girl" + (i+1) + "'>";
|
||||
text += " <img src='" + imgfile + "' alt='girl" + (i+1) + "' width='75' height='75'/>";
|
||||
text += " </a>";
|
||||
text += " <div class='caption'></div>";
|
||||
text += "</li>";
|
||||
text += buildThumbLi(fname);
|
||||
}
|
||||
text += "</ul>";
|
||||
$("#thumbs-list").html(text);
|
||||
initGallery();
|
||||
bindThumbDeleteHandler();
|
||||
}
|
||||
|
||||
function tryLoadFromLocalListApi() {
|
||||
|
||||
@@ -10,19 +10,6 @@
|
||||
return (Math.round(n * 10) / 10) + ' ' + units[i];
|
||||
}
|
||||
|
||||
function buildThumbLi(filename) {
|
||||
var imgUrl = 'img/' + encodeURIComponent(filename);
|
||||
var title = filename;
|
||||
return (
|
||||
"<li>" +
|
||||
" <a class='thumb' name='leaf' href='" + imgUrl + "' title='" + title + "'>" +
|
||||
" <img src='" + imgUrl + "' alt='" + title + "' width='75' height='75'/>" +
|
||||
" </a>" +
|
||||
" <div class='caption'></div>" +
|
||||
"</li>"
|
||||
);
|
||||
}
|
||||
|
||||
function setStatus(text) {
|
||||
var el = qs('upload-status');
|
||||
if (el) el.textContent = text || '';
|
||||
@@ -112,12 +99,15 @@
|
||||
|
||||
// Add immediately to current gallery
|
||||
var filename = data.filename;
|
||||
if (window.dreamgirlGallery && typeof window.dreamgirlGallery.appendImage === 'function') {
|
||||
window.dreamgirlGallery.appendImage(buildThumbLi(filename));
|
||||
} else {
|
||||
var thumbHtml = (typeof buildThumbLi === 'function')
|
||||
? buildThumbLi(filename)
|
||||
: '';
|
||||
if (thumbHtml && window.dreamgirlGallery && typeof window.dreamgirlGallery.appendImage === 'function') {
|
||||
window.dreamgirlGallery.appendImage(thumbHtml);
|
||||
} else if (thumbHtml) {
|
||||
// fallback: append to DOM
|
||||
var ul = document.querySelector('#thumbs ul.thumbs');
|
||||
if (ul) ul.insertAdjacentHTML('beforeend', buildThumbLi(filename));
|
||||
if (ul) ul.insertAdjacentHTML('beforeend', thumbHtml);
|
||||
}
|
||||
|
||||
// keep in-memory list (doesn't persist; list_images.php handles persistence)
|
||||
|
||||
Reference in New Issue
Block a user