mirror of
https://github.com/wekan/wekan.git
synced 2026-01-05 17:18:49 +01:00
Merge branch 'boeserwolf-feature-sortable-boards'
This commit is contained in:
commit
8cb838c0de
13 changed files with 153 additions and 34 deletions
|
|
@ -7,7 +7,7 @@ BlazeComponent.extendComponent({
|
|||
return Boards.find(
|
||||
{ archived: true },
|
||||
{
|
||||
sort: ['title'],
|
||||
sort: { sort: 1 /* boards default sorting */ }
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
query['members.userId'] = Meteor.userId();
|
||||
else query.permission = 'public';
|
||||
|
||||
return Boards.find(
|
||||
query,
|
||||
{ sort: ['title'] },
|
||||
);
|
||||
return Boards.find(query, { sort: { sort: 1 /* boards default sorting */ } });
|
||||
},
|
||||
isStarred() {
|
||||
const user = Meteor.user();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
},
|
||||
{
|
||||
sort: ['title'],
|
||||
sort: { sort: 1 /* boards default sorting */ },
|
||||
},
|
||||
);
|
||||
return boards;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ BlazeComponent.extendComponent({
|
|||
'members.isAdmin': true,
|
||||
},
|
||||
{
|
||||
sort: ['title'],
|
||||
sort: { sort: 1 /* boards default sorting */ },
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 */ },
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 }));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue