mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 11:13:22 +08:00
Migrate Discourse Polls to use vdom instead of embedded ember
This commit is contained in:
parent
846597f563
commit
f07443b488
|
@ -110,6 +110,7 @@ export default Ember.Component.extend({
|
|||
const opts = { model: this.get('model') };
|
||||
const newTree = new this._widgetClass(args, this.register, opts);
|
||||
|
||||
newTree._rerenderable = this;
|
||||
newTree._emberView = this;
|
||||
const patches = diff(this._tree || this._rootNode, newTree);
|
||||
|
||||
|
|
43
app/assets/javascripts/discourse/widgets/glue.js.es6
Normal file
43
app/assets/javascripts/discourse/widgets/glue.js.es6
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { diff, patch } from 'virtual-dom';
|
||||
import { queryRegistry } from 'discourse/widgets/widget';
|
||||
|
||||
export default class WidgetGlue {
|
||||
|
||||
constructor(name, register, attrs) {
|
||||
this._tree = null;
|
||||
this._rootNode = null;
|
||||
this.register = register;
|
||||
this.attrs = attrs;
|
||||
this._timeout = null;
|
||||
|
||||
this._widgetClass = queryRegistry(name) || this.register.lookupFactory(`widget:${name}`);
|
||||
if (!this._widgetClass) {
|
||||
console.error(`Error: Could not find widget: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
appendTo(elem) {
|
||||
this._rootNode = elem;
|
||||
this.queueRerender();
|
||||
}
|
||||
|
||||
queueRerender() {
|
||||
this._timeout = Ember.run.scheduleOnce('render', this, this.rerenderWidget);
|
||||
}
|
||||
|
||||
rerenderWidget() {
|
||||
Ember.run.cancel(this._timeout);
|
||||
const newTree = new this._widgetClass(this.attrs, this.register);
|
||||
const patches = diff(this._tree || this._rootNode, newTree);
|
||||
|
||||
newTree._rerenderable = this;
|
||||
this._rootNode = patch(this._rootNode, patches);
|
||||
this._tree = newTree;
|
||||
}
|
||||
|
||||
cleanUp() {
|
||||
Ember.run.cancel(this._timeout);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -246,10 +246,11 @@ export default class Widget {
|
|||
keyDirty(widget.key);
|
||||
}
|
||||
|
||||
const emberView = widget._emberView;
|
||||
if (emberView) {
|
||||
return emberView.queueRerender();
|
||||
const rerenderable = widget._rerenderable;
|
||||
if (rerenderable) {
|
||||
return rerenderable.queueRerender();
|
||||
}
|
||||
|
||||
widget = widget.parentWidget;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,213 +0,0 @@
|
|||
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
layoutName: 'components/discourse-poll',
|
||||
classNames: ["poll"],
|
||||
attributeBindings: ["data-poll-type", "data-poll-name", "data-poll-status", "data-poll-public"],
|
||||
|
||||
"data-poll-type": Ember.computed.alias("poll.type"),
|
||||
"data-poll-name": Ember.computed.alias("poll.name"),
|
||||
"data-poll-status": Ember.computed.alias("poll.status"),
|
||||
"data-poll-public": Ember.computed.alias("poll.public"),
|
||||
|
||||
isMultiple: Ember.computed.equal("poll.type", "multiple"),
|
||||
isNumber: Ember.computed.equal("poll.type", "number"),
|
||||
isClosed: Ember.computed.equal("poll.status", "closed"),
|
||||
isPublic: Ember.computed.equal("poll.public", "true"),
|
||||
|
||||
// shows the results when
|
||||
// - poll is closed
|
||||
// - topic is archived
|
||||
// - user wants to see the results
|
||||
showingResults: Ember.computed.or("isClosed", "post.topic.archived", "showResults"),
|
||||
|
||||
showResultsDisabled: Ember.computed.equal("poll.voters", 0),
|
||||
hideResultsDisabled: Ember.computed.or("isClosed", "post.topic.archived"),
|
||||
|
||||
@observes("post.polls")
|
||||
_updatePoll() {
|
||||
this.set("model", this.get("post.pollsObject")[this.get("model.name")]);
|
||||
},
|
||||
|
||||
@computed("model", "vote", "model.voters", "model.options", "model.status")
|
||||
poll(poll, vote) {
|
||||
if (poll) {
|
||||
const options = _.map(poll.get("options"), o => Em.Object.create(o));
|
||||
|
||||
if (vote) {
|
||||
options.forEach(o => o.set("selected", vote.indexOf(o.get("id")) >= 0));
|
||||
}
|
||||
|
||||
poll.set("options", options);
|
||||
}
|
||||
|
||||
return poll;
|
||||
},
|
||||
|
||||
@computed("poll.options.@each.selected")
|
||||
selectedOptions() {
|
||||
return _.map(this.get("poll.options").filterBy("selected"), o => o.get("id"));
|
||||
},
|
||||
|
||||
@computed("poll.min")
|
||||
min(min) {
|
||||
min = parseInt(min, 10);
|
||||
if (isNaN(min) || min < 1) { min = 1; }
|
||||
return min;
|
||||
},
|
||||
|
||||
@computed("poll.max", "poll.options.length")
|
||||
max(max, options) {
|
||||
max = parseInt(max, 10);
|
||||
if (isNaN(max) || max > options) { max = options; }
|
||||
return max;
|
||||
},
|
||||
|
||||
@computed("poll.voters")
|
||||
votersText(count) {
|
||||
return I18n.t("poll.voters", { count });
|
||||
},
|
||||
|
||||
@computed("poll.options.@each.votes")
|
||||
totalVotes() {
|
||||
return _.reduce(this.get("poll.options"), function(total, o) {
|
||||
return total + parseInt(o.get("votes"), 10);
|
||||
}, 0);
|
||||
},
|
||||
|
||||
@computed("totalVotes")
|
||||
totalVotesText(count) {
|
||||
return I18n.t("poll.total_votes", { count });
|
||||
},
|
||||
|
||||
@computed("min", "max", "poll.options.length")
|
||||
multipleHelpText(min, max, options) {
|
||||
if (max > 0) {
|
||||
if (min === max) {
|
||||
if (min > 1) {
|
||||
return I18n.t("poll.multiple.help.x_options", { count: min });
|
||||
}
|
||||
} else if (min > 1) {
|
||||
if (max < options) {
|
||||
return I18n.t("poll.multiple.help.between_min_and_max_options", { min, max });
|
||||
} else {
|
||||
return I18n.t("poll.multiple.help.at_least_min_options", { count: min });
|
||||
}
|
||||
} else if (max <= options) {
|
||||
return I18n.t("poll.multiple.help.up_to_max_options", { count: max });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@computed("isClosed", "showResults", "loading", "isMultiple", "selectedOptions.length", "min", "max")
|
||||
canCastVotes(isClosed, showResults, loading, isMultiple, selectedOptionCount, min, max) {
|
||||
if (isClosed || showResults || loading) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isMultiple) {
|
||||
return selectedOptionCount >= min && selectedOptionCount <= max;
|
||||
} else {
|
||||
return selectedOptionCount > 0;
|
||||
}
|
||||
},
|
||||
|
||||
castVotesDisabled: Em.computed.not("canCastVotes"),
|
||||
|
||||
@computed("castVotesDisabled")
|
||||
castVotesButtonClass(castVotesDisabled) {
|
||||
return `cast-votes ${castVotesDisabled ? '' : 'btn-primary'}`;
|
||||
},
|
||||
|
||||
@computed("loading", "post.user_id", "post.topic.archived")
|
||||
canToggleStatus(loading, userId, topicArchived) {
|
||||
return this.currentUser &&
|
||||
(this.currentUser.get("id") === userId || this.currentUser.get("staff")) &&
|
||||
!loading &&
|
||||
!topicArchived;
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
toggleOption(option) {
|
||||
if (this.get("isClosed")) { return; }
|
||||
if (!this.currentUser) { return this.send("showLogin"); }
|
||||
|
||||
const wasSelected = option.get("selected");
|
||||
|
||||
if (!this.get("isMultiple")) {
|
||||
this.get("poll.options").forEach(o => o.set("selected", false));
|
||||
}
|
||||
|
||||
option.toggleProperty("selected");
|
||||
|
||||
if (!this.get("isMultiple") && !wasSelected) { this.send("castVotes"); }
|
||||
},
|
||||
|
||||
castVotes() {
|
||||
if (!this.get("canCastVotes")) { return; }
|
||||
if (!this.currentUser) { return this.send("showLogin"); }
|
||||
|
||||
this.set("loading", true);
|
||||
|
||||
ajax("/polls/vote", {
|
||||
type: "PUT",
|
||||
data: {
|
||||
post_id: this.get("post.id"),
|
||||
poll_name: this.get("poll.name"),
|
||||
options: this.get("selectedOptions"),
|
||||
}
|
||||
}).then(results => {
|
||||
const poll = results.poll;
|
||||
const votes = results.vote;
|
||||
|
||||
this.setProperties({ vote: votes, showResults: true });
|
||||
this.set("model", Em.Object.create(poll));
|
||||
}).catch(() => {
|
||||
bootbox.alert(I18n.t("poll.error_while_casting_votes"));
|
||||
}).finally(() => {
|
||||
this.set("loading", false);
|
||||
});
|
||||
},
|
||||
|
||||
toggleResults() {
|
||||
this.toggleProperty("showResults");
|
||||
},
|
||||
|
||||
toggleStatus() {
|
||||
if (!this.get("canToggleStatus")) { return; }
|
||||
|
||||
const self = this,
|
||||
confirm = this.get("isClosed") ? "poll.open.confirm" : "poll.close.confirm";
|
||||
|
||||
bootbox.confirm(
|
||||
I18n.t(confirm),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
function(confirmed) {
|
||||
if (confirmed) {
|
||||
self.set("loading", true);
|
||||
|
||||
ajax("/polls/toggle_status", {
|
||||
type: "PUT",
|
||||
data: {
|
||||
post_id: self.get("post.id"),
|
||||
poll_name: self.get("poll.name"),
|
||||
status: self.get("isClosed") ? "open" : "closed",
|
||||
}
|
||||
}).then(results => {
|
||||
self.set("model", Em.Object.create(results.poll));
|
||||
}).catch(() => {
|
||||
bootbox.alert(I18n.t("poll.error_while_toggling_status"));
|
||||
}).finally(() => {
|
||||
self.set("loading", false);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { iconHTML } from 'discourse-common/helpers/fa-icon';
|
||||
|
||||
export default Em.Component.extend({
|
||||
tagName: "li",
|
||||
attributeBindings: ["data-poll-option-id"],
|
||||
|
||||
"data-poll-option-id": Em.computed.alias("option.id"),
|
||||
|
||||
@computed("option.selected", "isMultiple")
|
||||
optionIcon(selected, isMultiple) {
|
||||
if (isMultiple) {
|
||||
return iconHTML(selected ? 'check-square-o' : 'square-o');
|
||||
} else {
|
||||
return iconHTML(selected ? 'dot-circle-o' : 'circle-o');
|
||||
}
|
||||
},
|
||||
|
||||
click(e) {
|
||||
// ensure we're not clicking on a link
|
||||
if ($(e.target).closest("a").length === 0) {
|
||||
this.sendAction("toggle", this.get("option"));
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,15 +0,0 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import PollVoters from 'discourse/plugins/poll/components/poll-voters';
|
||||
|
||||
export default PollVoters.extend({
|
||||
@computed("poll.voters", "pollsVoters")
|
||||
canLoadMore(voters, pollsVoters) {
|
||||
return pollsVoters.length < voters;
|
||||
},
|
||||
|
||||
@computed("poll.options", "offset")
|
||||
voterIds(options) {
|
||||
const ids = [].concat(...(options.map(option => option.voter_ids)));
|
||||
return this._getIds(ids);
|
||||
}
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
import round from "discourse/lib/round";
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Em.Component.extend({
|
||||
@computed("poll.options.@each.{html,votes}")
|
||||
totalScore() {
|
||||
return _.reduce(this.get("poll.options"), function(total, o) {
|
||||
const value = parseInt(o.get("html"), 10),
|
||||
votes = parseInt(o.get("votes"), 10);
|
||||
return total + value * votes;
|
||||
}, 0);
|
||||
},
|
||||
|
||||
@computed("totalScore", "poll.voters")
|
||||
average() {
|
||||
const voters = this.get("poll.voters");
|
||||
return voters === 0 ? 0 : round(this.get("totalScore") / voters, -2);
|
||||
},
|
||||
|
||||
@computed("average")
|
||||
averageRating() {
|
||||
return I18n.t("poll.average_rating", { average: this.get("average") });
|
||||
},
|
||||
|
||||
});
|
|
@ -1,14 +0,0 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import PollVoters from 'discourse/plugins/poll/components/poll-voters';
|
||||
|
||||
export default PollVoters.extend({
|
||||
@computed("option.votes", "pollsVoters")
|
||||
canLoadMore(voters, pollsVoters) {
|
||||
return pollsVoters.length < voters;
|
||||
},
|
||||
|
||||
@computed("option.voter_ids", "offset")
|
||||
voterIds(ids) {
|
||||
return this._getIds(ids);
|
||||
}
|
||||
});
|
|
@ -1,40 +0,0 @@
|
|||
import evenRound from "discourse/plugins/poll/lib/even-round";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Em.Component.extend({
|
||||
tagName: "ul",
|
||||
classNames: ["results"],
|
||||
|
||||
@computed("poll.voters", "poll.type", "poll.options.[]")
|
||||
options(voters, type) {
|
||||
const options = this.get("poll.options").slice(0).sort((a, b) => {
|
||||
return b.get("votes") - a.get("votes");
|
||||
});
|
||||
|
||||
let percentages = voters === 0 ?
|
||||
Array(options.length).fill(0) :
|
||||
_.map(options, o => 100 * o.get("votes") / voters);
|
||||
|
||||
// properly round percentages
|
||||
if (type === "multiple") {
|
||||
// when the poll is multiple choices, just "round down"
|
||||
percentages = percentages.map(p => Math.floor(p));
|
||||
} else {
|
||||
// when the poll is single choice, adds up to 100%
|
||||
percentages = evenRound(percentages);
|
||||
}
|
||||
|
||||
options.forEach((option, i) => {
|
||||
const percentage = percentages[i];
|
||||
const style = new Handlebars.SafeString(`width: ${percentage}%`);
|
||||
|
||||
option.setProperties({
|
||||
percentage,
|
||||
style,
|
||||
title: I18n.t("poll.option_title", { count: option.get("votes") })
|
||||
});
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
});
|
|
@ -1,54 +0,0 @@
|
|||
import { ajax } from 'discourse/lib/ajax';
|
||||
export default Ember.Component.extend({
|
||||
layoutName: "components/poll-voters",
|
||||
tagName: 'ul',
|
||||
classNames: ["poll-voters-list"],
|
||||
isExpanded: false,
|
||||
numOfVotersToShow: 0,
|
||||
offset: 0,
|
||||
loading: false,
|
||||
pollsVoters: null,
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
this.set("pollsVoters", []);
|
||||
},
|
||||
|
||||
_fetchUsers() {
|
||||
this.set("loading", true);
|
||||
|
||||
ajax("/polls/voters.json", {
|
||||
type: "get",
|
||||
data: { user_ids: this.get("voterIds") }
|
||||
}).then(result => {
|
||||
if (this.isDestroyed) return;
|
||||
this.set("pollsVoters", this.get("pollsVoters").concat(result.users));
|
||||
this.incrementProperty("offset");
|
||||
this.set("loading", false);
|
||||
}).catch((error) => {
|
||||
Ember.logger.log(error);
|
||||
bootbox.alert(I18n.t('poll.error_while_fetching_voters'));
|
||||
});
|
||||
},
|
||||
|
||||
_getIds(ids) {
|
||||
const numOfVotersToShow = this.get("numOfVotersToShow");
|
||||
const offset = this.get("offset");
|
||||
return ids.slice(numOfVotersToShow * offset, numOfVotersToShow * (offset + 1));
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
|
||||
Ember.run.scheduleOnce('afterRender', () => {
|
||||
this.set("numOfVotersToShow", Math.round(this.$().width() / 25) * 2);
|
||||
if (this.get("voterIds").length > 0) this._fetchUsers();
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
loadMore() {
|
||||
this._fetchUsers();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,61 +0,0 @@
|
|||
<div>
|
||||
<div class="poll-container">
|
||||
{{#if showingResults}}
|
||||
{{#if isNumber}}
|
||||
{{poll-results-number isPublic=isPublic poll=poll}}
|
||||
{{else}}
|
||||
{{poll-results-standard isPublic=isPublic poll=poll}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<ul>
|
||||
{{#each poll.options as |option|}}
|
||||
{{poll-option option=option toggle="toggleOption" isMultiple=isMultiple}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="poll-info">
|
||||
<p>
|
||||
<span class="info-number">{{poll.voters}}</span>
|
||||
<span class="info-text">{{votersText}}</span>
|
||||
</p>
|
||||
{{#if isMultiple}}
|
||||
{{#if showingResults}}
|
||||
<p>
|
||||
<span class="info-number">{{totalVotes}}</span>
|
||||
<span class="info-text">{{totalVotesText}}</span>
|
||||
</p>
|
||||
{{else}}
|
||||
<p>{{{multipleHelpText}}}</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if isPublic}}
|
||||
{{#unless showingResults}}
|
||||
<p>{{i18n "poll.public.title"}}</p>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="poll-buttons">
|
||||
{{#if isMultiple}}
|
||||
{{#unless hideResultsDisabled}}
|
||||
{{d-button class=castVotesButtonClass title="poll.cast-votes.title" label="poll.cast-votes.label" disabled=castVotesDisabled action="castVotes"}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showingResults}}
|
||||
{{d-button class="toggle-results" title="poll.hide-results.title" label="poll.hide-results.label" icon="eye-slash" disabled=hideResultsDisabled action="toggleResults"}}
|
||||
{{else}}
|
||||
{{d-button class="toggle-results" title="poll.show-results.title" label="poll.show-results.label" icon="eye" disabled=showResultsDisabled action="toggleResults"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if canToggleStatus}}
|
||||
{{#if isClosed}}
|
||||
{{d-button class="toggle-status" title="poll.open.title" label="poll.open.label" icon="unlock-alt" action="toggleStatus"}}
|
||||
{{else}}
|
||||
{{d-button class="toggle-status btn-danger" title="poll.close.title" label="poll.close.label" icon="lock" action="toggleStatus"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
|
@ -1,2 +0,0 @@
|
|||
{{{optionIcon}}}
|
||||
{{{option.html}}}
|
|
@ -1,7 +0,0 @@
|
|||
<div class="poll-results-number-rating">
|
||||
{{{averageRating}}}
|
||||
</div>
|
||||
|
||||
{{#if isPublic}}
|
||||
{{poll-results-number-voters poll=poll}}
|
||||
{{/if}}
|
|
@ -1,17 +0,0 @@
|
|||
{{#each options as |option|}}
|
||||
<li>
|
||||
<div class="option">
|
||||
<p>
|
||||
<span class="percentage">{{option.percentage}}%</span>
|
||||
{{{option.html}}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="bar-back">
|
||||
<div class="bar" style={{option.style}}></div>
|
||||
</div>
|
||||
|
||||
{{#if isPublic}}
|
||||
{{poll-results-standard-voters option=option}}
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
|
@ -1,17 +0,0 @@
|
|||
<div class="poll-voters">
|
||||
{{#each pollsVoters as |user|}}
|
||||
<li>
|
||||
<a data-user-card={{unbound user.username}}>
|
||||
{{avatar user imageSize="tiny" ignoreTitle="true"}}
|
||||
</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
|
||||
<div class="poll-voters-toggle-expand">
|
||||
{{#if canLoadMore}}
|
||||
{{#conditional-loading-spinner condition=loading size="small"}}
|
||||
<a {{action "loadMore"}}>{{fa-icon "chevron-down"}}</a>
|
||||
{{/conditional-loading-spinner}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
|
@ -1,15 +1,10 @@
|
|||
import { withPluginApi } from 'discourse/lib/plugin-api';
|
||||
import { observes } from "ember-addons/ember-computed-decorators";
|
||||
|
||||
function createPollComponent(register, post, poll, vote) {
|
||||
const component = register.lookup("component:discourse-poll");
|
||||
component.setProperties({ model: poll, vote, post });
|
||||
return component;
|
||||
}
|
||||
|
||||
let _pollViews;
|
||||
import { getRegister } from 'discourse-common/lib/get-owner';
|
||||
import WidgetGlue from 'discourse/widgets/glue';
|
||||
|
||||
function initializePolls(api) {
|
||||
const register = getRegister(api);
|
||||
|
||||
const TopicController = api.container.lookupFactory('controller:topic');
|
||||
TopicController.reopen({
|
||||
|
@ -52,14 +47,8 @@ function initializePolls(api) {
|
|||
}
|
||||
});
|
||||
|
||||
function cleanUpPollViews() {
|
||||
if (_pollViews) {
|
||||
Object.keys(_pollViews).forEach(pollName => _pollViews[pollName].destroy());
|
||||
}
|
||||
_pollViews = null;
|
||||
}
|
||||
|
||||
function createPollViews($elem, helper) {
|
||||
const _glued = [];
|
||||
function attachPolls($elem, helper) {
|
||||
const $polls = $('.poll', $elem);
|
||||
if (!$polls.length) { return; }
|
||||
|
||||
|
@ -72,41 +61,33 @@ function initializePolls(api) {
|
|||
const polls = post.get("pollsObject");
|
||||
if (!polls) { return; }
|
||||
|
||||
const postPollViews = {};
|
||||
|
||||
$polls.each((idx, pollElem) => {
|
||||
const $div = $("<div>");
|
||||
const $poll = $(pollElem);
|
||||
|
||||
const pollName = $poll.data("poll-name");
|
||||
const pollId = `${pollName}-${post.id}`;
|
||||
const poll = polls[pollName];
|
||||
if (poll) {
|
||||
const isMultiple = poll.get('type') === 'multiple';
|
||||
|
||||
const pollComponent = createPollComponent(
|
||||
helper.register,
|
||||
post,
|
||||
polls[pollName],
|
||||
votes[pollName]
|
||||
);
|
||||
|
||||
// Destroy a poll view if we're replacing it
|
||||
if (_pollViews && _pollViews[pollId]) {
|
||||
_pollViews[pollId].destroy();
|
||||
const glue = new WidgetGlue('discourse-poll', register, {
|
||||
id: `${pollName}-${post.id}`,
|
||||
post,
|
||||
poll,
|
||||
vote: votes[pollName] || [],
|
||||
isMultiple,
|
||||
});
|
||||
glue.appendTo(pollElem);
|
||||
_glued.push(glue);
|
||||
}
|
||||
|
||||
$poll.replaceWith($div);
|
||||
Ember.run.scheduleOnce('afterRender', () => {
|
||||
pollComponent.renderer.appendTo(pollComponent, $div[0]);
|
||||
});
|
||||
|
||||
postPollViews[pollId] = pollComponent;
|
||||
});
|
||||
}
|
||||
|
||||
_pollViews = postPollViews;
|
||||
function cleanUpPolls() {
|
||||
_glued.forEach(g => g.cleanUp());
|
||||
}
|
||||
|
||||
api.includePostAttributes("polls", "polls_votes");
|
||||
api.decorateCooked(createPollViews, { onlyStream: true });
|
||||
api.cleanupStream(cleanUpPollViews);
|
||||
api.decorateCooked(attachPolls, { onlyStream: true });
|
||||
api.cleanupStream(cleanUpPolls);
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -3,7 +3,7 @@ function sumsUpTo100(percentages) {
|
|||
return percentages.map(p => Math.floor(p)).reduce((a, b) => a + b) === 100;
|
||||
}
|
||||
|
||||
export default (percentages) => {
|
||||
export default function(percentages) {
|
||||
var decimals = percentages.map(a => a % 1);
|
||||
const sumOfDecimals = Math.ceil(decimals.reduce((a, b) => a + b));
|
||||
// compensate error by adding 1 to n items with the greatest decimal part
|
||||
|
|
504
plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
Normal file
504
plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
Normal file
|
@ -0,0 +1,504 @@
|
|||
import { createWidget } from 'discourse/widgets/widget';
|
||||
import { h } from 'virtual-dom';
|
||||
import { iconNode } from 'discourse/helpers/fa-icon-node';
|
||||
import RawHtml from 'discourse/widgets/raw-html';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import evenRound from "discourse/plugins/poll/lib/even-round";
|
||||
import { avatarFor } from 'discourse/widgets/post';
|
||||
import round from "discourse/lib/round";
|
||||
|
||||
function optionHtml(option) {
|
||||
return new RawHtml({ html: `<span>${option.html}</span>` });
|
||||
}
|
||||
|
||||
createWidget('discourse-poll-option', {
|
||||
tagName: 'li',
|
||||
|
||||
buildAttributes(attrs) {
|
||||
return { 'data-poll-option-id': attrs.option.id };
|
||||
},
|
||||
|
||||
html(attrs) {
|
||||
const result = [];
|
||||
|
||||
const { option, vote } = attrs;
|
||||
const chosen = vote.indexOf(option.id) !== -1;
|
||||
|
||||
if (attrs.isMultiple) {
|
||||
result.push(iconNode(chosen ? 'check-square-o' : 'square-o'));
|
||||
} else {
|
||||
result.push(iconNode(chosen ? 'dot-circle-o' : 'circle-o'));
|
||||
}
|
||||
result.push(' ');
|
||||
result.push(optionHtml(option));
|
||||
return result;
|
||||
},
|
||||
|
||||
click(e) {
|
||||
if ($(e.target).closest("a").length === 0) {
|
||||
this.sendWidgetAction('toggleOption', this.attrs.option);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
createWidget('discourse-poll-load-more', {
|
||||
tagName: 'div.poll-voters-toggle-expand',
|
||||
buildKey: attrs => `${attrs.id}-load-more`,
|
||||
|
||||
defaultState() {
|
||||
return { loading: false };
|
||||
},
|
||||
|
||||
html(attrs, state) {
|
||||
return state.loading ? h('div.spinner.small') : h('a', iconNode('chevron-down'));
|
||||
},
|
||||
|
||||
click() {
|
||||
const { state } = this;
|
||||
if (state.loading) { return; }
|
||||
|
||||
state.loading = true;
|
||||
return this.sendWidgetAction('loadMore').finally(() => state.loading = false);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
createWidget('discourse-poll-voters', {
|
||||
tagName: 'ul.poll-voters-list',
|
||||
buildKey: attrs => attrs.id(),
|
||||
|
||||
defaultState() {
|
||||
return {
|
||||
loaded: 'new',
|
||||
pollVoters: [],
|
||||
offset: 0,
|
||||
canLoadMore: false
|
||||
};
|
||||
},
|
||||
|
||||
fetchVoters() {
|
||||
const { attrs, state } = this;
|
||||
|
||||
const { voterIds } = attrs;
|
||||
if (!voterIds.length) { return; }
|
||||
|
||||
const windowSize = Math.round(($('.poll-container:eq(0)').width() / 25) * 2);
|
||||
const index = state.offset * windowSize;
|
||||
const ids = voterIds.slice(index, index + windowSize);
|
||||
|
||||
state.loaded = 'loading';
|
||||
return ajax("/polls/voters.json", {
|
||||
type: "get",
|
||||
data: { user_ids: ids }
|
||||
}).then(result => {
|
||||
state.loaded = 'loaded';
|
||||
state.pollVoters = state.pollVoters.concat(result.users);
|
||||
state.canLoadMore = state.pollVoters.length < attrs.totalVotes;
|
||||
this.scheduleRerender();
|
||||
}).catch(() => {
|
||||
bootbox.alert(I18n.t('poll.error_while_fetching_voters'));
|
||||
});
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
this.state.offset += 1;
|
||||
return this.fetchVoters();
|
||||
},
|
||||
|
||||
html(attrs, state) {
|
||||
if (state.loaded === 'new') {
|
||||
this.fetchVoters();
|
||||
return;
|
||||
}
|
||||
|
||||
const contents = state.pollVoters.map(user => {
|
||||
return h('li', [avatarFor('tiny', {
|
||||
username: user.username,
|
||||
template: user.avatar_template
|
||||
}), ' ']);
|
||||
});
|
||||
|
||||
if (state.canLoadMore) {
|
||||
contents.push(this.attach('discourse-poll-load-more', { id: attrs.id() }));
|
||||
}
|
||||
|
||||
return h('div.poll-voters', contents);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
createWidget('discourse-poll-standard-results', {
|
||||
tagName: 'ul.results',
|
||||
|
||||
html(attrs) {
|
||||
const { poll } = attrs;
|
||||
const options = poll.get('options');
|
||||
if (options) {
|
||||
|
||||
const voters = poll.get('voters');
|
||||
const ordered = options.sort((a, b) => b.votes - a.votes);
|
||||
|
||||
const percentages = voters === 0 ?
|
||||
Array(ordered.length).fill(0) :
|
||||
ordered.map(o => 100 * o.votes / voters);
|
||||
|
||||
const rounded = attrs.isMultiple ? percentages.map(Math.floor) : evenRound(percentages);
|
||||
|
||||
return ordered.map((option, idx) => {
|
||||
const contents = [];
|
||||
|
||||
const per = rounded[idx].toString();
|
||||
contents.push(h('div.option',
|
||||
h('p', [ h('span.percentage', `${per}%`), optionHtml(option) ])
|
||||
));
|
||||
|
||||
contents.push(h('div.bar-back',
|
||||
h('div.bar', { attributes: { style: `width:${per}%` }})
|
||||
));
|
||||
|
||||
if (poll.get('public')) {
|
||||
contents.push(this.attach('discourse-poll-voters', {
|
||||
id: () => `poll-voters-${option.id}`,
|
||||
totalVotes: option.votes,
|
||||
voterIds: option.voter_ids
|
||||
}));
|
||||
}
|
||||
|
||||
return h('li', contents);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
createWidget('discourse-poll-number-results', {
|
||||
html(attrs) {
|
||||
const { poll } = attrs;
|
||||
|
||||
const totalScore = poll.get('options').reduce((total, o) => {
|
||||
return total + parseInt(o.html, 10) * parseInt(o.votes, 10);
|
||||
}, 0);
|
||||
|
||||
const voters = poll.voters;
|
||||
const average = voters === 0 ? 0 : round(totalScore / voters, -2);
|
||||
const averageRating = I18n.t("poll.average_rating", { average });
|
||||
const results = [h('div.poll-results-number-rating',
|
||||
new RawHtml({ html: `<span>${averageRating}</span>` }))];
|
||||
|
||||
if (poll.get('public')) {
|
||||
const options = poll.get('options');
|
||||
results.push(this.attach('discourse-poll-voters', {
|
||||
id: () => `poll-voters-${poll.get('name')}`,
|
||||
totalVotes: poll.get('voters'),
|
||||
voterIds: [].concat(...(options.map(option => option.voter_ids)))
|
||||
}));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
});
|
||||
|
||||
createWidget('discourse-poll-container', {
|
||||
tagName: 'div.poll-container',
|
||||
html(attrs) {
|
||||
const { poll } = attrs;
|
||||
|
||||
if (attrs.showResults) {
|
||||
const type = poll.get('type') === 'number' ? 'number' : 'standard';
|
||||
return this.attach(`discourse-poll-${type}-results`, attrs);
|
||||
}
|
||||
|
||||
const options = poll.get('options');
|
||||
if (options) {
|
||||
return h('ul', options.map(option => {
|
||||
return this.attach('discourse-poll-option', {
|
||||
option,
|
||||
isMultiple: attrs.isMultiple,
|
||||
vote: attrs.vote
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
createWidget('discourse-poll-info', {
|
||||
tagName: 'div.poll-info',
|
||||
|
||||
multipleHelpText(min, max, options) {
|
||||
if (max > 0) {
|
||||
if (min === max) {
|
||||
if (min > 1) {
|
||||
return I18n.t("poll.multiple.help.x_options", { count: min });
|
||||
}
|
||||
} else if (min > 1) {
|
||||
if (max < options) {
|
||||
return I18n.t("poll.multiple.help.between_min_and_max_options", { min, max });
|
||||
} else {
|
||||
return I18n.t("poll.multiple.help.at_least_min_options", { count: min });
|
||||
}
|
||||
} else if (max <= options) {
|
||||
return I18n.t("poll.multiple.help.up_to_max_options", { count: max });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
html(attrs) {
|
||||
const { poll } = attrs;
|
||||
const count = poll.get('voters');
|
||||
const result = [h('p', [
|
||||
h('span.info-number', count.toString()),
|
||||
h('span.info-text', I18n.t('poll.voters', { count }))
|
||||
])];
|
||||
|
||||
if (attrs.isMultiple) {
|
||||
if (attrs.showResults) {
|
||||
const totalVotes = poll.get('options').reduce((total, o) => {
|
||||
return total + parseInt(o.votes, 10);
|
||||
}, 0);
|
||||
|
||||
result.push(h('p', [
|
||||
h('span.info-number', totalVotes.toString()),
|
||||
h('span.info-text', I18n.t("poll.total_votes", { count: totalVotes }))
|
||||
]));
|
||||
} else {
|
||||
const help = this.multipleHelpText(attrs.min, attrs.max, poll.get('options.length'));
|
||||
if (help) {
|
||||
result.push(new RawHtml({ html: `<span>${help}</span>` }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!attrs.showResults && attrs.poll.get('public')) {
|
||||
result.push(h('p', I18n.t('poll.public.title')));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
createWidget('discourse-poll-buttons', {
|
||||
tagName: 'div.poll-buttons',
|
||||
|
||||
html(attrs) {
|
||||
const results = [];
|
||||
const { poll, post } = attrs;
|
||||
const topicArchived = post.get('topic.archived');
|
||||
const isClosed = poll.get('status') === 'closed';
|
||||
const hideResultsDisabled = isClosed || topicArchived;
|
||||
|
||||
if (attrs.isMultiple && !hideResultsDisabled) {
|
||||
const castVotesDisabled = !attrs.canCastVotes;
|
||||
results.push(this.attach('button', {
|
||||
className: `btn cast-votes ${castVotesDisabled ? '' : 'btn-primary'}`,
|
||||
label: 'poll.cast-votes.label',
|
||||
title: 'poll.cast-votes.title',
|
||||
disabled: castVotesDisabled,
|
||||
action: 'castVotes'
|
||||
}));
|
||||
results.push(' ');
|
||||
}
|
||||
|
||||
if (attrs.showResults) {
|
||||
results.push(this.attach('button', {
|
||||
className: 'btn toggle-results',
|
||||
label: 'poll.hide-results.label',
|
||||
title: 'poll.hide-results.title',
|
||||
icon: 'eye-slash',
|
||||
disabled: hideResultsDisabled,
|
||||
action: 'toggleResults'
|
||||
}));
|
||||
} else {
|
||||
results.push(this.attach('button', {
|
||||
className: 'btn toggle-results',
|
||||
label: 'poll.show-results.label',
|
||||
title: 'poll.show-results.title',
|
||||
icon: 'eye',
|
||||
disabled: poll.get('voters') === 0,
|
||||
action: 'toggleResults'
|
||||
}));
|
||||
}
|
||||
|
||||
if (this.currentUser &&
|
||||
(this.currentUser.get("id") === post.get('user_id') ||
|
||||
this.currentUser.get("staff")) &&
|
||||
!topicArchived) {
|
||||
|
||||
if (isClosed) {
|
||||
results.push(this.attach('button', {
|
||||
className: 'btn toggle-status',
|
||||
label: 'poll.open.label',
|
||||
title: 'poll.open.title',
|
||||
icon: 'unlock-alt',
|
||||
action: 'toggleStatus'
|
||||
}));
|
||||
} else {
|
||||
results.push(this.attach('button', {
|
||||
className: 'btn toggle-status btn-danger',
|
||||
label: 'poll.close.label',
|
||||
title: 'poll.close.title',
|
||||
icon: 'lock',
|
||||
action: 'toggleStatus'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return results;
|
||||
}
|
||||
});
|
||||
|
||||
export default createWidget('discourse-poll', {
|
||||
tagName: 'div.poll',
|
||||
buildKey: attrs => attrs.id,
|
||||
|
||||
buildAttributes(attrs) {
|
||||
const { poll } = attrs;
|
||||
return {
|
||||
"data-poll-type": poll.get('type'),
|
||||
"data-poll-name": poll.get('name'),
|
||||
"data-poll-status": poll.get('status'),
|
||||
"data-poll-public": poll.get('public')
|
||||
};
|
||||
},
|
||||
|
||||
defaultState(attrs) {
|
||||
const { poll, post } = attrs;
|
||||
return { loading: false,
|
||||
showResults: poll.get('isClosed') || post.get('topic.archived') };
|
||||
},
|
||||
|
||||
html(attrs, state) {
|
||||
const { showResults } = state;
|
||||
const newAttrs = jQuery.extend({}, attrs, {
|
||||
showResults,
|
||||
canCastVotes: this.canCastVotes(),
|
||||
min: this.min(),
|
||||
max: this.max()
|
||||
});
|
||||
return h('div', [
|
||||
this.attach('discourse-poll-container', newAttrs),
|
||||
this.attach('discourse-poll-info', newAttrs),
|
||||
this.attach('discourse-poll-buttons', newAttrs)
|
||||
]);
|
||||
},
|
||||
|
||||
isClosed() {
|
||||
return this.attrs.poll.get('status') === "closed";
|
||||
},
|
||||
|
||||
min() {
|
||||
let min = parseInt(this.attrs.poll.min, 10);
|
||||
if (isNaN(min) || min < 1) { min = 1; }
|
||||
return min;
|
||||
},
|
||||
|
||||
max() {
|
||||
let max = parseInt(this.attrs.poll.max, 10);
|
||||
const numOptions = this.attrs.poll.options.length;
|
||||
if (isNaN(max) || max > numOptions) { max = numOptions; }
|
||||
return max;
|
||||
},
|
||||
|
||||
canCastVotes() {
|
||||
const { state, attrs } = this;
|
||||
if (this.isClosed() || state.showResults || state.loading) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const selectedOptionCount = attrs.vote.length;
|
||||
if (attrs.isMultiple) {
|
||||
return selectedOptionCount >= this.min() && selectedOptionCount <= this.max();
|
||||
}
|
||||
return selectedOptionCount > 0;
|
||||
},
|
||||
|
||||
toggleStatus() {
|
||||
|
||||
const { state, attrs } = this;
|
||||
const { poll } = attrs;
|
||||
const isClosed = poll.get('status') === 'closed';
|
||||
|
||||
bootbox.confirm(
|
||||
I18n.t(isClosed ? "poll.open.confirm" : "poll.close.confirm"),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
confirmed => {
|
||||
if (confirmed) {
|
||||
state.loading = true;
|
||||
|
||||
const status = isClosed ? "open" : "closed";
|
||||
ajax("/polls/toggle_status", {
|
||||
type: "PUT",
|
||||
data: {
|
||||
post_id: attrs.post.get('id'),
|
||||
poll_name: poll.get('name'),
|
||||
status,
|
||||
}
|
||||
}).then(() => {
|
||||
poll.set('status', status);
|
||||
this.scheduleRerender();
|
||||
}).catch(() => {
|
||||
bootbox.alert(I18n.t("poll.error_while_toggling_status"));
|
||||
}).finally(() => {
|
||||
state.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
toggleResults() {
|
||||
this.state.showResults = !this.state.showResults;
|
||||
},
|
||||
|
||||
showLogin() {
|
||||
const appRoute = this.register.lookup('route:application');
|
||||
appRoute.send('showLogin');
|
||||
},
|
||||
|
||||
toggleOption(option) {
|
||||
if (this.isClosed()) { return; }
|
||||
if (!this.currentUser) { this.showLogin(); }
|
||||
|
||||
const { attrs } = this;
|
||||
const { vote } = attrs;
|
||||
|
||||
const chosenIdx = vote.indexOf(option.id);
|
||||
if (!attrs.isMultiple) {
|
||||
vote.length = 0;
|
||||
}
|
||||
|
||||
if (chosenIdx !== -1) {
|
||||
vote.splice(chosenIdx, 1);
|
||||
} else {
|
||||
vote.push(option.id);
|
||||
}
|
||||
|
||||
if (!attrs.isMultiple) {
|
||||
return this.castVotes();
|
||||
}
|
||||
},
|
||||
|
||||
castVotes() {
|
||||
if (!this.canCastVotes()) { return; }
|
||||
if (!this.currentUser) { return this.showLogin(); }
|
||||
|
||||
const { attrs, state } = this;
|
||||
|
||||
state.loading = true;
|
||||
|
||||
return ajax("/polls/vote", {
|
||||
type: "PUT",
|
||||
data: {
|
||||
post_id: attrs.post.id,
|
||||
poll_name: attrs.poll.name,
|
||||
options: attrs.vote
|
||||
}
|
||||
}).then(() => {
|
||||
state.showResults = true;
|
||||
}).catch(() => {
|
||||
bootbox.alert(I18n.t("poll.error_while_casting_votes"));
|
||||
}).finally(() => {
|
||||
state.loading = false;
|
||||
});
|
||||
}
|
||||
});
|
|
@ -109,7 +109,6 @@ div.poll {
|
|||
height: 10px;
|
||||
background: $primary;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&[data-poll-type="number"] {
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import componentTest from 'helpers/component-test';
|
||||
moduleForComponent('poll-option', { integration: true });
|
||||
|
||||
componentTest('test poll option', {
|
||||
template: '{{poll-option option=option isMultiple=isMultiple}}',
|
||||
|
||||
setup(store) {
|
||||
this.set('option', Em.Object.create({ id: 1, selected: false }));
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.ok(this.$('li .fa-circle-o:eq(0)').length === 1);
|
||||
|
||||
this.set('option.selected', true);
|
||||
|
||||
assert.ok(this.$('li .fa-dot-circle-o:eq(0)').length === 1);
|
||||
|
||||
this.set('isMultiple', true);
|
||||
|
||||
assert.ok(this.$('li .fa-check-square-o:eq(0)').length === 1);
|
||||
|
||||
this.set('option.selected', false);
|
||||
|
||||
assert.ok(this.$('li .fa-square-o:eq(0)').length === 1);
|
||||
}
|
||||
});
|
|
@ -1,58 +0,0 @@
|
|||
import componentTest from 'helpers/component-test';
|
||||
moduleForComponent('poll-results-standard', { integration: true });
|
||||
|
||||
componentTest('options in descending order', {
|
||||
template: '{{poll-results-standard poll=poll}}',
|
||||
|
||||
setup(store) {
|
||||
this.set('poll', {
|
||||
options: [Em.Object.create({ votes: 5 }), Em.Object.create({ votes: 4 })],
|
||||
voters: 9
|
||||
});
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.equal(this.$('.option .percentage:eq(0)').text(), '56%');
|
||||
assert.equal(this.$('.option .percentage:eq(1)').text(), '44%');
|
||||
}
|
||||
});
|
||||
|
||||
componentTest('options in ascending order', {
|
||||
template: '{{poll-results-standard poll=poll sortResults=sortResults}}',
|
||||
|
||||
setup() {
|
||||
this.set('poll', {
|
||||
options: [Em.Object.create({ votes: 4 }), Em.Object.create({ votes: 5 })],
|
||||
voters: 9
|
||||
});
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.equal(this.$('.option .percentage:eq(0)').text(), '56%');
|
||||
assert.equal(this.$('.option .percentage:eq(1)').text(), '44%');
|
||||
}
|
||||
});
|
||||
|
||||
componentTest('multiple options in descending order', {
|
||||
template: '{{poll-results-standard poll=poll}}',
|
||||
|
||||
setup(store) {
|
||||
this.set('poll', {
|
||||
type: 'multiple',
|
||||
options: [
|
||||
Em.Object.create({ votes: 5}),
|
||||
Em.Object.create({ votes: 2}),
|
||||
Em.Object.create({ votes: 4}),
|
||||
Em.Object.create({ votes: 1})
|
||||
],
|
||||
voters: 12
|
||||
});
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.equal(this.$('.option .percentage:eq(0)').text(), '41%');
|
||||
assert.equal(this.$('.option .percentage:eq(1)').text(), '33%');
|
||||
assert.equal(this.$('.option .percentage:eq(2)').text(), '16%');
|
||||
assert.equal(this.$('.option .percentage:eq(3)').text(), '8%');
|
||||
}
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
import { moduleForWidget, widgetTest } from 'helpers/widget-test';
|
||||
moduleForWidget('discourse-poll-option');
|
||||
|
||||
const template = `{{mount-widget
|
||||
widget="discourse-poll-option"
|
||||
args=(hash option=option isMultiple=isMultiple vote=vote)}}`;
|
||||
|
||||
widgetTest('single, not selected', {
|
||||
template,
|
||||
|
||||
setup() {
|
||||
this.set('option', { id: 'opt-id' });
|
||||
this.set('vote', []);
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.ok(find('li .fa-circle-o:eq(0)').length === 1);
|
||||
}
|
||||
});
|
||||
|
||||
widgetTest('single, selected', {
|
||||
template,
|
||||
|
||||
setup() {
|
||||
this.set('option', { id: 'opt-id' });
|
||||
this.set('vote', ['opt-id']);
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.ok(find('li .fa-dot-circle-o:eq(0)').length === 1);
|
||||
}
|
||||
});
|
||||
|
||||
widgetTest('multi, not selected', {
|
||||
template,
|
||||
|
||||
setup() {
|
||||
this.setProperties({
|
||||
option: { id: 'opt-id' },
|
||||
isMultiple: true,
|
||||
vote: []
|
||||
});
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.ok(find('li .fa-square-o:eq(0)').length === 1);
|
||||
}
|
||||
});
|
||||
|
||||
widgetTest('multi, selected', {
|
||||
template,
|
||||
|
||||
setup() {
|
||||
this.setProperties({
|
||||
option: { id: 'opt-id' },
|
||||
isMultiple: true,
|
||||
vote: ['opt-id']
|
||||
});
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.ok(find('li .fa-check-square-o:eq(0)').length === 1);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,63 @@
|
|||
import { moduleForWidget, widgetTest } from 'helpers/widget-test';
|
||||
moduleForWidget('discourse-poll-standard-results');
|
||||
|
||||
const template = `{{mount-widget
|
||||
widget="discourse-poll-standard-results"
|
||||
args=(hash poll=poll isMultiple=isMultiple)}}`;
|
||||
|
||||
widgetTest('options in descending order', {
|
||||
template,
|
||||
|
||||
setup() {
|
||||
this.set('poll', Ember.Object.create({
|
||||
options: [{ votes: 5 }, { votes: 4 }],
|
||||
voters: 9
|
||||
}));
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.equal(this.$('.option .percentage:eq(0)').text(), '56%');
|
||||
assert.equal(this.$('.option .percentage:eq(1)').text(), '44%');
|
||||
}
|
||||
});
|
||||
|
||||
widgetTest('options in ascending order', {
|
||||
template,
|
||||
|
||||
setup() {
|
||||
this.set('poll', Ember.Object.create({
|
||||
options: [{ votes: 4 }, { votes: 5 }],
|
||||
voters: 9
|
||||
}));
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.equal(this.$('.option .percentage:eq(0)').text(), '56%');
|
||||
assert.equal(this.$('.option .percentage:eq(1)').text(), '44%');
|
||||
}
|
||||
});
|
||||
|
||||
widgetTest('multiple options in descending order', {
|
||||
template,
|
||||
|
||||
setup() {
|
||||
this.set('isMultiple', true);
|
||||
this.set('poll', Ember.Object.create({
|
||||
type: 'multiple',
|
||||
options: [
|
||||
{ votes: 5 },
|
||||
{ votes: 2 },
|
||||
{ votes: 4 },
|
||||
{ votes: 1 }
|
||||
],
|
||||
voters: 12
|
||||
}));
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.equal(this.$('.option .percentage:eq(0)').text(), '41%');
|
||||
assert.equal(this.$('.option .percentage:eq(1)').text(), '33%');
|
||||
assert.equal(this.$('.option .percentage:eq(2)').text(), '16%');
|
||||
assert.equal(this.$('.option .percentage:eq(3)').text(), '8%');
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue
Block a user