This commit is contained in:
John Supplee 2021-12-19 12:18:05 +02:00
commit 241c3ed8ae
332 changed files with 18869 additions and 18221 deletions

View file

@ -12,7 +12,7 @@ template(name="boardActivities")
+activity(activity=activityData card=card mode=mode)
template(name="cardActivities")
each activityData in currentCard.activities
each activityData in activities
+activity(activity=activityData card=card mode=mode)
template(name="editOrDeleteComment")
@ -21,6 +21,26 @@ template(name="editOrDeleteComment")
= ' - '
a.js-delete-comment {{_ "delete"}}
template(name="deleteCommentPopup")
p {{_ "comment-delete"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="commentReactions")
.reactions
each reaction in reactions
span.reaction(class="{{#if isSelected reaction.userIds}}selected{{/if}}" data-codepoint="#{reaction.reactionCodepoint}" title="{{userNames reaction.userIds}}")
span.reaction-codepoint !{reaction.reactionCodepoint}
span.reaction-count #{reaction.userIds.length}
if (currentUser.isBoardMember)
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
i.fa.fa-smile-o
i.fa.fa-plus
template(name="addReactionPopup")
.reactions-popup
each codepoint in codepoints
span.add-comment-reaction(data-codepoint="#{codepoint}") !{codepoint}
template(name="activity")
.activity
+userAvatar(userId=activity.user._id)
@ -120,10 +140,12 @@ template(name="activity")
= activity.comment.text
.edit-controls
button.primary(type="submit") {{_ 'edit'}}
.fa.fa-times-thin.js-close-inlined-form
else
.activity-comment
+viewer
= activity.comment.text
+commentReactions(reactions=activity.comment.reactions commentId=activity.comment._id)
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
if($eq currentUser._id activity.comment.userId)
+editOrDeleteComment
@ -150,20 +172,20 @@ template(name="activity")
if($eq activity.activityType 'a-startAt')
| {{{_ 'activity-startDate' (sanitize startDate) cardLink}}}.
if($eq activity.activityType 'a-dueAt')
| {{{_ 'activity-dueDate' (sanitize dueDate) cardLink}}}.
if($eq activity.activityType 'a-endAt')
| {{{_ 'activity-endDate' (sanitize endDate) cardLink}}}.
if($eq mode 'board')
if($eq activity.activityType 'a-receivedAt')
| {{{_ 'activity-receivedDate' (sanitize receivedDate) cardLink}}}.
if($eq activity.activityType 'a-startAt')
| {{{_ 'activity-startDate' (sanitize startDate) cardLink}}}.
if($eq activity.activityType 'a-dueAt')
| {{{_ 'activity-dueDate' (sanitize dueDate) cardLink}}}.

View file

@ -13,14 +13,14 @@ BlazeComponent.extendComponent({
this.autorun(() => {
let mode = this.data().mode;
const capitalizedMode = Utils.capitalize(mode);
let thisId, searchId;
let searchId;
if (mode === 'linkedcard' || mode === 'linkedboard') {
thisId = Session.get('currentCard');
searchId = Cards.findOne({ _id: thisId }).linkedId;
searchId = Utils.getCurrentCard().linkedId;
mode = mode.replace('linked', '');
} else if (mode === 'card') {
searchId = Utils.getCurrentCardId();
} else {
thisId = Session.get(`current${capitalizedMode}`);
searchId = thisId;
searchId = Session.get(`current${capitalizedMode}`);
}
const limit = this.page.get() * activitiesPerPage;
const user = Meteor.user();
@ -54,6 +54,13 @@ BlazeComponent.extendComponent({
},
}).register('activities');
Template.activities.helpers({
activities() {
const ret = this.card.activities();
return ret;
},
});
BlazeComponent.extendComponent({
checkItem() {
const checkItemId = this.currentData().activity.checklistItemId;
@ -113,8 +120,10 @@ BlazeComponent.extendComponent({
).getLabelById(lastLabelId);
if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
return lastLabel.color;
} else {
} else if (lastLabel.name !== undefined && lastLabel.name !== '') {
return lastLabel.name;
} else {
return null;
}
},
@ -211,10 +220,11 @@ BlazeComponent.extendComponent({
return [
{
// XXX We should use Popup.afterConfirmation here
'click .js-delete-comment'() {
const commentId = this.currentData().activity.commentId;
'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => {
const commentId = this.data().activity.commentId;
CardComments.remove(commentId);
},
Popup.back();
}),
'submit .js-edit-comment'(evt) {
evt.preventDefault();
const commentText = this.currentComponent()
@ -240,6 +250,60 @@ Template.activity.helpers({
},
});
Template.commentReactions.events({
'click .reaction'(event) {
if (Meteor.user().isBoardMember()) {
const codepoint = event.currentTarget.dataset['codepoint'];
const commentId = Template.instance().data.commentId;
const cardComment = CardComments.findOne({_id: commentId});
cardComment.toggleReaction(codepoint);
}
},
'click .open-comment-reaction-popup': Popup.open('addReaction'),
})
Template.addReactionPopup.events({
'click .add-comment-reaction'(event) {
if (Meteor.user().isBoardMember()) {
const codepoint = event.currentTarget.dataset['codepoint'];
const commentId = Template.instance().data.commentId;
const cardComment = CardComments.findOne({_id: commentId});
cardComment.toggleReaction(codepoint);
}
Popup.back();
},
})
Template.addReactionPopup.helpers({
codepoints() {
// Starting set of unicode codepoints as comment reactions
return [
'👍',
'👎',
'👀',
'✅',
'❌',
'🙏',
'👏',
'🎉',
'🚀',
'😊',
'🤔',
'😔'];
}
})
Template.commentReactions.helpers({
isSelected(userIds) {
return userIds.includes(Meteor.user()._id);
},
userNames(userIds) {
return Users.find({_id: {$in: userIds}})
.map(user => user.profile.fullname)
.join(', ');
}
})
function createCardLink(card) {
if (!card) return '';
return (

View file

@ -5,6 +5,20 @@
display: flex
justify-content:space-between
.reactions-popup
.add-comment-reaction
display: inline-block
cursor: pointer
border-radius: 5px
font-size: 22px
text-align: center
line-height: 30px
width: 40px
&:hover {
background-color: #b0c4de
}
.activities
clear: both
@ -18,7 +32,7 @@
height: @width
.activity-member
font-weight: 700
font-weight: 700
.activity-desc
word-wrap: break-word
@ -39,6 +53,45 @@
margin-top: 5px
padding: 5px
.reactions
display: flex
margin-top: 5px
gap: 5px
.open-comment-reaction-popup
display: flex
align-items: center
text-decoration: none
height: 24px;
i.fa.fa-smile-o
font-size: 17px
font-weight: 500
margin-left: 2px
i.fa.fa-plus
font-size: 8px;
margin-top: -7px;
margin-left: 1px;
.reaction
cursor: pointer
border: 1px solid grey
border-radius: 15px
display: flex
padding: 2px 5px
&.selected {
background-color: #b0c4de
}
&:hover {
background-color: #b0c4de
}
.reaction-count
font-size: 12px
.activity-checklist
display: block
border-radius: 3px

View file

@ -64,7 +64,7 @@ function resetCommentInput(input) {
// Tracker.autorun to register the component dependencies, and re-run when these
// dependencies are invalidated. A better component API would remove this hack.
Tracker.autorun(() => {
Session.get('currentCard');
Utils.getCurrentCardId();
Tracker.afterFlush(() => {
autosize.update($('.js-new-comment-input'));
});
@ -75,7 +75,7 @@ EscapeActions.register(
() => {
const draftKey = {
fieldName: 'cardComment',
docId: Session.get('currentCard'),
docId: Utils.getCurrentCardId(),
};
const commentInput = $('.js-new-comment-input');
const draft = commentInput.val().trim();

View file

@ -31,7 +31,6 @@
background-color: #fff
border: 0
box-shadow: 0 1px 2px rgba(0, 0, 0, .23)
color: #8c8c8c
height: 36px
margin: 4px 4px 6px 0
padding: 9px 11px

View file

@ -34,7 +34,7 @@ BlazeComponent.extendComponent({
Utils.goBoardId(board._id);
},
'click .js-delete-board': Popup.afterConfirm('boardDelete', function() {
Popup.close();
Popup.back();
const isSandstorm =
Meteor.settings &&
Meteor.settings.public &&

View file

@ -13,26 +13,29 @@ template(name="board")
+spinner
template(name="boardBody")
.board-wrapper(class=currentBoard.colorClass)
+sidebar
.board-canvas.js-swimlanes(
class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}"
class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}"
class="{{#if draggingActive.get}}is-dragging-active{{/if}}")
if showOverlay.get
.board-overlay
if currentBoard.isTemplatesBoard
each currentBoard.swimlanes
+swimlane(this)
else if isViewSwimlanes
each currentBoard.swimlanes
+swimlane(this)
else if isViewLists
+listsGroup(currentBoard)
else if isViewCalendar
+calendarView
else
+listsGroup(currentBoard)
if notDisplayThisBoard
| {{_ 'tableVisibilityMode-allowPrivateOnly'}}
else
.board-wrapper(class=currentBoard.colorClass)
+sidebar
.board-canvas.js-swimlanes(
class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}"
class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}"
class="{{#if draggingActive.get}}is-dragging-active{{/if}}")
if showOverlay.get
.board-overlay
if currentBoard.isTemplatesBoard
each currentBoard.swimlanes
+swimlane(this)
else if isViewSwimlanes
each currentBoard.swimlanes
+swimlane(this)
else if isViewLists
+listsGroup(currentBoard)
else if isViewCalendar
+calendarView
else
+listsGroup(currentBoard)
template(name="calendarView")
if isViewCalendar

View file

@ -23,7 +23,7 @@ BlazeComponent.extendComponent({
},
onlyShowCurrentCard() {
return Utils.isMiniScreen() && Session.get('currentCard');
return Utils.isMiniScreen() && Utils.getCurrentCardId(true);
},
goHome() {
@ -33,6 +33,7 @@ BlazeComponent.extendComponent({
BlazeComponent.extendComponent({
onCreated() {
Meteor.subscribe('tableVisibilityModeSettings');
this.showOverlay = new ReactiveVar(false);
this.draggingActive = new ReactiveVar(false);
this._isDragging = false;
@ -190,21 +191,11 @@ BlazeComponent.extendComponent({
});
this.autorun(() => {
let showDesktopDragHandles = false;
currentUser = Meteor.user();
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
showDesktopDragHandles = true;
} else {
showDesktopDragHandles = false;
}
if (Utils.isMiniScreen() || showDesktopDragHandles) {
if (Utils.isMiniScreenOrShowDesktopDragHandles()) {
$swimlanesDom.sortable({
handle: '.js-swimlane-header-handle',
});
} else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
} else {
$swimlanesDom.sortable({
handle: '.swimlane-header',
});
@ -215,7 +206,7 @@ BlazeComponent.extendComponent({
$swimlanesDom.sortable(
'option',
'disabled',
!Meteor.user().isBoardAdmin(),
!Meteor.user() || !Meteor.user().isBoardAdmin(),
);
});
@ -235,6 +226,16 @@ BlazeComponent.extendComponent({
}
},
notDisplayThisBoard(){
let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly');
let currentBoard = Boards.findOne(Session.get('currentBoard'));
if(allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue && currentBoard.permission == 'public'){
return true;
}
return false;
},
isViewSwimlanes() {
currentUser = Meteor.user();
if (currentUser) {
@ -325,6 +326,7 @@ BlazeComponent.extendComponent({
defaultView: 'agendaDay',
editable: true,
timezone: 'local',
weekNumbers: true,
header: {
left: 'title today prev,next',
center:

View file

@ -876,7 +876,7 @@ setBoardClear(color1,color2)
padding: 10px
top: 0
.list-header .list-header-plus-icon
.list-header .list-header-plus-top
color: #a6a6a6
.list-body
@ -956,17 +956,24 @@ setBoardClear(color1,color2)
/* Card Details */
.card-details
position: absolute
top: 30px
left: calc(50% - 384px)
width: 768px
max-height: calc(100% - 60px)
background-color: #454545
color: #cccccc
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)
border: 1px solid #111111
z-index: 100 !important
@media screen and (max-width: 800px)
.card-details
width: 98%
@media screen and (min-width: 801px)
.card-details
position: absolute
top: 30px
left: calc(50% - 384px)
width: 768px
max-height: calc(100% - 60px)
.card-details
scrollbar-width: thin
scrollbar-color: #343434 #999999

View file

@ -80,6 +80,12 @@ template(name="boardHeaderBar")
if $eq watchLevel "muted"
i.fa.fa-bell-slash
span {{_ watchLevel}}
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
i.fa.fa-sort
span {{#if isSortActive }}{{_ 'Sort is on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
if isSortActive
a.board-header-btn-close.js-sort-reset(title="Remove Sort")
i.fa.fa-times-thin
else
a.board-header-btn.js-log-in(
@ -147,14 +153,15 @@ template(name="boardVisibilityList")
if visibilityCheck
i.fa.fa-check
span.sub-name {{_ 'private-desc'}}
li
with "public"
a.js-select-visibility
i.fa.fa-globe.colorful
| {{_ 'public'}}
if visibilityCheck
i.fa.fa-check
span.sub-name {{_ 'public-desc'}}
if notAllowPrivateVisibilityOnly
li
with "public"
a.js-select-visibility
i.fa.fa-globe.colorful
| {{_ 'public'}}
if visibilityCheck
i.fa.fa-check
span.sub-name {{_ 'public-desc'}}
template(name="boardChangeVisibilityPopup")
+boardVisibilityList

View file

@ -7,11 +7,11 @@ Template.boardMenuPopup.events({
'click .js-rename-board': Popup.open('boardChangeTitle'),
'click .js-custom-fields'() {
Sidebar.setView('customFields');
Popup.close();
Popup.back();
},
'click .js-open-archives'() {
Sidebar.setView('archives');
Popup.close();
Popup.back();
},
'click .js-change-board-color': Popup.open('boardChangeColor'),
'click .js-change-language': Popup.open('changeLanguage'),
@ -24,7 +24,7 @@ Template.boardMenuPopup.events({
}),
'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
Popup.close();
Popup.back();
Boards.remove(currentBoard._id);
FlowRouter.go('home');
}),
@ -47,7 +47,7 @@ Template.boardChangeTitlePopup.events({
if (newTitle) {
this.rename(newTitle);
this.setDescription(newDesc);
Popup.close();
Popup.back();
}
event.preventDefault();
},
@ -136,7 +136,7 @@ BlazeComponent.extendComponent({
Sidebar.setView('search');
},
'click .js-multiselection-activate'() {
const currentCard = Session.get('currentCard');
const currentCard = Utils.getCurrentCardId();
MultiSelection.activate();
if (currentCard) {
MultiSelection.add(currentCard);
@ -173,15 +173,15 @@ Template.boardHeaderBar.helpers({
Template.boardChangeViewPopup.events({
'click .js-open-lists-view'() {
Utils.setBoardView('board-view-lists');
Popup.close();
Popup.back();
},
'click .js-open-swimlanes-view'() {
Utils.setBoardView('board-view-swimlanes');
Popup.close();
Popup.back();
},
'click .js-open-cal-view'() {
Utils.setBoardView('board-view-cal');
Popup.close();
Popup.back();
},
});
@ -194,6 +194,11 @@ const CreateBoard = BlazeComponent.extendComponent({
this.visibilityMenuIsOpen = new ReactiveVar(false);
this.visibility = new ReactiveVar('private');
this.boardId = new ReactiveVar('');
Meteor.subscribe('tableVisibilityModeSettings');
},
notAllowPrivateVisibilityOnly(){
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
},
visibilityCheck() {
@ -310,6 +315,9 @@ const CreateBoard = BlazeComponent.extendComponent({
}.register('headerBarCreateBoardPopup'));
BlazeComponent.extendComponent({
notAllowPrivateVisibilityOnly(){
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
},
visibilityCheck() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
return this.currentData() === currentBoard.permission;
@ -319,7 +327,7 @@ BlazeComponent.extendComponent({
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const visibility = this.currentData();
currentBoard.setVisibility(visibility);
Popup.close();
Popup.back();
},
events() {
@ -352,7 +360,7 @@ BlazeComponent.extendComponent({
Session.get('currentBoard'),
level,
(err, ret) => {
if (!err && ret) Popup.close();
if (!err && ret) Popup.back();
},
);
},
@ -424,7 +432,7 @@ BlazeComponent.extendComponent({
const direction = down ? -1 : 1;
this.setSortBy([sortby, direction]);
if (Utils.isMiniScreen) {
Popup.close();
Popup.back();
}
},
},
@ -443,7 +451,7 @@ BlazeComponent.extendComponent({
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('due-date'));
Popup.close();
Popup.back();
},
'click .js-sort-title'() {
const sortBy = {
@ -451,7 +459,7 @@ BlazeComponent.extendComponent({
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('title'));
Popup.close();
Popup.back();
},
'click .js-sort-created-asc'() {
const sortBy = {
@ -459,7 +467,7 @@ BlazeComponent.extendComponent({
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-newest-first'));
Popup.close();
Popup.back();
},
'click .js-sort-created-desc'() {
const sortBy = {
@ -467,7 +475,7 @@ BlazeComponent.extendComponent({
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-oldest-first'));
Popup.close();
Popup.back();
},
},
];

View file

@ -1,11 +1,32 @@
template(name="boardList")
.wrapper
ul.AllBoardTeamsOrgs
li.AllBoardTeams
if userHasTeams
select.js-AllBoardTeams#jsAllBoardTeams("multiple")
option(value="-1") {{_ 'teams'}} :
each teamsDatas
option(value="{{teamId}}") {{_ teamDisplayName}}
li.AllBoardOrgs
if userHasOrgs
select.js-AllBoardOrgs#jsAllBoardOrgs("multiple")
option(value="-1") {{_ 'organizations'}} :
each orgsDatas
option(value="{{orgId}}") {{_ orgDisplayName}}
li.AllBoardBtns
div.AllBoardButtonsContainer
if userHasOrgsOrTeams
i.fa.fa-filter
input#filterBtn(type="button" value="{{_ 'filter'}}")
input#resetBtn(type="button" value="{{_ 'filter-clear'}}")
ul.board-list.clearfix.js-boards
li.js-add-board
a.board-list-item.label(title="{{_ 'add-board'}}")
| {{_ 'add-board'}}
each boards
li(class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board
li(class="{{_id}}" class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board
if isInvited
.board-list-item
span.details
@ -33,11 +54,11 @@ template(name="boardList")
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
if isMiniScreen
if isMiniScreenOrShowDesktopDragHandles
i.fa.board-handle(
class="fa-arrows"
title="{{_ 'Drag board'}}")
unless isMiniScreen
else
if isSandstorm
i.fa.js-clone-board(
class="fa-clone"
@ -75,11 +96,11 @@ template(name="boardList")
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
if isMiniScreen
if isMiniScreenOrShowDesktopDragHandles
i.fa.board-handle(
class="fa-arrows"
title="{{_ 'Drag board'}}")
unless isMiniScreen
else
if isSandstorm
i.fa.js-clone-board(
class="fa-clone"

View file

@ -1,5 +1,4 @@
const subManager = new SubsManager();
const { calculateIndex, enableClickOnTouch } = Utils;
Template.boardListHeaderBar.events({
'click .js-open-archived-board'() {
@ -22,6 +21,7 @@ Template.boardListHeaderBar.helpers({
BlazeComponent.extendComponent({
onCreated() {
Meteor.subscribe('setting');
Meteor.subscribe('tableVisibilityModeSettings');
let currUser = Meteor.user();
let userLanguage;
if(currUser && currUser.profile){
@ -55,7 +55,7 @@ BlazeComponent.extendComponent({
// of the previous and the following card -- if any.
const prevBoardDom = ui.item.prev('.js-board').get(0);
const nextBoardBom = ui.item.next('.js-board').get(0);
const sortIndex = calculateIndex(prevBoardDom, nextBoardBom, 1);
const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardBom, 1);
const boardDomElement = ui.item.get(0);
const board = Blaze.getData(boardDomElement);
@ -72,21 +72,56 @@ BlazeComponent.extendComponent({
},
});
// ugly touch event hotfix
enableClickOnTouch(itemsSelector);
// Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => {
if (Utils.isMiniScreen()) {
if (Utils.isMiniScreenOrShowDesktopDragHandles()) {
$boards.sortable({
handle: '.board-handle',
});
}
});
},
userHasTeams(){
if(Meteor.user() != null && Meteor.user().teams && Meteor.user().teams.length > 0)
return true;
else
return false;
},
teamsDatas() {
if(Meteor.user().teams)
return Meteor.user().teams.sort((a, b) => a.teamDisplayName.localeCompare(b.teamDisplayName));
else
return [];
},
userHasOrgs(){
if(Meteor.user() != null && Meteor.user().orgs && Meteor.user().orgs.length > 0)
return true;
else
return false;
},
orgsDatas() {
if(Meteor.user().orgs)
return Meteor.user().orgs.sort((a, b) => a.orgDisplayName.localeCompare(b.orgDisplayName));
else
return [];
},
userHasOrgsOrTeams(){
let boolUserHasOrgs;
if(Meteor.user() != null && Meteor.user().orgs && Meteor.user().orgs.length > 0)
boolUserHasOrgs = true;
else
boolUserHasOrgs = false;
let boolUserHasTeams;
if(Meteor.user() != null && Meteor.user().teams && Meteor.user().teams.length > 0)
boolUserHasTeams = true;
else
boolUserHasTeams = false;
return (boolUserHasOrgs || boolUserHasTeams);
},
boards() {
const query = {
let query = {
//archived: false,
////type: { $in: ['board','template-container'] },
//type: 'board',
@ -96,9 +131,15 @@ BlazeComponent.extendComponent({
{ $or:[] }
]
};
let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly');
if (FlowRouter.getRouteName() === 'home'){
query.$and[2].$or.push({'members.userId': Meteor.userId()});
if(allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue){
query.$and.push({'permission': 'private'});
}
const currUser = Users.findOne(Meteor.userId());
// const currUser = Users.findOne(Meteor.userId(), {
@ -108,7 +149,7 @@ BlazeComponent.extendComponent({
// },
// });
let orgIdsUserBelongs = currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : '';
let orgIdsUserBelongs = currUser !== undefined && currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : '';
if(orgIdsUserBelongs && orgIdsUserBelongs != ''){
let orgsIds = orgIdsUserBelongs.split(',');
// for(let i = 0; i < orgsIds.length; i++){
@ -119,7 +160,7 @@ BlazeComponent.extendComponent({
query.$and[2].$or.push({'orgs.orgId': {$in : orgsIds}});
}
let teamIdsUserBelongs = currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : '';
let teamIdsUserBelongs = currUser !== undefined && currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : '';
if(teamIdsUserBelongs && teamIdsUserBelongs != ''){
let teamsIds = teamIdsUserBelongs.split(',');
// for(let i = 0; i < teamsIds.length; i++){
@ -129,10 +170,17 @@ BlazeComponent.extendComponent({
query.$and[2].$or.push({'teams.teamId': {$in : teamsIds}});
}
}
else query.permission = 'public';
else if(allowPrivateVisibilityOnly !== undefined && !allowPrivateVisibilityOnly.booleanValue){
query = {
archived: false,
//type: { $in: ['board','template-container'] },
type: 'board',
permission: 'public',
};
}
return Boards.find(query, {
//sort: { sort: 1 /* boards default sorting */ },
sort: { sort: 1 /* boards default sorting */ },
});
},
isStarred() {
@ -206,6 +254,74 @@ BlazeComponent.extendComponent({
}
});
},
'click #resetBtn'(event){
let allBoards = document.getElementsByClassName("js-board");
let currBoard;
for(let i=0; i < allBoards.length; i++){
currBoard = allBoards[i];
currBoard.style.display = "block";
}
},
'click #filterBtn'(event) {
event.preventDefault();
let selectedTeams = document.querySelectorAll('#jsAllBoardTeams option:checked');
let selectedTeamsValues = Array.from(selectedTeams).map(function(elt){return elt.value});
let index = selectedTeamsValues.indexOf("-1");
if (index > -1) {
selectedTeamsValues.splice(index, 1);
}
let selectedOrgs = document.querySelectorAll('#jsAllBoardOrgs option:checked');
let selectedOrgsValues = Array.from(selectedOrgs).map(function(elt){return elt.value});
index = selectedOrgsValues.indexOf("-1");
if (index > -1) {
selectedOrgsValues.splice(index, 1);
}
if(selectedTeamsValues.length > 0 || selectedOrgsValues.length > 0){
const query = {
$and: [
{ archived: false },
{ type: 'board' },
{ $or:[] }
]
};
if(selectedTeamsValues.length > 0)
{
query.$and[2].$or.push({'teams.teamId': {$in : selectedTeamsValues}});
}
if(selectedOrgsValues.length > 0)
{
query.$and[2].$or.push({'orgs.orgId': {$in : selectedOrgsValues}});
}
let filteredBoards = Boards.find(query, {}).fetch();
let allBoards = document.getElementsByClassName("js-board");
let currBoard;
if(filteredBoards.length > 0){
let currBoardId;
let found;
for(let i=0; i < allBoards.length; i++){
currBoard = allBoards[i];
currBoardId = currBoard.classList[0];
found = filteredBoards.find(function(board){
return board._id == currBoardId;
});
if(found !== undefined)
currBoard.style.display = "block";
else
currBoard.style.display = "none";
}
}
else{
for(let i=0; i < allBoards.length; i++){
currBoard = allBoards[i];
currBoard.style.display = "none";
}
}
}
},
},
];
},

View file

@ -229,3 +229,25 @@ $spaceBetweenTiles = 16px
transform: translateY(-50%)
right: 10px
font-size: 24px
.AllBoardTeamsOrgs
list-style-type: none;
overflow: hidden;
.AllBoardTeams,.AllBoardOrgs,.AllBoardBtns
float: left;
.js-AllBoardOrgs
margin-left: 16px;
.AllBoardTeams
margin-left : 16px;
.AllBoardButtonsContainer
margin: 16px;
#filterBtn,#resetBtn
display: inline;
.js-board
display: block;

View file

@ -26,12 +26,25 @@ template(name="attachmentsGalery")
if isUploaded
if isImage
img.attachment-thumbnail-img(src="{{url}}")
else if($eq extension 'mp3')
video(width="100%" height="100%" controls="true")
source(src="{{url}}" type="audio/mpeg")
else if($eq extension 'ogg')
video(width="100%" height="100%" controls="true")
source(src="{{url}}" type="video/ogg")
else if($eq extension 'webm')
video(width="100%" height="100%" controls="true")
source(src="{{url}}" type="video/webm")
else if($eq extension 'mp4')
video(width="100%" height="100%" controls="true")
source(src="{{url}}" type="video/mp4")
else
span.attachment-thumbnail-ext= extension
else
+spinner
p.attachment-details
= name
span.file-size ({{fileSize size}} KB)
span.attachment-details-actions
a.js-download(href="{{url download=true}}")
i.fa.fa-download

View file

@ -4,7 +4,7 @@ Template.attachmentsGalery.events({
'attachmentDelete',
function() {
Attachments.remove(this._id);
Popup.close();
Popup.back();
},
),
// If we let this event bubble, FlowRouter will handle it and empty the page
@ -49,11 +49,14 @@ Template.attachmentsGalery.helpers({
isBoardAdmin() {
return Meteor.user().isBoardAdmin();
},
fileSize(size) {
return Math.round(size / 1024);
},
});
Template.previewAttachedImagePopup.events({
'click .js-large-image-clicked'() {
Popup.close();
Popup.back();
},
});
@ -65,7 +68,7 @@ Template.cardAttachmentsPopup.events({
if (attachment && attachment._id && attachment.isImage()) {
card.setCover(attachment._id);
}
Popup.close();
Popup.back();
});
};
@ -174,7 +177,7 @@ Template.previewClipboardImagePopup.events({
pastedResults = null;
$(document.body).pasteImageReader(() => {});
Popup.close();
Popup.back();
}
},
});

View file

@ -9,7 +9,7 @@
margin: 10px 1% 0
text-align: center
border-radius: 3px
overflow: hidden
overflow: auto
background: darken(white, 7%)
min-height: 120px

View file

@ -63,7 +63,7 @@ template(name="cardCustomField-checkbox")
template(name="cardCustomField-currency")
if canModifyCard
+inlinedForm(classNames="js-card-customfield-currency")
input(type="text" value=data.value)
input(type="text" value=data.value autofocus)
.edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
@ -79,18 +79,22 @@ template(name="cardCustomField-currency")
template(name="cardCustomField-date")
if canModifyCard
a.js-edit-date(title="{{showTitle}}" class="{{classes}}")
if value
div.card-date
time(datetime="{{showISODate}}")
| {{showDate}}
else
| {{_ 'edit'}}
else
a.js-edit-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
if value
div.card-date
time(datetime="{{showISODate}}")
| {{showDate}}
div.card-date
time(datetime="{{showISODate}}")
| {{showDate}}
b
| {{showWeek}}
else
| {{_ 'edit'}}
else
if value
div.card-date
time(datetime="{{showISODate}}")
| {{showDate}}
b
| {{showWeek}}
template(name="cardCustomField-dropdown")
if canModifyCard

View file

@ -3,7 +3,7 @@ import Cards from '/models/cards';
Template.cardCustomFieldsPopup.helpers({
hasCustomField() {
const card = Cards.findOne(Session.get('currentCard'));
const card = Utils.getCurrentCard();
const customFieldId = this._id;
return card.customFieldIndex(customFieldId) > -1;
},
@ -11,7 +11,7 @@ Template.cardCustomFieldsPopup.helpers({
Template.cardCustomFieldsPopup.events({
'click .js-select-field'(event) {
const card = Cards.findOne(Session.get('currentCard'));
const card = Utils.getCurrentCard();
const customFieldId = this._id;
card.toggleCustomField(customFieldId);
event.preventDefault();
@ -31,7 +31,7 @@ const CardCustomField = BlazeComponent.extendComponent({
onCreated() {
const self = this;
self.card = Cards.findOne(Session.get('currentCard'));
self.card = Utils.getCurrentCard();
self.customFieldId = this.data()._id;
},
@ -149,6 +149,10 @@ CardCustomField.register('cardCustomField');
});
}
showWeek() {
return this.date.get().week().toString();
}
showDate() {
// this will start working once mquandalle:moment
// is updated to at least moment.js 2.10.5
@ -190,7 +194,7 @@ CardCustomField.register('cardCustomField');
onCreated() {
super.onCreated();
const self = this;
self.card = Cards.findOne(Session.get('currentCard'));
self.card = Utils.getCurrentCard();
self.customFieldId = this.data()._id;
this.data().value && this.date.set(moment(this.data().value));
}
@ -267,7 +271,7 @@ CardCustomField.register('cardCustomField');
{
'submit .js-card-customfield-stringtemplate'(event) {
event.preventDefault();
const items = this.getItems();
const items = this.stringtemplateItems.get();
this.card.setCustomField(this.customFieldId, items);
},

View file

@ -1,14 +1,20 @@
template(name="dateBadge")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}}" class="{{classes}}")
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}}" class="{{classes}}")
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
b
| {{showWeek}}
template(name="dateCustomField")
a(title="{{showTitle}}" class="{{classes}}")
a(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
b
| {{showWeek}}

View file

@ -24,7 +24,7 @@ Template.dateBadge.helpers({
}
_deleteDate() {
this.card.setReceived(null);
this.card.unsetReceived();
}
}.register('editCardReceivedDatePopup'));
@ -50,7 +50,7 @@ Template.dateBadge.helpers({
}
_deleteDate() {
this.card.setStart(null);
this.card.unsetStart();
}
}.register('editCardStartDatePopup'));
@ -73,7 +73,7 @@ Template.dateBadge.helpers({
}
_deleteDate() {
this.card.setDue(null);
this.card.unsetDue();
}
}.register('editCardDueDatePopup'));
@ -96,7 +96,7 @@ Template.dateBadge.helpers({
}
_deleteDate() {
this.card.setEnd(null);
this.card.unsetEnd();
}
}.register('editCardEndDatePopup'));
@ -115,6 +115,10 @@ const CardDate = BlazeComponent.extendComponent({
}, 60000);
},
showWeek() {
return this.date.get().week().toString();
},
showDate() {
// this will start working once mquandalle:moment
// is updated to at least moment.js 2.10.5
@ -284,12 +288,25 @@ class CardCustomFieldDate extends CardDate {
});
}
classes() {
return 'customfield-date';
showWeek() {
return this.date.get().week().toString();
}
showDate() {
// this will start working once mquandalle:moment
// is updated to at least moment.js 2.10.5
// until then, the date is displayed in the "L" format
return this.date.get().calendar(null, {
sameElse: 'llll',
});
}
showTitle() {
return '';
return `${this.date.get().format('LLLL')}`;
}
classes() {
return 'customfield-date';
}
events() {

View file

@ -25,7 +25,10 @@ BlazeComponent.extendComponent({
// Pressing Ctrl+Enter should submit the form
'keydown form textarea'(evt) {
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
this.find('button[type=submit]').click();
const submitButton = this.find('button[type=submit]');
if (submitButton) {
submitButton.click();
}
}
},
},

View file

@ -1,27 +1,40 @@
template(name="cardDetailsPopup")
+cardDetails(popupCard)
template(name="cardDetails")
section.card-details.js-card-details(class='{{#if cardMaximized}}card-details-maximized{{/if}}'): .card-details-canvas
section.card-details.js-card-details(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}'): .card-details-canvas
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
+inlinedForm(classNames="js-card-details-title")
+editCardTitleForm
else
unless isMiniScreen
a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
unless cardMaximized
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
if cardMaximized
a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
unless isPopup
a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
if cardMaximized
a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
else
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
if currentUser.isBoardMember
a.fa.fa-navicon.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
input.inline-input(type="text" id="cardURL_copy" value="{{ originRelativeUrl }}")
a.fa.fa-link.card-copy-button.js-copy-link(
id="cardURL_copy"
class="fa-link"
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
if isMiniScreen
a.fa.fa-times-thin.close-card-details-mobile-web.js-close-card-details(title="{{_ 'close-card'}}")
span.copied-tooltip {{_ 'copied'}}
else
unless isPopup
a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
if currentUser.isBoardMember
a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
a.fa.fa-link.card-copy-mobile-button
a.fa.fa-link.card-copy-mobile-button.js-copy-link(
id="cardURL_copy"
class="fa-link"
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
span.copied-tooltip {{_ 'copied'}}
h2.card-details-title.js-card-title(
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
+viewer
@ -66,8 +79,10 @@ template(name="cardDetails")
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
i.fa.fa-plus
if currentBoard.allowsReceivedDate
if currentBoard.hasAnyAllowsDate
hr
if currentBoard.allowsReceivedDate
.card-details-item.card-details-item-received
h3.card-details-item-title
i.fa.fa-sign-out
@ -119,7 +134,9 @@ template(name="cardDetails")
a.card-label.add-label.js-end-date
i.fa.fa-plus
hr
if currentBoard.hasAnyAllowsUser
hr
if currentBoard.allowsCreator
.card-details-item.card-details-item-creator
h3.card-details-item-title
@ -160,17 +177,6 @@ template(name="cardDetails")
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
i.fa.fa-plus
//.card-details-items
if getSpentTime
.card-details-item.card-details-item-spent
if getIsOvertime
h3.card-details-item-title
| {{_ 'overtime-hours'}}
else
h3.card-details-item-title
| {{_ 'spent-time-hours'}}
+cardSpentTime
//.card-details-items
if currentBoard.allowsRequestedBy
.card-details-item.card-details-item-name
@ -212,6 +218,9 @@ template(name="cardDetails")
+viewer
= getAssignedBy
if $or currentBoard.allowsCardSortingByNumber getSpentTime
hr
if currentBoard.allowsCardSortingByNumber
.card-details-item.card-details-sort-order
h3.card-details-item-title
@ -225,15 +234,36 @@ template(name="cardDetails")
+viewer
= sort
//.card-details-items
if getSpentTime
.card-details-item.card-details-item-spent
if getIsOvertime
h3.card-details-item-title
| {{_ 'overtime-hours'}}
else
h3.card-details-item-title
| {{_ 'spent-time-hours'}}
+cardSpentTime
//.card-details-items
if customFieldsWD
hr
unless customFieldsGrid
hr
each customFieldsWD
if customFieldsGrid
hr
.card-details-item.card-details-item-customfield
h3.card-details-item-title
i.fa.fa-list-alt
= definition.name
+cardCustomField
.material-toggle-switch(title="{{_ 'change'}} {{_ 'custom-fields'}} {{_ 'layout'}}")
if customFieldsGrid
input.toggle-switch(type="checkbox" id="toggleCustomFieldsGridButton" checked="checked")
else
input.toggle-switch(type="checkbox" id="toggleCustomFieldsGridButton")
label.toggle-label(for="toggleCustomFieldsGridButton")
a.fa.fa-plus.js-custom-fields.card-details-item.custom-fields(title="{{_ 'custom-fields'}}")
if getVoteQuestion
hr
@ -519,6 +549,7 @@ template(name="cardDetails")
.card-details-right
unless currentUser.isNoComments
hr
.activity-title
h3.card-details-item-title
i.fa.fa-history
@ -708,8 +739,9 @@ template(name="boardsAndLists")
button.primary.confirm.js-done {{_ 'done'}}
template(name="cardMembersPopup")
input.card-members-filter(type="text" placeholder="{{_ 'search'}}")
ul.pop-over-list.js-card-member-list
each board.activeMembers
each members
li.item(class="{{#if isCardMember}}active{{/if}}")
a.name.js-select-member(href="#")
+userAvatar(userId=user._id)
@ -720,9 +752,10 @@ template(name="cardMembersPopup")
i.fa.fa-check
template(name="cardAssigneesPopup")
input.card-assignees-filter(type="text" placeholder="{{_ 'search'}}")
unless currentUser.isWorker
ul.pop-over-list.js-card-assignee-list
each board.activeMembers
each members
li.item(class="{{#if isCardAssignee}}active{{/if}}")
a.name.js-select-assignee(href="#")
+userAvatar(userId=user._id)
@ -767,6 +800,7 @@ template(name="cardMorePopup")
i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
input.inline-input(type="text" id="cardURL" readonly value="{{ originRelativeUrl }}" autofocus="autofocus")
button.js-copy-card-link-to-clipboard(class="btn" id="clipboard") {{_ 'copy-card-link-to-clipboard'}}
.copied-tooltip {{_ 'copied'}}
span.clearfix
br
h2 {{_ 'change-card-parent'}}
@ -815,6 +849,12 @@ template(name="cardDeletePopup")
p {{_ "card-delete-suggest-archive"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="cardArchivePopup")
p {{_ "card-archive-pop"}}
unless archived
p {{_ "card-archive-suggest-cancel"}}
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
template(name="deleteVotePopup")
p {{_ "vote-delete-pop"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}

View file

@ -34,15 +34,25 @@ BlazeComponent.extendComponent({
onCreated() {
this.currentBoard = Boards.findOne(Session.get('currentBoard'));
this.isLoaded = new ReactiveVar(false);
const boardBody = this.parentComponent().parentComponent();
//in Miniview parent is Board, not BoardBody.
if (boardBody !== null) {
boardBody.showOverlay.set(true);
boardBody.mouseHasEnterCardDetails = false;
if (this.parentComponent() && this.parentComponent().parentComponent()) {
const boardBody = this.parentComponent().parentComponent();
//in Miniview parent is Board, not BoardBody.
if (boardBody !== null) {
boardBody.showOverlay.set(true);
boardBody.mouseHasEnterCardDetails = false;
}
}
this.calculateNextPeak();
Meteor.subscribe('unsaved-edits');
// this.findUsersOptions = new ReactiveVar({});
// this.page = new ReactiveVar(1);
// this.autorun(() => {
// const limitUsers = this.page.get() * Number.MAX_SAFE_INTEGER;
// this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
// });
},
isWatching() {
@ -54,6 +64,11 @@ BlazeComponent.extendComponent({
return Meteor.user().hasHiddenSystemMessages();
},
customFieldsGrid() {
return Meteor.user().hasCustomFieldsGrid();
},
cardMaximized() {
return Meteor.user().hasCardMaximized();
},
@ -180,7 +195,7 @@ BlazeComponent.extendComponent({
integration,
'CardSelected',
params,
() => {},
() => { },
);
});
}
@ -203,7 +218,7 @@ BlazeComponent.extendComponent({
distance: 7,
start(evt, ui) {
ui.placeholder.height(ui.helper.height());
EscapeActions.executeUpTo('popup-close');
EscapeActions.clickExecute(evt.target, 'inlinedForm');
},
stop(evt, ui) {
let prevChecklist = ui.item.prev('.js-checklist').get(0);
@ -285,6 +300,7 @@ BlazeComponent.extendComponent({
},
onDestroyed() {
if (this.parentComponent() === null) return;
const parentComponent = this.parentComponent().parentComponent();
//on mobile view parent is Board, not board body.
if (parentComponent === null) return;
@ -307,30 +323,12 @@ BlazeComponent.extendComponent({
'click .js-close-card-details'() {
Utils.goBoardId(this.data().boardId);
},
'click .js-copy-link'() {
const StringToCopyElement = document.getElementById('cardURL_copy');
StringToCopyElement.value =
window.location.origin + window.location.pathname;
StringToCopyElement.select();
if (document.execCommand('copy')) {
StringToCopyElement.blur();
} else {
document.getElementById('cardURL_copy').selectionStart = 0;
document.getElementById('cardURL_copy').selectionEnd = 999;
document.execCommand('copy');
if (window.getSelection) {
if (window.getSelection().empty) {
// Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) {
// Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) {
// IE?
document.selection.empty();
}
}
'click .js-copy-link'(event) {
event.preventDefault();
const promise = Utils.copyTextToClipboard(event.target.href);
const $tooltip = this.$('.card-details-header .copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
'submit .js-card-description'(event) {
@ -365,6 +363,12 @@ BlazeComponent.extendComponent({
this.data().setRequestedBy('');
}
},
'keydown input.js-edit-card-sort'(evt) {
// enter = save
if (evt.keyCode === 13) {
this.find('button[type=submit]').click();
}
},
'submit .js-card-details-sort'(event) {
event.preventDefault();
const sort = parseFloat(this.currentComponent()
@ -389,7 +393,9 @@ BlazeComponent.extendComponent({
'click .js-end-date': Popup.open('editCardEndDate'),
'click .js-show-positive-votes': Popup.open('positiveVoteMembers'),
'click .js-show-negative-votes': Popup.open('negativeVoteMembers'),
'click .js-custom-fields': Popup.open('cardCustomFields'),
'mouseenter .js-card-details'() {
if (this.parentComponent() === null) return;
const parentComponent = this.parentComponent().parentComponent();
//on mobile view parent is Board, not BoardBody.
if (parentComponent === null) return;
@ -412,6 +418,9 @@ BlazeComponent.extendComponent({
'click #toggleButton'() {
Meteor.call('toggleSystemMessages');
},
'click #toggleCustomFieldsGridButton'() {
Meteor.call('toggleCustomFieldsGrid');
},
'click .js-maximize-card-details'() {
Meteor.call('toggleCardMaximized');
autosize($('.card-details'));
@ -511,6 +520,23 @@ BlazeComponent.extendComponent({
},
}).register('cardDetails');
Template.cardDetails.helpers({
isPopup() {
let ret = !!Utils.getPopupCardId();
return ret;
}
});
Template.cardDetailsPopup.onDestroyed(() => {
Session.delete('popupCardId');
Session.delete('popupCardBoardId');
});
Template.cardDetailsPopup.helpers({
popupCard() {
const ret = Utils.getPopupCard();
return ret;
},
});
BlazeComponent.extendComponent({
template() {
return 'exportCard';
@ -541,8 +567,8 @@ BlazeComponent.extendComponent({
}).register('exportCardPopup');
// only allow number input
Template.editCardSortOrderForm.onRendered(function() {
this.$('input').on("keypress paste", function(event) {
Template.editCardSortOrderForm.onRendered(function () {
this.$('input').on("keypress paste", function (event) {
let keyCode = event.keyCode;
let charCode = String.fromCharCode(keyCode);
let regex = new RegExp('[-0-9.]');
@ -561,16 +587,15 @@ Template.editCardSortOrderForm.onRendered(function() {
// XXX Recovering the currentCard identifier form a session variable is
// fragile because this variable may change for instance if the route
// change. We should use some component props instead.
docId: Session.get('currentCard'),
docId: Utils.getCurrentCardId(),
};
}
close(isReset = false) {
if (this.isOpen.get() && !isReset) {
const draft = this.getValue().trim();
if (
draft !== Cards.findOne(Session.get('currentCard')).getDescription()
) {
let card = Utils.getCurrentCard();
if (card && draft !== card.getDescription()) {
UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue());
}
}
@ -615,7 +640,6 @@ Template.cardDetailsActionsPopup.events({
'click .js-export-card': Popup.open('exportCard'),
'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-start-voting': Popup.open('cardStartVoting'),
'click .js-start-planning-poker': Popup.open('cardStartPlanningPoker'),
@ -634,25 +658,27 @@ Template.cardDetailsActionsPopup.events({
event.preventDefault();
const minOrder = _.min(
this.list()
.cards(this.swimlaneId)
.cardsUnfiltered(this.swimlaneId)
.map((c) => c.sort),
);
this.move(this.boardId, this.swimlaneId, this.listId, minOrder - 1);
Popup.back();
},
'click .js-move-card-to-bottom'(event) {
event.preventDefault();
const maxOrder = _.max(
this.list()
.cards(this.swimlaneId)
.cardsUnfiltered(this.swimlaneId)
.map((c) => c.sort),
);
this.move(this.boardId, this.swimlaneId, this.listId, maxOrder + 1);
Popup.back();
},
'click .js-archive'(event) {
event.preventDefault();
this.archive();
'click .js-archive': Popup.afterConfirm('cardArchive', function () {
Popup.close();
},
this.archive();
Utils.goBoardId(this.boardId);
}),
'click .js-more': Popup.open('cardMore'),
'click .js-toggle-watch-card'() {
const currentCard = this;
@ -667,6 +693,64 @@ Template.editCardTitleForm.onRendered(function () {
autosize(this.$('.js-edit-card-title'));
});
Template.cardMembersPopup.onCreated(function () {
let currBoard = Boards.findOne(Session.get('currentBoard'));
let members = currBoard.activeMembers();
// let query = {
// "teams.teamId": { $in: currBoard.teams.map(t => t.teamId) },
// };
// let boardTeamUsers = Users.find(query, {
// sort: { sort: 1 },
// });
// members = currBoard.activeMembers2(members, boardTeamUsers);
this.members = new ReactiveVar(members);
});
Template.cardMembersPopup.events({
'keyup .card-members-filter'(event) {
const members = filterMembers(event.target.value);
Template.instance().members.set(members);
}
});
Template.cardMembersPopup.helpers({
members() {
return Template.instance().members.get();
},
});
const filterMembers = (filterTerm) => {
let currBoard = Boards.findOne(Session.get('currentBoard'));
let members = currBoard.activeMembers();
// let query = {
// "teams.teamId": { $in: currBoard.teams.map(t => t.teamId) },
// };
// let boardTeamUsers = Users.find(query, {
// sort: { sort: 1 },
// });
// members = currBoard.activeMembers2(members, boardTeamUsers);
if (filterTerm) {
members = members
.map(member => ({
member,
user: Users.findOne(member.userId)
}))
.filter(({ user }) =>
(user.profile.fullname !== undefined && user.profile.fullname.toLowerCase().indexOf(filterTerm.toLowerCase()) !== -1)
|| user.profile.fullname === undefined && user.profile.username !== undefined && user.profile.username.toLowerCase().indexOf(filterTerm.toLowerCase()) !== -1)
.map(({ member }) => member);
}
return members;
}
Template.editCardTitleForm.events({
'keydown .js-edit-card-title'(event) {
// If enter key was pressed, submit the data
@ -707,7 +791,7 @@ Template.moveCardPopup.events({
'click .js-done'() {
// XXX We should *not* get the currentCard from the global state, but
// instead from a “component” state.
const card = Cards.findOne(Session.get('currentCard'));
const card = Utils.getCurrentCard();
const bSelect = $('.js-select-boards')[0];
let boardId;
// if we are a worker, we won't have a board select so we just use the
@ -719,7 +803,13 @@ Template.moveCardPopup.events({
const slSelect = $('.js-select-swimlanes')[0];
const swimlaneId = slSelect.options[slSelect.selectedIndex].value;
card.move(boardId, swimlaneId, listId, 0);
Popup.close();
// set new id's to card object in case the card is moved to top by the comment "moveCard" after this command (.js-move-card)
this.boardId = boardId;
this.swimlaneId = swimlaneId;
this.listId = listId;
Popup.back();
},
});
BlazeComponent.extendComponent({
@ -765,7 +855,7 @@ BlazeComponent.extendComponent({
Template.copyCardPopup.events({
'click .js-done'() {
const card = Cards.findOne(Session.get('currentCard'));
const card = Utils.getCurrentCard();
const lSelect = $('.js-select-lists')[0];
const listId = lSelect.options[lSelect.selectedIndex].value;
const slSelect = $('.js-select-swimlanes')[0];
@ -787,14 +877,14 @@ Template.copyCardPopup.events({
// See https://github.com/wekan/wekan/issues/80
Filter.addException(_id);
Popup.close();
Popup.back();
}
},
});
Template.convertChecklistItemToCardPopup.events({
'click .js-done'() {
const card = Cards.findOne(Session.get('currentCard'));
const card = Utils.getCurrentCard();
const lSelect = $('.js-select-lists')[0];
const listId = lSelect.options[lSelect.selectedIndex].value;
const slSelect = $('.js-select-swimlanes')[0];
@ -814,7 +904,7 @@ Template.convertChecklistItemToCardPopup.events({
});
Filter.addException(_id);
Popup.close();
Popup.back();
}
},
@ -822,7 +912,7 @@ Template.convertChecklistItemToCardPopup.events({
Template.copyChecklistToManyCardsPopup.events({
'click .js-done'() {
const card = Cards.findOne(Session.get('currentCard'));
const card = Utils.getCurrentCard();
const oldId = card._id;
card._id = null;
const lSelect = $('.js-select-lists')[0];
@ -870,7 +960,7 @@ Template.copyChecklistToManyCardsPopup.events({
cmt.copy(_id);
});
}
Popup.close();
Popup.back();
}
},
});
@ -941,7 +1031,7 @@ BlazeComponent.extendComponent({
},
cards() {
const currentId = Session.get('currentCard');
const currentId = Utils.getCurrentCardId();
if (this.parentBoard.get()) {
return Cards.find({
boardId: this.parentBoard.get(),
@ -980,30 +1070,11 @@ BlazeComponent.extendComponent({
events() {
return [
{
'click .js-copy-card-link-to-clipboard'() {
// Clipboard code from:
// https://stackoverflow.com/questions/6300213/copy-selected-text-to-the-clipboard-without-using-flash-must-be-cross-browser
const StringToCopyElement = document.getElementById('cardURL');
StringToCopyElement.select();
if (document.execCommand('copy')) {
StringToCopyElement.blur();
} else {
document.getElementById('cardURL').selectionStart = 0;
document.getElementById('cardURL').selectionEnd = 999;
document.execCommand('copy');
if (window.getSelection) {
if (window.getSelection().empty) {
// Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) {
// Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) {
// IE?
document.selection.empty();
}
}
'click .js-copy-card-link-to-clipboard'(event) {
const promise = Utils.copyTextToClipboard(location.origin + document.getElementById('cardURL').value);
const $tooltip = this.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
'click .js-delete': Popup.afterConfirm('cardDelete', function () {
Popup.close();
@ -1019,9 +1090,8 @@ BlazeComponent.extendComponent({
// https://github.com/wekan/wekan/issues/2785
const message = `${TAPi18n.__(
'delete-linked-card-before-this-card',
)} linkedId: ${
this._id
} at client/components/cards/cardDetails.js and https://github.com/wekan/wekan/issues/2785`;
)} linkedId: ${this._id
} at client/components/cards/cardDetails.js and https://github.com/wekan/wekan/issues/2785`;
alert(message);
}
Utils.goBoardId(this.boardId);
@ -1074,12 +1144,12 @@ BlazeComponent.extendComponent({
if (endString) {
this.currentCard.setVoteEnd(endString);
}
Popup.close();
Popup.back();
},
'click .js-remove-vote': Popup.afterConfirm('deleteVote', () => {
event.preventDefault();
this.currentCard.unsetVote();
Popup.close();
Popup.back();
}),
'click a.js-toggle-vote-public'(event) {
event.preventDefault();
@ -1119,7 +1189,7 @@ BlazeComponent.extendComponent({
// if active vote - store it
if (this.currentData().getVoteQuestion()) {
this._storeDate(newDate.toDate());
Popup.close();
Popup.back();
} else {
this.currentData().vote = { end: newDate.toDate() }; // set vote end temp
Popup.back();
@ -1153,86 +1223,77 @@ BlazeComponent.extendComponent({
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(usaDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: usaDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (euroAmDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(euroAmDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: euroAmDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (euro24hDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(euro24hDate.toDate());
this.card.setPokerEnd(euro24hDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: euro24hDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (eurodotDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(eurodotDate.toDate());
this.card.setPokerEnd(eurodotDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: eurodotDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (minusDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(minusDate.toDate());
this.card.setPokerEnd(minusDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: minusDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (slashDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(slashDate.toDate());
this.card.setPokerEnd(slashDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: slashDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (dotDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(dotDate.toDate());
this.card.setPokerEnd(dotDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: dotDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (brezhonegDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(brezhonegDate.toDate());
this.card.setPokerEnd(brezhonegDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: brezhonegDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (hrvatskiDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(hrvatskiDate.toDate());
this.card.setPokerEnd(hrvatskiDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: hrvatskiDate.toDate() }; // set poker end temp
Popup.back();
@ -1242,41 +1303,37 @@ BlazeComponent.extendComponent({
if (this.currentData().getPokerQuestion()) {
this._storeDate(latviaDate.toDate());
this.card.setPokerEnd(latviaDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: latviaDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (nederlandsDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(nederlandsDate.toDate());
this.card.setPokerEnd(nederlandsDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: nederlandsDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (greekDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(greekDate.toDate());
this.card.setPokerEnd(greekDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: greekDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (macedonianDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(macedonianDate.toDate());
this.card.setPokerEnd(macedonianDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: macedonianDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else {
this.error.set('invalid-date');
evt.target.date.focus();
@ -1285,7 +1342,7 @@ BlazeComponent.extendComponent({
'click .js-delete-date'(evt) {
evt.preventDefault();
this._deleteDate();
Popup.close();
Popup.back();
},
},
];
@ -1323,11 +1380,11 @@ BlazeComponent.extendComponent({
if (endString) {
this.currentCard.setPokerEnd(endString);
}
Popup.close();
Popup.back();
},
'click .js-remove-poker': Popup.afterConfirm('deletePoker', (event) => {
this.currentCard.unsetPoker();
Popup.close();
Popup.back();
}),
'click a.js-toggle-poker-allow-non-members'(event) {
event.preventDefault();
@ -1390,7 +1447,7 @@ BlazeComponent.extendComponent({
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(newDate.toDate());
Popup.close();
Popup.back();
} else {
this.currentData().poker = { end: newDate.toDate() }; // set poker end temp
Popup.back();
@ -1422,130 +1479,117 @@ BlazeComponent.extendComponent({
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(usaDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: usaDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (euroAmDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(euroAmDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: euroAmDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (euro24hDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(euro24hDate.toDate());
this.card.setPokerEnd(euro24hDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: euro24hDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (eurodotDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(eurodotDate.toDate());
this.card.setPokerEnd(eurodotDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: eurodotDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (minusDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(minusDate.toDate());
this.card.setPokerEnd(minusDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: minusDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (slashDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(slashDate.toDate());
this.card.setPokerEnd(slashDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: slashDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (dotDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(dotDate.toDate());
this.card.setPokerEnd(dotDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: dotDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (brezhonegDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(brezhonegDate.toDate());
this.card.setPokerEnd(brezhonegDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: brezhonegDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (hrvatskiDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(hrvatskiDate.toDate());
this.card.setPokerEnd(hrvatskiDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: hrvatskiDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (latviaDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(latviaDate.toDate());
this.card.setPokerEnd(latviaDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: latviaDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (nederlandsDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(nederlandsDate.toDate());
this.card.setPokerEnd(nederlandsDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: nederlandsDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (greekDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(greekDate.toDate());
this.card.setPokerEnd(greekDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: greekDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else if (macedonianDate.isValid()) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(macedonianDate.toDate());
this.card.setPokerEnd(macedonianDate.toDate());
Popup.close();
} else {
this.currentData().poker = { end: macedonianDate.toDate() }; // set poker end temp
Popup.back();
}
Popup.back();
} else {
// this.error.set('invalid-date);
this.error.set('invalid-date' + ' ' + dateString);
@ -1555,7 +1599,7 @@ BlazeComponent.extendComponent({
'click .js-delete-date'(evt) {
evt.preventDefault();
this._deleteDate();
Popup.close();
Popup.back();
},
},
];
@ -1589,13 +1633,34 @@ EscapeActions.register(
},
);
Template.cardAssigneesPopup.onCreated(function () {
let currBoard = Boards.findOne(Session.get('currentBoard'));
let members = currBoard.activeMembers();
// let query = {
// "teams.teamId": { $in: currBoard.teams.map(t => t.teamId) },
// };
// let boardTeamUsers = Users.find(query, {
// sort: { sort: 1 },
// });
// members = currBoard.activeMembers2(members, boardTeamUsers);
this.members = new ReactiveVar(members);
});
Template.cardAssigneesPopup.events({
'click .js-select-assignee'(event) {
const card = Cards.findOne(Session.get('currentCard'));
const card = Utils.getCurrentCard();
const assigneeId = this.userId;
card.toggleAssignee(assigneeId);
event.preventDefault();
},
'keyup .card-assignees-filter'(event) {
const members = filterMembers(event.target.value);
Template.instance().members.set(members);
},
});
Template.cardAssigneesPopup.helpers({
@ -1606,6 +1671,10 @@ Template.cardAssigneesPopup.helpers({
return _.contains(cardAssignees, this.userId);
},
members() {
return Template.instance().members.get();
},
user() {
return Users.findOne(this.userId);
},
@ -1657,7 +1726,7 @@ Template.cardAssigneePopup.helpers({
Template.cardAssigneePopup.events({
'click .js-remove-assignee'() {
Cards.findOne(this.cardId).unassignAssignee(this.userId);
Popup.close();
Popup.back();
},
'click .js-edit-profile': Popup.open('editProfile'),
});

View file

@ -4,15 +4,6 @@
avatar-radius = 50%
#cardURL_copy
// Have clipboard text not visible by moving it to far left
position: absolute
left: -2000px
top: 0px
#clipboard
white-space: normal
.assignee
border-radius: 3px
display: block
@ -85,6 +76,12 @@ avatar-radius = 50%
box-shadow: 0 0 0 2px darken(white, 60%) inset
// Other card details
.copied-tooltip
display: none
padding: 0px 10px;
background-color: #000000df;
color: #fff;
border-radius: 5px;
.card-details
padding: 0
@ -127,7 +124,8 @@ avatar-radius = 50%
.card-copy-button,
.card-copy-mobile-button,
.close-card-details-mobile-web,
.card-details-menu-mobile-web
.card-details-menu-mobile-web,
.copied-tooltip
float: right
.close-card-details,
@ -196,6 +194,14 @@ avatar-radius = 50%
border-radius: 3px
padding: 0px 5px
.copied-tooltip
display: none
margin-right: 10px
padding: 10px;
background-color: #000000df;
color: #fff;
border-radius: 5px;
.card-description textarea
min-height: 100px
@ -230,55 +236,54 @@ avatar-radius = 50%
word-wrap: break-word
max-width: 28%
flex-grow: 1
&.custom-fields
padding-left: 10px
.card-details-item-title
font-size: 16px
font-weight: bold
color: #4d4d4d
.card-label
padding-top: 5px
padding-bottom: 5px
.activities
padding-top: 10px
.card-details-maximized
padding: 0
flex-shrink: 0
flex-basis: calc(100% - 20px)
will-change: flex-basis
overflow-y: scroll
overflow-x: scroll
background: darken(white, 3%)
border-radius: bottom 3px
z-index: 1000 !important
animation: flexGrowIn 0.1s
box-shadow: 0 0 7px 0 darken(white, 30%)
transition: flex-basis 0.1s
box-sizing: border-box
position: absolute
top: 0
left: 0
height: calc(100% - 20px)
width: calc(100% - 20px)
float: left
.card-details-left
@media screen and (min-width: 801px)
.card-details-maximized
padding: 0
flex-shrink: 0
flex-basis: calc(100% - 20px)
will-change: flex-basis
overflow-y: scroll
overflow-x: scroll
background: darken(white, 3%)
border-radius: bottom 3px
z-index: 1000 !important
animation: flexGrowIn 0.1s
box-shadow: 0 0 7px 0 darken(white, 30%)
transition: flex-basis 0.1s
box-sizing: border-box
position: absolute
top: 0
left: 0
height: calc(100% - 20px)
width: calc(100% - 20px)
float: left
top: 60px
left: 20px
width: 47%
.card-details-right
position: absolute
float: right
top: 20px
left: 50%
.card-details-left
float: left
top: 60px
left: 20px
width: 47%
.card-details-header
width: 47%
.card-details-right
position: absolute
float: right
top: 20px
left: 50%
.card-details-header
width: 47%
input[type="text"].attachment-add-link-input
float: left
@ -297,6 +302,8 @@ input[type="submit"].attachment-add-link-submit
padding: 0px 20px 0px 20px
margin: 0px
transition: none
overflow-y: revert
overflow-x: revert
.card-details-canvas
width: 100%
@ -315,6 +322,21 @@ input[type="submit"].attachment-add-link-submit
.minimize-card-details
margin-right: 40px
.card-details-popup
padding: 0px 10px
.pop-over > .content-wrapper > .popup-container-depth-0
width: 100%
& > .content
width: calc(100% - 10px)
& > .content > .card-details-popup hr
margin: 15px 0px
.card-details-header
margin: 0
card-details-color(background, color...)
background: background !important
if color

View file

@ -9,7 +9,6 @@ BlazeComponent.extendComponent({
toggleOvertime() {
this.card.setIsOvertime(!this.card.getIsOvertime());
$('#overtime .materialCheckBox').toggleClass('is-checked');
$('#overtime').toggleClass('is-checked');
},
storeTime(spentTime, isOvertime) {
@ -18,6 +17,7 @@ BlazeComponent.extendComponent({
},
deleteTime() {
this.card.setSpentTime(null);
this.card.setIsOvertime(false);
},
events() {
return [
@ -27,11 +27,14 @@ BlazeComponent.extendComponent({
evt.preventDefault();
const spentTime = parseFloat(evt.target.time.value);
const isOvertime = this.card.getIsOvertime();
//const isOvertime = this.card.getIsOvertime();
let isOvertime = false;
if ($('#overtime').attr('class').indexOf('is-checked') >= 0) {
isOvertime = true;
}
if (spentTime >= 0) {
this.storeTime(spentTime, isOvertime);
Popup.close();
Popup.back();
} else {
this.error.set('invalid-time');
evt.target.time.focus();
@ -40,7 +43,7 @@ BlazeComponent.extendComponent({
'click .js-delete-time'(evt) {
evt.preventDefault();
this.deleteTime();
Popup.close();
Popup.back();
},
'click a.js-toggle-overtime': this.toggleOvertime,
},

View file

@ -12,20 +12,15 @@ template(name="checklists")
input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton")
label.toggle-label(for="toggleHideCheckedItemsButton")
if toggleDeleteDialog.get
.board-overlay#card-details-overlay
+checklistDeleteDialog(checklist = checklistToDelete)
.card-checklist-items
each checklist in currentCard.checklists
each checklist in checklists
+checklistDetail(checklist = checklist)
if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId)
+addChecklistItemForm
else
a.js-open-inlined-form(title="{{_ 'add-checklist'}}")
a.add-checklist.js-open-inlined-form(title="{{_ 'add-checklist'}}")
i.fa.fa-plus
template(name="checklistDetail")
@ -50,25 +45,21 @@ template(name="checklistDetail")
= checklist.title
+checklistItems(checklist = checklist)
template(name="checklistDeleteDialog")
.js-confirm-checklist-delete
p
i(class="fa fa-exclamation-triangle" aria-hidden="true")
p
| {{_ 'confirm-checklist-delete-dialog'}}
span {{checklist.title}}
| ?
.js-checklist-delete-buttons
button.confirm-checklist-delete(type="button") {{_ 'delete'}}
button.toggle-delete-checklist-dialog(type="button") {{_ 'cancel'}}
template(name="checklistDeletePopup")
p {{_ 'confirm-checklist-delete-popup'}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="addChecklistItemForm")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.js-add-checklist-item(rows='1' autofocus)
.edit-controls.clearfix
button.primary.confirm.js-submit-add-checklist-item-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
template(name="editChecklistItemForm")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.js-edit-checklist-item(rows='1' autofocus dir="auto")
if $eq type 'item'
= item.title

View file

@ -13,10 +13,10 @@ function initSorting(items) {
appendTo: 'parent',
distance: 7,
placeholder: 'checklist-item placeholder',
scroll: false,
scroll: true,
start(evt, ui) {
ui.placeholder.height(ui.helper.height());
EscapeActions.executeUpTo('popup-close');
EscapeActions.clickExecute(evt.target, 'inlinedForm');
},
stop(evt, ui) {
const parent = ui.item.parents('.js-checklist-items');
@ -55,7 +55,7 @@ BlazeComponent.extendComponent({
return Meteor.user() && Meteor.user().isBoardMember();
}
// Disable sorting if the current user is not a board member or is a miniscreen
// Disable sorting if the current user is not a board member
self.autorun(() => {
const $itemsDom = $(self.itemsDom);
if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
@ -94,16 +94,14 @@ BlazeComponent.extendComponent({
title,
sort: card.checklists().count(),
});
this.closeAllInlinedForms();
setTimeout(() => {
this.$('.add-checklist-item')
.last()
.click();
}, 100);
}
textarea.value = '';
textarea.focus();
},
addChecklistItem(event) {
event.preventDefault();
const textarea = this.find('textarea.js-add-checklist-item');
@ -132,14 +130,6 @@ BlazeComponent.extendComponent({
);
},
deleteChecklist() {
const checklist = this.currentData().checklist;
if (checklist && checklist._id) {
Checklists.remove(checklist._id);
this.toggleDeleteDialog.set(false);
}
},
deleteItem() {
const checklist = this.currentData().checklist;
const item = this.currentData().item;
@ -165,11 +155,6 @@ BlazeComponent.extendComponent({
item.setTitle(title);
},
onCreated() {
this.toggleDeleteDialog = new ReactiveVar(false);
this.checklistToDelete = null; //Store data context to pass to checklistDeleteDialog template
},
pressKey(event) {
//If user press enter key inside a form, submit it
//Unless the user is also holding down the 'shift' key
@ -190,14 +175,13 @@ BlazeComponent.extendComponent({
}
},
/** closes all inlined forms (checklist and checklist-item input fields) */
closeAllInlinedForms() {
this.$('.js-close-inlined-form').click();
},
events() {
const events = {
'click .toggle-delete-checklist-dialog'(event) {
if ($(event.target).hasClass('js-delete-checklist')) {
this.checklistToDelete = this.currentData().checklist; //Store data context
}
this.toggleDeleteDialog.set(!this.toggleDeleteDialog.get());
},
'click #toggleHideCheckedItemsButton'() {
Meteor.call('toggleHideCheckedItems');
},
@ -206,14 +190,22 @@ BlazeComponent.extendComponent({
return [
{
...events,
'click .toggle-delete-checklist-dialog' : Popup.afterConfirm('checklistDelete', function () {
Popup.close();
const checklist = this.checklist;
if (checklist && checklist._id) {
Checklists.remove(checklist._id);
}
}),
'submit .js-add-checklist': this.addChecklist,
'submit .js-edit-checklist-title': this.editChecklist,
'submit .js-add-checklist-item': this.addChecklistItem,
'submit .js-edit-checklist-item': this.editChecklistItem,
'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'),
'click .js-delete-checklist-item': this.deleteItem,
'click .confirm-checklist-delete': this.deleteChecklist,
'focus .js-add-checklist-item': this.focusChecklistItem,
// add and delete checklist / checklist-item
'click .js-open-inlined-form': this.closeAllInlinedForms,
keydown: this.pressKey,
},
];
@ -262,6 +254,11 @@ BlazeComponent.extendComponent({
}).register('boardsSwimlanesAndLists');
Template.checklists.helpers({
checklists() {
const card = Cards.findOne(this.cardId);
const ret = card.checklists();
return ret;
},
hideCheckedItems() {
const currentUser = Meteor.user();
if (currentUser) return currentUser.hasHideCheckedItems();
@ -269,39 +266,59 @@ Template.checklists.helpers({
},
});
Template.addChecklistItemForm.onRendered(() => {
autosize($('textarea.js-add-checklist-item'));
});
BlazeComponent.extendComponent({
onRendered() {
autosize(this.$('textarea.js-add-checklist-item'));
},
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
},
events() {
return [
{
'click a.fa.fa-copy'(event) {
const $editor = this.$('textarea');
const promise = Utils.copyTextToClipboard($editor[0].value);
Template.editChecklistItemForm.onRendered(() => {
autosize($('textarea.js-edit-checklist-item'));
});
const $tooltip = this.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
}
];
}
}).register('addChecklistItemForm');
Template.checklistDeleteDialog.onCreated(() => {
const $cardDetails = this.$('.card-details');
this.scrollState = {
position: $cardDetails.scrollTop(), //save current scroll position
top: false, //required for smooth scroll animation
};
//Callback's purpose is to only prevent scrolling after animation is complete
$cardDetails.animate({ scrollTop: 0 }, 500, () => {
this.scrollState.top = true;
});
BlazeComponent.extendComponent({
onRendered() {
autosize(this.$('textarea.js-edit-checklist-item'));
},
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
},
events() {
return [
{
'click a.fa.fa-copy'(event) {
const $editor = this.$('textarea');
const promise = Utils.copyTextToClipboard($editor[0].value);
//Prevent scrolling while dialog is open
$cardDetails.on('scroll', () => {
if (this.scrollState.top) {
//If it's already in position, keep it there. Otherwise let animation scroll
$cardDetails.scrollTop(0);
}
});
});
Template.checklistDeleteDialog.onDestroyed(() => {
const $cardDetails = this.$('.card-details');
$cardDetails.off('scroll'); //Reactivate scrolling
$cardDetails.animate({ scrollTop: this.scrollState.position });
});
const $tooltip = this.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
}
];
}
}).register('editChecklistItemForm');
Template.checklistItemDetail.helpers({
canModifyCard() {

View file

@ -47,41 +47,6 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
padding-top: 3px
float: left
.js-confirm-checklist-delete
background-color: darken(white, 3%)
position: absolute
float: left;
width: 60%
margin-top: 0
margin-left: 13%
padding-bottom: 2%
padding-left: 3%
padding-right: 3%
z-index: 17
border-radius: 3px
p
position: relative
margin-top: 3%
width: 100%
text-align: center
span
font-weight: bold
i
font-size: 2em
.js-checklist-delete-buttons
position: relative
padding: left 2% right 2%
.confirm-checklist-delete
margin-left: 12%
float: left
.toggle-delete-checklist-dialog
margin-right: 12%
float: right
#card-details-overlay
top: 0
bottom: -600px
@ -167,4 +132,13 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
.add-checklist-item
margin: 0.2em 0 0.5em 1.33em
display: inline-block
.add-checklist-item,.add-checklist
&.js-open-inlined-form
display: block
width: 50%
&:hover
background: #dbdbdb
color: #222
box-shadow: 0 1px 2px rgba(0,0,0,.2)

View file

@ -27,9 +27,11 @@ template(name="deleteLabelPopup")
template(name="cardLabelsPopup")
ul.edit-labels-pop-over
each board.labels
li
li.js-card-label-item
a.card-label-edit-button.fa.fa-pencil.js-edit-label
span.card-label.card-label-selectable.js-select-label(class="card-label-{{color}}"
if isMiniScreenOrShowDesktopDragHandles
span.fa.label-handle(class="fa-arrows" title="{{_ 'dragLabel'}}")
span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="card-label-{{color}}"
class="{{# if isLabelSelected ../_id }}active{{/if}}")
+viewer
= name

View file

@ -39,15 +39,67 @@ Template.createLabelPopup.helpers({
},
});
Template.cardLabelsPopup.events({
'click .js-select-label'(event) {
const card = Cards.findOne(Session.get('currentCard'));
const labelId = this._id;
card.toggleLabel(labelId);
event.preventDefault();
BlazeComponent.extendComponent({
onRendered() {
const itemsSelector = 'li.js-card-label-item:not(.placeholder)';
const $labels = this.$('.edit-labels-pop-over');
$labels.sortable({
connectWith: '.edit-labels-pop-over',
tolerance: 'pointer',
appendTo: '.edit-labels-pop-over',
helper(element, currentItem) {
let ret = currentItem.clone();
if (currentItem.closest('.popup-container-depth-0').size() == 0)
{ // only set css transform at every sub-popup, not at the main popup
const content = currentItem.closest('.content')[0]
const offsetLeft = content.offsetLeft;
const offsetTop = $('.pop-over > .header').height() * -1;
ret.css("transform", `translate(${offsetLeft}px, ${offsetTop}px)`);
}
return ret;
},
distance: 7,
items: itemsSelector,
placeholder: 'card-label-wrapper placeholder',
start(evt, ui) {
ui.helper.css('z-index', 1000);
ui.placeholder.height(ui.helper.height());
EscapeActions.clickExecute(evt.target, 'inlinedForm');
},
stop(evt, ui) {
const newLabelOrderOnlyIds = ui.item.parent().children().toArray().map(_element => Blaze.getData(_element)._id)
const card = Blaze.getData(this);
card.board().setNewLabelOrder(newLabelOrderOnlyIds);
},
});
// Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => {
if (Utils.isMiniScreenOrShowDesktopDragHandles()) {
$labels.sortable({
handle: '.label-handle',
});
}
});
},
'click .js-edit-label': Popup.open('editLabel'),
'click .js-add-label': Popup.open('createLabel'),
events() {
return [
{
'click .js-select-label'(event) {
const card = this.data();
const labelId = this.currentData()._id;
card.toggleLabel(labelId);
event.preventDefault();
},
'click .js-edit-label': Popup.open('editLabel'),
'click .js-add-label': Popup.open('createLabel'),
}
];
}
}).register('cardLabelsPopup');
Template.cardLabelsPopup.events({
});
Template.formLabel.events({

View file

@ -2,6 +2,7 @@
// XXX Use .board-widget-labels as a flexbox container
.card-label
border: 1px solid #000000
border-radius: 4px
color: white //Default white text, in select cases, changed to black to improve contrast between label colour and text
display: inline-block
@ -12,10 +13,11 @@
padding: 3px 8px
max-width: 210px
min-width: 8px
overflow: ellipsis
word-wrap: break-word
height: 18px
vertical-align: bottom
min-height: 18px
vertical-align: middle
white-space: initial
overflow: initial
&:hover
color: white
@ -27,12 +29,13 @@
&.add-label
box-shadow: 0 0 0 2px darken(white, 25%) inset
border: initial
&:hover, &.is-active
box-shadow: 0 0 0 2px darken(white, 60%) inset
i.fa-plus
margin-top: 3px
p
margin: 0px
.palette-colors
display: flex
@ -47,7 +50,6 @@
.card-label-white
background-color: #ffffff
color: #000000 //Black text for better visibility
border: 1px solid #c0c0c0
.card-label-white:hover
color: #aaaaaa //grey text for better visibility
@ -144,6 +146,7 @@
height: 25px
margin: 0px 3% 7px 0px
width: 10.5%
max-width: 10.5%
cursor: pointer
.edit-labels
@ -220,3 +223,9 @@
&:hover
background: #dbdbdb
ul.edit-labels-pop-over
span.fa.label-handle
padding-right: 10px;
span.fa.label-handle + .card-label
max-width: 180px

View file

@ -2,21 +2,17 @@ template(name="minicard")
.minicard(
class="{{#if isLinkedCard}}linked-card{{/if}}"
class="{{#if isLinkedBoard}}linked-board{{/if}}"
class="minicard-{{colorClass}}")
if isMiniScreen
class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
if isMiniScreenOrShowDesktopDragHandles
.handle
.fa.fa-arrows
unless isMiniScreen
if showDesktopDragHandles
.handle
.fa.fa-arrows
if cover
.minicard-cover(style="background-image: url('{{cover.url}}');")
if labels
.minicard-labels
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
each labels
unless hiddenMinicardLabelText
span.card-label(class="card-label-{{color}}" title=name)
span.js-card-label.card-label(class="card-label-{{color}}" title=name)
+viewer
= name
if hiddenMinicardLabelText
@ -92,15 +88,17 @@ template(name="minicard")
+viewer
= trueValue
if getAssignees
.minicard-assignees.js-minicard-assignees
each getAssignees
+userAvatar(userId=this)
if showAssignee
if getAssignees
.minicard-assignees.js-minicard-assignees
each getAssignees
+userAvatar(userId=this)
if getMembers
.minicard-members.js-minicard-members
each getMembers
+userAvatar(userId=this)
if showMembers
if getMembers
.minicard-members.js-minicard-members
each getMembers
+userAvatar(userId=this)
if showCreator
.minicard-creator
@ -145,4 +143,9 @@ template(name="minicard")
if currentBoard.allowsCardSortingByNumber
.badge
span.badge-icon.fa.fa-sort
span.badge-text {{ sort }}
span.badge-text.check-list-sort {{ sort }}
template(name="editCardSortOrderPopup")
input.js-edit-card-sort-popup(type='text' autofocus value=sort dir="auto")
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-card-sort-popup(type="submit") {{_ 'save'}}

View file

@ -49,6 +49,38 @@ BlazeComponent.extendComponent({
return false;
},
showMembers() {
if (this.data().board()) {
return (
this.data().board.allowsMembers === null ||
this.data().board().allowsMembers === undefined ||
this.data().board().allowsMembers
);
}
return false;
},
showAssignee() {
if (this.data().board()) {
return (
this.data().board.allowsAssignee === null ||
this.data().board().allowsAssignee === undefined ||
this.data().board().allowsAssignee
);
}
return false;
},
/** opens the card label popup only if clicked onto a label
* <li> this is necessary to have the data context of the minicard.
* if .js-card-label is used at click event, then only the data context of the label itself is available at this.currentData()
*/
cardLabelsPopup(event) {
if (this.find('.js-card-label:hover')) {
Popup.open("cardLabels")(event, {dataContextIfCurrentDataIsUndefined: this.currentData()});
}
},
events() {
return [
{
@ -57,8 +89,6 @@ BlazeComponent.extendComponent({
else if (this.data().isLinkedBoard())
Utils.goBoardId(this.data().linkedId);
},
},
{
'click .js-toggle-minicard-label-text'() {
if (window.localStorage.getItem('hiddenMinicardLabelText')) {
window.localStorage.removeItem('hiddenMinicardLabelText'); //true
@ -66,22 +96,14 @@ BlazeComponent.extendComponent({
window.localStorage.setItem('hiddenMinicardLabelText', 'true'); //true
}
},
},
'click span.badge-icon.fa.fa-sort, click span.badge-text.check-list-sort' : Popup.open("editCardSortOrder"),
'click .minicard-labels' : this.cardLabelsPopup,
}
];
},
}).register('minicard');
Template.minicard.helpers({
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
hiddenMinicardLabelText() {
currentUser = Meteor.user();
if (currentUser) {
@ -93,3 +115,30 @@ Template.minicard.helpers({
}
},
});
BlazeComponent.extendComponent({
events() {
return [
{
'keydown input.js-edit-card-sort-popup'(evt) {
// enter = save
if (evt.keyCode === 13) {
this.find('button[type=submit]').click();
}
},
'click button.js-submit-edit-card-sort-popup'(event) {
// save button pressed
event.preventDefault();
const sort = this.$('.js-edit-card-sort-popup')[0]
.value
.trim();
if (!Number.isNaN(sort)) {
let card = this.data();
card.move(card.boardId, card.swimlaneId, card.listId, sort);
Popup.back();
}
},
}
]
}
}).register('editCardSortOrderPopup');

View file

@ -80,8 +80,6 @@
.minicard-labels
float: none
display: flex
flex-wrap: wrap
.minicard-label
width: 11px
@ -90,6 +88,10 @@
margin-right: 3px
margin-bottom: 3px
.minicard-labels-no-text
display: flex
flex-wrap: wrap
.minicard-custom-fields
display:block;
.minicard-custom-field

View file

@ -1,6 +1,6 @@
template(name="resultCard")
.result-card-wrapper
a.minicard-wrapper.card-title(href=originRelativeUrl)
a.minicard-wrapper.js-minicard.card-title(href=originRelativeUrl)
+minicard(this)
//= card.title
ul.result-card-context-list

View file

@ -5,7 +5,31 @@ Template.resultCard.helpers({
});
BlazeComponent.extendComponent({
clickOnMiniCard(evt) {
evt.preventDefault();
const this_ = this;
const cardId = this.currentData()._id;
const boardId = this.currentData().boardId;
Meteor.subscribe('popupCardData', cardId, {
onReady() {
Session.set('popupCardId', cardId);
Session.set('popupCardBoardId', boardId);
this_.cardDetailsPopup(evt);
},
});
},
cardDetailsPopup(event) {
if (!Popup.isOpen()) {
Popup.open("cardDetails")(event);
}
},
events() {
return [{}];
return [
{
'click .js-minicard': this.clickOnMiniCard,
},
];
},
}).register('resultCard');

View file

@ -37,6 +37,8 @@ BlazeComponent.extendComponent({
? targetBoard.getDefaultSwimline()._id
: targetSwimlane._id;
const nextCardNumber = targetBoard.getNextCardNumber();
if (title) {
const _id = Cards.insert({
title,
@ -49,6 +51,7 @@ BlazeComponent.extendComponent({
sort: sortIndex,
swimlaneId,
type: 'cardType-card',
cardNumber: nextCardNumber
});
// In case the filter is active we need to add the newly inserted card in

View file

@ -247,6 +247,7 @@ textarea
position: absolute
left: -9999px
visibility: hidden
display: none
.materialCheckBox
position: relative

View file

@ -1,3 +1,5 @@
require('/client/lib/jquery-ui.js')
const { calculateIndex } = Utils;
BlazeComponent.extendComponent({
@ -93,7 +95,7 @@ BlazeComponent.extendComponent({
$cards.sortable('cancel');
if (MultiSelection.isActive()) {
Cards.find(MultiSelection.getMongoSelector()).forEach((card, i) => {
Cards.find(MultiSelection.getMongoSelector(), {sort: ['sort']}).forEach((card, i) => {
const newSwimlaneId = targetSwimlaneId
? targetSwimlaneId
: card.swimlaneId || defaultSwimlaneId;
@ -114,25 +116,51 @@ BlazeComponent.extendComponent({
}
boardComponent.setIsDragging(false);
},
sort(event, ui) {
const $boardCanvas = $('.board-canvas');
const boardCanvas = $boardCanvas[0];
if (event.pageX < 10)
{ // scroll to the left
boardCanvas.scrollLeft -= 15;
ui.helper[0].offsetLeft -= 15;
}
if (
event.pageX > boardCanvas.offsetWidth - 10 &&
boardCanvas.scrollLeft < $boardCanvas.data('scrollLeftMax') // don't scroll more than possible
)
{ // scroll to the right
boardCanvas.scrollLeft += 15;
}
if (
event.pageY > boardCanvas.offsetHeight - 10 &&
event.pageY + boardCanvas.scrollTop < $boardCanvas.data('scrollTopMax') // don't scroll more than possible
)
{ // scroll to the bottom
boardCanvas.scrollTop += 15;
}
if (event.pageY < 10)
{ // scroll to the top
boardCanvas.scrollTop -= 15;
}
},
activate(event, ui) {
const $boardCanvas = $('.board-canvas');
const boardCanvas = $boardCanvas[0];
// scrollTopMax and scrollLeftMax only available at Firefox (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTopMax)
// https://www.it-swarm.com.de/de/javascript/so-erhalten-sie-den-maximalen-dokument-scrolltop-wert/1069126844/
$boardCanvas.data('scrollTopMax', boardCanvas.scrollHeight - boardCanvas.clientTop);
// https://stackoverflow.com/questions/5138373/how-do-i-get-the-max-value-of-scrollleft/5704386#5704386
$boardCanvas.data('scrollLeftMax', boardCanvas.scrollWidth - boardCanvas.clientWidth);
},
});
this.autorun(() => {
let showDesktopDragHandles = false;
currentUser = Meteor.user();
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
showDesktopDragHandles = true;
} else {
showDesktopDragHandles = false;
}
if (Utils.isMiniScreen() || showDesktopDragHandles) {
if (Utils.isMiniScreenOrShowDesktopDragHandles()) {
$cards.sortable({
handle: '.handle',
});
} else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
} else {
$cards.sortable({
handle: '.minicard',
});
@ -178,19 +206,6 @@ BlazeComponent.extendComponent({
},
}).register('list');
Template.list.helpers({
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
});
Template.miniList.events({
'click .js-select-list'() {
const listId = this._id;

View file

@ -85,13 +85,9 @@
color: #a6a6a6
.list-header-menu
position: absolute
padding: 27px 19px
margin-top: 1px
top: -7px
right: 3px
float: right
.list-header-plus-icon
.list-header-plus-top
color: #a6a6a6
margin-right: 15px
@ -100,9 +96,10 @@
.cardCount
color: #8c8c8c
font-size: 0.8em
font-size: 12px
font-weight: bold
.list-header .list-header-plus-icon, .js-open-list-menu, .list-header-menu a
.list-header .list-header-plus-top, .js-open-list-menu, .list-header-menu a
color #4d4d4d
padding-left 4px
@ -160,18 +157,6 @@
float: left
@media screen and (max-width: 800px)
.list-header-menu
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
height: auto
@ -215,7 +200,6 @@
display: flex
align-items: center
.list-header-left-icon
display: inline
padding: 7px
padding-right: 27px
margin-top: 1px
@ -238,6 +222,30 @@
right: 10px
font-size: 24px
.list-header
display: grid
grid-template-columns: 30px 5fr 1fr
.list-header-left-icon
display: grid
grid-row: 1/3
grid-column: 1
.list-header-name
grid-row: 1
grid-column: 2
align-self: end
.cardCount
grid-row: 2
grid-column: 2
align-self: start
.list-header-menu
grid-row: 1/3
grid-column: 3
.inlined-form
grid-row: 1/3
grid-column: 1/4
.edit-controls
align-items: initial
.link-board-wrapper
display: flex
align-items: baseline

View file

@ -4,6 +4,17 @@ template(name="listBody")
if cards.count
+inlinedForm(autoclose=false position="top")
+addCardForm(listId=_id position="top")
ul.sidebar-list
each customFieldsSum
li
+viewer
= name
if $eq customFieldsSum.type "number"
+viewer
= value
if $eq customFieldsSum.type "currency"
+viewer
= formattedCurrencyCustomFieldValue(value)
each (cardsWithLimit (idOrNull ../../_id))
a.minicard-wrapper.js-minicard(href=originRelativeUrl
class="{{#if cardIsSelected}}is-selected{{/if}}"
@ -42,6 +53,7 @@ template(name="addCardForm")
.add-controls.clearfix
button.primary.confirm(type="submit") {{_ 'add'}}
a.fa.fa-times-thin.js-close-inlined-form
unless currentBoard.isTemplatesBoard
unless currentBoard.isTemplateBoard
span.quiet

View file

@ -13,6 +13,13 @@ BlazeComponent.extendComponent({
return [];
},
customFieldsSum() {
return CustomFields.find({
boardIds: { $in: [Session.get('currentBoard')] },
showSumAtTopOfList: true,
});
},
openForm(options) {
options = options || {};
options.position = options.position || 'top';
@ -141,6 +148,10 @@ BlazeComponent.extendComponent({
// If the card is already selected, we want to de-select it.
// XXX We should probably modify the minicard href attribute instead of
// overwriting the event in case the card is already selected.
} else if (Utils.isMiniScreen()) {
evt.preventDefault();
Session.set('popupCardId', this.currentData()._id);
this.cardDetailsPopup(evt);
} else if (Session.equals('currentCard', this.currentData()._id)) {
evt.stopImmediatePropagation();
evt.preventDefault();
@ -209,6 +220,12 @@ BlazeComponent.extendComponent({
);
},
cardDetailsPopup(event) {
if (!Popup.isOpen()) {
Popup.open("cardDetails")(event);
}
},
events() {
return [
{
@ -479,7 +496,7 @@ BlazeComponent.extendComponent({
evt.preventDefault();
const linkedId = $('.js-select-cards option:selected').val();
if (!linkedId) {
Popup.close();
Popup.back();
return;
}
const _id = Cards.insert({
@ -494,7 +511,7 @@ BlazeComponent.extendComponent({
linkedId,
});
Filter.addException(_id);
Popup.close();
Popup.back();
},
'click .js-link-board'(evt) {
//LINK BOARD
@ -505,7 +522,7 @@ BlazeComponent.extendComponent({
!impBoardId ||
Cards.findOne({ linkedId: impBoardId, archived: false })
) {
Popup.close();
Popup.back();
return;
}
const _id = Cards.insert({
@ -520,7 +537,7 @@ BlazeComponent.extendComponent({
linkedId: impBoardId,
});
Filter.addException(_id);
Popup.close();
Popup.back();
},
},
];
@ -567,7 +584,7 @@ BlazeComponent.extendComponent({
});
}
if (!board) {
Popup.close();
Popup.back();
return;
}
const boardId = board._id;
@ -694,7 +711,7 @@ BlazeComponent.extendComponent({
},
);
}
Popup.close();
Popup.back();
},
},
];
@ -782,17 +799,12 @@ BlazeComponent.extendComponent({
return false;
}
const spinnerViewPosition = this.spinner.offsetTop - this.container.offsetTop + this.spinner.clientHeight;
const parentViewHeight = this.container.clientHeight;
const bottomViewPosition = this.container.scrollTop + parentViewHeight;
let spinnerOffsetTop = this.spinner.offsetTop;
const addCard = $(this.container).find("a.open-minicard-composer").first()[0];
if (addCard !== undefined) {
spinnerOffsetTop -= addCard.clientHeight;
}
return bottomViewPosition > spinnerOffsetTop;
return bottomViewPosition > spinnerViewPosition;
}
getSkSpinnerName() {

View file

@ -18,9 +18,9 @@ template(name="listHeader")
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.count}}
|/#{wipLimit.value})
if showCardsCountForList cards.count
|&nbsp;
span(class="cardCount") {{cardsCount}} {{_ 'cards-count'}}
if showCardsCountForList cards.count
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.count}}
if isMiniScreen
if currentList
if isWatching
@ -28,7 +28,7 @@ template(name="listHeader")
div.list-header-menu
unless currentUser.isCommentOnly
if canSeeAddCard
a.js-add-card.fa.fa-plus.list-header-plus-icon(title="{{_ 'add-card-to-top-of-list'}}")
a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
else
a.list-header-menu-icon.fa.fa-angle-right.js-select-list
@ -39,12 +39,12 @@ template(name="listHeader")
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}}")
// a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}")
if canSeeAddCard
a.js-add-card.fa.fa-plus.list-header-plus-icon(title="{{_ 'add-card-to-top-of-list'}}")
a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
if currentUser.isBoardAdmin
if showDesktopDragHandles
if isShowDesktopDragHandles
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
template(name="editListTitleForm")
@ -55,6 +55,13 @@ template(name="editListTitleForm")
a.fa.fa-times-thin.js-close-inlined-form
template(name="listActionPopup")
ul.pop-over-list
li
a.js-add-card.list-header-plus-bottom
i.fa.fa-plus
i.fa.fa-arrow-down
| {{_ 'add-card-to-bottom-of-list'}}
hr
ul.pop-over-list
li
a.js-toggle-watch-list

View file

@ -85,6 +85,14 @@ BlazeComponent.extendComponent({
return limit >= 0 && count >= limit;
},
cardsCountForListIsOne(count) {
if (count === 1) {
return TAPi18n.__('cards-count-one');
} else {
return TAPi18n.__('cards-count');
}
},
events() {
return [
{
@ -93,7 +101,7 @@ BlazeComponent.extendComponent({
this.starred(!this.starred());
},
'click .js-open-list-menu': Popup.open('listAction'),
'click .js-add-card'(event) {
'click .js-add-card.list-header-plus-top'(event) {
const listDom = $(event.target).parents(
`#js-list-${this.currentData()._id}`,
)[0];
@ -114,18 +122,7 @@ BlazeComponent.extendComponent({
Template.listHeader.helpers({
isBoardAdmin() {
return Meteor.user().isBoardAdmin();
},
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
}
});
Template.listActionPopup.helpers({
@ -144,23 +141,31 @@ Template.listActionPopup.helpers({
Template.listActionPopup.events({
'click .js-list-subscribe'() {},
'click .js-add-card.list-header-plus-bottom'(event) {
const listDom = $(`#js-list-${this._id}`)[0];
const listComponent = BlazeComponent.getComponentForElement(listDom);
listComponent.openForm({
position: 'bottom',
});
Popup.back();
},
'click .js-set-color-list': Popup.open('setListColor'),
'click .js-select-cards'() {
const cardIds = this.allCards().map(card => card._id);
MultiSelection.add(cardIds);
Popup.close();
Popup.back();
},
'click .js-toggle-watch-list'() {
const currentList = this;
const level = currentList.findWatcher(Meteor.userId()) ? null : 'watching';
Meteor.call('watch', 'list', currentList._id, level, (err, ret) => {
if (!err && ret) Popup.close();
if (!err && ret) Popup.back();
});
},
'click .js-close-list'(event) {
event.preventDefault();
this.archive();
Popup.close();
Popup.back();
},
'click .js-set-wip-limit': Popup.open('setWipLimit'),
'click .js-more': Popup.open('listMore'),
@ -236,7 +241,7 @@ BlazeComponent.extendComponent({
Template.listMorePopup.events({
'click .js-delete': Popup.afterConfirm('listDelete', function() {
Popup.close();
Popup.back();
// TODO how can we avoid the fetch call?
const allCards = this.allCards().fetch();
const allCardIds = _.pluck(allCards, '_id');
@ -302,11 +307,11 @@ BlazeComponent.extendComponent({
},
'click .js-submit'() {
this.currentList.setColor(this.currentColor.get());
Popup.close();
Popup.back();
},
'click .js-remove-color'() {
this.currentList.setColor(null);
Popup.close();
Popup.back();
},
},
];

View file

@ -38,12 +38,12 @@ BlazeComponent.extendComponent({
{
'click .js-due-cards-view-me'() {
Utils.setDueCardsView('me');
Popup.close();
Popup.back();
},
'click .js-due-cards-view-all'() {
Utils.setDueCardsView('all');
Popup.close();
Popup.back();
},
},
];

View file

@ -1,4 +1,6 @@
template(name="editor")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.editor(
dir="auto"
class="{{class}}"

View file

@ -4,281 +4,299 @@ const specialHandles = [
];
const specialHandleNames = specialHandles.map(m => m.username);
Template.editor.onRendered(() => {
const textareaSelector = 'textarea';
const mentions = [
// User mentions
{
match: /\B@([\w.]*)$/,
search(term, callback) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
callback(
_.union(
currentBoard
.activeMembers()
.map(member => {
const username = Users.findOne(member.userId).username;
return username.includes(term) ? username : null;
})
.filter(Boolean), [...specialHandleNames])
);
},
template(value) {
return value;
},
replace(username) {
return `@${username} `;
},
index: 1,
},
];
const enableTextarea = function() {
const $textarea = this.$(textareaSelector);
autosize($textarea);
$textarea.escapeableTextComplete(mentions);
};
if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false) {
const isSmall = Utils.isMiniScreen();
const toolbar = isSmall
? [
['view', ['fullscreen']],
['table', ['table']],
['font', ['bold', 'underline']],
//['fontsize', ['fontsize']],
['color', ['color']],
]
: [
['style', ['style']],
['font', ['bold', 'underline', 'clear']],
['fontsize', ['fontsize']],
['fontname', ['fontname']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
//['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
['insert', ['link']], //, 'picture']], // modal popup has issue somehow :(
['view', ['fullscreen', 'codeview', 'help']],
];
const cleanPastedHTML = function(input) {
const badTags = [
'style',
'script',
'applet',
'embed',
'noframes',
'noscript',
'meta',
'link',
'button',
'form',
].join('|');
const badPatterns = new RegExp(
`(?:${[
`<(${badTags})s*[^>][\\s\\S]*?<\\/\\1>`,
`<(${badTags})[^>]*?\\/>`,
].join('|')})`,
'gi',
);
let output = input;
// remove bad Tags
output = output.replace(badPatterns, '');
// remove attributes ' style="..."'
const badAttributes = new RegExp(
`(?:${[
'on\\S+=([\'"]?).*?\\1',
'href=([\'"]?)javascript:.*?\\2',
'style=([\'"]?).*?\\3',
'target=\\S+',
].join('|')})`,
'gi',
);
output = output.replace(badAttributes, '');
output = output.replace(/(<a )/gi, '$1target=_ '); // always to new target
return output;
};
const editor = '.editor';
const selectors = [
`.js-new-description-form ${editor}`,
`.js-new-comment-form ${editor}`,
`.js-edit-comment ${editor}`,
].join(','); // only new comment and edit comment
const inputs = $(selectors);
if (inputs.length === 0) {
// only enable richereditor to new comment or edit comment no others
enableTextarea();
} else {
const placeholder = inputs.attr('placeholder') || '';
const mSummernotes = [];
const getSummernote = function(input) {
const idx = inputs.index(input);
if (idx > -1) {
return mSummernotes[idx];
}
return undefined;
};
inputs.each(function(idx, input) {
mSummernotes[idx] = $(input).summernote({
placeholder,
callbacks: {
onInit(object) {
const originalInput = this;
$(originalInput).on('submitted', function() {
// when comment is submitted, the original textarea will be set to '', so shall we
if (!this.value) {
const sn = getSummernote(this);
sn && sn.summernote('code', '');
}
});
const jEditor = object && object.editable;
const toolbar = object && object.toolbar;
if (jEditor !== undefined) {
jEditor.escapeableTextComplete(mentions);
}
if (toolbar !== undefined) {
const fBtn = toolbar.find('.btn-fullscreen');
fBtn.on('click', function() {
const $this = $(this),
isActive = $this.hasClass('active');
$('.minicards,#header-quick-access').toggle(!isActive); // mini card is still showing when editor is in fullscreen mode, we hide here manually
});
}
},
onImageUpload(files) {
const $summernote = getSummernote(this);
if (files && files.length > 0) {
const image = files[0];
const currentCard = Cards.findOne(Session.get('currentCard'));
const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
const insertImage = src => {
// process all image upload types to the description/comment window
const img = document.createElement('img');
img.src = src;
img.setAttribute('width', '100%');
$summernote.summernote('insertNode', img);
};
const processData = function(fileObj) {
Utils.processUploadedAttachment(
currentCard,
fileObj,
attachment => {
if (
attachment &&
attachment._id &&
attachment.isImage()
) {
attachment.one('uploaded', function() {
const maxTry = 3;
const checkItvl = 500;
let retry = 0;
const checkUrl = function() {
// even though uploaded event fired, attachment.url() is still null somehow //TODO
const url = attachment.url();
if (url) {
insertImage(
`${location.protocol}//${location.host}${url}`,
);
} else {
retry++;
if (retry < maxTry) {
setTimeout(checkUrl, checkItvl);
BlazeComponent.extendComponent({
onRendered() {
const textareaSelector = 'textarea';
const mentions = [
// User mentions
{
match: /\B@([\w.]*)$/,
search(term, callback) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
callback(
_.union(
currentBoard
.activeMembers()
.map(member => {
const user = Users.findOne(member.userId);
const username = user.username;
const fullName = user.profile && user.profile !== undefined ? user.profile.fullname : "";
return username.includes(term) || fullName.includes(term) ? fullName + "(" + username + ")" : null;
})
.filter(Boolean), [...specialHandleNames])
);
},
template(value) {
return value;
},
replace(username) {
return `@${username} `;
},
index: 1,
},
];
const enableTextarea = function() {
const $textarea = this.$(textareaSelector);
autosize($textarea);
$textarea.escapeableTextComplete(mentions);
};
if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false) {
const isSmall = Utils.isMiniScreen();
const toolbar = isSmall
? [
['view', ['fullscreen']],
['table', ['table']],
['font', ['bold', 'underline']],
//['fontsize', ['fontsize']],
['color', ['color']],
]
: [
['style', ['style']],
['font', ['bold', 'underline', 'clear']],
['fontsize', ['fontsize']],
['fontname', ['fontname']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
//['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
['insert', ['link']], //, 'picture']], // modal popup has issue somehow :(
['view', ['fullscreen', 'codeview', 'help']],
];
const cleanPastedHTML = function(input) {
const badTags = [
'style',
'script',
'applet',
'embed',
'noframes',
'noscript',
'meta',
'link',
'button',
'form',
].join('|');
const badPatterns = new RegExp(
`(?:${[
`<(${badTags})s*[^>][\\s\\S]*?<\\/\\1>`,
`<(${badTags})[^>]*?\\/>`,
].join('|')})`,
'gi',
);
let output = input;
// remove bad Tags
output = output.replace(badPatterns, '');
// remove attributes ' style="..."'
const badAttributes = new RegExp(
`(?:${[
'on\\S+=([\'"]?).*?\\1',
'href=([\'"]?)javascript:.*?\\2',
'style=([\'"]?).*?\\3',
'target=\\S+',
].join('|')})`,
'gi',
);
output = output.replace(badAttributes, '');
output = output.replace(/(<a )/gi, '$1target=_ '); // always to new target
return output;
};
const editor = '.editor';
const selectors = [
`.js-new-description-form ${editor}`,
`.js-new-comment-form ${editor}`,
`.js-edit-comment ${editor}`,
].join(','); // only new comment and edit comment
const inputs = $(selectors);
if (inputs.length === 0) {
// only enable richereditor to new comment or edit comment no others
enableTextarea();
} else {
const placeholder = inputs.attr('placeholder') || '';
const mSummernotes = [];
const getSummernote = function(input) {
const idx = inputs.index(input);
if (idx > -1) {
return mSummernotes[idx];
}
return undefined;
};
inputs.each(function(idx, input) {
mSummernotes[idx] = $(input).summernote({
placeholder,
callbacks: {
onInit(object) {
const originalInput = this;
$(originalInput).on('submitted', function() {
// when comment is submitted, the original textarea will be set to '', so shall we
if (!this.value) {
const sn = getSummernote(this);
sn && sn.summernote('code', '');
}
});
const jEditor = object && object.editable;
const toolbar = object && object.toolbar;
if (jEditor !== undefined) {
jEditor.escapeableTextComplete(mentions);
}
if (toolbar !== undefined) {
const fBtn = toolbar.find('.btn-fullscreen');
fBtn.on('click', function() {
const $this = $(this),
isActive = $this.hasClass('active');
$('.minicards,#header-quick-access').toggle(!isActive); // mini card is still showing when editor is in fullscreen mode, we hide here manually
});
}
},
onImageUpload(files) {
const $summernote = getSummernote(this);
if (files && files.length > 0) {
const image = files[0];
const currentCard = Utils.getCurrentCard();
const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
const insertImage = src => {
// process all image upload types to the description/comment window
const img = document.createElement('img');
img.src = src;
img.setAttribute('width', '100%');
$summernote.summernote('insertNode', img);
};
const processData = function(fileObj) {
Utils.processUploadedAttachment(
currentCard,
fileObj,
attachment => {
if (
attachment &&
attachment._id &&
attachment.isImage()
) {
attachment.one('uploaded', function() {
const maxTry = 3;
const checkItvl = 500;
let retry = 0;
const checkUrl = function() {
// even though uploaded event fired, attachment.url() is still null somehow //TODO
const url = attachment.url();
if (url) {
insertImage(
`${location.protocol}//${location.host}${url}`,
);
} else {
retry++;
if (retry < maxTry) {
setTimeout(checkUrl, checkItvl);
}
}
};
checkUrl();
});
}
},
);
};
if (MAX_IMAGE_PIXEL) {
const reader = new FileReader();
reader.onload = function(e) {
const dataurl = e && e.target && e.target.result;
if (dataurl !== undefined) {
// need to shrink image
Utils.shrinkImage({
dataurl,
maxSize: MAX_IMAGE_PIXEL,
ratio: COMPRESS_RATIO,
toBlob: true,
callback(blob) {
if (blob !== false) {
blob.name = image.name;
processData(blob);
}
};
checkUrl();
},
});
}
},
);
};
if (MAX_IMAGE_PIXEL) {
const reader = new FileReader();
reader.onload = function(e) {
const dataurl = e && e.target && e.target.result;
if (dataurl !== undefined) {
// need to shrink image
Utils.shrinkImage({
dataurl,
maxSize: MAX_IMAGE_PIXEL,
ratio: COMPRESS_RATIO,
toBlob: true,
callback(blob) {
if (blob !== false) {
blob.name = image.name;
processData(blob);
}
},
});
}
};
reader.readAsDataURL(image);
} else {
processData(image);
};
reader.readAsDataURL(image);
} else {
processData(image);
}
}
}
},
onPaste(e) {
var clipboardData = e.clipboardData;
var pastedData = clipboardData.getData('Text');
},
onPaste(e) {
var clipboardData = e.clipboardData;
var pastedData = clipboardData.getData('Text');
//if pasted data is an image, exit
if (!pastedData.length) {
e.preventDefault();
return;
}
//if pasted data is an image, exit
if (!pastedData.length) {
e.preventDefault();
return;
}
// clear up unwanted tag info when user pasted in text
const thisNote = this;
const updatePastedText = function(object) {
const someNote = getSummernote(object);
// Fix Pasting text into a card is adding a line before and after
// (and multiplies by pasting more) by changing paste "p" to "br".
// Fixes https://github.com/wekan/wekan/2890 .
// == Fix Start ==
someNote.execCommand('defaultParagraphSeparator', false, 'br');
// == Fix End ==
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('code', ''); //clear original
someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code.
};
setTimeout(function() {
//this kinda sucks, but if you don't do a setTimeout,
//the function is called before the text is really pasted.
updatePastedText(thisNote);
}, 10);
// clear up unwanted tag info when user pasted in text
const thisNote = this;
const updatePastedText = function(object) {
const someNote = getSummernote(object);
// Fix Pasting text into a card is adding a line before and after
// (and multiplies by pasting more) by changing paste "p" to "br".
// Fixes https://github.com/wekan/wekan/2890 .
// == Fix Start ==
someNote.execCommand('defaultParagraphSeparator', false, 'br');
// == Fix End ==
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('code', ''); //clear original
someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code.
};
setTimeout(function() {
//this kinda sucks, but if you don't do a setTimeout,
//the function is called before the text is really pasted.
updatePastedText(thisNote);
}, 10);
},
},
},
dialogsInBody: true,
spellCheck: true,
disableGrammar: false,
disableDragAndDrop: false,
toolbar,
popover: {
image: [
['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']],
['float', ['floatLeft', 'floatRight', 'floatNone']],
['remove', ['removeMedia']],
],
link: [['link', ['linkDialogShow', 'unlink']]],
table: [
['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']],
['delete', ['deleteRow', 'deleteCol', 'deleteTable']],
],
air: [
['color', ['color']],
['font', ['bold', 'underline', 'clear']],
],
},
height: 200,
dialogsInBody: true,
spellCheck: true,
disableGrammar: false,
disableDragAndDrop: false,
toolbar,
popover: {
image: [
['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']],
['float', ['floatLeft', 'floatRight', 'floatNone']],
['remove', ['removeMedia']],
],
link: [['link', ['linkDialogShow', 'unlink']]],
table: [
['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']],
['delete', ['deleteRow', 'deleteCol', 'deleteTable']],
],
air: [
['color', ['color']],
['font', ['bold', 'underline', 'clear']],
],
},
height: 200,
});
});
});
}
} else {
enableTextarea();
}
} else {
enableTextarea();
},
events() {
return [
{
'click a.fa.fa-copy'(event) {
const $editor = this.$('textarea.editor');
const promise = Utils.copyTextToClipboard($editor[0].value);
const $tooltip = this.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
}
]
}
});
}).register('editor');
import DOMPurify from 'dompurify';

View file

@ -0,0 +1,7 @@
.new-comment,
.inlined-form
a.fa.fa-copy
float: right
position: relative
top: 20px
right: 6px

View file

@ -16,12 +16,14 @@ template(name="header")
each currentBoard.lists
li(class="{{#if $.Session.equals 'currentList' _id}}current{{/if}}")
a.js-select-list
= title
+viewer
= title
else
each currentUser.starredBoards
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
a(href="{{pathFor 'board' id=_id slug=slug}}")
= title
+viewer
= title
#header-new-board-icon
else
//-
@ -36,7 +38,8 @@ template(name="header")
unless currentSetting.customTopLeftCornerLogoLinkUrl
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
unless currentSetting.customTopLeftCornerLogoImageUrl
img(src="{{pathFor '/logo-header.png'}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
div#headerIsSettingDatabaseCallDone
img(src="{{pathFor '/logo-header.png'}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
span.allBoards
a(href="{{pathFor 'home'}}")
span.fa.fa-home
@ -49,7 +52,8 @@ template(name="header")
each currentUser.starredBoards
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
a(href="{{pathFor 'board' id=_id slug=slug}}")
= title
+viewer
= title
else
li.current.empty {{_ 'quick-access-description'}}
@ -90,3 +94,5 @@ template(name="offlineWarning")
p
i.fa.fa-warning
| {{_ 'app-is-offline'}}
a.app-try-reconnect {{_ 'app-try-reconnect'}}

View file

@ -1,7 +1,23 @@
Meteor.subscribe('user-admin');
Meteor.subscribe('boards');
Meteor.subscribe('setting');
Template.header.onCreated(function(){
const templateInstance = this;
templateInstance.currentSetting = new ReactiveVar();
templateInstance.isLoading = new ReactiveVar(false);
Meteor.subscribe('setting', {
onReady() {
templateInstance.currentSetting.set(Settings.findOne());
let currSetting = templateInstance.currentSetting.curValue;
if(currSetting && currSetting !== undefined && currSetting.customLoginLogoImageUrl !== undefined && document.getElementById("headerIsSettingDatabaseCallDone") != null)
document.getElementById("headerIsSettingDatabaseCallDone").style.display = 'none';
else if(document.getElementById("headerIsSettingDatabaseCallDone") != null)
document.getElementById("headerIsSettingDatabaseCallDone").style.display = 'block';
return this.stop();
},
});
});
Template.header.helpers({
wrappedHeader() {
return !Session.get('currentBoard');
@ -41,3 +57,10 @@ Template.header.events({
Session.set('currentCard', null);
},
});
Template.offlineWarning.events({
'click a.app-try-reconnect'(event) {
event.preventDefault();
Meteor.reconnect();
},
});

View file

@ -135,6 +135,14 @@
padding: 12px 10px
margin: -10px 0px
.viewer
display: inline
white-space: nowrap
p
display: inline
white-space: nowrap
&.current
color: darken(white, 5%)
@ -242,3 +250,6 @@
p
margin: 7px
padding: 0
#headerIsSettingDatabaseCallDone
display: none;

View file

@ -34,8 +34,9 @@ template(name="userFormsLayout")
img(src="{{currentSetting.customLoginLogoImageUrl}}" width="300" height="auto")
br
unless currentSetting.customLoginLogoImageUrl
img(src="{{pathFor '/wekan-logo.svg'}}" alt="" width="300" height="auto")
br
div#isSettingDatabaseCallDone
img(src="{{pathFor '/wekan-logo.svg'}}" alt="" width="300" height="auto")
br
if currentSetting.textBelowCustomLoginLogo
+viewer
| {{currentSetting.textBelowCustomLoginLogo}}
@ -47,6 +48,13 @@ template(name="userFormsLayout")
+Template.dynamic(template=content)
if currentSetting.displayAuthenticationMethod
+connectionMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)
if isLegalNoticeLinkExist
div#legalNoticeDiv
span#legalNoticeSpan {{_ 'acceptance_of_our_legalNotice'}}
a#legalNoticeAtLink.at-link(href="{{currentSetting.legalNotice}}", target="_blank", rel="noopener noreferrer")
| {{_ 'legalNotice'}}
if getLegalNoticeWithWritTraduction
div
div.at-form-lang
select.select-lang.js-userform-set-language
each languages

View file

@ -6,6 +6,9 @@ const i18nTagToT9n = i18nTag => {
return i18nTag;
};
let alreadyCheck = 1;
let isCheckDone = false;
const validator = {
set(obj, prop, value) {
if (prop === 'state' && value !== 'signIn') {
@ -20,6 +23,8 @@ const validator = {
},
};
// let isSettingDatabaseFctCallDone = false;
Template.userFormsLayout.onCreated(function() {
const templateInstance = this;
templateInstance.currentSetting = new ReactiveVar();
@ -28,6 +33,18 @@ Template.userFormsLayout.onCreated(function() {
Meteor.subscribe('setting', {
onReady() {
templateInstance.currentSetting.set(Settings.findOne());
let currSetting = templateInstance.currentSetting.curValue;
let oidcBtnElt = $("#at-oidc");
if(currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined){
let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
oidcBtnElt.html(htmlvalue);
}
// isSettingDatabaseFctCallDone = true;
if(currSetting && currSetting !== undefined && currSetting.customLoginLogoImageUrl !== undefined)
document.getElementById("isSettingDatabaseCallDone").style.display = 'none';
else
document.getElementById("isSettingDatabaseCallDone").style.display = 'block';
return this.stop();
},
});
@ -56,6 +73,31 @@ Template.userFormsLayout.helpers({
return Template.instance().currentSetting.get();
},
// isSettingDatabaseCallDone(){
// return isSettingDatabaseFctCallDone;
// },
isLegalNoticeLinkExist(){
const currSet = Template.instance().currentSetting.get();
if(currSet && currSet !== undefined && currSet != null){
return currSet.legalNotice !== undefined && currSet.legalNotice.trim() != "";
}
else
return false;
},
getLegalNoticeWithWritTraduction(){
let spanLegalNoticeElt = $("#legalNoticeSpan");
if(spanLegalNoticeElt != null && spanLegalNoticeElt != undefined){
spanLegalNoticeElt.html(TAPi18n.__('acceptance_of_our_legalNotice', {}, T9n.getLanguage() || 'en'));
}
let atLinkLegalNoticeElt = $("#legalNoticeAtLink");
if(atLinkLegalNoticeElt != null && atLinkLegalNoticeElt != undefined){
atLinkLegalNoticeElt.html(TAPi18n.__('legalNotice', {}, T9n.getLanguage() || 'en'));
}
return true;
},
isLoading() {
return Template.instance().isLoading.get();
},
@ -79,6 +121,10 @@ Template.userFormsLayout.helpers({
name = 'مَصرى';
} else if (lang.name === 'de-CH') {
name = 'Deutsch (Schweiz)';
} else if (lang.name === 'de-AT') {
name = 'Deutsch (Österreich)';
} else if (lang.name === 'en-DE') {
name = 'English (Germany)';
} else if (lang.name === 'fa-IR') {
// fa-IR = Persian (Iran)
name = 'فارسی/پارسی (ایران‎)';
@ -86,14 +132,28 @@ Template.userFormsLayout.helpers({
name = 'Français (Belgique)';
} else if (lang.name === 'fr-CA') {
name = 'Français (Canada)';
} else if (lang.name === 'fr-CH') {
name = 'Français (Schweiz)';
} else if (lang.name === 'gu-IN') {
// gu-IN = Gurajati (India)
name = 'ગુજરાતી';
} else if (lang.name === 'hi-IN') {
// hi-IN = Hindi (India)
name = 'हिंदी (भारत)';
} else if (lang.name === 'ig') {
name = 'Igbo';
} else if (lang.name === 'lv') {
name = 'Latviešu';
} else if (lang.name === 'latviešu valoda') {
name = 'Latviešu';
} else if (lang.name === 'ms-MY') {
// ms-MY = Malay (Malaysia)
name = 'بهاس ملايو';
} else if (lang.name === 'en-IT') {
name = 'English (Italy)';
} else if (lang.name === 'el-GR') {
// el-GR = Greek (Greece)
name = 'Ελληνικά (Ελλάδα)';
} else if (lang.name === 'Español') {
name = 'español';
} else if (lang.name === 'es_419') {
@ -125,6 +185,7 @@ Template.userFormsLayout.helpers({
} else if (lang.name === 'st') {
name = 'Sãotomense';
} else if (lang.name === '繁体中文(台湾)') {
// Traditional Chinese (Taiwan)
name = '繁體中文(台灣)';
}
return { tag, name };
@ -157,6 +218,53 @@ Template.userFormsLayout.events({
templateInstance.isLoading.set(false);
});
}
isCheckDone = false;
},
'click #at-signUp'(event, templateInstance){
isCheckDone = false;
},
'DOMSubtreeModified #at-oidc'(event){
if(alreadyCheck <= 2){
let currSetting = Settings.findOne();
let oidcBtnElt = $("#at-oidc");
if(currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined){
let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
if(alreadyCheck == 1){
alreadyCheck++;
oidcBtnElt.html("");
}
else{
alreadyCheck++;
oidcBtnElt.html(htmlvalue);
}
}
}
else{
alreadyCheck = 1;
}
},
'DOMSubtreeModified .at-form'(event){
if(alreadyCheck <= 2 && !isCheckDone){
if(document.getElementById("at-oidc") != null){
let currSetting = Settings.findOne();
let oidcBtnElt = $("#at-oidc");
if(currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined){
let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
if(alreadyCheck == 1){
alreadyCheck++;
oidcBtnElt.html("");
}
else{
alreadyCheck++;
isCheckDone = true;
oidcBtnElt.html(htmlvalue);
}
}
}
}
else{
alreadyCheck = 1;
}
},
});

View file

@ -433,7 +433,7 @@ a
margin-top: 0px
.wrapper
height: 100%
height: calc(100% - 31px)
margin: 0px
.panel-default
@ -542,3 +542,11 @@ a
100%
transform: rotate(360deg)
#isSettingDatabaseCallDone
display: none;
.at-link
color: #17683a;
text-decoration: underline;
text-decoration-color: #17683a;

View file

@ -14,8 +14,7 @@ $popupWidth = 300px
margin-top: 5px
hr
margin: 4px -10px
width: $popupWidth
margin: 4px 0px
p,
textarea,
@ -23,7 +22,6 @@ $popupWidth = 300px
input[type="email"],
input[type="password"],
input[type="file"]
margin: 4px 0 12px
width: 100%
select
@ -313,22 +311,13 @@ $popupWidth = 300px
input[type="email"],
input[type="password"],
input[type="file"]
margin: 4px 0 12px
width: 100%
box-sizing: border-box
.pop-over-list
li > a
width: calc(100% - 20px)
padding: 10px 10px
margin: 0px 0px
border-bottom: 1px solid #eee
hr
width: 100%
height: 20px
margin: 0px 0px
color: #eee
for depth in (1..6)
.popup-container-depth-{depth}

View file

@ -232,7 +232,7 @@ BlazeComponent.extendComponent({
},
'click .js-submit'() {
this.colorButtonValue.set(this.currentColor.get());
Popup.close();
Popup.back();
},
},
];

View file

@ -116,6 +116,6 @@ Template.boardCardTitlePopup.events({
.trim();
Popup.getOpenerComponent().setNameFilter(title);
event.preventDefault();
Popup.close();
Popup.back();
},
});

View file

@ -21,7 +21,7 @@ template(name='statistics')
table
tbody
tr
th Wekan {{_ 'info'}}
th WeKan ® {{_ 'info'}}
td {{statistics.version}}
tr
th {{_ 'Meteor_version'}}
@ -65,3 +65,49 @@ template(name='statistics')
tr
th {{_ 'OS_Cpus'}}
td {{statistics.os.cpus.length}}
unless isSandstorm
tr
th {{_ 'Node_heap_total_heap_size'}}
td {{bytesToSize statistics.nodeHeapStats.totalHeapSize}}
tr
th {{_ 'Node_heap_total_heap_size_executable'}}
td {{bytesToSize statistics.nodeHeapStats.totalHeapSizeExecutable}}
tr
th {{_ 'Node_heap_total_physical_size'}}
td {{bytesToSize statistics.nodeHeapStats.totalPhysicalSize}}
tr
th {{_ 'Node_heap_total_available_size'}}
td {{bytesToSize statistics.nodeHeapStats.totalAvailableSize}}
tr
th {{_ 'Node_heap_used_heap_size'}}
td {{bytesToSize statistics.nodeHeapStats.usedHeapSize}}
tr
th {{_ 'Node_heap_heap_size_limit'}}
td {{bytesToSize statistics.nodeHeapStats.heapSizeLimit}}
tr
th {{_ 'Node_heap_malloced_memory'}}
td {{bytesToSize statistics.nodeHeapStats.mallocedMemory}}
tr
th {{_ 'Node_heap_peak_malloced_memory'}}
td {{bytesToSize statistics.nodeHeapStats.peakMallocedMemory}}
tr
th {{_ 'Node_heap_does_zap_garbage'}}
td {{statistics.nodeHeapStats.doesZapGarbage}}
tr
th {{_ 'Node_heap_number_of_native_contexts'}}
td {{statistics.nodeHeapStats.numberOfNativeContexts}}
tr
th {{_ 'Node_heap_number_of_detached_contexts'}}
td {{statistics.nodeHeapStats.numberOfDetachedContexts}}
tr
th {{_ 'Node_memory_usage_rss'}}
td {{bytesToSize statistics.nodeMemoryUsage.rss}}
tr
th {{_ 'Node_memory_usage_heap_total'}}
td {{bytesToSize statistics.nodeMemoryUsage.heapTotal}}
tr
th {{_ 'Node_memory_usage_heap_used'}}
td {{bytesToSize statistics.nodeMemoryUsage.heapUsed}}
tr
th {{_ 'Node_memory_usage_external'}}
td {{bytesToSize statistics.nodeMemoryUsage.external}}

View file

@ -40,6 +40,11 @@ template(name="people")
| {{_ 'search'}}
.ext-box-right
span {{#unless isMiniScreen}}{{_ 'people-number'}}{{/unless}} #{peopleNumber}
.divAddOrRemoveTeam#divAddOrRemoveTeam
button#addOrRemoveTeam
i.fa.fa-edit
| {{_ 'add'}} / {{_ 'delete'}} {{_ 'teams'}}
.content-body
.side-menu
ul
@ -97,9 +102,13 @@ template(name="teamGeneral")
+teamRow(teamId=team._id)
template(name="peopleGeneral")
#divAddOrRemoveTeamContainer
+modifyTeamsUsers
table
tbody
tr
th
+selectAllUser
th {{_ 'username'}}
th {{_ 'fullname'}}
th {{_ 'initials'}}
@ -117,6 +126,10 @@ template(name="peopleGeneral")
each user in peopleList
+peopleRow(userId=user._id)
template(name="selectAllUser")
| {{_ 'dueCardsViewChange-choice-all'}}
input.allUserChkBox(type="checkbox", id="chkSelectAll")
template(name="newOrgRow")
a.new-org
i.fa.fa-plus-square
@ -202,6 +215,12 @@ template(name="teamRow")
template(name="peopleRow")
tr
if userData.loginDisabled
td
input.selectUserChkBox(type="checkbox", disabled="disabled", id="{{userData._id}}")
else
td
input.selectUserChkBox(type="checkbox", id="{{userData._id}}")
if userData.loginDisabled
td.username <s>{{ userData.username }}</s>
else
@ -342,7 +361,7 @@ template(name="editUserPopup")
input.js-profile-fullname(type="text" value=user.profile.fullname required)
label
| {{_ 'initials'}}
input.js-profile-initials(type="text" value=user.profile.initials required)
input.js-profile-initials(type="text" value=user.profile.initials)
label
| {{_ 'admin'}}
select.select-role.js-profile-isadmin
@ -453,6 +472,24 @@ template(name="newTeamPopup")
div.buttonsContainer
input.primary.wide(type="submit" value="{{_ 'save'}}")
template(name="modifyTeamsUsers")
label
| {{_ 'teams'}}
select.js-teamsUser#jsteamsUser
each value in teamsDatas
option(value="{{value._id}}") {{_ value.teamDisplayName}}
hr
label
| {{_ 'r-action'}}
.form-group.flex
input.wekan-form-control#addAction(type="radio" name="action" value="true" checked="checked")
span {{_ 'add'}}
input.wekan-form-control#deleteAction(type="radio" name="action" value="false")
span {{_ 'delete'}}
div.buttonsContainer
input.primary.wide#addTeamBtn(type="submit" value="{{_ 'save'}}")
input.primary.wide#cancelBtn(type="submit" value="{{_ 'cancel'}}")
template(name="newUserPopup")
form
//label.hide.userId(type="text" value=user._id)
@ -469,7 +506,7 @@ template(name="newUserPopup")
input.js-profile-username(type="text" value="" required)
label
| {{_ 'initials'}}
input.js-profile-initials(type="text" value="" required)
input.js-profile-initials(type="text" value="")
label
| {{_ 'email'}}
span.error.hide.email-taken
@ -571,10 +608,14 @@ template(name="settingsUserPopup")
a.impersonate-user
i.fa.fa-user
| {{_ 'impersonate-user'}}
br
hr
li
form
label.hide.userId(type="text" value=user._id)
label
| {{_ 'delete-user-confirm-popup' }}
br
div.buttonsContainer
input#deleteButton.card-details-red.right.wide(type="button" value="{{_ 'delete'}}")
// Delete is enabled, but there is still bug of leaving empty user avatars

View file

@ -2,6 +2,7 @@ const orgsPerPage = 25;
const teamsPerPage = 25;
const usersPerPage = 25;
let userOrgsTeamsAction = ""; //poosible actions 'addOrg', 'addTeam', 'removeOrg' or 'removeTeam' when adding or modifying a user
let selectedUserChkBoxUserIds = [];
BlazeComponent.extendComponent({
mixins() {
@ -81,6 +82,9 @@ BlazeComponent.extendComponent({
'click #searchButton'() {
this.filterPeople();
},
'click #addOrRemoveTeam'(){
document.getElementById("divAddOrRemoveTeamContainer").style.display = 'block';
},
'keydown #searchInput'(event) {
if (event.keyCode === 13 && !event.shiftKey) {
this.filterPeople();
@ -140,6 +144,7 @@ BlazeComponent.extendComponent({
},
orgList() {
const orgs = Org.find(this.findOrgsOptions.get(), {
sort: { orgDisplayName: 1 },
fields: { _id: true },
});
this.numberOrgs.set(orgs.count(false));
@ -147,6 +152,7 @@ BlazeComponent.extendComponent({
},
teamList() {
const teams = Team.find(this.findTeamsOptions.get(), {
sort: { teamDisplayName: 1 },
fields: { _id: true },
});
this.numberTeams.set(teams.count(false));
@ -154,6 +160,7 @@ BlazeComponent.extendComponent({
},
peopleList() {
const users = Users.find(this.findUsersOptions.get(), {
sort: { username: 1 },
fields: { _id: true },
});
this.numberPeople.set(users.count(false));
@ -247,10 +254,10 @@ Template.editUserPopup.helpers({
return Template.instance().authenticationMethods.get();
},
orgsDatas() {
return Org.find({}, {sort: { createdAt: -1 }});
return Org.find({}, {sort: { orgDisplayName: 1 }});
},
teamsDatas() {
return Team.find({}, {sort: { createdAt: -1 }});
return Team.find({}, {sort: { teamDisplayName: 1 }});
},
isSelected(match) {
const userId = Template.instance().data.userId;
@ -320,10 +327,10 @@ Template.newUserPopup.helpers({
return Template.instance().authenticationMethods.get();
},
orgsDatas() {
return Org.find({}, {sort: { createdAt: -1 }});
return Org.find({}, {sort: { orgDisplayName: 1 }});
},
teamsDatas() {
return Team.find({}, {sort: { createdAt: -1 }});
return Team.find({}, {sort: { teamDisplayName: 1 }});
},
isSelected(match) {
const userId = Template.instance().data.userId;
@ -385,11 +392,111 @@ BlazeComponent.extendComponent({
{
'click a.edit-user': Popup.open('editUser'),
'click a.more-settings-user': Popup.open('settingsUser'),
'click .selectUserChkBox': function(ev){
if(ev.currentTarget){
if(ev.currentTarget.checked){
if(!selectedUserChkBoxUserIds.includes(ev.currentTarget.id)){
selectedUserChkBoxUserIds.push(ev.currentTarget.id);
}
}
else{
if(selectedUserChkBoxUserIds.includes(ev.currentTarget.id)){
let index = selectedUserChkBoxUserIds.indexOf(ev.currentTarget.id);
if(index > -1)
selectedUserChkBoxUserIds.splice(index, 1);
}
}
}
if(selectedUserChkBoxUserIds.length > 0)
document.getElementById("divAddOrRemoveTeam").style.display = 'block';
else
document.getElementById("divAddOrRemoveTeam").style.display = 'none';
},
},
];
},
}).register('peopleRow');
BlazeComponent.extendComponent({
onCreated() {},
teamsDatas() {
return Team.find({}, {sort: { teamDisplayName: 1 }});
},
events() {
return [
{
'click #cancelBtn': function(){
let selectedElt = document.getElementById("jsteamsUser");
document.getElementById("divAddOrRemoveTeamContainer").style.display = 'none';
},
'click #addTeamBtn': function(){
let selectedElt;
let selectedEltValue;
let selectedEltValueId;
let userTms = [];
let currentUser;
let currUserTeamIndex;
selectedElt = document.getElementById("jsteamsUser");
selectedEltValue = selectedElt.options[selectedElt.selectedIndex].text;
selectedEltValueId = selectedElt.options[selectedElt.selectedIndex].value;
if(document.getElementById('addAction').checked){
for(let i = 0; i < selectedUserChkBoxUserIds.length; i++){
currentUser = Users.findOne(selectedUserChkBoxUserIds[i]);
userTms = currentUser.teams;
if(userTms == undefined || userTms.length == 0){
userTms = [];
userTms.push({
"teamId": selectedEltValueId,
"teamDisplayName": selectedEltValue,
})
}
else if(userTms.length > 0)
{
currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId});
if(currUserTeamIndex == -1){
userTms.push({
"teamId": selectedEltValueId,
"teamDisplayName": selectedEltValue,
});
}
}
Users.update(selectedUserChkBoxUserIds[i], {
$set:{
teams: userTms
}
});
}
}
else{
for(let i = 0; i < selectedUserChkBoxUserIds.length; i++){
currentUser = Users.findOne(selectedUserChkBoxUserIds[i]);
userTms = currentUser.teams;
if(userTms !== undefined || userTms.length > 0)
{
currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId});
if(currUserTeamIndex != -1){
userTms.splice(currUserTeamIndex, 1);
}
}
Users.update(selectedUserChkBoxUserIds[i], {
$set:{
teams: userTms
}
});
}
}
document.getElementById("divAddOrRemoveTeamContainer").style.display = 'none';
},
},
];
},
}).register('modifyTeamsUsers');
BlazeComponent.extendComponent({
events() {
return [
@ -420,6 +527,41 @@ BlazeComponent.extendComponent({
},
}).register('newUserRow');
BlazeComponent.extendComponent({
events() {
return [
{
'click .allUserChkBox': function(ev){
selectedUserChkBoxUserIds = [];
const checkboxes = document.getElementsByClassName("selectUserChkBox");
if(ev.currentTarget){
if(ev.currentTarget.checked){
for (let i=0; i<checkboxes.length; i++) {
if (!checkboxes[i].disabled) {
selectedUserChkBoxUserIds.push(checkboxes[i].id);
checkboxes[i].checked = true;
}
}
}
else{
for (let i=0; i<checkboxes.length; i++) {
if (!checkboxes[i].disabled) {
checkboxes[i].checked = false;
}
}
}
}
if(selectedUserChkBoxUserIds.length > 0)
document.getElementById("divAddOrRemoveTeam").style.display = 'block';
else
document.getElementById("divAddOrRemoveTeam").style.display = 'none';
},
},
];
},
}).register('selectAllUser');
Template.editOrgPopup.events({
submit(event, templateInstance) {
event.preventDefault();
@ -431,8 +573,7 @@ Template.editOrgPopup.events({
const orgDesc = templateInstance.find('.js-orgDesc').value.trim();
const orgShortName = templateInstance.find('.js-orgShortName').value.trim();
const orgWebsite = templateInstance.find('.js-orgWebsite').value.trim();
const orgIsActive =
templateInstance.find('.js-org-isactive').value.trim() == 'true';
const orgIsActive = templateInstance.find('.js-org-isactive').value.trim() == 'true';
const isChangeOrgDisplayName = orgDisplayName !== org.orgDisplayName;
const isChangeOrgDesc = orgDesc !== org.orgDesc;
@ -458,7 +599,7 @@ Template.editOrgPopup.events({
);
}
Popup.close();
Popup.back();
},
});
@ -502,7 +643,7 @@ Template.editTeamPopup.events({
);
}
Popup.close();
Popup.back();
},
});
@ -617,7 +758,7 @@ Template.editUserPopup.events({
} else {
usernameMessageElement.hide();
emailMessageElement.hide();
Popup.close();
Popup.back();
}
},
);
@ -631,7 +772,7 @@ Template.editUserPopup.events({
}
} else {
usernameMessageElement.hide();
Popup.close();
Popup.back();
}
});
} else if (isChangeEmail) {
@ -648,11 +789,11 @@ Template.editUserPopup.events({
}
} else {
emailMessageElement.hide();
Popup.close();
Popup.back();
}
},
);
} else Popup.close();
} else Popup.back();
},
'click #addUserOrg'(event) {
event.preventDefault();
@ -787,7 +928,7 @@ Template.newOrgPopup.events({
orgWebsite,
orgIsActive,
);
Popup.close();
Popup.back();
},
});
@ -813,7 +954,7 @@ Template.newTeamPopup.events({
teamWebsite,
teamIsActive,
);
Popup.close();
Popup.back();
},
});
@ -839,20 +980,24 @@ Template.newUserPopup.events({
let userTeamsIdsList = userTeamsIds.split(",");
let userTms = [];
for(let i = 0; i < userTeamsList.length; i++){
userTms.push({
"teamId": userTeamsIdsList[i],
"teamDisplayName": userTeamsList[i],
})
if(!!userTeamsIdsList[i] && !!userTeamsList[i]) {
userTms.push({
"teamId": userTeamsIdsList[i],
"teamDisplayName": userTeamsList[i],
})
}
}
let userOrgsList = userOrgs.split(",");
let userOrgsIdsList = userOrgsIds.split(",");
let userOrganizations = [];
for(let i = 0; i < userOrgsList.length; i++){
userOrganizations.push({
"orgId": userOrgsIdsList[i],
"orgDisplayName": userOrgsList[i],
})
if(!!userOrgsIdsList[i] && !!userOrgsList[i]) {
userOrganizations.push({
"orgId": userOrgsIdsList[i],
"orgDisplayName": userOrgsList[i],
})
}
}
Meteor.call(
@ -882,11 +1027,11 @@ Template.newUserPopup.events({
} else {
usernameMessageElement.hide();
emailMessageElement.hide();
Popup.close();
Popup.back();
}
},
);
Popup.close();
Popup.back();
},
'click #addUserOrgNewUser'(event) {
event.preventDefault();
@ -940,7 +1085,7 @@ Template.settingsOrgPopup.events({
return;
}
Org.remove(this.orgId);
Popup.close();
Popup.back();
}
});
@ -958,7 +1103,7 @@ Template.settingsTeamPopup.events({
return;
}
Team.remove(this.teamId);
Popup.close();
Popup.back();
}
});
@ -975,10 +1120,13 @@ Template.settingsUserPopup.events({
},
'click #deleteButton'(event) {
event.preventDefault();
Users.remove(this.userId);
/*
// Delete is not enabled yet, because it does leave empty user avatars
// to boards: boards members, card members and assignees have
// empty users. See:
// Delete user is enabled, but you should remove user from all boards
// before deleting user, because there is possibility of leaving empty user avatars
// to boards. You can remove non-existing user ids manually from database,
// if that happens.
//. See:
// - wekan/client/components/settings/peopleBody.jade deleteButton
// - wekan/client/components/settings/peopleBody.js deleteButton
// - wekan/client/components/sidebar/sidebar.js Popup.afterConfirm('removeMember'
@ -986,9 +1134,9 @@ Template.settingsUserPopup.events({
// but that should be used to remove user from all boards similarly
// - wekan/models/users.js Delete is not enabled
//
//Users.remove(this.userId);
//
*/
Popup.close();
Popup.back();
},
});

View file

@ -55,3 +55,32 @@ table
.js-teams,.js-teamsNewUser
display: none;
.selectUserChkBox,.allUserChkBox
position: static !important;
visibility: visible !important;
left: 0 !important;
display: block !important;
#divAddOrRemoveTeam
background: green;
display: none;
#addOrRemoveTeam
background: green;
color: white;
#divAddOrRemoveTeamContainer
display: none;
margin: auto;
width: 50%;
border: 3px solid green;
padding: 10px;
#cancelBtn
margin-left: 5% !important;
background: orange;
color: white;
#deleteAction
margin-left: 5% !important;

View file

@ -22,6 +22,10 @@ template(name="setting")
a.js-setting-menu(data-id="account-setting")
i.fa.fa-users
| {{_ 'accounts'}}
li
a.js-setting-menu(data-id="tableVisibilityMode-setting")
i.fa.fa-eye
| {{_ 'tableVisibilityMode'}}
li
a.js-setting-menu(data-id="announcement-setting")
i.fa.fa-bullhorn
@ -44,6 +48,8 @@ template(name="setting")
+email
else if accountSetting.get
+accountSettings
else if tableVisibilityModeSetting.get
+tableVisibilityModeSettings
else if announcementSetting.get
+announcementSettings
else if layoutSetting.get
@ -96,7 +102,7 @@ template(name='email')
// li.smtp-form
// .title {{_ 'smtp-username'}}
// .form-group
// input.wekan-form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}")
// input.wekan-form-control#mail-server-u"accounts-allowUserNameChange": "Allow Username Change",sername(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}")
// li.smtp-form
// .title {{_ 'smtp-password'}}
// .form-group
@ -120,6 +126,17 @@ template(name='email')
li
button.js-send-smtp-test-email.primary {{_ 'send-smtp-test'}}
template(name='tableVisibilityModeSettings')
ul#tableVisibilityMode-setting.setting-detail
li.tableVisibilityMode-form
.title {{_ 'tableVisibilityMode-allowPrivateOnly'}}
.form-group.flex
input.wekan-form-control#accounts-allowPrivateOnly(type="radio" name="allowPrivateOnly" value="true" checked="{{#if allowPrivateOnly}}checked{{/if}}")
span {{_ 'yes'}}
input.wekan-form-control#accounts-allowPrivateOnly(type="radio" name="allowPrivateOnly" value="false" checked="{{#unless allowPrivateOnly}}checked{{/unless}}")
span {{_ 'no'}}
button.js-tableVisibilityMode-save.primary {{_ 'save'}}
template(name='accountSettings')
ul#account-setting.setting-detail
li
@ -163,6 +180,18 @@ template(name='announcementSettings')
template(name='layoutSettings')
ul#layout-setting.setting-detail
li.layout-form
.title {{_ 'oidc-button-text'}}
.form-group
input.wekan-form-control#oidcBtnTextvalue(type="text", placeholder="" value="{{currentSetting.oidcBtnText}}")
li.layout-form
.title {{_ 'can-invite-if-same-mailDomainName'}}
.form-group
input.wekan-form-control#mailDomainNamevalue(type="text", placeholder="" value="{{currentSetting.mailDomainName}}")
li.layout-form
.title {{_ 'custom-legal-notice-link-url'}}
.form-group
input.wekan-form-control#legalNoticevalue(type="text", placeholder="" value="{{currentSetting.legalNotice}}")
li.layout-form
.title {{_ 'display-authentication-method'}}
.form-group.flex

View file

@ -7,6 +7,7 @@ BlazeComponent.extendComponent({
this.generalSetting = new ReactiveVar(true);
this.emailSetting = new ReactiveVar(false);
this.accountSetting = new ReactiveVar(false);
this.tableVisibilityModeSetting = new ReactiveVar(false);
this.announcementSetting = new ReactiveVar(false);
this.layoutSetting = new ReactiveVar(false);
this.webhookSetting = new ReactiveVar(false);
@ -14,6 +15,7 @@ BlazeComponent.extendComponent({
Meteor.subscribe('setting');
Meteor.subscribe('mailServer');
Meteor.subscribe('accountSettings');
Meteor.subscribe('tableVisibilityModeSettings');
Meteor.subscribe('announcements');
Meteor.subscribe('globalwebhooks');
},
@ -88,6 +90,7 @@ BlazeComponent.extendComponent({
this.announcementSetting.set('announcement-setting' === targetID);
this.layoutSetting.set('layout-setting' === targetID);
this.webhookSetting.set('webhook-setting' === targetID);
this.tableVisibilityModeSetting.set('tableVisibilityMode-setting' === targetID);
}
},
@ -196,6 +199,22 @@ BlazeComponent.extendComponent({
)
.val()
.trim();
const oidcBtnText = $(
'#oidcBtnTextvalue',
)
.val()
.trim();
const mailDomainName = $(
'#mailDomainNamevalue',
)
.val()
.trim();
const legalNotice = $(
'#legalNoticevalue',
)
.val()
.trim();
const hideLogoChange = $('input[name=hideLogo]:checked').val() === 'true';
const displayAuthenticationMethod =
$('input[name=displayAuthenticationMethod]:checked').val() === 'true';
@ -218,6 +237,9 @@ BlazeComponent.extendComponent({
defaultAuthenticationMethod,
automaticLinkedUrlSchemes,
spinnerName,
oidcBtnText,
mailDomainName,
legalNotice,
},
});
} catch (e) {
@ -317,6 +339,46 @@ BlazeComponent.extendComponent({
},
}).register('accountSettings');
BlazeComponent.extendComponent({
saveTableVisibilityChange() {
const allowPrivateOnly =
$('input[name=allowPrivateOnly]:checked').val() === 'true';
TableVisibilityModeSettings.update('tableVisibilityMode-allowPrivateOnly', {
$set: { booleanValue: allowPrivateOnly },
});
},
allowPrivateOnly() {
return TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
},
allHideSystemMessages() {
Meteor.call('setAllUsersHideSystemMessages', (err, ret) => {
if (!err && ret) {
if (ret === true) {
const message = `${TAPi18n.__(
'now-system-messages-of-all-users-are-hidden',
)}`;
alert(message);
}
} else {
const reason = err.reason || '';
const message = `${TAPi18n.__(err.error)}\n${reason}`;
alert(message);
}
});
},
events() {
return [
{
'click button.js-tableVisibilityMode-save': this.saveTableVisibilityChange,
},
{
'click button.js-all-hide-system-messages': this.allHideSystemMessages,
},
];
},
}).register('tableVisibilityModeSettings');
BlazeComponent.extendComponent({
onCreated() {
this.loading = new ReactiveVar(false);

View file

@ -11,7 +11,7 @@
color: #727479
background: #dedede
width 100%
height 100%
height calc(100% - 80px)
position: absolute;
.content-title
@ -88,6 +88,8 @@
&.is-checked
background #fff
input[type=radio]
margin: 4px
.option
@extends .flex

View file

@ -31,26 +31,28 @@ template(name='homeSidebar')
+activities(mode="board")
template(name="membersWidget")
.board-widget.board-widget-members
h3
i.fa.fa-users
| {{_ 'organizations'}}
if AtLeastOneOrgWasCreated
.board-widget.board-widget-members
h3
i.fa.fa-users
| {{_ 'organizations'}}
.board-widget-content
+boardOrgGeneral
.clearfix
br
hr
.board-widget.board-widget-members
h3
i.fa.fa-users
| {{_ 'teams'}}
.board-widget-content
+boardOrgGeneral
.clearfix
br
hr
if AtLeastOneTeamWasCreated
.board-widget.board-widget-members
h3
i.fa.fa-users
| {{_ 'teams'}}
.board-widget-content
+boardTeamGeneral
.clearfix
br
hr
.board-widget-content
+boardTeamGeneral
.clearfix
br
hr
.board-widget.board-widget-members
h3
i.fa.fa-users
@ -89,11 +91,20 @@ template(name="boardOrgGeneral")
table
tbody
tr
th {{_ 'displayName'}}
th
| {{_ 'add-organizations'}}
br
i.addOrganizationsLabel
| {{_ 'to-create-organizations-contact-admin'}}
br
i.addOrganizationsLabel
| {{_ 'add-organizations-label'}}
th
if currentUser.isBoardAdmin
a.member.orgOrTeamMember.add-member.js-manage-board-addOrg(title="{{_ 'add-members'}}")
i.fa.fa-plus
i.addTeamFaPlus.fa.fa-plus
.divaddfaplusminus
| {{_ 'add'}}
each org in currentBoard.activeOrgs
+boardOrgRow(orgId=org.orgId)
@ -101,11 +112,20 @@ template(name="boardTeamGeneral")
table
tbody
tr
th {{_ 'displayName'}}
th
| {{_ 'add-teams'}}
br
i.addTeamsLabel
| {{_ 'to-create-teams-contact-admin'}}
br
i.addTeamsLabel
| {{_ 'add-teams-label'}}
th
if currentUser.isBoardAdmin
a.member.orgOrTeamMember.add-member.js-manage-board-addTeam(title="{{_ 'add-members'}}")
i.fa.fa-plus
i.addTeamFaPlus.fa.fa-plus
.divaddfaplusminus
| {{_ 'add'}}
each currentBoard.activeTeams
+boardTeamRow(teamId=this.teamId)
@ -398,7 +418,11 @@ template(name="exportBoard")
li
a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}")
i.fa.fa-share-alt
| {{_ 'export-board-csv'}}
| {{_ 'export-board-csv'}} ,
li
a(href="{{exportScsvUrl}}", download="{{exportCsvFilename}}")
i.fa.fa-share-alt
| {{_ 'export-board-csv'}} ;
li
a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}")
i.fa.fa-share-alt
@ -470,12 +494,12 @@ template(name="removeBoardOrgPopup")
form
input.hide#hideOrgId(type="text" value=org._id)
label
| {{_ 'leave-board'}} ?
| {{_ 'remove-organization-from-board'}}
br
hr
div.buttonsContainer
input.primary.wide.leaveBoardBtn#leaveBoardBtn(type="submit" value="{{_ 'leave-board'}}")
input.primary.wide.cancelLeaveBoardBtn#cancelLeaveBoardBtn(type="submit" value="{{_ 'Cancel'}}")
input.primary.wide.leaveBoardBtn#leaveBoardBtn(type="submit" value="{{_ 'confirm-btn'}}")
input.primary.wide.cancelLeaveBoardBtn#cancelLeaveBoardBtn(type="submit" value="{{_ 'cancel'}}")
template(name="addBoardTeamPopup")
select.js-boardTeams#jsBoardTeams
@ -487,12 +511,12 @@ template(name="removeBoardTeamPopup")
form
input.hide#hideTeamId(type="text" value=team._id)
label
| {{_ 'leave-board'}} ?
| {{_ 'remove-team-from-table'}}
br
hr
div.buttonsContainer
input.primary.wide.leaveBoardBtn#leaveBoardTeamBtn(type="submit" value="{{_ 'leave-board'}}")
input.primary.wide.cancelLeaveBoardBtn#cancelLeaveBoardTeamBtn(type="submit" value="{{_ 'Cancel'}}")
input.primary.wide.leaveBoardBtn#leaveBoardTeamBtn(type="submit" value="{{_ 'confirm-btn'}}")
input.primary.wide.cancelLeaveBoardBtn#cancelLeaveBoardTeamBtn(type="submit" value="{{_ 'cancel'}}")
template(name="addMemberPopup")
.js-search-member

View file

@ -183,19 +183,20 @@ Template.memberPopup.helpers({
},
});
Template.boardMenuPopup.events({
'click .js-rename-board': Popup.open('boardChangeTitle'),
'click .js-open-rules-view'() {
Modal.openWide('rulesMain');
Popup.close();
Popup.back();
},
'click .js-custom-fields'() {
Sidebar.setView('customFields');
Popup.close();
Popup.back();
},
'click .js-open-archives'() {
Sidebar.setView('archives');
Popup.close();
Popup.back();
},
'click .js-change-board-color': Popup.open('boardChangeColor'),
'click .js-change-language': Popup.open('changeLanguage'),
@ -208,7 +209,7 @@ Template.boardMenuPopup.events({
}),
'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
Popup.close();
Popup.back();
Boards.remove(currentBoard._id);
FlowRouter.go('home');
}),
@ -251,7 +252,7 @@ Template.boardMenuPopup.helpers({
Template.memberPopup.events({
'click .js-filter-member'() {
Filter.members.toggle(this.userId);
Popup.close();
Popup.back();
},
'click .js-change-role': Popup.open('changePermissions'),
'click .js-remove-member': Popup.afterConfirm('removeMember', function() {
@ -265,12 +266,12 @@ Template.memberPopup.events({
card.unassignAssignee(memberId);
});
Boards.findOne(boardId).removeMember(memberId);
Popup.close();
Popup.back();
}),
'click .js-leave-member': Popup.afterConfirm('leaveBoard', () => {
const boardId = Session.get('currentBoard');
Meteor.call('quitBoard', boardId, () => {
Popup.close();
Popup.back();
FlowRouter.go('home');
});
}),
@ -290,6 +291,42 @@ Template.leaveBoardPopup.helpers({
return Boards.findOne(Session.get('currentBoard'));
},
});
BlazeComponent.extendComponent({
onCreated() {
this.error = new ReactiveVar('');
this.loading = new ReactiveVar(false);
this.findOrgsOptions = new ReactiveVar({});
this.findTeamsOptions = new ReactiveVar({});
this.page = new ReactiveVar(1);
this.teamPage = new ReactiveVar(1);
this.autorun(() => {
const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {});
});
this.autorun(() => {
const limitTeams = this.teamPage.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('team', this.findTeamsOptions.get(), limitTeams, () => {});
});
},
onRendered() {
this.setLoading(false);
},
setError(error) {
this.error.set(error);
},
setLoading(w) {
this.loading.set(w);
},
isLoading() {
return this.loading.get();
},
}).register('membersWidget');
Template.membersWidget.helpers({
isInvited() {
@ -307,6 +344,21 @@ Template.membersWidget.helpers({
isBoardAdmin() {
return Meteor.user().isBoardAdmin();
},
AtLeastOneOrgWasCreated(){
let orgs = Org.find({}, {sort: { createdAt: -1 }});
if(orgs === undefined)
return false;
return orgs.count() > 0;
},
AtLeastOneTeamWasCreated(){
let teams = Team.find({}, {sort: { createdAt: -1 }});
if(teams === undefined)
return false;
return teams.count() > 0;
},
});
Template.membersWidget.events({
@ -408,7 +460,7 @@ BlazeComponent.extendComponent({
activities: ['all'],
});
}
Popup.close();
Popup.back();
},
},
];
@ -460,6 +512,21 @@ BlazeComponent.extendComponent({
};
const queryParams = {
authToken: Accounts._storedLoginToken(),
delimiter: ',',
};
return FlowRouter.path(
'/api/boards/:boardId/export/csv',
params,
queryParams,
);
},
exportScsvUrl() {
const params = {
boardId: Session.get('currentBoard'),
};
const queryParams = {
authToken: Accounts._storedLoginToken(),
delimiter: ';',
};
return FlowRouter.path(
'/api/boards/:boardId/export/csv',
@ -1162,7 +1229,7 @@ BlazeComponent.extendComponent({
self.setLoading(false);
if (err) self.setError(err.error);
else if (ret.email) self.setError('email-sent');
else Popup.close();
else Popup.back();
});
},
@ -1249,7 +1316,7 @@ BlazeComponent.extendComponent({
}
}
Popup.close();
Popup.back();
},
},
];
@ -1258,8 +1325,7 @@ BlazeComponent.extendComponent({
Template.addBoardOrgPopup.helpers({
orgsDatas() {
// return Org.find({}, {sort: { createdAt: -1 }});
let orgs = Org.find({}, {sort: { createdAt: -1 }});
let orgs = Org.find({}, {sort: { orgDisplayName: 1 }});
return orgs;
},
});
@ -1313,10 +1379,10 @@ BlazeComponent.extendComponent({
Meteor.call('setBoardOrgs', boardOrganizations, currentBoard._id);
Popup.close();
Popup.back();
},
'click #cancelLeaveBoardBtn'(){
Popup.close();
Popup.back();
},
},
];
@ -1340,6 +1406,13 @@ BlazeComponent.extendComponent({
const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
});
this.findUsersOptions = new ReactiveVar({});
this.userPage = new ReactiveVar(1);
this.autorun(() => {
const limitUsers = this.userPage.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
});
},
onRendered() {
@ -1384,11 +1457,39 @@ BlazeComponent.extendComponent({
})
if (selectedTeamId != "-1") {
Meteor.call('setBoardTeams', boardTeams, currentBoard._id);
let members = currentBoard.members;
let query = {
"teams.teamId": { $in: boardTeams.map(t => t.teamId) },
};
const boardTeamUsers = Users.find(query, {
sort: { sort: 1 },
});
if(boardTeams !== undefined && boardTeams.length > 0){
let index;
if(boardTeamUsers && boardTeamUsers.count() > 0){
boardTeamUsers.forEach((u) => {
index = members.findIndex(function(m){ return m.userId == u._id});
if(index == -1){
members.push({
"isActive": true,
"isAdmin": u.isAdmin !== undefined ? u.isAdmin : false,
"isCommentOnly" : false,
"isNoComments" : false,
"userId": u._id,
});
}
});
}
}
Meteor.call('setBoardTeams', boardTeams, members, currentBoard._id);
}
}
Popup.close();
Popup.back();
},
},
];
@ -1397,7 +1498,7 @@ BlazeComponent.extendComponent({
Template.addBoardTeamPopup.helpers({
teamsDatas() {
let teams = Team.find({}, {sort: { createdAt: -1 }});
let teams = Team.find({}, {sort: { teamDisplayName: 1 }});
return teams;
},
});
@ -1413,6 +1514,13 @@ BlazeComponent.extendComponent({
const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
});
this.findUsersOptions = new ReactiveVar({});
this.userPage = new ReactiveVar(1);
this.autorun(() => {
const limitUsers = this.userPage.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
});
},
onRendered() {
@ -1449,12 +1557,33 @@ BlazeComponent.extendComponent({
}
}
Meteor.call('setBoardTeams', boardTeams, currentBoard._id);
let members = currentBoard.members;
let query = {
"teams.teamId": stringTeamId
};
Popup.close();
const boardTeamUsers = Users.find(query, {
sort: { sort: 1 },
});
if(currentBoard.teams !== undefined && currentBoard.teams.length > 0){
let index;
if(boardTeamUsers && boardTeamUsers.count() > 0){
boardTeamUsers.forEach((u) => {
index = members.findIndex(function(m){ return m.userId == u._id});
if(index !== -1 && (u.isAdmin === undefined || u.isAdmin == false)){
members.splice(index, 1);
}
});
}
}
Meteor.call('setBoardTeams', boardTeams, members, currentBoard._id);
Popup.back();
},
'click #cancelLeaveBoardTeamBtn'(){
Popup.close();
Popup.back();
},
},
];

View file

@ -224,3 +224,24 @@
.cancelLeaveBoardBtn
margin-left: 5% !important
background-color: red !important
.addTeamsLabel, .addOrganizationsLabel
font-weight: normal
.js-manage-board-removeTeam:hover, .js-manage-board-removeTeam.is-active,
.js-manage-board-removeOrg:hover, .js-manage-board-removeOrg.is-active
box-shadow: 0 0 0 2px #e23210 inset !important
.js-manage-board-addTeam:hover, .js-manage-board-addTeam.is-active,
.js-manage-board-addOrg:hover , .js-manage-board-addOrg.is-active
box-shadow: 0 0 0 2px #73ea10 inset !important
.addTeamFaPlus
color: green !important
.removeTeamFaMinus
color: red !important
.divaddfaplusminus
padding-top: 5px;
margin-left: 40px;

View file

@ -1,4 +1,4 @@
archivedRequested = false;
//archivedRequested = false;
const subManager = new SubsManager();
BlazeComponent.extendComponent({
@ -13,7 +13,7 @@ BlazeComponent.extendComponent({
const currentBoardId = Session.get('currentBoard');
if (!currentBoardId) return;
const handle = subManager.subscribe('board', currentBoardId, true);
archivedRequested = true;
//archivedRequested = true;
Tracker.nonreactive(() => {
Tracker.autorun(() => {
this.isArchiveReady.set(handle.ready());
@ -94,13 +94,13 @@ BlazeComponent.extendComponent({
'click .js-delete-card': Popup.afterConfirm('cardDelete', function() {
const cardId = this._id;
Cards.remove(cardId);
Popup.close();
Popup.back();
}),
'click .js-delete-all-cards': Popup.afterConfirm('cardDelete', () => {
this.archivedCards().forEach(card => {
Cards.remove(card._id);
});
Popup.close();
Popup.back();
}),
'click .js-restore-list'() {
@ -115,13 +115,13 @@ BlazeComponent.extendComponent({
'click .js-delete-list': Popup.afterConfirm('listDelete', function() {
this.remove();
Popup.close();
Popup.back();
}),
'click .js-delete-all-lists': Popup.afterConfirm('listDelete', () => {
this.archivedLists().forEach(list => {
list.remove();
});
Popup.close();
Popup.back();
}),
'click .js-restore-swimlane'() {
@ -138,7 +138,7 @@ BlazeComponent.extendComponent({
'swimlaneDelete',
function() {
this.remove();
Popup.close();
Popup.back();
},
),
'click .js-delete-all-swimlanes': Popup.afterConfirm(
@ -147,7 +147,7 @@ BlazeComponent.extendComponent({
this.archivedSwimlanes().forEach(swimlane => {
swimlane.remove();
});
Popup.close();
Popup.back();
},
),
},

View file

@ -43,6 +43,14 @@ template(name="createCustomFieldPopup")
option(value=value selected="selected") {{name}}
else
option(value=value) {{name}}
a.flex.js-field-show-sum-at-top-of-list(class="{{#if showSumAtTopOfList}}is-checked{{/if}}")
.materialCheckBox(class="{{#if showSumAtTopOfList}}is-checked{{/if}}")
span {{_ 'showSum-field-on-list'}}
div.js-field-settings.js-field-settings-currency(class="{{#if isTypeNotSelected 'number'}}hide{{/if}}")
a.flex.js-field-show-sum-at-top-of-list(class="{{#if showSumAtTopOfList}}is-checked{{/if}}")
.materialCheckBox(class="{{#if showSumAtTopOfList}}is-checked{{/if}}")
span {{_ 'showSum-field-on-list'}}
div.js-field-settings.js-field-settings-dropdown(class="{{#if isTypeNotSelected 'dropdown'}}hide{{/if}}")
label

View file

@ -234,6 +234,14 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
$target.find('.materialCheckBox').toggleClass('is-checked');
$target.toggleClass('is-checked');
},
'click .js-field-show-sum-at-top-of-list'(evt) {
let $target = $(evt.target);
if (!$target.hasClass('js-field-show-sum-at-top-of-list')) {
$target = $target.parent();
}
$target.find('.materialCheckBox').toggleClass('is-checked');
$target.toggleClass('is-checked');
},
'click .primary'(evt) {
evt.preventDefault();
@ -248,6 +256,8 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
this.find('.js-field-automatically-on-card.is-checked') !== null,
alwaysOnCard:
this.find('.js-field-always-on-card.is-checked') !== null,
showSumAtTopOfList:
this.find('.js-field-show-sum-at-top-of-list.is-checked') !== null,
};
// insert or update
@ -273,7 +283,7 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
} else {
CustomFields.remove(customField._id);
}
Popup.close();
Popup.back();
},
),
},
@ -292,6 +302,6 @@ CreateCustomFieldPopup.register('createCustomFieldPopup');
'submit'(evt) {
const customFieldId = this._id;
CustomFields.remove(customFieldId);
Popup.close();
Popup.back();
}
});*/

View file

@ -4,11 +4,18 @@
and #each x in y constructors to fix this.
template(name="filterSidebar")
h3 {{_ 'list-filter-label'}}
h3
i.fa.fa-trello
| {{_ 'list-filter-label'}}
ul.sidebar-list
form.js-list-filter
input(type="text")
hr
h3
i.fa.fa-list-alt
| {{_ 'filter-card-title-label'}}
input.js-field-card-filter(type="text")
hr
h3
i.fa.fa-tags
| {{_ 'filter-labels-label'}}

View file

@ -8,6 +8,11 @@ BlazeComponent.extendComponent({
evt.preventDefault();
Filter.lists.set(this.find('.js-list-filter input').value.trim());
},
'change .js-field-card-filter'(evt) {
evt.preventDefault();
Filter.title.set(this.find('.js-field-card-filter').value.trim());
Filter.resetExceptions();
},
'click .js-toggle-label-filter'(evt) {
evt.preventDefault();
Filter.labelIds.toggle(this.currentData()._id);
@ -94,14 +99,14 @@ BlazeComponent.extendComponent({
}).register('filterSidebar');
function mutateSelectedCards(mutationName, ...args) {
Cards.find(MultiSelection.getMongoSelector()).forEach(card => {
Cards.find(MultiSelection.getMongoSelector(), {sort: ['sort']}).forEach(card => {
card[mutationName](...args);
});
}
BlazeComponent.extendComponent({
mapSelection(kind, _id) {
return Cards.find(MultiSelection.getMongoSelector()).map(card => {
return Cards.find(MultiSelection.getMongoSelector(), {sort: ['sort']}).map(card => {
const methodName = kind === 'label' ? 'hasLabel' : 'isAssigned';
return card[methodName](_id);
});
@ -171,22 +176,22 @@ Template.multiselectionSidebar.helpers({
Template.disambiguateMultiLabelPopup.events({
'click .js-remove-label'() {
mutateSelectedCards('removeLabel', this._id);
Popup.close();
Popup.back();
},
'click .js-add-label'() {
mutateSelectedCards('addLabel', this._id);
Popup.close();
Popup.back();
},
});
Template.disambiguateMultiMemberPopup.events({
'click .js-unassign-member'() {
mutateSelectedCards('assignMember', this._id);
Popup.close();
Popup.back();
},
'click .js-assign-member'() {
mutateSelectedCards('unassignMember', this._id);
Popup.close();
Popup.back();
},
});

View file

@ -3,10 +3,14 @@ template(name="searchSidebar")
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto")
.list-body
.minilists.clearfix.js-minilists
hr
| {{_ 'lists' }}
each (lists)
a.minilist-wrapper.js-minilist(href=originRelativeUrl)
+minilist(this)
.minicards.clearfix.js-minicards
each (results)
hr
| {{_ 'cards' }}
each (cards)
a.minicard-wrapper.js-minicard(href=originRelativeUrl)
+minicard(this)

View file

@ -3,7 +3,7 @@ BlazeComponent.extendComponent({
this.term = new ReactiveVar('');
},
results() {
cards() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
return currentBoard.searchCards(this.term.get());
},
@ -13,9 +13,24 @@ BlazeComponent.extendComponent({
return currentBoard.searchLists(this.term.get());
},
clickOnMiniCard(evt) {
if (Utils.isMiniScreen()) {
evt.preventDefault();
Session.set('popupCardId', this.currentData()._id);
this.cardDetailsPopup(evt);
}
},
cardDetailsPopup(event) {
if (!Popup.isOpen()) {
Popup.open("cardDetails")(event);
}
},
events() {
return [
{
'click .js-minicard': this.clickOnMiniCard,
'submit .js-search-term-form'(evt) {
evt.preventDefault();
this.term.set(evt.target.searchTerm.value);

View file

@ -26,7 +26,7 @@ template(name="swimlaneFixedHeader")
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}")
a.fa.fa-navicon.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
unless isMiniScreen
if showDesktopDragHandles
if isShowDesktopDragHandles
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

View file

@ -28,19 +28,6 @@ BlazeComponent.extendComponent({
},
}).register('swimlaneHeader');
Template.swimlaneHeader.helpers({
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
});
Template.swimlaneFixedHeader.helpers({
isBoardAdmin() {
return Meteor.user().isBoardAdmin();
@ -52,7 +39,7 @@ Template.swimlaneActionPopup.events({
'click .js-close-swimlane'(event) {
event.preventDefault();
this.archive();
Popup.close();
Popup.back();
},
'click .js-move-swimlane': Popup.open('moveSwimlane'),
'click .js-copy-swimlane': Popup.open('copySwimlane'),
@ -101,7 +88,7 @@ BlazeComponent.extendComponent({
// XXX ideally, we should move the popup to the newly
// created swimlane so a user can add more than one swimlane
// with a minimum of interactions
Popup.close();
Popup.back();
},
'click .js-swimlane-template': Popup.open('searchElement'),
},
@ -131,11 +118,11 @@ BlazeComponent.extendComponent({
},
'click .js-submit'() {
this.currentSwimlane.setColor(this.currentColor.get());
Popup.close();
Popup.back();
},
'click .js-remove-color'() {
this.currentSwimlane.setColor(null);
Popup.close();
Popup.back();
},
},
];

View file

@ -14,7 +14,8 @@ template(name="swimlane")
+addListForm
else
each lists
+list(this)
if visible this
+list(this)
if currentCardIsInThisList _id ../_id
+cardDetails(currentCard)
if currentUser.isBoardMember
@ -52,6 +53,7 @@ template(name="addListForm")
autocomplete="off" autofocus)
.edit-controls.clearfix
button.primary.confirm(type="submit") {{_ 'save'}}
.fa.fa-times-thin.js-close-inlined-form
unless currentBoard.isTemplatesBoard
unless currentBoard.isTemplateBoard
span.quiet

View file

@ -9,7 +9,7 @@ function currentListIsInThisSwimlane(swimlaneId) {
}
function currentCardIsInThisList(listId, swimlaneId) {
const currentCard = Cards.findOne(Session.get('currentCard'));
const currentCard = Utils.getCurrentCard();
const currentUser = Meteor.user();
if (
currentUser &&
@ -57,7 +57,7 @@ function initSortable(boardComponent, $listsDom) {
tolerance: 'pointer',
helper: 'clone',
items: '.js-list:not(.js-list-composer)',
placeholder: 'list placeholder',
placeholder: 'js-list placeholder',
distance: 7,
start(evt, ui) {
ui.placeholder.height(ui.helper.height());
@ -95,22 +95,11 @@ function initSortable(boardComponent, $listsDom) {
//}
boardComponent.autorun(() => {
let showDesktopDragHandles = false;
currentUser = Meteor.user();
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
showDesktopDragHandles = true;
} else {
showDesktopDragHandles = false;
}
if (Utils.isMiniScreen() || showDesktopDragHandles) {
if (Utils.isMiniScreenOrShowDesktopDragHandles()) {
$listsDom.sortable({
handle: '.js-list-handle',
});
} else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
} else {
$listsDom.sortable({
handle: '.js-list-header',
});
@ -123,7 +112,7 @@ function initSortable(boardComponent, $listsDom) {
'disabled',
// Disable drag-dropping when user is not member/is worker
//!userIsMember() || Meteor.user().isWorker(),
!Meteor.user().isBoardAdmin(),
!Meteor.user() || !Meteor.user().isBoardAdmin(),
// Not disable drag-dropping while in multi-selection mode
// MultiSelection.isActive() || !userIsMember(),
);
@ -136,7 +125,7 @@ BlazeComponent.extendComponent({
const boardComponent = this.parentComponent();
const $listsDom = this.$('.js-lists');
if (!Session.get('currentCard')) {
if (!Utils.getCurrentCardId()) {
boardComponent.scrollLeft();
}
@ -148,19 +137,38 @@ BlazeComponent.extendComponent({
this._isDragging = false;
this._lastDragPositionX = 0;
},
id() {
return this._id;
},
currentCardIsInThisList(listId, swimlaneId) {
return currentCardIsInThisList(listId, swimlaneId);
},
currentListIsInThisSwimlane(swimlaneId) {
return currentListIsInThisSwimlane(swimlaneId);
},
visible(list) {
if (list.archived) {
// Show archived list only when filter archive is on
if (!Filter.archive.isSelected()) {
return false;
}
}
if (Filter.lists._isActive()) {
if (!list.title.match(Filter.lists.getRegexSelector())) {
return false;
}
}
if (Filter.hideEmpty.isSelected()) {
const swimlaneId = this.parentComponent()
.parentComponent()
.data()._id;
const cards = list.cards(swimlaneId);
if (cards.count() === 0) {
return false;
}
}
return true;
},
events() {
return [
{
@ -172,19 +180,8 @@ BlazeComponent.extendComponent({
// the user will legitimately expect to be able to select some text with
// his mouse.
let showDesktopDragHandles = false;
currentUser = Meteor.user();
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
showDesktopDragHandles = true;
} else {
showDesktopDragHandles = false;
}
const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
Utils.isMiniScreen() || showDesktopDragHandles
Utils.isMiniScreenOrShowDesktopDragHandles()
? ['.js-list-handle', '.js-swimlane-header-handle']
: ['.js-list-header'],
);
@ -240,13 +237,15 @@ BlazeComponent.extendComponent({
{
submit(evt) {
evt.preventDefault();
const lastList = this.currentBoard.getLastList();
const sortIndex = Utils.calculateIndexData(lastList, null).base;
const titleInput = this.find('.list-name-input');
const title = titleInput.value.trim();
if (title) {
Lists.insert({
title,
boardId: Session.get('currentBoard'),
sort: $('.list').length,
sort: sortIndex,
type: this.isListTemplatesSwimlane ? 'template-list' : 'list',
swimlaneId: this.currentBoard.isTemplatesBoard()
? this.currentSwimlane._id
@ -264,16 +263,6 @@ BlazeComponent.extendComponent({
}).register('addListForm');
Template.swimlane.helpers({
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
canSeeAddList() {
return Meteor.user().isBoardAdmin();
/*
@ -291,8 +280,8 @@ BlazeComponent.extendComponent({
},
visible(list) {
if (list.archived) {
// Show archived list only when filter archive is on or archive is selected
if (!(Filter.archive.isSelected() || archivedRequested)) {
// Show archived list only when filter archive is on
if (!Filter.archive.isSelected()) {
return false;
}
}
@ -316,7 +305,7 @@ BlazeComponent.extendComponent({
const boardComponent = this.parentComponent();
const $listsDom = this.$('.js-lists');
if (!Session.get('currentCard')) {
if (!Utils.getCurrentCardId()) {
boardComponent.scrollLeft();
}
@ -359,7 +348,7 @@ class MoveSwimlaneComponent extends BlazeComponent {
boardId = bSelect.options[bSelect.selectedIndex].value;
Meteor.call(this.serverMethod, this.currentSwimlane._id, boardId);
}
Popup.close();
Popup.back();
},
},
];

View file

@ -32,7 +32,9 @@ template(name="boardOrgRow")
td
if currentUser.isBoardAdmin
a.member.orgOrTeamMember.add-member.js-manage-board-removeOrg(title="{{_ 'remove-from-board'}}")
i.fa.fa-minus
i.removeTeamFaMinus.fa.fa-minus
.divaddfaplusminus
| {{_ 'remove-btn'}}
template(name="boardTeamRow")
tr
@ -43,7 +45,9 @@ template(name="boardTeamRow")
td
if currentUser.isBoardAdmin
a.member.orgOrTeamMember.add-member.js-manage-board-removeTeam(title="{{_ 'remove-from-board'}}")
i.fa.fa-minus
i.removeTeamFaMinus.fa.fa-minus
.divaddfaplusminus
| {{_ 'remove-btn'}}
template(name="boardOrgName")
svg.avatar.avatar-initials(viewBox="0 0 {{orgViewPortWidth}} 15")

View file

@ -274,7 +274,7 @@ Template.cardMembersPopup.helpers({
Template.cardMembersPopup.events({
'click .js-select-member'(event) {
const card = Cards.findOne(Session.get('currentCard'));
const card = Utils.getCurrentCard();
const memberId = this.userId;
card.toggleMember(memberId);
event.preventDefault();
@ -290,7 +290,7 @@ Template.cardMemberPopup.helpers({
Template.cardMemberPopup.events({
'click .js-remove-member'() {
Cards.findOne(this.cardId).unassignMember(this.userId);
Popup.close();
Popup.back();
},
'click .js-edit-profile': Popup.open('editProfile'),
});

View file

@ -49,10 +49,16 @@ template(name="memberMenuPopup")
i.fa.fa-lock
| {{_ 'admin-panel'}}
hr
li
a.js-edit-profile
i.fa.fa-user
| {{_ 'edit-profile'}}
if isSameDomainNameSettingValue
li
a.js-invite-people
i.fa.fa-envelope
| {{_ 'invite-people'}}
if isNotOAuth2AuthenticationMethod
li
a.js-edit-profile
i.fa.fa-user
| {{_ 'edit-profile'}}
li
a.js-change-settings
i.fa.fa-cog
@ -62,10 +68,11 @@ template(name="memberMenuPopup")
i.fa.fa-picture-o
| {{_ 'edit-avatar'}}
unless isSandstorm
li
a.js-change-password
i.fa.fa-key
| {{_ 'changePasswordPopup-title'}}
if isNotOAuth2AuthenticationMethod
li
a.js-change-password
i.fa.fa-key
| {{_ 'changePasswordPopup-title'}}
li
a.js-change-language
i.fa.fa-flag
@ -78,6 +85,30 @@ template(name="memberMenuPopup")
i.fa.fa-sign-out
| {{_ 'log-out'}}
template(name="invitePeoplePopup")
ul#registration-setting.setting-detail
li
#invite-people-infos
li
br
li
.invite-people(class="{{#if currentSetting.disableRegistration}}{{else}}hide{{/if}}")
ul
li
.title {{_ 'invite-people'}}
textarea#email-to-invite.wekan-form-control(rows='5', placeholder="{{_ 'email-addresses'}}")
li
.title {{_ 'to-boards'}}
.bg-white
each boards
a.option.flex.js-toggle-board-choose(id= _id)
.materialCheckBox(data-id= _id)
span= title
li
button.js-email-invite.primary {{_ 'invite'}}
template(name="editProfilePopup")
form
label
@ -132,7 +163,7 @@ template(name="changeSettingsPopup")
a.js-toggle-desktop-drag-handles
i.fa.fa-arrows
| {{_ 'show-desktop-drag-handles'}}
if showDesktopDragHandles
if isShowDesktopDragHandles
i.fa.fa-check
unless currentUser.isWorker
li

View file

@ -3,6 +3,12 @@ Template.headerUserBar.events({
'click .js-change-avatar': Popup.open('changeAvatar'),
});
BlazeComponent.extendComponent({
onCreated() {
Meteor.subscribe('setting');
},
}).register('memberMenuPopup');
Template.memberMenuPopup.helpers({
templatesBoardId() {
currentUser = Meteor.user();
@ -22,18 +28,47 @@ Template.memberMenuPopup.helpers({
return false;
}
},
isSameDomainNameSettingValue(){
const currSett = Settings.findOne();
if(currSett && currSett != undefined && currSett.disableRegistration && currSett.mailDomainName !== undefined && currSett.mailDomainName != ""){
currentUser = Meteor.user();
if (currentUser) {
let found = false;
for(let i = 0; i < currentUser.emails.length; i++) {
if(currentUser.emails[i].address.endsWith(currSett.mailDomainName)){
found = true;
break;
}
}
return found;
} else {
return true;
}
}
else
return false;
},
isNotOAuth2AuthenticationMethod(){
currentUser = Meteor.user();
if (currentUser) {
return currentUser.authenticationMethod.toLowerCase() != 'oauth2';
} else {
return true;
}
}
});
Template.memberMenuPopup.events({
'click .js-my-cards'() {
Popup.close();
Popup.back();
},
'click .js-due-cards'() {
Popup.close();
Popup.back();
},
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
},
'click .js-invite-people': Popup.open('invitePeople'),
'click .js-edit-profile': Popup.open('editProfile'),
'click .js-change-settings': Popup.open('changeSettings'),
'click .js-change-avatar': Popup.open('changeAvatar'),
@ -45,7 +80,67 @@ Template.memberMenuPopup.events({
AccountsTemplates.logout();
},
'click .js-go-setting'() {
Popup.close();
Popup.back();
},
});
BlazeComponent.extendComponent({
onCreated() {
Meteor.subscribe('setting');
},
}).register('editProfilePopup');
Template.invitePeoplePopup.events({
'click a.js-toggle-board-choose'(event){
let target = $(event.target);
if (!target.hasClass('js-toggle-board-choose')) {
target = target.parent();
}
const checkboxId = target.attr('id');
$(`#${checkboxId} .materialCheckBox`).toggleClass('is-checked');
$(`#${checkboxId}`).toggleClass('is-checked');
},
'click button.js-email-invite'(event){
const emails = $('#email-to-invite')
.val()
.toLowerCase()
.trim()
.split('\n')
.join(',')
.split(',');
const boardsToInvite = [];
$('.js-toggle-board-choose .materialCheckBox.is-checked').each(function() {
boardsToInvite.push($(this).data('id'));
});
const validEmails = [];
emails.forEach(email => {
if (email && SimpleSchema.RegEx.Email.test(email.trim())) {
validEmails.push(email.trim());
}
});
if (validEmails.length) {
Meteor.call('sendInvitation', validEmails, boardsToInvite, (_, rc) => {
if (rc == 0) {
let divInfos = document.getElementById("invite-people-infos");
if(divInfos && divInfos !== undefined){
divInfos.innerHTML = "<span style='color: green'>" + TAPi18n.__('invite-people-success') + "</span>";
}
}
else{
let divInfos = document.getElementById("invite-people-infos");
if(divInfos && divInfos !== undefined){
divInfos.innerHTML = "<span style='color: red'>" + TAPi18n.__('invite-people-error') + "</span>";
}
}
// Popup.close();
});
}
},
});
Template.invitePeoplePopup.helpers({
currentSetting() {
return Settings.findOne();
},
});
@ -147,7 +242,7 @@ Template.editProfilePopup.events({
} else Popup.back();
},
'click #deleteButton': Popup.afterConfirm('userDelete', function() {
Popup.close();
Popup.back();
Users.remove(Meteor.userId());
AccountsTemplates.logout();
}),
@ -171,23 +266,41 @@ Template.changeLanguagePopup.helpers({
} else if (lang.name === 'ar-EG') {
// ar-EG = Arabic (Egypt), simply Masri (مَصرى, [ˈmɑsˤɾi], Egyptian, Masr refers to Cairo)
name = 'مَصرى';
} else if (lang.name === 'de-CH') {
name = 'Deutsch (Schweiz)';
} else if (lang.name === 'de-AT') {
name = 'Deutsch (Österreich)';
} else if (lang.name === 'en-DE') {
name = 'English (Germany)';
} else if (lang.name === 'fa-IR') {
// fa-IR = Persian (Iran)
name = 'فارسی/پارسی (ایران‎)';
} else if (lang.name === 'de-CH') {
name = 'Deutsch (Schweiz)';
} else if (lang.name === 'fr-BE') {
name = 'Français (Belgique)';
} else if (lang.name === 'fr-CA') {
name = 'Français (Canada)';
} else if (lang.name === 'fr-CH') {
name = 'Français (Schweiz)';
} else if (lang.name === 'gu-IN') {
// gu-IN = Gurajati (India)
name = 'ગુજરાતી';
} else if (lang.name === 'hi-IN') {
// hi-IN = Hindi (India)
name = 'हिंदी (भारत)';
} else if (lang.name === 'ig') {
name = 'Igbo';
} else if (lang.name === 'lv') {
name = 'Latviešu';
} else if (lang.name === 'latviešu valoda') {
name = 'Latviešu';
} else if (lang.name === 'ms-MY') {
// ms-MY = Malay (Malaysia)
name = 'بهاس ملايو';
} else if (lang.name === 'en-IT') {
name = 'English (Italy)';
} else if (lang.name === 'el-GR') {
// el-GR = Greek (Greece)
name = 'Ελληνικά (Ελλάδα)';
} else if (lang.name === 'Español') {
name = 'español';
} else if (lang.name === 'es_419') {
@ -219,6 +332,7 @@ Template.changeLanguagePopup.helpers({
} else if (lang.name === 'st') {
name = 'Sãotomense';
} else if (lang.name === '繁体中文(台湾)') {
// Traditional Chinese (Taiwan)
name = '繁體中文(台灣)';
}
return { tag, name };
@ -249,16 +363,6 @@ Template.changeLanguagePopup.events({
});
Template.changeSettingsPopup.helpers({
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
hiddenSystemMessages() {
currentUser = Meteor.user();
if (currentUser) {