diff --git a/api/delete_image.php b/api/delete_image.php
new file mode 100644
index 0000000..b753aae
--- /dev/null
+++ b/api/delete_image.php
@@ -0,0 +1,52 @@
+ 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);
diff --git a/css/galleriffic-2.css b/css/galleriffic-2.css
index 9a9058b..53e4f47 100644
--- a/css/galleriffic-2.css
+++ b/css/galleriffic-2.css
@@ -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;
diff --git a/index.php b/index.php
index 35ee73c..1797bc4 100644
--- a/index.php
+++ b/index.php
@@ -33,8 +33,7 @@ dreamgirl_require_login_page();
-
-
+
diff --git a/js/bestpic.js b/js/bestpic.js
index 646fae2..96c4201 100644
--- a/js/bestpic.js
+++ b/js/bestpic.js
@@ -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 (
+ "
" +
+ " " +
+ " " +
+ "
" +
+ " " +
+ " " +
+ ""
+ );
+}
+
+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
";
- text += " ";
- text += "
";
- text += " ";
- text += " ";
- text += "";
+ text += buildThumbLi(fname);
}
text += "";
$("#thumbs-list").html(text);
initGallery();
+ bindThumbDeleteHandler();
}
function tryLoadFromLocalListApi() {
diff --git a/js/uploader.js b/js/uploader.js
index 564d8cd..31d439b 100644
--- a/js/uploader.js
+++ b/js/uploader.js
@@ -10,19 +10,6 @@
return (Math.round(n * 10) / 10) + ' ' + units[i];
}
- function buildThumbLi(filename) {
- var imgUrl = 'img/' + encodeURIComponent(filename);
- var title = filename;
- return (
- "" +
- " " +
- "
" +
- " " +
- " " +
- ""
- );
- }
-
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)