mirror of https://github.com/wekan/wekan
The UI and the internal APIs are still rough around the edges but the feature is basically working. You can now select multiple cards and move them together or (un|)assign them a label.pull/188/head
parent
6457615e6a
commit
2c0030da62
@ -1,73 +1,73 @@ |
||||
{ |
||||
"disallowSpacesInNamedFunctionExpression": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowSpacesInFunctionExpression": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowSpacesInAnonymousFunctionExpression": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowSpacesInFunctionDeclaration": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowEmptyBlocks": true, |
||||
"disallowSpacesInsideArrayBrackets": true, |
||||
"disallowSpacesInsideParentheses": true, |
||||
"disallowQuotedKeysInObjects": "allButReserved", |
||||
"disallowSpaceAfterObjectKeys": true, |
||||
"disallowSpaceAfterPrefixUnaryOperators": [ |
||||
"++", |
||||
"--", |
||||
"+", |
||||
"-", |
||||
"~" |
||||
], |
||||
"disallowSpaceBeforePostfixUnaryOperators": true, |
||||
"disallowSpaceBeforeBinaryOperators": [ |
||||
"," |
||||
], |
||||
"disallowMixedSpacesAndTabs": true, |
||||
"disallowTrailingWhitespace": true, |
||||
"disallowTrailingComma": true, |
||||
"disallowYodaConditions": true, |
||||
"disallowKeywords": [ "with" ], |
||||
"disallowMultipleLineBreaks": true, |
||||
"disallowMultipleVarDecl": "exceptUndefined", |
||||
"requireSpaceBeforeBlockStatements": true, |
||||
"requireParenthesesAroundIIFE": true, |
||||
"requireSpacesInConditionalExpression": true, |
||||
"requireBlocksOnNewline": 1, |
||||
"requireCommaBeforeLineBreak": true, |
||||
"requireSpaceAfterPrefixUnaryOperators": [ |
||||
"!" |
||||
], |
||||
"requireSpaceBeforeBinaryOperators": true, |
||||
"requireSpaceAfterBinaryOperators": true, |
||||
"requireCamelCaseOrUpperCaseIdentifiers": true, |
||||
"requireLineFeedAtFileEnd": true, |
||||
"requireCapitalizedConstructors": true, |
||||
"requireDotNotation": true, |
||||
"requireSpacesInForStatement": true, |
||||
"requireSpaceBetweenArguments": true, |
||||
"requireCurlyBraces": [ |
||||
"do" |
||||
], |
||||
"requireSpaceAfterKeywords": [ |
||||
"if", |
||||
"else", |
||||
"for", |
||||
"while", |
||||
"do", |
||||
"switch", |
||||
"case", |
||||
"return", |
||||
"try", |
||||
"catch", |
||||
"typeof" |
||||
], |
||||
"validateLineBreaks": "LF", |
||||
"validateQuoteMarks": "'", |
||||
"validateIndentation": 2, |
||||
"maximumLineLength": 80 |
||||
"disallowSpacesInNamedFunctionExpression": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowSpacesInFunctionExpression": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowSpacesInAnonymousFunctionExpression": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowSpacesInFunctionDeclaration": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowEmptyBlocks": true, |
||||
"disallowSpacesInsideArrayBrackets": true, |
||||
"disallowSpacesInsideParentheses": true, |
||||
"disallowQuotedKeysInObjects": "allButReserved", |
||||
"disallowSpaceAfterObjectKeys": true, |
||||
"disallowSpaceAfterPrefixUnaryOperators": [ |
||||
"++", |
||||
"--", |
||||
"+", |
||||
"-", |
||||
"~" |
||||
], |
||||
"disallowSpaceBeforePostfixUnaryOperators": true, |
||||
"disallowSpaceBeforeBinaryOperators": [ |
||||
"," |
||||
], |
||||
"disallowMixedSpacesAndTabs": true, |
||||
"disallowTrailingWhitespace": true, |
||||
"disallowTrailingComma": true, |
||||
"disallowYodaConditions": true, |
||||
"disallowKeywords": [ "with" ], |
||||
"disallowMultipleLineBreaks": true, |
||||
"disallowMultipleVarDecl": "exceptUndefined", |
||||
"requireSpaceBeforeBlockStatements": true, |
||||
"requireParenthesesAroundIIFE": true, |
||||
"requireSpacesInConditionalExpression": true, |
||||
"requireBlocksOnNewline": 1, |
||||
"requireCommaBeforeLineBreak": true, |
||||
"requireSpaceAfterPrefixUnaryOperators": [ |
||||
"!" |
||||
], |
||||
"requireSpaceBeforeBinaryOperators": true, |
||||
"requireSpaceAfterBinaryOperators": true, |
||||
"requireCamelCaseOrUpperCaseIdentifiers": true, |
||||
"requireLineFeedAtFileEnd": true, |
||||
"requireCapitalizedConstructors": true, |
||||
"requireDotNotation": true, |
||||
"requireSpacesInForStatement": true, |
||||
"requireSpaceBetweenArguments": true, |
||||
"requireCurlyBraces": [ |
||||
"do" |
||||
], |
||||
"requireSpaceAfterKeywords": [ |
||||
"if", |
||||
"else", |
||||
"for", |
||||
"while", |
||||
"do", |
||||
"switch", |
||||
"case", |
||||
"return", |
||||
"try", |
||||
"catch", |
||||
"typeof" |
||||
], |
||||
"validateLineBreaks": "LF", |
||||
"validateQuoteMarks": "'", |
||||
"validateIndentation": 2, |
||||
"maximumLineLength": 80 |
||||
} |
||||
|
@ -0,0 +1,57 @@ |
||||
//- |
||||
XXX There is a *lot* of code duplication in the above templates and in the |
||||
corresponding JavaScript components. We will probably need the upcoming #let |
||||
and #each x in y constructors. |
||||
|
||||
template(name="filterSidebar") |
||||
ul.sidebar-list |
||||
each currentBoard.labels |
||||
li |
||||
a.name.js-toggle-label-filter |
||||
span.card-label.square(class="card-label-{{color}}") |
||||
span.sidebar-list-item-description |
||||
if name |
||||
= name |
||||
else |
||||
span.quiet {{_ "label-default" color}} |
||||
if Filter.labelIds.isSelected _id |
||||
i.fa.fa-check |
||||
hr |
||||
ul.sidebar-list |
||||
each currentBoard.members |
||||
if isActive |
||||
with getUser userId |
||||
li(class="{{#if Filter.members.isSelected _id}}active{{/if}}") |
||||
a.name.js-toogle-member-filter |
||||
+userAvatar(user=this size="small") |
||||
span.sidebar-list-item-description |
||||
= profile.name |
||||
| (<span class="username">{{ username }}</span>) |
||||
if Filter.members.isSelected _id |
||||
i.fa.fa-check |
||||
hr |
||||
a.js-clear-all(class="{{#unless Filter.isActive}}disabled{{/unless}}") |
||||
| {{_ 'filter-clear'}} |
||||
|
||||
template(name="multiselectionSidebar") |
||||
ul.sidebar-list |
||||
each currentBoard.labels |
||||
li |
||||
a.name.js-toggle-label-multiselection |
||||
span.card-label.square(class="card-label-{{color}}") |
||||
span.sidebar-list-item-description |
||||
if name |
||||
= name |
||||
else |
||||
span.quiet {{_ "label-default" color}} |
||||
if allSelectedElementHave 'label' _id |
||||
i.fa.fa-check |
||||
else if someSelectedElementHave 'label' _id |
||||
i.fa.fa-ellipsis-h |
||||
//- |
||||
XXX We should be able to assign a member to the list of selected cards. |
||||
|
||||
template(name="disambiguateMultiLabelPopup") |
||||
p What do you want to do? |
||||
button.wide.js-remove-label Remove the label |
||||
button.wide.js-add-label Add the label |
@ -0,0 +1,94 @@ |
||||
BlazeComponent.extendComponent({ |
||||
template: function() { |
||||
return 'filterSidebar'; |
||||
}, |
||||
|
||||
events: function() { |
||||
return [{ |
||||
'click .js-toggle-label-filter': function(event) { |
||||
Filter.labelIds.toogle(this._id); |
||||
Filter.resetExceptions(); |
||||
event.preventDefault(); |
||||
}, |
||||
'click .js-toogle-member-filter': function(event) { |
||||
Filter.members.toogle(this._id); |
||||
Filter.resetExceptions(); |
||||
event.preventDefault(); |
||||
}, |
||||
'click .js-clear-all': function(event) { |
||||
Filter.reset(); |
||||
event.preventDefault(); |
||||
} |
||||
}]; |
||||
} |
||||
}).register('filterSidebar'); |
||||
|
||||
var updateSelectedCards = function(query) { |
||||
Cards.find(MultiSelection.getMongoSelector()).forEach(function(card) { |
||||
Cards.update(card._id, query); |
||||
}); |
||||
}; |
||||
|
||||
BlazeComponent.extendComponent({ |
||||
template: function() { |
||||
return 'multiselectionSidebar'; |
||||
}, |
||||
|
||||
mapSelection: function(kind, _id) { |
||||
return Cards.find(MultiSelection.getMongoSelector()).map(function(card) { |
||||
var methodName = kind === 'label' ? 'hasLabel' : 'isAssigned'; |
||||
return card[methodName](_id); |
||||
}); |
||||
}, |
||||
|
||||
allSelectedElementHave: function(kind, _id) { |
||||
if (MultiSelection.isEmpty()) |
||||
return false; |
||||
else |
||||
return _.every(this.mapSelection(kind, _id)); |
||||
}, |
||||
|
||||
someSelectedElementHave: function(kind, _id) { |
||||
if (MultiSelection.isEmpty()) |
||||
return false; |
||||
else |
||||
return _.some(this.mapSelection(kind, _id)); |
||||
}, |
||||
|
||||
events: function() { |
||||
return [{ |
||||
'click .js-toggle-label-multiselection': function(evt, tpl) { |
||||
var labelId = this.currentData()._id; |
||||
var mappedSelection = this.mapSelection('label', labelId); |
||||
var operation; |
||||
if (_.every(mappedSelection)) |
||||
operation = '$pull'; |
||||
else if (_.every(mappedSelection, function(bool) { return ! bool; })) |
||||
operation = '$addToSet'; |
||||
else { |
||||
var popup = Popup.open('disambiguateMultiLabel'); |
||||
// XXX We need to have a better integration between the popup and the
|
||||
// UI components systems.
|
||||
return popup.call(this.currentData(), evt, tpl); |
||||
} |
||||
|
||||
var query = {}; |
||||
query[operation] = { |
||||
labelIds: labelId |
||||
}; |
||||
updateSelectedCards(query); |
||||
} |
||||
}]; |
||||
} |
||||
}).register('multiselectionSidebar'); |
||||
|
||||
Template.disambiguateMultiLabelPopup.events({ |
||||
'click .js-remove-label': function() { |
||||
updateSelectedCards({$pull: {labelIds: this._id}}); |
||||
Popup.close(); |
||||
}, |
||||
'click .js-add-label': function() { |
||||
updateSelectedCards({$addToSet: {labelIds: this._id}}); |
||||
Popup.close(); |
||||
} |
||||
}); |
@ -0,0 +1,77 @@ |
||||
<!-- XXX Translate these template into jade --> |
||||
<template name="closeBoardPopup"> |
||||
<p>{{_ 'close-board-pop'}}</p> |
||||
<input type="submit" class="js-confirm negate full" value="{{_ 'close'}}"> |
||||
</template> |
||||
|
||||
<template name="removeMemberPopup"> |
||||
<p>{{_ 'remove-member-pop' |
||||
name=user.profile.name |
||||
username=user.username |
||||
boardTitle=board.title}}</p> |
||||
<input type="submit" class="js-confirm negate full" value="{{_ 'remove-member'}}"> |
||||
</template> |
||||
|
||||
<template name="addMemberPopup"> |
||||
<div class="search-with-spinner"> |
||||
{{> esInput index="users" }} |
||||
</div> |
||||
|
||||
<div class="manage-member-section hide js-search-results" style="display: block;"> |
||||
<ul class="pop-over-member-list options js-list"> |
||||
{{# esEach index="users"}} |
||||
<li class="item js-member-item {{# if isBoardMember }}disabled{{/if}}"> |
||||
<a href="#" class="name js-select-member {{# if isBoardMember }}multi-line{{/if}}" title="{{ profile.name }} ({{ username }})"> |
||||
{{> userAvatar user=this size="small" }} |
||||
<span class="full-name"> |
||||
{{ profile.name }} (<span class="username">{{ username }}</span>) |
||||
</span> |
||||
{{# if isBoardMember }} |
||||
<div class="extra-text quiet">({{_ 'joined'}})</div> |
||||
{{/if}} |
||||
<span class="icon-sm fa fa-chevron-right light option js-open-option"></span> |
||||
</a> |
||||
</li> |
||||
{{/esEach }} |
||||
</ul> |
||||
</div> |
||||
|
||||
{{# ifEsIsSearching index='users' }} |
||||
<div class="tac"> |
||||
<span class="tabbed-pane-main-col-loading-spinner spinner"></span> |
||||
</div> |
||||
{{ /ifEsIsSearching }} |
||||
|
||||
{{# ifEsHasNoResults index="users" }} |
||||
<div class="manage-member-section js-no-results"> |
||||
<p class="quiet center" style="padding: 16px 4px;">{{_ 'no-results'}}</p> |
||||
</div> |
||||
{{ /ifEsHasNoResults }} |
||||
|
||||
<div class="manage-member-section js-helper"> |
||||
<p class="bottom quiet" style="padding: 6px;">{{_ 'search-member-desc'}}</p> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="changePermissionsPopup"> |
||||
<ul class="pop-over-list"> |
||||
<li> |
||||
<a class="{{#if isLastAdmin}}disabled{{else}}js-set-admin{{/if}}"> |
||||
{{_ 'admin'}} |
||||
{{#if isAdmin}}<span class="icon-sm fa fa-check"></span>{{/if}} |
||||
<span class="sub-name">{{_ 'admin-desc'}}</span> |
||||
</a> |
||||
</li> |
||||
<li> |
||||
<a class="{{#if isLastAdmin}}disabled{{else}}js-set-normal{{/if}}"> |
||||
{{_ 'normal'}} |
||||
{{#unless isAdmin}}<span class="icon-sm fa fa-check"></span>{{/unless}} |
||||
<span class="sub-name">{{_ 'normal-desc'}}</span> |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
{{#if isLastAdmin}} |
||||
<hr> |
||||
<p class="quiet bottom">{{_ 'last-admin-desc'}}</p> |
||||
{{/if}} |
||||
</template> |
@ -1,307 +0,0 @@ |
||||
<template name="boardWidgets"> |
||||
<a href="#" class="sidebar-show-btn dark-hover js-show-sidebar"> |
||||
<span class="icon-sm fa fa-chevron-left"></span> |
||||
<span class="text">{{_ 'show-sidebar'}}</span> |
||||
</a> |
||||
<div class="board-widgets {{#if session 'sidebarIsOpen'}}show{{else}}hide{{/if}}"> |
||||
<div> |
||||
<a href="#" class="sidebar-hide-btn dark-hover js-hide-sidebar" title="{{_ 'close-sidebar-title'}}"> |
||||
<span class="icon-sm fa fa-chevron-right"></span> |
||||
</a> |
||||
{{#unless isTrue currentWidget "homeWidget"}} |
||||
<div class="board-widgets-title clearfix"> |
||||
<a href="#" class="board-sidebar-back-btn js-pop-widget-view"> |
||||
<span class="left-arrow"></span>{{_ 'back'}} |
||||
</a> |
||||
<h3 class="text">{{currentWidgetTitle}}</h3> |
||||
<hr> |
||||
</div> |
||||
{{/unless}} |
||||
<div class="board-widgets-content-wrapper"> |
||||
<div class="board-widgets-content default fancy-scrollbar short{{#unless session 'menuWidgetIsOpen'}} short{{/unless}}"> |
||||
{{> UI.dynamic template=currentWidget data=this }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="homeWidget"> |
||||
{{ > menuWidget }} |
||||
{{ > membersWidget }} |
||||
{{ > activityWidget }} |
||||
</template> |
||||
|
||||
<template name="menuWidget"> |
||||
<div class="board-widget board-widget-nav clearfix{{#unless session 'menuWidgetIsOpen'}} collapsed{{/unless}}"> |
||||
<h3 class="dark-hover toggle-widget-nav js-toggle-widget-nav">{{_ 'menu'}} |
||||
<span class="icon-sm fa fa-chevron-circle-down toggle-menu-icon"></span> |
||||
</h3> |
||||
<ul class="nav-list"> |
||||
<hr style="margin-top: 0;"> |
||||
<li> |
||||
<a href="#" class="nav-list-item js-open-archive"> |
||||
<span class="icon-sm fa fa-archive icon-type"></span> |
||||
{{_ 'archived-items'}} |
||||
</a> |
||||
</li> |
||||
<li> |
||||
<a href="#" class="nav-list-item js-open-card-filter"> |
||||
<span class="icon-sm fa fa-filter icon-type"></span> |
||||
{{_ 'filter-cards'}} |
||||
</a> |
||||
</li> |
||||
{{#if currentUser.isBoardAdmin}} |
||||
<hr> |
||||
<li> |
||||
<a class="nav-list-item nav-list-sub-item board-settings-background js-change-background"> |
||||
<span class="board-settings-background-preview" style="background-color:{{board.background.color}}"></span> |
||||
{{_ 'change-background'}}… |
||||
</a> |
||||
</li> |
||||
{{#unless isSandstorm }} |
||||
<li> |
||||
<a class="nav-list-item nav-list-sub-item js-close-board" href="#">{{_ 'close-board'}}</a> |
||||
</li> |
||||
{{/unless}} |
||||
{{/if}} |
||||
{{! |
||||
XXX Language should be handled by sandstorm, but for now display a language selection link in the board menu. |
||||
This link is normally present in the header bar that is not displayed on sandstorm. |
||||
}} |
||||
{{#if isSandstorm}} |
||||
<hr> |
||||
<li> |
||||
<a class="nav-list-item nav-list-sub-item js-language">{{_ 'language'}}</a> |
||||
</li> |
||||
{{/if}} |
||||
</ul> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="membersWidget"> |
||||
<hr> |
||||
<div class="board-widget board-widget-members clearfix"> |
||||
<div class="board-widget-title"> |
||||
<h3>{{_ 'members'}}</h3> |
||||
</div> |
||||
<div class="board-widget-content"> |
||||
<div class="board-widget-members js-list-board-members clearfix js-list-draggable-board-members"> |
||||
{{# each board.members }} |
||||
{{> userAvatar userId=this.userId draggable=true size="small" showBadges=true}} |
||||
{{/ each }} |
||||
</div> |
||||
{{# unless isSandstrom }} |
||||
{{# if currentUser.isBoardAdmin }} |
||||
<a href="#" class="button-link js-open-manage-board-members"> |
||||
<span class="icon-sm fa fa-user"></span> {{_ 'add-members'}} |
||||
</a> |
||||
{{/ if }} |
||||
{{/ unless }} |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="activityWidget"> |
||||
{{# if board.activities.count }} |
||||
<hr> |
||||
<div class="board-widget board-widget-activity bottom clearfix"> |
||||
<div class="board-widget-title"> |
||||
<h3>{{_ 'activity'}}</h3> |
||||
</div> |
||||
<div class="board-widget-content"> |
||||
<div class="activity-gradient-t"></div> |
||||
<div class="activity-gradient-b"></div> |
||||
<div class="board-actions-list fancy-scrollbar"> |
||||
{{ > activities }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{{/if}} |
||||
</template> |
||||
|
||||
<template name="memberPopup"> |
||||
<div class="board-member-menu"> |
||||
<div class="mini-profile-info"> |
||||
{{> userAvatar user=user}} |
||||
<div class="info"> |
||||
<h3 class="bottom" style="margin-right: 40px;"> |
||||
<a class="js-profile" href="{{ pathFor route='Profile' username=user.username }}">{{ user.profile.name }}</a> |
||||
</h3> |
||||
<p class="quiet bottom">@{{ user.username }}</p> |
||||
</div> |
||||
</div> |
||||
{{# if currentUser.isBoardMember }} |
||||
<ul class="pop-over-list"> |
||||
{{# if currentUser.isBoardAdmin }} |
||||
<li> |
||||
<a class="js-change-role" href="#"> |
||||
{{_ 'change-permissions'}} <span class="quiet" style="font-weight: normal;">({{ memberType }})</span> |
||||
</a> |
||||
</li> |
||||
{{/ if }} |
||||
|
||||
<li> |
||||
{{# if currentUser.isBoardAdmin }} |
||||
<a class="js-remove-member">{{_ 'remove-from-board'}}</a> |
||||
{{ else }} |
||||
<a class="js-leave-member">{{_ 'leave-board'}}</a> |
||||
{{/ if }} |
||||
</li> |
||||
</ul> |
||||
{{/ if }} |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="filterWidget"> |
||||
<ul class="pop-over-label-list checkable"> |
||||
{{#each board.labels}} |
||||
<li class="item matches-filter"> |
||||
<a class="name js-toggle-label-filter"> |
||||
<span class="card-label card-label-{{color}}"></span> |
||||
<span class="full-name"> |
||||
{{#if name}} |
||||
{{name}} |
||||
{{else}} |
||||
<span class="quiet">{{_ "label-default" color}}</span> |
||||
{{/if}} |
||||
</span> |
||||
{{#if Filter.labelIds.isSelected _id}} |
||||
<span class="icon-sm fa fa-check"></span> |
||||
{{/if}} |
||||
</a> |
||||
</li> |
||||
{{/each}} |
||||
</ul> |
||||
<hr> |
||||
<ul class="pop-over-member-list checkable"> |
||||
{{#each board.members}} |
||||
{{#with getUser userId}} |
||||
<li class="item js-member-item {{#if Filter.members.isSelected _id}}active{{/if}}"> |
||||
<a href="#" class="name js-toogle-member-filter"> |
||||
{{> userAvatar user=this size="small" }} |
||||
<span class="full-name"> |
||||
{{ profile.name }} |
||||
(<span class="username">{{ username }}</span>) |
||||
</span> |
||||
{{#if Filter.members.isSelected _id}} |
||||
<span class="icon-sm fa fa-check checked-icon"></span> |
||||
{{/if}} |
||||
</a> |
||||
</li> |
||||
{{/with}} |
||||
{{/each}} |
||||
</ul> |
||||
<hr> |
||||
<ul class="pop-over-list inset normal-weight"> |
||||
<li> |
||||
<a class="js-clear-all {{#unless Filter.isActive}}disabled{{/unless}}" style="padding-left: 40px;"> |
||||
{{_ 'filter-clear'}} |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
</template> |
||||
|
||||
<template name="backgroundWidget"> |
||||
<div class="board-widgets-content-wrapper fancy-scrollbar"> |
||||
<div class="board-widgets-content"> |
||||
<div class="board-backgrounds-list clearfix"> |
||||
{{#each backgroundColors}} |
||||
<div class="board-background-select js-select-background"> |
||||
<span class="background-box " style="background-color: {{this}}; "></span> |
||||
</div> |
||||
{{/each}} |
||||
</div> |
||||
{{!-- |
||||
<h2 class="clear">Photos</h2> |
||||
<div class="board-backgrounds-list relative clearfix js-gold-photos-list disabled"> |
||||
<div class="board-background-select js-select-background"> |
||||
<span class="background-box " style="background-image: url("{{url}}");"> |
||||
<a class="background-option js-background-attribution" href={{href}} target="_blank" title={{title}}> |
||||
<img src="https://d78fikflryjgj.cloudfront.net/images/d906fe5c1274c56c5571d49705547587/cc.png" style="height: 14px; width: 14px; vertical-align: text-top;" title="http://creativecommons.org/licenses/by/2.0/deed.en"> |
||||
<span class="text" style="margin-left: 2px;">{{author}}</span> |
||||
</a> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
--}} |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="closeBoardPopup"> |
||||
<p>{{_ 'close-board-pop'}}</p> |
||||
<input type="submit" class="js-confirm negate full" value="{{_ 'close'}}"> |
||||
</template> |
||||
|
||||
<template name="removeMemberPopup"> |
||||
<p>{{_ 'remove-member-pop' |
||||
name=user.profile.name |
||||
username=user.username |
||||
boardTitle=board.title}}</p> |
||||
<input type="submit" class="js-confirm negate full" value="{{_ 'remove-member'}}"> |
||||
</template> |
||||
|
||||
<template name="addMemberPopup"> |
||||
<div class="search-with-spinner"> |
||||
{{> esInput index="users" }} |
||||
</div> |
||||
|
||||
<div class="manage-member-section hide js-search-results" style="display: block;"> |
||||
<ul class="pop-over-member-list options js-list"> |
||||
{{# esEach index="users"}} |
||||
<li class="item js-member-item {{# if isBoardMember }}disabled{{/if}}"> |
||||
<a href="#" class="name js-select-member {{# if isBoardMember }}multi-line{{/if}}" title="{{ profile.name }} ({{ username }})"> |
||||
{{> userAvatar user=this size="small" }} |
||||
<span class="full-name"> |
||||
{{ profile.name }} (<span class="username">{{ username }}</span>) |
||||
</span> |
||||
{{# if isBoardMember }} |
||||
<div class="extra-text quiet">({{_ 'joined'}})</div> |
||||
{{/if}} |
||||
<span class="icon-sm fa fa-chevron-right light option js-open-option"></span> |
||||
</a> |
||||
</li> |
||||
{{/esEach }} |
||||
</ul> |
||||
</div> |
||||
|
||||
{{# ifEsIsSearching index='users' }} |
||||
<div class="tac"> |
||||
<span class="tabbed-pane-main-col-loading-spinner spinner"></span> |
||||
</div> |
||||
{{ /ifEsIsSearching }} |
||||
|
||||
{{# ifEsHasNoResults index="users" }} |
||||
<div class="manage-member-section js-no-results"> |
||||
<p class="quiet center" style="padding: 16px 4px;">{{_ 'no-results'}}</p> |
||||
</div> |
||||
{{ /ifEsHasNoResults }} |
||||
|
||||
<div class="manage-member-section js-helper"> |
||||
<p class="bottom quiet" style="padding: 6px;">{{_ 'search-member-desc'}}</p> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="changePermissionsPopup"> |
||||
<ul class="pop-over-list"> |
||||
<li> |
||||
<a class="{{#if isLastAdmin}}disabled{{else}}js-set-admin{{/if}}"> |
||||
{{_ 'admin'}} |
||||
{{#if isAdmin}}<span class="icon-sm fa fa-check"></span>{{/if}} |
||||
<span class="sub-name">{{_ 'admin-desc'}}</span> |
||||
</a> |
||||
</li> |
||||
<li> |
||||
<a class="{{#if isLastAdmin}}disabled{{else}}js-set-normal{{/if}}"> |
||||
{{_ 'normal'}} |
||||
{{#unless isAdmin}}<span class="icon-sm fa fa-check"></span>{{/unless}} |
||||
<span class="sub-name">{{_ 'normal-desc'}}</span> |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
{{#if isLastAdmin}} |
||||
<hr> |
||||
<p class="quiet bottom">{{_ 'last-admin-desc'}}</p> |
||||
{{/if}} |
||||
</template> |
@ -0,0 +1,159 @@ |
||||
|
||||
var getCardsBetween = function(idA, idB) { |
||||
|
||||
var pluckId = function(doc) { |
||||
return doc._id; |
||||
}; |
||||
|
||||
var getListsStrictlyBetween = function(id1, id2) { |
||||
return Lists.find({ |
||||
$and: [ |
||||
{ sort: { $gt: Lists.findOne(id1).sort } }, |
||||
{ sort: { $lt: Lists.findOne(id2).sort } } |
||||
], |
||||
archived: false |
||||
}).map(pluckId); |
||||
}; |
||||
|
||||
var cards = _.sortBy([Cards.findOne(idA), Cards.findOne(idB)], function(c) { |
||||
return c.sort; |
||||
}); |
||||
|
||||
var selector; |
||||
if (cards[0].listId === cards[1].listId) { |
||||
selector = { |
||||
listId: cards[0].listId, |
||||
sort: { |
||||
$gte: cards[0].sort, |
||||
$lte: cards[1].sort |
||||
}, |
||||
archived: false |
||||
}; |
||||
} else { |
||||
selector = { |
||||
$or: [{ |
||||
listId: cards[0].listId, |
||||
sort: { $lte: cards[0].sort } |
||||
}, { |
||||
listId: { |
||||
$in: getListsStrictlyBetween(cards[0].listId, cards[1].listId) |
||||
} |
||||
}, { |
||||
listId: cards[1].listId, |
||||
sort: { $gte: cards[1].sort } |
||||
}], |
||||
archived: false |
||||
}; |
||||
} |
||||
|
||||
return Cards.find(Filter.mongoSelector(selector)).map(pluckId); |
||||
}; |
||||
|
||||
MultiSelection = { |
||||
sidebarView: 'multiselection', |
||||
|
||||
_selectedCards: new ReactiveVar([]), |
||||
|
||||
_isActive: new ReactiveVar(false), |
||||
|
||||
startRangeCardId: null, |
||||
|
||||
reset: function() { |
||||
this._selectedCards.set([]); |
||||
}, |
||||
|
||||
getMongoSelector: function() { |
||||
return Filter.mongoSelector({ |
||||
_id: { $in: this._selectedCards.get() } |
||||
}); |
||||
}, |
||||
|
||||
isActive: function() { |
||||
return this._isActive.get(); |
||||
}, |
||||
|
||||
isEmpty: function() { |
||||
return this._selectedCards.get().length === 0; |
||||
}, |
||||
|
||||
activate: function() { |
||||
if (! this.isActive()) { |
||||
EscapeActions.executeLowerThan('detailsPane'); |
||||
this._isActive.set(true); |
||||
Sidebar.setView(this.sidebarView); |
||||
Tracker.flush(); |
||||
} |
||||
}, |
||||
|
||||
disable: function() { |
||||
if (this.isActive()) { |
||||
this._isActive.set(false); |
||||
if (Sidebar && Sidebar.getView() === this.sidebarView) { |
||||
Sidebar.setView(); |
||||
} |
||||
} |
||||
}, |
||||
|
||||
add: function(cardIds) { |
||||
return this.toogle(cardIds, { add: true, remove: false }); |
||||
}, |
||||
|
||||
remove: function(cardIds) { |
||||
return this.toogle(cardIds, { add: false, remove: true }); |
||||
}, |
||||
|
||||
toogleRange: function(cardId) { |
||||
var selectedCards = this._selectedCards.get(); |
||||
var startRange; |
||||
this.reset(); |
||||
if (! this.isActive() || selectedCards.length === 0) { |
||||
this.toogle(cardId); |
||||
} else { |
||||
startRange = selectedCards[selectedCards.length - 1]; |
||||
this.toogle(getCardsBetween(startRange, cardId)); |
||||
} |
||||
}, |
||||
|
||||
toogle: function(cardIds, options) { |
||||
var self = this; |
||||
cardIds = _.isString(cardIds) ? [cardIds] : cardIds; |
||||
options = _.extend({ |
||||
add: true, |
||||
remove: true |
||||
}, options || {}); |
||||
|
||||
if (! self.isActive()) { |
||||
self.reset(); |
||||
self.activate(); |
||||
} |
||||
|
||||
var selectedCards = self._selectedCards.get(); |
||||
|
||||
_.each(cardIds, function(cardId) { |
||||
var indexOfCard = selectedCards.indexOf(cardId); |
||||
|
||||
if (options.remove && indexOfCard > -1) |
||||
selectedCards.splice(indexOfCard, 1); |
||||
|
||||
else if (options.add) |
||||
selectedCards.push(cardId); |
||||
}); |
||||
|
||||
self._selectedCards.set(selectedCards); |
||||
}, |
||||
|
||||
isSelected: function(cardId) { |
||||
return this._selectedCards.get().indexOf(cardId) > -1; |
||||
} |
||||
}; |
||||
|
||||
Blaze.registerHelper('MultiSelection', MultiSelection); |
||||
|
||||
EscapeActions.register('multiselection-disable', |
||||
function() { MultiSelection.disable(); }, |
||||
function() { return MultiSelection.isActive(); } |
||||
); |
||||
|
||||
EscapeActions.register('multiselection-reset', |
||||
function() { MultiSelection.reset(); } |
||||
); |
Loading…
Reference in new issue