Files
Hellion-NewTab/src/js/drag.js
T
JonKazama-Hellion 00baa0231b feat(ui): Custom Dialog-System, Onboarding und Backup-Reminder
- HellionDialog.alert/confirm ersetzt alle nativen confirm() und alert() Aufrufe
- 6-stufiger Onboarding-Flow beim ersten Start (Boards, Themes, Features, Backup)
- Backup-Reminder erinnert alle 7 Tage an JSON-Export
- innerHTML komplett durch createElement/createElementNS ersetzt (XSS-Schutz)
- Drag & Drop Inline-Styles durch CSS-Klassen ersetzt
2026-03-21 19:08:17 +01:00

144 lines
4.9 KiB
JavaScript

/* =============================================
HELLION NEWTAB — drag.js
Drag & Drop via Pointer Events
Boards: Reihenfolge per Handle
Bookmarks: Reihenfolge innerhalb eines Boards
============================================= */
// ---- BOARD DRAG (Pointer Events) ----
function initBoardDragDrop() {
const wrapper = document.getElementById('boardsWrapper');
let dragging = null;
let placeholder = null;
function getInsertTarget(clientX, clientY) {
const boardEls = Array.from(wrapper.querySelectorAll('.board:not(.dragging)'));
for (const b of boardEls) {
const r = b.getBoundingClientRect();
if (clientX >= r.left && clientX <= r.right && clientY >= r.top && clientY <= r.bottom) {
return { el: b, before: clientX < r.left + r.width / 2 };
}
}
return null;
}
wrapper.querySelectorAll('.board').forEach(boardEl => {
const handle = boardEl.querySelector('.board-drag-handle');
if (!handle) return;
handle.style.cursor = 'grab';
handle.addEventListener('pointerdown', e => {
e.preventDefault();
handle.setPointerCapture(e.pointerId);
handle.style.cursor = 'grabbing';
const rect = boardEl.getBoundingClientRect();
// Ghost
const ghost = boardEl.cloneNode(true);
ghost.className += ' drag-ghost';
ghost.style.left = rect.left + 'px';
ghost.style.top = rect.top + 'px';
ghost.style.width = rect.width + 'px';
ghost.style.height = rect.height + 'px';
document.body.appendChild(ghost);
// Placeholder
placeholder = document.createElement('div');
placeholder.className = 'board-placeholder';
placeholder.style.cssText = `width:${rect.width}px; height:${rect.height}px;`;
boardEl.parentNode.insertBefore(placeholder, boardEl);
boardEl.classList.add('dragging');
dragging = { el: boardEl, ghost,
offsetX: e.clientX - rect.left,
offsetY: e.clientY - rect.top
};
});
handle.addEventListener('pointermove', e => {
if (!dragging || dragging.el !== boardEl) return;
e.preventDefault();
dragging.ghost.style.left = (e.clientX - dragging.offsetX) + 'px';
dragging.ghost.style.top = (e.clientY - dragging.offsetY) + 'px';
const target = getInsertTarget(e.clientX, e.clientY);
if (target && target.el !== boardEl) {
target.before
? target.el.parentNode.insertBefore(placeholder, target.el)
: target.el.parentNode.insertBefore(placeholder, target.el.nextSibling);
}
});
handle.addEventListener('pointerup', async () => {
if (!dragging || dragging.el !== boardEl) return;
handle.style.cursor = 'grab';
placeholder.parentNode.insertBefore(boardEl, placeholder);
placeholder.remove(); placeholder = null;
boardEl.classList.remove('dragging');
dragging.ghost.remove();
dragging = null;
// Neue Reihenfolge aus DOM ablesen
const newOrder = Array.from(wrapper.querySelectorAll('.board'))
.map(el => el.dataset.boardId).filter(Boolean);
boards.sort((a, b) => newOrder.indexOf(a.id) - newOrder.indexOf(b.id));
await saveBoards();
});
handle.addEventListener('pointercancel', () => {
if (!dragging) return;
dragging.ghost.remove();
if (placeholder) { placeholder.remove(); placeholder = null; }
boardEl.classList.remove('dragging');
dragging = null;
handle.style.cursor = 'grab';
});
});
}
// ---- BOOKMARK DRAG (innerhalb eines Boards) ----
function initBookmarkDragDrop(listEl, board) {
let dragSrcBmId = null;
listEl.addEventListener('dragstart', e => {
const item = e.target.closest('.bm-item');
if (!item) return;
dragSrcBmId = item.dataset.bmId;
e.dataTransfer.effectAllowed = 'move';
setTimeout(() => item.classList.add('dragging-source'), 0);
});
listEl.addEventListener('dragend', e => {
const item = e.target.closest('.bm-item');
if (item) item.classList.remove('dragging-source');
});
listEl.addEventListener('dragover', e => {
e.preventDefault();
const item = e.target.closest('.bm-item');
if (item) item.classList.add('drag-over');
});
listEl.addEventListener('dragleave', e => {
const item = e.target.closest('.bm-item');
if (item) item.classList.remove('drag-over');
});
listEl.addEventListener('drop', async e => {
e.preventDefault(); e.stopPropagation();
const item = e.target.closest('.bm-item');
if (!item) return;
item.classList.remove('drag-over');
const targetBmId = item.dataset.bmId;
if (!dragSrcBmId || dragSrcBmId === targetBmId) return;
const srcIdx = board.bookmarks.findIndex(b => b.id === dragSrcBmId);
const tgtIdx = board.bookmarks.findIndex(b => b.id === targetBmId);
const [moved] = board.bookmarks.splice(srcIdx, 1);
board.bookmarks.splice(tgtIdx, 0, moved);
await saveBoards();
renderBoards();
});
}