mirror of
https://github.com/wekan/wekan.git
synced 2026-03-08 06:32:34 +01:00
Resolve merge conflicts by accepting PR #6131 changes
Co-authored-by: xet7 <15545+xet7@users.noreply.github.com>
This commit is contained in:
parent
dc0b68ee80
commit
97dd5d2064
257 changed files with 9483 additions and 14103 deletions
|
|
@ -3,16 +3,168 @@ import { TAPi18n } from '/imports/i18n';
|
|||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import { Spinner } from '/client/lib/spinner';
|
||||
import getSlug from 'limax';
|
||||
import { itemsSelector } from './list';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
const InfiniteScrollIter = 10;
|
||||
|
||||
|
||||
function sortableCards(boardComponent, $cards) {
|
||||
return {
|
||||
connectWith: '.js-minicards:not(.js-list-full)',
|
||||
tolerance: 'pointer',
|
||||
appendTo: '.board-canvas',
|
||||
helper(evt, item) {
|
||||
const helper = item.clone();
|
||||
const cardHeight = item.height();
|
||||
const cardWidth = item.width();
|
||||
helper[0].setAttribute('style', `height: ${cardHeight}px !important; width: ${cardWidth}px !important;`);
|
||||
|
||||
if (MultiSelection.isActive()) {
|
||||
const andNOthers = $cards.find('.js-minicard.is-checked').length - 1;
|
||||
if (andNOthers > 0) {
|
||||
helper.append(
|
||||
$(
|
||||
Blaze.toHTML(
|
||||
HTML.DIV(
|
||||
{ class: 'and-n-other' },
|
||||
TAPi18n.__('and-n-other-card', { count: andNOthers }),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return helper;
|
||||
},
|
||||
distance: 7,
|
||||
items: itemsSelector,
|
||||
placeholder: 'minicard-wrapper placeholder',
|
||||
/* cursor must be tied to smaller objects, position approximately from the button
|
||||
(can be computed if visually confusing) */
|
||||
cursorAt: { right: 20, top: 30 },
|
||||
start(evt, ui) {
|
||||
const cardHeight = ui.helper.height();
|
||||
ui.placeholder[0].setAttribute('style', `height: ${cardHeight}px !important;`);
|
||||
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 prevCardDom = ui.item.prev('.js-minicard').get(0);
|
||||
const nextCardDom = ui.item.next('.js-minicard').get(0);
|
||||
const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1;
|
||||
const sortIndex = Utils.calculateIndex(prevCardDom, nextCardDom, nCards);
|
||||
const listId = Blaze.getData(ui.item.parents('.list-body').get(0))._id;
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
const defaultSwimlaneId = currentBoard.getDefaultSwimline()._id;
|
||||
let targetSwimlaneId = null;
|
||||
|
||||
// only set a new swimelane ID if the swimlanes view is active
|
||||
if (
|
||||
Utils.boardView() === 'board-view-swimlanes' ||
|
||||
currentBoard.isTemplatesBoard()
|
||||
)
|
||||
targetSwimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))
|
||||
._id;
|
||||
|
||||
// Normally the jquery-ui sortable library moves the dragged DOM element
|
||||
// to its new position, which disrupts Blaze reactive updates mechanism
|
||||
// (especially when we move the last card of a list, or when multiple
|
||||
// users move some cards at the same time). To prevent these UX glitches
|
||||
// we ask sortable to gracefully cancel the move, and to put back the
|
||||
// DOM in its initial state. The card move is then handled reactively by
|
||||
// Blaze with the below query.
|
||||
$cards.sortable('cancel');
|
||||
|
||||
if (MultiSelection.isActive()) {
|
||||
ReactiveCache.getCards(MultiSelection.getMongoSelector(), { sort: ['sort'] }).forEach((card, i) => {
|
||||
const newSwimlaneId = targetSwimlaneId
|
||||
? targetSwimlaneId
|
||||
: card.swimlaneId || defaultSwimlaneId;
|
||||
card.move(
|
||||
currentBoard._id,
|
||||
newSwimlaneId,
|
||||
listId,
|
||||
sortIndex.base + i * sortIndex.increment,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
const cardDomElement = ui.item.get(0);
|
||||
const card = Blaze.getData(cardDomElement);
|
||||
const newSwimlaneId = targetSwimlaneId
|
||||
? targetSwimlaneId
|
||||
: card.swimlaneId || defaultSwimlaneId;
|
||||
card.move(currentBoard._id, newSwimlaneId, listId, sortIndex.base);
|
||||
}
|
||||
boardComponent.setIsDragging(false);
|
||||
},
|
||||
sort(event, ui) {
|
||||
Utils.scrollIfNeeded(event);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
// for infinite scrolling
|
||||
this.cardlimit = new ReactiveVar(InfiniteScrollIter);
|
||||
},
|
||||
|
||||
onRendered() {
|
||||
// Prefer handling drag/sort in listBody rather than list as
|
||||
// it is shared between mobile and desktop view
|
||||
const boardComponent = BlazeComponent.getComponentForElement(document.getElementsByClassName('board-canvas')[0]);
|
||||
const $cards = this.$('.js-minicards');
|
||||
$cards.sortable(sortableCards(boardComponent, $cards));
|
||||
|
||||
this.autorun(() => {
|
||||
if ($cards.data('uiSortable') || $cards.data('sortable')) {
|
||||
// Use handle button on mobile, classic move otherwise
|
||||
if (Utils.isMiniScreen()) {
|
||||
$cards.sortable('option', 'handle', '.handle');
|
||||
} else {
|
||||
$cards.sortable('option', 'handle', '.minicard');
|
||||
}
|
||||
|
||||
$cards.sortable(
|
||||
'option',
|
||||
'disabled',
|
||||
// Disable drag-dropping when user is not member
|
||||
!Utils.canModifyBoard(),
|
||||
// Not disable drag-dropping while in multi-selection mode
|
||||
// MultiSelection.isActive() || !Utils.canModifyBoard(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// We want to re-run this function any time a card is added.
|
||||
this.autorun(() => {
|
||||
const currentBoardId = Tracker.nonreactive(() => {
|
||||
return Session.get('currentBoard');
|
||||
});
|
||||
Tracker.afterFlush(() => {
|
||||
$cards.find(itemsSelector).droppable({
|
||||
hoverClass: 'draggable-hover-card',
|
||||
accept: '.js-member,.js-label',
|
||||
drop(event, ui) {
|
||||
const cardId = Blaze.getData(this)._id;
|
||||
const card = ReactiveCache.getCard(cardId);
|
||||
|
||||
if (ui.draggable.hasClass('js-member')) {
|
||||
const memberId = Blaze.getData(ui.draggable.get(0)).userId;
|
||||
card.assignMember(memberId);
|
||||
} else {
|
||||
const labelId = Blaze.getData(ui.draggable.get(0))._id;
|
||||
card.addLabel(labelId);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
mixins() {
|
||||
return [];
|
||||
},
|
||||
|
|
@ -82,9 +234,10 @@ BlazeComponent.extendComponent({
|
|||
evt.preventDefault();
|
||||
const firstCardDom = this.find('.js-minicard:first');
|
||||
const lastCardDom = this.find('.js-minicard:last');
|
||||
const textarea = $(evt.currentTarget).find('textarea');
|
||||
// more robust to start from the form
|
||||
const textarea = $(evt.currentTarget).closest('.inlined-form').find('textarea');
|
||||
const position = this.currentData().position;
|
||||
const title = textarea.val().trim();
|
||||
const title = $(textarea).val().trim();
|
||||
|
||||
let sortIndex;
|
||||
if (position === 'top') {
|
||||
|
|
@ -168,7 +321,6 @@ BlazeComponent.extendComponent({
|
|||
|
||||
// We keep the form opened, empty it, and scroll to it.
|
||||
textarea.val('').focus();
|
||||
autosize.update(textarea);
|
||||
if (position === 'bottom') {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
|
@ -194,21 +346,19 @@ BlazeComponent.extendComponent({
|
|||
|
||||
clickOnMiniCard(evt) {
|
||||
if (MultiSelection.isActive() || evt.shiftKey) {
|
||||
evt.stopImmediatePropagation();
|
||||
evt.preventDefault();
|
||||
const methodName = evt.shiftKey ? 'toggleRange' : 'toggle';
|
||||
MultiSelection[methodName](this.currentData()._id);
|
||||
|
||||
// If the card is already selected, we want to de-select it.
|
||||
// XXX We should probably modify the minicard href attribute instead of
|
||||
// overwriting the event in case the card is already selected.
|
||||
} else if (Utils.isMiniScreen()) {
|
||||
evt.preventDefault();
|
||||
Session.set('popupCardId', this.currentData()._id);
|
||||
this.cardDetailsPopup(evt);
|
||||
} else if (Session.equals('currentCard', this.currentData()._id)) {
|
||||
evt.stopImmediatePropagation();
|
||||
evt.preventDefault();
|
||||
// We need to wait a little because router gets called first,
|
||||
// we probably need a level of indirection
|
||||
// #FIXME remove if it works with commits we rebased on,
|
||||
// which change the route declaration order
|
||||
Meteor.setTimeout(() => {
|
||||
Session.set('currentCard', null)
|
||||
}, 50);
|
||||
Utils.goBoardId(Session.get('currentBoard'));
|
||||
} else {
|
||||
// Allow normal href navigation, but if it's the same card URL,
|
||||
|
|
@ -283,12 +433,6 @@ BlazeComponent.extendComponent({
|
|||
return user && user.isVerticalScrollbars();
|
||||
},
|
||||
|
||||
cardDetailsPopup(event) {
|
||||
if (!Popup.isOpen()) {
|
||||
Popup.open("cardDetails")(event);
|
||||
}
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
|
|
@ -296,6 +440,8 @@ BlazeComponent.extendComponent({
|
|||
'click .js-toggle-multi-selection': this.toggleMultiSelection,
|
||||
'click .open-minicard-composer': this.scrollToBottom,
|
||||
submit: this.addCard,
|
||||
// #FIXME remove in final MR if it works
|
||||
'click .confirm': this.addCard
|
||||
},
|
||||
];
|
||||
},
|
||||
|
|
@ -401,6 +547,17 @@ BlazeComponent.extendComponent({
|
|||
'click .js-link': Popup.open('linkCard'),
|
||||
'click .js-search': Popup.open('searchElement'),
|
||||
'click .js-card-template': Popup.open('searchElement'),
|
||||
submit: this.addCard,
|
||||
'click .minicard-label': (event) => {
|
||||
const clickedData = BlazeComponent.getComponentForElement(event.target).currentData?.()
|
||||
this.labels.set(this.labels.get().filter(e => e !== clickedData?._id));
|
||||
},
|
||||
'click .member': (event) => {
|
||||
const clickedData = BlazeComponent.getComponentForElement(event.target).currentData?.()
|
||||
this.members.set(this.members.get().filter(e => e !== clickedData?.userId));
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
|
|
@ -409,8 +566,6 @@ BlazeComponent.extendComponent({
|
|||
const editor = this;
|
||||
const $textarea = this.$('textarea');
|
||||
|
||||
autosize($textarea);
|
||||
|
||||
$textarea.escapeableTextComplete(
|
||||
[
|
||||
// User mentions
|
||||
|
|
@ -421,7 +576,9 @@ BlazeComponent.extendComponent({
|
|||
callback(
|
||||
$.map(currentBoard.activeMembers(), member => {
|
||||
const user = ReactiveCache.getUser(member.userId);
|
||||
return user.username.indexOf(term) === 0 ? user : null;
|
||||
return user.username.indexOf(term) === 0 &&
|
||||
// don't show already selected members
|
||||
!editor.members.get().find((e) => e === member.userId) ? user : null;
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
|
@ -445,8 +602,12 @@ BlazeComponent.extendComponent({
|
|||
const currentBoard = Utils.getCurrentBoard();
|
||||
callback(
|
||||
$.map(currentBoard.labels, label => {
|
||||
if (label.name == undefined) {
|
||||
label.name = "";
|
||||
if (
|
||||
label.name == undefined ||
|
||||
// don't show already selected labels
|
||||
editor.getLabels().find((e) => e._id === label._id)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
label.name.indexOf(term) > -1 ||
|
||||
|
|
@ -503,10 +664,10 @@ BlazeComponent.extendComponent({
|
|||
subManager.subscribe('board', this.boardId, false);
|
||||
this.board = ReactiveCache.getBoard(this.boardId);
|
||||
// List where to insert card
|
||||
this.list = $(Popup._getTopStack().openerElement).closest('.js-list');
|
||||
this.list = $(PopupComponent.stack[0].openerElement).closest('.js-list');
|
||||
this.listId = Blaze.getData(this.list[0])._id;
|
||||
// Swimlane where to insert card
|
||||
const swimlane = $(Popup._getTopStack().openerElement).closest(
|
||||
const swimlane = $(PopupComponent.stack[0].openerElement).closest(
|
||||
'.js-swimlane',
|
||||
);
|
||||
this.swimlaneId = '';
|
||||
|
|
@ -539,10 +700,10 @@ BlazeComponent.extendComponent({
|
|||
if (!board) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
// Ensure default swimlane exists
|
||||
board.getDefaultSwimline();
|
||||
|
||||
|
||||
const swimlanes = ReactiveCache.getSwimlanes(
|
||||
{
|
||||
boardId: this.selectedBoardId.get()
|
||||
|
|
@ -559,7 +720,8 @@ BlazeComponent.extendComponent({
|
|||
}
|
||||
const lists = ReactiveCache.getLists(
|
||||
{
|
||||
boardId: this.selectedBoardId.get()
|
||||
boardId: this.selectedBoardId.get(),
|
||||
swimlaneId: this.selectedSwimlaneId?.get?.()
|
||||
},
|
||||
{
|
||||
sort: { sort: 1 },
|
||||
|
|
@ -703,16 +865,16 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
onCreated() {
|
||||
this.isCardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass(
|
||||
this.isCardTemplateSearch = $(PopupComponent.stack[0].openerElement).hasClass(
|
||||
'js-card-template',
|
||||
);
|
||||
this.isListTemplateSearch = $(Popup._getTopStack().openerElement).hasClass(
|
||||
this.isListTemplateSearch = $(PopupComponent.stack[0].openerElement).hasClass(
|
||||
'js-list-template',
|
||||
);
|
||||
this.isSwimlaneTemplateSearch = $(
|
||||
Popup._getTopStack().openerElement,
|
||||
PopupComponent.stack[0].openerElement,
|
||||
).hasClass('js-open-add-swimlane-menu');
|
||||
this.isBoardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass(
|
||||
this.isBoardTemplateSearch = $(PopupComponent.stack[0].openerElement).hasClass(
|
||||
'js-add-board',
|
||||
);
|
||||
this.isTemplateSearch =
|
||||
|
|
@ -731,20 +893,16 @@ BlazeComponent.extendComponent({
|
|||
} else {
|
||||
this.board = Utils.getCurrentBoard();
|
||||
}
|
||||
if (!this.board) {
|
||||
Popup.back();
|
||||
return;
|
||||
}
|
||||
this.boardId = this.board._id;
|
||||
this.boardId = this.board?._id;
|
||||
// Subscribe to this board
|
||||
subManager.subscribe('board', this.boardId, false);
|
||||
this.selectedBoardId = new ReactiveVar(this.boardId);
|
||||
this.list = $(Popup._getTopStack().openerElement).closest('.js-list');
|
||||
|
||||
if (!this.isBoardTemplateSearch) {
|
||||
this.list = $(PopupComponent.stack[0].openerElement).closest('.js-list');
|
||||
this.swimlaneId = '';
|
||||
// Swimlane where to insert card
|
||||
const swimlane = $(Popup._getTopStack().openerElement).parents(
|
||||
const swimlane = $(PopupComponent.stack[0].openerElement).parents(
|
||||
'.js-swimlane',
|
||||
);
|
||||
if (Utils.boardView() === 'board-view-swimlanes')
|
||||
|
|
@ -783,11 +941,7 @@ BlazeComponent.extendComponent({
|
|||
} else if (this.isSwimlaneTemplateSearch) {
|
||||
return board.searchSwimlanes(this.term.get());
|
||||
} else if (this.isBoardTemplateSearch) {
|
||||
const boards = board.searchBoards(this.term.get());
|
||||
boards.forEach(board => {
|
||||
subManager.subscribe('board', board.linkedId, false);
|
||||
});
|
||||
return boards;
|
||||
return board.searchBoards(this.term.get());
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue