diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index cb13abebd..56c38f590 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -550,22 +550,20 @@ BlazeComponent.extendComponent({ // Always reset dragscroll on view switch dragscroll.reset(); - if (Utils.isTouchScreenOrShowDesktopDragHandles()) { - $swimlanesDom.sortable({ - handle: '.js-swimlane-header-handle', - }); - } else { - $swimlanesDom.sortable({ - handle: '.swimlane-header', - }); - } + if ($swimlanesDom.data('uiSortable') || $swimlanesDom.data('sortable')) { + if (Utils.isTouchScreenOrShowDesktopDragHandles()) { + $swimlanesDom.sortable('option', 'handle', '.js-swimlane-header-handle'); + } else { + $swimlanesDom.sortable('option', 'handle', '.swimlane-header'); + } - // Disable drag-dropping if the current user is not a board member - $swimlanesDom.sortable( - 'option', - 'disabled', - !ReactiveCache.getCurrentUser()?.isBoardAdmin(), - ); + // Disable drag-dropping if the current user is not a board member + $swimlanesDom.sortable( + 'option', + 'disabled', + !ReactiveCache.getCurrentUser()?.isBoardAdmin(), + ); + } }); // If there is no data in the board (ie, no lists) we autofocus the list diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade index b612c4019..3d01c19c9 100644 --- a/client/components/boards/boardsList.jade +++ b/client/components/boards/boardsList.jade @@ -64,11 +64,9 @@ template(name="boardList") i.fa.js-has-spenttime-cards( class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") - if isTouchScreenOrShowDesktopDragHandles - i.fa.board-handle( - class="fa-arrows" - title="{{_ 'drag-board'}}") - else + i.fa.board-handle( + class="fa-arrows" + title="{{_ 'drag-board'}}") if isSandstorm i.fa.js-clone-board( class="fa-clone" @@ -119,11 +117,9 @@ template(name="boardList") i.fa.js-has-spenttime-cards( class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") - if isTouchScreenOrShowDesktopDragHandles - i.fa.board-handle( - class="fa-arrows" - title="{{_ 'drag-board'}}") - else + i.fa.board-handle( + class="fa-arrows" + title="{{_ 'drag-board'}}") if isSandstorm a.js-clone-board( class="fa-clone" diff --git a/client/components/cards/checklists.jade b/client/components/cards/checklists.jade index 3adccaf43..82e4a1a6e 100644 --- a/client/components/cards/checklists.jade +++ b/client/components/cards/checklists.jade @@ -125,8 +125,7 @@ template(name='checklistItemDetail') if canModifyCard .check-box-container .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}") - if isTouchScreenOrShowDesktopDragHandles - span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}") + span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}") .item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}") +viewer = item.title diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index 402b651c5..9721bc484 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -4,12 +4,9 @@ template(name="minicard") class="{{#if isLinkedBoard}}linked-board{{/if}}" class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}") if canModifyCard - if isTouchScreenOrShowDesktopDragHandles - a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") | ☰ - .handle - | ↔️ - else - a.minicard-details-menu.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") | ☰ + a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") | ☰ + .handle + | ↕️ .dates if getReceived .date diff --git a/client/components/lists/list.css b/client/components/lists/list.css index 8b76046ad..68fd62270 100644 --- a/client/components/lists/list.css +++ b/client/components/lists/list.css @@ -191,6 +191,11 @@ body.list-resizing-active * { margin-right: 0 !important; /* Ensure proper display */ display: inline-block !important; + /* Ensure it's clickable and shows proper cursor */ + cursor: move !important; + pointer-events: auto !important; + /* Add some padding for better clickability */ + padding: 4px !important; } /* Ensure buttons maintain original positioning */ diff --git a/client/components/lists/list.js b/client/components/lists/list.js index da7c16e16..b80ecff84 100644 --- a/client/components/lists/list.js +++ b/client/components/lists/list.js @@ -150,26 +150,13 @@ BlazeComponent.extendComponent({ }); this.autorun(() => { - if (Utils.isTouchScreenOrShowDesktopDragHandles()) { - $cards.sortable({ - handle: '.handle', - }); - } else { - $cards.sortable({ - handle: '.minicard', - // Prevent sortable from interfering with file drag and drop - start: function(event, ui) { - // Check if this is a file drag operation - const dataTransfer = event.originalEvent?.dataTransfer; - if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { - // Cancel sortable for file drags - return false; - } - }, - }); - } - if ($cards.data('uiSortable') || $cards.data('sortable')) { + if (Utils.isTouchScreenOrShowDesktopDragHandles()) { + $cards.sortable('option', 'handle', '.handle'); + } else { + $cards.sortable('option', 'handle', '.minicard'); + } + $cards.sortable( 'option', 'disabled', diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade index db5f86c2f..1aa7f2820 100644 --- a/client/components/lists/listHeader.jade +++ b/client/components/lists/listHeader.jade @@ -70,9 +70,9 @@ template(name="listHeader") | ⬅️ | ➡️ a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") | ☰ - if currentUser.isBoardAdmin - if isTouchScreenOrShowDesktopDragHandles - a.list-header-handle.handle.js-list-handle | ↔️ + if currentUser.isBoardMember + unless currentUser.isCommentOnly + a.list-header-handle.handle.js-list-handle | ↕️ template(name="editListTitleForm") .list-composer diff --git a/client/components/swimlanes/swimlaneHeader.jade b/client/components/swimlanes/swimlaneHeader.jade index bd9245e05..78fc9d4af 100644 --- a/client/components/swimlanes/swimlaneHeader.jade +++ b/client/components/swimlanes/swimlaneHeader.jade @@ -38,9 +38,8 @@ template(name="swimlaneFixedHeader") // ⬆️.swimlane-header-collapse-up // i.fa.fa-arrow-down.swimlane-header-collapse-down unless isTouchScreen - if isShowDesktopDragHandles - a.swimlane-header-handle.handle.js-swimlane-header-handle - | ↕️ + a.swimlane-header-handle.handle.js-swimlane-header-handle + | ↕️ if isTouchScreen a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle | ↕️ diff --git a/client/components/swimlanes/swimlanes.js b/client/components/swimlanes/swimlanes.js index dc149c48c..115138f76 100644 --- a/client/components/swimlanes/swimlanes.js +++ b/client/components/swimlanes/swimlanes.js @@ -1,4 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; +import dragscroll from '@wekanteam/dragscroll'; const { calculateIndex } = Utils; function currentListIsInThisSwimlane(swimlaneId) { @@ -43,6 +44,20 @@ function currentCardIsInThisList(listId, swimlaneId) { } function initSortable(boardComponent, $listsDom) { + // Safety check: ensure we have valid DOM elements + if (!$listsDom || $listsDom.length === 0) { + console.error('initSortable: No valid DOM elements provided'); + return; + } + + // Check if sortable is already initialized + if ($listsDom.data('uiSortable') || $listsDom.data('sortable')) { + console.log('initSortable: Sortable already initialized, skipping'); + return; + } + + console.log('initSortable: Initializing sortable for', $listsDom.length, 'elements'); + // We want to animate the card details window closing. We rely on CSS // transition for the actual animation. $listsDom._uihooks = { @@ -62,20 +77,74 @@ function initSortable(boardComponent, $listsDom) { }, }; - $listsDom.sortable({ - connectWith: '.js-swimlane, .js-lists', - tolerance: 'pointer', - helper: 'clone', - items: '.js-list:not(.js-list-composer)', - placeholder: 'js-list placeholder', - distance: 7, + console.log('Initializing list sortable with', $listsDom.length, 'elements'); + console.log('Connected elements:', $('.js-swimlane, .js-lists').length); + console.log('Available swimlanes:', $('.js-swimlane').map(function() { return $(this).attr('id'); }).get()); + + // Add click debugging for drag handles + $listsDom.on('mousedown', '.js-list-handle', function(e) { + console.log('List drag handle clicked!', e.target); + e.stopPropagation(); + }); + + $listsDom.on('mousedown', '.js-list-header', function(e) { + console.log('List header clicked!', e.target); + }); + + // Add debugging for sortable events + $listsDom.on('sortstart', function(e, ui) { + console.log('Sortable start event fired!', ui.item); + }); + + $listsDom.on('sortbeforestop', function(e, ui) { + console.log('Sortable beforeStop event fired!', ui.item); + }); + + $listsDom.on('sortstop', function(e, ui) { + console.log('Sortable stop event fired!', ui.item); + }); + + try { + $listsDom.sortable({ + connectWith: '.js-swimlane, .js-lists', + tolerance: 'pointer', + appendTo: '.board-canvas', + helper(evt, item) { + const helper = item.clone(); + helper.css('z-index', 1000); + return helper; + }, + items: '.js-list:not(.js-list-composer)', + placeholder: 'list placeholder', + distance: 3, + forcePlaceholderSize: true, + cursor: 'move', start(evt, ui) { + console.log('List drag started'); + ui.helper.css('z-index', 1000); ui.placeholder.height(ui.helper.height()); ui.placeholder.width(ui.helper.width()); EscapeActions.executeUpTo('popup-close'); boardComponent.setIsDragging(true); + + // Add visual feedback for list being dragged + ui.item.addClass('ui-sortable-helper'); + + // Disable dragscroll during list dragging to prevent interference + console.log('Disabling dragscroll for list dragging'); + dragscroll.reset(); + + // Also disable dragscroll on all swimlanes during list dragging + $('.js-swimlane').each(function() { + $(this).removeClass('dragscroll'); + }); + }, + beforeStop(evt, ui) { + // Clean up visual feedback + ui.item.removeClass('ui-sortable-helper'); }, stop(evt, ui) { + console.log('List drag stopped'); // To attribute the new index number, we need to get the DOM element // of the previous and the following card -- if any. const prevListDom = ui.item.prev('.js-list').get(0); @@ -83,15 +152,39 @@ function initSortable(boardComponent, $listsDom) { const sortIndex = calculateIndex(prevListDom, nextListDom, 1); const listDomElement = ui.item.get(0); - const list = Blaze.getData(listDomElement); + if (!listDomElement) { + console.error('List DOM element not found during drag stop'); + return; + } + + let list; + try { + list = Blaze.getData(listDomElement); + } catch (error) { + console.error('Error getting list data:', error); + return; + } + + if (!list) { + console.error('List data not found for element:', listDomElement); + return; + } // Detect if the list was dropped in a different swimlane const targetSwimlaneDom = ui.item.closest('.js-swimlane'); let targetSwimlaneId = null; + console.log('Target swimlane DOM:', targetSwimlaneDom.length, targetSwimlaneDom.attr('id')); + if (targetSwimlaneDom.length > 0) { // List was dropped in a swimlane - targetSwimlaneId = targetSwimlaneDom.attr('id').replace('swimlane-', ''); + try { + targetSwimlaneId = targetSwimlaneDom.attr('id').replace('swimlane-', ''); + console.log('List dropped in swimlane:', targetSwimlaneId); + } catch (error) { + console.error('Error getting target swimlane ID:', error); + return; + } } else { // List was dropped in lists view (not swimlanes view) // In this case, assign to the default swimlane @@ -100,6 +193,7 @@ function initSortable(boardComponent, $listsDom) { const defaultSwimlane = currentBoard.getDefaultSwimline(); if (defaultSwimlane) { targetSwimlaneId = defaultSwimlane._id; + console.log('List dropped in lists view, using default swimlane:', targetSwimlaneId); } } } @@ -123,6 +217,11 @@ function initSortable(boardComponent, $listsDom) { // Check if the list was dropped in a different swimlane const isDifferentSwimlane = targetSwimlaneId && targetSwimlaneId !== originalSwimlaneId; + console.log('Cross-swimlane check:', { + originalSwimlaneId, + targetSwimlaneId, + isDifferentSwimlane + }); // If the list was dropped in a different swimlane, update the swimlaneId if (isDifferentSwimlane) { @@ -152,34 +251,63 @@ function initSortable(boardComponent, $listsDom) { $listsDom.sortable('cancel'); } - Lists.update(list._id, { - $set: updateData, - }); + try { + Lists.update(list._id, { + $set: updateData, + }); + console.log('List updated successfully:', list._id, updateData); + } catch (error) { + console.error('Error updating list:', error); + return; + } boardComponent.setIsDragging(false); + + // Re-enable dragscroll after list dragging is complete + console.log('Re-enabling dragscroll after list dragging'); + dragscroll.reset(); + + // Re-enable dragscroll on all swimlanes + $('.js-swimlane').each(function() { + $(this).addClass('dragscroll'); + }); }, }); + } catch (error) { + console.error('Error initializing list sortable:', error); + return; + } + + console.log('List sortable initialization completed successfully'); + console.log('Sortable data after init:', $listsDom.data('uiSortable') || $listsDom.data('sortable')); boardComponent.autorun(() => { - if (Utils.isTouchScreenOrShowDesktopDragHandles()) { - $listsDom.sortable({ - handle: '.js-list-handle', - connectWith: '.js-swimlane, .js-lists', - }); - } else { - $listsDom.sortable({ - handle: '.js-list-header', - connectWith: '.js-swimlane, .js-lists', - }); - } - const $listDom = $listsDom; + console.log('Autorun running, checking sortable status...'); + console.log('Has uiSortable:', $listDom.data('uiSortable')); + console.log('Has sortable:', $listDom.data('sortable')); if ($listDom.data('uiSortable') || $listDom.data('sortable')) { - $listsDom.sortable( - 'option', - 'disabled', - !ReactiveCache.getCurrentUser()?.isBoardAdmin(), - ); + if (Utils.isTouchScreenOrShowDesktopDragHandles()) { + $listsDom.sortable('option', 'handle', '.js-list-handle'); + console.log('List sortable: Using .js-list-handle as handle'); + console.log('Found drag handles:', $listsDom.find('.js-list-handle').length); + } else { + $listsDom.sortable('option', 'handle', '.js-list-header'); + console.log('List sortable: Using .js-list-header as handle'); + console.log('Found list headers:', $listsDom.find('.js-list-header').length); + } + + const currentUser = ReactiveCache.getCurrentUser(); + const canModify = Utils.canModifyBoard(); + const isDisabled = !canModify; + $listsDom.sortable('option', 'disabled', isDisabled); + console.log('List sortable disabled:', isDisabled); + console.log('Can modify board:', canModify); + console.log('Current user:', currentUser ? currentUser.username : 'null'); + console.log('Is board member:', currentUser ? currentUser.isBoardMember() : 'null'); + console.log('Is comment only:', currentUser ? currentUser.isCommentOnly() : 'null'); + } else { + console.log('List sortable not initialized yet'); } }); } @@ -188,11 +316,15 @@ BlazeComponent.extendComponent({ onRendered() { const boardComponent = this.parentComponent(); const $listsDom = this.$('.js-lists'); + + console.log('Swimlane onRendered - DOM elements found:', $listsDom.length); + console.log('Swimlane onRendered - DOM element:', $listsDom[0]); if (!Utils.getCurrentCardId()) { boardComponent.scrollLeft(); } + console.log('Calling initSortable...'); initSortable(boardComponent, $listsDom); }, onCreated() { @@ -538,11 +670,15 @@ BlazeComponent.extendComponent({ onRendered() { const boardComponent = this.parentComponent(); const $listsDom = this.$('.js-lists'); + + console.log('ListsGroup onRendered - DOM elements found:', $listsDom.length); + console.log('ListsGroup onRendered - DOM element:', $listsDom[0]); if (!Utils.getCurrentCardId()) { boardComponent.scrollLeft(); } + console.log('ListsGroup calling initSortable...'); initSortable(boardComponent, $listsDom); }, }).register('listsGroup'); diff --git a/client/lib/utils.js b/client/lib/utils.js index 503e998fc..c979c1773 100644 --- a/client/lib/utils.js +++ b/client/lib/utils.js @@ -496,30 +496,14 @@ Utils = { // returns if desktop drag handles are enabled isShowDesktopDragHandles() { - if (this.isTouchScreen()) { - return true; - /* - const currentUser = ReactiveCache.getCurrentUser(); - if (currentUser) { - return (currentUser.profile || {}).showDesktopDragHandles; - } else if (window.localStorage.getItem('showDesktopDragHandles')) { - // - if (window.localStorage.getItem('showDesktopDragHandles')) { - return true; - } else { - return false; - */ - } else { - return false; - } + // Always show drag handles on all displays + return true; }, // returns if mini screen or desktop drag handles isTouchScreenOrShowDesktopDragHandles() { - // Always enable drag handles for mobile screens (touch devices) - return this.isTouchScreen() || this.isMiniScreen(); - //return this.isTouchScreen() || this.isShowDesktopDragHandles(); - //return this.isShowDesktopDragHandles(); + // Always enable drag handles for all displays + return true; }, calculateIndexData(prevData, nextData, nItems = 1) { diff --git a/packages/wekan-fullcalendar/fullcalendar/fullcalendar.js b/packages/wekan-fullcalendar/fullcalendar/fullcalendar.js index a06cae5de..6caf88b04 100644 --- a/packages/wekan-fullcalendar/fullcalendar/fullcalendar.js +++ b/packages/wekan-fullcalendar/fullcalendar/fullcalendar.js @@ -2509,14 +2509,14 @@ var GlobalEmitter = /** @class */ (function () { // http://stackoverflow.com/a/32954565/96342 window.addEventListener('scroll', this.handleScrollProxy = function (ev) { _this.handleScroll($.Event(ev)); - }, true // useCapture + }, { passive: true, capture: true } // useCapture with passive for better performance ); }; GlobalEmitter.prototype.unbind = function () { this.stopListeningTo($(document)); window.removeEventListener('touchmove', this.handleTouchMoveProxy, { passive: false } // use same options as addEventListener ); - window.removeEventListener('scroll', this.handleScrollProxy, true // useCapture + window.removeEventListener('scroll', this.handleScrollProxy, { passive: true, capture: true } // use same options as addEventListener ); }; // Touch Handlers