| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 | import { ReactiveCache } from '/imports/reactiveCache';const { calculateIndex } = Utils;function currentListIsInThisSwimlane(swimlaneId) {  const currentList = Utils.getCurrentList();  return (    currentList &&    (currentList.swimlaneId === swimlaneId || currentList.swimlaneId === '')  );}function currentCardIsInThisList(listId, swimlaneId) {  const currentCard = Utils.getCurrentCard();  //const currentUser = ReactiveCache.getCurrentUser();  if (    //currentUser &&    //currentUser.profile &&    Utils.boardView() === 'board-view-swimlanes'  )    return (      currentCard &&      currentCard.listId === listId &&      currentCard.swimlaneId === swimlaneId    );  else if (    //currentUser &&    //currentUser.profile &&    Utils.boardView() === 'board-view-lists'  )    return (      currentCard &&      currentCard.listId === listId    );  // https://github.com/wekan/wekan/issues/1623  // https://github.com/ChronikEwok/wekan/commit/cad9b20451bb6149bfb527a99b5001873b06c3de  // TODO: In public board, if you would like to switch between List/Swimlane view, you could  //       1) If there is no view cookie, save to cookie board-view-lists  //          board-view-lists / board-view-swimlanes / board-view-cal  //       2) If public user changes clicks board-view-lists then change view and  //          then change view and save cookie with view value  //          without using currentuser above, because currentuser is null.}function initSortable(boardComponent, $listsDom) {  // We want to animate the card details window closing. We rely on CSS  // transition for the actual animation.  $listsDom._uihooks = {    removeElement(node) {      const removeNode = _.once(() => {        node.parentNode.removeChild(node);      });      if ($(node).hasClass('js-card-details')) {        $(node).css({          flexBasis: 0,          padding: 0,        });        $listsDom.one(CSSEvents.transitionend, removeNode);      } else {        removeNode();      }    },  };  $listsDom.sortable({    connectWith: '.js-swimlane, .js-lists',    tolerance: 'pointer',    helper: 'clone',    items: '.js-list:not(.js-list-composer)',    placeholder: 'js-list placeholder',    distance: 7,    start(evt, ui) {      ui.placeholder.height(ui.helper.height());      ui.placeholder.width(ui.helper.width());      EscapeActions.executeUpTo('popup-close');      boardComponent.setIsDragging(true);    },    stop(evt, ui) {      // 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);      const nextListDom = ui.item.next('.js-list').get(0);      const sortIndex = calculateIndex(prevListDom, nextListDom, 1);      const listDomElement = ui.item.get(0);      const list = Blaze.getData(listDomElement);      // Detect if the list was dropped in a different swimlane      const targetSwimlaneDom = ui.item.closest('.js-swimlane');      let targetSwimlaneId = null;      if (targetSwimlaneDom.length > 0) {        // List was dropped in a swimlane        targetSwimlaneId = targetSwimlaneDom.attr('id').replace('swimlane-', '');      } else {        // List was dropped in lists view (not swimlanes view)        // In this case, assign to the default swimlane        const currentBoard = ReactiveCache.getBoard(Session.get('currentBoard'));        if (currentBoard) {          const defaultSwimlane = currentBoard.getDefaultSwimline();          if (defaultSwimlane) {            targetSwimlaneId = defaultSwimlane._id;          }        }      }      // Get the original swimlane ID of the list (handle backward compatibility)      const originalSwimlaneId = list.getEffectiveSwimlaneId ? list.getEffectiveSwimlaneId() : (list.swimlaneId || null);      /*            Reverted incomplete change list width,            removed from below Lists.update:             https://github.com/wekan/wekan/issues/4558                $set: {                  width: list._id.width(),                  height: list._id.height(),      */      // Prepare update object      const updateData = {        sort: sortIndex.base,      };      // Check if the list was dropped in a different swimlane      const isDifferentSwimlane = targetSwimlaneId && targetSwimlaneId !== originalSwimlaneId;      // If the list was dropped in a different swimlane, update the swimlaneId      if (isDifferentSwimlane) {        updateData.swimlaneId = targetSwimlaneId;        if (process.env.DEBUG === 'true') {          console.log(`Moving list "${list.title}" from swimlane ${originalSwimlaneId} to swimlane ${targetSwimlaneId}`);        }        // Move all cards in the list to the new swimlane        const cardsInList = ReactiveCache.getCards({          listId: list._id,          archived: false        });        cardsInList.forEach(card => {          card.move(list.boardId, targetSwimlaneId, list._id);        });        if (process.env.DEBUG === 'true') {          console.log(`Moved ${cardsInList.length} cards to swimlane ${targetSwimlaneId}`);        }        // Don't cancel the sortable when moving to a different swimlane        // The DOM move should be allowed to complete      } else {        // If staying in the same swimlane, cancel the sortable to prevent DOM manipulation issues        $listsDom.sortable('cancel');      }      Lists.update(list._id, {        $set: updateData,      });      boardComponent.setIsDragging(false);    },  });  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;    if ($listDom.data('uiSortable') || $listDom.data('sortable')) {      $listsDom.sortable(        'option',        'disabled',        !ReactiveCache.getCurrentUser()?.isBoardAdmin(),      );    }  });}BlazeComponent.extendComponent({  onRendered() {    const boardComponent = this.parentComponent();    const $listsDom = this.$('.js-lists');    if (!Utils.getCurrentCardId()) {      boardComponent.scrollLeft();    }    initSortable(boardComponent, $listsDom);  },  onCreated() {    this.draggingActive = new ReactiveVar(false);    this._isDragging = false;    this._lastDragPositionX = 0;  },  id() {    return this._id;  },  currentCardIsInThisList(listId, swimlaneId) {    return currentCardIsInThisList(listId, swimlaneId);  },  currentListIsInThisSwimlane(swimlaneId) {    return currentListIsInThisSwimlane(swimlaneId);  },  visible(list) {    if (list.archived) {      // Show archived list only when filter archive is on      if (!Filter.archive.isSelected()) {        return false;      }    }    if (Filter.lists._isActive()) {      if (!list.title.match(Filter.lists.getRegexSelector())) {        return false;      }    }    if (Filter.hideEmpty.isSelected()) {      // Check for cards in all swimlanes, not just the current one      // This ensures lists with cards in other swimlanes are still visible      const cards = list.cards();      if (cards.length === 0) {        return false;      }    }    return true;  },  events() {    return [      {        // Click-and-drag action        'mousedown .board-canvas'(evt) {          // Translating the board canvas using the click-and-drag action can          // conflict with the build-in browser mechanism to select text. We          // define a list of elements in which we disable the dragging because          // the user will legitimately expect to be able to select some text with          // his mouse.          const noDragInside = ['a', 'input', 'textarea', 'p'].concat(            Utils.isTouchScreenOrShowDesktopDragHandles()              ? ['.js-list-handle', '.js-swimlane-header-handle']              : ['.js-list-header'],          );          if (            $(evt.target).closest(noDragInside.join(',')).length === 0 &&            this.$('.swimlane').prop('clientHeight') > evt.offsetY          ) {            this._isDragging = true;            this._lastDragPositionX = evt.clientX;          }        },        mouseup() {          if (this._isDragging) {            this._isDragging = false;          }        },        mousemove(evt) {          if (this._isDragging) {            // Update the canvas position            this.listsDom.scrollLeft -= evt.clientX - this._lastDragPositionX;            this._lastDragPositionX = evt.clientX;            // Disable browser text selection while dragging            evt.stopPropagation();            evt.preventDefault();            // Don't close opened card or inlined form at the end of the            // click-and-drag.            EscapeActions.executeUpTo('popup-close');            EscapeActions.preventNextClick();          }        },      },    ];  },  swimlaneHeight() {    const user = ReactiveCache.getCurrentUser();    const swimlane = Template.currentData();    const height = user.getSwimlaneHeight(swimlane.boardId, swimlane._id);    return height == -1 ? "auto" : (height + 5 + "px");  },}).register('swimlane');BlazeComponent.extendComponent({  onCreated() {    this.currentBoard = Utils.getCurrentBoard();    this.isListTemplatesSwimlane =      this.currentBoard.isTemplatesBoard() &&      this.currentData().isListTemplatesSwimlane();    this.currentSwimlane = this.currentData();  },  // Proxy  open() {    this.childComponents('inlinedForm')[0].open();  },  events() {    return [      {        submit(evt) {            evt.preventDefault();            const titleInput = this.find('.list-name-input');            const title = titleInput?.value.trim();            if (!title) return;            let sortIndex = 0;            const lastList = this.currentBoard.getLastList();            const boardId = Utils.getCurrentBoardId();            const positionInput = this.find('.list-position-input');            if (positionInput) {              const positionId = positionInput.value.trim();              const selectedList = ReactiveCache.getList({ boardId, _id: positionId, archived: false });              if (selectedList) {                sortIndex = selectedList.sort + 1;              } else {                sortIndex = Utils.calculateIndexData(lastList, null).base;              }            } else {              sortIndex = Utils.calculateIndexData(lastList, null).base;            }            Lists.insert({              title,              boardId: Session.get('currentBoard'),              sort: sortIndex,              type: this.isListTemplatesSwimlane ? 'template-list' : 'list',              swimlaneId: this.currentSwimlane._id, // Always set swimlaneId for per-swimlane list titles            });            titleInput.value = '';            titleInput.focus();        }      },      {        'click .js-list-template': Popup.open('searchElement'),      },    ];  },}).register('addListForm');Template.swimlane.helpers({  canSeeAddList() {    return ReactiveCache.getCurrentUser().isBoardAdmin();  },});BlazeComponent.extendComponent({  currentCardIsInThisList(listId, swimlaneId) {    return currentCardIsInThisList(listId, swimlaneId);  },  visible(list) {    if (list.archived) {      // Show archived list only when filter archive is on      if (!Filter.archive.isSelected()) {        return false;      }    }    if (Filter.lists._isActive()) {      if (!list.title.match(Filter.lists.getRegexSelector())) {        return false;      }    }    if (Filter.hideEmpty.isSelected()) {      // Check for cards in all swimlanes, not just the current one      // This ensures lists with cards in other swimlanes are still visible      const cards = list.cards();      if (cards.length === 0) {        return false;      }    }    return true;  },  onRendered() {    const boardComponent = this.parentComponent();    const $listsDom = this.$('.js-lists');    if (!Utils.getCurrentCardId()) {      boardComponent.scrollLeft();    }    initSortable(boardComponent, $listsDom);  },}).register('listsGroup');class MoveSwimlaneComponent extends BlazeComponent {  serverMethod = 'moveSwimlane';  onCreated() {    this.currentSwimlane = this.currentData();  }  board() {    return Utils.getCurrentBoard();  }  toBoardsSelector() {    return {      archived: false,      'members.userId': Meteor.userId(),      type: 'board',      _id: { $ne: this.board()._id },    };  }  toBoards() {    const ret = ReactiveCache.getBoards(this.toBoardsSelector(), { sort: { title: 1 } });    return ret;  }  events() {    return [      {        'click .js-done'() {          const bSelect = $('.js-select-boards')[0];          let boardId;          if (bSelect) {            boardId = bSelect.options[bSelect.selectedIndex].value;            Meteor.call(this.serverMethod, this.currentSwimlane._id, boardId);          }          Popup.back();        },      },    ];  }}MoveSwimlaneComponent.register('moveSwimlanePopup');(class extends MoveSwimlaneComponent {  serverMethod = 'copySwimlane';  toBoardsSelector() {    const selector = super.toBoardsSelector();    delete selector._id;    return selector;  }}.register('copySwimlanePopup'));
 |