mirror of
https://github.com/wekan/wekan.git
synced 2025-04-22 21:17:18 -04: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());
|
||||
return helper;
|
||||
},
|
||||
handle: '.js-swimlane-header',
|
||||
handle: '.js-swimlane-header-handle',
|
||||
items: '.swimlane:not(.placeholder)',
|
||||
placeholder: 'swimlane placeholder',
|
||||
distance: 7,
|
||||
|
|
|
@ -77,6 +77,10 @@ template(name="boardHeaderBar")
|
|||
i.fa.fa-archive
|
||||
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(
|
||||
title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
|
||||
class="{{#if Filter.isActive}}emphasis{{/if}}")
|
||||
|
@ -194,6 +198,20 @@ template(name="createBoard")
|
|||
| /
|
||||
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")
|
||||
form
|
||||
label
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const DOWNCLS = 'fa-sort-down';
|
||||
const UPCLS = 'fa-sort-up';
|
||||
Template.boardMenuPopup.events({
|
||||
'click .js-rename-board': Popup.open('boardChangeTitle'),
|
||||
'click .js-custom-fields'() {
|
||||
|
@ -80,7 +82,25 @@ BlazeComponent.extendComponent({
|
|||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
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() {
|
||||
return [
|
||||
{
|
||||
|
@ -118,6 +138,16 @@ BlazeComponent.extendComponent({
|
|||
'click .js-open-filter-view'() {
|
||||
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) {
|
||||
event.stopPropagation();
|
||||
Sidebar.setView();
|
||||
|
@ -277,3 +307,73 @@ BlazeComponent.extendComponent({
|
|||
];
|
||||
},
|
||||
}).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 isLinkedBoard}}linked-board{{/if}}"
|
||||
class="minicard-{{colorClass}}")
|
||||
if isMiniScreen
|
||||
.handle
|
||||
.fa.fa-arrows
|
||||
unless isMiniScreen
|
||||
if showDesktopDragHandles
|
||||
.handle
|
||||
.fa.fa-arrows
|
||||
if cover
|
||||
.minicard-cover(style="background-image: url('{{cover.url}}');")
|
||||
if labels
|
||||
|
@ -15,8 +22,6 @@ template(name="minicard")
|
|||
if hiddenMinicardLabelText
|
||||
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
||||
.minicard-title
|
||||
.handle
|
||||
.fa.fa-arrows
|
||||
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
|
||||
.parent-prefix
|
||||
| {{ parentString ' > ' }}
|
||||
|
@ -53,6 +58,8 @@ template(name="minicard")
|
|||
if getDue
|
||||
.date
|
||||
+minicardDueDate
|
||||
if getEnd
|
||||
+minicardEndDate
|
||||
if getSpentTime
|
||||
.date
|
||||
+cardSpentTime
|
||||
|
|
|
@ -26,6 +26,9 @@ BlazeComponent.extendComponent({
|
|||
}).register('minicard');
|
||||
|
||||
Template.minicard.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
},
|
||||
hiddenMinicardLabelText() {
|
||||
return Meteor.user().hasHiddenMinicardLabelText();
|
||||
},
|
||||
|
|
|
@ -105,8 +105,7 @@
|
|||
right: 5px;
|
||||
top: 5px;
|
||||
display:none;
|
||||
// @media only screen and (max-width: 1199px) {
|
||||
@media only screen and (max-width: 800px) {
|
||||
@media only screen {
|
||||
display:block;
|
||||
}
|
||||
.fa-arrows
|
||||
|
@ -128,7 +127,7 @@
|
|||
.badges
|
||||
float: left
|
||||
margin-top: 7px
|
||||
color: darken(white, 80%)
|
||||
color: darken(white, 50%)
|
||||
|
||||
&:empty
|
||||
display: none
|
||||
|
|
|
@ -31,7 +31,13 @@ BlazeComponent.extendComponent({
|
|||
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
||||
const $cards = this.$('.js-minicards');
|
||||
|
||||
if (Utils.isMiniScreen()) {
|
||||
if (Utils.isMiniScreen) {
|
||||
$('.js-minicards').sortable({
|
||||
handle: '.handle',
|
||||
});
|
||||
}
|
||||
|
||||
if (!Utils.isMiniScreen && showDesktopDragHandles) {
|
||||
$('.js-minicards').sortable({
|
||||
handle: '.handle',
|
||||
});
|
||||
|
@ -155,6 +161,12 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
}).register('list');
|
||||
|
||||
Template.list.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
},
|
||||
});
|
||||
|
||||
Template.miniList.events({
|
||||
'click .js-select-list'() {
|
||||
const listId = this._id;
|
||||
|
|
|
@ -84,17 +84,16 @@
|
|||
padding-left: 10px
|
||||
color: #a6a6a6
|
||||
|
||||
|
||||
.list-header-menu
|
||||
position: absolute
|
||||
padding: 27px 19px
|
||||
margin-top: 1px
|
||||
top: -7px
|
||||
right: -7px
|
||||
right: 3px
|
||||
|
||||
.list-header-plus-icon
|
||||
color: #a6a6a6
|
||||
margin-right: 10px
|
||||
margin-right: 15px
|
||||
|
||||
.highlight
|
||||
color: #ce1414
|
||||
|
@ -165,7 +164,16 @@
|
|||
|
||||
@media screen and (max-width: 800px)
|
||||
.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
|
||||
flex: 0 0 60px
|
||||
|
@ -221,9 +229,17 @@
|
|||
padding: 7px
|
||||
top: 50%
|
||||
transform: translateY(-50%)
|
||||
right: 17px
|
||||
margin-right: 27px
|
||||
font-size: 20px
|
||||
|
||||
.list-header-menu-handle
|
||||
position: absolute
|
||||
padding: 7px
|
||||
top: 50%
|
||||
transform: translateY(-50%)
|
||||
right: 10px
|
||||
font-size: 24px
|
||||
|
||||
.link-board-wrapper
|
||||
display: flex
|
||||
align-items: baseline
|
||||
|
|
|
@ -9,6 +9,7 @@ template(name="listHeader")
|
|||
if currentList
|
||||
a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
|
||||
h2.list-header-name(
|
||||
title="{{ moment modifiedAt 'LLL' }}"
|
||||
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}js-open-inlined-form is-editable{{/unless}}{{/if}}")
|
||||
+viewer
|
||||
= title
|
||||
|
@ -29,16 +30,22 @@ template(name="listHeader")
|
|||
if canSeeAddCard
|
||||
a.js-add-card.fa.fa-plus.list-header-plus-icon
|
||||
a.fa.fa-navicon.js-open-list-menu
|
||||
a.list-header-menu-handle.handle.fa.fa-arrows.js-list-handle
|
||||
else
|
||||
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
|
||||
if isWatching
|
||||
i.list-header-watch-icon.fa.fa-eye
|
||||
div.list-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
if isBoardAdmin
|
||||
a.fa.js-list-star.list-header-plus-icon(class="fa-star{{#unless starred}}-o{{/unless}}")
|
||||
if canSeeAddCard
|
||||
a.js-add-card.fa.fa-plus.list-header-plus-icon
|
||||
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")
|
||||
.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) {
|
||||
event.preventDefault();
|
||||
const newTitle = this.childComponents('inlinedForm')[0]
|
||||
|
@ -61,6 +75,10 @@ BlazeComponent.extendComponent({
|
|||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-list-star'(event) {
|
||||
event.preventDefault();
|
||||
this.starred(!this.starred());
|
||||
},
|
||||
'click .js-open-list-menu': Popup.open('listAction'),
|
||||
'click .js-add-card'(event) {
|
||||
const listDom = $(event.target).parents(
|
||||
|
@ -80,6 +98,12 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
}).register('listHeader');
|
||||
|
||||
Template.listHeader.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
},
|
||||
});
|
||||
|
||||
Template.listActionPopup.helpers({
|
||||
isWipLimitEnabled() {
|
||||
return Template.currentData().getWipLimit('enabled');
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
and #each x in y constructors to fix this.
|
||||
|
||||
template(name="filterSidebar")
|
||||
ul.sidebar-list
|
||||
span {{_ 'list-filter-label'}}
|
||||
form.js-list-filter
|
||||
input(type="text")
|
||||
ul.sidebar-list
|
||||
li(class="{{#if Filter.labelIds.isSelected undefined}}active{{/if}}")
|
||||
a.name.js-toggle-label-filter
|
||||
|
|
|
@ -4,6 +4,10 @@ BlazeComponent.extendComponent({
|
|||
events() {
|
||||
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) {
|
||||
evt.preventDefault();
|
||||
Filter.labelIds.toggle(this.currentData()._id);
|
||||
|
|
|
@ -2,6 +2,10 @@ template(name="searchSidebar")
|
|||
form.js-search-term-form
|
||||
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto")
|
||||
.list-body.js-perfect-scrollbar
|
||||
.minilists.clearfix.js-minilists
|
||||
each (lists)
|
||||
a.minilist-wrapper.js-minilist(href=absoluteUrl)
|
||||
+minilist(this)
|
||||
.minicards.clearfix.js-minicards
|
||||
each (results)
|
||||
a.minicard-wrapper.js-minicard(href=absoluteUrl)
|
||||
|
|
|
@ -8,6 +8,11 @@ BlazeComponent.extendComponent({
|
|||
return currentBoard.searchCards(this.term.get());
|
||||
},
|
||||
|
||||
lists() {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
return currentBoard.searchLists(this.term.get());
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
|
|
|
@ -16,6 +16,8 @@ template(name="swimlaneFixedHeader")
|
|||
unless currentUser.isCommentOnly
|
||||
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon
|
||||
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")
|
||||
.list-composer
|
||||
|
|
|
@ -28,6 +28,12 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
}).register('swimlaneHeader');
|
||||
|
||||
Template.swimlaneHeader.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
},
|
||||
});
|
||||
|
||||
Template.swimlaneActionPopup.events({
|
||||
'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'),
|
||||
'click .js-close-swimlane'(event) {
|
||||
|
|
|
@ -12,13 +12,13 @@ template(name="swimlane")
|
|||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
else
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
each lists
|
||||
+list(this)
|
||||
if currentCardIsInThisList _id ../_id
|
||||
+cardDetails(currentCard)
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
|
||||
template(name="listsGroup")
|
||||
.swimlane.list-group.js-lists
|
||||
|
@ -26,23 +26,23 @@ template(name="listsGroup")
|
|||
if currentList
|
||||
+list(currentList)
|
||||
else
|
||||
each lists
|
||||
+miniList(this)
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
each lists
|
||||
+miniList(this)
|
||||
else
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
each lists
|
||||
if visible this
|
||||
+list(this)
|
||||
if currentCardIsInThisList _id null
|
||||
+cardDetails(currentCard)
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+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
|
||||
+inlinedForm(autoclose=false)
|
||||
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({
|
||||
tolerance: 'pointer',
|
||||
helper: 'clone',
|
||||
handle: '.js-list-header',
|
||||
items: '.js-list:not(.js-list-composer)',
|
||||
placeholder: 'list placeholder',
|
||||
distance: 7,
|
||||
|
@ -151,13 +162,13 @@ BlazeComponent.extendComponent({
|
|||
// 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
|
||||
// his mouse.
|
||||
const noDragInside = [
|
||||
'a',
|
||||
'input',
|
||||
'textarea',
|
||||
'p',
|
||||
'.js-list-header',
|
||||
];
|
||||
|
||||
const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
|
||||
Util.isMiniScreen || (!Util.isMiniScreen && showDesktopDragHandles)
|
||||
? ['.js-list-handle', '.js-swimlane-header-handle']
|
||||
: ['.js-list-header'],
|
||||
);
|
||||
|
||||
if (
|
||||
$(evt.target).closest(noDragInside.join(',')).length === 0 &&
|
||||
this.$('.swimlane').prop('clientHeight') > evt.offsetY
|
||||
|
@ -233,6 +244,9 @@ BlazeComponent.extendComponent({
|
|||
}).register('addListForm');
|
||||
|
||||
Template.swimlane.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
},
|
||||
canSeeAddList() {
|
||||
return (
|
||||
Meteor.user() &&
|
||||
|
@ -253,6 +267,11 @@ BlazeComponent.extendComponent({
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (Filter.lists._isActive()) {
|
||||
if (!list.title.match(Filter.lists.getRegexSelector())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (Filter.hideEmpty.isSelected()) {
|
||||
const swimlaneId = this.parentComponent()
|
||||
.parentComponent()
|
||||
|
|
|
@ -50,6 +50,14 @@
|
|||
margin-left: 5px
|
||||
margin-right: 10px
|
||||
|
||||
.swimlane-header-menu-handle
|
||||
position: absolute
|
||||
padding: 7px
|
||||
top: 50%
|
||||
transform: translateY(-50%)
|
||||
left: 300px
|
||||
font-size: 18px
|
||||
|
||||
.list-group
|
||||
height: 100%
|
||||
|
||||
|
|
|
@ -78,6 +78,11 @@ template(name="changeSettingsPopup")
|
|||
| {{_ 'hide-system-messages'}}
|
||||
if hiddenSystemMessages
|
||||
i.fa.fa-check
|
||||
li
|
||||
a.js-toggle-desktop-drag-handles
|
||||
| {{_ 'show-desktop-drag-handles'}}
|
||||
if showDesktopDragHandles
|
||||
i.fa.fa-check
|
||||
li
|
||||
label.bold
|
||||
| {{_ 'show-cards-minimum-count'}}
|
||||
|
|
|
@ -161,6 +161,9 @@ Template.changeLanguagePopup.events({
|
|||
});
|
||||
|
||||
Template.changeSettingsPopup.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
},
|
||||
hiddenSystemMessages() {
|
||||
return Meteor.user().hasHiddenSystemMessages();
|
||||
},
|
||||
|
@ -170,6 +173,9 @@ Template.changeSettingsPopup.helpers({
|
|||
});
|
||||
|
||||
Template.changeSettingsPopup.events({
|
||||
'click .js-toggle-desktop-drag-handles'() {
|
||||
Meteor.call('toggleDesktopDragHandles');
|
||||
},
|
||||
'click .js-toggle-system-messages'() {
|
||||
Meteor.call('toggleSystemMessages');
|
||||
},
|
||||
|
|
|
@ -439,6 +439,14 @@ class AdvancedFilter {
|
|||
const commands = this._filterToCommands();
|
||||
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.
|
||||
|
@ -455,6 +463,7 @@ Filter = {
|
|||
hideEmpty: new SetFilter(),
|
||||
customFields: new SetFilter('_id'),
|
||||
advanced: new AdvancedFilter(),
|
||||
lists: new AdvancedFilter(), // we need the ability to filter list by name as well
|
||||
|
||||
_fields: ['labelIds', 'members', 'archive', 'hideEmpty', 'customFields'],
|
||||
|
||||
|
@ -533,6 +542,7 @@ Filter = {
|
|||
const filter = this[fieldName];
|
||||
filter.reset();
|
||||
});
|
||||
this.lists.reset();
|
||||
this.advanced.reset();
|
||||
this.resetExceptions();
|
||||
},
|
||||
|
|
|
@ -300,8 +300,18 @@
|
|||
"error-username-taken": "This username is already taken",
|
||||
"error-email-taken": "Email has already been taken",
|
||||
"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-cards": "Filter Cards",
|
||||
"filter-cards": "Filter Cards or Lists",
|
||||
"list-filter-label": "Filter List by Title",
|
||||
"filter-clear": "Clear filter",
|
||||
"filter-no-label": "No label",
|
||||
"filter-no-member": "No member",
|
||||
|
@ -426,7 +436,7 @@
|
|||
"save": "Save",
|
||||
"search": "Search",
|
||||
"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?",
|
||||
"select-color": "Select Color",
|
||||
"set-wip-limit-value": "Set a limit for the maximum number of tasks in this list",
|
||||
|
|
|
@ -409,6 +409,23 @@ Boards.helpers({
|
|||
},
|
||||
|
||||
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 } });
|
||||
},
|
||||
|
||||
|
|
|
@ -1695,6 +1695,25 @@ if (Meteor.isServer) {
|
|||
const oldvalue = doc[action] || '';
|
||||
const activityType = `a-${action}`;
|
||||
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 activity = {
|
||||
userId,
|
||||
|
@ -1852,15 +1871,8 @@ if (Meteor.isServer) {
|
|||
const check = Users.findOne({
|
||||
_id: req.body.authorId,
|
||||
});
|
||||
const members = req.body.members || [req.body.authorId];
|
||||
if (typeof check !== 'undefined') {
|
||||
let members = req.body.members || [];
|
||||
if (_.isString(members)) {
|
||||
if (members === '') {
|
||||
members = [];
|
||||
} else {
|
||||
members = [members];
|
||||
}
|
||||
}
|
||||
const id = Cards.direct.insert({
|
||||
title: req.body.title,
|
||||
boardId: paramBoardId,
|
||||
|
|
|
@ -50,12 +50,18 @@ if (Meteor.isServer) {
|
|||
});
|
||||
}
|
||||
|
||||
// exporter maybe is broken since Gridfs introduced, add fs and path
|
||||
|
||||
export class Exporter {
|
||||
constructor(boardId) {
|
||||
this._boardId = boardId;
|
||||
}
|
||||
|
||||
build() {
|
||||
const fs = Npm.require('fs');
|
||||
const os = Npm.require('os');
|
||||
const path = Npm.require('path');
|
||||
|
||||
const byBoard = { boardId: this._boardId };
|
||||
const byBoardNoLinked = {
|
||||
boardId: this._boardId,
|
||||
|
@ -134,6 +140,11 @@ export class Exporter {
|
|||
const getBase64Data = function(doc, callback) {
|
||||
let buffer = new Buffer(0);
|
||||
// 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();
|
||||
readStream.on('data', function(chunk) {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
|
@ -143,8 +154,12 @@ export class Exporter {
|
|||
});
|
||||
readStream.on('end', function() {
|
||||
// done
|
||||
fs.unlink(tmpFile, () => {
|
||||
//ignored
|
||||
});
|
||||
callback(null, buffer.toString('base64'));
|
||||
});
|
||||
readStream.pipe(tmpWriteable);
|
||||
};
|
||||
const getBase64DataSync = Meteor.wrapAsync(getBase64Data);
|
||||
result.attachments = Attachments.find(byBoard)
|
||||
|
|
|
@ -11,6 +11,15 @@ Lists.attachSchema(
|
|||
*/
|
||||
type: String,
|
||||
},
|
||||
starred: {
|
||||
/**
|
||||
* if a list is stared
|
||||
* then we put it on the top
|
||||
*/
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
},
|
||||
archived: {
|
||||
/**
|
||||
* is the list archived
|
||||
|
@ -81,10 +90,14 @@ Lists.attachSchema(
|
|||
denyUpdate: false,
|
||||
// eslint-disable-next-line consistent-return
|
||||
autoValue() {
|
||||
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||
// this is redundant with updatedAt
|
||||
/*if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||
return new Date();
|
||||
} else {
|
||||
this.unset();
|
||||
}*/
|
||||
if (!this.isSet) {
|
||||
return new Date();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -252,6 +265,14 @@ Lists.helpers({
|
|||
return this.type === 'template-list';
|
||||
},
|
||||
|
||||
isStarred() {
|
||||
return this.starred === true;
|
||||
},
|
||||
|
||||
absoluteUrl() {
|
||||
const card = Cards.findOne({ listId: this._id });
|
||||
return card && card.absoluteUrl();
|
||||
},
|
||||
remove() {
|
||||
Lists.remove({ _id: this._id });
|
||||
},
|
||||
|
@ -261,6 +282,9 @@ Lists.mutations({
|
|||
rename(title) {
|
||||
return { $set: { title } };
|
||||
},
|
||||
star(enable = true) {
|
||||
return { $set: { starred: !!enable } };
|
||||
},
|
||||
|
||||
archive() {
|
||||
if (this.isTemplateList()) {
|
||||
|
|
|
@ -174,6 +174,21 @@ Swimlanes.helpers({
|
|||
},
|
||||
|
||||
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(
|
||||
{
|
||||
boardId: this.boardId,
|
||||
|
|
|
@ -4,6 +4,16 @@ const isSandstorm =
|
|||
Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm;
|
||||
Users = Meteor.users;
|
||||
|
||||
const allowedSortValues = [
|
||||
'-modifiedAt',
|
||||
'modifiedAt',
|
||||
'-title',
|
||||
'title',
|
||||
'-sort',
|
||||
'sort',
|
||||
];
|
||||
const defaultSortBy = allowedSortValues[0];
|
||||
|
||||
/**
|
||||
* A User in wekan
|
||||
*/
|
||||
|
@ -109,6 +119,13 @@ Users.attachSchema(
|
|||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
'profile.showDesktopDragHandles': {
|
||||
/**
|
||||
* does the user want to hide system messages?
|
||||
*/
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
'profile.hiddenSystemMessages': {
|
||||
/**
|
||||
* does the user want to hide system messages?
|
||||
|
@ -184,6 +201,15 @@ Users.attachSchema(
|
|||
'board-view-cal',
|
||||
],
|
||||
},
|
||||
'profile.listSortBy': {
|
||||
/**
|
||||
* default sort list for user
|
||||
*/
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: defaultSortBy,
|
||||
allowedValues: allowedSortValues,
|
||||
},
|
||||
'profile.templatesBoardId': {
|
||||
/**
|
||||
* Reference to the templates board
|
||||
|
@ -358,6 +384,31 @@ Users.helpers({
|
|||
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) {
|
||||
const { tags = [] } = this.profile || {};
|
||||
return _.contains(tags, tag);
|
||||
|
@ -368,6 +419,11 @@ Users.helpers({
|
|||
return _.contains(notifications, activityId);
|
||||
},
|
||||
|
||||
hasShowDesktopDragHandles() {
|
||||
const profile = this.profile || {};
|
||||
return profile.showDesktopDragHandles || false;
|
||||
},
|
||||
|
||||
hasHiddenSystemMessages() {
|
||||
const profile = this.profile || {};
|
||||
return profile.hiddenSystemMessages || false;
|
||||
|
@ -473,6 +529,21 @@ Users.mutations({
|
|||
else this.addTag(tag);
|
||||
},
|
||||
|
||||
setListSortBy(value) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.listSortBy': value,
|
||||
},
|
||||
};
|
||||
},
|
||||
toggleDesktopHandles(value = false) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.showDesktopDragHandles': !value,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
toggleSystem(value = false) {
|
||||
return {
|
||||
$set: {
|
||||
|
@ -549,6 +620,14 @@ Meteor.methods({
|
|||
Users.update(userId, { $set: { username } });
|
||||
}
|
||||
},
|
||||
setListSortBy(value) {
|
||||
check(value, String);
|
||||
Meteor.user().setListSortBy(value);
|
||||
},
|
||||
toggleDesktopDragHandles() {
|
||||
const user = Meteor.user();
|
||||
user.toggleDesktopHandles(user.hasShowDesktopDragHandles());
|
||||
},
|
||||
toggleSystemMessages() {
|
||||
const user = Meteor.user();
|
||||
user.toggleSystem(user.hasHiddenSystemMessages());
|
||||
|
@ -776,6 +855,9 @@ if (Meteor.isServer) {
|
|||
if (Meteor.isServer) {
|
||||
// Let mongoDB ensure username unicity
|
||||
Meteor.startup(() => {
|
||||
allowedSortValues.forEach(value => {
|
||||
Lists._collection._ensureIndex(value);
|
||||
});
|
||||
Users._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Users._collection._ensureIndex(
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue