commit
ccb4248f20
|
@ -1,7 +1,7 @@
|
||||||
<table>
|
<table>
|
||||||
{{#each poll.options}}
|
{{#each poll.options}}
|
||||||
<tr {{bind-attr class=checked:active}} {{action selectOption option}}>
|
<tr {{bind-attr class=checked:active}} {{action selectOption option}}>
|
||||||
<td class="radio"><input type="radio" name="poll" {{bind-attr checked=checked disabled=controller.loading}}></td>
|
<td class="radio"><input type="radio" name="poll" {{bind-attr checked=checked disabled=controller.disableRadio}}></td>
|
||||||
<td class="option">
|
<td class="option">
|
||||||
<div class="option">
|
<div class="option">
|
||||||
{{{ option }}}
|
{{{ option }}}
|
||||||
|
|
|
@ -38,8 +38,14 @@ var PollController = Discourse.Controller.extend({
|
||||||
poll: null,
|
poll: null,
|
||||||
showResults: false,
|
showResults: false,
|
||||||
|
|
||||||
|
disableRadio: Em.computed.any('poll.post.topic.closed', 'loading'),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
selectOption: function(option) {
|
selectOption: function(option) {
|
||||||
|
if (this.get('disableRadio')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.get('currentUser.id')) {
|
if (!this.get('currentUser.id')) {
|
||||||
this.get('postController').send('showLogin');
|
this.get('postController').send('showLogin');
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -5,7 +5,12 @@
|
||||||
# http://yamllint.com/
|
# http://yamllint.com/
|
||||||
|
|
||||||
en:
|
en:
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
post:
|
||||||
|
poll_options: "Poll options"
|
||||||
poll:
|
poll:
|
||||||
must_contain_poll_options: "must contain a list of poll options"
|
must_contain_poll_options: "must contain a list of poll options"
|
||||||
cannot_have_modified_options: "cannot have modified poll options after 5 minutes"
|
cannot_have_modified_options: "cannot be modified after the first five minutes. Contact a moderator if you need to change them."
|
||||||
|
cannot_add_or_remove_options: "can only be edited, not added or removed. If you need to add or remove options you should lock this thread and create a new one."
|
||||||
prefix: "Poll:"
|
prefix: "Poll:"
|
||||||
|
|
|
@ -14,4 +14,4 @@ fr:
|
||||||
poll:
|
poll:
|
||||||
must_contain_poll_options: "doit contenir une liste de choix pour le sondage"
|
must_contain_poll_options: "doit contenir une liste de choix pour le sondage"
|
||||||
cannot_have_modified_options: "ne peut pas être modifié car il contient un sondage publié depuis plus de 5 minutes"
|
cannot_have_modified_options: "ne peut pas être modifié car il contient un sondage publié depuis plus de 5 minutes"
|
||||||
prefix: "Sondage:"
|
prefix: "Sondage\\s?:"
|
||||||
|
|
|
@ -6,8 +6,7 @@
|
||||||
load File.expand_path("../poll.rb", __FILE__)
|
load File.expand_path("../poll.rb", __FILE__)
|
||||||
|
|
||||||
# Without this line we can't lookup the constant inside the after_initialize blocks,
|
# Without this line we can't lookup the constant inside the after_initialize blocks,
|
||||||
# probably because all of this is instance_eval'd inside an instance of
|
# because all of this is instance_eval'd inside an instance of Plugin::Instance.
|
||||||
# Plugin::Instance.
|
|
||||||
PollPlugin = PollPlugin
|
PollPlugin = PollPlugin
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
|
@ -64,8 +63,6 @@ after_initialize do
|
||||||
# Starting a topic title with "Poll:" will create a poll topic. If the title
|
# Starting a topic title with "Poll:" will create a poll topic. If the title
|
||||||
# starts with "poll:" but the first post doesn't contain a list of options in
|
# starts with "poll:" but the first post doesn't contain a list of options in
|
||||||
# it we need to raise an error.
|
# it we need to raise an error.
|
||||||
# Need to add an error when:
|
|
||||||
# * there is no list of options.
|
|
||||||
Post.class_eval do
|
Post.class_eval do
|
||||||
validate :poll_options
|
validate :poll_options
|
||||||
def poll_options
|
def poll_options
|
||||||
|
@ -77,30 +74,15 @@ after_initialize do
|
||||||
self.errors.add(:raw, I18n.t('poll.must_contain_poll_options'))
|
self.errors.add(:raw, I18n.t('poll.must_contain_poll_options'))
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.created_at and self.created_at < 5.minutes.ago and poll.options.sort != poll.details.keys.sort
|
poll.ensure_can_be_edited!
|
||||||
self.errors.add(:raw, I18n.t('poll.cannot_have_modified_options'))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Save the list of options to PluginStore after the post is saved.
|
# Save the list of options to PluginStore after the post is saved.
|
||||||
Post.class_eval do
|
Post.class_eval do
|
||||||
after_save :save_poll_options_to_topic_metadata
|
after_save :save_poll_options_to_plugin_store
|
||||||
def save_poll_options_to_topic_metadata
|
def save_poll_options_to_plugin_store
|
||||||
poll = PollPlugin::Poll.new(self)
|
PollPlugin::Poll.new(self).update_options!
|
||||||
if poll.is_poll?
|
|
||||||
details = poll.details || {}
|
|
||||||
new_options = poll.options
|
|
||||||
details.each do |key, value|
|
|
||||||
unless new_options.include? key
|
|
||||||
details.delete(key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
new_options.each do |key|
|
|
||||||
details[key] ||= 0
|
|
||||||
end
|
|
||||||
poll.set_details! details
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,29 @@ module ::PollPlugin
|
||||||
topic.title =~ /^#{I18n.t('poll.prefix')}/i
|
topic.title =~ /^#{I18n.t('poll.prefix')}/i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Called during validation of poll posts. Discourse already restricts edits to
|
||||||
|
# the OP and staff, we want to make sure that:
|
||||||
|
#
|
||||||
|
# * OP cannot edit options after 5 minutes.
|
||||||
|
# * Staff can only edit options after 5 minutes, not add/remove.
|
||||||
|
def ensure_can_be_edited!
|
||||||
|
# Return if this is a new post or the options were not modified.
|
||||||
|
return if @post.id.nil? || (options.sort == details.keys.sort)
|
||||||
|
|
||||||
|
# First 5 minutes -- allow any modification.
|
||||||
|
return unless @post.created_at < 5.minutes.ago
|
||||||
|
|
||||||
|
if User.find(@post.last_editor_id).staff?
|
||||||
|
# Allow editing options, but not adding or removing.
|
||||||
|
if options.length != details.keys.length
|
||||||
|
@post.errors.add(:poll_options, I18n.t('poll.cannot_add_or_remove_options'))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# Regular user, tell them to contact a moderator.
|
||||||
|
@post.errors.add(:poll_options, I18n.t('poll.cannot_have_modified_options'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def options
|
def options
|
||||||
cooked = PrettyText.cook(@post.raw, topic_id: @post.topic_id)
|
cooked = PrettyText.cook(@post.raw, topic_id: @post.topic_id)
|
||||||
parsed = Nokogiri::HTML(cooked)
|
parsed = Nokogiri::HTML(cooked)
|
||||||
|
@ -35,6 +58,58 @@ module ::PollPlugin
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_options!
|
||||||
|
return unless self.is_poll?
|
||||||
|
return if details && details.keys.sort == options.sort
|
||||||
|
|
||||||
|
if details.try(:length) == options.length
|
||||||
|
|
||||||
|
# Assume only renaming, no reordering. Preserve votes.
|
||||||
|
old_details = self.details
|
||||||
|
old_options = old_details.keys
|
||||||
|
new_details = {}
|
||||||
|
new_options = self.options
|
||||||
|
rename = {}
|
||||||
|
|
||||||
|
0.upto(options.length-1) do |i|
|
||||||
|
new_details[ new_options[i] ] = old_details[ old_options[i] ]
|
||||||
|
|
||||||
|
if new_options[i] != old_options[i]
|
||||||
|
rename[ old_options[i] ] = new_options[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.set_details! new_details
|
||||||
|
|
||||||
|
# Update existing user votes.
|
||||||
|
# Accessing PluginStoreRow directly isn't a very nice approach but there's
|
||||||
|
# no way around it unfortunately.
|
||||||
|
# TODO: Probably want to move this to a background job.
|
||||||
|
PluginStoreRow.where(plugin_name: "poll", value: rename.keys).where('key LIKE ?', vote_key_prefix+"%").find_each do |row|
|
||||||
|
# This could've been done more efficiently using `update_all` instead of
|
||||||
|
# iterating over each individual vote, however this will be needed in the
|
||||||
|
# future once we support multiple choice polls.
|
||||||
|
row.value = rename[ row.value ]
|
||||||
|
row.save
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
# Options were added or removed.
|
||||||
|
new_options = self.options
|
||||||
|
new_details = self.details || {}
|
||||||
|
new_details.each do |key, value|
|
||||||
|
unless new_options.include? key
|
||||||
|
new_details.delete(key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
new_options.each do |key|
|
||||||
|
new_details[key] ||= 0
|
||||||
|
end
|
||||||
|
self.set_details! new_details
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def details
|
def details
|
||||||
@details ||= ::PluginStore.get("poll", details_key)
|
@details ||= ::PluginStore.get("poll", details_key)
|
||||||
end
|
end
|
||||||
|
@ -49,6 +124,8 @@ module ::PollPlugin
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_vote!(user, option)
|
def set_vote!(user, option)
|
||||||
|
return if @post.topic.closed?
|
||||||
|
|
||||||
# Get the user's current vote.
|
# Get the user's current vote.
|
||||||
vote = get_vote(user)
|
vote = get_vote(user)
|
||||||
vote = nil unless details.keys.include? vote
|
vote = nil unless details.keys.include? vote
|
||||||
|
@ -71,8 +148,12 @@ module ::PollPlugin
|
||||||
"poll_options_#{@post.id}"
|
"poll_options_#{@post.id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def vote_key_prefix
|
||||||
|
"poll_vote_#{@post.id}_"
|
||||||
|
end
|
||||||
|
|
||||||
def vote_key(user)
|
def vote_key(user)
|
||||||
"poll_vote_#{@post.id}_#{user.id}"
|
"#{vote_key_prefix}#{user.id}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -50,6 +50,14 @@ describe PollPlugin::Poll do
|
||||||
poll.details["Onodera"].should eq(1)
|
poll.details["Onodera"].should eq(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should not set votes on closed polls" do
|
||||||
|
poll.set_vote!(user, "Onodera")
|
||||||
|
post.topic.closed = true
|
||||||
|
post.topic.save!
|
||||||
|
poll.set_vote!(user, "Chitoge")
|
||||||
|
poll.get_vote(user).should eq("Onodera")
|
||||||
|
end
|
||||||
|
|
||||||
it "should serialize correctly" do
|
it "should serialize correctly" do
|
||||||
poll.serialize(user).should eq({options: poll.details, selected: nil})
|
poll.serialize(user).should eq({options: poll.details, selected: nil})
|
||||||
poll.set_vote!(user, "Onodera")
|
poll.set_vote!(user, "Onodera")
|
||||||
|
@ -63,4 +71,19 @@ describe PollPlugin::Poll do
|
||||||
poll = PollPlugin::Poll.new(post)
|
poll = PollPlugin::Poll.new(post)
|
||||||
poll.serialize(user).should eq(nil)
|
poll.serialize(user).should eq(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "stores poll options to plugin store" do
|
||||||
|
poll.set_vote!(user, "Onodera")
|
||||||
|
poll.stubs(:options).returns(["Chitoge", "Onodera", "Inferno Cop"])
|
||||||
|
poll.update_options!
|
||||||
|
poll.details.keys.sort.should eq(["Chitoge", "Inferno Cop", "Onodera"])
|
||||||
|
poll.details["Inferno Cop"].should eq(0)
|
||||||
|
poll.details["Onodera"].should eq(1)
|
||||||
|
|
||||||
|
poll.stubs(:options).returns(["Chitoge", "Onodera v2", "Inferno Cop"])
|
||||||
|
poll.update_options!
|
||||||
|
poll.details.keys.sort.should eq(["Chitoge", "Inferno Cop", "Onodera v2"])
|
||||||
|
poll.details["Onodera v2"].should eq(1)
|
||||||
|
poll.get_vote(user).should eq("Onodera v2")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,22 +3,34 @@ require 'post_creator'
|
||||||
|
|
||||||
describe PostCreator do
|
describe PostCreator do
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
|
let(:admin) { Fabricate(:admin) }
|
||||||
|
|
||||||
context "poll topic" do
|
context "poll topic" do
|
||||||
|
let(:poll_post) { PostCreator.create(user, {title: "Poll: This is a poll", raw: "[poll]\n* option 1\n* option 2\n* option 3\n* option 4\n[/poll]"}) }
|
||||||
|
|
||||||
it "cannot be created without a list of options" do
|
it "cannot be created without a list of options" do
|
||||||
post = PostCreator.create(user, {title: "Poll: This is a poll", raw: "body does not contain a list"})
|
post = PostCreator.create(user, {title: "Poll: This is a poll", raw: "body does not contain a list"})
|
||||||
post.errors[:raw].should be_present
|
post.errors[:raw].should be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
it "cannot have options changed after 5 minutes" do
|
it "cannot have options changed after 5 minutes" do
|
||||||
post = PostCreator.create(user, {title: "Poll: This is a poll", raw: "[poll]\n* option 1\n* option 2\n* option 3\n* option 4\n[/poll]"})
|
poll_post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n[/poll]"
|
||||||
post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n[/poll]"
|
poll_post.valid?.should be_true
|
||||||
post.valid?.should be_true
|
poll_post.save
|
||||||
post.save
|
|
||||||
Timecop.freeze(Time.now + 6.minutes) do
|
Timecop.freeze(Time.now + 6.minutes) do
|
||||||
post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n* option 4\n[/poll]"
|
poll_post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n* option 4\n[/poll]"
|
||||||
post.valid?.should be_false
|
poll_post.valid?.should be_false
|
||||||
post.errors[:raw].should be_present
|
poll_post.errors[:poll_options].should be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allows staff to edit options after 5 minutes" do
|
||||||
|
poll_post.last_editor_id = admin.id
|
||||||
|
Timecop.freeze(Time.now + 6.minutes) do
|
||||||
|
poll_post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n* option 4.1\n[/poll]"
|
||||||
|
poll_post.valid?.should be_true
|
||||||
|
poll_post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n[/poll]"
|
||||||
|
poll_post.valid?.should be_false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue