diff options
Diffstat (limited to 'client')
-rw-r--r-- | client/components/boards/boardArchive.js | 6 | ||||
-rw-r--r-- | client/components/boards/boardBody.jade | 11 | ||||
-rw-r--r-- | client/components/boards/boardHeader.jade | 17 | ||||
-rw-r--r-- | client/components/boards/boardHeader.js | 1 | ||||
-rw-r--r-- | client/components/boards/boardsList.jade | 3 | ||||
-rw-r--r-- | client/components/boards/boardsList.js | 16 | ||||
-rw-r--r-- | client/components/boards/miniboard.jade | 8 | ||||
-rw-r--r-- | client/components/cards/cardDetails.js | 58 | ||||
-rw-r--r-- | client/components/lists/listBody.jade | 64 | ||||
-rw-r--r-- | client/components/lists/listBody.js | 201 | ||||
-rw-r--r-- | client/components/lists/minilist.jade | 8 | ||||
-rwxr-xr-x | client/components/main/editor.js | 4 | ||||
-rw-r--r-- | client/components/swimlanes/miniswimlane.jade | 8 | ||||
-rw-r--r-- | client/components/swimlanes/swimlaneHeader.jade | 36 | ||||
-rw-r--r-- | client/components/swimlanes/swimlaneHeader.js | 3 | ||||
-rw-r--r-- | client/components/swimlanes/swimlanes.jade | 18 | ||||
-rw-r--r-- | client/components/swimlanes/swimlanes.js | 18 | ||||
-rw-r--r-- | client/components/users/userHeader.jade | 3 | ||||
-rw-r--r-- | client/components/users/userHeader.js | 9 |
19 files changed, 318 insertions, 174 deletions
diff --git a/client/components/boards/boardArchive.js b/client/components/boards/boardArchive.js index 8f4d5434..c8bbb341 100644 --- a/client/components/boards/boardArchive.js +++ b/client/components/boards/boardArchive.js @@ -1,9 +1,3 @@ -Template.boardListHeaderBar.events({ - 'click .js-open-archived-board'() { - Modal.open('archivedBoards'); - }, -}); - BlazeComponent.extendComponent({ onCreated() { this.subscribe('archivedBoards'); diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade index 3a40921d..32f8629f 100644 --- a/client/components/boards/boardBody.jade +++ b/client/components/boards/boardBody.jade @@ -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") diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index 746dae09..c4c9eeef 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -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 diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 89f686ab..492fda40 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -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'); diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade index 89852570..e36b8fc6 100644 --- a/client/components/boards/boardsList.jade +++ b/client/components/boards/boardsList.jade @@ -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'}} diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index 1ed88146..df495bb1 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -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'], }); diff --git a/client/components/boards/miniboard.jade b/client/components/boards/miniboard.jade new file mode 100644 index 00000000..d1fb0b07 --- /dev/null +++ b/client/components/boards/miniboard.jade @@ -0,0 +1,8 @@ +template(name="miniboard") + .minicard( + class="minicard-{{colorClass}}") + .minicard-title + .handle + .fa.fa-arrows + +viewer + = title diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index a571e21a..73a7a67d 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -456,26 +456,9 @@ 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; const slSelect = $('.js-select-swimlanes')[0]; @@ -490,38 +473,13 @@ Template.copyCardPopup.events({ if (title) { card.title = title; card.coverId = ''; - const _id = Cards.insert(card); + const _id = card.copy(); // 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(); diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade index d31070bd..9a9c322a 100644 --- a/client/components/lists/listBody.jade +++ b/client/components/lists/listBody.jade @@ -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,35 @@ 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") + 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) diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 0f5caac5..04c7eede 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -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,50 @@ 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 element = Blaze.getData(evt.currentTarget); + let _id = ''; + if (!this.isTemplateSearch || this.isCardTemplateSearch) { + // Card insertion + // 1. Common + element.boardId = this.boardId; + element.listId = this.listId; + element.swimlaneId = this.swimlaneId; + element.sort = Lists.findOne(this.listId).cards().count(); + // 1.A From template + if (this.isTemplateSearch) { + element.type = 'cardType-card'; + element.linkedId = ''; + _id = element.copy(); + // 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.boardId = this.boardId; + element.sort = Swimlanes.findOne(this.swimlaneId).lists().count(); + element.type = 'list'; + _id = element.copy(this.swimlaneId); + } else if (this.isSwimlaneTemplateSearch) { + element.boardId = this.boardId; + element.sort = Boards.findOne(this.boardId).swimlanes().count(); + element.type = 'swimlalne'; + _id = element.copy(); + } else if (this.isBoardTemplateSearch) { + board = Boards.findOne(element.linkedId); + board.sort = Boards.find({archived: false}).count(); + board.type = 'board'; + delete board.slug; + delete board.members; + _id = board.copy(); + } Popup.close(); }, }]; }, -}).register('searchCardPopup'); +}).register('searchElementPopup'); diff --git a/client/components/lists/minilist.jade b/client/components/lists/minilist.jade new file mode 100644 index 00000000..e34214c4 --- /dev/null +++ b/client/components/lists/minilist.jade @@ -0,0 +1,8 @@ +template(name="minilist") + .minicard( + class="minicard-{{colorClass}}") + .minicard-title + .handle + .fa.fa-arrows + +viewer + = title diff --git a/client/components/main/editor.js b/client/components/main/editor.js index 20ece562..88d8abf0 100755 --- a/client/components/main/editor.js +++ b/client/components/main/editor.js @@ -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) { diff --git a/client/components/swimlanes/miniswimlane.jade b/client/components/swimlanes/miniswimlane.jade new file mode 100644 index 00000000..d4be8599 --- /dev/null +++ b/client/components/swimlanes/miniswimlane.jade @@ -0,0 +1,8 @@ +template(name="miniswimlane") + .minicard( + class="minicard-{{colorClass}}") + .minicard-title + .handle + .fa.fa-arrows + +viewer + = title diff --git a/client/components/swimlanes/swimlaneHeader.jade b/client/components/swimlanes/swimlaneHeader.jade index 33eb5731..de9621d5 100644 --- a/client/components/swimlanes/swimlaneHeader.jade +++ b/client/components/swimlanes/swimlaneHeader.jade @@ -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 diff --git a/client/components/swimlanes/swimlaneHeader.js b/client/components/swimlanes/swimlaneHeader.js index 1004cb25..e7f3cc76 100644 --- a/client/components/swimlanes/swimlaneHeader.js +++ b/client/components/swimlanes/swimlaneHeader.js @@ -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'); diff --git a/client/components/swimlanes/swimlanes.jade b/client/components/swimlanes/swimlanes.jade index 34177a02..c56834df 100644 --- a/client/components/swimlanes/swimlanes.jade +++ b/client/components/swimlanes/swimlanes.jade @@ -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 diff --git a/client/components/swimlanes/swimlanes.js b/client/components/swimlanes/swimlanes.js index ce327f54..519b00d2 100644 --- a/client/components/swimlanes/swimlanes.js +++ b/client/components/swimlanes/swimlanes.js @@ -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'); diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade index af120045..c55b65c2 100644 --- a/client/components/users/userHeader.jade +++ b/client/components/users/userHeader.jade @@ -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 diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js index 63cbb14f..6a2397a4 100644 --- a/client/components/users/userHeader.js +++ b/client/components/users/userHeader.js @@ -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'), |