summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
author蔡仲明 (Romulus Urakagi Tsai) <urakagi@gmail.com>2019-11-21 11:25:56 +0800
committerGitHub <noreply@github.com>2019-11-21 11:25:56 +0800
commit3e0bedd8c7a6dec97352212adb1cbde1ade44190 (patch)
tree651ff30d25ddb0416444370368d699e597c142d7 /client
parent9bbeb73db1cd0ce1caaaca8dfb14ea92131bbf9d (diff)
parent4f5de87cc4c2281bd576548693de7c94e6a988c6 (diff)
downloadwekan-3e0bedd8c7a6dec97352212adb1cbde1ade44190.tar.gz
wekan-3e0bedd8c7a6dec97352212adb1cbde1ade44190.tar.bz2
wekan-3e0bedd8c7a6dec97352212adb1cbde1ade44190.zip
Merge pull request #1 from wekan/master
Update master
Diffstat (limited to 'client')
-rw-r--r--client/components/activities/activities.js2
-rw-r--r--client/components/boards/boardBody.js141
-rw-r--r--client/components/boards/boardBody.styl78
-rw-r--r--client/components/boards/boardHeader.jade88
-rw-r--r--client/components/boards/boardHeader.js166
-rw-r--r--client/components/cards/attachments.js2
-rw-r--r--client/components/cards/cardDate.js16
-rw-r--r--client/components/cards/cardDetails.jade69
-rw-r--r--client/components/cards/cardDetails.js174
-rw-r--r--client/components/cards/cardDetails.styl90
-rw-r--r--client/components/cards/minicard.jade17
-rw-r--r--client/components/cards/minicard.js35
-rw-r--r--client/components/cards/minicard.styl16
-rw-r--r--client/components/lists/list.js66
-rw-r--r--client/components/lists/list.styl26
-rw-r--r--client/components/lists/listBody.js46
-rw-r--r--client/components/lists/listHeader.jade7
-rw-r--r--client/components/lists/listHeader.js63
-rwxr-xr-xclient/components/main/editor.js50
-rw-r--r--client/components/main/layouts.styl4
-rw-r--r--client/components/main/popup.styl6
-rw-r--r--client/components/settings/peopleBody.js4
-rw-r--r--client/components/settings/settingBody.jade8
-rw-r--r--client/components/settings/settingBody.js3
-rw-r--r--client/components/sidebar/sidebar.jade36
-rw-r--r--client/components/sidebar/sidebar.js112
-rw-r--r--client/components/sidebar/sidebarFilters.jade4
-rw-r--r--client/components/sidebar/sidebarFilters.js4
-rw-r--r--client/components/sidebar/sidebarSearches.jade4
-rw-r--r--client/components/sidebar/sidebarSearches.js5
-rw-r--r--client/components/swimlanes/swimlaneHeader.jade5
-rw-r--r--client/components/swimlanes/swimlaneHeader.js17
-rw-r--r--client/components/swimlanes/swimlanes.jade53
-rw-r--r--client/components/swimlanes/swimlanes.js138
-rw-r--r--client/components/swimlanes/swimlanes.styl78
-rw-r--r--client/components/users/userHeader.jade5
-rw-r--r--client/components/users/userHeader.js88
-rw-r--r--client/lib/datepicker.js13
-rw-r--r--client/lib/filter.js14
-rw-r--r--client/lib/textComplete.js1
-rw-r--r--client/lib/utils.js72
41 files changed, 1535 insertions, 291 deletions
diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js
index 05149826..b082273a 100644
--- a/client/components/activities/activities.js
+++ b/client/components/activities/activities.js
@@ -85,7 +85,7 @@ BlazeComponent.extendComponent({
const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(
lastLabelId,
);
- if (lastLabel.name === undefined || lastLabel.name === '') {
+ if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
return lastLabel.color;
} else {
return lastLabel.name;
diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js
index 6cff5ab1..41b6f4ef 100644
--- a/client/components/boards/boardBody.js
+++ b/client/components/boards/boardBody.js
@@ -89,7 +89,6 @@ BlazeComponent.extendComponent({
helper.append(list.clone());
return helper;
},
- handle: '.js-swimlane-header',
items: '.swimlane:not(.placeholder)',
placeholder: 'swimlane placeholder',
distance: 7,
@@ -193,11 +192,42 @@ BlazeComponent.extendComponent({
// ugly touch event hotfix
enableClickOnTouch('.js-swimlane:not(.placeholder)');
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+
+ this.autorun(() => {
+ let showDesktopDragHandles = false;
+ currentUser = Meteor.user();
+ if (currentUser) {
+ showDesktopDragHandles = (currentUser.profile || {})
+ .showDesktopDragHandles;
+ } else if (cookies.has('showDesktopDragHandles')) {
+ showDesktopDragHandles = true;
+ } else {
+ showDesktopDragHandles = false;
+ }
+ if (
+ Utils.isMiniScreen()
+ || (!Utils.isMiniScreen() && showDesktopDragHandles)
+ ) {
+ $swimlanesDom.sortable({
+ handle: '.js-swimlane-header-handle',
+ });
+ } else {
+ $swimlanesDom.sortable({
+ handle: '.swimlane-header',
+ });
+ }
+
+ // Disable drag-dropping if the current user is not a board member or is comment only
+ $swimlanesDom.sortable('option', 'disabled', !userIsMember());
+ });
+
function userIsMember() {
return (
- Meteor.user() &&
- Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ Meteor.user()
+ && Meteor.user().isBoardMember()
+ && !Meteor.user().isCommentOnly()
);
}
@@ -210,21 +240,36 @@ BlazeComponent.extendComponent({
},
isViewSwimlanes() {
- const currentUser = Meteor.user();
- if (!currentUser) return false;
- return (currentUser.profile || {}).boardView === 'board-view-swimlanes';
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).boardView === 'board-view-swimlanes';
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ return cookies.get('boardView') === 'board-view-swimlanes';
+ }
},
isViewLists() {
- const currentUser = Meteor.user();
- if (!currentUser) return true;
- return (currentUser.profile || {}).boardView === 'board-view-lists';
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).boardView === 'board-view-lists';
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ return cookies.get('boardView') === 'board-view-lists';
+ }
},
isViewCalendar() {
- const currentUser = Meteor.user();
- if (!currentUser) return false;
- return (currentUser.profile || {}).boardView === 'board-view-cal';
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).boardView === 'board-view-cal';
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ return cookies.get('boardView') === 'board-view-cal';
+ }
},
openNewListForm() {
@@ -261,16 +306,16 @@ BlazeComponent.extendComponent({
scrollLeft(position = 0) {
const swimlanes = this.$('.js-swimlanes');
- swimlanes &&
- swimlanes.animate({
+ swimlanes
+ && swimlanes.animate({
scrollLeft: position,
});
},
scrollTop(position = 0) {
const swimlanes = this.$('.js-swimlanes');
- swimlanes &&
- swimlanes.animate({
+ swimlanes
+ && swimlanes.animate({
scrollTop: position,
});
},
@@ -309,25 +354,46 @@ BlazeComponent.extendComponent({
events(start, end, timezone, callback) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const events = [];
+ const pushEvent = function(card, title, start, end, extraCls) {
+ start = start || card.startAt;
+ end = end || card.endAt;
+ title = title || card.title;
+ const className =
+ (extraCls ? `${extraCls} ` : '')
+ + (card.color ? `calendar-event-${card.color}` : '');
+ events.push({
+ id: card._id,
+ title,
+ start,
+ end: end || card.endAt,
+ allDay:
+ Math.abs(end.getTime() - start.getTime()) / 1000 === 24 * 3600,
+ url: FlowRouter.url('card', {
+ boardId: currentBoard._id,
+ slug: currentBoard.slug,
+ cardId: card._id,
+ }),
+ className,
+ });
+ };
currentBoard
.cardsInInterval(start.toDate(), end.toDate())
.forEach(function(card) {
- events.push({
- id: card._id,
- title: card.title,
- start: card.startAt,
- end: card.endAt,
- allDay:
- Math.abs(card.endAt.getTime() - card.startAt.getTime()) /
- 1000 ===
- 24 * 3600,
- url: FlowRouter.url('card', {
- boardId: currentBoard._id,
- slug: currentBoard.slug,
- cardId: card._id,
- }),
- });
+ pushEvent(card);
+ });
+ currentBoard
+ .cardsDueInBetween(start.toDate(), end.toDate())
+ .forEach(function(card) {
+ pushEvent(
+ card,
+ `${card.title} ${TAPi18n.__('card-due')}`,
+ card.dueAt,
+ new Date(card.dueAt.getTime() + 36e5),
+ );
});
+ events.sort(function(first, second) {
+ return first.id > second.id ? 1 : -1;
+ });
callback(events);
},
eventResize(event, delta, revertFunc) {
@@ -360,8 +426,13 @@ BlazeComponent.extendComponent({
};
},
isViewCalendar() {
- const currentUser = Meteor.user();
- if (!currentUser) return false;
- return (currentUser.profile || {}).boardView === 'board-view-cal';
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).boardView === 'board-view-cal';
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ return cookies.get('boardView') === 'board-view-cal';
+ }
},
}).register('calendarView');
diff --git a/client/components/boards/boardBody.styl b/client/components/boards/boardBody.styl
index dfaaa050..32207d82 100644
--- a/client/components/boards/boardBody.styl
+++ b/client/components/boards/boardBody.styl
@@ -53,3 +53,81 @@ position()
padding: 0 0px 0px 0
overflow-x: hidden
overflow-y: auto
+
+calendar-event-color(background, borderColor, color...)
+ background: background !important
+ border-color: borderColor
+ if color
+ color: color !important //overwrite text for better visibility
+
+.calendar-event-green
+ calendar-event-color(#3cb500, #2a8000, #ffffff) //White text for better visibility
+
+.calendar-event-yellow
+ calendar-event-color(#fad900, #c7ac00, #000) //Black text for better visibility
+
+.calendar-event-orange
+ calendar-event-color(#ff9f19, #cc7c14, #000) //Black text for better visibility
+
+.calendar-event-red
+ calendar-event-color(#eb4646, #b83737, #ffffff) //White text for better visibility
+
+.calendar-event-purple
+ calendar-event-color(#a632db, #7d26a6, #ffffff) //White text for better visibility
+
+.calendar-event-blue
+ calendar-event-color(#0079bf, #005a8a, #ffffff) //White text for better visibility
+
+.calendar-event-pink
+ calendar-event-color(#ff78cb, #cc62a3, #000) //Black text for better visibility
+
+.calendar-event-sky
+ calendar-event-color(#00c2e0, #0094ab, #ffffff) //White text for better visibility
+
+.calendar-event-black
+ calendar-event-color(#4d4d4d, #1a1a1a, #ffffff) //White text for better visibility
+
+.calendar-event-lime
+ calendar-event-color(#51e898, #3eb375, #000) //Black text for better visibility
+
+.calendar-event-silver
+ calendar-event-color(#c0c0c0, #8c8c8c, #000) //Black text for better visibility
+
+.calendar-event-peachpuff
+ calendar-event-color(#ffdab9, #ccaf95, #000) //Black text for better visibility
+
+.calendar-event-crimson
+ calendar-event-color(#dc143c, #a8112f, #ffffff) //White text for better visibility
+
+.calendar-event-plum
+ calendar-event-color(#dda0dd, #a87ba8, #000) //Black text for better visibility
+
+.calendar-event-darkgreen
+ calendar-event-color(#006400, #003000, #ffffff) //White text for better visibility
+
+.calendar-event-slateblue
+ calendar-event-color(#6a5acd, #4f4399, #ffffff) //White text for better visibility
+
+.calendar-event-magenta
+ calendar-event-color(#ff00ff, #cc00cc, #ffffff) //White text for better visibility
+
+.calendar-event-gold
+ calendar-event-color(#ffd700, #ccaa00, #000) //Black text for better visibility
+
+.calendar-event-navy
+ calendar-event-color(#000080, #000033, #ffffff) //White text for better visibility
+
+.calendar-event-gray
+ calendar-event-color(#808080, #333333, #ffffff) //White text for better visibility
+
+.calendar-event-saddlebrown
+ calendar-event-color(#8b4513, #572b0c, #ffffff) //White text for better visibility
+
+.calendar-event-paleturquoise
+ calendar-event-color(#afeeee, #8ababa, #000) //Black text for better visibility
+
+.calendar-event-mistyrose
+ calendar-event-color(#ffe4e1, #ccb8b6, #000) //Black text for better visibility
+
+.calendar-event-indigo
+ calendar-event-color(#4b0082, #2b004d, #ffffff) //White text for better visibility
diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade
index fe533f95..39221778 100644
--- a/client/components/boards/boardHeader.jade
+++ b/client/components/boards/boardHeader.jade
@@ -77,6 +77,11 @@ 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}}")
@@ -85,15 +90,6 @@ template(name="boardHeaderBar")
if Filter.isActive
a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}")
i.fa.fa-times-thin
-
- if currentUser.isAdmin
- a.board-header-btn.js-open-rules-view(title="{{_ 'rules'}}")
- i.fa.fa-magic
- span {{_ 'rules'}}
- else if currentUser.isBoardAdmin
- a.board-header-btn.js-open-rules-view(title="{{_ 'rules'}}")
- i.fa.fa-magic
- span {{_ 'rules'}}
a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
i.fa.fa-search
@@ -102,8 +98,19 @@ template(name="boardHeaderBar")
unless currentBoard.isTemplatesBoard
a.board-header-btn.js-toggle-board-view(
title="{{_ 'board-view'}}")
- i.fa.fa-th-large
- span {{#if currentUser.profile.boardView}}{{_ currentUser.profile.boardView}}{{else}}{{_ 'board-view-lists'}}{{/if}}
+ i.fa.fa-caret-down
+ if $eq boardView 'board-view-lists'
+ i.fa.fa-trello
+ if $eq boardView 'board-view-swimlanes'
+ i.fa.fa-th-large
+ // unless collapseSwimlane
+ // i.fa.fa-th-large
+ // if collapseSwimlane
+ // i.fa.fa-play
+ if $eq boardView 'board-view-cal'
+ i.fa.fa-calendar
+ span {{#if boardView}}{{_ boardView}}{{else}}{{_ 'board-view-lists'}}{{/if}}
+ //span {{#if collapseSwimlane}}{{_ 'board-view-collapse'}}{{else}}{{#if boardView}}{{_ boardView}}{{else}}{{_ 'board-view-lists'}}{{/if}}{{/if}}
if canModifyBoard
a.board-header-btn.js-multiselection-activate(
@@ -168,6 +175,51 @@ template(name="boardChangeWatchPopup")
i.fa.fa-check
span.sub-name {{_ 'muted-info'}}
+template(name="boardChangeViewPopup")
+ ul.pop-over-list
+ li
+ with "board-view-lists"
+ a.js-open-lists-view
+ i.fa.fa-trello.colorful
+ | {{_ 'board-view-lists'}}
+ if $eq Utils.boardView "board-view-lists"
+ i.fa.fa-check
+ li
+ with "board-view-swimlanes"
+ a.js-open-swimlanes-view
+ i.fa.fa-th-large.colorful
+ | {{_ 'board-view-swimlanes'}}
+ if $eq Utils.boardView "board-view-swimlanes"
+ i.fa.fa-check
+ //li
+ // with "board-view-collapse"
+ // a.js-open-collapse-view
+ // i.fa.fa-play.colorful
+ // | {{_ 'board-view-collapse'}}
+ // if $eq Utils.boardView "board-view-collapse"
+ // i.fa.fa-check
+ li
+ with "board-view-cal"
+ a.js-open-cal-view
+ i.fa.fa-calendar.colorful
+ | {{_ 'board-view-cal'}}
+ if $eq Utils.boardView "board-view-cal"
+ i.fa.fa-check
+ if currentUser.isAdmin
+ hr
+ li
+ with "board-view-rules"
+ a.js-open-rules-view(title="{{_ 'rules'}}")
+ i.fa.fa-magic
+ | {{_ 'rules'}}
+ else if currentUser.isBoardAdmin
+ hr
+ li
+ with "board-view-rules"
+ a.js-open-rules-view(title="{{_ 'rules'}}")
+ i.fa.fa-magic
+ | {{_ 'rules'}}
+
template(name="createBoard")
form
label
@@ -194,6 +246,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
diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js
index cb84c233..ffbb9b72 100644
--- a/client/components/boards/boardHeader.js
+++ b/client/components/boards/boardHeader.js
@@ -1,3 +1,7 @@
+/*
+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 +84,27 @@ 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 [
{
@@ -94,30 +118,25 @@ BlazeComponent.extendComponent({
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
},
- 'click .js-toggle-board-view'() {
- const currentUser = Meteor.user();
- if (
- (currentUser.profile || {}).boardView === 'board-view-swimlanes'
- ) {
- currentUser.setBoardView('board-view-cal');
- } else if (
- (currentUser.profile || {}).boardView === 'board-view-lists'
- ) {
- currentUser.setBoardView('board-view-swimlanes');
- } else if (
- (currentUser.profile || {}).boardView === 'board-view-cal'
- ) {
- currentUser.setBoardView('board-view-lists');
- } else {
- currentUser.setBoardView('board-view-swimlanes');
- }
- },
+ 'click .js-toggle-board-view': Popup.open('boardChangeView'),
'click .js-toggle-sidebar'() {
Sidebar.toggle();
},
'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();
@@ -126,9 +145,6 @@ BlazeComponent.extendComponent({
'click .js-open-search-view'() {
Sidebar.setView('search');
},
- 'click .js-open-rules-view'() {
- Modal.openWide('rulesMain');
- },
'click .js-multiselection-activate'() {
const currentCard = Session.get('currentCard');
MultiSelection.activate();
@@ -156,6 +172,40 @@ Template.boardHeaderBar.helpers({
!Meteor.user().isCommentOnly()
);
},
+ boardView() {
+ return Utils.boardView();
+ },
+ //collapseSwimlane() {
+ // import { Cookies } from 'meteor/ostrio:cookies';
+ // const cookies = new Cookies();
+ // if (cookies.has('collapseSwimlane')) {
+ // return true;
+ // } else {
+ // return false;
+ // }
+ //},
+});
+
+Template.boardChangeViewPopup.events({
+ 'click .js-open-lists-view'() {
+ Utils.setBoardView('board-view-lists');
+ Popup.close();
+ },
+ 'click .js-open-swimlanes-view'() {
+ Utils.setBoardView('board-view-swimlanes');
+ Popup.close();
+ },
+ //'click .js-open-collapse-view'() {
+ // Utils.setBoardView('board-view-collapse');
+ //Popup.close();
+ 'click .js-open-cal-view'() {
+ Utils.setBoardView('board-view-cal');
+ Popup.close();
+ },
+ 'click .js-open-rules-view'() {
+ Modal.openWide('rulesMain');
+ Popup.close();
+ },
});
const CreateBoard = BlazeComponent.extendComponent({
@@ -277,3 +327,75 @@ 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');
+*/
diff --git a/client/components/cards/attachments.js b/client/components/cards/attachments.js
index 843f1eb7..e4439155 100644
--- a/client/components/cards/attachments.js
+++ b/client/components/cards/attachments.js
@@ -131,6 +131,8 @@ Template.previewClipboardImagePopup.onRendered(() => {
direct(results);
},
});
+ } else {
+ direct(results);
}
}
};
diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js
index 91205f1c..cb54b033 100644
--- a/client/components/cards/cardDate.js
+++ b/client/components/cards/cardDate.js
@@ -105,7 +105,7 @@ Template.dateBadge.helpers({
// editCardReceivedDatePopup
(class extends DatePicker {
onCreated() {
- super.onCreated();
+ super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
this.data().getReceived() &&
this.date.set(moment(this.data().getReceived()));
}
@@ -122,7 +122,7 @@ Template.dateBadge.helpers({
// editCardStartDatePopup
(class extends DatePicker {
onCreated() {
- super.onCreated();
+ super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
this.data().getStart() && this.date.set(moment(this.data().getStart()));
}
@@ -148,7 +148,7 @@ Template.dateBadge.helpers({
// editCardDueDatePopup
(class extends DatePicker {
onCreated() {
- super.onCreated();
+ super.onCreated('1970-01-01 17:00:00');
this.data().getDue() && this.date.set(moment(this.data().getDue()));
}
@@ -171,7 +171,7 @@ Template.dateBadge.helpers({
// editCardEndDatePopup
(class extends DatePicker {
onCreated() {
- super.onCreated();
+ super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
this.data().getEnd() && this.date.set(moment(this.data().getEnd()));
}
@@ -237,7 +237,7 @@ class CardReceivedDate extends CardDate {
const theDate = this.date.get();
// if dueAt, endAt and startAt exist & are > receivedAt, receivedAt doesn't need to be flagged
if (
- (startAt && theDate.isAfter(dueAt)) ||
+ (startAt && theDate.isAfter(startAt)) ||
(endAt && theDate.isAfter(endAt)) ||
(dueAt && theDate.isAfter(dueAt))
)
@@ -344,9 +344,9 @@ class CardEndDate extends CardDate {
let classes = 'end-date' + ' ';
const dueAt = this.data().getDue();
const theDate = this.date.get();
- if (theDate.diff(dueAt, 'days') >= 2) classes += 'long-overdue';
- else if (theDate.diff(dueAt, 'days') >= 0) classes += 'due';
- else if (theDate.diff(dueAt, 'days') >= -2) classes += 'almost-due';
+ if (!dueAt) classes += '';
+ else if (theDate.isBefore(dueAt)) classes += 'current';
+ else if (theDate.isAfter(dueAt)) classes += 'due';
return classes;
}
diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade
index 13b6bd13..2b4f44b9 100644
--- a/client/components/cards/cardDetails.jade
+++ b/client/components/cards/cardDetails.jade
@@ -4,9 +4,14 @@ template(name="cardDetails")
+inlinedForm(classNames="js-card-details-title")
+editCardTitleForm
else
- a.fa.fa-times-thin.close-card-details.js-close-card-details
- if currentUser.isBoardMember
- a.fa.fa-navicon.card-details-menu.js-open-card-details-menu
+ unless isMiniScreen
+ a.fa.fa-times-thin.close-card-details.js-close-card-details
+ if currentUser.isBoardMember
+ a.fa.fa-navicon.card-details-menu.js-open-card-details-menu
+ if isMiniScreen
+ a.fa.fa-times-thin.close-card-details-mobile-web.js-close-card-details
+ if currentUser.isBoardMember
+ a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu
h2.card-details-title.js-card-title(
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
+viewer
@@ -73,6 +78,16 @@ template(name="cardDetails")
a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
i.fa.fa-plus
+ .card-details-item.card-details-item-assignees
+ h3.card-details-item-title {{_ 'assignee'}}
+ each getAssignees
+ +userAvatarAssignee(userId=this cardId=../_id)
+ | {{! XXX Hack to hide syntaxic coloration /// }}
+ if canModifyCard
+ unless assigneeSelected
+ a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
+ i.fa.fa-plus
+
.card-details-item.card-details-item-labels
h3.card-details-item-title {{_ 'labels'}}
a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
@@ -296,6 +311,54 @@ template(name="cardMembersPopup")
if isCardMember
i.fa.fa-check
+template(name="cardAssigneesPopup")
+ ul.pop-over-list.js-card-assignee-list
+ each board.activeMembers
+ li.item(class="{{#if isCardAssignee}}active{{/if}}")
+ a.name.js-select-assignee(href="#")
+ +userAvatar(userId=user._id)
+ span.full-name
+ = user.profile.fullname
+ | (<span class="username">{{ user.username }}</span>)
+ if isCardAssignee
+ i.fa.fa-check
+
+template(name="userAvatarAssignee")
+ a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
+ if userData.profile.avatarUrl
+ img.avatar.avatar-image(src="{{userData.profile.avatarUrl}}")
+ else
+ +userAvatarAssigneeInitials(userId=userData._id)
+
+ if showStatus
+ span.assignee-presence-status(class=presenceStatusClassName)
+ span.member-type(class=memberType)
+
+ unless isSandstorm
+ if showEdit
+ if $eq currentUser._id userData._id
+ a.edit-avatar.js-change-avatar
+ i.fa.fa-pencil
+
+template(name="cardAssigneePopup")
+ .board-assignee-menu
+ .mini-profile-info
+ +userAvatar(userId=user._id showEdit=true)
+ .info
+ h3= user.profile.fullname
+ p.quiet @{{ user.username }}
+ ul.pop-over-list
+ if currentUser.isNotCommentOnly
+ li: a.js-remove-assignee {{_ 'remove-member-from-card'}}
+
+ if $eq currentUser._id user._id
+ with currentUser
+ li: a.js-edit-profile {{_ 'edit-profile'}}
+
+template(name="userAvatarAssigneeInitials")
+ svg.avatar.avatar-assignee-initials(viewBox="0 0 {{viewPortWidth}} 15")
+ text(x="50%" y="13" text-anchor="middle")= initials
+
template(name="cardMorePopup")
p.quiet
span.clearfix
diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js
index cd8813f5..7bb54223 100644
--- a/client/components/cards/cardDetails.js
+++ b/client/components/cards/cardDetails.js
@@ -121,11 +121,6 @@ BlazeComponent.extendComponent({
// Send Webhook but not create Activities records ---
const card = this.currentData();
const userId = Meteor.userId();
- //console.log(`userId: ${userId}`);
- //console.log(`cardId: ${card._id}`);
- //console.log(`boardId: ${card.boardId}`);
- //console.log(`listId: ${card.listId}`);
- //console.log(`swimlaneId: ${card.swimlaneId}`);
const params = {
userId,
cardId: card._id,
@@ -134,16 +129,25 @@ BlazeComponent.extendComponent({
user: Meteor.user().username,
url: '',
};
- //console.log('looking for integrations...');
+
const integrations = Integrations.find({
- boardId: card.boardId,
- type: 'outgoing-webhooks',
+ boardId: { $in: [card.boardId, Integrations.Const.GLOBAL_WEBHOOK_ID] },
enabled: true,
activities: { $in: ['CardDetailsRendered', 'all'] },
}).fetch();
- //console.log(`Investigation length: ${integrations.length}`);
+
if (integrations.length > 0) {
- Meteor.call('outgoingWebhooks', integrations, 'CardSelected', params);
+ integrations.forEach(integration => {
+ Meteor.call(
+ 'outgoingWebhooks',
+ integration,
+ 'CardSelected',
+ params,
+ () => {
+ return;
+ },
+ );
+ });
}
//-------------
}
@@ -309,6 +313,8 @@ BlazeComponent.extendComponent({
},
'click .js-member': Popup.open('cardMember'),
'click .js-add-members': Popup.open('cardMembers'),
+ 'click .js-assignee': Popup.open('cardAssignee'),
+ 'click .js-add-assignees': Popup.open('cardAssignees'),
'click .js-add-labels': Popup.open('cardLabels'),
'click .js-received-date': Popup.open('editCardReceivedDate'),
'click .js-start-date': Popup.open('editCardStartDate'),
@@ -321,6 +327,19 @@ BlazeComponent.extendComponent({
parentComponent.showOverlay.set(true);
parentComponent.mouseHasEnterCardDetails = true;
},
+ 'mousedown .js-card-details'() {
+ Session.set('cardDetailsIsDragging', false);
+ Session.set('cardDetailsIsMouseDown', true);
+ },
+ 'mousemove .js-card-details'() {
+ if (Session.get('cardDetailsIsMouseDown')) {
+ Session.set('cardDetailsIsDragging', true);
+ }
+ },
+ 'mouseup .js-card-details'() {
+ Session.set('cardDetailsIsDragging', false);
+ Session.set('cardDetailsIsMouseDown', false);
+ },
'click #toggleButton'() {
Meteor.call('toggleSystemMessages');
},
@@ -329,6 +348,58 @@ BlazeComponent.extendComponent({
},
}).register('cardDetails');
+Template.cardDetails.helpers({
+ userData() {
+ // We need to handle a special case for the search results provided by the
+ // `matteodem:easy-search` package. Since these results gets published in a
+ // separate collection, and not in the standard Meteor.Users collection as
+ // expected, we use a component parameter ("property") to distinguish the
+ // two cases.
+ const userCollection = this.esSearch ? ESSearchResults : Users;
+ return userCollection.findOne(this.userId, {
+ fields: {
+ profile: 1,
+ username: 1,
+ },
+ });
+ },
+
+ assigneeSelected() {
+ if (this.getAssignees().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ memberType() {
+ const user = Users.findOne(this.userId);
+ return user && user.isBoardAdmin() ? 'admin' : 'normal';
+ },
+
+ presenceStatusClassName() {
+ const user = Users.findOne(this.userId);
+ const userPresence = presences.findOne({ userId: this.userId });
+ if (user && user.isInvitedTo(Session.get('currentBoard'))) return 'pending';
+ else if (!userPresence) return 'disconnected';
+ else if (Session.equals('currentBoard', userPresence.state.currentBoardId))
+ return 'active';
+ else return 'idle';
+ },
+});
+
+Template.userAvatarAssigneeInitials.helpers({
+ initials() {
+ const user = Users.findOne(this.userId);
+ return user && user.getInitials();
+ },
+
+ viewPortWidth() {
+ const user = Users.findOne(this.userId);
+ return ((user && user.getInitials().length) || 1) * 12;
+ },
+});
+
// We extends the normal InlinedForm component to support UnsavedEdits draft
// feature.
(class extends InlinedForm {
@@ -386,6 +457,7 @@ Template.cardDetailsActionsPopup.helpers({
Template.cardDetailsActionsPopup.events({
'click .js-members': Popup.open('cardMembers'),
+ 'click .js-assignees': Popup.open('cardAssignees'),
'click .js-labels': Popup.open('cardLabels'),
'click .js-attachments': Popup.open('cardAttachments'),
'click .js-custom-fields': Popup.open('cardCustomFields'),
@@ -777,7 +849,14 @@ BlazeComponent.extendComponent({
EscapeActions.register(
'detailsPane',
() => {
- Utils.goBoardId(Session.get('currentBoard'));
+ if (Session.get('cardDetailsIsDragging')) {
+ // Reset dragging status as the mouse landed outside the cardDetails template area and this will prevent a mousedown event from firing
+ Session.set('cardDetailsIsDragging', false);
+ Session.set('cardDetailsIsMouseDown', false);
+ } else {
+ // Prevent close card when the user is selecting text and moves the mouse cursor outside the card detail area
+ Utils.goBoardId(Session.get('currentBoard'));
+ }
},
() => {
return !Session.equals('currentCard', null);
@@ -786,3 +865,76 @@ EscapeActions.register(
noClickEscapeOn: '.js-card-details,.board-sidebar,#header',
},
);
+
+Template.cardAssigneesPopup.events({
+ 'click .js-select-assignee'(event) {
+ const card = Cards.findOne(Session.get('currentCard'));
+ const assigneeId = this.userId;
+ card.toggleAssignee(assigneeId);
+ event.preventDefault();
+ },
+});
+
+Template.cardAssigneesPopup.helpers({
+ isCardAssignee() {
+ const card = Template.parentData();
+ const cardAssignees = card.getAssignees();
+
+ return _.contains(cardAssignees, this.userId);
+ },
+
+ user() {
+ return Users.findOne(this.userId);
+ },
+});
+
+Template.cardAssigneePopup.helpers({
+ userData() {
+ // We need to handle a special case for the search results provided by the
+ // `matteodem:easy-search` package. Since these results gets published in a
+ // separate collection, and not in the standard Meteor.Users collection as
+ // expected, we use a component parameter ("property") to distinguish the
+ // two cases.
+ const userCollection = this.esSearch ? ESSearchResults : Users;
+ return userCollection.findOne(this.userId, {
+ fields: {
+ profile: 1,
+ username: 1,
+ },
+ });
+ },
+
+ memberType() {
+ const user = Users.findOne(this.userId);
+ return user && user.isBoardAdmin() ? 'admin' : 'normal';
+ },
+
+ presenceStatusClassName() {
+ const user = Users.findOne(this.userId);
+ const userPresence = presences.findOne({ userId: this.userId });
+ if (user && user.isInvitedTo(Session.get('currentBoard'))) return 'pending';
+ else if (!userPresence) return 'disconnected';
+ else if (Session.equals('currentBoard', userPresence.state.currentBoardId))
+ return 'active';
+ else return 'idle';
+ },
+
+ isCardAssignee() {
+ const card = Template.parentData();
+ const cardAssignees = card.getAssignees();
+
+ return _.contains(cardAssignees, this.userId);
+ },
+
+ user() {
+ return Users.findOne(this.userId);
+ },
+});
+
+Template.cardAssigneePopup.events({
+ 'click .js-remove-assignee'() {
+ Cards.findOne(this.cardId).unassignAssignee(this.userId);
+ Popup.close();
+ },
+ 'click .js-edit-profile': Popup.open('editProfile'),
+});
diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl
index cd475072..3fc4d047 100644
--- a/client/components/cards/cardDetails.styl
+++ b/client/components/cards/cardDetails.styl
@@ -1,5 +1,80 @@
@import 'nib'
+// Assignee, code copied from wekan/client/users/userAvatar.styl
+
+avatar-radius = 50%
+
+.assignee
+ border-radius: 3px
+ display: block
+ position: relative
+ float: left
+ height: 30px
+ width: @height
+ margin: 0 4px 4px 0
+ cursor: pointer
+ user-select: none
+ z-index: 1
+ text-decoration: none
+ border-radius: avatar-radius
+
+ .avatar
+ overflow: hidden
+ border-radius: avatar-radius
+
+ &.avatar-assignee-initials
+ height: 70%
+ width: @height
+ padding: 15%
+ background-color: #dbdbdb
+ color: #444444
+ position: absolute
+
+ &.avatar-image
+ height: 100%
+ width: @height
+
+ .assignee-presence-status
+ background-color: #b3b3b3
+ border: 1px solid #fff
+ border-radius: 50%
+ height: 7px
+ width: @height
+ position: absolute
+ right: -1px
+ bottom: -1px
+ border: 1px solid white
+ z-index: 15
+
+ &.active
+ background: #64c464
+ border-color: #daf1da
+
+ &.idle
+ background: #e4e467
+ border-color: #f7f7d4
+
+ &.disconnected
+ background: #bdbdbd
+ border-color: #ededed
+
+ &.pending
+ background: #e44242
+ border-color: #f1dada
+
+
+
+ &.add-assignee
+ display: flex
+ align-items: center
+ justify-content: center
+ box-shadow: 0 0 0 2px darken(white, 25%) inset
+
+ &:hover, &.is-active
+ box-shadow: 0 0 0 2px darken(white, 60%) inset
+
+// Other card details
+
.card-details
padding: 0
flex-shrink: 0
@@ -32,7 +107,9 @@
border-bottom: 1px solid darken(white, 14%)
.close-card-details,
- .card-details-menu
+ .card-details-menu,
+ .close-card-details-mobile-web,
+ .card-details-menu-mobile-web
float: right
.close-card-details
@@ -40,10 +117,20 @@
padding: 5px
margin-right: -8px
+ .close-card-details-mobile-web
+ font-size: 24px
+ padding: 5px
+ margin-right: 40px
+
.card-details-menu
font-size: 17px
padding: 10px
+ .card-details-menu-mobile-web
+ font-size: 17px
+ padding: 10px
+ margin-right: 30px
+
.card-details-watch
font-size: 17px
padding-left: 7px
@@ -93,6 +180,7 @@
margin-right: 0
&.card-details-item-labels,
&.card-details-item-members,
+ &.card-details-item-assignees,
&.card-details-item-received,
&.card-details-item-start,
&.card-details-item-due,
diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade
index 3806ce41..79672f8c 100644
--- a/client/components/cards/minicard.jade
+++ b/client/components/cards/minicard.jade
@@ -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
@@ -69,6 +76,12 @@ template(name="minicard")
+viewer
= trueValue
+ if getAssignees
+ .minicard-assignees.js-minicard-assignees
+ each getAssignees
+ +userAvatar(userId=this)
+ hr
+
if getMembers
.minicard-members.js-minicard-members
each getMembers
diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js
index 4c25c11d..a9f92dec 100644
--- a/client/components/cards/minicard.js
+++ b/client/components/cards/minicard.js
@@ -18,7 +18,13 @@ BlazeComponent.extendComponent({
},
{
'click .js-toggle-minicard-label-text'() {
- Meteor.call('toggleMinicardLabelText');
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('hiddenMinicardLabelText')) {
+ cookies.remove('hiddenMinicardLabelText'); //true
+ } else {
+ cookies.set('hiddenMinicardLabelText', 'true'); //true
+ }
},
},
];
@@ -26,7 +32,32 @@ BlazeComponent.extendComponent({
}).register('minicard');
Template.minicard.helpers({
+ showDesktopDragHandles() {
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).showDesktopDragHandles;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('showDesktopDragHandles')) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ },
hiddenMinicardLabelText() {
- return Meteor.user().hasHiddenMinicardLabelText();
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).hiddenMinicardLabelText;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('hiddenMinicardLabelText')) {
+ return true;
+ } else {
+ return false;
+ }
+ }
},
});
diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl
index c4172572..8607e118 100644
--- a/client/components/cards/minicard.styl
+++ b/client/components/cards/minicard.styl
@@ -105,7 +105,7 @@
right: 5px;
top: 5px;
display:none;
- @media only screen and (max-width: 1199px) {
+ @media only screen {
display:block;
}
.fa-arrows
@@ -160,9 +160,10 @@
padding-left: 0px
line-height: 12px
- .minicard-members
+ .minicard-members,
+ .minicard-assignees
float: right
- margin: 2px -8px -2px 0
+ margin: 2px -8px 12px 0
.member
float: right
@@ -170,10 +171,17 @@
height: 28px
width: @height
+ .assignee
+ float: right
+ border-radius: 50%
+ height: 28px
+ width: @height
+
+ .badges
margin-top: 10px
- .minicard-members:empty
+ .minicard-members:empty,
+ .minicard-assignees:empty
display: none
&.minicard-composer
diff --git a/client/components/lists/list.js b/client/components/lists/list.js
index c2b39be9..e58ea430 100644
--- a/client/components/lists/list.js
+++ b/client/components/lists/list.js
@@ -22,21 +22,15 @@ BlazeComponent.extendComponent({
function userIsMember() {
return (
- Meteor.user() &&
- Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ Meteor.user()
+ && Meteor.user().isBoardMember()
+ && !Meteor.user().isCommentOnly()
);
}
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
const $cards = this.$('.js-minicards');
- if (window.matchMedia('(max-width: 1199px)').matches) {
- $('.js-minicards').sortable({
- handle: '.handle',
- });
- }
-
$cards.sortable({
connectWith: '.js-minicards:not(.js-list-full)',
tolerance: 'pointer',
@@ -79,16 +73,15 @@ BlazeComponent.extendComponent({
const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
const currentBoard = Boards.findOne(Session.get('currentBoard'));
let swimlaneId = '';
- const boardView = (Meteor.user().profile || {}).boardView;
if (
- boardView === 'board-view-swimlanes' ||
- currentBoard.isTemplatesBoard()
+ Utils.boardView() === 'board-view-swimlanes'
+ || currentBoard.isTemplatesBoard()
)
swimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))._id;
else if (
- boardView === 'board-view-lists' ||
- boardView === 'board-view-cal' ||
- !boardView
+ Utils.boardView() === 'board-view-lists'
+ || Utils.boardView() === 'board-view-cal'
+ || !Utils.boardView
)
swimlaneId = currentBoard.getDefaultSwimline()._id;
@@ -122,8 +115,32 @@ BlazeComponent.extendComponent({
// ugly touch event hotfix
enableClickOnTouch(itemsSelector);
- // Disable drag-dropping if the current user is not a board member or is comment only
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+
this.autorun(() => {
+ let showDesktopDragHandles = false;
+ currentUser = Meteor.user();
+ if (currentUser) {
+ showDesktopDragHandles = (currentUser.profile || {})
+ .showDesktopDragHandles;
+ } else if (cookies.has('showDesktopDragHandles')) {
+ showDesktopDragHandles = true;
+ } else {
+ showDesktopDragHandles = false;
+ }
+
+ if (!Utils.isMiniScreen() && showDesktopDragHandles) {
+ $cards.sortable({
+ handle: '.handle',
+ });
+ } else {
+ $cards.sortable({
+ handle: '.minicard',
+ });
+ }
+
+ // Disable drag-dropping if the current user is not a board member or is comment only
$cards.sortable('option', 'disabled', !userIsMember());
});
@@ -155,6 +172,23 @@ BlazeComponent.extendComponent({
},
}).register('list');
+Template.list.helpers({
+ showDesktopDragHandles() {
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).showDesktopDragHandles;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('showDesktopDragHandles')) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ },
+});
+
Template.miniList.events({
'click .js-select-list'() {
const listId = this._id;
diff --git a/client/components/lists/list.styl b/client/components/lists/list.styl
index 81938c1a..27cf678c 100644
--- a/client/components/lists/list.styl
+++ b/client/components/lists/list.styl
@@ -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: 7px
+ 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
+ right: 47px
font-size: 20px
+ .list-header-handle
+ position: absolute
+ padding: 7px
+ top: 50%
+ transform: translateY(-50%)
+ right: 10px
+ font-size: 24px
+
.link-board-wrapper
display: flex
align-items: baseline
diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js
index a1a4c11a..b0974705 100644
--- a/client/components/lists/listBody.js
+++ b/client/components/lists/listBody.js
@@ -48,7 +48,6 @@ BlazeComponent.extendComponent({
const board = this.data().board();
let linkedId = '';
let swimlaneId = '';
- const boardView = (Meteor.user().profile || {}).boardView;
let cardType = 'cardType-card';
if (title) {
if (board.isTemplatesBoard()) {
@@ -71,14 +70,14 @@ BlazeComponent.extendComponent({
});
cardType = 'cardType-linkedBoard';
}
- } else if (boardView === 'board-view-swimlanes')
+ } else if (Utils.boardView() === 'board-view-swimlanes')
swimlaneId = this.parentComponent()
.parentComponent()
.data()._id;
else if (
- boardView === 'board-view-lists' ||
- boardView === 'board-view-cal' ||
- !boardView
+ Utils.boardView() === 'board-view-lists' ||
+ Utils.boardView() === 'board-view-cal' ||
+ !Utils.boardView
)
swimlaneId = board.getDefaultSwimline()._id;
@@ -157,9 +156,8 @@ BlazeComponent.extendComponent({
},
idOrNull(swimlaneId) {
- const currentUser = Meteor.user();
if (
- (currentUser.profile || {}).boardView === 'board-view-swimlanes' ||
+ Utils.boardView() === 'board-view-swimlanes' ||
this.data()
.board()
.isTemplatesBoard()
@@ -397,10 +395,9 @@ BlazeComponent.extendComponent({
'.js-swimlane',
);
this.swimlaneId = '';
- const boardView = (Meteor.user().profile || {}).boardView;
- if (boardView === 'board-view-swimlanes')
+ if (Utils.boardView() === 'board-view-swimlanes')
this.swimlaneId = Blaze.getData(swimlane[0])._id;
- else if (boardView === 'board-view-lists' || !boardView)
+ else if (Utils.boardView() === 'board-view-lists' || !Utils.boardView)
this.swimlaneId = Swimlanes.findOne({ boardId: this.boardId })._id;
},
@@ -580,7 +577,7 @@ BlazeComponent.extendComponent({
const swimlane = $(Popup._getTopStack().openerElement).parents(
'.js-swimlane',
);
- if ((Meteor.user().profile || {}).boardView === 'board-view-swimlanes')
+ if (Utils.boardView() === 'board-view-swimlanes')
this.swimlaneId = Blaze.getData(swimlane[0])._id;
else this.swimlaneId = Swimlanes.findOne({ boardId: this.boardId })._id;
// List where to insert card
@@ -701,15 +698,26 @@ BlazeComponent.extendComponent({
this.listId = this.parentComponent().data()._id;
this.swimlaneId = '';
- let user = Meteor.user();
- if (user) {
- const boardView = (Meteor.user().profile || {}).boardView;
- if (boardView === 'board-view-swimlanes') {
- this.swimlaneId = this.parentComponent()
- .parentComponent()
- .parentComponent()
- .data()._id;
+ const isSandstorm =
+ Meteor.settings &&
+ Meteor.settings.public &&
+ Meteor.settings.public.sandstorm;
+
+ if (isSandstorm) {
+ const user = Meteor.user();
+ if (user) {
+ if (Utils.boardView() === 'board-view-swimlanes') {
+ this.swimlaneId = this.parentComponent()
+ .parentComponent()
+ .parentComponent()
+ .data()._id;
+ }
}
+ } else if (Utils.boardView() === 'board-view-swimlanes') {
+ this.swimlaneId = this.parentComponent()
+ .parentComponent()
+ .parentComponent()
+ .data()._id;
}
},
diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade
index f930e57a..631f68a0 100644
--- a/client/components/lists/listHeader.jade
+++ b/client/components/lists/listHeader.jade
@@ -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-handle.handle.fa.fa-arrows.js-list-handle
else
a.list-header-menu-icon.fa.fa-angle-right.js-select-list
+ a.list-header-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-handle.handle.fa.fa-arrows.js-list-handle
template(name="editListTitleForm")
.list-composer
diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js
index e8a82499..34322fa9 100644
--- a/client/components/lists/listHeader.js
+++ b/client/components/lists/listHeader.js
@@ -7,12 +7,26 @@ BlazeComponent.extendComponent({
canSeeAddCard() {
const list = Template.currentData();
return (
- !list.getWipLimit('enabled') ||
- list.getWipLimit('soft') ||
- !this.reachedWipLimit()
+ !list.getWipLimit('enabled')
+ || list.getWipLimit('soft')
+ || !this.reachedWipLimit()
);
},
+ 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]
@@ -30,14 +44,18 @@ BlazeComponent.extendComponent({
},
limitToShowCardsCount() {
- return Meteor.user().getLimitToShowCardsCount();
+ const currentUser = Meteor.user();
+ if (currentUser) {
+ return Meteor.user().getLimitToShowCardsCount();
+ } else {
+ return false;
+ }
},
cardsCount() {
const list = Template.currentData();
let swimlaneId = '';
- const boardView = (Meteor.user().profile || {}).boardView;
- if (boardView === 'board-view-swimlanes')
+ if (Utils.boardView() === 'board-view-swimlanes')
swimlaneId = this.parentComponent()
.parentComponent()
.data()._id;
@@ -48,8 +66,8 @@ BlazeComponent.extendComponent({
reachedWipLimit() {
const list = Template.currentData();
return (
- list.getWipLimit('enabled') &&
- list.getWipLimit('value') <= list.cards().count()
+ list.getWipLimit('enabled')
+ && list.getWipLimit('value') <= list.cards().count()
);
},
@@ -61,6 +79,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 +102,23 @@ BlazeComponent.extendComponent({
},
}).register('listHeader');
+Template.listHeader.helpers({
+ showDesktopDragHandles() {
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).showDesktopDragHandles;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('showDesktopDragHandles')) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ },
+});
+
Template.listActionPopup.helpers({
isWipLimitEnabled() {
return Template.currentData().getWipLimit('enabled');
@@ -138,8 +177,8 @@ BlazeComponent.extendComponent({
const list = Template.currentData();
if (
- list.getWipLimit('soft') &&
- list.getWipLimit('value') < list.cards().count()
+ list.getWipLimit('soft')
+ && list.getWipLimit('value') < list.cards().count()
) {
list.setWipLimit(list.cards().count());
}
@@ -150,8 +189,8 @@ BlazeComponent.extendComponent({
const list = Template.currentData();
// Prevent user from using previously stored wipLimit.value if it is less than the current number of cards in the list
if (
- !list.getWipLimit('enabled') &&
- list.getWipLimit('value') < list.cards().count()
+ !list.getWipLimit('enabled')
+ && list.getWipLimit('value') < list.cards().count()
) {
list.setWipLimit(list.cards().count());
}
diff --git a/client/components/main/editor.js b/client/components/main/editor.js
index 91403086..39c03aa9 100755
--- a/client/components/main/editor.js
+++ b/client/components/main/editor.js
@@ -94,7 +94,13 @@ Template.editor.onRendered(() => {
currentBoard
.activeMembers()
.map(member => {
- const username = Users.findOne(member.userId).username;
+ const user = Users.findOne(member.userId);
+ if (user._id === Meteor.userId()) {
+ return null;
+ }
+ const value = user.username;
+ const username =
+ value && value.match(/\s+/) ? `"${value}"` : value;
return username.includes(term) ? username : null;
})
.filter(Boolean),
@@ -120,9 +126,10 @@ Template.editor.onRendered(() => {
? [
['view', ['fullscreen']],
['table', ['table']],
- ['font', ['bold', 'underline']],
- //['fontsize', ['fontsize']],
+ ['font', ['bold']],
['color', ['color']],
+ ['insert', ['video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
+ //['fontsize', ['fontsize']],
]
: [
['style', ['style']],
@@ -156,25 +163,45 @@ Template.editor.onRendered(() => {
}
return undefined;
};
+ let popupShown = false;
inputs.each(function(idx, input) {
mSummernotes[idx] = $(input).summernote({
placeholder,
callbacks: {
+ onKeydown(e) {
+ if (popupShown) {
+ e.preventDefault();
+ }
+ },
+ onKeyup(e) {
+ if (popupShown) {
+ e.preventDefault();
+ }
+ },
onInit(object) {
const originalInput = this;
+ const setAutocomplete = function(jEditor) {
+ if (jEditor !== undefined) {
+ jEditor.escapeableTextComplete(mentions).on({
+ 'textComplete:show'() {
+ popupShown = true;
+ },
+ 'textComplete:hide'() {
+ popupShown = false;
+ },
+ });
+ }
+ };
$(originalInput).on('submitted', function() {
// resetCommentInput has been called
if (!this.value) {
const sn = getSummernote(this);
- sn && sn.summernote('reset');
- object && object.editingArea.find('.note-placeholder').show();
+ sn && sn.summernote('code', '');
}
});
const jEditor = object && object.editable;
const toolbar = object && object.toolbar;
- if (jEditor !== undefined) {
- jEditor.escapeableTextComplete(mentions);
- }
+ setAutocomplete(jEditor);
if (toolbar !== undefined) {
const fBtn = toolbar.find('.btn-fullscreen');
fBtn.on('click', function() {
@@ -264,7 +291,7 @@ Template.editor.onRendered(() => {
const someNote = getSummernote(object);
const original = someNote.summernote('code');
const cleaned = cleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML.
- someNote.summernote('reset'); //clear original
+ someNote.summernote('code', ''); //clear original
someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code.
};
setTimeout(function() {
@@ -325,11 +352,12 @@ Blaze.Template.registerHelper(
}
return member;
});
- const mentionRegex = /\B@([\w.]*)/gi;
+ const mentionRegex = /\B@(?:(?:"([\w.\s]*)")|([\w.]+))/gi; // including space in username
let currentMention;
while ((currentMention = mentionRegex.exec(content)) !== null) {
- const [fullMention, username] = currentMention;
+ const [fullMention, quoteduser, simple] = currentMention;
+ const username = quoteduser || simple;
const knowedUser = _.findWhere(knowedUsers, { username });
if (!knowedUser) {
continue;
diff --git a/client/components/main/layouts.styl b/client/components/main/layouts.styl
index 56c35284..01ce2f16 100644
--- a/client/components/main/layouts.styl
+++ b/client/components/main/layouts.styl
@@ -381,6 +381,10 @@ a
display: block
word-wrap: break-word
+ table
+ word-wrap: normal
+ word-break: normal
+
ol
list-style-type: decimal
padding-left: 20px
diff --git a/client/components/main/popup.styl b/client/components/main/popup.styl
index ff00eef3..023cba3d 100644
--- a/client/components/main/popup.styl
+++ b/client/components/main/popup.styl
@@ -130,7 +130,8 @@ $popupWidth = 300px
.popup-container-depth-{depth}
transform: translateX(- depth * $popupWidth)
-.select-members-list
+.select-members-list,
+.select-avatars-list
margin-bottom: 8px
.pop-over-list
@@ -230,7 +231,8 @@ $popupWidth = 300px
min-height: 56px
position: relative
- .member
+ .member,
+ .avatar
position: absolute
top: 2px
left: 2px
diff --git a/client/components/settings/peopleBody.js b/client/components/settings/peopleBody.js
index a9f2247c..8610034e 100644
--- a/client/components/settings/peopleBody.js
+++ b/client/components/settings/peopleBody.js
@@ -17,7 +17,7 @@ BlazeComponent.extendComponent({
this.autorun(() => {
const limit = this.page.get() * usersPerPage;
- this.subscribe('people', limit, () => {
+ this.subscribe('people', this.findUsersOptions.get(), limit, () => {
this.loadNextPageLocked = false;
const nextPeakBefore = this.callFirstWith(null, 'getNextPeak');
this.calculateNextPeak();
@@ -85,7 +85,7 @@ BlazeComponent.extendComponent({
const users = Users.find(this.findUsersOptions.get(), {
fields: { _id: true },
});
- this.number.set(users.count());
+ this.number.set(users.count(false));
return users;
},
peopleNumber() {
diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade
index 8eb584dc..04b635e8 100644
--- a/client/components/settings/settingBody.jade
+++ b/client/components/settings/settingBody.jade
@@ -18,6 +18,8 @@ template(name="setting")
a.js-setting-menu(data-id="announcement-setting") {{_ 'admin-announcement'}}
li
a.js-setting-menu(data-id="layout-setting") {{_ 'layout'}}
+ li
+ a.js-setting-menu(data-id="webhook-setting") {{_ 'global-webhook'}}
.main-body
if loading.get
+spinner
@@ -31,6 +33,12 @@ template(name="setting")
+announcementSettings
else if layoutSetting.get
+layoutSettings
+ else if webhookSetting.get
+ +webhookSettings
+
+template(name="webhookSettings")
+ span
+ +outgoingWebhooksPopup
template(name="general")
ul#registration-setting.setting-detail
diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js
index f9b5c08d..4ff5aedd 100644
--- a/client/components/settings/settingBody.js
+++ b/client/components/settings/settingBody.js
@@ -7,11 +7,13 @@ BlazeComponent.extendComponent({
this.accountSetting = new ReactiveVar(false);
this.announcementSetting = new ReactiveVar(false);
this.layoutSetting = new ReactiveVar(false);
+ this.webhookSetting = new ReactiveVar(false);
Meteor.subscribe('setting');
Meteor.subscribe('mailServer');
Meteor.subscribe('accountSettings');
Meteor.subscribe('announcements');
+ Meteor.subscribe('globalwebhooks');
},
setError(error) {
@@ -83,6 +85,7 @@ BlazeComponent.extendComponent({
this.accountSetting.set('account-setting' === targetID);
this.announcementSetting.set('announcement-setting' === targetID);
this.layoutSetting.set('layout-setting' === targetID);
+ this.webhookSetting.set('webhook-setting' === targetID);
}
},
diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade
index 2dfe41b3..ccfadc0c 100644
--- a/client/components/sidebar/sidebar.jade
+++ b/client/components/sidebar/sidebar.jade
@@ -135,22 +135,30 @@ template(name="archiveBoardPopup")
template(name="outgoingWebhooksPopup")
each integrations
form.integration-form
- if title
- h4 {{title}}
- else
- h4 {{_ 'no-name'}}
- label
- | URL
- input.js-outgoing-webhooks-url(type="text" name="url" value=url)
- input(type="hidden" value=_id name="id")
+ a.flex
+ span {{_ 'disable-webhook'}}
+ b &nbsp;
+ .materialCheckBox(class="{{#unless enabled}}is-checked{{/unless}}")
+ input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" value=title)
+ input.js-outgoing-webhooks-url(type="text" name="url" value=url autofocus)
+ input.js-outgoing-webhooks-token(placeholder="{{_ 'webhook-token' }}" type="text" value=token name="token")
+ select.js-outgoing-webhooks-type(name="type")
+ each _type in types
+ if($eq _type this.type)
+ option(value=_type selected="selected") {{_ _type}}
+ else
+ option(value=_type) {{_ _type}}
+ input(type="hidden" value=this.type name="_type")
+ input(type="hidden" value=_id name="id")
input.primary.wide(type="submit" value="{{_ 'save'}}")
form.integration-form
- h4
- | {{_ 'new-outgoing-webhook'}}
- label
- | URL
- input.js-outgoing-webhooks-url(type="text" name="url" autofocus)
- input.primary.wide(type="submit" value="{{_ 'save'}}")
+ input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" autofocus)
+ input.js-outgoing-webhooks-url(placeholder="{{_ 'URL' }}" type="text" name="url")
+ input.js-outgoing-webhooks-token(placeholder="{{_ 'webhook-token' }}" type="text" name="token")
+ select.js-outgoing-webhooks-type(name="type")
+ each _type in types
+ option(value=_type) {{_ _type}}
+ input.primary.wide(type="submit" value="{{_ 'create'}}")
template(name="boardMenuPopup")
ul.pop-over-list
diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js
index f7efb1e8..6bb22f39 100644
--- a/client/components/sidebar/sidebar.js
+++ b/client/components/sidebar/sidebar.js
@@ -1,6 +1,8 @@
Sidebar = null;
const defaultView = 'home';
+const MCB = '.materialCheckBox';
+const CKCLS = 'is-checked';
const viewTitles = {
filter: 'filter-cards',
@@ -105,7 +107,18 @@ BlazeComponent.extendComponent({
'click .js-toggle-sidebar': this.toggle,
'click .js-back-home': this.setView,
'click .js-toggle-minicard-label-text'() {
- Meteor.call('toggleMinicardLabelText');
+ currentUser = Meteor.user();
+ if (currentUser) {
+ Meteor.call('toggleMinicardLabelText');
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('hiddenMinicardLabelText')) {
+ cookies.remove('hiddenMinicardLabelText');
+ } else {
+ cookies.set('hiddenMinicardLabelText', 'true');
+ }
+ }
},
'click .js-shortcuts'() {
FlowRouter.go('shortcuts');
@@ -119,7 +132,18 @@ Blaze.registerHelper('Sidebar', () => Sidebar);
Template.homeSidebar.helpers({
hiddenMinicardLabelText() {
- return Meteor.user().hasHiddenMinicardLabelText();
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).hiddenMinicardLabelText;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('hiddenMinicardLabelText')) {
+ return true;
+ } else {
+ return false;
+ }
+ }
},
});
@@ -280,44 +304,71 @@ Template.membersWidget.events({
});
BlazeComponent.extendComponent({
+ boardId() {
+ return Session.get('currentBoard') || Integrations.Const.GLOBAL_WEBHOOK_ID;
+ },
integrations() {
- const boardId = Session.get('currentBoard');
+ const boardId = this.boardId();
return Integrations.find({ boardId: `${boardId}` }).fetch();
},
-
- integration(id) {
- const boardId = Session.get('currentBoard');
- return Integrations.findOne({ _id: id, boardId: `${boardId}` });
+ types() {
+ return Integrations.Const.WEBHOOK_TYPES;
+ },
+ integration(cond) {
+ const boardId = this.boardId();
+ const condition = { boardId, ...cond };
+ for (const k in condition) {
+ if (!condition[k]) delete condition[k];
+ }
+ return Integrations.findOne(condition);
+ },
+ onCreated() {
+ this.disabled = new ReactiveVar(false);
},
-
events() {
return [
{
+ 'click a.flex'(evt) {
+ this.disabled.set(!this.disabled.get());
+ $(evt.target).toggleClass(CKCLS, this.disabled.get());
+ },
submit(evt) {
evt.preventDefault();
const url = evt.target.url.value;
- const boardId = Session.get('currentBoard');
+ const boardId = this.boardId();
let id = null;
let integration = null;
+ const title = evt.target.title.value;
+ const token = evt.target.token.value;
+ const type = evt.target.type.value;
+ const enabled = !this.disabled.get();
+ let remove = false;
+ const values = {
+ url,
+ type,
+ token,
+ title,
+ enabled,
+ };
if (evt.target.id) {
id = evt.target.id.value;
- integration = this.integration(id);
- if (url) {
- Integrations.update(integration._id, {
- $set: {
- url: `${url}`,
- },
- });
- } else {
- Integrations.remove(integration._id);
- }
+ integration = this.integration({ _id: id });
+ remove = !url;
+ } else if (url) {
+ integration = this.integration({ url, token });
+ }
+ if (remove) {
+ Integrations.remove(integration._id);
+ } else if (integration && integration._id) {
+ Integrations.update(integration._id, {
+ $set: values,
+ });
} else if (url) {
Integrations.insert({
+ ...values,
userId: Meteor.userId(),
enabled: true,
- type: 'outgoing-webhooks',
- url: `${url}`,
- boardId: `${boardId}`,
+ boardId,
activities: ['all'],
});
}
@@ -474,12 +525,12 @@ BlazeComponent.extendComponent({
evt.preventDefault();
this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks;
this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks);
- $('.js-field-has-subtasks .materialCheckBox').toggleClass(
- 'is-checked',
+ $(`.js-field-has-subtasks ${MCB}`).toggleClass(
+ CKCLS,
this.currentBoard.allowsSubtasks,
);
$('.js-field-has-subtasks').toggleClass(
- 'is-checked',
+ CKCLS,
this.currentBoard.allowsSubtasks,
);
$('.js-field-deposit-board').prop(
@@ -515,15 +566,12 @@ BlazeComponent.extendComponent({
];
options.forEach(function(element) {
if (element !== value) {
- $(`#${element} .materialCheckBox`).toggleClass(
- 'is-checked',
- false,
- );
- $(`#${element}`).toggleClass('is-checked', false);
+ $(`#${element} ${MCB}`).toggleClass(CKCLS, false);
+ $(`#${element}`).toggleClass(CKCLS, false);
}
});
- $(`#${value} .materialCheckBox`).toggleClass('is-checked', true);
- $(`#${value}`).toggleClass('is-checked', true);
+ $(`#${value} ${MCB}`).toggleClass(CKCLS, true);
+ $(`#${value}`).toggleClass(CKCLS, true);
this.currentBoard.setPresentParentTask(value);
evt.preventDefault();
},
diff --git a/client/components/sidebar/sidebarFilters.jade b/client/components/sidebar/sidebarFilters.jade
index 55ab213a..5f929cb9 100644
--- a/client/components/sidebar/sidebarFilters.jade
+++ b/client/components/sidebar/sidebarFilters.jade
@@ -5,6 +5,10 @@
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
span.sidebar-list-item-description
diff --git a/client/components/sidebar/sidebarFilters.js b/client/components/sidebar/sidebarFilters.js
index 3483d00c..ee0176b9 100644
--- a/client/components/sidebar/sidebarFilters.js
+++ b/client/components/sidebar/sidebarFilters.js
@@ -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);
diff --git a/client/components/sidebar/sidebarSearches.jade b/client/components/sidebar/sidebarSearches.jade
index 96877c50..4ee7fc9c 100644
--- a/client/components/sidebar/sidebarSearches.jade
+++ b/client/components/sidebar/sidebarSearches.jade
@@ -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)
diff --git a/client/components/sidebar/sidebarSearches.js b/client/components/sidebar/sidebarSearches.js
index 8944c04e..02677260 100644
--- a/client/components/sidebar/sidebarSearches.js
+++ b/client/components/sidebar/sidebarSearches.js
@@ -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 [
{
diff --git a/client/components/swimlanes/swimlaneHeader.jade b/client/components/swimlanes/swimlaneHeader.jade
index 8c6aa5a3..72a7f054 100644
--- a/client/components/swimlanes/swimlaneHeader.jade
+++ b/client/components/swimlanes/swimlaneHeader.jade
@@ -16,6 +16,11 @@ 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
+ unless isMiniScreen
+ if showDesktopDragHandles
+ a.swimlane-header-handle.handle.fa.fa-arrows.js-swimlane-header-handle
+ if isMiniScreen
+ a.swimlane-header-miniscreen-handle.handle.fa.fa-arrows.js-swimlane-header-handle
template(name="editSwimlaneTitleForm")
.list-composer
diff --git a/client/components/swimlanes/swimlaneHeader.js b/client/components/swimlanes/swimlaneHeader.js
index ee21d100..69971b05 100644
--- a/client/components/swimlanes/swimlaneHeader.js
+++ b/client/components/swimlanes/swimlaneHeader.js
@@ -28,6 +28,23 @@ BlazeComponent.extendComponent({
},
}).register('swimlaneHeader');
+Template.swimlaneHeader.helpers({
+ showDesktopDragHandles() {
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).showDesktopDragHandles;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('showDesktopDragHandles')) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ },
+});
+
Template.swimlaneActionPopup.events({
'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'),
'click .js-close-swimlane'(event) {
diff --git a/client/components/swimlanes/swimlanes.jade b/client/components/swimlanes/swimlanes.jade
index 3ad43777..b2e03afe 100644
--- a/client/components/swimlanes/swimlanes.jade
+++ b/client/components/swimlanes/swimlanes.jade
@@ -1,24 +1,47 @@
template(name="swimlane")
.swimlane
+swimlaneHeader
- .swimlane.js-lists.js-swimlane
- if isMiniScreen
- if currentListIsInThisSwimlane _id
- +list(currentList)
- unless currentList
+ unless collapseSwimlane
+ .swimlane.js-lists.js-swimlane
+ if isMiniScreen
+ if currentListIsInThisSwimlane _id
+ +list(currentList)
+ unless currentList
+ each lists
+ +miniList(this)
+ if currentUser.isBoardMember
+ unless currentUser.isCommentOnly
+ +addListForm
+ else
each lists
- +miniList(this)
+ +list(this)
+ if currentCardIsInThisList _id ../_id
+ +cardDetails(currentCard)
if currentUser.isBoardMember
unless currentUser.isCommentOnly
+addListForm
- else
- each lists
- +list(this)
- if currentCardIsInThisList _id ../_id
- +cardDetails(currentCard)
- if currentUser.isBoardMember
- unless currentUser.isCommentOnly
- +addListForm
+ //if collapseSwimlane
+ // // Minimize swimlanes next 2 lines below https://www.w3schools.com/howto/howto_js_accordion.asp
+ // button(class="accordion")
+ // div(class="panel")
+ // .swimlane.js-lists.js-swimlane
+ // if isMiniScreen
+ // if currentListIsInThisSwimlane _id
+ // +list(currentList)
+ // unless currentList
+ // each lists
+ // +miniList(this)
+ // if currentUser.isBoardMember
+ // unless currentUser.isCommentOnly
+ // +addListForm
+ // else
+ // 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
@@ -42,7 +65,7 @@ template(name="listsGroup")
+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'}}"
diff --git a/client/components/swimlanes/swimlanes.js b/client/components/swimlanes/swimlanes.js
index e0857003..9bc093be 100644
--- a/client/components/swimlanes/swimlanes.js
+++ b/client/components/swimlanes/swimlanes.js
@@ -3,8 +3,8 @@ const { calculateIndex, enableClickOnTouch } = Utils;
function currentListIsInThisSwimlane(swimlaneId) {
const currentList = Lists.findOne(Session.get('currentList'));
return (
- currentList &&
- (currentList.swimlaneId === swimlaneId || currentList.swimlaneId === '')
+ currentList
+ && (currentList.swimlaneId === swimlaneId || currentList.swimlaneId === '')
);
}
@@ -12,14 +12,14 @@ function currentCardIsInThisList(listId, swimlaneId) {
const currentCard = Cards.findOne(Session.get('currentCard'));
const currentUser = Meteor.user();
if (
- currentUser &&
- currentUser.profile &&
- currentUser.profile.boardView === 'board-view-swimlanes'
+ currentUser
+ && currentUser.profile
+ && Utils.boardView() === 'board-view-swimlanes'
)
return (
- currentCard &&
- currentCard.listId === listId &&
- currentCard.swimlaneId === swimlaneId
+ currentCard
+ && currentCard.listId === listId
+ && currentCard.swimlaneId === swimlaneId
);
// Default view: board-view-lists
else return currentCard && currentCard.listId === listId;
@@ -56,7 +56,6 @@ function initSortable(boardComponent, $listsDom) {
$listsDom.sortable({
tolerance: 'pointer',
helper: 'clone',
- handle: '.js-list-header',
items: '.js-list:not(.js-list-composer)',
placeholder: 'list placeholder',
distance: 7,
@@ -91,21 +90,47 @@ function initSortable(boardComponent, $listsDom) {
function userIsMember() {
return (
- Meteor.user() &&
- Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ Meteor.user()
+ && Meteor.user().isBoardMember()
+ && !Meteor.user().isCommentOnly()
);
}
- // Disable drag-dropping while in multi-selection mode, or if the current user
- // is not a board member
boardComponent.autorun(() => {
+ let showDesktopDragHandles = false;
+ currentUser = Meteor.user();
+ if (currentUser) {
+ showDesktopDragHandles = (currentUser.profile || {})
+ .showDesktopDragHandles;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('showDesktopDragHandles')) {
+ showDesktopDragHandles = true;
+ } else {
+ showDesktopDragHandles = false;
+ }
+ }
+
+ if (!Utils.isMiniScreen() && showDesktopDragHandles) {
+ $listsDom.sortable({
+ handle: '.js-list-handle',
+ });
+ } else {
+ $listsDom.sortable({
+ handle: '.js-list-header',
+ });
+ }
+
const $listDom = $listsDom;
if ($listDom.data('sortable')) {
$listsDom.sortable(
'option',
'disabled',
- MultiSelection.isActive() || !userIsMember(),
+ // Disable drag-dropping when user is not member
+ !userIsMember(),
+ // Not disable drag-dropping while in multi-selection mode
+ // MultiSelection.isActive() || !userIsMember(),
);
}
});
@@ -121,6 +146,26 @@ BlazeComponent.extendComponent({
}
initSortable(boardComponent, $listsDom);
+
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('collapseSwimlane')) {
+ // Minimize swimlanes start https://www.w3schools.com/howto/howto_js_accordion.asp
+ const acc = document.getElementsByClassName('accordion');
+ let i;
+ for (i = 0; i < acc.length; i++) {
+ acc[i].addEventListener('click', function() {
+ this.classList.toggle('active');
+ const panel = this.nextElementSibling;
+ if (panel.style.maxHeight) {
+ panel.style.maxHeight = null;
+ } else {
+ panel.style.maxHeight = `${panel.scrollHeight}px`;
+ }
+ });
+ }
+ // Minimize swimlanes end https://www.w3schools.com/howto/howto_js_accordion.asp
+ }
},
onCreated() {
this.draggingActive = new ReactiveVar(false);
@@ -151,16 +196,32 @@ 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',
- ];
+
+ let showDesktopDragHandles = false;
+ currentUser = Meteor.user();
+ if (currentUser) {
+ showDesktopDragHandles = (currentUser.profile || {})
+ .showDesktopDragHandles;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('showDesktopDragHandles')) {
+ showDesktopDragHandles = true;
+ } else {
+ showDesktopDragHandles = false;
+ }
+ }
+
+ const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
+ Utils.isMiniScreen()
+ || (!Utils.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
+ $(evt.target).closest(noDragInside.join(',')).length === 0
+ && this.$('.swimlane').prop('clientHeight') > evt.offsetY
) {
this._isDragging = true;
this._lastDragPositionX = evt.clientX;
@@ -194,8 +255,8 @@ BlazeComponent.extendComponent({
onCreated() {
this.currentBoard = Boards.findOne(Session.get('currentBoard'));
this.isListTemplatesSwimlane =
- this.currentBoard.isTemplatesBoard() &&
- this.currentData().isListTemplatesSwimlane();
+ this.currentBoard.isTemplatesBoard()
+ && this.currentData().isListTemplatesSwimlane();
this.currentSwimlane = this.currentData();
},
@@ -233,11 +294,25 @@ BlazeComponent.extendComponent({
}).register('addListForm');
Template.swimlane.helpers({
+ showDesktopDragHandles() {
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).showDesktopDragHandles;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('showDesktopDragHandles')) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ },
canSeeAddList() {
return (
- Meteor.user() &&
- Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ Meteor.user()
+ && Meteor.user().isBoardMember()
+ && !Meteor.user().isCommentOnly()
);
},
});
@@ -253,6 +328,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()
diff --git a/client/components/swimlanes/swimlanes.styl b/client/components/swimlanes/swimlanes.styl
index 1056e1e3..ca5611cc 100644
--- a/client/components/swimlanes/swimlanes.styl
+++ b/client/components/swimlanes/swimlanes.styl
@@ -1,5 +1,41 @@
@import 'nib'
+/*
+// Minimize swimlanes start https://www.w3schools.com/howto/howto_js_accordion.asp
+
+.accordion
+ cursor: pointer
+ width: 30px
+ height: 20px
+ border: none
+ outline: none
+ font-size: 18px
+ transition: 0.4s
+ padding-top: 0px
+ margin-top: 0px
+
+.accordion:after
+ // Unicode triagle right:
+ content: '\25B6'
+ color: #777
+ font-weight: bold
+ float: left
+
+.active:after
+ // Unicode triangle down:
+ content: '\25BC'
+
+.panel
+ width: 100%
+ max-height: 0
+ overflow: hidden
+ transition: max-height 0.2s ease-out
+ margin: 0px
+ padding: 0px
+
+// Minimize swimlanes end https://www.w3schools.com/howto/howto_js_accordion.asp
+*/
+
.swimlane
// Even if this background color is the same as the body we can't leave it
// transparent, because that won't work during a swimlane drag.
@@ -25,22 +61,22 @@
cursor: grabbing
.swimlane-header-wrap
- display: flex;
- flex-direction: row;
- flex: 1 0 100%;
- background-color: #ccc;
+ display: flex
+ flex-direction: row
+ flex: 1 0 100%
+ background-color: #ccc
.swimlane-header
- font-size: 14px;
+ font-size: 14px
padding: 5px 5px
- font-weight: bold;
- min-height: 9px;
- width: 100%;
- overflow: hidden;
- -o-text-overflow: ellipsis;
- text-overflow: ellipsis;
- word-wrap: break-word;
- text-align: center;
+ font-weight: bold
+ min-height: 9px
+ width: 100%
+ overflow: hidden
+ -o-text-overflow: ellipsis
+ text-overflow: ellipsis
+ word-wrap: break-word
+ text-align: center
.swimlane-header-menu
position: absolute
@@ -50,6 +86,22 @@
margin-left: 5px
margin-right: 10px
+ .swimlane-header-handle
+ position: absolute
+ padding: 7px
+ top: 50%
+ transform: translateY(-50%)
+ left: 230px
+ font-size: 18px
+
+ .swimlane-header-miniscreen-handle
+ position: absolute
+ padding: 7px
+ top: 50%
+ transform: translateY(-50%)
+ left: 87vw
+ font-size: 24px
+
.list-group
height: 100%
diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade
index 946bdab1..50a80396 100644
--- a/client/components/users/userHeader.jade
+++ b/client/components/users/userHeader.jade
@@ -79,6 +79,11 @@ template(name="changeSettingsPopup")
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'}}
input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="0" max="99" onkeydown="return false")
diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js
index 36fb2020..1f0e3ef0 100644
--- a/client/components/users/userHeader.js
+++ b/client/components/users/userHeader.js
@@ -5,10 +5,22 @@ Template.headerUserBar.events({
Template.memberMenuPopup.helpers({
templatesBoardId() {
- return Meteor.user().getTemplatesBoardId();
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return Meteor.user().getTemplatesBoardId();
+ } else {
+ // No need to getTemplatesBoardId on public board
+ return false;
+ }
},
templatesBoardSlug() {
- return Meteor.user().getTemplatesBoardSlug();
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return Meteor.user().getTemplatesBoardSlug();
+ } else {
+ // No need to getTemplatesBoardSlug() on public board
+ return false;
+ }
},
});
@@ -161,17 +173,74 @@ Template.changeLanguagePopup.events({
});
Template.changeSettingsPopup.helpers({
+ showDesktopDragHandles() {
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).showDesktopDragHandles;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('showDesktopDragHandles')) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ },
hiddenSystemMessages() {
- return Meteor.user().hasHiddenSystemMessages();
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).hasHiddenSystemMessages;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('hasHiddenSystemMessages')) {
+ return true;
+ } else {
+ return false;
+ }
+ }
},
showCardsCountAt() {
- return Meteor.user().getLimitToShowCardsCount();
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return Meteor.user().getLimitToShowCardsCount();
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ return cookies.get('limitToShowCardsCount');
+ }
},
});
Template.changeSettingsPopup.events({
+ 'click .js-toggle-desktop-drag-handles'() {
+ currentUser = Meteor.user();
+ if (currentUser) {
+ Meteor.call('toggleDesktopDragHandles');
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('showDesktopDragHandles')) {
+ cookies.remove('showDesktopDragHandles');
+ } else {
+ cookies.set('showDesktopDragHandles', 'true');
+ }
+ }
+ },
'click .js-toggle-system-messages'() {
- Meteor.call('toggleSystemMessages');
+ currentUser = Meteor.user();
+ if (currentUser) {
+ Meteor.call('toggleSystemMessages');
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.has('hasHiddenSystemMessages')) {
+ cookies.remove('hasHiddenSystemMessages');
+ } else {
+ cookies.set('hasHiddenSystemMessages', 'true');
+ }
+ }
},
'click .js-apply-show-cards-at'(event, templateInstance) {
event.preventDefault();
@@ -180,7 +249,14 @@ Template.changeSettingsPopup.events({
10,
);
if (!isNaN(minLimit)) {
- Meteor.call('changeLimitToShowCardsCount', minLimit);
+ currentUser = Meteor.user();
+ if (currentUser) {
+ Meteor.call('changeLimitToShowCardsCount', minLimit);
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ cookies.set('limitToShowCardsCount', minLimit);
+ }
Popup.back();
}
},
diff --git a/client/lib/datepicker.js b/client/lib/datepicker.js
index eb5b60b8..8ad66c5f 100644
--- a/client/lib/datepicker.js
+++ b/client/lib/datepicker.js
@@ -3,10 +3,11 @@ DatePicker = BlazeComponent.extendComponent({
return 'datepicker';
},
- onCreated() {
+ onCreated(defaultTime = '1970-01-01 08:00:00') {
this.error = new ReactiveVar('');
this.card = this.data();
this.date = new ReactiveVar(moment.invalid());
+ this.defaultTime = defaultTime;
},
onRendered() {
@@ -21,7 +22,15 @@ DatePicker = BlazeComponent.extendComponent({
function(evt) {
this.find('#date').value = moment(evt.date).format('L');
this.error.set('');
- this.find('#time').focus();
+ const timeInput = this.find('#time');
+ timeInput.focus();
+ if (!timeInput.value) {
+ const currentHour = evt.date.getHours();
+ const defaultMoment = moment(
+ currentHour > 0 ? evt.date : this.defaultTime,
+ ); // default to 8:00 am local time
+ timeInput.value = defaultMoment.format('LT');
+ }
}.bind(this),
);
diff --git a/client/lib/filter.js b/client/lib/filter.js
index 1ca3a280..592eb4ab 100644
--- a/client/lib/filter.js
+++ b/client/lib/filter.js
@@ -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'],
@@ -468,7 +477,9 @@ Filter = {
return (
_.any(this._fields, fieldName => {
return this[fieldName]._isActive();
- }) || this.advanced._isActive()
+ }) ||
+ this.advanced._isActive() ||
+ this.lists._isActive()
);
},
@@ -533,6 +544,7 @@ Filter = {
const filter = this[fieldName];
filter.reset();
});
+ this.lists.reset();
this.advanced.reset();
this.resetExceptions();
},
diff --git a/client/lib/textComplete.js b/client/lib/textComplete.js
index 0261d7f6..8b6dc1f7 100644
--- a/client/lib/textComplete.js
+++ b/client/lib/textComplete.js
@@ -45,6 +45,7 @@ $.fn.escapeableTextComplete = function(strategies, options, ...otherArgs) {
});
},
});
+ return this;
};
EscapeActions.register('textcomplete', () => {}, () => dropdownMenuIsOpened, {
diff --git a/client/lib/utils.js b/client/lib/utils.js
index 81835929..c90dd749 100644
--- a/client/lib/utils.js
+++ b/client/lib/utils.js
@@ -1,10 +1,59 @@
Utils = {
+ setBoardView(view) {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ currentUser = Meteor.user();
+ if (currentUser) {
+ Meteor.user().setBoardView(view);
+ } else if (view === 'board-view-lists') {
+ cookies.set('boardView', 'board-view-lists'); //true
+ } else if (view === 'board-view-swimlanes') {
+ cookies.set('boardView', 'board-view-swimlanes'); //true
+ //} else if (view === 'board-view-collapse') {
+ // cookies.set('boardView', 'board-view-swimlane'); //true
+ // cookies.set('collapseSwimlane', 'true'); //true
+ } else if (view === 'board-view-cal') {
+ cookies.set('boardView', 'board-view-cal'); //true
+ }
+ },
+
+ unsetBoardView() {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ cookies.remove('boardView');
+ cookies.remove('collapseSwimlane');
+ },
+
+ boardView() {
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return (currentUser.profile || {}).boardView;
+ } else {
+ import { Cookies } from 'meteor/ostrio:cookies';
+ const cookies = new Cookies();
+ if (cookies.get('boardView') === 'board-view-lists') {
+ return 'board-view-lists';
+ } else if (
+ cookies.get('boardView') === 'board-view-swimlanes'
+ //&& !cookies.has('collapseSwimlane')
+ ) {
+ return 'board-view-swimlanes';
+ //} else if (cookies.has('collapseSwimlane')) {
+ // return 'board-view-swimlanes';
+ } else if (cookies.get('boardView') === 'board-view-cal') {
+ return 'board-view-cal';
+ } else {
+ return false;
+ }
+ }
+ },
+
// XXX We should remove these two methods
goBoardId(_id) {
const board = Boards.findOne(_id);
return (
- board &&
- FlowRouter.go('board', {
+ board
+ && FlowRouter.go('board', {
id: board._id,
slug: board.slug,
})
@@ -15,15 +64,14 @@ Utils = {
const card = Cards.findOne(_id);
const board = Boards.findOne(card.boardId);
return (
- board &&
- FlowRouter.go('card', {
+ board
+ && FlowRouter.go('card', {
cardId: card._id,
boardId: board._id,
slug: board.slug,
})
);
},
-
MAX_IMAGE_PIXEL: Meteor.settings.public.MAX_IMAGE_PIXEL,
COMPRESS_RATIO: Meteor.settings.public.IMAGE_COMPRESS_RATIO,
processUploadedAttachment(card, fileObj, callback) {
@@ -188,8 +236,8 @@ Utils = {
};
if (
- 'ontouchstart' in window ||
- (window.DocumentTouch && document instanceof window.DocumentTouch)
+ 'ontouchstart' in window
+ || (window.DocumentTouch && document instanceof window.DocumentTouch)
) {
return true;
}
@@ -210,8 +258,8 @@ Utils = {
calculateTouchDistance(touchA, touchB) {
return Math.sqrt(
- Math.pow(touchA.screenX - touchB.screenX, 2) +
- Math.pow(touchA.screenY - touchB.screenY, 2),
+ Math.pow(touchA.screenX - touchB.screenX, 2)
+ + Math.pow(touchA.screenY - touchB.screenY, 2),
);
},
@@ -228,9 +276,9 @@ Utils = {
});
$(document).on('touchend', selector, function(e) {
if (
- touchStart &&
- lastTouch &&
- Utils.calculateTouchDistance(touchStart, lastTouch) <= 20
+ touchStart
+ && lastTouch
+ && Utils.calculateTouchDistance(touchStart, lastTouch) <= 20
) {
e.preventDefault();
const clickEvent = document.createEvent('MouseEvents');