Add image upload UI and server-side upload/list APIs
This commit is contained in:
33
api/list_images.php
Normal file
33
api/list_images.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
// Returns a JSON list of images currently present in /img
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedExt = ['jpg','jpeg','png','gif','webp'];
|
||||||
|
$images = [];
|
||||||
|
|
||||||
|
$files = scandir($imgDir);
|
||||||
|
if ($files === false) $files = [];
|
||||||
|
|
||||||
|
foreach ($files as $f) {
|
||||||
|
if ($f === '.' || $f === '..') continue;
|
||||||
|
$path = $imgDir . DIRECTORY_SEPARATOR . $f;
|
||||||
|
if (!is_file($path)) continue;
|
||||||
|
$ext = strtolower(pathinfo($f, PATHINFO_EXTENSION));
|
||||||
|
if (!in_array($ext, $allowedExt, true)) continue;
|
||||||
|
$images[] = $f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stable order
|
||||||
|
natcasesort($images);
|
||||||
|
$images = array_values($images);
|
||||||
|
|
||||||
|
echo json_encode(['ok' => true, 'images' => $images], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
|
||||||
87
api/upload_image.php
Normal file
87
api/upload_image.php
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
// Upload handler: saves an uploaded image into /img and returns JSON.
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
// Limit: 10MB
|
||||||
|
$maxBytes = 10 * 1024 * 1024;
|
||||||
|
|
||||||
|
if (!isset($_FILES['image'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'No file uploaded']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $_FILES['image'];
|
||||||
|
if (!is_array($file) || !isset($file['error'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'Invalid upload']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'Upload error: ' . $file['error']]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($file['size']) || $file['size'] <= 0 || $file['size'] > $maxBytes) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'File too large (max 10MB)']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate image content
|
||||||
|
$tmp = $file['tmp_name'];
|
||||||
|
$info = @getimagesize($tmp);
|
||||||
|
if ($info === false) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'Not a valid image']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mime = isset($info['mime']) ? strtolower($info['mime']) : '';
|
||||||
|
$allowedMimeToExt = [
|
||||||
|
'image/jpeg' => 'jpg',
|
||||||
|
'image/png' => 'png',
|
||||||
|
'image/gif' => 'gif',
|
||||||
|
'image/webp' => 'webp',
|
||||||
|
];
|
||||||
|
if (!isset($allowedMimeToExt[$mime])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'Unsupported image 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate safe unique filename
|
||||||
|
$ext = $allowedMimeToExt[$mime];
|
||||||
|
$base = 'upload_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4));
|
||||||
|
$filename = $base . '.' . $ext;
|
||||||
|
$dest = $imgDir . DIRECTORY_SEPARATOR . $filename;
|
||||||
|
|
||||||
|
// Ensure we don't overwrite (extremely unlikely)
|
||||||
|
$tries = 0;
|
||||||
|
while (file_exists($dest) && $tries < 5) {
|
||||||
|
$filename = $base . '_' . bin2hex(random_bytes(2)) . '.' . $ext;
|
||||||
|
$dest = $imgDir . DIRECTORY_SEPARATOR . $filename;
|
||||||
|
$tries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!move_uploaded_file($tmp, $dest)) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'Failed to save file (check permissions)']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conservative permissions
|
||||||
|
@chmod($dest, 0644);
|
||||||
|
|
||||||
|
echo json_encode(['ok' => true, 'filename' => $filename], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
|
||||||
@@ -100,6 +100,84 @@ div.navigation {
|
|||||||
/* The navigation style is set using jQuery so that the javascript specific styles won't be applied unless javascript is enabled. */
|
/* The navigation style is set using jQuery so that the javascript specific styles won't be applied unless javascript is enabled. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Upload panel under thumbnails */
|
||||||
|
.upload-panel {
|
||||||
|
margin-top: 14px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
.upload-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.dropzone {
|
||||||
|
position: relative;
|
||||||
|
border: 2px dashed #cfcfcf;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 14px 12px;
|
||||||
|
background: #fafafa;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.dropzone:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px rgba(46, 131, 255, 0.25);
|
||||||
|
border-color: #2e83ff;
|
||||||
|
}
|
||||||
|
.dropzone.dragover {
|
||||||
|
border-color: #2e83ff;
|
||||||
|
background: #f2f7ff;
|
||||||
|
}
|
||||||
|
.dropzone-text {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.file-input {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.upload-preview {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.upload-preview img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 160px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
background: #fff;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.preview-meta {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 6px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.upload-actions {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.upload-actions button {
|
||||||
|
padding: 7px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #cfcfcf;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.upload-actions button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.upload-status {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- Layout: slideshow (left) + thumbs (right), full width ---- */
|
/* ---- Layout: slideshow (left) + thumbs (right), full width ---- */
|
||||||
.gallery-wrap {
|
.gallery-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
42
index.html
42
index.html
@@ -13,6 +13,7 @@
|
|||||||
<script type="text/javascript" src="js/jquery.opacityrollover.js"></script>
|
<script type="text/javascript" src="js/jquery.opacityrollover.js"></script>
|
||||||
<script type="text/javascript" src="js/bestpic.js"></script>
|
<script type="text/javascript" src="js/bestpic.js"></script>
|
||||||
<script type="text/javascript" src="js/image_manifest.js"></script>
|
<script type="text/javascript" src="js/image_manifest.js"></script>
|
||||||
|
<script type="text/javascript" src="js/uploader.js"></script>
|
||||||
|
|
||||||
<!-- We only want the thunbnails to display when javascript is disabled -->
|
<!-- We only want the thunbnails to display when javascript is disabled -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@@ -41,22 +42,33 @@
|
|||||||
<div id="caption" class="caption-container"></div>
|
<div id="caption" class="caption-container"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="thumbs" class="navigation">
|
<div id="thumbs" class="navigation">
|
||||||
<ul class="thumbs noscript">
|
<div id="thumbs-list">
|
||||||
<li>
|
<ul class="thumbs noscript">
|
||||||
<a class="thumb" name="leaf" href="http://dreamgirl.ncue.net/img/0.jpg" title="Title #0">
|
<li>
|
||||||
<!-- <img src="http://dreamgirl.ncue.net/img/0.jpg" alt="Title #0" width='75' height='75'/> -->
|
<a class="thumb" name="leaf" href="http://dreamgirl.ncue.net/img/0.jpg" title="Title #0">
|
||||||
</a>
|
<!-- <img src="http://dreamgirl.ncue.net/img/0.jpg" alt="Title #0" width='75' height='75'/> -->
|
||||||
<div class="caption">
|
</a>
|
||||||
<!--
|
<div class="caption"></div>
|
||||||
<div class="download">
|
</li>
|
||||||
<a href="http://dreamgirl.ncue.net/img/0.jpg">Download</a>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="image-title">Title #0</div>
|
<div id="upload-panel" class="upload-panel">
|
||||||
<div class="image-desc">Description</div>
|
<div class="upload-title">이미지 추가</div>
|
||||||
-->
|
<div id="dropzone" class="dropzone" tabindex="0">
|
||||||
|
<div class="dropzone-text">
|
||||||
|
드래그&드랍 또는 파일 선택
|
||||||
</div>
|
</div>
|
||||||
</li>
|
<input id="file-input" class="file-input" type="file" accept="image/*" />
|
||||||
</ul>
|
</div>
|
||||||
|
<div id="upload-preview" class="upload-preview" style="display:none;">
|
||||||
|
<img id="preview-img" alt="preview" />
|
||||||
|
<div id="preview-meta" class="preview-meta"></div>
|
||||||
|
</div>
|
||||||
|
<div class="upload-actions">
|
||||||
|
<button id="upload-btn" type="button" disabled>업로드</button>
|
||||||
|
<span id="upload-status" class="upload-status"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ function onLoad() {
|
|||||||
exemptionSelector: '.selected'
|
exemptionSelector: '.selected'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize Advanced Galleriffic Gallery
|
// Initialize Advanced Galleriffic Gallery (store instance for uploader)
|
||||||
$('#thumbs').galleriffic({
|
window.dreamgirlGallery = $('#thumbs').galleriffic({
|
||||||
delay: 2500,
|
delay: 2500,
|
||||||
numThumbs: 15,
|
numThumbs: 15,
|
||||||
preloadAhead: 10,
|
preloadAhead: 10,
|
||||||
@@ -96,10 +96,20 @@ function onLoad() {
|
|||||||
text += "</li>";
|
text += "</li>";
|
||||||
}
|
}
|
||||||
text += "</ul>";
|
text += "</ul>";
|
||||||
$("#thumbs").html(text);
|
$("#thumbs-list").html(text);
|
||||||
initGallery();
|
initGallery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tryLoadFromLocalListApi() {
|
||||||
|
return $.ajax({
|
||||||
|
url: 'api/list_images.php',
|
||||||
|
type: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
cache: false,
|
||||||
|
timeout: 8000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function fallbackToLocalManifest() {
|
function fallbackToLocalManifest() {
|
||||||
if (window.DREAMGIRL_IMAGES && window.DREAMGIRL_IMAGES.length) {
|
if (window.DREAMGIRL_IMAGES && window.DREAMGIRL_IMAGES.length) {
|
||||||
buildThumbsFromList(window.DREAMGIRL_IMAGES);
|
buildThumbsFromList(window.DREAMGIRL_IMAGES);
|
||||||
@@ -110,33 +120,44 @@ function onLoad() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$.ajax({
|
// Prefer same-origin dynamic list (so newly uploaded images appear on refresh).
|
||||||
url : getApiUrl(),
|
tryLoadFromLocalListApi()
|
||||||
type: 'GET',
|
.done(function(resp) {
|
||||||
data : "id=user",
|
if (resp && resp.ok && resp.images && resp.images.length) {
|
||||||
dataType : "jsonp",
|
buildThumbsFromList(resp.images);
|
||||||
jsonp : "callback",
|
} else {
|
||||||
cache : false,
|
|
||||||
timeout: 15000,
|
|
||||||
success: function(data) {
|
|
||||||
try {
|
|
||||||
var filelist = data && data.filelist ? data.filelist : null;
|
|
||||||
if (!filelist || !filelist.length) return fallbackToLocalManifest();
|
|
||||||
var names = [];
|
|
||||||
for (var i=0; i<filelist.length; i++) {
|
|
||||||
if (filelist[i] && filelist[i]["img"]) names.push(filelist[i]["img"]);
|
|
||||||
}
|
|
||||||
if (!names.length) return fallbackToLocalManifest();
|
|
||||||
buildThumbsFromList(names);
|
|
||||||
} catch (e) {
|
|
||||||
fallbackToLocalManifest();
|
fallbackToLocalManifest();
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
error: function () {
|
.fail(function() {
|
||||||
// If remote API fails (common on HTTPS if api host doesn't support it), fall back to local images.
|
// If local list API is unavailable, try remote JSONP API, then fall back to static manifest.
|
||||||
fallbackToLocalManifest();
|
$.ajax({
|
||||||
}
|
url : getApiUrl(),
|
||||||
});
|
type: 'GET',
|
||||||
|
data : "id=user",
|
||||||
|
dataType : "jsonp",
|
||||||
|
jsonp : "callback",
|
||||||
|
cache : false,
|
||||||
|
timeout: 15000,
|
||||||
|
success: function(data) {
|
||||||
|
try {
|
||||||
|
var filelist = data && data.filelist ? data.filelist : null;
|
||||||
|
if (!filelist || !filelist.length) return fallbackToLocalManifest();
|
||||||
|
var names = [];
|
||||||
|
for (var i=0; i<filelist.length; i++) {
|
||||||
|
if (filelist[i] && filelist[i]["img"]) names.push(filelist[i]["img"]);
|
||||||
|
}
|
||||||
|
if (!names.length) return fallbackToLocalManifest();
|
||||||
|
buildThumbsFromList(names);
|
||||||
|
} catch (e) {
|
||||||
|
fallbackToLocalManifest();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
fallbackToLocalManifest();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
150
js/uploader.js
Normal file
150
js/uploader.js
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
(function () {
|
||||||
|
function qs(id) { return document.getElementById(id); }
|
||||||
|
|
||||||
|
function humanSize(bytes) {
|
||||||
|
if (!bytes && bytes !== 0) return '';
|
||||||
|
var units = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
var i = 0;
|
||||||
|
var n = bytes;
|
||||||
|
while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
|
||||||
|
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 || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
var dropzone = qs('dropzone');
|
||||||
|
var fileInput = qs('file-input');
|
||||||
|
var previewWrap = qs('upload-preview');
|
||||||
|
var previewImg = qs('preview-img');
|
||||||
|
var previewMeta = qs('preview-meta');
|
||||||
|
var uploadBtn = qs('upload-btn');
|
||||||
|
|
||||||
|
if (!dropzone || !fileInput || !uploadBtn) return;
|
||||||
|
|
||||||
|
var selectedFile = null;
|
||||||
|
|
||||||
|
function showPreview(file) {
|
||||||
|
selectedFile = file;
|
||||||
|
uploadBtn.disabled = !file;
|
||||||
|
setStatus('');
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
if (previewWrap) previewWrap.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previewWrap) previewWrap.style.display = 'block';
|
||||||
|
if (previewMeta) previewMeta.textContent = file.name + ' · ' + humanSize(file.size);
|
||||||
|
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function (e) {
|
||||||
|
if (previewImg) previewImg.src = e.target.result;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isImageFile(file) {
|
||||||
|
return file && file.type && file.type.indexOf('image/') === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFiles(files) {
|
||||||
|
if (!files || !files.length) return;
|
||||||
|
var f = files[0];
|
||||||
|
if (!isImageFile(f)) {
|
||||||
|
setStatus('이미지 파일만 업로드 가능합니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showPreview(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
dropzone.addEventListener('dragover', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
dropzone.classList.add('dragover');
|
||||||
|
});
|
||||||
|
dropzone.addEventListener('dragleave', function () {
|
||||||
|
dropzone.classList.remove('dragover');
|
||||||
|
});
|
||||||
|
dropzone.addEventListener('drop', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
dropzone.classList.remove('dragover');
|
||||||
|
onFiles(e.dataTransfer.files);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileInput.addEventListener('change', function () {
|
||||||
|
onFiles(fileInput.files);
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadBtn.addEventListener('click', function () {
|
||||||
|
if (!selectedFile) return;
|
||||||
|
uploadBtn.disabled = true;
|
||||||
|
setStatus('업로드 중...');
|
||||||
|
|
||||||
|
var fd = new FormData();
|
||||||
|
fd.append('image', selectedFile);
|
||||||
|
|
||||||
|
fetch('api/upload_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 : '업로드 실패');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add immediately to current gallery
|
||||||
|
var filename = data.filename;
|
||||||
|
if (window.dreamgirlGallery && typeof window.dreamgirlGallery.appendImage === 'function') {
|
||||||
|
window.dreamgirlGallery.appendImage(buildThumbLi(filename));
|
||||||
|
} else {
|
||||||
|
// fallback: append to DOM
|
||||||
|
var ul = document.querySelector('#thumbs ul.thumbs');
|
||||||
|
if (ul) ul.insertAdjacentHTML('beforeend', buildThumbLi(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep in-memory list (doesn't persist; list_images.php handles persistence)
|
||||||
|
if (window.DREAMGIRL_IMAGES && window.DREAMGIRL_IMAGES.push) {
|
||||||
|
window.DREAMGIRL_IMAGES.push(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus('업로드 완료: ' + filename);
|
||||||
|
// reset
|
||||||
|
selectedFile = null;
|
||||||
|
if (fileInput) fileInput.value = '';
|
||||||
|
if (previewWrap) previewWrap.style.display = 'none';
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
setStatus('에러: ' + (err && err.message ? err.message : '업로드 실패'));
|
||||||
|
})
|
||||||
|
.finally(function () {
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user