summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNico <paetni1@gmail.com>2020-05-03 00:33:15 +0200
committerNico <paetni1@gmail.com>2020-05-03 00:33:15 +0200
commit3cc0a93e0ea2399d239923e3a89d49d93a979684 (patch)
treead1dbb6ce522229d18388bb1d45cb287d0314b07
parent533bc045d06269dba2f42cdfe61817a1b3407974 (diff)
downloadwekan-3cc0a93e0ea2399d239923e3a89d49d93a979684.tar.gz
wekan-3cc0a93e0ea2399d239923e3a89d49d93a979684.tar.bz2
wekan-3cc0a93e0ea2399d239923e3a89d49d93a979684.zip
Card vote options in new fork
-rw-r--r--client/components/boards/boardsList.js3
-rw-r--r--client/components/cards/cardDate.js27
-rw-r--r--client/components/cards/cardDetails.jade79
-rw-r--r--client/components/cards/cardDetails.js102
-rw-r--r--client/components/cards/cardDetails.styl5
-rw-r--r--client/components/cards/minicard.jade2
-rwxr-xr-xclient/components/main/editor.js2
-rw-r--r--i18n/en.i18n.json12
-rw-r--r--models/cards.js70
-rw-r--r--sandstorm.js2
10 files changed, 239 insertions, 65 deletions
diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js
index 9208fdb2..b99c0c31 100644
--- a/client/components/boards/boardsList.js
+++ b/client/components/boards/boardsList.js
@@ -25,7 +25,6 @@ BlazeComponent.extendComponent({
},
onRendered() {
- const self = this;
function userIsAllowedToMove() {
return Meteor.user();
}
@@ -78,7 +77,7 @@ BlazeComponent.extendComponent({
},
boards() {
- let query = {
+ const query = {
archived: false,
type: 'board',
};
diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js
index c4b5c6d8..9b2268e9 100644
--- a/client/components/cards/cardDate.js
+++ b/client/components/cards/cardDate.js
@@ -386,3 +386,30 @@ CardEndDate.register('cardEndDate');
return this.date.get().format('l');
}
}.register('minicardEndDate'));
+
+class VoteEndDate extends CardDate {
+ onCreated() {
+ super.onCreated();
+ const self = this;
+ self.autorun(() => {
+ self.date.set(moment(self.data().getVoteEnd()));
+ });
+ }
+ classes() {
+ const classes = 'end-date' + ' ';
+ return classes;
+ }
+ showDate() {
+ return this.date.get().format('l LT');
+ }
+ showTitle() {
+ return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`;
+ }
+
+ events() {
+ return super.events().concat({
+ 'click .js-edit-date': Popup.open('editVoteEndDate'),
+ });
+ }
+}
+VoteEndDate.register('voteEndDate');
diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade
index ae97e0e9..9f3b188b 100644
--- a/client/components/cards/cardDetails.jade
+++ b/client/components/cards/cardDetails.jade
@@ -202,9 +202,12 @@ template(name="cardDetails")
if getVoteQuestion
hr
.vote-title
- h3
- i.fa.fa-thumbs-up
- card-details-item-title {{_ 'vote-question'}}
+ div.flex
+ h3
+ i.fa.fa-thumbs-up
+ | {{_ 'vote-question'}}
+ if getVoteEnd
+ +voteEndDate
.vote-result
if votePublic
a.card-label.card-label-green.js-show-positive-votes {{ voteCountPositive }}
@@ -212,10 +215,13 @@ template(name="cardDetails")
else
.card-label.card-label-green {{ voteCountPositive }}
.card-label.card-label-red {{ voteCountNegative }}
+ unless ($and currentBoard.isPublic voteAllowNonBoardMembers )
+ .card-label.card-label-gray {{ voteCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }}
+viewer
= getVoteQuestion
- button.card-details-green.js-vote.js-vote-positive(class="{{#if voteState}}voted{{/if}}") {{_ 'vote-for-it'}}
- button.card-details-red.js-vote.js-vote-negative(class="{{#if $eq voteState false}}voted{{/if}}") {{_ 'vote-against'}}
+ if showVotingButtons
+ button.card-details-green.js-vote.js-vote-positive(class="{{#if voteState}}voted{{/if}}") {{_ 'vote-for-it'}}
+ button.card-details-red.js-vote.js-vote-negative(class="{{#if $eq voteState false}}voted{{/if}}") {{_ 'vote-against'}}
//- XXX We should use "editable" to avoid repetiting ourselves
if canModifyCard
@@ -333,16 +339,10 @@ template(name="cardDetailsActionsPopup")
//li: a.js-members {{_ 'card-edit-members'}}
//li: a.js-labels {{_ 'card-edit-labels'}}
//li: a.js-attachments {{_ 'card-edit-attachments'}}
- if getVoteQuestion
- li
- a.js-cancel-voting
- i.fa.fa-thumbs-up
- | {{_ 'card-cancel-voting'}}
- else
- li
- a.js-start-voting
- i.fa.fa-thumbs-up
- | {{_ 'card-start-voting'}}
+ li
+ a.js-start-voting
+ i.fa.fa-thumbs-up
+ | {{_ 'card-edit-voting'}}
li
a.js-custom-fields
i.fa.fa-list-alt
@@ -465,14 +465,14 @@ template(name="cardAssigneesPopup")
i.fa.fa-check
if currentUser.isWorker
ul.pop-over-list.js-card-assignee-list
- li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}")
- a.name.js-select-assignee(href="#")
- +userAvatar(userId=currentUser._id)
- span.full-name
- = currentUser.profile.fullname
- | (<span class="username">{{ currentUser.username }}</span>)
- if currentUser.isCardAssignee
- i.fa.fa-check
+ li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}")
+ a.name.js-select-assignee(href="#")
+ +userAvatar(userId=currentUser._id)
+ span.full-name
+ = currentUser.profile.fullname
+ | (<span class="username">{{ currentUser.username }}</span>)
+ if currentUser.isCardAssignee
+ i.fa.fa-check
template(name="userAvatarAssignee")
a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
@@ -564,20 +564,39 @@ template(name="setCardColorPopup")
template(name="cardDeletePopup")
p {{_ "card-delete-pop"}}
unless archived
- p {{_ "card-delete-suggest-archive"}}
+ p {{_ "card-delete-suggest-archive"}}
+ button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
+
+template(name="deleteVotePopup")
+ p {{_ "vote-delete-pop"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="cardStartVotingPopup")
form.edit-vote-question
.fields
label(for="vote") {{_ 'vote-question'}}
- input.js-vote-field#vote(type="text" name="vote" value="{{card.getVoteQuestion}}" autofocus)
- label(for="vote-public") {{_ 'vote-public'}}
- a.js-toggle-vote-public
- .materialCheckBox#vote-public(name="vote-public")
+ input.js-vote-field#vote(type="text" name="vote" value="{{getVoteQuestion}}" autofocus disabled="{{#if getVoteQuestion}}disabled{{/if}}")
+ .check-div
+ a.flex(class="{{#if getVoteQuestion}}is-disabled{{else}}js-toggle-vote-allow-non-members{{/if}}")
+ .materialCheckBox#vote-allow-non-members(name="vote-allow-non-members" class="{{#if voteAllowNonBoardMembers}}is-checked{{/if}}")
+ span {{_ 'allowNonBoardMembers'}}
+ .check-div
+ a.flex(class="{{#if getVoteQuestion}}is-disabled{{else}}js-toggle-vote-public{{/if}}")
+ .materialCheckBox#vote-public(name="vote-public" class="{{#if votePublic}}is-checked{{/if}}")
+ span {{_ 'vote-public'}}
+ .check-div.flex
+ i.fa.fa-hourglass-end
+ a.js-end-date
+ span
+ | {{_ 'card-end'}}
+ unless getVoteEnd
+ i.fa.fa-plus
+ if getVoteEnd
+ +voteEndDate
- button.primary.confirm.js-submit {{_ 'save'}}
- //- button.js-remove-color.negate.wide.right {{_ 'delete'}}
+ button.primary.js-submit {{_ 'save'}}
+ if getVoteQuestion
+ button.js-remove-vote.negate.wide.right {{_ 'delete'}}
template(name="positiveVoteMembersPopup")
ul.pop-over-list.js-card-member-list
diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js
index 271fbe2f..7dcadfe3 100644
--- a/client/components/cards/cardDetails.js
+++ b/client/components/cards/cardDetails.js
@@ -54,21 +54,6 @@ BlazeComponent.extendComponent({
}
return null;
},
- votePublic() {
- const card = this.currentData();
- if (card.vote) return card.vote.public;
- return null;
- },
- voteCountPositive() {
- const card = this.currentData();
- if (card.vote && card.vote.positive) return card.vote.positive.length;
- return null;
- },
- voteCountNegative() {
- const card = this.currentData();
- if (card.vote && card.vote.negative) return card.vote.negative.length;
- return null;
- },
isWatching() {
const card = this.currentData();
return card.findWatcher(Meteor.userId());
@@ -148,6 +133,15 @@ BlazeComponent.extendComponent({
return result;
},
+ showVotingButtons() {
+ const card = this.currentData();
+ return (
+ (currentUser.isBoardMember() ||
+ (currentUser && card.voteAllowNonBoardMembers())) &&
+ !card.expiredVote()
+ );
+ },
+
onRendered() {
if (Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED) {
// Send Webhook but not create Activities records ---
@@ -611,11 +605,6 @@ Template.cardDetailsActionsPopup.events({
'click .js-copy-card': Popup.open('copyCard'),
'click .js-copy-checklist-cards': Popup.open('copyChecklistToManyCards'),
'click .js-set-card-color': Popup.open('setCardColor'),
- 'click .js-cancel-voting'(event) {
- event.preventDefault();
- this.unsetVote();
- Popup.close();
- },
'click .js-move-card-to-top'(event) {
event.preventDefault();
const minOrder = _.min(
@@ -1000,22 +989,93 @@ BlazeComponent.extendComponent({
events() {
return [
{
+ 'click .js-end-date': Popup.open('editVoteEndDate'),
'submit .edit-vote-question'(evt) {
evt.preventDefault();
const voteQuestion = evt.target.vote.value;
const publicVote = $('#vote-public').hasClass('is-checked');
- this.currentCard.setVoteQuestion(voteQuestion, publicVote);
+ const allowNonBoardMembers = $('#vote-allow-non-members').hasClass(
+ 'is-checked',
+ );
+ const endString = this.currentCard.getVoteEnd();
+
+ this.currentCard.setVoteQuestion(
+ voteQuestion,
+ publicVote,
+ allowNonBoardMembers,
+ );
+ if (endString) {
+ this.currentCard.setVoteEnd(endString);
+ }
Popup.close();
},
+ 'click .js-remove-vote': Popup.afterConfirm('deleteVote', () => {
+ event.preventDefault();
+ this.currentCard.unsetVote();
+ Popup.close();
+ }),
'click a.js-toggle-vote-public'(event) {
event.preventDefault();
$('#vote-public').toggleClass('is-checked');
},
+ 'click a.js-toggle-vote-allow-non-members'(event) {
+ event.preventDefault();
+ $('#vote-allow-non-members').toggleClass('is-checked');
+ },
},
];
},
}).register('cardStartVotingPopup');
+// editVoteEndDatePopup
+(class extends DatePicker {
+ onCreated() {
+ super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
+ this.data().getVoteEnd() && this.date.set(moment(this.data().getVoteEnd()));
+ }
+ events() {
+ return [
+ {
+ 'submit .edit-date'(evt) {
+ evt.preventDefault();
+
+ // if no time was given, init with 12:00
+ const time =
+ evt.target.time.value ||
+ moment(new Date().setHours(12, 0, 0)).format('LT');
+
+ const dateString = `${evt.target.date.value} ${time}`;
+ const newDate = moment(dateString, 'L LT', true);
+ if (newDate.isValid()) {
+ // if active vote - store it
+ if (this.currentData().getVoteQuestion()) {
+ this._storeDate(newDate.toDate());
+ Popup.close();
+ } else {
+ this.currentData().vote = { end: newDate.toDate() }; // set vote end temp
+ Popup.back();
+ }
+ } else {
+ this.error.set('invalid-date');
+ evt.target.date.focus();
+ }
+ },
+ 'click .js-delete-date'(evt) {
+ evt.preventDefault();
+ this._deleteDate();
+ Popup.close();
+ },
+ },
+ ];
+ }
+ _storeDate(newDate) {
+ this.card.setVoteEnd(newDate);
+ }
+ _deleteDate() {
+ this.card.unsetVoteEnd();
+ }
+}.register('editVoteEndDatePopup'));
+
// Close the card details pane by pressing escape
EscapeActions.register(
'detailsPane',
diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl
index 3e2beadd..cfdc450d 100644
--- a/client/components/cards/cardDetails.styl
+++ b/client/components/cards/cardDetails.styl
@@ -337,6 +337,11 @@ card-details-color(background, color...)
.vote-title
display: flex
justify-content: space-between
+
+ .js-edit-date
+ align-self: baseline
+ margin-left: 5px
+
.vote-result
display: flex
.js-show-positive-votes
diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade
index b6ccd4d7..79dd9127 100644
--- a/client/components/cards/minicard.jade
+++ b/client/components/cards/minicard.jade
@@ -103,7 +103,9 @@ template(name="minicard")
if getVoteQuestion
.badge.badge-state-image-only(title=getVoteQuestion)
span.badge-icon.fa.fa-thumbs-up
+ span.badge-text {{ voteCountPositive }}
span.badge-icon.fa.fa-thumbs-down
+ span.badge-text {{ voteCountNegative }}
if attachments.count
.badge
span.badge-icon.fa.fa-paperclip
diff --git a/client/components/main/editor.js b/client/components/main/editor.js
index 081c6521..0c2e3186 100755
--- a/client/components/main/editor.js
+++ b/client/components/main/editor.js
@@ -330,7 +330,7 @@ Template.viewer.events({
// the corresponding text). Clicking a link shouldn't fire these actions, stop
// we stop these event at the viewer component level.
'click a'(event, templateInstance) {
- let prevent = true;
+ const prevent = true;
const userId = event.currentTarget.dataset.userid;
if (userId) {
Popup.open('member').call({ userId }, event, templateInstance);
diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index 11e7e2dd..06593b20 100644
--- a/i18n/en.i18n.json
+++ b/i18n/en.i18n.json
@@ -152,8 +152,6 @@
"card-spent": "Spent Time",
"card-edit-attachments": "Edit attachments",
"card-edit-custom-fields": "Edit custom fields",
- "card-start-voting": "Start voting",
- "card-cancel-voting": "Delete voting and all votes",
"card-edit-labels": "Edit labels",
"card-edit-members": "Edit members",
"card-labels-title": "Change the labels for the card.",
@@ -166,11 +164,15 @@
"cardStartVotingPopup-title": "Start a vote",
"positiveVoteMembersPopup-title": "Proponents",
"negativeVoteMembersPopup-title": "Opponents",
- "allowNonBoardMembers": "Allow anonymous vote on public board",
+ "card-edit-voting": "Edit voting",
+ "editVoteEndDatePopup-title": "Change vote end date",
+ "allowNonBoardMembers": "Allow all logged in users",
"vote-question": "Voting question",
"vote-public": "Show who voted what",
"vote-for-it": "for it",
"vote-against": "against",
+ "deleteVotePopup-title": "Delete vote?",
+ "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.",
"cardDeletePopup-title": "Delete Card?",
"cardDetailsActionsPopup-title": "Card Actions",
"cardLabelsPopup-title": "Labels",
@@ -642,8 +644,6 @@
"r-when-a-member": "When a member is",
"r-when-the-member": "When the member",
"r-name": "name",
- "r-is": "is",
- "r-when-a-attach": "When an attachment",
"r-when-a-checklist": "When a checklist is",
"r-when-the-checklist": "When the checklist",
"r-completed": "Completed",
@@ -656,7 +656,6 @@
"r-top-of": "Top of",
"r-bottom-of": "Bottom of",
"r-its-list": "its list",
- "r-list": "list",
"r-archive": "Move to Archive",
"r-unarchive": "Restore from Archive",
"r-card": "card",
@@ -712,7 +711,6 @@
"r-swimlane-name": "swimlane name",
"r-board-note": "Note: leave a field empty to match every possible value. ",
"r-checklist-note": "Note: checklist's items have to be written as comma separated values.",
- "r-added-to": "added to",
"r-when-a-card-is-moved": "When a card is moved to another list",
"r-set": "Set",
"r-update": "Update",
diff --git a/models/cards.js b/models/cards.js
index 4197f7ab..b0783898 100644
--- a/models/cards.js
+++ b/models/cards.js
@@ -340,6 +340,10 @@ Cards.attachSchema(
type: Boolean,
defaultValue: false,
},
+ 'vote.allowNonBoardMembers': {
+ type: Boolean,
+ defaultValue: false,
+ },
}),
);
@@ -347,8 +351,14 @@ Cards.allow({
insert(userId, doc) {
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
},
- update(userId, doc) {
- return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
+
+ update(userId, doc, fields) {
+ // Allow board members or logged in users if only vote get's changed
+ return (
+ allowIsBoardMember(userId, Boards.findOne(doc.boardId)) ||
+ (_.isEqual(fields, ['vote', 'modifiedAt', 'dateLastActivity']) &&
+ !!userId)
+ );
},
remove(userId, doc) {
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
@@ -1048,6 +1058,29 @@ Cards.helpers({
}
},
+ getVoteEnd() {
+ if (this.isLinkedCard()) {
+ const card = Cards.findOne({ _id: this.linkedId });
+ if (card && card.vote) return card.vote.end;
+ else return null;
+ } else if (this.isLinkedBoard()) {
+ const board = Boards.findOne({ _id: this.linkedId });
+ if (board && board.vote) return board.vote.end;
+ else return null;
+ } else if (this.vote) {
+ return this.vote.end;
+ } else {
+ return null;
+ }
+ },
+ expiredVote() {
+ let end = this.getVoteEnd();
+ if (end) {
+ end = moment(end);
+ return end.isBefore(new Date());
+ }
+ return false;
+ },
voteMemberPositive() {
if (this.vote && this.vote.positive)
return Users.find({ _id: { $in: this.vote.positive } });
@@ -1153,6 +1186,26 @@ Cards.helpers({
isTemplateCard() {
return this.type === 'template-card';
},
+
+ votePublic() {
+ if (this.vote) return this.vote.public;
+ return null;
+ },
+ voteAllowNonBoardMembers() {
+ if (this.vote) return this.vote.allowNonBoardMembers;
+ return null;
+ },
+ voteCountNegative() {
+ if (this.vote && this.vote.negative) return this.vote.negative.length;
+ return null;
+ },
+ voteCountPositive() {
+ if (this.vote && this.vote.positive) return this.vote.positive.length;
+ return null;
+ },
+ voteCount() {
+ return this.voteCountPositive() + this.voteCountNegative();
+ },
});
Cards.mutations({
@@ -1476,12 +1529,13 @@ Cards.mutations({
},
};
},
- setVoteQuestion(question, publicVote) {
+ setVoteQuestion(question, publicVote, allowNonBoardMembers) {
return {
$set: {
vote: {
question,
public: publicVote,
+ allowNonBoardMembers,
positive: [],
negative: [],
},
@@ -1495,6 +1549,16 @@ Cards.mutations({
},
};
},
+ setVoteEnd(end) {
+ return {
+ $set: { 'vote.end': end },
+ };
+ },
+ unsetVoteEnd() {
+ return {
+ $unset: { 'vote.end': '' },
+ };
+ },
setVote(userId, forIt) {
switch (forIt) {
case true:
diff --git a/sandstorm.js b/sandstorm.js
index 8615e419..de386d14 100644
--- a/sandstorm.js
+++ b/sandstorm.js
@@ -22,7 +22,7 @@ const sandstormBoard = {
if (isSandstorm && Meteor.isServer) {
const fs = require('fs');
- const Capnp = Npm.require(`capnp`);
+ const Capnp = Npm.require('capnp');
const Package = Capnp.importSystem('sandstorm/package.capnp');
const Powerbox = Capnp.importSystem('sandstorm/powerbox.capnp');
const Identity = Capnp.importSystem('sandstorm/identity.capnp');