mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 07:20:12 +01:00
Add Features: allowing lists to be sorted by modifiedAt when not in draggable mode.
Bug Fix #2093: the broken should be prior to file attachment feature introduced, and tested export board is working. Thanks to whowillcare ! ( xet7 merged this pull request manually from https://github.com/wekan/wekan/pull/2756 ) Closes #2093
This commit is contained in:
parent
13a2bd6380
commit
7d6d3af54a
29 changed files with 475 additions and 41 deletions
|
|
@ -89,7 +89,7 @@ BlazeComponent.extendComponent({
|
||||||
helper.append(list.clone());
|
helper.append(list.clone());
|
||||||
return helper;
|
return helper;
|
||||||
},
|
},
|
||||||
handle: '.js-swimlane-header',
|
handle: '.js-swimlane-header-handle',
|
||||||
items: '.swimlane:not(.placeholder)',
|
items: '.swimlane:not(.placeholder)',
|
||||||
placeholder: 'swimlane placeholder',
|
placeholder: 'swimlane placeholder',
|
||||||
distance: 7,
|
distance: 7,
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,10 @@ template(name="boardHeaderBar")
|
||||||
i.fa.fa-archive
|
i.fa.fa-archive
|
||||||
span {{_ 'archives'}}
|
span {{_ 'archives'}}
|
||||||
|
|
||||||
|
if showSort
|
||||||
|
a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}")
|
||||||
|
i.fa(class="{{directionClass}}")
|
||||||
|
span {{_ 'sort'}}{{_ listSortShortDesc}}
|
||||||
a.board-header-btn.js-open-filter-view(
|
a.board-header-btn.js-open-filter-view(
|
||||||
title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
|
title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
|
||||||
class="{{#if Filter.isActive}}emphasis{{/if}}")
|
class="{{#if Filter.isActive}}emphasis{{/if}}")
|
||||||
|
|
@ -194,6 +198,20 @@ template(name="createBoard")
|
||||||
| /
|
| /
|
||||||
a.js-board-template {{_ 'template'}}
|
a.js-board-template {{_ 'template'}}
|
||||||
|
|
||||||
|
template(name="listsortPopup")
|
||||||
|
h2
|
||||||
|
| {{_ 'list-sort-by'}}
|
||||||
|
hr
|
||||||
|
ul.pop-over-list
|
||||||
|
each value in allowedSortValues
|
||||||
|
li
|
||||||
|
a.js-sort-by(name="{{value.name}}")
|
||||||
|
if $eq sortby value.name
|
||||||
|
i(class="fa {{Direction}}")
|
||||||
|
| {{_ value.label }}{{_ value.shortLabel}}
|
||||||
|
if $eq sortby value.name
|
||||||
|
i(class="fa fa-check")
|
||||||
|
|
||||||
template(name="boardChangeTitlePopup")
|
template(name="boardChangeTitlePopup")
|
||||||
form
|
form
|
||||||
label
|
label
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
const DOWNCLS = 'fa-sort-down';
|
||||||
|
const UPCLS = 'fa-sort-up';
|
||||||
Template.boardMenuPopup.events({
|
Template.boardMenuPopup.events({
|
||||||
'click .js-rename-board': Popup.open('boardChangeTitle'),
|
'click .js-rename-board': Popup.open('boardChangeTitle'),
|
||||||
'click .js-custom-fields'() {
|
'click .js-custom-fields'() {
|
||||||
|
|
@ -80,7 +82,25 @@ BlazeComponent.extendComponent({
|
||||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||||
return currentBoard && currentBoard.stars >= 2;
|
return currentBoard && currentBoard.stars >= 2;
|
||||||
},
|
},
|
||||||
|
showSort() {
|
||||||
|
return Meteor.user().hasSortBy();
|
||||||
|
},
|
||||||
|
directionClass() {
|
||||||
|
return this.currentDirection() === -1 ? DOWNCLS : UPCLS;
|
||||||
|
},
|
||||||
|
changeDirection() {
|
||||||
|
const direction = 0 - this.currentDirection() === -1 ? '-' : '';
|
||||||
|
Meteor.call('setListSortBy', direction + this.currentListSortBy());
|
||||||
|
},
|
||||||
|
currentDirection() {
|
||||||
|
return Meteor.user().getListSortByDirection();
|
||||||
|
},
|
||||||
|
currentListSortBy() {
|
||||||
|
return Meteor.user().getListSortBy();
|
||||||
|
},
|
||||||
|
listSortShortDesc() {
|
||||||
|
return `list-label-short-${this.currentListSortBy()}`;
|
||||||
|
},
|
||||||
events() {
|
events() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -118,6 +138,16 @@ BlazeComponent.extendComponent({
|
||||||
'click .js-open-filter-view'() {
|
'click .js-open-filter-view'() {
|
||||||
Sidebar.setView('filter');
|
Sidebar.setView('filter');
|
||||||
},
|
},
|
||||||
|
'click .js-open-sort-view'(evt) {
|
||||||
|
const target = evt.target;
|
||||||
|
if (target.tagName === 'I') {
|
||||||
|
// click on the text, popup choices
|
||||||
|
this.changeDirection();
|
||||||
|
} else {
|
||||||
|
// change the sort order
|
||||||
|
Popup.open('listsort')(evt);
|
||||||
|
}
|
||||||
|
},
|
||||||
'click .js-filter-reset'(event) {
|
'click .js-filter-reset'(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
Sidebar.setView();
|
Sidebar.setView();
|
||||||
|
|
@ -277,3 +307,73 @@ BlazeComponent.extendComponent({
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
}).register('boardChangeWatchPopup');
|
}).register('boardChangeWatchPopup');
|
||||||
|
|
||||||
|
BlazeComponent.extendComponent({
|
||||||
|
onCreated() {
|
||||||
|
//this.sortBy = new ReactiveVar();
|
||||||
|
////this.sortDirection = new ReactiveVar();
|
||||||
|
//this.setSortBy();
|
||||||
|
this.downClass = DOWNCLS;
|
||||||
|
this.upClass = UPCLS;
|
||||||
|
},
|
||||||
|
allowedSortValues() {
|
||||||
|
const types = [];
|
||||||
|
const pushed = {};
|
||||||
|
Meteor.user()
|
||||||
|
.getListSortTypes()
|
||||||
|
.forEach(type => {
|
||||||
|
const key = type.replace(/^-/, '');
|
||||||
|
if (pushed[key] === undefined) {
|
||||||
|
types.push({
|
||||||
|
name: key,
|
||||||
|
label: `list-label-${key}`,
|
||||||
|
shortLabel: `list-label-short-${key}`,
|
||||||
|
});
|
||||||
|
pushed[key] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return types;
|
||||||
|
},
|
||||||
|
Direction() {
|
||||||
|
return Meteor.user().getListSortByDirection() === -1
|
||||||
|
? this.downClass
|
||||||
|
: this.upClass;
|
||||||
|
},
|
||||||
|
sortby() {
|
||||||
|
return Meteor.user().getListSortBy();
|
||||||
|
},
|
||||||
|
|
||||||
|
setSortBy(type = null) {
|
||||||
|
const user = Meteor.user();
|
||||||
|
if (type === null) {
|
||||||
|
type = user._getListSortBy();
|
||||||
|
} else {
|
||||||
|
let value = '';
|
||||||
|
if (type.map) {
|
||||||
|
// is an array
|
||||||
|
value = (type[1] === -1 ? '-' : '') + type[0];
|
||||||
|
}
|
||||||
|
Meteor.call('setListSortBy', value);
|
||||||
|
}
|
||||||
|
//this.sortBy.set(type[0]);
|
||||||
|
//this.sortDirection.set(type[1]);
|
||||||
|
},
|
||||||
|
|
||||||
|
events() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'click .js-sort-by'(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
const target = evt.target;
|
||||||
|
const sortby = target.getAttribute('name');
|
||||||
|
const down = !!target.querySelector(`.${this.upClass}`);
|
||||||
|
const direction = down ? -1 : 1;
|
||||||
|
this.setSortBy([sortby, direction]);
|
||||||
|
if (Utils.isMiniScreen) {
|
||||||
|
Popup.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
}).register('listsortPopup');
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,13 @@ template(name="minicard")
|
||||||
class="{{#if isLinkedCard}}linked-card{{/if}}"
|
class="{{#if isLinkedCard}}linked-card{{/if}}"
|
||||||
class="{{#if isLinkedBoard}}linked-board{{/if}}"
|
class="{{#if isLinkedBoard}}linked-board{{/if}}"
|
||||||
class="minicard-{{colorClass}}")
|
class="minicard-{{colorClass}}")
|
||||||
|
if isMiniScreen
|
||||||
|
.handle
|
||||||
|
.fa.fa-arrows
|
||||||
|
unless isMiniScreen
|
||||||
|
if showDesktopDragHandles
|
||||||
|
.handle
|
||||||
|
.fa.fa-arrows
|
||||||
if cover
|
if cover
|
||||||
.minicard-cover(style="background-image: url('{{cover.url}}');")
|
.minicard-cover(style="background-image: url('{{cover.url}}');")
|
||||||
if labels
|
if labels
|
||||||
|
|
@ -15,8 +22,6 @@ template(name="minicard")
|
||||||
if hiddenMinicardLabelText
|
if hiddenMinicardLabelText
|
||||||
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
||||||
.minicard-title
|
.minicard-title
|
||||||
.handle
|
|
||||||
.fa.fa-arrows
|
|
||||||
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
|
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
|
||||||
.parent-prefix
|
.parent-prefix
|
||||||
| {{ parentString ' > ' }}
|
| {{ parentString ' > ' }}
|
||||||
|
|
@ -53,6 +58,8 @@ template(name="minicard")
|
||||||
if getDue
|
if getDue
|
||||||
.date
|
.date
|
||||||
+minicardDueDate
|
+minicardDueDate
|
||||||
|
if getEnd
|
||||||
|
+minicardEndDate
|
||||||
if getSpentTime
|
if getSpentTime
|
||||||
.date
|
.date
|
||||||
+cardSpentTime
|
+cardSpentTime
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ BlazeComponent.extendComponent({
|
||||||
}).register('minicard');
|
}).register('minicard');
|
||||||
|
|
||||||
Template.minicard.helpers({
|
Template.minicard.helpers({
|
||||||
|
showDesktopDragHandles() {
|
||||||
|
return Meteor.user().hasShowDesktopDragHandles();
|
||||||
|
},
|
||||||
hiddenMinicardLabelText() {
|
hiddenMinicardLabelText() {
|
||||||
return Meteor.user().hasHiddenMinicardLabelText();
|
return Meteor.user().hasHiddenMinicardLabelText();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,7 @@
|
||||||
right: 5px;
|
right: 5px;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
display:none;
|
display:none;
|
||||||
// @media only screen and (max-width: 1199px) {
|
@media only screen {
|
||||||
@media only screen and (max-width: 800px) {
|
|
||||||
display:block;
|
display:block;
|
||||||
}
|
}
|
||||||
.fa-arrows
|
.fa-arrows
|
||||||
|
|
@ -128,7 +127,7 @@
|
||||||
.badges
|
.badges
|
||||||
float: left
|
float: left
|
||||||
margin-top: 7px
|
margin-top: 7px
|
||||||
color: darken(white, 80%)
|
color: darken(white, 50%)
|
||||||
|
|
||||||
&:empty
|
&:empty
|
||||||
display: none
|
display: none
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,13 @@ BlazeComponent.extendComponent({
|
||||||
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
||||||
const $cards = this.$('.js-minicards');
|
const $cards = this.$('.js-minicards');
|
||||||
|
|
||||||
if (Utils.isMiniScreen()) {
|
if (Utils.isMiniScreen) {
|
||||||
|
$('.js-minicards').sortable({
|
||||||
|
handle: '.handle',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Utils.isMiniScreen && showDesktopDragHandles) {
|
||||||
$('.js-minicards').sortable({
|
$('.js-minicards').sortable({
|
||||||
handle: '.handle',
|
handle: '.handle',
|
||||||
});
|
});
|
||||||
|
|
@ -155,6 +161,12 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
}).register('list');
|
}).register('list');
|
||||||
|
|
||||||
|
Template.list.helpers({
|
||||||
|
showDesktopDragHandles() {
|
||||||
|
return Meteor.user().hasShowDesktopDragHandles();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Template.miniList.events({
|
Template.miniList.events({
|
||||||
'click .js-select-list'() {
|
'click .js-select-list'() {
|
||||||
const listId = this._id;
|
const listId = this._id;
|
||||||
|
|
|
||||||
|
|
@ -84,17 +84,16 @@
|
||||||
padding-left: 10px
|
padding-left: 10px
|
||||||
color: #a6a6a6
|
color: #a6a6a6
|
||||||
|
|
||||||
|
|
||||||
.list-header-menu
|
.list-header-menu
|
||||||
position: absolute
|
position: absolute
|
||||||
padding: 27px 19px
|
padding: 27px 19px
|
||||||
margin-top: 1px
|
margin-top: 1px
|
||||||
top: -7px
|
top: -7px
|
||||||
right: -7px
|
right: 3px
|
||||||
|
|
||||||
.list-header-plus-icon
|
.list-header-plus-icon
|
||||||
color: #a6a6a6
|
color: #a6a6a6
|
||||||
margin-right: 10px
|
margin-right: 15px
|
||||||
|
|
||||||
.highlight
|
.highlight
|
||||||
color: #ce1414
|
color: #ce1414
|
||||||
|
|
@ -165,7 +164,16 @@
|
||||||
|
|
||||||
@media screen and (max-width: 800px)
|
@media screen and (max-width: 800px)
|
||||||
.list-header-menu
|
.list-header-menu
|
||||||
margin-right: 30px
|
position: absolute
|
||||||
|
padding: 27px 19px
|
||||||
|
margin-top: 1px
|
||||||
|
top: -7px
|
||||||
|
margin-right: 50px
|
||||||
|
right: -3px
|
||||||
|
|
||||||
|
.list-header
|
||||||
|
.list-header-name
|
||||||
|
margin-left: 1.4rem
|
||||||
|
|
||||||
.mini-list
|
.mini-list
|
||||||
flex: 0 0 60px
|
flex: 0 0 60px
|
||||||
|
|
@ -221,9 +229,17 @@
|
||||||
padding: 7px
|
padding: 7px
|
||||||
top: 50%
|
top: 50%
|
||||||
transform: translateY(-50%)
|
transform: translateY(-50%)
|
||||||
right: 17px
|
margin-right: 27px
|
||||||
font-size: 20px
|
font-size: 20px
|
||||||
|
|
||||||
|
.list-header-menu-handle
|
||||||
|
position: absolute
|
||||||
|
padding: 7px
|
||||||
|
top: 50%
|
||||||
|
transform: translateY(-50%)
|
||||||
|
right: 10px
|
||||||
|
font-size: 24px
|
||||||
|
|
||||||
.link-board-wrapper
|
.link-board-wrapper
|
||||||
display: flex
|
display: flex
|
||||||
align-items: baseline
|
align-items: baseline
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ template(name="listHeader")
|
||||||
if currentList
|
if currentList
|
||||||
a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
|
a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
|
||||||
h2.list-header-name(
|
h2.list-header-name(
|
||||||
|
title="{{ moment modifiedAt 'LLL' }}"
|
||||||
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}js-open-inlined-form is-editable{{/unless}}{{/if}}")
|
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}js-open-inlined-form is-editable{{/unless}}{{/if}}")
|
||||||
+viewer
|
+viewer
|
||||||
= title
|
= title
|
||||||
|
|
@ -29,16 +30,22 @@ template(name="listHeader")
|
||||||
if canSeeAddCard
|
if canSeeAddCard
|
||||||
a.js-add-card.fa.fa-plus.list-header-plus-icon
|
a.js-add-card.fa.fa-plus.list-header-plus-icon
|
||||||
a.fa.fa-navicon.js-open-list-menu
|
a.fa.fa-navicon.js-open-list-menu
|
||||||
|
a.list-header-menu-handle.handle.fa.fa-arrows.js-list-handle
|
||||||
else
|
else
|
||||||
a.list-header-menu-icon.fa.fa-angle-right.js-select-list
|
a.list-header-menu-icon.fa.fa-angle-right.js-select-list
|
||||||
|
a.list-header-menu-handle.handle.fa.fa-arrows.js-list-handle
|
||||||
else if currentUser.isBoardMember
|
else if currentUser.isBoardMember
|
||||||
if isWatching
|
if isWatching
|
||||||
i.list-header-watch-icon.fa.fa-eye
|
i.list-header-watch-icon.fa.fa-eye
|
||||||
div.list-header-menu
|
div.list-header-menu
|
||||||
unless currentUser.isCommentOnly
|
unless currentUser.isCommentOnly
|
||||||
|
if isBoardAdmin
|
||||||
|
a.fa.js-list-star.list-header-plus-icon(class="fa-star{{#unless starred}}-o{{/unless}}")
|
||||||
if canSeeAddCard
|
if canSeeAddCard
|
||||||
a.js-add-card.fa.fa-plus.list-header-plus-icon
|
a.js-add-card.fa.fa-plus.list-header-plus-icon
|
||||||
a.fa.fa-navicon.js-open-list-menu
|
a.fa.fa-navicon.js-open-list-menu
|
||||||
|
if showDesktopDragHandles
|
||||||
|
a.list-header-menu-handle.handle.fa.fa-arrows.js-list-handle
|
||||||
|
|
||||||
template(name="editListTitleForm")
|
template(name="editListTitleForm")
|
||||||
.list-composer
|
.list-composer
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,20 @@ BlazeComponent.extendComponent({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isBoardAdmin() {
|
||||||
|
return Meteor.user().isBoardAdmin();
|
||||||
|
},
|
||||||
|
starred(check = undefined) {
|
||||||
|
const list = Template.currentData();
|
||||||
|
const status = list.isStarred();
|
||||||
|
if (check === undefined) {
|
||||||
|
// just check
|
||||||
|
return status;
|
||||||
|
} else {
|
||||||
|
list.star(!status);
|
||||||
|
return !status;
|
||||||
|
}
|
||||||
|
},
|
||||||
editTitle(event) {
|
editTitle(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const newTitle = this.childComponents('inlinedForm')[0]
|
const newTitle = this.childComponents('inlinedForm')[0]
|
||||||
|
|
@ -61,6 +75,10 @@ BlazeComponent.extendComponent({
|
||||||
events() {
|
events() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
'click .js-list-star'(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.starred(!this.starred());
|
||||||
|
},
|
||||||
'click .js-open-list-menu': Popup.open('listAction'),
|
'click .js-open-list-menu': Popup.open('listAction'),
|
||||||
'click .js-add-card'(event) {
|
'click .js-add-card'(event) {
|
||||||
const listDom = $(event.target).parents(
|
const listDom = $(event.target).parents(
|
||||||
|
|
@ -80,6 +98,12 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
}).register('listHeader');
|
}).register('listHeader');
|
||||||
|
|
||||||
|
Template.listHeader.helpers({
|
||||||
|
showDesktopDragHandles() {
|
||||||
|
return Meteor.user().hasShowDesktopDragHandles();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Template.listActionPopup.helpers({
|
Template.listActionPopup.helpers({
|
||||||
isWipLimitEnabled() {
|
isWipLimitEnabled() {
|
||||||
return Template.currentData().getWipLimit('enabled');
|
return Template.currentData().getWipLimit('enabled');
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
and #each x in y constructors to fix this.
|
and #each x in y constructors to fix this.
|
||||||
|
|
||||||
template(name="filterSidebar")
|
template(name="filterSidebar")
|
||||||
|
ul.sidebar-list
|
||||||
|
span {{_ 'list-filter-label'}}
|
||||||
|
form.js-list-filter
|
||||||
|
input(type="text")
|
||||||
ul.sidebar-list
|
ul.sidebar-list
|
||||||
li(class="{{#if Filter.labelIds.isSelected undefined}}active{{/if}}")
|
li(class="{{#if Filter.labelIds.isSelected undefined}}active{{/if}}")
|
||||||
a.name.js-toggle-label-filter
|
a.name.js-toggle-label-filter
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@ BlazeComponent.extendComponent({
|
||||||
events() {
|
events() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
'submit .js-list-filter'(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
Filter.lists.set(this.find('.js-list-filter input').value.trim());
|
||||||
|
},
|
||||||
'click .js-toggle-label-filter'(evt) {
|
'click .js-toggle-label-filter'(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
Filter.labelIds.toggle(this.currentData()._id);
|
Filter.labelIds.toggle(this.currentData()._id);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@ template(name="searchSidebar")
|
||||||
form.js-search-term-form
|
form.js-search-term-form
|
||||||
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto")
|
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto")
|
||||||
.list-body.js-perfect-scrollbar
|
.list-body.js-perfect-scrollbar
|
||||||
|
.minilists.clearfix.js-minilists
|
||||||
|
each (lists)
|
||||||
|
a.minilist-wrapper.js-minilist(href=absoluteUrl)
|
||||||
|
+minilist(this)
|
||||||
.minicards.clearfix.js-minicards
|
.minicards.clearfix.js-minicards
|
||||||
each (results)
|
each (results)
|
||||||
a.minicard-wrapper.js-minicard(href=absoluteUrl)
|
a.minicard-wrapper.js-minicard(href=absoluteUrl)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,11 @@ BlazeComponent.extendComponent({
|
||||||
return currentBoard.searchCards(this.term.get());
|
return currentBoard.searchCards(this.term.get());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
lists() {
|
||||||
|
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||||
|
return currentBoard.searchLists(this.term.get());
|
||||||
|
},
|
||||||
|
|
||||||
events() {
|
events() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ template(name="swimlaneFixedHeader")
|
||||||
unless currentUser.isCommentOnly
|
unless currentUser.isCommentOnly
|
||||||
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon
|
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon
|
||||||
a.fa.fa-navicon.js-open-swimlane-menu
|
a.fa.fa-navicon.js-open-swimlane-menu
|
||||||
|
if showDesktopDragHandles
|
||||||
|
a.swimlane-header-menu-handle.handle.fa.fa-arrows.js-swimlane-header-handle
|
||||||
|
|
||||||
template(name="editSwimlaneTitleForm")
|
template(name="editSwimlaneTitleForm")
|
||||||
.list-composer
|
.list-composer
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,12 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
}).register('swimlaneHeader');
|
}).register('swimlaneHeader');
|
||||||
|
|
||||||
|
Template.swimlaneHeader.helpers({
|
||||||
|
showDesktopDragHandles() {
|
||||||
|
return Meteor.user().hasShowDesktopDragHandles();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Template.swimlaneActionPopup.events({
|
Template.swimlaneActionPopup.events({
|
||||||
'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'),
|
'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'),
|
||||||
'click .js-close-swimlane'(event) {
|
'click .js-close-swimlane'(event) {
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,13 @@ template(name="swimlane")
|
||||||
unless currentUser.isCommentOnly
|
unless currentUser.isCommentOnly
|
||||||
+addListForm
|
+addListForm
|
||||||
else
|
else
|
||||||
|
if currentUser.isBoardMember
|
||||||
|
unless currentUser.isCommentOnly
|
||||||
|
+addListForm
|
||||||
each lists
|
each lists
|
||||||
+list(this)
|
+list(this)
|
||||||
if currentCardIsInThisList _id ../_id
|
if currentCardIsInThisList _id ../_id
|
||||||
+cardDetails(currentCard)
|
+cardDetails(currentCard)
|
||||||
if currentUser.isBoardMember
|
|
||||||
unless currentUser.isCommentOnly
|
|
||||||
+addListForm
|
|
||||||
|
|
||||||
template(name="listsGroup")
|
template(name="listsGroup")
|
||||||
.swimlane.list-group.js-lists
|
.swimlane.list-group.js-lists
|
||||||
|
|
@ -26,23 +26,23 @@ template(name="listsGroup")
|
||||||
if currentList
|
if currentList
|
||||||
+list(currentList)
|
+list(currentList)
|
||||||
else
|
else
|
||||||
each lists
|
|
||||||
+miniList(this)
|
|
||||||
if currentUser.isBoardMember
|
if currentUser.isBoardMember
|
||||||
unless currentUser.isCommentOnly
|
unless currentUser.isCommentOnly
|
||||||
+addListForm
|
+addListForm
|
||||||
|
each lists
|
||||||
|
+miniList(this)
|
||||||
else
|
else
|
||||||
|
if currentUser.isBoardMember
|
||||||
|
unless currentUser.isCommentOnly
|
||||||
|
+addListForm
|
||||||
each lists
|
each lists
|
||||||
if visible this
|
if visible this
|
||||||
+list(this)
|
+list(this)
|
||||||
if currentCardIsInThisList _id null
|
if currentCardIsInThisList _id null
|
||||||
+cardDetails(currentCard)
|
+cardDetails(currentCard)
|
||||||
if currentUser.isBoardMember
|
|
||||||
unless currentUser.isCommentOnly
|
|
||||||
+addListForm
|
|
||||||
|
|
||||||
template(name="addListForm")
|
template(name="addListForm")
|
||||||
.list.list-composer.js-list-composer
|
.list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
|
||||||
.list-header-add
|
.list-header-add
|
||||||
+inlinedForm(autoclose=false)
|
+inlinedForm(autoclose=false)
|
||||||
input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
|
input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,21 @@ function initSortable(boardComponent, $listsDom) {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Utils.isMiniScreen) {
|
||||||
|
$listsDom.sortable({
|
||||||
|
handle: '.js-list-handle',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Utils.isMiniScreen && showDesktopDragHandles) {
|
||||||
|
$listsDom.sortable({
|
||||||
|
handle: '.js-list-header',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$listsDom.sortable({
|
$listsDom.sortable({
|
||||||
tolerance: 'pointer',
|
tolerance: 'pointer',
|
||||||
helper: 'clone',
|
helper: 'clone',
|
||||||
handle: '.js-list-header',
|
|
||||||
items: '.js-list:not(.js-list-composer)',
|
items: '.js-list:not(.js-list-composer)',
|
||||||
placeholder: 'list placeholder',
|
placeholder: 'list placeholder',
|
||||||
distance: 7,
|
distance: 7,
|
||||||
|
|
@ -151,13 +162,13 @@ BlazeComponent.extendComponent({
|
||||||
// define a list of elements in which we disable the dragging because
|
// define a list of elements in which we disable the dragging because
|
||||||
// the user will legitimately expect to be able to select some text with
|
// the user will legitimately expect to be able to select some text with
|
||||||
// his mouse.
|
// his mouse.
|
||||||
const noDragInside = [
|
|
||||||
'a',
|
const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
|
||||||
'input',
|
Util.isMiniScreen || (!Util.isMiniScreen && showDesktopDragHandles)
|
||||||
'textarea',
|
? ['.js-list-handle', '.js-swimlane-header-handle']
|
||||||
'p',
|
: ['.js-list-header'],
|
||||||
'.js-list-header',
|
);
|
||||||
];
|
|
||||||
if (
|
if (
|
||||||
$(evt.target).closest(noDragInside.join(',')).length === 0 &&
|
$(evt.target).closest(noDragInside.join(',')).length === 0 &&
|
||||||
this.$('.swimlane').prop('clientHeight') > evt.offsetY
|
this.$('.swimlane').prop('clientHeight') > evt.offsetY
|
||||||
|
|
@ -233,6 +244,9 @@ BlazeComponent.extendComponent({
|
||||||
}).register('addListForm');
|
}).register('addListForm');
|
||||||
|
|
||||||
Template.swimlane.helpers({
|
Template.swimlane.helpers({
|
||||||
|
showDesktopDragHandles() {
|
||||||
|
return Meteor.user().hasShowDesktopDragHandles();
|
||||||
|
},
|
||||||
canSeeAddList() {
|
canSeeAddList() {
|
||||||
return (
|
return (
|
||||||
Meteor.user() &&
|
Meteor.user() &&
|
||||||
|
|
@ -253,6 +267,11 @@ BlazeComponent.extendComponent({
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Filter.lists._isActive()) {
|
||||||
|
if (!list.title.match(Filter.lists.getRegexSelector())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (Filter.hideEmpty.isSelected()) {
|
if (Filter.hideEmpty.isSelected()) {
|
||||||
const swimlaneId = this.parentComponent()
|
const swimlaneId = this.parentComponent()
|
||||||
.parentComponent()
|
.parentComponent()
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,14 @@
|
||||||
margin-left: 5px
|
margin-left: 5px
|
||||||
margin-right: 10px
|
margin-right: 10px
|
||||||
|
|
||||||
|
.swimlane-header-menu-handle
|
||||||
|
position: absolute
|
||||||
|
padding: 7px
|
||||||
|
top: 50%
|
||||||
|
transform: translateY(-50%)
|
||||||
|
left: 300px
|
||||||
|
font-size: 18px
|
||||||
|
|
||||||
.list-group
|
.list-group
|
||||||
height: 100%
|
height: 100%
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,11 @@ template(name="changeSettingsPopup")
|
||||||
| {{_ 'hide-system-messages'}}
|
| {{_ 'hide-system-messages'}}
|
||||||
if hiddenSystemMessages
|
if hiddenSystemMessages
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
|
li
|
||||||
|
a.js-toggle-desktop-drag-handles
|
||||||
|
| {{_ 'show-desktop-drag-handles'}}
|
||||||
|
if showDesktopDragHandles
|
||||||
|
i.fa.fa-check
|
||||||
li
|
li
|
||||||
label.bold
|
label.bold
|
||||||
| {{_ 'show-cards-minimum-count'}}
|
| {{_ 'show-cards-minimum-count'}}
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,9 @@ Template.changeLanguagePopup.events({
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.changeSettingsPopup.helpers({
|
Template.changeSettingsPopup.helpers({
|
||||||
|
showDesktopDragHandles() {
|
||||||
|
return Meteor.user().hasShowDesktopDragHandles();
|
||||||
|
},
|
||||||
hiddenSystemMessages() {
|
hiddenSystemMessages() {
|
||||||
return Meteor.user().hasHiddenSystemMessages();
|
return Meteor.user().hasHiddenSystemMessages();
|
||||||
},
|
},
|
||||||
|
|
@ -170,6 +173,9 @@ Template.changeSettingsPopup.helpers({
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.changeSettingsPopup.events({
|
Template.changeSettingsPopup.events({
|
||||||
|
'click .js-toggle-desktop-drag-handles'() {
|
||||||
|
Meteor.call('toggleDesktopDragHandles');
|
||||||
|
},
|
||||||
'click .js-toggle-system-messages'() {
|
'click .js-toggle-system-messages'() {
|
||||||
Meteor.call('toggleSystemMessages');
|
Meteor.call('toggleSystemMessages');
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -439,6 +439,14 @@ class AdvancedFilter {
|
||||||
const commands = this._filterToCommands();
|
const commands = this._filterToCommands();
|
||||||
return this._arrayToSelector(commands);
|
return this._arrayToSelector(commands);
|
||||||
}
|
}
|
||||||
|
getRegexSelector() {
|
||||||
|
// generate a regex for filter list
|
||||||
|
this._dep.depend();
|
||||||
|
return new RegExp(
|
||||||
|
`^.*${this._filter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*$`,
|
||||||
|
'i',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The global Filter object.
|
// The global Filter object.
|
||||||
|
|
@ -455,6 +463,7 @@ Filter = {
|
||||||
hideEmpty: new SetFilter(),
|
hideEmpty: new SetFilter(),
|
||||||
customFields: new SetFilter('_id'),
|
customFields: new SetFilter('_id'),
|
||||||
advanced: new AdvancedFilter(),
|
advanced: new AdvancedFilter(),
|
||||||
|
lists: new AdvancedFilter(), // we need the ability to filter list by name as well
|
||||||
|
|
||||||
_fields: ['labelIds', 'members', 'archive', 'hideEmpty', 'customFields'],
|
_fields: ['labelIds', 'members', 'archive', 'hideEmpty', 'customFields'],
|
||||||
|
|
||||||
|
|
@ -533,6 +542,7 @@ Filter = {
|
||||||
const filter = this[fieldName];
|
const filter = this[fieldName];
|
||||||
filter.reset();
|
filter.reset();
|
||||||
});
|
});
|
||||||
|
this.lists.reset();
|
||||||
this.advanced.reset();
|
this.advanced.reset();
|
||||||
this.resetExceptions();
|
this.resetExceptions();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -300,8 +300,18 @@
|
||||||
"error-username-taken": "This username is already taken",
|
"error-username-taken": "This username is already taken",
|
||||||
"error-email-taken": "Email has already been taken",
|
"error-email-taken": "Email has already been taken",
|
||||||
"export-board": "Export board",
|
"export-board": "Export board",
|
||||||
|
"sort": "Sort",
|
||||||
|
"sort-desc": "Click to Sort List",
|
||||||
|
"list-sort-by": "Sort the List By:",
|
||||||
|
"list-label-modifiedAt": "Last Access Time",
|
||||||
|
"list-label-title": "Name of the List",
|
||||||
|
"list-label-sort": "Your Manual Order",
|
||||||
|
"list-label-short-modifiedAt": "(L)",
|
||||||
|
"list-label-short-title": "(N)",
|
||||||
|
"list-label-short-sort": "(M)",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
"filter-cards": "Filter Cards",
|
"filter-cards": "Filter Cards or Lists",
|
||||||
|
"list-filter-label": "Filter List by Title",
|
||||||
"filter-clear": "Clear filter",
|
"filter-clear": "Clear filter",
|
||||||
"filter-no-label": "No label",
|
"filter-no-label": "No label",
|
||||||
"filter-no-member": "No member",
|
"filter-no-member": "No member",
|
||||||
|
|
@ -426,7 +436,7 @@
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"rules": "Rules",
|
"rules": "Rules",
|
||||||
"search-cards": "Search from card titles and descriptions on this board",
|
"search-cards": "Search from card/list titles and descriptions on this board",
|
||||||
"search-example": "Text to search for?",
|
"search-example": "Text to search for?",
|
||||||
"select-color": "Select Color",
|
"select-color": "Select Color",
|
||||||
"set-wip-limit-value": "Set a limit for the maximum number of tasks in this list",
|
"set-wip-limit-value": "Set a limit for the maximum number of tasks in this list",
|
||||||
|
|
|
||||||
|
|
@ -409,6 +409,23 @@ Boards.helpers({
|
||||||
},
|
},
|
||||||
|
|
||||||
lists() {
|
lists() {
|
||||||
|
const enabled = Meteor.user().hasSortBy();
|
||||||
|
return enabled ? this.newestLists() : this.draggableLists();
|
||||||
|
},
|
||||||
|
|
||||||
|
newestLists() {
|
||||||
|
// sorted lists from newest to the oldest, by its creation date or its cards' last modification date
|
||||||
|
const value = Meteor.user()._getListSortBy();
|
||||||
|
const sortKey = { starred: -1, [value[0]]: value[1] }; // [["starred",-1],value];
|
||||||
|
return Lists.find(
|
||||||
|
{
|
||||||
|
boardId: this._id,
|
||||||
|
archived: false,
|
||||||
|
},
|
||||||
|
{ sort: sortKey },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
draggableLists() {
|
||||||
return Lists.find({ boardId: this._id }, { sort: { sort: 1 } });
|
return Lists.find({ boardId: this._id }, { sort: { sort: 1 } });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1695,6 +1695,25 @@ if (Meteor.isServer) {
|
||||||
const oldvalue = doc[action] || '';
|
const oldvalue = doc[action] || '';
|
||||||
const activityType = `a-${action}`;
|
const activityType = `a-${action}`;
|
||||||
const card = Cards.findOne(doc._id);
|
const card = Cards.findOne(doc._id);
|
||||||
|
const list = card.list();
|
||||||
|
if (list && action === 'endAt') {
|
||||||
|
// change list modifiedAt
|
||||||
|
const modifiedAt = new Date(
|
||||||
|
new Date(value).getTime() - 365 * 24 * 3600 * 1e3,
|
||||||
|
); // set it as 1 year before
|
||||||
|
const boardId = list.boardId;
|
||||||
|
Lists.direct.update(
|
||||||
|
{
|
||||||
|
_id: list._id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
modifiedAt,
|
||||||
|
boardId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
const username = Users.findOne(userId).username;
|
const username = Users.findOne(userId).username;
|
||||||
const activity = {
|
const activity = {
|
||||||
userId,
|
userId,
|
||||||
|
|
@ -1852,15 +1871,8 @@ if (Meteor.isServer) {
|
||||||
const check = Users.findOne({
|
const check = Users.findOne({
|
||||||
_id: req.body.authorId,
|
_id: req.body.authorId,
|
||||||
});
|
});
|
||||||
|
const members = req.body.members || [req.body.authorId];
|
||||||
if (typeof check !== 'undefined') {
|
if (typeof check !== 'undefined') {
|
||||||
let members = req.body.members || [];
|
|
||||||
if (_.isString(members)) {
|
|
||||||
if (members === '') {
|
|
||||||
members = [];
|
|
||||||
} else {
|
|
||||||
members = [members];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const id = Cards.direct.insert({
|
const id = Cards.direct.insert({
|
||||||
title: req.body.title,
|
title: req.body.title,
|
||||||
boardId: paramBoardId,
|
boardId: paramBoardId,
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,18 @@ if (Meteor.isServer) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// exporter maybe is broken since Gridfs introduced, add fs and path
|
||||||
|
|
||||||
export class Exporter {
|
export class Exporter {
|
||||||
constructor(boardId) {
|
constructor(boardId) {
|
||||||
this._boardId = boardId;
|
this._boardId = boardId;
|
||||||
}
|
}
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
|
const fs = Npm.require('fs');
|
||||||
|
const os = Npm.require('os');
|
||||||
|
const path = Npm.require('path');
|
||||||
|
|
||||||
const byBoard = { boardId: this._boardId };
|
const byBoard = { boardId: this._boardId };
|
||||||
const byBoardNoLinked = {
|
const byBoardNoLinked = {
|
||||||
boardId: this._boardId,
|
boardId: this._boardId,
|
||||||
|
|
@ -134,6 +140,11 @@ export class Exporter {
|
||||||
const getBase64Data = function(doc, callback) {
|
const getBase64Data = function(doc, callback) {
|
||||||
let buffer = new Buffer(0);
|
let buffer = new Buffer(0);
|
||||||
// callback has the form function (err, res) {}
|
// callback has the form function (err, res) {}
|
||||||
|
const tmpFile = path.join(
|
||||||
|
os.tmpdir(),
|
||||||
|
`tmpexport${process.pid}${Math.random()}`,
|
||||||
|
);
|
||||||
|
const tmpWriteable = fs.createWriteStream(tmpFile);
|
||||||
const readStream = doc.createReadStream();
|
const readStream = doc.createReadStream();
|
||||||
readStream.on('data', function(chunk) {
|
readStream.on('data', function(chunk) {
|
||||||
buffer = Buffer.concat([buffer, chunk]);
|
buffer = Buffer.concat([buffer, chunk]);
|
||||||
|
|
@ -143,8 +154,12 @@ export class Exporter {
|
||||||
});
|
});
|
||||||
readStream.on('end', function() {
|
readStream.on('end', function() {
|
||||||
// done
|
// done
|
||||||
|
fs.unlink(tmpFile, () => {
|
||||||
|
//ignored
|
||||||
|
});
|
||||||
callback(null, buffer.toString('base64'));
|
callback(null, buffer.toString('base64'));
|
||||||
});
|
});
|
||||||
|
readStream.pipe(tmpWriteable);
|
||||||
};
|
};
|
||||||
const getBase64DataSync = Meteor.wrapAsync(getBase64Data);
|
const getBase64DataSync = Meteor.wrapAsync(getBase64Data);
|
||||||
result.attachments = Attachments.find(byBoard)
|
result.attachments = Attachments.find(byBoard)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,15 @@ Lists.attachSchema(
|
||||||
*/
|
*/
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
starred: {
|
||||||
|
/**
|
||||||
|
* if a list is stared
|
||||||
|
* then we put it on the top
|
||||||
|
*/
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
archived: {
|
archived: {
|
||||||
/**
|
/**
|
||||||
* is the list archived
|
* is the list archived
|
||||||
|
|
@ -81,10 +90,14 @@ Lists.attachSchema(
|
||||||
denyUpdate: false,
|
denyUpdate: false,
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
autoValue() {
|
autoValue() {
|
||||||
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
// this is redundant with updatedAt
|
||||||
|
/*if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
return new Date();
|
return new Date();
|
||||||
} else {
|
} else {
|
||||||
this.unset();
|
this.unset();
|
||||||
|
}*/
|
||||||
|
if (!this.isSet) {
|
||||||
|
return new Date();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -252,6 +265,14 @@ Lists.helpers({
|
||||||
return this.type === 'template-list';
|
return this.type === 'template-list';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isStarred() {
|
||||||
|
return this.starred === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
absoluteUrl() {
|
||||||
|
const card = Cards.findOne({ listId: this._id });
|
||||||
|
return card && card.absoluteUrl();
|
||||||
|
},
|
||||||
remove() {
|
remove() {
|
||||||
Lists.remove({ _id: this._id });
|
Lists.remove({ _id: this._id });
|
||||||
},
|
},
|
||||||
|
|
@ -261,6 +282,9 @@ Lists.mutations({
|
||||||
rename(title) {
|
rename(title) {
|
||||||
return { $set: { title } };
|
return { $set: { title } };
|
||||||
},
|
},
|
||||||
|
star(enable = true) {
|
||||||
|
return { $set: { starred: !!enable } };
|
||||||
|
},
|
||||||
|
|
||||||
archive() {
|
archive() {
|
||||||
if (this.isTemplateList()) {
|
if (this.isTemplateList()) {
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,21 @@ Swimlanes.helpers({
|
||||||
},
|
},
|
||||||
|
|
||||||
lists() {
|
lists() {
|
||||||
|
const enabled = Meteor.user().hasSortBy();
|
||||||
|
return enabled ? this.newestLists() : this.draggableLists();
|
||||||
|
},
|
||||||
|
newestLists() {
|
||||||
|
// sorted lists from newest to the oldest, by its creation date or its cards' last modification date
|
||||||
|
return Lists.find(
|
||||||
|
{
|
||||||
|
boardId: this.boardId,
|
||||||
|
swimlaneId: { $in: [this._id, ''] },
|
||||||
|
archived: false,
|
||||||
|
},
|
||||||
|
{ sort: { modifiedAt: -1 } },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
draggableLists() {
|
||||||
return Lists.find(
|
return Lists.find(
|
||||||
{
|
{
|
||||||
boardId: this.boardId,
|
boardId: this.boardId,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,16 @@ const isSandstorm =
|
||||||
Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm;
|
Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm;
|
||||||
Users = Meteor.users;
|
Users = Meteor.users;
|
||||||
|
|
||||||
|
const allowedSortValues = [
|
||||||
|
'-modifiedAt',
|
||||||
|
'modifiedAt',
|
||||||
|
'-title',
|
||||||
|
'title',
|
||||||
|
'-sort',
|
||||||
|
'sort',
|
||||||
|
];
|
||||||
|
const defaultSortBy = allowedSortValues[0];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A User in wekan
|
* A User in wekan
|
||||||
*/
|
*/
|
||||||
|
|
@ -109,6 +119,13 @@ Users.attachSchema(
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
'profile.showDesktopDragHandles': {
|
||||||
|
/**
|
||||||
|
* does the user want to hide system messages?
|
||||||
|
*/
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
'profile.hiddenSystemMessages': {
|
'profile.hiddenSystemMessages': {
|
||||||
/**
|
/**
|
||||||
* does the user want to hide system messages?
|
* does the user want to hide system messages?
|
||||||
|
|
@ -184,6 +201,15 @@ Users.attachSchema(
|
||||||
'board-view-cal',
|
'board-view-cal',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
'profile.listSortBy': {
|
||||||
|
/**
|
||||||
|
* default sort list for user
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
defaultValue: defaultSortBy,
|
||||||
|
allowedValues: allowedSortValues,
|
||||||
|
},
|
||||||
'profile.templatesBoardId': {
|
'profile.templatesBoardId': {
|
||||||
/**
|
/**
|
||||||
* Reference to the templates board
|
* Reference to the templates board
|
||||||
|
|
@ -358,6 +384,31 @@ Users.helpers({
|
||||||
return _.contains(invitedBoards, boardId);
|
return _.contains(invitedBoards, boardId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_getListSortBy() {
|
||||||
|
const profile = this.profile || {};
|
||||||
|
const sortBy = profile.listSortBy || defaultSortBy;
|
||||||
|
const keyPattern = /^(-{0,1})(.*$)/;
|
||||||
|
const ret = [];
|
||||||
|
if (keyPattern.exec(sortBy)) {
|
||||||
|
ret[0] = RegExp.$2;
|
||||||
|
ret[1] = RegExp.$1 ? -1 : 1;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
hasSortBy() {
|
||||||
|
// if use doesn't have dragHandle, then we can let user to choose sort list by different order
|
||||||
|
return !this.hasShowDesktopDragHandles();
|
||||||
|
},
|
||||||
|
getListSortBy() {
|
||||||
|
return this._getListSortBy()[0];
|
||||||
|
},
|
||||||
|
getListSortTypes() {
|
||||||
|
return allowedSortValues;
|
||||||
|
},
|
||||||
|
getListSortByDirection() {
|
||||||
|
return this._getListSortBy()[1];
|
||||||
|
},
|
||||||
|
|
||||||
hasTag(tag) {
|
hasTag(tag) {
|
||||||
const { tags = [] } = this.profile || {};
|
const { tags = [] } = this.profile || {};
|
||||||
return _.contains(tags, tag);
|
return _.contains(tags, tag);
|
||||||
|
|
@ -368,6 +419,11 @@ Users.helpers({
|
||||||
return _.contains(notifications, activityId);
|
return _.contains(notifications, activityId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hasShowDesktopDragHandles() {
|
||||||
|
const profile = this.profile || {};
|
||||||
|
return profile.showDesktopDragHandles || false;
|
||||||
|
},
|
||||||
|
|
||||||
hasHiddenSystemMessages() {
|
hasHiddenSystemMessages() {
|
||||||
const profile = this.profile || {};
|
const profile = this.profile || {};
|
||||||
return profile.hiddenSystemMessages || false;
|
return profile.hiddenSystemMessages || false;
|
||||||
|
|
@ -473,6 +529,21 @@ Users.mutations({
|
||||||
else this.addTag(tag);
|
else this.addTag(tag);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setListSortBy(value) {
|
||||||
|
return {
|
||||||
|
$set: {
|
||||||
|
'profile.listSortBy': value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
toggleDesktopHandles(value = false) {
|
||||||
|
return {
|
||||||
|
$set: {
|
||||||
|
'profile.showDesktopDragHandles': !value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
toggleSystem(value = false) {
|
toggleSystem(value = false) {
|
||||||
return {
|
return {
|
||||||
$set: {
|
$set: {
|
||||||
|
|
@ -549,6 +620,14 @@ Meteor.methods({
|
||||||
Users.update(userId, { $set: { username } });
|
Users.update(userId, { $set: { username } });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setListSortBy(value) {
|
||||||
|
check(value, String);
|
||||||
|
Meteor.user().setListSortBy(value);
|
||||||
|
},
|
||||||
|
toggleDesktopDragHandles() {
|
||||||
|
const user = Meteor.user();
|
||||||
|
user.toggleDesktopHandles(user.hasShowDesktopDragHandles());
|
||||||
|
},
|
||||||
toggleSystemMessages() {
|
toggleSystemMessages() {
|
||||||
const user = Meteor.user();
|
const user = Meteor.user();
|
||||||
user.toggleSystem(user.hasHiddenSystemMessages());
|
user.toggleSystem(user.hasHiddenSystemMessages());
|
||||||
|
|
@ -776,6 +855,9 @@ if (Meteor.isServer) {
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
// Let mongoDB ensure username unicity
|
// Let mongoDB ensure username unicity
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
allowedSortValues.forEach(value => {
|
||||||
|
Lists._collection._ensureIndex(value);
|
||||||
|
});
|
||||||
Users._collection._ensureIndex({ modifiedAt: -1 });
|
Users._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
Users._collection._ensureIndex(
|
Users._collection._ensureIndex(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue