mirror of https://github.com/wekan/wekan
The Open Source kanban (built with Meteor). Keep variable/table/field names camelCase. For translations, only add Pull Request changes to wekan/i18n/en.i18n.json , other translations are done at https://transifex.com/wekan/wekan only.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
384 lines
11 KiB
384 lines
11 KiB
import { ReactiveCache } from '/imports/reactiveCache';
|
|
import { TAPi18n } from '/imports/i18n';
|
|
import Cards from '/models/cards';
|
|
import Boards from '/models/boards';
|
|
import { DialogWithBoardSwimlaneListCard } from '/client/lib/dialogWithBoardSwimlaneListCard';
|
|
|
|
const subManager = new SubsManager();
|
|
const { calculateIndexData, capitalize } = Utils;
|
|
|
|
function initSorting(items) {
|
|
items.sortable({
|
|
tolerance: 'pointer',
|
|
helper: 'clone',
|
|
items: '.js-checklist-item:not(.placeholder)',
|
|
connectWith: '.js-checklist-items',
|
|
appendTo: 'parent',
|
|
distance: 7,
|
|
placeholder: 'checklist-item placeholder',
|
|
scroll: true,
|
|
start(evt, ui) {
|
|
ui.placeholder.height(ui.helper.height());
|
|
EscapeActions.clickExecute(evt.target, 'inlinedForm');
|
|
},
|
|
stop(evt, ui) {
|
|
const parent = ui.item.parents('.js-checklist-items');
|
|
const checklistId = Blaze.getData(parent.get(0)).checklist._id;
|
|
let prevItem = ui.item.prev('.js-checklist-item').get(0);
|
|
if (prevItem) {
|
|
prevItem = Blaze.getData(prevItem).item;
|
|
}
|
|
let nextItem = ui.item.next('.js-checklist-item').get(0);
|
|
if (nextItem) {
|
|
nextItem = Blaze.getData(nextItem).item;
|
|
}
|
|
const nItems = 1;
|
|
const sortIndex = calculateIndexData(prevItem, nextItem, nItems);
|
|
const checklistDomElement = ui.item.get(0);
|
|
const checklistData = Blaze.getData(checklistDomElement);
|
|
const checklistItem = checklistData.item;
|
|
|
|
items.sortable('cancel');
|
|
|
|
checklistItem.move(checklistId, sortIndex.base);
|
|
},
|
|
});
|
|
}
|
|
|
|
BlazeComponent.extendComponent({
|
|
onRendered() {
|
|
const self = this;
|
|
self.itemsDom = this.$('.js-checklist-items');
|
|
initSorting(self.itemsDom);
|
|
self.itemsDom.mousedown(function (evt) {
|
|
evt.stopPropagation();
|
|
});
|
|
|
|
function userIsMember() {
|
|
return Meteor.user() && Meteor.user().isBoardMember();
|
|
}
|
|
|
|
// Disable sorting if the current user is not a board member
|
|
self.autorun(() => {
|
|
const $itemsDom = $(self.itemsDom);
|
|
if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
|
|
$(self.itemsDom).sortable('option', 'disabled', !userIsMember());
|
|
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
|
$(self.itemsDom).sortable({
|
|
handle: 'span.fa.checklistitem-handle',
|
|
});
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
/** returns the finished percent of the checklist */
|
|
finishedPercent() {
|
|
const ret = this.data().checklist.finishedPercent();
|
|
return ret;
|
|
},
|
|
}).register('checklistDetail');
|
|
|
|
BlazeComponent.extendComponent({
|
|
addChecklist(event) {
|
|
event.preventDefault();
|
|
const textarea = this.find('textarea.js-add-checklist-item');
|
|
const title = textarea.value.trim();
|
|
let cardId = this.currentData().cardId;
|
|
const card = Cards.findOne(cardId);
|
|
//if (card.isLinked()) cardId = card.linkedId;
|
|
if (card.isLinkedCard()) cardId = card.linkedId;
|
|
|
|
let sortIndex;
|
|
let checklistItemIndex;
|
|
if (this.currentData().position === 'top') {
|
|
sortIndex = Utils.calculateIndexData(null, card.firstChecklist()).base;
|
|
checklistItemIndex = 0;
|
|
} else {
|
|
sortIndex = Utils.calculateIndexData(card.lastChecklist(), null).base;
|
|
checklistItemIndex = -1;
|
|
}
|
|
|
|
if (title) {
|
|
Checklists.insert({
|
|
cardId,
|
|
title,
|
|
sort: sortIndex,
|
|
});
|
|
this.closeAllInlinedForms();
|
|
setTimeout(() => {
|
|
this.$('.add-checklist-item')
|
|
.eq(checklistItemIndex)
|
|
.click();
|
|
}, 100);
|
|
}
|
|
},
|
|
addChecklistItem(event) {
|
|
event.preventDefault();
|
|
const textarea = this.find('textarea.js-add-checklist-item');
|
|
const newlineBecomesNewChecklistItem = this.find('input#toggleNewlineBecomesNewChecklistItem');
|
|
const title = textarea.value.trim();
|
|
const checklist = this.currentData().checklist;
|
|
|
|
if (title) {
|
|
let checklistItems = [title];
|
|
if (newlineBecomesNewChecklistItem.checked) {
|
|
checklistItems = title.split('\n').map(_value => _value.trim());
|
|
if (this.currentData().position === 'top') {
|
|
checklistItems = checklistItems.reverse();
|
|
}
|
|
}
|
|
for (let checklistItem of checklistItems) {
|
|
let sortIndex;
|
|
if (this.currentData().position === 'top') {
|
|
sortIndex = Utils.calculateIndexData(null, checklist.firstItem()).base;
|
|
} else {
|
|
sortIndex = Utils.calculateIndexData(checklist.lastItem(), null).base;
|
|
}
|
|
ChecklistItems.insert({
|
|
title: checklistItem,
|
|
checklistId: checklist._id,
|
|
cardId: checklist.cardId,
|
|
sort: sortIndex,
|
|
});
|
|
}
|
|
}
|
|
// We keep the form opened, empty it.
|
|
textarea.value = '';
|
|
textarea.focus();
|
|
},
|
|
|
|
deleteItem() {
|
|
const checklist = this.currentData().checklist;
|
|
const item = this.currentData().item;
|
|
if (checklist && item && item._id) {
|
|
ChecklistItems.remove(item._id);
|
|
}
|
|
},
|
|
|
|
editChecklist(event) {
|
|
event.preventDefault();
|
|
const textarea = this.find('textarea.js-edit-checklist-item');
|
|
const title = textarea.value.trim();
|
|
const checklist = this.currentData().checklist;
|
|
checklist.setTitle(title);
|
|
},
|
|
|
|
editChecklistItem(event) {
|
|
event.preventDefault();
|
|
|
|
const textarea = this.find('textarea.js-edit-checklist-item');
|
|
const title = textarea.value.trim();
|
|
const item = this.currentData().item;
|
|
item.setTitle(title);
|
|
},
|
|
|
|
pressKey(event) {
|
|
//If user press enter key inside a form, submit it
|
|
//Unless the user is also holding down the 'shift' key
|
|
if (event.keyCode === 13 && !event.shiftKey) {
|
|
event.preventDefault();
|
|
const $form = $(event.currentTarget).closest('form');
|
|
$form.find('button[type=submit]').click();
|
|
}
|
|
},
|
|
|
|
focusChecklistItem(event) {
|
|
// If a new checklist is created, pre-fill the title and select it.
|
|
const checklist = this.currentData().checklist;
|
|
if (!checklist) {
|
|
const textarea = event.target;
|
|
textarea.value = capitalize(TAPi18n.__('r-checklist'));
|
|
textarea.select();
|
|
}
|
|
},
|
|
|
|
/** closes all inlined forms (checklist and checklist-item input fields) */
|
|
closeAllInlinedForms() {
|
|
this.$('.js-close-inlined-form').click();
|
|
},
|
|
|
|
events() {
|
|
const events = {
|
|
'click #toggleHideCheckedItemsButton'() {
|
|
Meteor.call('toggleHideCheckedItems');
|
|
},
|
|
};
|
|
|
|
return [
|
|
{
|
|
...events,
|
|
'click .js-open-checklist-details-menu': Popup.open('checklistActions'),
|
|
'submit .js-add-checklist': this.addChecklist,
|
|
'submit .js-edit-checklist-title': this.editChecklist,
|
|
'submit .js-add-checklist-item': this.addChecklistItem,
|
|
'submit .js-edit-checklist-item': this.editChecklistItem,
|
|
'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'),
|
|
'click .js-delete-checklist-item': this.deleteItem,
|
|
'focus .js-add-checklist-item': this.focusChecklistItem,
|
|
// add and delete checklist / checklist-item
|
|
'click .js-open-inlined-form': this.closeAllInlinedForms,
|
|
keydown: this.pressKey,
|
|
},
|
|
];
|
|
},
|
|
}).register('checklists');
|
|
|
|
BlazeComponent.extendComponent({
|
|
onCreated() {
|
|
subManager.subscribe('board', Session.get('currentBoard'), false);
|
|
this.selectedBoardId = new ReactiveVar(Session.get('currentBoard'));
|
|
},
|
|
|
|
boards() {
|
|
return Boards.find(
|
|
{
|
|
archived: false,
|
|
'members.userId': Meteor.userId(),
|
|
_id: { $ne: Meteor.user().getTemplatesBoardId() },
|
|
},
|
|
{
|
|
sort: { sort: 1 /* boards default sorting */ },
|
|
},
|
|
);
|
|
},
|
|
|
|
swimlanes() {
|
|
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
|
return board.swimlanes();
|
|
},
|
|
|
|
aBoardLists() {
|
|
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
|
return board.lists();
|
|
},
|
|
|
|
events() {
|
|
return [
|
|
{
|
|
'change .js-select-boards'(event) {
|
|
this.selectedBoardId.set($(event.currentTarget).val());
|
|
subManager.subscribe('board', this.selectedBoardId.get(), false);
|
|
},
|
|
},
|
|
];
|
|
},
|
|
}).register('boardsSwimlanesAndLists');
|
|
|
|
Template.checklists.helpers({
|
|
checklists() {
|
|
const card = Cards.findOne(this.cardId);
|
|
const ret = card.checklists();
|
|
return ret;
|
|
},
|
|
hideCheckedItems() {
|
|
const currentUser = Meteor.user();
|
|
if (currentUser) return currentUser.hasHideCheckedItems();
|
|
return false;
|
|
},
|
|
});
|
|
|
|
BlazeComponent.extendComponent({
|
|
onRendered() {
|
|
autosize(this.$('textarea.js-add-checklist-item'));
|
|
},
|
|
events() {
|
|
return [
|
|
{
|
|
'click a.fa.fa-copy'(event) {
|
|
const $editor = this.$('textarea');
|
|
const promise = Utils.copyTextToClipboard($editor[0].value);
|
|
|
|
const $tooltip = this.$('.copied-tooltip');
|
|
Utils.showCopied(promise, $tooltip);
|
|
},
|
|
}
|
|
];
|
|
}
|
|
}).register('addChecklistItemForm');
|
|
|
|
BlazeComponent.extendComponent({
|
|
events() {
|
|
return [
|
|
{
|
|
'click .js-delete-checklist': Popup.afterConfirm('checklistDelete', function () {
|
|
Popup.back(2);
|
|
const checklist = this.checklist;
|
|
if (checklist && checklist._id) {
|
|
Checklists.remove(checklist._id);
|
|
}
|
|
}),
|
|
'click .js-move-checklist': Popup.open('moveChecklist'),
|
|
'click .js-copy-checklist': Popup.open('copyChecklist'),
|
|
}
|
|
]
|
|
}
|
|
}).register('checklistActionsPopup');
|
|
|
|
BlazeComponent.extendComponent({
|
|
onRendered() {
|
|
autosize(this.$('textarea.js-edit-checklist-item'));
|
|
},
|
|
events() {
|
|
return [
|
|
{
|
|
'click a.fa.fa-copy'(event) {
|
|
const $editor = this.$('textarea');
|
|
const promise = Utils.copyTextToClipboard($editor[0].value);
|
|
|
|
const $tooltip = this.$('.copied-tooltip');
|
|
Utils.showCopied(promise, $tooltip);
|
|
},
|
|
}
|
|
];
|
|
}
|
|
}).register('editChecklistItemForm');
|
|
|
|
Template.checklistItemDetail.helpers({
|
|
hideCheckedItems() {
|
|
const user = Meteor.user();
|
|
if (user) return user.hasHideCheckedItems();
|
|
return false;
|
|
},
|
|
});
|
|
|
|
BlazeComponent.extendComponent({
|
|
toggleItem() {
|
|
const checklist = this.currentData().checklist;
|
|
const item = this.currentData().item;
|
|
if (checklist && item && item._id) {
|
|
item.toggleItem();
|
|
}
|
|
},
|
|
events() {
|
|
return [
|
|
{
|
|
'click .js-checklist-item .check-box-container': this.toggleItem,
|
|
},
|
|
];
|
|
},
|
|
}).register('checklistItemDetail');
|
|
|
|
/** Move Checklist Dialog */
|
|
(class extends DialogWithBoardSwimlaneListCard {
|
|
getDialogOptions() {
|
|
const ret = Meteor.user().getMoveChecklistDialogOptions();
|
|
return ret;
|
|
}
|
|
setDone(cardId, options) {
|
|
Meteor.user().setMoveChecklistDialogOption(this.currentBoardId, options);
|
|
this.data().checklist.move(cardId);
|
|
}
|
|
}).register('moveChecklistPopup');
|
|
|
|
/** Copy Checklist Dialog */
|
|
(class extends DialogWithBoardSwimlaneListCard {
|
|
getDialogOptions() {
|
|
const ret = Meteor.user().getCopyChecklistDialogOptions();
|
|
return ret;
|
|
}
|
|
setDone(cardId, options) {
|
|
Meteor.user().setCopyChecklistDialogOption(this.currentBoardId, options);
|
|
this.data().checklist.copy(cardId);
|
|
}
|
|
}).register('copyChecklistPopup');
|
|
|