v4.0.0: 덱 UX·i18n·썸네일 저장 개선 및 413 수정
즐겨찾기·홀드 미리보기·페이지 도움말·3개국어 번역을 추가하고, 덱 저장 시 썸네일을 1200px로 통일하며 Nginx/API 업로드 제한을 상향했다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
179
script/deck-preview-hold.js
Normal file
179
script/deck-preview-hold.js
Normal file
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* 썸네일 미리보기 확대 (클릭 고정 / 길게 누르기)
|
||||
*/
|
||||
(function initDeckPreviewHold() {
|
||||
/** @type {HTMLElement|null} */
|
||||
let overlay = null;
|
||||
|
||||
/** @type {number} */
|
||||
let pressStartAt = 0;
|
||||
|
||||
/** @type {boolean} */
|
||||
let suppressClick = false;
|
||||
|
||||
/** @type {number} */
|
||||
const HOLD_THRESHOLD_MS = 280;
|
||||
|
||||
/**
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
function ensureOverlay() {
|
||||
if (!overlay) {
|
||||
overlay = document.createElement('div');
|
||||
overlay.className = 'deck-preview-hold-overlay hidden';
|
||||
overlay.setAttribute('role', 'dialog');
|
||||
overlay.setAttribute('aria-modal', 'true');
|
||||
overlay.innerHTML =
|
||||
'<div class="deck-preview-hold-box"><img alt="" draggable="false" /></div>';
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) hide();
|
||||
});
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
return overlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isVisible() {
|
||||
return Boolean(overlay && !overlay.classList.contains('hidden'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} src
|
||||
*/
|
||||
function show(src) {
|
||||
if (!src) return;
|
||||
const el = ensureOverlay();
|
||||
const img = el.querySelector('img');
|
||||
if (img) img.src = src;
|
||||
el.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (overlay) overlay.classList.add('hidden');
|
||||
pressStartAt = 0;
|
||||
suppressClick = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} e
|
||||
* @returns {HTMLElement|null}
|
||||
*/
|
||||
function findHoldTarget(e) {
|
||||
const target = e.target;
|
||||
if (!(target instanceof Element)) return null;
|
||||
return target.closest('[data-hold-preview]');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} hold
|
||||
* @returns {string}
|
||||
*/
|
||||
function getPreviewSrc(hold) {
|
||||
return hold.dataset.previewSrc || '';
|
||||
}
|
||||
|
||||
function initGlobal() {
|
||||
if (document.documentElement.dataset.deckHoldInit === '1') return;
|
||||
document.documentElement.dataset.deckHoldInit = '1';
|
||||
|
||||
document.addEventListener(
|
||||
'pointerdown',
|
||||
(e) => {
|
||||
const hold = findHoldTarget(e);
|
||||
if (!hold) return;
|
||||
const src = getPreviewSrc(hold);
|
||||
if (!src) return;
|
||||
|
||||
pressStartAt = Date.now();
|
||||
show(src);
|
||||
|
||||
if (hold.setPointerCapture && e.pointerId !== undefined) {
|
||||
try {
|
||||
hold.setPointerCapture(e.pointerId);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
document.addEventListener(
|
||||
'pointerup',
|
||||
(e) => {
|
||||
if (!pressStartAt) return;
|
||||
|
||||
const hold = findHoldTarget(e);
|
||||
if (hold?.releasePointerCapture && e.pointerId !== undefined) {
|
||||
try {
|
||||
hold.releasePointerCapture(e.pointerId);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
const heldMs = Date.now() - pressStartAt;
|
||||
pressStartAt = 0;
|
||||
|
||||
if (heldMs >= HOLD_THRESHOLD_MS) {
|
||||
hide();
|
||||
suppressClick = true;
|
||||
window.setTimeout(() => {
|
||||
suppressClick = false;
|
||||
}, 0);
|
||||
} else {
|
||||
suppressClick = true;
|
||||
window.setTimeout(() => {
|
||||
suppressClick = false;
|
||||
}, 400);
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
document.addEventListener(
|
||||
'pointercancel',
|
||||
() => {
|
||||
hide();
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (suppressClick) return;
|
||||
|
||||
const hold = findHoldTarget(e);
|
||||
if (hold) {
|
||||
const src = getPreviewSrc(hold);
|
||||
if (!src) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (isVisible()) hide();
|
||||
else show(src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVisible() && e.target === overlay) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') hide();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} _container
|
||||
*/
|
||||
function bindHoldPreview(_container) {
|
||||
initGlobal();
|
||||
}
|
||||
|
||||
initGlobal();
|
||||
|
||||
window.UaDeckPreviewHold = { bindHoldPreview, hide };
|
||||
})();
|
||||
Reference in New Issue
Block a user