diff options
author | Lauri Ojansivu <x@xet7.org> | 2020-04-19 16:31:12 +0300 |
---|---|---|
committer | Lauri Ojansivu <x@xet7.org> | 2020-04-19 16:31:12 +0300 |
commit | 8cb838c0de872ca561ab37130c64a7492fb52fc3 (patch) | |
tree | 1ce50b227c98de1c0bb1c34cdb412c4fd1372896 | |
parent | e7002f417b1e9550b257dfee6b669ef0ffa69012 (diff) | |
parent | 6cae14d6e9c58f394df14cf2ce48d3d53990c223 (diff) | |
download | wekan-8cb838c0de872ca561ab37130c64a7492fb52fc3.tar.gz wekan-8cb838c0de872ca561ab37130c64a7492fb52fc3.tar.bz2 wekan-8cb838c0de872ca561ab37130c64a7492fb52fc3.zip |
Merge branch 'boeserwolf-feature-sortable-boards'
-rw-r--r-- | client/components/boards/boardArchive.js | 2 | ||||
-rw-r--r-- | client/components/boards/boardsList.jade | 4 | ||||
-rw-r--r-- | client/components/boards/boardsList.js | 72 | ||||
-rw-r--r-- | client/components/boards/boardsList.styl | 15 | ||||
-rw-r--r-- | client/components/cards/cardDetails.js | 6 | ||||
-rw-r--r-- | client/components/lists/listBody.js | 4 | ||||
-rw-r--r-- | client/components/rules/actions/boardActions.js | 2 | ||||
-rw-r--r-- | client/components/settings/settingBody.js | 2 | ||||
-rw-r--r-- | client/components/sidebar/sidebar.js | 6 | ||||
-rw-r--r-- | models/boards.js | 29 | ||||
-rw-r--r-- | models/users.js | 30 | ||||
-rw-r--r-- | server/migrations.js | 12 | ||||
-rw-r--r-- | server/publications/boards.js | 5 |
13 files changed, 154 insertions, 35 deletions
diff --git a/client/components/boards/boardArchive.js b/client/components/boards/boardArchive.js index d3e65bd8..9f4d60a1 100644 --- a/client/components/boards/boardArchive.js +++ b/client/components/boards/boardArchive.js @@ -7,7 +7,7 @@ BlazeComponent.extendComponent({ return Boards.find( { archived: true }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ } }, ); }, diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade index 46086693..bbce1d6f 100644 --- a/client/components/boards/boardsList.jade +++ b/client/components/boards/boardsList.jade @@ -1,10 +1,10 @@ template(name="boardList") .wrapper - ul.board-list.clearfix + ul.board-list.clearfix.js-boards li.js-add-board a.board-list-item.label {{_ 'add-board'}} each boards - li(class="{{#if isStarred}}starred{{/if}}" class=colorClass) + li(class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board if isInvited .board-list-item span.details diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index 65bed16a..c700084f 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -1,4 +1,5 @@ const subManager = new SubsManager(); +const { calculateIndex, enableClickOnTouch } = Utils; Template.boardListHeaderBar.events({ 'click .js-open-archived-board'() { @@ -7,8 +8,8 @@ Template.boardListHeaderBar.events({ }); Template.boardListHeaderBar.helpers({ - title(){ - return FlowRouter.getRouteName() == 'home' ? 'my-boards' :'public'; + title() { + return FlowRouter.getRouteName() == 'home' ? 'my-boards' : 'public'; }, templatesBoardId() { return Meteor.user() && Meteor.user().getTemplatesBoardId(); @@ -23,20 +24,69 @@ BlazeComponent.extendComponent({ Meteor.subscribe('setting'); }, + onRendered() { + const self = this; + function userIsAllowedToMove() { + return Meteor.user(); + } + + const itemsSelector = '.js-board:not(.placeholder)'; + + const $boards = this.$('.js-boards'); + $boards.sortable({ + connectWith: '.js-boards', + tolerance: 'pointer', + appendTo: '.board-list', + helper: 'clone', + distance: 7, + items: itemsSelector, + placeholder: 'board-wrapper placeholder', + start(evt, ui) { + ui.helper.css('z-index', 1000); + ui.placeholder.height(ui.helper.height()); + EscapeActions.executeUpTo('popup-close'); + }, + stop(evt, ui) { + // To attribute the new index number, we need to get the DOM element + // of the previous and the following card -- if any. + const prevBoardDom = ui.item.prev('.js-board').get(0); + const nextBoardBom = ui.item.next('.js-board').get(0); + const sortIndex = calculateIndex(prevBoardDom, nextBoardBom, 1); + + const boardDomElement = ui.item.get(0); + const board = Blaze.getData(boardDomElement); + // Normally the jquery-ui sortable library moves the dragged DOM element + // to its new position, which disrupts Blaze reactive updates mechanism + // (especially when we move the last card of a list, or when multiple + // users move some cards at the same time). To prevent these UX glitches + // we ask sortable to gracefully cancel the move, and to put back the + // DOM in its initial state. The card move is then handled reactively by + // Blaze with the below query. + $boards.sortable('cancel'); + + board.move(sortIndex.base); + }, + }); + + // ugly touch event hotfix + enableClickOnTouch(itemsSelector); + + // Disable drag-dropping if the current user is not a board member or is comment only + this.autorun(() => { + $boards.sortable('option', 'disabled', !userIsAllowedToMove()); + }); + }, + boards() { let query = { archived: false, type: 'board', - } + }; if (FlowRouter.getRouteName() == 'home') - query['members.userId'] = Meteor.userId() - else - query.permission = 'public' - - return Boards.find( - query, - { sort: ['title'] }, - ); + query['members.userId'] = Meteor.userId(); + else query.permission = 'public'; + + return Boards.find(query, { sort: { sort: 1 /* boards default sorting */ } }); }, isStarred() { const user = Meteor.user(); diff --git a/client/components/boards/boardsList.styl b/client/components/boards/boardsList.styl index ae366e83..d12a0337 100644 --- a/client/components/boards/boardsList.styl +++ b/client/components/boards/boardsList.styl @@ -11,6 +11,19 @@ $spaceBetweenTiles = 16px box-sizing: border-box position: relative + &.placeholder:after + content: ''; + display: block; + background: darken(white, 20%) + border-radius: 3px; + height: 106px; + margin: 8px; + + &.ui-sortable-helper + cursor: grabbing + transform: rotate(4deg) + display: block !important + &.starred .fa-star, .fa-star-o @@ -183,7 +196,7 @@ $spaceBetweenTiles = 16px overflow: scroll li - width: 50% + width: 50% .board-list-item overflow: hidden diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 9d31fc60..ce504146 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -727,7 +727,7 @@ BlazeComponent.extendComponent({ _id: { $ne: Meteor.user().getTemplatesBoardId() }, }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); return boards; @@ -903,7 +903,7 @@ BlazeComponent.extendComponent({ }, }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); return boards; @@ -974,7 +974,7 @@ BlazeComponent.extendComponent({ } } }, - 'click .js-delete': Popup.afterConfirm('cardDelete', function () { + 'click .js-delete': Popup.afterConfirm('cardDelete', function() { Popup.close(); Cards.remove(this._id); Utils.goBoardId(this.boardId); diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 03f88f63..88f88db0 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -411,7 +411,7 @@ BlazeComponent.extendComponent({ type: 'board', }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); return boards; @@ -597,7 +597,7 @@ BlazeComponent.extendComponent({ type: 'board', }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); return boards; diff --git a/client/components/rules/actions/boardActions.js b/client/components/rules/actions/boardActions.js index c2f2375a..02910cc1 100644 --- a/client/components/rules/actions/boardActions.js +++ b/client/components/rules/actions/boardActions.js @@ -11,7 +11,7 @@ BlazeComponent.extendComponent({ }, }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); return boards; diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js index 319c066b..62752084 100644 --- a/client/components/settings/settingBody.js +++ b/client/components/settings/settingBody.js @@ -48,7 +48,7 @@ BlazeComponent.extendComponent({ 'members.isAdmin': true, }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); }, diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index 78b47a48..11471c2f 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -510,7 +510,7 @@ BlazeComponent.extendComponent({ 'members.userId': Meteor.userId(), }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); }, @@ -589,7 +589,7 @@ BlazeComponent.extendComponent({ 'subtext-with-parent', 'no-parent', ]; - options.forEach(function (element) { + options.forEach(function(element) { if (element !== value) { $(`#${element} ${MCB}`).toggleClass(CKCLS, false); $(`#${element}`).toggleClass(CKCLS, false); @@ -688,7 +688,7 @@ BlazeComponent.extendComponent({ 'members.userId': Meteor.userId(), }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); }, diff --git a/models/boards.js b/models/boards.js index 35ee1a36..170ebc5a 100644 --- a/models/boards.js +++ b/models/boards.js @@ -493,6 +493,14 @@ Boards.attachSchema( type: String, defaultValue: 'board', }, + sort: { + /** + * Sort value + */ + type: Number, + decimal: true, + defaultValue: -1, + }, }), ); @@ -1186,6 +1194,10 @@ Boards.mutations({ setPresentParentTask(presentParentTask) { return { $set: { presentParentTask } }; }, + + move(sortIndex) { + return { $set: { sort: sortIndex } }; + }, }); function boardRemover(userId, doc) { @@ -1283,6 +1295,14 @@ if (Meteor.isServer) { }); } +// Insert new board at last position in sort order. +Boards.before.insert((userId, doc) => { + const lastBoard = Boards.findOne({ sort: { $exists: true } }, { sort: { sort: -1 } }); + if (lastBoard && typeof lastBoard.sort !== 'undefined') { + doc.sort = lastBoard.sort + 1; + } +}); + if (Meteor.isServer) { // Let MongoDB ensure that a member is not included twice in the same board Meteor.startup(() => { @@ -1466,7 +1486,7 @@ if (Meteor.isServer) { 'members.userId': paramUserId, }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ).map(function(board) { return { @@ -1496,7 +1516,12 @@ if (Meteor.isServer) { Authentication.checkUserId(req.userId); JsonRoutes.sendResult(res, { code: 200, - data: Boards.find({ permission: 'public' }).map(function(doc) { + data: Boards.find( + { permission: 'public' }, + { + sort: { sort: 1 /* boards default sorting */ }, + }, + ).map(function(doc) { return { _id: doc._id, title: doc.title, diff --git a/models/users.js b/models/users.js index a9eeb38b..f4b7329a 100644 --- a/models/users.js +++ b/models/users.js @@ -386,12 +386,20 @@ if (Meteor.isClient) { Users.helpers({ boards() { - return Boards.find({ 'members.userId': this._id }); + return Boards.find( + { 'members.userId': this._id }, + { sort: { sort: 1 /* boards default sorting */ } }, + ); }, starredBoards() { const { starredBoards = [] } = this.profile || {}; - return Boards.find({ archived: false, _id: { $in: starredBoards } }); + return Boards.find( + { archived: false, _id: { $in: starredBoards } }, + { + sort: { sort: 1 /* boards default sorting */ }, + }, + ); }, hasStarred(boardId) { @@ -401,7 +409,12 @@ Users.helpers({ invitedBoards() { const { invitedBoards = [] } = this.profile || {}; - return Boards.find({ archived: false, _id: { $in: invitedBoards } }); + return Boards.find( + { archived: false, _id: { $in: invitedBoards } }, + { + sort: { sort: 1 /* boards default sorting */ }, + }, + ); }, isInvitedTo(boardId) { @@ -1292,10 +1305,13 @@ if (Meteor.isServer) { let data = Meteor.users.findOne({ _id: id }); if (data !== undefined) { if (action === 'takeOwnership') { - data = Boards.find({ - 'members.userId': id, - 'members.isAdmin': true, - }).map(function(board) { + data = Boards.find( + { + 'members.userId': id, + 'members.isAdmin': true, + }, + { sort: { sort: 1 /* boards default sorting */ } }, + ).map(function(board) { if (board.hasMember(req.userId)) { board.removeMember(req.userId); } diff --git a/server/migrations.js b/server/migrations.js index b4489987..21b54bda 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -1033,3 +1033,15 @@ Migrations.add('add-description-text-allowed', () => { noValidateMulti, ); }); + +Migrations.add('add-sort-field-to-boards', () => { + Boards.find().forEach((board, index) => { + if (!board.hasOwnProperty('sort')) { + Boards.direct.update( + board._id, + { $set: { sort: index } }, + noValidate + ); + } + }); +}); diff --git a/server/publications/boards.js b/server/publications/boards.js index 6fbd9860..b80a6b23 100644 --- a/server/publications/boards.js +++ b/server/publications/boards.js @@ -35,7 +35,9 @@ Meteor.publish('boards', function() { members: 1, permission: 1, type: 1, + sort: 1, }, + sort: { sort: 1 /* boards default sorting */ }, }, ); }); @@ -61,6 +63,7 @@ Meteor.publish('archivedBoards', function() { slug: 1, title: 1, }, + sort: { sort: 1 /* boards default sorting */ }, }, ); }); @@ -90,7 +93,7 @@ Meteor.publishRelations('board', function(boardId, isArchived) { $or, // Sort required to ensure oplog usage }, - { limit: 1, sort: { _id: 1 } }, + { limit: 1, sort: { sort: 1 /* boards default sorting */ } }, ), function(boardId, board) { this.cursor(Lists.find({ boardId, archived: isArchived })); |