@@ -195,6 +195,7 @@ class UploadServer(
195195 .library-section.drag-over,.file-picker.drag-over{outline:3px solid rgba(179,83,24,.24);background:#fff4e4}
196196 .book-card[draggable="true"]{cursor:grab}
197197 .book-card.dragging{opacity:.45}
198+ .book-card.touch-dragging{opacity:.72;transform:scale(.985);touch-action:none}
198199 .book-card.drag-target{outline:3px solid rgba(31,111,105,.22);border-color:var(--accent-2)}
199200 .chev{display:inline-flex;width:14px;justify-content:center;color:var(--muted);font-size:0.9rem;flex:0 0 auto}
200201 .folder-shell,.folder-head{display:flex;align-items:center;gap:10px}
@@ -610,6 +611,79 @@ class UploadServer(
610611 // dragover for security. Track the active in-page drag so we can
611612 // still highlight valid drop targets while a book is being moved.
612613 var activeBookDrag = null;
614+ var touchBookDrag = null;
615+ function isInteractiveDragStart(target) {
616+ return !!(target && target.closest && target.closest('button,input,select,textarea,a,form'));
617+ }
618+ function clearTouchDragTargets() {
619+ document.querySelectorAll('.drop-zone.drag-over').forEach(function(z){ z.classList.remove('drag-over'); });
620+ document.querySelectorAll('.book-card.drag-target').forEach(function(z){ z.classList.remove('drag-target'); });
621+ }
622+ function dragTargetAtPoint(x, y, sourceCard) {
623+ var el = document.elementFromPoint(x, y);
624+ if (!el || !el.closest) return null;
625+ var card = el.closest('.book-card');
626+ if (card && card !== sourceCard) return {type:'card', el:card};
627+ var zone = el.closest('.drop-zone');
628+ if (zone) return {type:'zone', el:zone};
629+ return null;
630+ }
631+ function installTouchBookSorting(card) {
632+ if (!window.PointerEvent) return;
633+ card.addEventListener('pointerdown', function(e) {
634+ if (e.pointerType === 'mouse' || e.button !== 0 || isInteractiveDragStart(e.target)) return;
635+ var rel = card.dataset.rel || '';
636+ if (!rel || touchBookDrag) return;
637+ var startX = e.clientX;
638+ var startY = e.clientY;
639+ var timer = window.setTimeout(function() {
640+ touchBookDrag.active = true;
641+ activeBookDrag = rel;
642+ card.classList.add('touch-dragging');
643+ try { card.setPointerCapture(e.pointerId); } catch (err) { /* ignore */ }
644+ }, 320);
645+ touchBookDrag = {rel:rel, card:card, pointerId:e.pointerId, startX:startX, startY:startY, active:false, timer:timer};
646+ });
647+ card.addEventListener('pointermove', function(e) {
648+ if (!touchBookDrag || touchBookDrag.pointerId !== e.pointerId) return;
649+ var dx = e.clientX - touchBookDrag.startX;
650+ var dy = e.clientY - touchBookDrag.startY;
651+ if (!touchBookDrag.active && Math.sqrt(dx * dx + dy * dy) > 12) {
652+ window.clearTimeout(touchBookDrag.timer);
653+ touchBookDrag = null;
654+ return;
655+ }
656+ if (!touchBookDrag.active) return;
657+ e.preventDefault();
658+ clearTouchDragTargets();
659+ var target = dragTargetAtPoint(e.clientX, e.clientY, touchBookDrag.card);
660+ if (target) target.el.classList.add(target.type === 'zone' ? 'drag-over' : 'drag-target');
661+ });
662+ function finishPointerDrag(e, shouldDrop) {
663+ if (!touchBookDrag || touchBookDrag.pointerId !== e.pointerId) return;
664+ window.clearTimeout(touchBookDrag.timer);
665+ var drag = touchBookDrag;
666+ touchBookDrag = null;
667+ drag.card.classList.remove('touch-dragging');
668+ try { drag.card.releasePointerCapture(e.pointerId); } catch (err) { /* ignore */ }
669+ if (drag.active && shouldDrop) {
670+ e.preventDefault();
671+ var target = dragTargetAtPoint(e.clientX, e.clientY, drag.card);
672+ clearTouchDragTargets();
673+ activeBookDrag = null;
674+ if (target && target.type === 'card') {
675+ reorderBookRelative(drag.rel, target.el.dataset.rel || '', dropPlacement(e, target.el));
676+ } else if (target && target.type === 'zone') {
677+ moveBookTo(drag.rel, target.el.dataset.folder || '');
678+ }
679+ } else {
680+ clearTouchDragTargets();
681+ activeBookDrag = null;
682+ }
683+ }
684+ card.addEventListener('pointerup', function(e) { finishPointerDrag(e, true); });
685+ card.addEventListener('pointercancel', function(e) { finishPointerDrag(e, false); });
686+ }
613687 function installDropZones() {
614688 document.querySelectorAll('.drop-zone').forEach(function(zone) {
615689 zone.addEventListener('dragover', function(e) {
@@ -673,6 +747,7 @@ class UploadServer(
673747 document.querySelectorAll('.drop-zone.drag-over').forEach(function(z){ z.classList.remove('drag-over'); });
674748 document.querySelectorAll('.book-card.drag-target').forEach(function(z){ z.classList.remove('drag-target'); });
675749 });
750+ installTouchBookSorting(card);
676751 });
677752 var pin = document.getElementById('pin');
678753 pin.value = sessionStorage.getItem('wbooksPin') || '';
0 commit comments