Compare commits

...
45 Commits
Author SHA1 Message Date
Lauri Ojansivu bd2c54398b v2.33 2019-02-28 23:31:39 +02:00
Lauri Ojansivu e65776f9f0 [Upgrade Node.js to v8.15.1](https://github.com/wekan/wekan/commit/5cafdd9878ab4b6123024ec33279ccdae75f554f).
Thanks to Node.js developers and xet7 !
2019-02-28 23:29:13 +02:00
Lauri Ojansivu 5cafdd9878 Upgrade node to v8.15.1
Thanks to xet7 !
2019-02-28 23:25:03 +02:00
Lauri Ojansivu 49882a05d1 v2.32 2019-02-28 23:01:56 +02:00
Lauri Ojansivu 8e36a2bc83 [Fix filtering in swimlane view](https://github.com/wekan/wekan/commit/49229e1723de14cdc66dc6480624bba426d35e36)
that was [broken since v2.29](https://github.com/wekan/wekan/issues/2213).

Thanks to andresmanelli !

Closes #2213,
Related #2209
2019-02-28 22:57:40 +02:00
Lauri Ojansivu 24e47195e5 - [Fix](https://github.com/wekan/wekan/commit/49229e1723de14cdc66dc6480624bba426d35e36)
[Filtering in swimlane view is broken since v2.29](https://github.com/wekan/wekan/issues/2213).

Thanks to andresmanelli !

Related #2209
2019-02-28 22:45:37 +02:00
Lauri Ojansivu 4b37958140 Merge branch 'devel' into edge 2019-02-28 22:39:02 +02:00
Lauri Ojansivu 3edff32bde [Performance improvements](https://github.com/wekan/wekan/pull/2214), thanks to justinr1234:
- New indexes for queries that were missing an index;
- Bulk querying documents to reduce the number of mongo queries when loading a board;
- Ensure oplog is being used to query the database by providing a `sort` key when `limit` is used querying the `boards` collection.
2019-02-28 22:37:28 +02:00
Lauri Ojansivu 78c5e0f7ef Merge branch 'justinr1234-wekan-perf-improvements' into devel 2019-02-28 22:32:33 +02:00
Andrés Manelli 49229e1723 Fix filter in swimlanes view 2019-02-28 20:46:50 +01:00
Lauri Ojansivu 14e2bfd1bf Merge branch 'wekan-perf-improvements' of https://github.com/justinr1234/wekan into justinr1234-wekan-perf-improvements 2019-02-28 21:19:50 +02:00
Justin Reynolds 3c49e2d0ed Performance Enhancements 2019-02-28 11:44:29 -06:00
Lauri Ojansivu d5bc66c9a4 v2.31 2019-02-28 12:41:07 +02:00
Lauri Ojansivu 4f6977668b Update translations. 2019-02-28 12:37:36 +02:00
Lauri Ojansivu 17826c28e5 - [Fix copy card](https://github.com/wekan/wekan/issues/2210).
Thanks to andresmanelli !

Closes #2210,
Related #2209
2019-02-28 12:34:48 +02:00
Lauri Ojansivu a3d71dd694 Merge remote-tracking branch 'andresmanelli/edge' into edge 2019-02-28 12:29:14 +02:00
Andrés Manelli a5c39342ba Fix card copy 2019-02-28 08:10:33 +01:00
Lauri Ojansivu ad75c541cf v2.30 2019-02-28 00:52:36 +02:00
Lauri Ojansivu cd46cbeb8f Add new [Template features](https://github.com/wekan/wekan/issues/2209):
- [Fix popup title. Add element title modification](https://github.com/wekan/wekan/commit/888e1ad5d3e32be53283aa32198057f669f3d706);
- [Copy template attachments](https://github.com/wekan/wekan/commit/abb71083215462d91b084c4de13af0b130638e4d);
- [Standarize copy functions. Match labels by name](https://github.com/wekan/wekan/commit/da21a2a410c9b905de89d66236748e0c8f5357ea).

Thanks to andresmanelli !

Related #2209
2019-02-28 00:44:13 +02:00
Andrés Manelli 888e1ad5d3 Fix popup title
Add element title modification
2019-02-27 21:50:08 +01:00
Andrés Manelli abb7108321 Copy template attachments 2019-02-27 21:50:08 +01:00
Andrés Manelli da21a2a410 Standarize copy functions. Match labels by name 2019-02-27 21:50:08 +01:00
Lauri Ojansivu 904b5bf0f5 v2.29 2019-02-27 21:01:32 +02:00
Lauri Ojansivu d21aa97219 Update translations. 2019-02-27 20:39:56 +02:00
Andrés Manelli 6ee75fbdba Fix templates board not found 2019-02-27 19:34:53 +01:00
Lauri Ojansivu c3b2a0c955 Merge branch 'devel' into edge 2019-02-27 17:29:21 +02:00
Lauri Ojansivu 0ac42c145e Fix typo. 2019-02-27 17:28:44 +02:00
Lauri Ojansivu 4a77bc930f Merge branch 'devel' into edge 2019-02-27 16:26:48 +02:00
Lauri Ojansivu 8d51245161 Merge branch 'devel' into edge 2019-02-27 06:20:30 +02:00
Lauri Ojansivu a707129146 Removed console.log. 2019-02-26 16:51:24 +02:00
Lauri Ojansivu 2f3138207c Update translations. 2019-02-26 00:28:01 +02:00
Andrés Manelli dc7286a0ef Fix list view issues. Allow creation of boards from templates 2019-02-25 22:48:32 +01:00
Lauri Ojansivu 13c2157e36 Update translations. 2019-02-25 06:14:43 +02:00
Andrés Manelli 7033315cd3 Add migrations 2019-02-24 12:55:34 +01:00
Andrés Manelli 775476f97c Fix miniscreen render 2019-02-24 11:54:52 +01:00
Andrés Manelli eb62c9ce6a Fix lint errors 2019-02-24 00:13:35 +01:00
Andrés Manelli 60be4df76e Allow swimlane creation from template
Mix lists with same name to avoid duplicates
2019-02-24 00:05:00 +01:00
Andrés Manelli f888cfd565 Allow list creation from template 2019-02-24 00:05:00 +01:00
Andrés Manelli 044126188d Allow card creation from template 2019-02-24 00:05:00 +01:00
Andrés Manelli 0fec711545 Prepare to create card from template 2019-02-24 00:05:00 +01:00
Andrés Manelli 7a6afb8aea Add template search in Add Card menu
Archive all cards in list when list is archived
Remove default board in link popup
Only list non-template boards in card link and search
2019-02-24 00:05:00 +01:00
Andrés Manelli 1e72177991 Avoid links on a template-board
Allow creation of template boards with a linked card
Avoid changing the name of the template-container swimlanes
2019-02-24 00:05:00 +01:00
Andrés Manelli cdf070189e Remove links from templates board for the moment
Insert the correct template type in templates board
Allow independant lists in templates board
Add some helpers
2019-02-24 00:05:00 +01:00
Andrés Manelli 64bf455b29 Save template swimlanes in profile. Fix swimlane view for templates board. Avoid deleting template containers 2019-02-24 00:05:00 +01:00
Andrés Manelli 0a53ee87b9 Add first draft of data model and user interface. No actions. 2019-02-24 00:05:00 +01:00
90 changed files with 987 additions and 262 deletions

No files matched your search

+1
View File
@@ -91,3 +91,4 @@ wekan:accounts-cas
wekan-scrollbar
mquandalle:perfect-scrollbar
mdg:meteor-apm-agent
meteorhacks:unblock
+1
View File
@@ -94,6 +94,7 @@ meteorhacks:collection-utils@1.2.0
meteorhacks:meteorx@1.4.1
meteorhacks:picker@1.0.3
meteorhacks:subs-manager@1.6.4
meteorhacks:unblock@1.1.0
meteorspark:util@0.2.0
minifier-css@1.2.16
minifier-js@2.2.2
+44 -1
View File
@@ -1,3 +1,46 @@
# v2.33 2019-02-28 Wekan release
This release adds the following upgrades:
- [Upgrade Node.js to v8.15.1](https://github.com/wekan/wekan/commit/5cafdd9878ab4b6123024ec33279ccdae75f554f).
Thanks to Node.js developers and GitHub user xet7 for contributions.
# v2.32 2019-02-28 Wekan release
This release adds the following [performance improvements](https://github.com/wekan/wekan/pull/2214), thanks to justinr1234:
- New indexes for queries that were missing an index;
- Bulk querying documents to reduce the number of mongo queries when loading a board;
- Ensure oplog is being used to query the database by providing a `sort` key when `limit` is used querying the `boards` collection.
and fixes the following bugs related to [Template features](https://github.com/wekan/wekan/issues/2209), thanks to andresmanelli:
- [Fix filtering in swimlane view](https://github.com/wekan/wekan/commit/49229e1723de14cdc66dc6480624bba426d35e36) that was [broken since v2.29](https://github.com/wekan/wekan/issues/2213).
Thanks to above GitHub users for their contributions.
# v2.31 2019-02-28 Wekan release
This release fixes the following bugs related to [Template features](https://github.com/wekan/wekan/issues/2209), thanks to GitHub user andresmanelli:
- [Fix copy card](https://github.com/wekan/wekan/issues/2210).
# v2.30 2019-02-28 Wekan release
This release adds the following new [Template features](https://github.com/wekan/wekan/issues/2209), thanks to GitHub user andresmanelli:
- [Fix popup title. Add element title modification](https://github.com/wekan/wekan/commit/888e1ad5d3e32be53283aa32198057f669f3d706);
- [Copy template attachments](https://github.com/wekan/wekan/commit/abb71083215462d91b084c4de13af0b130638e4d);
- [Standarize copy functions. Match labels by name](https://github.com/wekan/wekan/commit/da21a2a410c9b905de89d66236748e0c8f5357ea).
# v2.29 2019-02-27 Wekan release
This release adds the following new features:
- Swimlane/List/Board/Card templates. In Progress, please test and [add comment if you find not listed bugs](https://github.com/wekan/wekan/issues/2165).
Thanks to GitHub user andresmanelli.
# v2.28 2019-02-27 Wekan release
This release adds the following new Sandstorm features and fixes:
@@ -5,7 +48,7 @@ This release adds the following new Sandstorm features and fixes:
- All Boards page [so it's possible to go back from subtask board](https://github.com/wekan/wekan/issues/2082).
- Board favorites.
- New Sandstorm board first user is Admin and [has IFTTT Rules](https://github.com/wekan/wekan/issues/2125) and Standalone Wekan Admin Panel.
Probably some Admin Panel features do not work yet. Please keep backup of your grains before testig Admin Panel.
Probably some Admin Panel features do not work yet. Please keep backup of your grains before testing Admin Panel.
- Linked Cards and Linked Boards.
- Some not needed options like Logout etc have been hidden from top bar right menu.
- [Import board now works. "Board not found" is not problem anymore](https://github.com/wekan/wekan/issues/1430), because you can go to All Boards page to change to imported board.
+1 -1
View File
@@ -87,7 +87,7 @@ ARG DEFAULT_AUTHENTICATION_METHOD
# ENV BUILD_DEPS="paxctl"
ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential python python3 python3-distutils git ca-certificates gcc-7" \
DEBUG=false \
NODE_VERSION=v8.15.0 \
NODE_VERSION=v8.15.1 \
METEOR_RELEASE=1.6.0.1 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v2.28.0"
appVersion: "v2.33.0"
files:
userUploads:
- README.md
-6
View File
@@ -1,9 +1,3 @@
Template.boardListHeaderBar.events({
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
},
});
BlazeComponent.extendComponent({
onCreated() {
this.subscribe('archivedBoards');
+7 -4
View File
@@ -20,12 +20,15 @@ template(name="boardBody")
class="{{#if draggingActive.get}}is-dragging-active{{/if}}")
if showOverlay.get
.board-overlay
if isViewSwimlanes
if currentBoard.isTemplatesBoard
each currentBoard.swimlanes
+swimlane(this)
if isViewLists
+listsGroup
if isViewCalendar
else if isViewSwimlanes
each currentBoard.swimlanes
+swimlane(this)
else if isViewLists
+listsGroup(currentBoard)
else if isViewCalendar
+calendarView
template(name="calendarView")
+11 -6
View File
@@ -94,10 +94,11 @@ template(name="boardHeaderBar")
i.fa.fa-search
span {{_ 'search'}}
a.board-header-btn.js-toggle-board-view(
title="{{_ 'board-view'}}")
i.fa.fa-th-large
span {{_ currentUser.profile.boardView}}
unless currentBoard.isTemplatesBoard
a.board-header-btn.js-toggle-board-view(
title="{{_ 'board-view'}}")
i.fa.fa-th-large
span {{_ currentUser.profile.boardView}}
if canModifyBoard
a.board-header-btn.js-multiselection-activate(
@@ -130,7 +131,8 @@ template(name="boardMenuPopup")
hr
ul.pop-over-list
li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
li: a.js-archive-board {{_ 'archive-board'}}
unless currentBoard.isTemplatesBoard
li: a.js-archive-board {{_ 'archive-board'}}
li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
hr
ul.pop-over-list
@@ -275,7 +277,10 @@ template(name="createBoard")
input.primary.wide(type="submit" value="{{_ 'create'}}")
span.quiet
| {{_ 'or'}}
a.js-import-board {{_ 'import-board'}}
a.js-import-board {{_ 'import'}}
span.quiet
| /
a.js-board-template {{_ 'template'}}
template(name="chooseBoardSource")
ul.pop-over-list
+1
View File
@@ -304,6 +304,7 @@ const CreateBoard = BlazeComponent.extendComponent({
'click .js-import': Popup.open('boardImportBoard'),
submit: this.onSubmit,
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-board-template': Popup.open('searchElement'),
}];
},
}).register('createBoardPopup');
+3
View File
@@ -36,3 +36,6 @@ template(name="boardListHeaderBar")
a.board-header-btn.js-open-archived-board
i.fa.fa-archive
span {{_ 'archives'}}
a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
i.fa.fa-clone
span {{_ 'templates'}}
+16
View File
@@ -1,5 +1,20 @@
const subManager = new SubsManager();
Template.boardListHeaderBar.events({
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
},
});
Template.boardListHeaderBar.helpers({
templatesBoardId() {
return Meteor.user().getTemplatesBoardId();
},
templatesBoardSlug() {
return Meteor.user().getTemplatesBoardSlug();
},
});
BlazeComponent.extendComponent({
onCreated() {
Meteor.subscribe('setting');
@@ -9,6 +24,7 @@ BlazeComponent.extendComponent({
return Boards.find({
archived: false,
'members.userId': Meteor.userId(),
type: 'board',
}, {
sort: ['title'],
});
+8
View File
@@ -0,0 +1,8 @@
template(name="miniboard")
.minicard(
class="minicard-{{colorClass}}")
.minicard-title
.handle
.fa.fa-arrows
+viewer
= title
+8 -56
View File
@@ -456,32 +456,15 @@ BlazeComponent.extendComponent({
},
}).register('boardsAndLists');
function cloneCheckList(_id, checklist) {
'use strict';
const checklistId = checklist._id;
checklist.cardId = _id;
checklist._id = null;
const newChecklistId = Checklists.insert(checklist);
ChecklistItems.find({checklistId}).forEach(function(item) {
item._id = null;
item.checklistId = newChecklistId;
item.cardId = _id;
ChecklistItems.insert(item);
});
}
Template.copyCardPopup.events({
'click .js-done'() {
const card = Cards.findOne(Session.get('currentCard'));
const oldId = card._id;
card._id = null;
const lSelect = $('.js-select-lists')[0];
card.listId = lSelect.options[lSelect.selectedIndex].value;
listId = lSelect.options[lSelect.selectedIndex].value;
const slSelect = $('.js-select-swimlanes')[0];
card.swimlaneId = slSelect.options[slSelect.selectedIndex].value;
const swimlaneId = slSelect.options[slSelect.selectedIndex].value;
const bSelect = $('.js-select-boards')[0];
card.boardId = bSelect.options[bSelect.selectedIndex].value;
const boardId = bSelect.options[bSelect.selectedIndex].value;
const textarea = $('#copy-card-title');
const title = textarea.val().trim();
// insert new card to the bottom of new list
@@ -490,38 +473,13 @@ Template.copyCardPopup.events({
if (title) {
card.title = title;
card.coverId = '';
const _id = Cards.insert(card);
const _id = card.copy(boardId, swimlaneId, listId);
// In case the filter is active we need to add the newly inserted card in
// the list of exceptions -- cards that are not filtered. Otherwise the
// card will disappear instantly.
// See https://github.com/wekan/wekan/issues/80
Filter.addException(_id);
// copy checklists
let cursor = Checklists.find({cardId: oldId});
cursor.forEach(function() {
cloneCheckList(_id, arguments[0]);
});
// copy subtasks
cursor = Cards.find({parentId: oldId});
cursor.forEach(function() {
'use strict';
const subtask = arguments[0];
subtask.parentId = _id;
subtask._id = null;
/* const newSubtaskId = */ Cards.insert(subtask);
});
// copy card comments
cursor = CardComments.find({cardId: oldId});
cursor.forEach(function () {
'use strict';
const comment = arguments[0];
comment.cardId = _id;
comment._id = null;
CardComments.insert(comment);
});
Popup.close();
}
},
@@ -558,9 +516,8 @@ Template.copyChecklistToManyCardsPopup.events({
Filter.addException(_id);
// copy checklists
let cursor = Checklists.find({cardId: oldId});
cursor.forEach(function() {
cloneCheckList(_id, arguments[0]);
Checklists.find({cardId: oldId}).forEach((ch) => {
ch.copy(_id);
});
// copy subtasks
@@ -574,13 +531,8 @@ Template.copyChecklistToManyCardsPopup.events({
});
// copy card comments
cursor = CardComments.find({cardId: oldId});
cursor.forEach(function () {
'use strict';
const comment = arguments[0];
comment.cardId = _id;
comment._id = null;
CardComments.insert(comment);
CardComments.find({cardId: oldId}).forEach((cmt) => {
cmt.copy(_id);
});
}
Popup.close();
+46 -22
View File
@@ -44,13 +44,19 @@ template(name="addCardForm")
.add-controls.clearfix
button.primary.confirm(type="submit") {{_ 'add'}}
span.quiet
| {{_ 'or'}}
a.js-link {{_ 'link'}}
span.quiet
|  
| /
a.js-search {{_ 'search'}}
unless currentBoard.isTemplatesBoard
unless currentBoard.isTemplateBoard
span.quiet
| {{_ 'or'}}
a.js-link {{_ 'link'}}
span.quiet
|  
| /
a.js-search {{_ 'search'}}
span.quiet
|  
| /
a.js-card-template {{_ 'template'}}
template(name="autocompleteLabelLine")
.minicard-label(class="card-label-{{colorName}}" title=labelName)
@@ -60,11 +66,9 @@ template(name="linkCardPopup")
label {{_ 'boards'}}:
.link-board-wrapper
select.js-select-boards
option(value="")
each boards
if $eq _id currentBoard._id
option(value="{{_id}}" selected) {{_ 'current'}}
else
option(value="{{_id}}") {{title}}
option(value="{{_id}}") {{title}}
input.primary.confirm.js-link-board(type="button" value="{{_ 'link'}}")
label {{_ 'swimlanes'}}:
@@ -85,19 +89,39 @@ template(name="linkCardPopup")
.edit-controls.clearfix
input.primary.confirm.js-done(type="button" value="{{_ 'link'}}")
template(name="searchCardPopup")
label {{_ 'boards'}}:
.link-board-wrapper
select.js-select-boards
each boards
if $eq _id currentBoard._id
option(value="{{_id}}" selected) {{_ 'current'}}
else
template(name="searchElementPopup")
form
label
| {{_ 'title'}}
input.js-element-title(type="text" placeholder="{{_ 'title'}}" autofocus required)
unless isTemplateSearch
label {{_ 'boards'}}:
.link-board-wrapper
select.js-select-boards
option(value="")
each boards
option(value="{{_id}}") {{title}}
form.js-search-term-form
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus)
.list-body.js-perfect-scrollbar.search-card-results
.minicards.clearfix.js-minicards
each results
a.minicard-wrapper.js-minicard
+minicard(this)
if isBoardTemplateSearch
each results
a.minicard-wrapper.js-minicard
+miniboard(this)
if isListTemplateSearch
each results
a.minicard-wrapper.js-minicard
+minilist(this)
if isSwimlaneTemplateSearch
each results
a.minicard-wrapper.js-minicard
+miniswimlane(this)
if isCardTemplateSearch
each results
a.minicard-wrapper.js-minicard
+minicard(this)
unless isTemplateSearch
each results
a.minicard-wrapper.js-minicard
+minicard(this)
+137 -63
View File
@@ -67,25 +67,47 @@ BlazeComponent.extendComponent({
const labelIds = formComponent.labels.get();
const customFields = formComponent.customFields.get();
const boardId = this.data().board();
const board = this.data().board();
let linkedId = '';
let swimlaneId = '';
const boardView = Meteor.user().profile.boardView;
if (boardView === 'board-view-swimlanes')
swimlaneId = this.parentComponent().parentComponent().data()._id;
else if ((boardView === 'board-view-lists') || (boardView === 'board-view-cal'))
swimlaneId = boardId.getDefaultSwimline()._id;
let cardType = 'cardType-card';
if (title) {
if (board.isTemplatesBoard()) {
swimlaneId = this.parentComponent().parentComponent().data()._id; // Always swimlanes view
const swimlane = Swimlanes.findOne(swimlaneId);
// If this is the card templates swimlane, insert a card template
if (swimlane.isCardTemplatesSwimlane())
cardType = 'template-card';
// If this is the board templates swimlane, insert a board template and a linked card
else if (swimlane.isBoardTemplatesSwimlane()) {
linkedId = Boards.insert({
title,
permission: 'private',
type: 'template-board',
});
Swimlanes.insert({
title: TAPi18n.__('default'),
boardId: linkedId,
});
cardType = 'cardType-linkedBoard';
}
} else if (boardView === 'board-view-swimlanes')
swimlaneId = this.parentComponent().parentComponent().data()._id;
else if ((boardView === 'board-view-lists') || (boardView === 'board-view-cal'))
swimlaneId = board.getDefaultSwimline()._id;
const _id = Cards.insert({
title,
members,
labelIds,
customFields,
listId: this.data()._id,
boardId: boardId._id,
boardId: board._id,
sort: sortIndex,
swimlaneId,
type: 'cardType-card',
type: cardType,
linkedId,
});
// if the displayed card count is less than the total cards in the list,
@@ -127,9 +149,9 @@ BlazeComponent.extendComponent({
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.
// 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 (Session.equals('currentCard', this.currentData()._id)) {
evt.stopImmediatePropagation();
evt.preventDefault();
@@ -149,7 +171,8 @@ BlazeComponent.extendComponent({
idOrNull(swimlaneId) {
const currentUser = Meteor.user();
if (currentUser.profile.boardView === 'board-view-swimlanes')
if (currentUser.profile.boardView === 'board-view-swimlanes' ||
this.data().board().isTemplatesBoard())
return swimlaneId;
return undefined;
},
@@ -269,8 +292,8 @@ BlazeComponent.extendComponent({
// work.
$form.find('button[type=submit]').click();
// Pressing Tab should open the form of the next column, and Maj+Tab go
// in the reverse order
// Pressing Tab should open the form of the next column, and Maj+Tab go
// in the reverse order
} else if (evt.keyCode === 9) {
evt.preventDefault();
const isReverse = evt.shiftKey;
@@ -292,7 +315,8 @@ BlazeComponent.extendComponent({
return [{
keydown: this.pressKey,
'click .js-link': Popup.open('linkCard'),
'click .js-search': Popup.open('searchCard'),
'click .js-search': Popup.open('searchElement'),
'click .js-card-template': Popup.open('searchElement'),
}];
},
@@ -330,7 +354,7 @@ BlazeComponent.extendComponent({
const currentBoard = Boards.findOne(Session.get('currentBoard'));
callback($.map(currentBoard.labels, (label) => {
if (label.name.indexOf(term) > -1 ||
label.color.indexOf(term) > -1) {
label.color.indexOf(term) > -1) {
return label;
}
return null;
@@ -367,17 +391,7 @@ BlazeComponent.extendComponent({
BlazeComponent.extendComponent({
onCreated() {
// Prefetch first non-current board id
const boardId = Boards.findOne({
archived: false,
'members.userId': Meteor.userId(),
_id: {$ne: Session.get('currentBoard')},
}, {
sort: ['title'],
})._id;
// Subscribe to this board
subManager.subscribe('board', boardId);
this.selectedBoardId = new ReactiveVar(boardId);
this.selectedBoardId = new ReactiveVar('');
this.selectedSwimlaneId = new ReactiveVar('');
this.selectedListId = new ReactiveVar('');
@@ -403,6 +417,7 @@ BlazeComponent.extendComponent({
archived: false,
'members.userId': Meteor.userId(),
_id: {$ne: Session.get('currentBoard')},
type: 'board',
}, {
sort: ['title'],
});
@@ -410,7 +425,7 @@ BlazeComponent.extendComponent({
},
swimlanes() {
if (!this.selectedBoardId) {
if (!this.selectedBoardId.get()) {
return [];
}
const swimlanes = Swimlanes.find({boardId: this.selectedBoardId.get()});
@@ -420,7 +435,7 @@ BlazeComponent.extendComponent({
},
lists() {
if (!this.selectedBoardId) {
if (!this.selectedBoardId.get()) {
return [];
}
const lists = Lists.find({boardId: this.selectedBoardId.get()});
@@ -441,6 +456,7 @@ BlazeComponent.extendComponent({
archived: false,
linkedId: {$nin: ownCardsIds},
_id: {$nin: ownCardsIds},
type: {$nin: ['template-card']},
});
},
@@ -508,12 +524,25 @@ BlazeComponent.extendComponent({
},
onCreated() {
// Prefetch first non-current board id
let board = Boards.findOne({
archived: false,
'members.userId': Meteor.userId(),
_id: {$ne: Session.get('currentBoard')},
});
this.isCardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-card-template');
this.isListTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-list-template');
this.isSwimlaneTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-open-add-swimlane-menu');
this.isBoardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-add-board');
this.isTemplateSearch = this.isCardTemplateSearch ||
this.isListTemplateSearch ||
this.isSwimlaneTemplateSearch ||
this.isBoardTemplateSearch;
let board = {};
if (this.isTemplateSearch) {
board = Boards.findOne(Meteor.user().profile.templatesBoardId);
} else {
// Prefetch first non-current board id
board = Boards.findOne({
archived: false,
'members.userId': Meteor.userId(),
_id: {$nin: [Session.get('currentBoard'), Meteor.user().profile.templatesBoardId]},
});
}
if (!board) {
Popup.close();
return;
@@ -523,20 +552,21 @@ BlazeComponent.extendComponent({
subManager.subscribe('board', boardId);
this.selectedBoardId = new ReactiveVar(boardId);
this.boardId = Session.get('currentBoard');
// In order to get current board info
subManager.subscribe('board', this.boardId);
board = Boards.findOne(this.boardId);
// List where to insert card
const list = $(Popup._getTopStack().openerElement).closest('.js-list');
this.listId = Blaze.getData(list[0])._id;
// Swimlane where to insert card
const swimlane = $(Popup._getTopStack().openerElement).closest('.js-swimlane');
this.swimlaneId = '';
if (board.view === 'board-view-swimlanes')
this.swimlaneId = Blaze.getData(swimlane[0])._id;
else
this.swimlaneId = Swimlanes.findOne({boardId: this.boardId})._id;
if (!this.isBoardTemplateSearch) {
this.boardId = Session.get('currentBoard');
// In order to get current board info
subManager.subscribe('board', this.boardId);
this.swimlaneId = '';
// Swimlane where to insert card
const swimlane = $(Popup._getTopStack().openerElement).parents('.js-swimlane');
if (Meteor.user().profile.boardView === 'board-view-swimlanes')
this.swimlaneId = Blaze.getData(swimlane[0])._id;
else
this.swimlaneId = Swimlanes.findOne({boardId: this.boardId})._id;
// List where to insert card
const list = $(Popup._getTopStack().openerElement).closest('.js-list');
this.listId = Blaze.getData(list[0])._id;
}
this.term = new ReactiveVar('');
},
@@ -545,6 +575,7 @@ BlazeComponent.extendComponent({
archived: false,
'members.userId': Meteor.userId(),
_id: {$ne: Session.get('currentBoard')},
type: 'board',
}, {
sort: ['title'],
});
@@ -556,7 +587,21 @@ BlazeComponent.extendComponent({
return [];
}
const board = Boards.findOne(this.selectedBoardId.get());
return board.searchCards(this.term.get(), false);
if (!this.isTemplateSearch || this.isCardTemplateSearch) {
return board.searchCards(this.term.get(), false);
} else if (this.isListTemplateSearch) {
return board.searchLists(this.term.get());
} 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);
});
return boards;
} else {
return [];
}
},
events() {
@@ -570,20 +615,49 @@ BlazeComponent.extendComponent({
this.term.set(evt.target.searchTerm.value);
},
'click .js-minicard'(evt) {
// LINK CARD
const card = Blaze.getData(evt.currentTarget);
const _id = Cards.insert({
title: card.title, //dummy
listId: this.listId,
swimlaneId: this.swimlaneId,
boardId: this.boardId,
sort: Lists.findOne(this.listId).cards().count(),
type: 'cardType-linkedCard',
linkedId: card.linkedId || card._id,
});
Filter.addException(_id);
// 0. Common
const title = $('.js-element-title').val().trim();
if (!title)
return;
const element = Blaze.getData(evt.currentTarget);
element.title = title;
let _id = '';
if (!this.isTemplateSearch || this.isCardTemplateSearch) {
// Card insertion
// 1. Common
element.sort = Lists.findOne(this.listId).cards().count();
// 1.A From template
if (this.isTemplateSearch) {
element.type = 'cardType-card';
element.linkedId = '';
_id = element.copy(this.boardId, this.swimlaneId, this.listId);
// 1.B Linked card
} else {
delete element._id;
element.type = 'cardType-linkedCard';
element.linkedId = element.linkedId || element._id;
_id = Cards.insert(element);
}
Filter.addException(_id);
// List insertion
} else if (this.isListTemplateSearch) {
element.sort = Swimlanes.findOne(this.swimlaneId).lists().count();
element.type = 'list';
_id = element.copy(this.boardId, this.swimlaneId);
} else if (this.isSwimlaneTemplateSearch) {
element.sort = Boards.findOne(this.boardId).swimlanes().count();
element.type = 'swimlalne';
_id = element.copy(this.boardId);
} else if (this.isBoardTemplateSearch) {
board = Boards.findOne(element.linkedId);
board.sort = Boards.find({archived: false}).count();
board.type = 'board';
board.title = element.title;
delete board.slug;
_id = board.copy();
}
Popup.close();
},
}];
},
}).register('searchCardPopup');
}).register('searchElementPopup');
+8
View File
@@ -0,0 +1,8 @@
template(name="minilist")
.minicard(
class="minicard-{{colorClass}}")
.minicard-title
.handle
.fa.fa-arrows
+viewer
= title
+3 -1
View File
@@ -36,7 +36,10 @@ import sanitizeXss from 'xss';
const at = HTML.CharRef({html: '@', str: '@'});
Blaze.Template.registerHelper('mentions', new Template('mentions', function() {
const view = this;
let content = Blaze.toHTML(view.templateContentBlock);
const currentBoard = Boards.findOne(Session.get('currentBoard'));
if (!currentBoard)
return HTML.Raw(sanitizeXss(content));
const knowedUsers = currentBoard.members.map((member) => {
const u = Users.findOne(member.userId);
if(u){
@@ -45,7 +48,6 @@ Blaze.Template.registerHelper('mentions', new Template('mentions', function() {
return member;
});
const mentionRegex = /\B@([\w.]*)/gi;
let content = Blaze.toHTML(view.templateContentBlock);
let currentMention;
while ((currentMention = mentionRegex.exec(content)) !== null) {
@@ -0,0 +1,8 @@
template(name="miniswimlane")
.minicard(
class="minicard-{{colorClass}}")
.minicard-title
.handle
.fa.fa-arrows
+viewer
= title
+24 -12
View File
@@ -1,15 +1,21 @@
template(name="swimlaneHeader")
.swimlane-header-wrap.js-swimlane-header(class='{{#if colorClass}}swimlane-{{colorClass}}{{/if}}')
+inlinedForm
+editSwimlaneTitleForm
if this.isTemplateContainer
+swimlaneFixedHeader(this)
else
.swimlane-header(
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
= title
.swimlane-header-menu
unless currentUser.isCommentOnly
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon
a.fa.fa-navicon.js-open-swimlane-menu
+inlinedForm
+editSwimlaneTitleForm
else
+swimlaneFixedHeader(this)
template(name="swimlaneFixedHeader")
.swimlane-header(
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
= title
.swimlane-header-menu
unless currentUser.isCommentOnly
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon
a.fa.fa-navicon.js-open-swimlane-menu
template(name="editSwimlaneTitleForm")
.list-composer
@@ -22,9 +28,10 @@ template(name="swimlaneActionPopup")
unless currentUser.isCommentOnly
ul.pop-over-list
li: a.js-set-swimlane-color {{_ 'select-color'}}
hr
ul.pop-over-list
li: a.js-close-swimlane {{_ 'archive-swimlane'}}
unless this.isTemplateContainer
hr
ul.pop-over-list
li: a.js-close-swimlane {{_ 'archive-swimlane'}}
template(name="swimlaneAddPopup")
unless currentUser.isCommentOnly
@@ -33,6 +40,11 @@ template(name="swimlaneAddPopup")
autocomplete="off" autofocus)
.edit-controls.clearfix
button.primary.confirm(type="submit") {{_ 'add'}}
unless currentBoard.isTemplatesBoard
unless currentBoard.isTemplateBoard
span.quiet
| {{_ 'or'}}
a.js-swimlane-template {{_ 'template'}}
template(name="setSwimlaneColorPopup")
form.edit-label
@@ -47,12 +47,14 @@ BlazeComponent.extendComponent({
const titleInput = this.find('.swimlane-name-input');
const title = titleInput.value.trim();
const sortValue = calculateIndexData(this.currentSwimlane, nextSwimlane, 1);
const swimlaneType = (currentBoard.isTemplatesBoard())?'template-swimlane':'swimlane';
if (title) {
Swimlanes.insert({
title,
boardId: Session.get('currentBoard'),
sort: sortValue.base,
type: swimlaneType,
});
titleInput.value = '';
@@ -63,6 +65,7 @@ BlazeComponent.extendComponent({
// with a minimum of interactions
Popup.close();
},
'click .js-swimlane-template': Popup.open('searchElement'),
}];
},
}).register('swimlaneAddPopup');
+11 -7
View File
@@ -3,15 +3,15 @@ template(name="swimlane")
+swimlaneHeader
.swimlane.js-lists.js-swimlane
if isMiniScreen
if currentList
if currentListIsInThisSwimlane _id
+list(currentList)
else
each currentBoard.lists
unless currentList
each lists
+miniList(this)
if currentUser.isBoardMember
+addListForm
else
each currentBoard.lists
each lists
+list(this)
if currentCardIsInThisList _id ../_id
+cardDetails(currentCard)
@@ -24,12 +24,12 @@ template(name="listsGroup")
if currentList
+list(currentList)
else
each currentBoard.lists
each lists
+miniList(this)
if currentUser.isBoardMember
+addListForm
else
each currentBoard.lists
each lists
+list(this)
if currentCardIsInThisList _id null
+cardDetails(currentCard)
@@ -44,7 +44,11 @@ template(name="addListForm")
autocomplete="off" autofocus)
.edit-controls.clearfix
button.primary.confirm(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
unless currentBoard.isTemplatesBoard
unless currentBoard.isTemplateBoard
span.quiet
| {{_ 'or'}}
a.js-list-template {{_ 'template'}}
else
a.open-list-composer.js-open-inlined-form
i.fa.fa-plus
+18
View File
@@ -1,5 +1,10 @@
const { calculateIndex, enableClickOnTouch } = Utils;
function currentListIsInThisSwimlane(swimlaneId) {
const currentList = Lists.findOne(Session.get('currentList'));
return currentList && (currentList.swimlaneId === swimlaneId || currentList.swimlaneId === '');
}
function currentCardIsInThisList(listId, swimlaneId) {
const currentCard = Cards.findOne(Session.get('currentCard'));
const currentUser = Meteor.user();
@@ -114,6 +119,10 @@ BlazeComponent.extendComponent({
return currentCardIsInThisList(listId, swimlaneId);
},
currentListIsInThisSwimlane(swimlaneId) {
return currentListIsInThisSwimlane(swimlaneId);
},
events() {
return [{
// Click-and-drag action
@@ -153,6 +162,12 @@ BlazeComponent.extendComponent({
}).register('swimlane');
BlazeComponent.extendComponent({
onCreated() {
this.currentBoard = Boards.findOne(Session.get('currentBoard'));
this.isListTemplatesSwimlane = this.currentBoard.isTemplatesBoard() && this.currentData().isListTemplatesSwimlane();
this.currentSwimlane = this.currentData();
},
// Proxy
open() {
this.childComponents('inlinedForm')[0].open();
@@ -169,12 +184,15 @@ BlazeComponent.extendComponent({
title,
boardId: Session.get('currentBoard'),
sort: $('.list').length,
type: (this.isListTemplatesSwimlane)?'template-list':'list',
swimlaneId: (this.currentBoard.isTemplatesBoard())?this.currentSwimlane._id:'',
});
titleInput.value = '';
titleInput.focus();
}
},
'click .js-list-template': Popup.open('searchElement'),
}];
},
}).register('addListForm');
+3
View File
@@ -21,6 +21,9 @@ template(name="memberMenuPopup")
li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
if currentUser.isAdmin
li: a.js-go-setting(href="{{pathFor 'setting'}}") {{_ 'admin-panel'}}
hr
ul.pop-over-list
li: a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") {{_ 'templates'}}
unless isSandstorm
hr
ul.pop-over-list
+9
View File
@@ -3,6 +3,15 @@ Template.headerUserBar.events({
'click .js-change-avatar': Popup.open('changeAvatar'),
});
Template.memberMenuPopup.helpers({
templatesBoardId() {
return Meteor.user().getTemplatesBoardId();
},
templatesBoardSlug() {
return Meteor.user().getTemplatesBoardSlug();
},
});
Template.memberMenuPopup.events({
'click .js-edit-profile': Popup.open('editProfile'),
'click .js-change-settings': Popup.open('changeSettings'),
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "نسخ رابط البطاقة إلى الحافظة",
"linkCardPopup-title": "ربط البطاقة",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "بحث",
"copyCardPopup-title": "نسخ البطاقة",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Сигурни ли сте, че искате да изтриете този чеклист?",
"copy-card-link-to-clipboard": "Копирай връзката на картата в клипборда",
"linkCardPopup-title": "Свържи картата",
"searchCardPopup-title": "Търсене на карта",
"searchElementPopup-title": "Търсене",
"copyCardPopup-title": "Копирай картата",
"copyChecklistToManyCardsPopup-title": "Копирай чеклисти в други карти",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copia l'enllaç de la ftixa al porta-retalls",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Buscar Fitxa",
"searchElementPopup-title": "Cerca",
"copyCardPopup-title": "Copia la fitxa",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Títols de fitxa i Descripcions de destí en aquest format JSON",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Opravdu chcete smazat tento checklist?",
"copy-card-link-to-clipboard": "Kopírovat adresu karty do mezipaměti",
"linkCardPopup-title": "Propojit kartu",
"searchCardPopup-title": "Hledat Kartu",
"searchElementPopup-title": "Hledat",
"copyCardPopup-title": "Kopírovat kartu",
"copyChecklistToManyCardsPopup-title": "Kopírovat checklist do více karet",
"copyChecklistToManyCardsPopup-instructions": "Názvy a popisy cílové karty v tomto formátu JSON",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Wollen Sie die Checkliste wirklich löschen?",
"copy-card-link-to-clipboard": "Kopiere Link zur Karte in die Zwischenablage",
"linkCardPopup-title": "Karte verknüpfen",
"searchCardPopup-title": "Karte suchen",
"searchElementPopup-title": "Suche",
"copyCardPopup-title": "Karte kopieren",
"copyChecklistToManyCardsPopup-title": "Checklistenvorlage in mehrere Karten kopieren",
"copyChecklistToManyCardsPopup-instructions": "Titel und Beschreibungen der Zielkarten im folgenden JSON-Format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Αναζήτηση",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+7 -1
View File
@@ -92,6 +92,8 @@
"restore-board": "Restore Board",
"no-archived-boards": "No Boards in Archive.",
"archives": "Archive",
"template": "Template",
"templates": "Templates",
"assign-member": "Assign member",
"attached": "attached",
"attachment": "Attachment",
@@ -143,6 +145,7 @@
"cardLabelsPopup-title": "Labels",
"cardMembersPopup-title": "Members",
"cardMorePopup-title": "More",
"cardTemplatePopup-title": "Create template",
"cards": "Cards",
"cards-count": "Cards",
"casSignIn" : "Sign In with CAS",
@@ -204,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
@@ -453,6 +456,9 @@
"welcome-swimlane": "Milestone 1",
"welcome-list1": "Basics",
"welcome-list2": "Advanced",
"card-templates-swimlane": "Card Templates",
"list-templates-swimlane": "List Templates",
"board-templates-swimlane": "Board Templates",
"what-to-do": "What do you want to do?",
"wipLimitErrorPopup-title": "Invalid WIP Limit",
"wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Serĉi",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copiar enlace a tarjeta en el portapapeles",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Buscar",
"copyCardPopup-title": "Copiar Tarjeta",
"copyChecklistToManyCardsPopup-title": "Copiar Plantilla Checklist a Muchas Tarjetas",
"copyChecklistToManyCardsPopup-instructions": "Títulos y Descripciones de la Tarjeta Destino en este formato JSON",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "¿Seguro que quieres eliminar la lista de verificación?",
"copy-card-link-to-clipboard": "Copiar el enlace de la tarjeta al portapapeles",
"linkCardPopup-title": "Enlazar tarjeta",
"searchCardPopup-title": "Buscar tarjeta",
"searchElementPopup-title": "Buscar",
"copyCardPopup-title": "Copiar la tarjeta",
"copyChecklistToManyCardsPopup-title": "Copiar la plantilla de la lista de verificación en varias tarjetas",
"copyChecklistToManyCardsPopup-instructions": "Títulos y descripciones de las tarjetas de destino en formato JSON",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Kopiatu txartela arbelera",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Bilatu",
"copyCardPopup-title": "Kopiatu txartela",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "مطمئنا چک لیست پاک شود؟",
"copy-card-link-to-clipboard": "درج پیوند کارت در حافظه",
"linkCardPopup-title": "ارتباط دادن کارت",
"searchCardPopup-title": "جستجوی کارت",
"searchElementPopup-title": "جستجو",
"copyCardPopup-title": "کپی کارت",
"copyChecklistToManyCardsPopup-title": "کپی قالب کارت به کارت‌های متعدد",
"copyChecklistToManyCardsPopup-instructions": "عنوان و توضیحات کارت مقصد در این قالب JSON",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Oletko varma että haluat poistaa tarkistuslistan?",
"copy-card-link-to-clipboard": "Kopioi kortin linkki leikepöydälle",
"linkCardPopup-title": "Linkitä kortti",
"searchCardPopup-title": "Etsi kortti",
"searchElementPopup-title": "Etsi",
"copyCardPopup-title": "Kopioi kortti",
"copyChecklistToManyCardsPopup-title": "Kopioi tarkistuslistan malli monille korteille",
"copyChecklistToManyCardsPopup-instructions": "Kohde korttien otsikot ja kuvaukset JSON muodossa",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Êtes-vous sûr de vouloir supprimer la checklist ?",
"copy-card-link-to-clipboard": "Copier le lien vers la carte dans le presse-papier",
"linkCardPopup-title": "Lier une Carte",
"searchCardPopup-title": "Chercher une Carte",
"searchElementPopup-title": "Chercher",
"copyCardPopup-title": "Copier la carte",
"copyChecklistToManyCardsPopup-title": "Copier le modèle de checklist vers plusieurs cartes",
"copyChecklistToManyCardsPopup-instructions": "Titres et descriptions des cartes de destination dans ce format JSON",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "למחוק את רשימת המשימות?",
"copy-card-link-to-clipboard": "העתקת קישור הכרטיס ללוח הגזירים",
"linkCardPopup-title": "קישור כרטיס",
"searchCardPopup-title": "חיפוש כרטיס",
"searchElementPopup-title": "חיפוש",
"copyCardPopup-title": "העתק כרטיס",
"copyChecklistToManyCardsPopup-title": "העתקת תבנית רשימת מטלות למגוון כרטיסים",
"copyChecklistToManyCardsPopup-instructions": "כותרות ותיאורים של כרטיסי יעד בתצורת JSON זו",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "क्या आप वाकई जांचसूची हटाना चाहते हैं?",
"copy-card-link-to-clipboard": "कॉपी कार्ड क्लिपबोर्ड करने के लिए लिंक",
"linkCardPopup-title": "कार्ड कड़ी",
"searchCardPopup-title": "कार्ड खोज",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "कार्ड प्रतिलिपि",
"copyChecklistToManyCardsPopup-title": "कई कार्ड के लिए जांचसूची खाके की प्रतिलिपि बनाएँ",
"copyChecklistToManyCardsPopup-instructions": "इस JSON प्रारूप में गंतव्य कार्ड शीर्षक और विवरण",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Kártya hivatkozásának másolása a vágólapra",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Keresés",
"copyCardPopup-title": "Kártya másolása",
"copyChecklistToManyCardsPopup-title": "Ellenőrzőlista sablon másolása több kártyára",
"copyChecklistToManyCardsPopup-instructions": "A célkártyák címe és a leírások ebben a JSON formátumban",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Cari",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Sei sicuro di voler eliminare la checklist?",
"copy-card-link-to-clipboard": "Copia link della scheda sulla clipboard",
"linkCardPopup-title": "Collega scheda",
"searchCardPopup-title": "Cerca scheda",
"searchElementPopup-title": "Cerca",
"copyCardPopup-title": "Copia Scheda",
"copyChecklistToManyCardsPopup-title": "Copia template checklist su più schede",
"copyChecklistToManyCardsPopup-instructions": "Titolo e la descrizione della scheda di destinazione in questo formato JSON",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "カードへのリンクをクリップボードにコピー",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "検索",
"copyCardPopup-title": "カードをコピー",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "დარწმუნებული ხართ, რომ გსურთ კატალოგის წაშლა ? ",
"copy-card-link-to-clipboard": "დააკოპირეთ ბარათის ბმული clipboard-ზე",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "ძებნა",
"copyCardPopup-title": "ბარათის ასლი",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "클립보드에 카드의 링크가 복사되었습니다.",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "검색",
"copyCardPopup-title": "카드 복사",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Сигурни ли сте, че искате да изтриете този чеклист?",
"copy-card-link-to-clipboard": "Копирай връзката на картата в клипборда",
"linkCardPopup-title": "Свържи картата",
"searchCardPopup-title": "Търсене на карта",
"searchElementPopup-title": "Търсене",
"copyCardPopup-title": "Копирай картата",
"copyChecklistToManyCardsPopup-title": "Копирай чеклисти в други карти",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Kopieer kaart link naar klembord",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Zoek",
"copyCardPopup-title": "Kopieer kaart",
"copyChecklistToManyCardsPopup-title": "Checklist sjabloon kopiëren naar meerdere kaarten",
"copyChecklistToManyCardsPopup-instructions": "Doel kaart titels en omschrijvingen in dit JSON formaat",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Czy jesteś pewien, że chcesz usunąć listę zadań?",
"copy-card-link-to-clipboard": "Skopiuj łącze karty do schowka",
"linkCardPopup-title": "Podepnij kartę",
"searchCardPopup-title": "Znajdź kartę",
"searchElementPopup-title": "Wyszukaj",
"copyCardPopup-title": "Skopiuj kartę",
"copyChecklistToManyCardsPopup-title": "Kopiuj szablon listy zadań do wielu kart",
"copyChecklistToManyCardsPopup-instructions": "Docelowe tytuły i opisy kart są w formacie JSON",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Tem certeza que quer excluir a lista de verificação?",
"copy-card-link-to-clipboard": "Copiar link do cartão para a área de transferência",
"linkCardPopup-title": "Ligar Cartão",
"searchCardPopup-title": "Procurar Cartão",
"searchElementPopup-title": "Buscar",
"copyCardPopup-title": "Copiar o cartão",
"copyChecklistToManyCardsPopup-title": "Copiar modelo de lista de verificação para vários cartões",
"copyChecklistToManyCardsPopup-instructions": "Títulos e descrições do cartão de destino neste formato JSON",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Caută",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+7 -7
View File
@@ -92,8 +92,8 @@
"restore-board": "Востановить доску",
"no-archived-boards": "Нет досок в архиве.",
"archives": "Архив",
"template": "Template",
"templates": "Templates",
"template": "Шаблон",
"templates": "Шаблоны",
"assign-member": "Назначить участника",
"attached": "прикреплено",
"attachment": "Вложение",
@@ -145,7 +145,7 @@
"cardLabelsPopup-title": "Метки",
"cardMembersPopup-title": "Участники",
"cardMorePopup-title": "Поделиться",
"cardTemplatePopup-title": "Create template",
"cardTemplatePopup-title": "Создать шаблон",
"cards": "Карточки",
"cards-count": "Карточки",
"casSignIn": "Войти через CAS",
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Вы уверены, что хотите удалить контрольный список?",
"copy-card-link-to-clipboard": "Копировать ссылку на карточку в буфер обмена",
"linkCardPopup-title": "Карточка-ссылка",
"searchCardPopup-title": "Найти карточку",
"searchElementPopup-title": "Поиск",
"copyCardPopup-title": "Копировать карточку",
"copyChecklistToManyCardsPopup-title": "Копировать шаблон контрольного списка в несколько карточек",
"copyChecklistToManyCardsPopup-instructions": "Названия и описания целевых карт в формате JSON",
@@ -456,9 +456,9 @@
"welcome-swimlane": "Этап 1",
"welcome-list1": "Основы",
"welcome-list2": "Расширенно",
"card-templates-swimlane": "Card Templates",
"list-templates-swimlane": "List Templates",
"board-templates-swimlane": "Board Templates",
"card-templates-swimlane": "Шаблоны карточек",
"list-templates-swimlane": "Шаблоны списков",
"board-templates-swimlane": "Шаблоны досок",
"what-to-do": "Что вы хотите сделать?",
"wipLimitErrorPopup-title": "Некорректный лимит на кол-во задач",
"wipLimitErrorPopup-dialog-pt1": "Количество задач в этом списке превышает установленный вами лимит",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Pretraga",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Är du säker på att du vill radera checklista?",
"copy-card-link-to-clipboard": "Kopiera kortlänk till urklipp",
"linkCardPopup-title": "Länka kort",
"searchCardPopup-title": "Sök kort",
"searchElementPopup-title": "Sök",
"copyCardPopup-title": "Kopiera kort",
"copyChecklistToManyCardsPopup-title": "Kopiera checklist-mallen till flera kort",
"copyChecklistToManyCardsPopup-instructions": "Destinationskorttitlar och beskrivningar i detta JSON-format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "தேடு ",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "ค้นหา",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Kontrol listesini silmek istediğinden emin misin?",
"copy-card-link-to-clipboard": "Kartın linkini kopyala",
"linkCardPopup-title": "Bağlantı kartı",
"searchCardPopup-title": "Kart Ara",
"searchElementPopup-title": "Arama",
"copyCardPopup-title": "Kartı Kopyala",
"copyChecklistToManyCardsPopup-title": "Yapılacaklar Listesi şemasını birden çok karta kopyala",
"copyChecklistToManyCardsPopup-instructions": "Hedef Kart Başlıkları ve Açıklamaları bu JSON formatında",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Скопіювати посилання на картку в буфер обміну",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
"copy-card-link-to-clipboard": "Copy card link to clipboard",
"linkCardPopup-title": "Link Card",
"searchCardPopup-title": "Search Card",
"searchElementPopup-title": "Search",
"copyCardPopup-title": "Copy Card",
"copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
"copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "确定要删除清单吗?",
"copy-card-link-to-clipboard": "复制卡片链接到剪贴板",
"linkCardPopup-title": "链接卡片",
"searchCardPopup-title": "搜索卡片",
"searchElementPopup-title": "搜索",
"copyCardPopup-title": "复制卡片",
"copyChecklistToManyCardsPopup-title": "复制清单模板至多个卡片",
"copyChecklistToManyCardsPopup-instructions": "以JSON格式表示目标卡片的标题和描述",
+1 -1
View File
@@ -207,7 +207,7 @@
"confirm-checklist-delete-dialog": "你確定要刪除待辦清單嗎?",
"copy-card-link-to-clipboard": "將卡片連結複製到剪貼簿",
"linkCardPopup-title": "連結卡片",
"searchCardPopup-title": "搜尋卡片",
"searchElementPopup-title": "搜尋",
"copyCardPopup-title": "複製卡片",
"copyChecklistToManyCardsPopup-title": "複製待辦清單的樣板到多個卡片",
"copyChecklistToManyCardsPopup-instructions": "使用此 JSON 格式來表示目標卡片的標題和描述",
+4
View File
@@ -27,6 +27,10 @@ Attachments = new FS.Collection('attachments', {
if (Meteor.isServer) {
Meteor.startup(() => {
Attachments.files._ensureIndex({ cardId: 1 });
});
Attachments.allow({
insert(userId, doc) {
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+106 -3
View File
@@ -304,10 +304,31 @@ Boards.attachSchema(new SimpleSchema({
defaultValue: false,
optional: true,
},
type: {
/**
* The type of board
*/
type: String,
defaultValue: 'board',
},
}));
Boards.helpers({
copy() {
const oldId = this._id;
delete this._id;
const _id = Boards.insert(this);
// Copy all swimlanes in board
Swimlanes.find({
boardId: oldId,
archived: false,
}).forEach((swimlane) => {
swimlane.type = 'swimlane';
swimlane.copy(_id);
});
},
/**
* Is supplied user authorized to view this board?
*/
@@ -456,6 +477,75 @@ Boards.helpers({
return _id;
},
searchBoards(term) {
check(term, Match.OneOf(String, null, undefined));
const query = { boardId: this._id };
query.type = 'cardType-linkedBoard';
query.archived = false;
const projection = { limit: 10, sort: { createdAt: -1 } };
if (term) {
const regex = new RegExp(term, 'i');
query.$or = [
{ title: regex },
{ description: regex },
];
}
return Cards.find(query, projection);
},
searchSwimlanes(term) {
check(term, Match.OneOf(String, null, undefined));
const query = { boardId: this._id };
if (this.isTemplatesBoard()) {
query.type = 'template-swimlane';
query.archived = false;
} else {
query.type = {$nin: ['template-swimlane']};
}
const projection = { limit: 10, sort: { createdAt: -1 } };
if (term) {
const regex = new RegExp(term, 'i');
query.$or = [
{ title: regex },
{ description: regex },
];
}
return Swimlanes.find(query, projection);
},
searchLists(term) {
check(term, Match.OneOf(String, null, undefined));
const query = { boardId: this._id };
if (this.isTemplatesBoard()) {
query.type = 'template-list';
query.archived = false;
} else {
query.type = {$nin: ['template-list']};
}
const projection = { limit: 10, sort: { createdAt: -1 } };
if (term) {
const regex = new RegExp(term, 'i');
query.$or = [
{ title: regex },
{ description: regex },
];
}
return Lists.find(query, projection);
},
searchCards(term, excludeLinked) {
check(term, Match.OneOf(String, null, undefined));
@@ -463,6 +553,12 @@ Boards.helpers({
if (excludeLinked) {
query.linkedId = null;
}
if (this.isTemplatesBoard()) {
query.type = 'template-card';
query.archived = false;
} else {
query.type = {$nin: ['template-card']};
}
const projection = { limit: 10, sort: { createdAt: -1 } };
if (term) {
@@ -559,6 +655,13 @@ Boards.helpers({
});
},
isTemplateBoard() {
return this.type === 'template-board';
},
isTemplatesBoard() {
return this.type === 'template-container';
},
});
@@ -907,7 +1010,7 @@ if (Meteor.isServer) {
* @param {string} userId the ID of the user to retrieve the data
* @return_type [{_id: string,
title: string}]
*/
*/
JsonRoutes.add('GET', '/api/users/:userId/boards', function (req, res) {
try {
Authentication.checkLoggedIn(req.userId);
@@ -944,7 +1047,7 @@ if (Meteor.isServer) {
*
* @return_type [{_id: string,
title: string}]
*/
*/
JsonRoutes.add('GET', '/api/boards', function (req, res) {
try {
Authentication.checkUserId(req.userId);
@@ -1015,7 +1118,7 @@ if (Meteor.isServer) {
*
* @return_type {_id: string,
defaultSwimlaneId: string}
*/
*/
JsonRoutes.add('POST', '/api/boards', function (req, res) {
try {
Authentication.checkUserId(req.userId);
+6
View File
@@ -67,6 +67,12 @@ CardComments.allow({
});
CardComments.helpers({
copy(newCardId) {
this.cardId = newCardId;
delete this._id;
CardComments.insert(this);
},
user() {
return Users.findOne(this.userId);
},
+61 -2
View File
@@ -246,7 +246,7 @@ Cards.attachSchema(new SimpleSchema({
* type of the card
*/
type: String,
defaultValue: '',
defaultValue: 'cardType-card',
},
linkedId: {
/**
@@ -272,6 +272,61 @@ Cards.allow({
});
Cards.helpers({
copy(boardId, swimlaneId, listId) {
const oldBoard = Boards.findOne(this.boardId);
const oldBoardLabels = oldBoard.labels;
// Get old label names
const oldCardLabels = _.pluck(_.filter(oldBoardLabels, (label) => {
return _.contains(this.labelIds, label._id);
}), 'name');
const newBoard = Boards.findOne(boardId);
const newBoardLabels = newBoard.labels;
const newCardLabels = _.pluck(_.filter(newBoardLabels, (label) => {
return _.contains(oldCardLabels, label.name);
}), '_id');
const oldId = this._id;
const oldCard = Cards.findOne(oldId);
delete this._id;
delete this.labelIds;
this.labelIds= newCardLabels;
this.boardId = boardId;
this.swimlaneId = swimlaneId;
this.listId = listId;
const _id = Cards.insert(this);
// Copy attachments
oldCard.attachments().forEach((att) => {
att.cardId = _id;
delete att._id;
return Attachments.insert(att);
});
// copy checklists
Checklists.find({cardId: oldId}).forEach((ch) => {
// REMOVE verify copy with arguments
ch.copy(_id);
});
// copy subtasks
Cards.find({parentId: oldId}).forEach((subtask) => {
subtask.parentId = _id;
subtask._id = null;
// REMOVE verify copy with arguments instead of insert?
Cards.insert(subtask);
});
// copy card comments
CardComments.find({cardId: oldId}).forEach((cmt) => {
// REMOVE verify copy with arguments
cmt.copy(_id);
});
return _id;
},
list() {
return Lists.findOne(this.listId);
},
@@ -930,6 +985,10 @@ Cards.helpers({
return this.assignedBy;
}
},
isTemplateCard() {
return this.type === 'template-card';
},
});
Cards.mutations({
@@ -1230,7 +1289,7 @@ Cards.mutations({
function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId) {
if ((_.contains(fieldNames, 'listId') && doc.listId !== oldListId) ||
(_.contains(fieldNames, 'swimlaneId') && doc.swimlaneId !== oldSwimlaneId)){
(_.contains(fieldNames, 'swimlaneId') && doc.swimlaneId !== oldSwimlaneId)){
Activities.insert({
userId,
oldListId,
+1
View File
@@ -189,6 +189,7 @@ function publishChekListUncompleted(userId, doc){
if (Meteor.isServer) {
Meteor.startup(() => {
ChecklistItems._collection._ensureIndex({ checklistId: 1 });
ChecklistItems._collection._ensureIndex({ cardId: 1 });
});
ChecklistItems.after.update((userId, doc, fieldNames) => {
+13
View File
@@ -48,6 +48,19 @@ Checklists.attachSchema(new SimpleSchema({
}));
Checklists.helpers({
copy(newCardId) {
const oldChecklistId = this._id;
this._id = null;
this.cardId = newCardId;
const newChecklistId = Checklists.insert(this);
ChecklistItems.find({checklistId: oldChecklistId}).forEach((item) => {
item._id = null;
item.checklistId = newChecklistId;
item.cardId = newCardId;
ChecklistItems.insert(item);
});
},
itemCount() {
return ChecklistItems.find({ checklistId: this._id }).count();
},
+3 -3
View File
@@ -98,9 +98,9 @@ function customFieldCreation(userId, doc){
}
if (Meteor.isServer) {
/*Meteor.startup(() => {
CustomFields._collection._ensureIndex({ boardId: 1});
});*/
Meteor.startup(() => {
CustomFields._collection._ensureIndex({ boardId: 1 });
});
CustomFields.after.insert((userId, doc) => {
customFieldCreation(userId, doc);
+4
View File
@@ -88,6 +88,10 @@ Integrations.allow({
//INTEGRATIONS REST API
if (Meteor.isServer) {
Meteor.startup(() => {
Integrations._collection._ensureIndex({ boardId: 1 });
});
/**
* @operation get_all_integrations
* @summary Get all integrations in board
+58
View File
@@ -27,6 +27,13 @@ Lists.attachSchema(new SimpleSchema({
*/
type: String,
},
swimlaneId: {
/**
* the swimlane associated to this list. Used for templates
*/
type: String,
defaultValue: '',
},
createdAt: {
/**
* creation date
@@ -107,6 +114,13 @@ Lists.attachSchema(new SimpleSchema({
'saddlebrown', 'paleturquoise', 'mistyrose', 'indigo',
],
},
type: {
/**
* The type of list
*/
type: String,
defaultValue: 'list',
},
}));
Lists.allow({
@@ -123,6 +137,36 @@ Lists.allow({
});
Lists.helpers({
copy(boardId, swimlaneId) {
const oldId = this._id;
const oldSwimlaneId = this.swimlaneId || null;
this.boardId = boardId;
this.swimlaneId = swimlaneId;
let _id = null;
existingListWithSameName = Lists.findOne({
boardId,
title: this.title,
archived: false,
});
if (existingListWithSameName) {
_id = existingListWithSameName._id;
} else {
delete this._id;
delete this.swimlaneId;
_id = Lists.insert(this);
}
// Copy all cards in list
Cards.find({
swimlaneId: oldSwimlaneId,
listId: oldId,
archived: false,
}).forEach((card) => {
card.copy(boardId, swimlaneId, _id);
});
},
cards(swimlaneId) {
const selector = {
listId: this._id,
@@ -169,6 +213,10 @@ Lists.helpers({
return this.color;
return '';
},
isTemplateList() {
return this.type === 'template-list';
},
});
Lists.mutations({
@@ -177,10 +225,20 @@ Lists.mutations({
},
archive() {
if (this.isTemplateList()) {
this.cards().forEach((card) => {
return card.archive();
});
}
return { $set: { archived: true } };
},
restore() {
if (this.isTemplateList()) {
this.allCards().forEach((card) => {
return card.restore();
});
}
return { $set: { archived: false } };
},
+76
View File
@@ -78,6 +78,13 @@ Swimlanes.attachSchema(new SimpleSchema({
}
},
},
type: {
/**
* The type of swimlane
*/
type: String,
defaultValue: 'swimlane',
},
}));
Swimlanes.allow({
@@ -94,6 +101,30 @@ Swimlanes.allow({
});
Swimlanes.helpers({
copy(boardId) {
const oldId = this._id;
const oldBoardId = this.boardId;
this.boardId = boardId;
delete this._id;
const _id = Swimlanes.insert(this);
const query = {
swimlaneId: {$in: [oldId, '']},
archived: false,
};
if (oldBoardId) {
query.boardId = oldBoardId;
}
// Copy all lists in swimlane
Lists.find(query).forEach((list) => {
list.type = 'list';
list.swimlaneId = oldId;
list.boardId = boardId;
list.copy(boardId, _id);
});
},
cards() {
return Cards.find(Filter.mongoSelector({
swimlaneId: this._id,
@@ -101,6 +132,18 @@ Swimlanes.helpers({
}), { sort: ['sort'] });
},
lists() {
return Lists.find({
boardId: this.boardId,
swimlaneId: {$in: [this._id, '']},
archived: false,
}, { sort: ['sort'] });
},
myLists() {
return Lists.find({ swimlaneId: this._id });
},
allCards() {
return Cards.find({ swimlaneId: this._id });
},
@@ -114,6 +157,29 @@ Swimlanes.helpers({
return this.color;
return '';
},
isTemplateSwimlane() {
return this.type === 'template-swimlane';
},
isTemplateContainer() {
return this.type === 'template-container';
},
isListTemplatesSwimlane() {
const user = Users.findOne(Meteor.userId());
return user.profile.listTemplatesSwimlaneId === this._id;
},
isCardTemplatesSwimlane() {
const user = Users.findOne(Meteor.userId());
return user.profile.cardTemplatesSwimlaneId === this._id;
},
isBoardTemplatesSwimlane() {
const user = Users.findOne(Meteor.userId());
return user.profile.boardTemplatesSwimlaneId === this._id;
},
});
Swimlanes.mutations({
@@ -122,10 +188,20 @@ Swimlanes.mutations({
},
archive() {
if (this.isTemplateSwimlane()) {
this.myLists().forEach((list) => {
return list.archive();
});
}
return { $set: { archived: true } };
},
restore() {
if (this.isTemplateSwimlane()) {
this.myLists().forEach((list) => {
return list.restore();
});
}
return { $set: { archived: false } };
},
+84 -2
View File
@@ -159,6 +159,34 @@ Users.attachSchema(new SimpleSchema({
'board-view-cal',
],
},
'profile.templatesBoardId': {
/**
* Reference to the templates board
*/
type: String,
defaultValue: '',
},
'profile.cardTemplatesSwimlaneId': {
/**
* Reference to the card templates swimlane Id
*/
type: String,
defaultValue: '',
},
'profile.listTemplatesSwimlaneId': {
/**
* Reference to the list templates swimlane Id
*/
type: String,
defaultValue: '',
},
'profile.boardTemplatesSwimlaneId': {
/**
* Reference to the board templates swimlane Id
*/
type: String,
defaultValue: '',
},
services: {
/**
* services field of the user
@@ -328,6 +356,14 @@ Users.helpers({
const profile = this.profile || {};
return profile.language || 'en';
},
getTemplatesBoardId() {
return this.profile.templatesBoardId;
},
getTemplatesBoardSlug() {
return Boards.findOne(this.profile.templatesBoardId).slug;
},
});
Users.mutations({
@@ -675,7 +711,6 @@ if (Meteor.isServer) {
CollectionHooks.getUserId = () => {
return fakeUserId.get() || getUserId();
};
/*
if (!isSandstorm) {
Users.after.insert((userId, doc) => {
const fakeUser = {
@@ -685,6 +720,7 @@ if (Meteor.isServer) {
};
fakeUserId.withValue(doc._id, () => {
/*
// Insert the Welcome Board
Boards.insert({
title: TAPi18n.__('welcome-board'),
@@ -701,10 +737,56 @@ if (Meteor.isServer) {
Lists.insert({title: TAPi18n.__(title), boardId, sort: titleIndex}, fakeUser);
});
});
*/
Boards.insert({
title: TAPi18n.__('templates'),
permission: 'private',
type: 'template-container',
}, fakeUser, (err, boardId) => {
// Insert the reference to our templates board
Users.update(fakeUserId.get(), {$set: {'profile.templatesBoardId': boardId}});
// Insert the card templates swimlane
Swimlanes.insert({
title: TAPi18n.__('card-templates-swimlane'),
boardId,
sort: 1,
type: 'template-container',
}, fakeUser, (err, swimlaneId) => {
// Insert the reference to out card templates swimlane
Users.update(fakeUserId.get(), {$set: {'profile.cardTemplatesSwimlaneId': swimlaneId}});
});
// Insert the list templates swimlane
Swimlanes.insert({
title: TAPi18n.__('list-templates-swimlane'),
boardId,
sort: 2,
type: 'template-container',
}, fakeUser, (err, swimlaneId) => {
// Insert the reference to out list templates swimlane
Users.update(fakeUserId.get(), {$set: {'profile.listTemplatesSwimlaneId': swimlaneId}});
});
// Insert the board templates swimlane
Swimlanes.insert({
title: TAPi18n.__('board-templates-swimlane'),
boardId,
sort: 3,
type: 'template-container',
}, fakeUser, (err, swimlaneId) => {
// Insert the reference to out board templates swimlane
Users.update(fakeUserId.get(), {$set: {'profile.boardTemplatesSwimlaneId': swimlaneId}});
});
});
});
});
}
*/
Users.after.insert((userId, doc) => {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v2.28.0",
"version": "v2.33.0",
"description": "Open-Source kanban",
"private": true,
"scripts": {
+2 -2
View File
@@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = (
appTitle = (defaultText = "Wekan"),
# The name of the app as it is displayed to the user.
appVersion = 230,
appVersion = 235,
# Increment this for every release.
appMarketingVersion = (defaultText = "2.28.0~2019-02-27"),
appMarketingVersion = (defaultText = "2.33.0~2019-02-28"),
# Human-readable presentation of the app version.
minUpgradableAppVersion = 0,
+95
View File
@@ -422,3 +422,98 @@ Migrations.add('add-defaultAuthenticationMethod', () => {
},
}, noValidateMulti);
});
Migrations.add('add-templates', () => {
Boards.update({
type: {
$exists: false,
},
}, {
$set: {
type: 'board',
},
}, noValidateMulti);
Swimlanes.update({
type: {
$exists: false,
},
}, {
$set: {
type: 'swimlane',
},
}, noValidateMulti);
Lists.update({
type: {
$exists: false,
},
swimlaneId: {
$exists: false,
},
}, {
$set: {
type: 'list',
swimlaneId: '',
},
}, noValidateMulti);
Users.find({
'profile.templatesBoardId': {
$exists: false,
},
}).forEach((user) => {
// Create board and swimlanes
Boards.insert({
title: TAPi18n.__('templates'),
permission: 'private',
type: 'template-container',
members: [
{
userId: user._id,
isAdmin: true,
isActive: true,
isNoComments: false,
isCommentOnly: false,
},
],
}, (err, boardId) => {
// Insert the reference to our templates board
Users.update(user._id, {$set: {'profile.templatesBoardId': boardId}});
// Insert the card templates swimlane
Swimlanes.insert({
title: TAPi18n.__('card-templates-swimlane'),
boardId,
sort: 1,
type: 'template-container',
}, (err, swimlaneId) => {
// Insert the reference to out card templates swimlane
Users.update(user._id, {$set: {'profile.cardTemplatesSwimlaneId': swimlaneId}});
});
// Insert the list templates swimlane
Swimlanes.insert({
title: TAPi18n.__('list-templates-swimlane'),
boardId,
sort: 2,
type: 'template-container',
}, (err, swimlaneId) => {
// Insert the reference to out list templates swimlane
Users.update(user._id, {$set: {'profile.listTemplatesSwimlaneId': swimlaneId}});
});
// Insert the board templates swimlane
Swimlanes.insert({
title: TAPi18n.__('board-templates-swimlane'),
boardId,
sort: 3,
type: 'template-container',
}, (err, swimlaneId) => {
// Insert the reference to out board templates swimlane
Users.update(user._id, {$set: {'profile.boardTemplatesSwimlaneId': swimlaneId}});
});
});
});
});
+38 -12
View File
@@ -32,6 +32,7 @@ Meteor.publish('boards', function() {
color: 1,
members: 1,
permission: 1,
type: 1,
},
});
});
@@ -59,6 +60,7 @@ Meteor.publish('archivedBoards', function() {
});
Meteor.publishRelations('board', function(boardId) {
this.unblock();
check(boardId, String);
const thisUserId = this.userId;
@@ -71,7 +73,8 @@ Meteor.publishRelations('board', function(boardId) {
{ permission: 'public' },
{ members: { $elemMatch: { userId: this.userId, isActive: true }}},
],
}, { limit: 1 }), function(boardId, board) {
// Sort required to ensure oplog usage
}, { limit: 1, sort: { _id: 1 } }), function(boardId, board) {
this.cursor(Lists.find({ boardId }));
this.cursor(Swimlanes.find({ boardId }));
this.cursor(Integrations.find({ boardId }));
@@ -98,24 +101,47 @@ Meteor.publishRelations('board', function(boardId) {
//
// And in the meantime our code below works pretty well -- it's not even a
// hack!
// Gather queries and send in bulk
const cardComments = this.join(CardComments);
cardComments.selector = (_ids) => ({ cardId: _ids });
const attachments = this.join(Attachments);
attachments.selector = (_ids) => ({ cardId: _ids });
const checklists = this.join(Checklists);
checklists.selector = (_ids) => ({ cardId: _ids });
const checklistItems = this.join(ChecklistItems);
checklistItems.selector = (_ids) => ({ cardId: _ids });
const parentCards = this.join(Cards);
parentCards.selector = (_ids) => ({ parentId: _ids });
const boards = this.join(Boards);
const subCards = this.join(Cards);
this.cursor(Cards.find({ boardId }), function(cardId, card) {
if (card.type === 'cardType-linkedCard') {
const impCardId = card.linkedId;
this.cursor(Cards.find({ _id: impCardId }));
this.cursor(CardComments.find({ cardId: impCardId }));
this.cursor(Attachments.find({ cardId: impCardId }));
this.cursor(Checklists.find({ cardId: impCardId }));
this.cursor(ChecklistItems.find({ cardId: impCardId }));
subCards.push(impCardId);
cardComments.push(impCardId);
attachments.push(impCardId);
checklists.push(impCardId);
checklistItems.push(impCardId);
} else if (card.type === 'cardType-linkedBoard') {
this.cursor(Boards.find({ _id: card.linkedId}));
boards.push(card.linkedId);
}
this.cursor(CardComments.find({ cardId }));
this.cursor(Attachments.find({ cardId }));
this.cursor(Checklists.find({ cardId }));
this.cursor(ChecklistItems.find({ cardId }));
this.cursor(Cards.find({ parentId: cardId }));
cardComments.push(cardId);
attachments.push(cardId);
checklists.push(cardId);
checklistItems.push(cardId);
parentCards.push(cardId);
});
// Send bulk queries for all found ids
subCards.send();
cardComments.send();
attachments.send();
checklists.send();
checklistItems.send();
boards.send();
if (board.members) {
// Board members. This publication also includes former board members that
// aren't members anymore but may have some activities attached to them in
+1 -1
View File
@@ -81,7 +81,7 @@ parts:
wekan:
source: .
plugin: nodejs
node-engine: 8.15.0
node-engine: 8.15.1
node-packages:
- node-gyp
- node-pre-gyp
+1 -1
View File
@@ -2,7 +2,7 @@
set -euxo pipefail
BUILD_DEPS="bsdtar gnupg wget curl bzip2 python git ca-certificates perl-Digest-SHA"
NODE_VERSION=v8.15.0
NODE_VERSION=v8.15.1
#METEOR_RELEASE=1.6.0.1 - for Stacksmith, meteor-1.8 branch that could have METEOR@1.8.1-beta.8 or newer
USE_EDGE=false
METEOR_EDGE=1.5-beta.17