mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 22:43:33 +08:00
FEATURE: automatically close a poll on a given date and time
This commit is contained in:
@ -426,21 +426,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-timestamp {
|
.change-timestamp,
|
||||||
|
.poll-ui-builder {
|
||||||
|
|
||||||
.date-picker {
|
.date-picker {
|
||||||
width: 10em;
|
width: 9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#date-container {
|
#date-container {
|
||||||
.pika-single {
|
.pika-single {
|
||||||
position: relative !important; // overriding another important
|
position: relative !important; // overriding another important
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=time] {
|
input[type=time] {
|
||||||
width: 6em;
|
width: 6em;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
|
@ -13,13 +13,11 @@ export default Ember.Controller.extend({
|
|||||||
|
|
||||||
@computed("regularPollType", "numberPollType", "multiplePollType")
|
@computed("regularPollType", "numberPollType", "multiplePollType")
|
||||||
pollTypes(regularPollType, numberPollType, multiplePollType) {
|
pollTypes(regularPollType, numberPollType, multiplePollType) {
|
||||||
let types = [];
|
return [
|
||||||
|
{ name: I18n.t("poll.ui_builder.poll_type.regular"), value: regularPollType },
|
||||||
types.push({ name: I18n.t("poll.ui_builder.poll_type.regular"), value: regularPollType });
|
{ name: I18n.t("poll.ui_builder.poll_type.number"), value: numberPollType },
|
||||||
types.push({ name: I18n.t("poll.ui_builder.poll_type.number"), value: numberPollType });
|
{ name: I18n.t("poll.ui_builder.poll_type.multiple"), value: multiplePollType },
|
||||||
types.push({ name: I18n.t("poll.ui_builder.poll_type.multiple"), value: multiplePollType });
|
];
|
||||||
|
|
||||||
return types;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("pollType", "regularPollType")
|
@computed("pollType", "regularPollType")
|
||||||
@ -101,8 +99,8 @@ export default Ember.Controller.extend({
|
|||||||
return this._comboboxOptions(1, (parseInt(pollMax) || 1) + 1);
|
return this._comboboxOptions(1, (parseInt(pollMax) || 1) + 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("isNumber", "showMinMax", "pollType", "publicPoll", "pollOptions", "pollMin", "pollMax", "pollStep")
|
@computed("isNumber", "showMinMax", "pollType", "publicPoll", "pollOptions", "pollMin", "pollMax", "pollStep", "autoClose", "date", "time")
|
||||||
pollOutput(isNumber, showMinMax, pollType, publicPoll, pollOptions, pollMin, pollMax, pollStep) {
|
pollOutput(isNumber, showMinMax, pollType, publicPoll, pollOptions, pollMin, pollMax, pollStep, autoClose, date, time) {
|
||||||
let pollHeader = '[poll';
|
let pollHeader = '[poll';
|
||||||
let output = '';
|
let output = '';
|
||||||
|
|
||||||
@ -113,15 +111,15 @@ export default Ember.Controller.extend({
|
|||||||
};
|
};
|
||||||
|
|
||||||
let step = pollStep;
|
let step = pollStep;
|
||||||
if (step < 1) {
|
if (step < 1) { step = 1; }
|
||||||
step = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pollType) pollHeader += ` type=${pollType}`;
|
if (pollType) pollHeader += ` type=${pollType}`;
|
||||||
if (pollMin && showMinMax) pollHeader += ` min=${pollMin}`;
|
if (pollMin && showMinMax) pollHeader += ` min=${pollMin}`;
|
||||||
if (pollMax) pollHeader += ` max=${pollMax}`;
|
if (pollMax) pollHeader += ` max=${pollMax}`;
|
||||||
if (isNumber) pollHeader += ` step=${step}`;
|
if (isNumber) pollHeader += ` step=${step}`;
|
||||||
if (publicPoll) pollHeader += ' public=true';
|
if (publicPoll) pollHeader += ` public=true`;
|
||||||
|
if (autoClose) pollHeader += ` close=${moment(date + " " + time, "YYYY-MM-DD HH:mm").toISOString()}`;
|
||||||
|
|
||||||
pollHeader += ']';
|
pollHeader += ']';
|
||||||
output += `${pollHeader}\n`;
|
output += `${pollHeader}\n`;
|
||||||
|
|
||||||
@ -186,7 +184,10 @@ export default Ember.Controller.extend({
|
|||||||
pollOptions: '',
|
pollOptions: '',
|
||||||
pollMin: 1,
|
pollMin: 1,
|
||||||
pollMax: null,
|
pollMax: null,
|
||||||
pollStep: 1
|
pollStep: 1,
|
||||||
|
autoClose: false,
|
||||||
|
date: moment().add(1, "day").format("YYYY-DD-MM"),
|
||||||
|
time: moment().add(1, "hour").format("HH:mm"),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -40,13 +40,6 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<div class="input-group">
|
|
||||||
<label>
|
|
||||||
{{input type='checkbox' checked=publicPoll}}
|
|
||||||
{{i18n "poll.ui_builder.poll_public.label"}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#unless isNumber}}
|
{{#unless isNumber}}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label>{{i18n 'poll.ui_builder.poll_options.label'}}</label>
|
<label>{{i18n 'poll.ui_builder.poll_options.label'}}</label>
|
||||||
@ -54,6 +47,28 @@
|
|||||||
{{textarea value=pollOptions}}
|
{{textarea value=pollOptions}}
|
||||||
</div>
|
</div>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<label>
|
||||||
|
{{input type='checkbox' checked=publicPoll}}
|
||||||
|
{{i18n "poll.ui_builder.poll_public.label"}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<label>
|
||||||
|
{{input type="checkbox" checked=autoClose}}
|
||||||
|
{{i18n "poll.ui_builder.automatic_close.label"}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if autoClose}}
|
||||||
|
<div class="input-group">
|
||||||
|
{{date-picker-future value=date containerId="date-container"}}
|
||||||
|
{{input type="time" value=time}}
|
||||||
|
<div id="date-container"></div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</form>
|
</form>
|
||||||
{{/d-modal-body}}
|
{{/d-modal-body}}
|
||||||
|
|
||||||
|
@ -22,6 +22,13 @@ function initializePolls(api) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let _glued = [];
|
||||||
|
let _interval = null;
|
||||||
|
|
||||||
|
function rerender() {
|
||||||
|
_glued.forEach(g => g.queueRerender());
|
||||||
|
}
|
||||||
|
|
||||||
api.modifyClass('model:post', {
|
api.modifyClass('model:post', {
|
||||||
_polls: null,
|
_polls: null,
|
||||||
pollsObject: null,
|
pollsObject: null,
|
||||||
@ -41,12 +48,12 @@ function initializePolls(api) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.set("pollsObject", this._polls);
|
this.set("pollsObject", this._polls);
|
||||||
_glued.forEach(g => g.queueRerender());
|
rerender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const _glued = [];
|
|
||||||
function attachPolls($elem, helper) {
|
function attachPolls($elem, helper) {
|
||||||
const $polls = $('.poll', $elem);
|
const $polls = $('.poll', $elem);
|
||||||
if (!$polls.length) { return; }
|
if (!$polls.length) { return; }
|
||||||
@ -60,6 +67,8 @@ function initializePolls(api) {
|
|||||||
const polls = post.get("pollsObject");
|
const polls = post.get("pollsObject");
|
||||||
if (!polls) { return; }
|
if (!polls) { return; }
|
||||||
|
|
||||||
|
_interval = _interval || setInterval(rerender, 30000);
|
||||||
|
|
||||||
$polls.each((idx, pollElem) => {
|
$polls.each((idx, pollElem) => {
|
||||||
const $poll = $(pollElem);
|
const $poll = $(pollElem);
|
||||||
const pollName = $poll.data("poll-name");
|
const pollName = $poll.data("poll-name");
|
||||||
@ -81,7 +90,13 @@ function initializePolls(api) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanUpPolls() {
|
function cleanUpPolls() {
|
||||||
|
if (_interval) {
|
||||||
|
clearInterval(_interval);
|
||||||
|
_interval = null;
|
||||||
|
}
|
||||||
|
|
||||||
_glued.forEach(g => g.cleanUp());
|
_glued.forEach(g => g.cleanUp());
|
||||||
|
_glued = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
api.includePostAttributes("polls", "polls_votes");
|
api.includePostAttributes("polls", "polls_votes");
|
||||||
|
@ -2,35 +2,7 @@
|
|||||||
|
|
||||||
const DATA_PREFIX = "data-poll-";
|
const DATA_PREFIX = "data-poll-";
|
||||||
const DEFAULT_POLL_NAME = "poll";
|
const DEFAULT_POLL_NAME = "poll";
|
||||||
const WHITELISTED_ATTRIBUTES = ["type", "name", "min", "max", "step", "order", "status", "public"];
|
const WHITELISTED_ATTRIBUTES = ["type", "name", "min", "max", "step", "order", "status", "public", "close"];
|
||||||
|
|
||||||
function getHelpText(count, min, max) {
|
|
||||||
|
|
||||||
// default values
|
|
||||||
if (isNaN(min) || min < 1) { min = 1; }
|
|
||||||
if (isNaN(max) || max > count) { max = count; }
|
|
||||||
|
|
||||||
// add some help text
|
|
||||||
let help;
|
|
||||||
|
|
||||||
if (max > 0) {
|
|
||||||
if (min === max) {
|
|
||||||
if (min > 1) {
|
|
||||||
help = I18n.t("poll.multiple.help.x_options", { count: min });
|
|
||||||
}
|
|
||||||
} else if (min > 1) {
|
|
||||||
if (max < count) {
|
|
||||||
help = I18n.t("poll.multiple.help.between_min_and_max_options", { min: min, max: max });
|
|
||||||
} else {
|
|
||||||
help = I18n.t("poll.multiple.help.at_least_min_options", { count: min });
|
|
||||||
}
|
|
||||||
} else if (max <= count) {
|
|
||||||
help = I18n.t("poll.multiple.help.up_to_max_options", { count: max });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return help;
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceToken(tokens, target, list) {
|
function replaceToken(tokens, target, list) {
|
||||||
let pos = tokens.indexOf(target);
|
let pos = tokens.indexOf(target);
|
||||||
@ -50,7 +22,6 @@ function replaceToken(tokens, target, list) {
|
|||||||
|
|
||||||
// analyzes the block to that we have poll options
|
// analyzes the block to that we have poll options
|
||||||
function getListItems(tokens, startToken) {
|
function getListItems(tokens, startToken) {
|
||||||
|
|
||||||
let i = tokens.length-1;
|
let i = tokens.length-1;
|
||||||
let listItems = [];
|
let listItems = [];
|
||||||
let buffer = [];
|
let buffer = [];
|
||||||
@ -217,63 +188,13 @@ const rule = {
|
|||||||
|
|
||||||
token = state.push('span_open', 'span', 1);
|
token = state.push('span_open', 'span', 1);
|
||||||
token.block = false;
|
token.block = false;
|
||||||
token.attrs = [['class', 'info-text']];
|
token.attrs = [['class', 'info-label']];
|
||||||
token = state.push('text', '', 0);
|
token = state.push('text', '', 0);
|
||||||
token.content = I18n.t("poll.voters", { count: 0 });
|
token.content = I18n.t("poll.voters", { count: 0 });
|
||||||
state.push('span_close', 'span', -1);
|
state.push('span_close', 'span', -1);
|
||||||
|
|
||||||
state.push('paragraph_close', 'p', -1);
|
state.push('paragraph_close', 'p', -1);
|
||||||
|
|
||||||
// multiple help text
|
|
||||||
if (attributes[DATA_PREFIX + "type"] === "multiple") {
|
|
||||||
let help = getHelpText(items.length, min, max);
|
|
||||||
if (help) {
|
|
||||||
state.push('paragraph_open', 'p', 1);
|
|
||||||
token = state.push('html_inline', '', 0);
|
|
||||||
token.content = help;
|
|
||||||
state.push('paragraph_close', 'p', -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attributes[DATA_PREFIX + 'public'] === 'true') {
|
|
||||||
state.push('paragraph_open', 'p', 1);
|
|
||||||
token = state.push('text', '', 0);
|
|
||||||
token.content = I18n.t('poll.public.title');
|
|
||||||
state.push('paragraph_close', 'p', -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.push('poll_close', 'div', -1);
|
|
||||||
state.push('poll_close', 'div', -1);
|
|
||||||
|
|
||||||
token = state.push('poll_open', 'div', 1);
|
|
||||||
token.attrs = [['class', 'poll-buttons']];
|
|
||||||
|
|
||||||
if (attributes[DATA_PREFIX + 'type'] === 'multiple') {
|
|
||||||
token = state.push('link_open', 'a', 1);
|
|
||||||
token.block = false;
|
|
||||||
token.attrs = [
|
|
||||||
['class', 'button cast-votes'],
|
|
||||||
['title', I18n.t('poll.cast-votes.title')]
|
|
||||||
];
|
|
||||||
|
|
||||||
token = state.push('text', '', 0);
|
|
||||||
token.content = I18n.t('poll.cast-votes.label');
|
|
||||||
|
|
||||||
state.push('link_close', 'a', -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
token = state.push('link_open', 'a', 1);
|
|
||||||
token.block = false;
|
|
||||||
token.attrs = [
|
|
||||||
['class', 'button toggle-results'],
|
|
||||||
['title', I18n.t('poll.show-results.title')]
|
|
||||||
];
|
|
||||||
|
|
||||||
token = state.push('text', '', 0);
|
|
||||||
token.content = I18n.t("poll.show-results.label");
|
|
||||||
|
|
||||||
state.push('link_close', 'a', -1);
|
|
||||||
|
|
||||||
state.push('poll_close', 'div', -1);
|
state.push('poll_close', 'div', -1);
|
||||||
state.push('poll_close', 'div', -1);
|
state.push('poll_close', 'div', -1);
|
||||||
}
|
}
|
||||||
@ -299,6 +220,7 @@ export function setup(helper) {
|
|||||||
'div[data-*]',
|
'div[data-*]',
|
||||||
'span.info-number',
|
'span.info-number',
|
||||||
'span.info-text',
|
'span.info-text',
|
||||||
|
'span.info-label',
|
||||||
'a.button.cast-votes',
|
'a.button.cast-votes',
|
||||||
'a.button.toggle-results',
|
'a.button.toggle-results',
|
||||||
'li[data-*]'
|
'li[data-*]'
|
||||||
|
@ -34,7 +34,6 @@ createWidget('discourse-poll-option', {
|
|||||||
|
|
||||||
html(attrs) {
|
html(attrs) {
|
||||||
const result = [];
|
const result = [];
|
||||||
|
|
||||||
const { option, vote } = attrs;
|
const { option, vote } = attrs;
|
||||||
const chosen = vote.indexOf(option.id) !== -1;
|
const chosen = vote.indexOf(option.id) !== -1;
|
||||||
|
|
||||||
@ -45,6 +44,7 @@ createWidget('discourse-poll-option', {
|
|||||||
}
|
}
|
||||||
result.push(' ');
|
result.push(' ');
|
||||||
result.push(optionHtml(option));
|
result.push(optionHtml(option));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -235,7 +235,6 @@ createWidget('discourse-poll-number-results', {
|
|||||||
const { attrs, state } = this;
|
const { attrs, state } = this;
|
||||||
|
|
||||||
if (state.loaded === 'new') {
|
if (state.loaded === 'new') {
|
||||||
|
|
||||||
fetchVoters({
|
fetchVoters({
|
||||||
post_id: attrs.post.id,
|
post_id: attrs.post.id,
|
||||||
poll_name: attrs.poll.get('name')
|
poll_name: attrs.poll.get('name')
|
||||||
@ -258,8 +257,7 @@ createWidget('discourse-poll-number-results', {
|
|||||||
const voters = poll.voters;
|
const voters = poll.voters;
|
||||||
const average = voters === 0 ? 0 : round(totalScore / voters, -2);
|
const average = voters === 0 ? 0 : round(totalScore / voters, -2);
|
||||||
const averageRating = I18n.t("poll.average_rating", { average });
|
const averageRating = I18n.t("poll.average_rating", { average });
|
||||||
const results = [h('div.poll-results-number-rating',
|
const results = [h('div.poll-results-number-rating', new RawHtml({ html: `<span>${averageRating}</span>` }))];
|
||||||
new RawHtml({ html: `<span>${averageRating}</span>` }))];
|
|
||||||
|
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
this.fetchVoters();
|
this.fetchVoters();
|
||||||
@ -283,7 +281,7 @@ createWidget('discourse-poll-container', {
|
|||||||
html(attrs) {
|
html(attrs) {
|
||||||
const { poll } = attrs;
|
const { poll } = attrs;
|
||||||
|
|
||||||
if (attrs.showResults) {
|
if (attrs.showResults || attrs.isClosed) {
|
||||||
const type = poll.get('type') === 'number' ? 'number' : 'standard';
|
const type = poll.get('type') === 'number' ? 'number' : 'standard';
|
||||||
return this.attach(`discourse-poll-${type}-results`, attrs);
|
return this.attach(`discourse-poll-${type}-results`, attrs);
|
||||||
}
|
}
|
||||||
@ -327,29 +325,37 @@ createWidget('discourse-poll-info', {
|
|||||||
const count = poll.get('voters');
|
const count = poll.get('voters');
|
||||||
const result = [h('p', [
|
const result = [h('p', [
|
||||||
h('span.info-number', count.toString()),
|
h('span.info-number', count.toString()),
|
||||||
h('span.info-text', I18n.t('poll.voters', { count }))
|
h('span.info-label', I18n.t('poll.voters', { count }))
|
||||||
])];
|
])];
|
||||||
|
|
||||||
if (attrs.isMultiple) {
|
if (attrs.isMultiple) {
|
||||||
if (attrs.showResults) {
|
if (attrs.showResults || attrs.isClosed) {
|
||||||
const totalVotes = poll.get('options').reduce((total, o) => {
|
const totalVotes = poll.get('options').reduce((total, o) => {
|
||||||
return total + parseInt(o.votes, 10);
|
return total + parseInt(o.votes, 10);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
result.push(h('p', [
|
result.push(h('p', [
|
||||||
h('span.info-number', totalVotes.toString()),
|
h('span.info-number', totalVotes.toString()),
|
||||||
h('span.info-text', I18n.t("poll.total_votes", { count: totalVotes }))
|
h('span.info-label', I18n.t("poll.total_votes", { count: totalVotes }))
|
||||||
]));
|
]));
|
||||||
} else {
|
} else {
|
||||||
const help = this.multipleHelpText(attrs.min, attrs.max, poll.get('options.length'));
|
const help = this.multipleHelpText(attrs.min, attrs.max, poll.get('options.length'));
|
||||||
if (help) {
|
if (help) {
|
||||||
result.push(new RawHtml({ html: `<span>${help}</span>` }));
|
result.push(new RawHtml({ html: `<span class="info-text">${help}</span>` }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!attrs.showResults && attrs.poll.get('public')) {
|
if (!attrs.isClosed) {
|
||||||
result.push(h('p', I18n.t('poll.public.title')));
|
if (!attrs.showResults && poll.get('public')) {
|
||||||
|
result.push(h('span.info-text', I18n.t('poll.public.title')));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poll.close) {
|
||||||
|
const closeDate = moment.utc(poll.close);
|
||||||
|
const timeLeft = moment().to(closeDate.local(), true);
|
||||||
|
result.push(new RawHtml({ html: `<span class="info-text" title="${closeDate.format("LLL")}">${I18n.t("poll.automatic_close.closes_in", { timeLeft })}</span>` }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -363,8 +369,8 @@ createWidget('discourse-poll-buttons', {
|
|||||||
const results = [];
|
const results = [];
|
||||||
const { poll, post } = attrs;
|
const { poll, post } = attrs;
|
||||||
const topicArchived = post.get('topic.archived');
|
const topicArchived = post.get('topic.archived');
|
||||||
const isClosed = poll.get('status') === 'closed';
|
const closed = attrs.isClosed;
|
||||||
const hideResultsDisabled = isClosed || topicArchived;
|
const hideResultsDisabled = closed || topicArchived;
|
||||||
|
|
||||||
if (attrs.isMultiple && !hideResultsDisabled) {
|
if (attrs.isMultiple && !hideResultsDisabled) {
|
||||||
const castVotesDisabled = !attrs.canCastVotes;
|
const castVotesDisabled = !attrs.canCastVotes;
|
||||||
@ -378,7 +384,7 @@ createWidget('discourse-poll-buttons', {
|
|||||||
results.push(' ');
|
results.push(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attrs.showResults) {
|
if (attrs.showResults || hideResultsDisabled) {
|
||||||
results.push(this.attach('button', {
|
results.push(this.attach('button', {
|
||||||
className: 'btn toggle-results',
|
className: 'btn toggle-results',
|
||||||
label: 'poll.hide-results.label',
|
label: 'poll.hide-results.label',
|
||||||
@ -403,7 +409,8 @@ createWidget('discourse-poll-buttons', {
|
|||||||
this.currentUser.get("staff")) &&
|
this.currentUser.get("staff")) &&
|
||||||
!topicArchived) {
|
!topicArchived) {
|
||||||
|
|
||||||
if (isClosed) {
|
if (closed) {
|
||||||
|
if (!attrs.isAutomaticallyClosed) {
|
||||||
results.push(this.attach('button', {
|
results.push(this.attach('button', {
|
||||||
className: 'btn toggle-status',
|
className: 'btn toggle-status',
|
||||||
label: 'poll.open.label',
|
label: 'poll.open.label',
|
||||||
@ -411,6 +418,7 @@ createWidget('discourse-poll-buttons', {
|
|||||||
icon: 'unlock-alt',
|
icon: 'unlock-alt',
|
||||||
action: 'toggleStatus'
|
action: 'toggleStatus'
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
results.push(this.attach('button', {
|
results.push(this.attach('button', {
|
||||||
className: 'btn toggle-status btn-danger',
|
className: 'btn toggle-status btn-danger',
|
||||||
@ -422,7 +430,6 @@ createWidget('discourse-poll-buttons', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -437,14 +444,14 @@ export default createWidget('discourse-poll', {
|
|||||||
"data-poll-type": poll.get('type'),
|
"data-poll-type": poll.get('type'),
|
||||||
"data-poll-name": poll.get('name'),
|
"data-poll-name": poll.get('name'),
|
||||||
"data-poll-status": poll.get('status'),
|
"data-poll-status": poll.get('status'),
|
||||||
"data-poll-public": poll.get('public')
|
"data-poll-public": poll.get('public'),
|
||||||
|
"data-poll-close": poll.get('close'),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
defaultState(attrs) {
|
defaultState(attrs) {
|
||||||
const { poll, post } = attrs;
|
const showResults = this.isClosed() || attrs.post.get('topic.archived');
|
||||||
return { loading: false,
|
return { loading: false, showResults };
|
||||||
showResults: poll.get('isClosed') || post.get('topic.archived') };
|
|
||||||
},
|
},
|
||||||
|
|
||||||
html(attrs, state) {
|
html(attrs, state) {
|
||||||
@ -452,9 +459,12 @@ export default createWidget('discourse-poll', {
|
|||||||
const newAttrs = jQuery.extend({}, attrs, {
|
const newAttrs = jQuery.extend({}, attrs, {
|
||||||
showResults,
|
showResults,
|
||||||
canCastVotes: this.canCastVotes(),
|
canCastVotes: this.canCastVotes(),
|
||||||
|
isClosed: this.isClosed(),
|
||||||
|
isAutomaticallyClosed: this.isAutomaticallyClosed(),
|
||||||
min: this.min(),
|
min: this.min(),
|
||||||
max: this.max()
|
max: this.max()
|
||||||
});
|
});
|
||||||
|
|
||||||
return h('div', [
|
return h('div', [
|
||||||
this.attach('discourse-poll-container', newAttrs),
|
this.attach('discourse-poll-container', newAttrs),
|
||||||
this.attach('discourse-poll-info', newAttrs),
|
this.attach('discourse-poll-info', newAttrs),
|
||||||
@ -462,10 +472,6 @@ export default createWidget('discourse-poll', {
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
isClosed() {
|
|
||||||
return this.attrs.poll.get('status') === "closed";
|
|
||||||
},
|
|
||||||
|
|
||||||
min() {
|
min() {
|
||||||
let min = parseInt(this.attrs.poll.min, 10);
|
let min = parseInt(this.attrs.poll.min, 10);
|
||||||
if (isNaN(min) || min < 1) { min = 1; }
|
if (isNaN(min) || min < 1) { min = 1; }
|
||||||
@ -479,37 +485,51 @@ export default createWidget('discourse-poll', {
|
|||||||
return max;
|
return max;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isAutomaticallyClosed() {
|
||||||
|
const { poll } = this.attrs;
|
||||||
|
return poll.get("close") && moment.utc(poll.get("close")) <= moment();
|
||||||
|
},
|
||||||
|
|
||||||
|
isClosed() {
|
||||||
|
const { poll } = this.attrs;
|
||||||
|
return poll.get("status") === "closed" || this.isAutomaticallyClosed();
|
||||||
|
},
|
||||||
|
|
||||||
canCastVotes() {
|
canCastVotes() {
|
||||||
const { state, attrs } = this;
|
const { state, attrs } = this;
|
||||||
|
|
||||||
if (this.isClosed() || state.showResults || state.loading) {
|
if (this.isClosed() || state.showResults || state.loading) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedOptionCount = attrs.vote.length;
|
const selectedOptionCount = attrs.vote.length;
|
||||||
|
|
||||||
if (attrs.isMultiple) {
|
if (attrs.isMultiple) {
|
||||||
return selectedOptionCount >= this.min() && selectedOptionCount <= this.max();
|
return selectedOptionCount >= this.min() && selectedOptionCount <= this.max();
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedOptionCount > 0;
|
return selectedOptionCount > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleStatus() {
|
toggleStatus() {
|
||||||
const { state, attrs } = this;
|
const { state, attrs } = this;
|
||||||
const { poll } = attrs;
|
const { post, poll } = attrs;
|
||||||
const isClosed = poll.get('status') === 'closed';
|
|
||||||
|
if (this.isAutomaticallyClosed()) { return; }
|
||||||
|
|
||||||
bootbox.confirm(
|
bootbox.confirm(
|
||||||
I18n.t(isClosed ? "poll.open.confirm" : "poll.close.confirm"),
|
I18n.t(this.isClosed() ? "poll.open.confirm" : "poll.close.confirm"),
|
||||||
I18n.t("no_value"),
|
I18n.t("no_value"),
|
||||||
I18n.t("yes_value"),
|
I18n.t("yes_value"),
|
||||||
confirmed => {
|
confirmed => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
state.loading = true;
|
state.loading = true;
|
||||||
|
const status = this.isClosed() ? "open" : "closed";
|
||||||
|
|
||||||
const status = isClosed ? "open" : "closed";
|
|
||||||
ajax("/polls/toggle_status", {
|
ajax("/polls/toggle_status", {
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
data: {
|
data: {
|
||||||
post_id: attrs.post.get('id'),
|
post_id: post.get('id'),
|
||||||
poll_name: poll.get('name'),
|
poll_name: poll.get('name'),
|
||||||
status,
|
status,
|
||||||
}
|
}
|
||||||
@ -535,15 +555,15 @@ export default createWidget('discourse-poll', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
showLogin() {
|
showLogin() {
|
||||||
const appRoute = this.register.lookup('route:application');
|
this.register.lookup('route:application').send('showLogin');
|
||||||
appRoute.send('showLogin');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleOption(option) {
|
toggleOption(option) {
|
||||||
|
const { attrs } = this;
|
||||||
|
|
||||||
if (this.isClosed()) { return; }
|
if (this.isClosed()) { return; }
|
||||||
if (!this.currentUser) { this.showLogin(); }
|
if (!this.currentUser) { this.showLogin(); }
|
||||||
|
|
||||||
const { attrs } = this;
|
|
||||||
const { vote } = attrs;
|
const { vote } = attrs;
|
||||||
|
|
||||||
const chosenIdx = vote.indexOf(option.id);
|
const chosenIdx = vote.indexOf(option.id);
|
||||||
|
@ -52,9 +52,14 @@ div.poll {
|
|||||||
font-size: 3.5em;
|
font-size: 3.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-text {
|
.info-label {
|
||||||
font-size: 1.7em;
|
font-size: 1.7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info-text {
|
||||||
|
margin: 5px 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.poll-container {
|
.poll-container {
|
||||||
|
@ -14,7 +14,7 @@ div.poll {
|
|||||||
margin: 40px 20px;
|
margin: 40px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-text {
|
.info-label {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ div.poll {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.poll-info {
|
.poll-info {
|
||||||
.info-text:before {content:"\00a0";} // nbsp
|
.info-label:before {
|
||||||
|
content:"\00a0"; //nbsp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,9 @@ en:
|
|||||||
label: "Close"
|
label: "Close"
|
||||||
confirm: "Are you sure you want to close this poll?"
|
confirm: "Are you sure you want to close this poll?"
|
||||||
|
|
||||||
|
automatic_close:
|
||||||
|
closes_in: "Closes in <strong>%{timeLeft}</strong>."
|
||||||
|
|
||||||
error_while_toggling_status: "Sorry, there was an error toggling the status of this poll."
|
error_while_toggling_status: "Sorry, there was an error toggling the status of this poll."
|
||||||
error_while_casting_votes: "Sorry, there was an error casting your votes."
|
error_while_casting_votes: "Sorry, there was an error casting your votes."
|
||||||
error_while_fetching_voters: "Sorry, there was an error displaying the voters."
|
error_while_fetching_voters: "Sorry, there was an error displaying the voters."
|
||||||
@ -89,3 +92,5 @@ en:
|
|||||||
label: Show who voted
|
label: Show who voted
|
||||||
poll_options:
|
poll_options:
|
||||||
label: Enter one poll option per line
|
label: Enter one poll option per line
|
||||||
|
automatic_close:
|
||||||
|
label: Automatically close poll
|
||||||
|
@ -44,7 +44,7 @@ en:
|
|||||||
requires_at_least_1_valid_option: "You must select at least 1 valid option."
|
requires_at_least_1_valid_option: "You must select at least 1 valid option."
|
||||||
|
|
||||||
default_cannot_be_made_public: "Poll with votes cannot be made public."
|
default_cannot_be_made_public: "Poll with votes cannot be made public."
|
||||||
named_cannot_be_made_public: "Poll named <strong>%{name}</strong> has votes cannot be made public."
|
named_cannot_be_made_public: "Poll named <strong>%{name}</strong> has votes and cannot be made public."
|
||||||
|
|
||||||
edit_window_expired:
|
edit_window_expired:
|
||||||
op_cannot_edit_options: "You cannot add or remove poll options after the first %{minutes} minutes. Please contact a moderator if you need to edit a poll option."
|
op_cannot_edit_options: "You cannot add or remove poll options after the first %{minutes} minutes. Please contact a moderator if you need to edit a poll option."
|
||||||
|
11
plugins/poll/jobs/regular/close_poll.rb
Normal file
11
plugins/poll/jobs/regular/close_poll.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
module Jobs
|
||||||
|
|
||||||
|
class ClosePoll < Jobs::Base
|
||||||
|
|
||||||
|
def execute(args)
|
||||||
|
DiscoursePoll::Poll.toggle_status(args[:post_id], args[:poll_name], "closed", -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -1,6 +1,6 @@
|
|||||||
module DiscoursePoll
|
module DiscoursePoll
|
||||||
class PollsUpdater
|
class PollsUpdater
|
||||||
VALID_POLLS_CONFIGS = %w{type min max public}.map(&:freeze)
|
VALID_POLLS_CONFIGS = %w{type min max public close}.map(&:freeze)
|
||||||
|
|
||||||
def self.update(post, polls)
|
def self.update(post, polls)
|
||||||
# load previous polls
|
# load previous polls
|
||||||
@ -18,28 +18,18 @@ module DiscoursePoll
|
|||||||
poll_edit_window_mins = SiteSetting.poll_edit_window_mins
|
poll_edit_window_mins = SiteSetting.poll_edit_window_mins
|
||||||
|
|
||||||
if post.created_at < poll_edit_window_mins.minutes.ago && has_votes
|
if post.created_at < poll_edit_window_mins.minutes.ago && has_votes
|
||||||
is_staff = User.staff.where(id: post.last_editor_id).exists?
|
|
||||||
|
|
||||||
# deal with option changes
|
# deal with option changes
|
||||||
if is_staff
|
if User.staff.where(id: post.last_editor_id).exists?
|
||||||
# staff can edit options
|
# staff can edit options
|
||||||
polls.each_key do |poll_name|
|
polls.each_key do |poll_name|
|
||||||
if polls.dig(poll_name, "options")&.size != previous_polls.dig(poll_name, "options")&.size && previous_polls.dig(poll_name, "voters").to_i > 0
|
if polls.dig(poll_name, "options")&.size != previous_polls.dig(poll_name, "options")&.size && previous_polls.dig(poll_name, "voters").to_i > 0
|
||||||
post.errors.add(:base, I18n.t(
|
post.errors.add(:base, I18n.t("poll.edit_window_expired.staff_cannot_add_or_remove_options", minutes: poll_edit_window_mins))
|
||||||
"poll.edit_window_expired.staff_cannot_add_or_remove_options",
|
|
||||||
minutes: poll_edit_window_mins
|
|
||||||
))
|
|
||||||
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# OP cannot edit poll options
|
# OP cannot edit poll options
|
||||||
post.errors.add(:base, I18n.t(
|
post.errors.add(:base, I18n.t("poll.edit_window_expired.op_cannot_edit_options", minutes: poll_edit_window_mins))
|
||||||
"poll.edit_window_expired.op_cannot_edit_options",
|
|
||||||
minutes: poll_edit_window_mins
|
|
||||||
))
|
|
||||||
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -93,6 +83,9 @@ module DiscoursePoll
|
|||||||
post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD] = polls
|
post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD] = polls
|
||||||
post.save_custom_fields(true)
|
post.save_custom_fields(true)
|
||||||
|
|
||||||
|
# re-schedule jobs
|
||||||
|
DiscoursePoll::Poll.schedule_jobs(post)
|
||||||
|
|
||||||
# publish the changes
|
# publish the changes
|
||||||
MessageBus.publish("/polls/#{post.topic_id}", post_id: post.id, polls: polls)
|
MessageBus.publish("/polls/#{post.topic_id}", post_id: post.id, polls: polls)
|
||||||
end
|
end
|
||||||
@ -102,9 +95,7 @@ module DiscoursePoll
|
|||||||
return true if (current_polls.keys.sort != previous_polls.keys.sort)
|
return true if (current_polls.keys.sort != previous_polls.keys.sort)
|
||||||
|
|
||||||
current_polls.each_key do |poll_name|
|
current_polls.each_key do |poll_name|
|
||||||
if !previous_polls[poll_name] ||
|
if !previous_polls[poll_name] || (current_polls[poll_name].values_at(*VALID_POLLS_CONFIGS) != previous_polls[poll_name].values_at(*VALID_POLLS_CONFIGS))
|
||||||
(current_polls[poll_name].values_at(*VALID_POLLS_CONFIGS) != previous_polls[poll_name].values_at(*VALID_POLLS_CONFIGS))
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -127,12 +118,9 @@ module DiscoursePoll
|
|||||||
current_poll = current_polls[poll_name]
|
current_poll = current_polls[poll_name]
|
||||||
|
|
||||||
if previous_polls["public"].nil? && current_poll["public"] == "true"
|
if previous_polls["public"].nil? && current_poll["public"] == "true"
|
||||||
error =
|
error = poll_name == DiscoursePoll::DEFAULT_POLL_NAME ?
|
||||||
if poll_name == DiscoursePoll::DEFAULT_POLL_NAME
|
I18n.t("poll.default_cannot_be_made_public") :
|
||||||
I18n.t("poll.default_cannot_be_made_public")
|
|
||||||
else
|
|
||||||
I18n.t("poll.named_cannot_be_made_public", name: poll_name)
|
I18n.t("poll.named_cannot_be_made_public", name: poll_name)
|
||||||
end
|
|
||||||
|
|
||||||
post.errors.add(:base, error)
|
post.errors.add(:base, error)
|
||||||
return true
|
return true
|
||||||
|
@ -6,17 +6,13 @@ module DiscoursePoll
|
|||||||
|
|
||||||
def validate_post
|
def validate_post
|
||||||
min_trust_level = SiteSetting.poll_minimum_trust_level_to_create
|
min_trust_level = SiteSetting.poll_minimum_trust_level_to_create
|
||||||
trusted = @post&.user&.staff? ||
|
|
||||||
@post&.user&.trust_level >= TrustLevel[min_trust_level]
|
|
||||||
|
|
||||||
if !trusted
|
|
||||||
message = I18n.t("poll.insufficient_rights_to_create")
|
|
||||||
|
|
||||||
@post.errors.add(:base, message)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
if @post&.user&.staff? || @post&.user&.trust_level >= TrustLevel[min_trust_level]
|
||||||
true
|
true
|
||||||
|
else
|
||||||
|
@post.errors.add(:base, I18n.t("poll.insufficient_rights_to_create"))
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -10,15 +10,16 @@ register_asset "stylesheets/desktop/poll.scss", :desktop
|
|||||||
register_asset "stylesheets/mobile/poll.scss", :mobile
|
register_asset "stylesheets/mobile/poll.scss", :mobile
|
||||||
|
|
||||||
PLUGIN_NAME ||= "discourse_poll".freeze
|
PLUGIN_NAME ||= "discourse_poll".freeze
|
||||||
|
|
||||||
DATA_PREFIX ||= "data-poll-".freeze
|
DATA_PREFIX ||= "data-poll-".freeze
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
|
|
||||||
|
require File.expand_path("../jobs/regular/close_poll", __FILE__)
|
||||||
|
|
||||||
module ::DiscoursePoll
|
module ::DiscoursePoll
|
||||||
DEFAULT_POLL_NAME ||= "poll".freeze
|
DEFAULT_POLL_NAME ||= "poll".freeze
|
||||||
POLLS_CUSTOM_FIELD ||= "polls".freeze
|
POLLS_CUSTOM_FIELD ||= "polls".freeze
|
||||||
VOTES_CUSTOM_FIELD ||= "polls-votes".freeze
|
VOTES_CUSTOM_FIELD ||= "polls-votes".freeze
|
||||||
MUTEX_PREFIX ||= PLUGIN_NAME
|
|
||||||
|
|
||||||
autoload :PostValidator, "#{Rails.root}/plugins/poll/lib/post_validator"
|
autoload :PostValidator, "#{Rails.root}/plugins/poll/lib/post_validator"
|
||||||
autoload :PollsValidator, "#{Rails.root}/plugins/poll/lib/polls_validator"
|
autoload :PollsValidator, "#{Rails.root}/plugins/poll/lib/polls_validator"
|
||||||
@ -62,7 +63,12 @@ after_initialize do
|
|||||||
|
|
||||||
raise StandardError.new I18n.t("poll.no_poll_with_this_name", name: poll_name) if poll.blank?
|
raise StandardError.new I18n.t("poll.no_poll_with_this_name", name: poll_name) if poll.blank?
|
||||||
raise StandardError.new I18n.t("poll.poll_must_be_open_to_vote") if poll["status"] != "open"
|
raise StandardError.new I18n.t("poll.poll_must_be_open_to_vote") if poll["status"] != "open"
|
||||||
public_poll = (poll["public"] == "true")
|
|
||||||
|
# ensure no race condition when poll is automatically closed
|
||||||
|
if poll["close"].present?
|
||||||
|
close_date = Time.zone.parse(poll["close"]) rescue nil
|
||||||
|
raise StandardError.new I18n.t("poll.poll_must_be_open_to_vote") if close_date && close_date <= Time.zone.now
|
||||||
|
end
|
||||||
|
|
||||||
# remove options that aren't available in the poll
|
# remove options that aren't available in the poll
|
||||||
available_options = poll["options"].map { |o| o["id"] }.to_set
|
available_options = poll["options"].map { |o| o["id"] }.to_set
|
||||||
@ -83,6 +89,8 @@ after_initialize do
|
|||||||
poll["voters"] += 1 if (available_options & votes.to_set).size > 0
|
poll["voters"] += 1 if (available_options & votes.to_set).size > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
public_poll = (poll["public"] == "true")
|
||||||
|
|
||||||
poll["options"].each do |option|
|
poll["options"].each do |option|
|
||||||
anonymous_votes = option["anonymous_votes"] || 0
|
anonymous_votes = option["anonymous_votes"] || 0
|
||||||
option["votes"] = all_options[option["id"]] + anonymous_votes
|
option["votes"] = all_options[option["id"]] + anonymous_votes
|
||||||
@ -102,14 +110,11 @@ after_initialize do
|
|||||||
post.save_custom_fields(true)
|
post.save_custom_fields(true)
|
||||||
|
|
||||||
payload = { post_id: post_id, polls: polls }
|
payload = { post_id: post_id, polls: polls }
|
||||||
|
payload.merge!(user: UserNameSerializer.new(user).serializable_hash) if public_poll
|
||||||
if public_poll
|
|
||||||
payload.merge!(user: UserNameSerializer.new(user).serializable_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
MessageBus.publish("/polls/#{post.topic_id}", payload)
|
MessageBus.publish("/polls/#{post.topic_id}", payload)
|
||||||
|
|
||||||
return [poll, options]
|
[poll, options]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -149,36 +154,40 @@ after_initialize do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def schedule_jobs(post)
|
||||||
|
post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD].each do |name, poll|
|
||||||
|
Jobs.cancel_scheduled_job(:close_poll, post_id: post.id, poll_name: name)
|
||||||
|
|
||||||
|
if poll["status"] == "open" && poll["close"].present?
|
||||||
|
close_date = Time.zone.parse(poll["close"]) rescue nil
|
||||||
|
Jobs.enqueue_at(close_date, :close_poll, post_id: post.id, poll_name: name) if close_date && close_date > Time.zone.now
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def extract(raw, topic_id, user_id = nil)
|
def extract(raw, topic_id, user_id = nil)
|
||||||
# TODO: we should fix the callback mess so that the cooked version is available
|
# TODO: we should fix the callback mess so that the cooked version is available
|
||||||
# in the validators instead of cooking twice
|
# in the validators instead of cooking twice
|
||||||
cooked = PrettyText.cook(raw, topic_id: topic_id, user_id: user_id)
|
cooked = PrettyText.cook(raw, topic_id: topic_id, user_id: user_id)
|
||||||
parsed = Nokogiri::HTML(cooked)
|
|
||||||
|
|
||||||
extracted_polls = []
|
Nokogiri::HTML(cooked).css("div.poll").map do |p|
|
||||||
|
|
||||||
# extract polls
|
|
||||||
parsed.css("div.poll").each do |p|
|
|
||||||
poll = { "options" => [], "voters" => 0 }
|
poll = { "options" => [], "voters" => 0 }
|
||||||
|
|
||||||
# extract attributes
|
# attributes
|
||||||
p.attributes.values.each do |attribute|
|
p.attributes.values.each do |attribute|
|
||||||
if attribute.name.start_with?(DATA_PREFIX)
|
if attribute.name.start_with?(DATA_PREFIX)
|
||||||
poll[attribute.name[DATA_PREFIX.length..-1]] = CGI.escapeHTML(attribute.value || "")
|
poll[attribute.name[DATA_PREFIX.length..-1]] = CGI.escapeHTML(attribute.value || "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# extract options
|
# options
|
||||||
p.css("li[#{DATA_PREFIX}option-id]").each do |o|
|
p.css("li[#{DATA_PREFIX}option-id]").each do |o|
|
||||||
option_id = o.attributes[DATA_PREFIX + "option-id"].value || ""
|
option_id = o.attributes[DATA_PREFIX + "option-id"].value || ""
|
||||||
poll["options"] << { "id" => option_id, "html" => o.inner_html, "votes" => 0 }
|
poll["options"] << { "id" => option_id, "html" => o.inner_html, "votes" => 0 }
|
||||||
end
|
end
|
||||||
|
|
||||||
# add the poll
|
poll
|
||||||
extracted_polls << poll
|
|
||||||
end
|
end
|
||||||
|
|
||||||
extracted_polls
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -235,7 +244,6 @@ after_initialize do
|
|||||||
options = poll["options"]
|
options = poll["options"]
|
||||||
|
|
||||||
if poll["type"] != "number"
|
if poll["type"] != "number"
|
||||||
|
|
||||||
per_option_voters = {}
|
per_option_voters = {}
|
||||||
|
|
||||||
options.each do |option|
|
options.each do |option|
|
||||||
@ -256,7 +264,7 @@ after_initialize do
|
|||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
User.where(id: user_ids).map do |user|
|
User.where(id: user_ids).each do |user|
|
||||||
user_hash = UserNameSerializer.new(user).serializable_hash
|
user_hash = UserNameSerializer.new(user).serializable_hash
|
||||||
|
|
||||||
poll_votes[user.id.to_s][poll_name].each do |option_id|
|
poll_votes[user.id.to_s][poll_name].each do |option_id|
|
||||||
@ -277,12 +285,7 @@ after_initialize do
|
|||||||
user_ids.flatten!
|
user_ids.flatten!
|
||||||
user_ids.uniq!
|
user_ids.uniq!
|
||||||
user_ids = user_ids.slice((params[:offset].to_i || 0) * voter_limit, voter_limit)
|
user_ids = user_ids.slice((params[:offset].to_i || 0) * voter_limit, voter_limit)
|
||||||
|
result = User.where(id: user_ids).map { |user| UserNameSerializer.new(user).serializable_hash }
|
||||||
result = []
|
|
||||||
|
|
||||||
User.where(id: user_ids).map do |user|
|
|
||||||
result << UserNameSerializer.new(user).serializable_hash
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: { poll_name => result }
|
render json: { poll_name => result }
|
||||||
@ -373,18 +376,19 @@ after_initialize do
|
|||||||
if post.nil? || post.trashed?
|
if post.nil? || post.trashed?
|
||||||
fragment.css(".poll, [data-poll-name]").each(&:remove)
|
fragment.css(".poll, [data-poll-name]").each(&:remove)
|
||||||
else
|
else
|
||||||
post_url = "#{Discourse.base_url}#{post.url}"
|
post_url = post.full_url
|
||||||
fragment.css(".poll, [data-poll-name]").each do |poll|
|
fragment.css(".poll, [data-poll-name]").each do |poll|
|
||||||
poll.replace "<p><a href='#{post_url}'>#{I18n.t("poll.email.link_to_poll")}</a></p>"
|
poll.replace "<p><a href='#{post_url}'>#{I18n.t("poll.email.link_to_poll")}</a></p>"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# tells the front-end we have a poll for that post
|
|
||||||
on(:post_created) do |post|
|
on(:post_created) do |post|
|
||||||
next if post.is_first_post? || post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD].blank?
|
next if post.is_first_post? || post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD].blank?
|
||||||
MessageBus.publish("/polls/#{post.topic_id}", post_id: post.id,
|
# signals the front-end we have polls for that post
|
||||||
polls: post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD])
|
MessageBus.publish("/polls/#{post.topic_id}", post_id: post.id, polls: post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD])
|
||||||
|
# schedule automatic close jobs
|
||||||
|
DiscoursePoll::Poll.schedule_jobs(post)
|
||||||
end
|
end
|
||||||
|
|
||||||
on(:merging_users) do |source_user, target_user|
|
on(:merging_users) do |source_user, target_user|
|
||||||
|
Reference in New Issue
Block a user