"),
+ $poll = $(this),
+ pollName = $poll.data("poll-name"),
+ pollView = createPollView(container, post, polls[pollName], votes[pollName]);
+
+ $poll.replaceWith($div);
+ pollView.constructor.renderer.replaceIn(pollView, $div[0]);
+ pollViews[pollName] = pollView;
+ });
+
+ this.messageBus.subscribe("/polls/" + this.get("post.id"), results => {
+ pollViews[results.poll.name].get("controller").set("model", Em.Object.create(results.poll));
+ });
+
+ this.set("pollViews", pollViews);
+ }.on("postViewInserted"),
+
+ _cleanUpPollViews: function() {
+ this.messageBus.unsubscribe("/polls/*");
+
+ if (this.get("pollViews")) {
+ _.forEach(this.get("pollViews"), v => v.destroy());
+ }
+ }.on("willClearRender")
+ });
+ }
+}
diff --git a/plugins/poll/assets/javascripts/initializers/poll.js.es6 b/plugins/poll/assets/javascripts/initializers/poll.js.es6
deleted file mode 100644
index 45b102311a9..00000000000
--- a/plugins/poll/assets/javascripts/initializers/poll.js.es6
+++ /dev/null
@@ -1,51 +0,0 @@
-import Poll from "discourse/plugins/poll/models/poll";
-import PollView from "discourse/plugins/poll/views/poll";
-import PollController from "discourse/plugins/poll/controllers/poll";
-
-import PostView from "discourse/views/post";
-
-function initializePollView(self) {
- const post = self.get('post'),
- pollDetails = post.get('poll_details');
-
- let poll = Poll.create({ post: post });
- poll.updateFromJson(pollDetails);
-
- const pollController = PollController.create({
- poll: poll,
- showResults: pollDetails["selected"],
- postController: self.get('controller')
- });
-
- return self.createChildView(PollView, { controller: pollController });
-}
-
-export default {
- name: 'poll',
-
- initialize: function() {
- PostView.reopen({
- createPollUI: function($post) {
- if (!this.get('post').get('poll_details')) {
- return;
- }
-
- let view = initializePollView(this),
- pollContainer = $post.find(".poll-ui:first");
-
- if (pollContainer.length === 0) {
- pollContainer = $post.find("ul:first");
- }
-
- let $div = $('
');
- pollContainer.replaceWith($div);
- view.constructor.renderer.appendTo(view, $div[0]);
- this.set('pollView', view);
- }.on('postViewInserted'),
-
- clearPollView: function() {
- if (this.get('pollView')) { this.get('pollView').destroy(); }
- }.on('willClearRender')
- });
- }
-};
diff --git a/plugins/poll/assets/javascripts/lib/decimal-adjust.js.es6 b/plugins/poll/assets/javascripts/lib/decimal-adjust.js.es6
new file mode 100644
index 00000000000..b6da75350ef
--- /dev/null
+++ b/plugins/poll/assets/javascripts/lib/decimal-adjust.js.es6
@@ -0,0 +1,16 @@
+// from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor
+
+export default function(type, value, exp) {
+ // If the exp is undefined or zero...
+ if (typeof exp === 'undefined' || +exp === 0) { return Math[type](value); }
+ value = +value;
+ exp = +exp;
+ // If the value is not a number or the exp is not an integer...
+ if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) { return NaN; }
+ // Shift
+ value = value.toString().split('e');
+ value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
+ // Shift back
+ value = value.toString().split('e');
+ return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
+}
diff --git a/plugins/poll/assets/javascripts/lib/round.js.es6 b/plugins/poll/assets/javascripts/lib/round.js.es6
new file mode 100644
index 00000000000..b26eed0736a
--- /dev/null
+++ b/plugins/poll/assets/javascripts/lib/round.js.es6
@@ -0,0 +1,5 @@
+import decimalAdjust from "discourse/plugins/poll/lib/decimal-adjust";
+
+export default function(value, exp) {
+ return decimalAdjust("round", value, exp);
+}
diff --git a/plugins/poll/assets/javascripts/models/poll.js.es6 b/plugins/poll/assets/javascripts/models/poll.js.es6
deleted file mode 100644
index af5abdb9498..00000000000
--- a/plugins/poll/assets/javascripts/models/poll.js.es6
+++ /dev/null
@@ -1,42 +0,0 @@
-export default Discourse.Model.extend({
- post: null,
- options: [],
- closed: false,
-
- postObserver: function() {
- this.updateFromJson(this.get('post.poll_details'));
- }.observes('post.poll_details'),
-
- fetchNewPostDetails: Discourse.debounce(function() {
- this.get('post.topic.postStream').triggerChangedPost(this.get('post.id'), this.get('post.topic.updated_at'));
- }, 250).observes('post.topic.title'),
-
- updateFromJson(json) {
- const selectedOption = json["selected"];
- let options = [];
-
- Object.keys(json["options"]).forEach(function(option) {
- options.push(Ember.Object.create({
- option: option,
- votes: json["options"][option],
- checked: option === selectedOption
- }));
- });
-
- this.setProperties({ options: options, closed: json.closed });
- },
-
- saveVote(option) {
- this.get('options').forEach(function(opt) {
- opt.set('checked', opt.get('option') === option);
- });
-
- const self = this;
- return Discourse.ajax("/poll", {
- type: "PUT",
- data: { post_id: this.get('post.id'), option: option }
- }).then(function(newJSON) {
- self.updateFromJson(newJSON);
- });
- }
-});
diff --git a/plugins/poll/assets/javascripts/poll_bbcode.js b/plugins/poll/assets/javascripts/poll_bbcode.js
deleted file mode 100644
index 4e0c6f86c5f..00000000000
--- a/plugins/poll/assets/javascripts/poll_bbcode.js
+++ /dev/null
@@ -1,9 +0,0 @@
-Discourse.Dialect.inlineBetween({
- start: '[poll]',
- stop: '[/poll]',
- rawContents: true,
- emitter: function(contents) {
- var list = Discourse.Dialect.cook(contents, {});
- return ['div', { class: 'poll-ui' }, list];
- }
-});
diff --git a/plugins/poll/assets/javascripts/poll_dialect.js b/plugins/poll/assets/javascripts/poll_dialect.js
new file mode 100644
index 00000000000..e3b88d6f3a2
--- /dev/null
+++ b/plugins/poll/assets/javascripts/poll_dialect.js
@@ -0,0 +1,149 @@
+(function() {
+
+ const DATA_PREFIX = "data-poll-";
+ const DEFAULT_POLL_NAME = "poll";
+
+ const WHITELISTED_ATTRIBUTES = ["type", "name", "min", "max", "step", "order", "color", "background", "status"];
+ const WHITELISTED_STYLES = ["color", "background"];
+
+ const ATTRIBUTES_REGEX = new RegExp("(" + WHITELISTED_ATTRIBUTES.join("|") + ")=[^\\s\\]]+", "g");
+
+ Discourse.Dialect.replaceBlock({
+ start: /\[poll([^\]]*)\]([\s\S]*)/igm,
+ stop: /\[\/poll\]/igm,
+
+ emitter: function(blockContents, matches, options) {
+ // post-process inside block contents
+ var contents = [];
+
+ if (blockContents.length) {
+ var self = this, b;
+ while ((b = blockContents.shift()) !== undefined) {
+ this.processBlock(b, blockContents).forEach(function (bc) {
+ if (typeof bc === "string" || bc instanceof String) {
+ var processed = self.processInline(String(bc));
+ if (processed.length) {
+ contents.push(["p"].concat(processed));
+ }
+ } else {
+ contents.push(bc);
+ }
+ });
+ }
+ }
+
+ // default poll attributes
+ var attributes = { "class": "poll" };
+ attributes[DATA_PREFIX + "status"] = "open";
+ attributes[DATA_PREFIX + "name"] = DEFAULT_POLL_NAME;
+
+ // extract poll attributes
+ (matches[1].match(ATTRIBUTES_REGEX) || []).forEach(function(m) {
+ var attr = m.split("=");
+ attributes[DATA_PREFIX + attr[0]] = attr[1];
+ });
+
+ // we might need these values later...
+ var min = parseInt(attributes[DATA_PREFIX + "min"], 10),
+ max = parseInt(attributes[DATA_PREFIX + "max"], 10),
+ step = parseInt(attributes[DATA_PREFIX + "step"], 10);
+
+ // generate the options when the type is "number"
+ if (attributes[DATA_PREFIX + "type"] === "number") {
+ // default values
+ if (isNaN(min)) { min = 1; }
+ if (isNaN(max)) { max = 10; }
+ if (isNaN(step)) { step = 1; }
+ // dynamically generate options
+ contents.push(["bulletlist"]);
+ for (var o = min; o <= max; o += step) {
+ contents[0].push(["listitem", String(o)]);
+ }
+ }
+
+ // make sure the first child is a list with at least 1 option
+ if (contents.length === 0 || contents[0].length <= 1 || (contents[0][0] !== "numberlist" && contents[0][0] !== "bulletlist")) {
+ return ["div"].concat(contents);
+ }
+
+ // TODO: remove non whitelisted content
+
+ // generate
styles (if any)
+ var styles = [];
+ WHITELISTED_STYLES.forEach(function(style) {
+ if (attributes[DATA_PREFIX + style]) {
+ styles.push(style + ":" + attributes[DATA_PREFIX + style]);
+ }
+ });
+
+ var style = styles.join(";");
+
+ // add option id (hash) + style
+ for (var o = 1; o < contents[0].length; o++) {
+ // break as soon as the list is done
+ if (contents[0][o][0] !== "listitem") { break; }
+
+ var attr = {};
+ // apply styles if any
+ if (style.length > 0) { attr["style"] = style; }
+ // compute md5 hash of the content of the option
+ attr[DATA_PREFIX + "option-id"] = md5(JSON.stringify(contents[0][o].slice(1)));
+ // store options attributes
+ contents[0][o].splice(1, 0, attr);
+ }
+
+ // that's our poll!
+ var result = ["div", attributes].concat(contents);
+
+ // add a small paragraph displaying the total number of votes
+ result.push(["p", I18n.t("poll.total_votes", { count: 0 })]);
+
+ // add some information when type is "multiple"
+ if (attributes[DATA_PREFIX + "type"] === "multiple") {
+ var optionCount = contents[0].length - 1;
+
+ // default values
+ if (isNaN(min) || min < 1) { min = 1; }
+ if (isNaN(max) || max > optionCount) { max = optionCount; }
+
+ // add some help text
+ var 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 < optionCount) {
+ 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 <= optionCount) {
+ help = I18n.t("poll.multiple.help.up_to_max_options", { count: max });
+ }
+ }
+
+ if (help) { result.push(["p", help]); }
+
+ // add "cast-votes" button
+ result.push(["a", { "class": "button cast-votes", "title": I18n.t("poll.cast-votes.title") }, I18n.t("poll.cast-votes.label")]);
+ }
+
+ // add "toggle-results" button
+ result.push(["a", { "class": "button toggle-results", "title": I18n.t("poll.show-results.title") }, I18n.t("poll.show-results.label")]);
+
+ return result;
+ }
+ });
+
+ Discourse.Markdown.whiteListTag("div", "class", "poll");
+ Discourse.Markdown.whiteListTag("div", "data-*");
+
+ Discourse.Markdown.whiteListTag("a", "class", /^button (cast-votes|toggle-results)/);
+
+ Discourse.Markdown.whiteListTag("li", "data-*");
+ Discourse.Markdown.whiteListTag("li", "style");
+
+})();
diff --git a/plugins/poll/assets/javascripts/views/poll.js.es6 b/plugins/poll/assets/javascripts/views/poll.js.es6
index 74efb078ab3..5ee730eaf3c 100644
--- a/plugins/poll/assets/javascripts/views/poll.js.es6
+++ b/plugins/poll/assets/javascripts/views/poll.js.es6
@@ -1,4 +1,16 @@
-export default Ember.View.extend({
+export default Em.View.extend({
templateName: "poll",
- classNames: ['poll-ui'],
+ classNames: ["poll"],
+ attributeBindings: ["data-poll-type", "data-poll-name", "data-poll-status"],
+
+ poll: Em.computed.alias("controller.poll"),
+
+ "data-poll-type": Em.computed.alias("poll.type"),
+ "data-poll-name": Em.computed.alias("poll.name"),
+ "data-poll-status": Em.computed.alias("poll.status"),
+
+ _fixPollContainerHeight: function() {
+ const pollContainer = this.$(".poll-container");
+ pollContainer.height(pollContainer.height());
+ }.on("didInsertElement")
});
diff --git a/plugins/poll/assets/stylesheets/poll.scss b/plugins/poll/assets/stylesheets/poll.scss
new file mode 100644
index 00000000000..74982c5b439
--- /dev/null
+++ b/plugins/poll/assets/stylesheets/poll.scss
@@ -0,0 +1,105 @@
+div.poll {
+
+ ul, ol {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ display: inline-block;
+ max-width: 90%;
+ }
+
+ li, .option {
+ cursor: pointer;
+ font-size: 1.125em;
+ line-height: 2;
+ }
+
+ li[data-poll-option-id] {
+ color: $secondary;
+ background: $primary;
+ padding: 0 .8em;
+ margin-bottom: .7em;
+ border-radius: .25rem;
+ box-shadow: inset 0 -.2em 0 0 rgba(0,0,0,.2),
+ inset 0 0 0 100px rgba(0,0,0,0),
+ 0 .2em 0 0 rgba(0,0,0,.2);
+
+ &:hover {
+ box-shadow: inset 0 -.2em 0 0 rgba(0,0,0,.25),
+ inset 0 0 0 100px rgba(0,0,0,.1),
+ 0 .2em 0 0 rgba(0,0,0,.2);
+ }
+
+ &:active {
+ -webkit-transform: translate(0,2px);
+ transform: translate(0,2px);
+ box-shadow: inset 0 -.1em 0 0 rgba(0,0,0,.25),
+ inset 0 0 0 100px rgba(0,0,0,.1),
+ 0 .1em 0 0 rgba(0,0,0,.2);
+ }
+
+ &[data-poll-selected="selected"] {
+ background: green !important;
+ }
+ }
+
+ .button {
+ display: inline-block;
+ padding: 6px 12px;
+ margin-right: 5px;
+ text-align: center;
+ cursor: pointer;
+ color: $primary;
+ background: dark-light-diff($primary, $secondary, 90%, -65%);
+
+ &:hover {
+ background: dark-light-diff($primary, $secondary, 65%, -75%);
+ color: #fff;
+ }
+ }
+
+ .poll-container {
+ margin: 0;
+ span {
+ font-size: 1.125em;
+ line-height: 2
+ }
+ }
+
+ .results {
+
+ .option {
+ max-width: 90%;
+ padding-right: 1.6em;
+ }
+
+ .percentage {
+ width: 10%;
+ font-size: 1.7em;
+ text-align: right;
+ vertical-align: middle;
+ color: #9E9E9E;
+ }
+
+ .bar-back {
+ background: rgb(219,219,219);
+ }
+
+ .bar {
+ height: 10px;
+ background: $primary;
+ transition: all 0.25s;
+ }
+
+ }
+
+ &[data-poll-type="number"] {
+
+ li {
+ display: inline-block;
+ margin-right: .7em;
+ }
+
+ }
+
+}
diff --git a/plugins/poll/config/locales/client.ar.yml b/plugins/poll/config/locales/client.ar.yml
deleted file mode 100644
index 45eff0a0486..00000000000
--- a/plugins/poll/config/locales/client.ar.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-ar:
- js:
- poll:
- voteCount:
- zero: "صوت 1"
- one: "صوت 1"
- two: "صوت 1"
- few: "صوت 1"
- many: "%{احسب} الأصوات"
- other: "%{احسب} الأصوات"
- results:
- show: إظهار النتائج
- hide: إخفاء النتائج
- close_poll: "إغلاق التصويت"
- open_poll: "فتح التصويت"
diff --git a/plugins/poll/config/locales/client.ca.yml b/plugins/poll/config/locales/client.ca.yml
deleted file mode 100644
index a620da27724..00000000000
--- a/plugins/poll/config/locales/client.ca.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-ca:
- js:
- poll:
- voteCount:
- one: "1 vot"
- other: "%{count} vots"
- results:
- show: Mostra resultats
- hide: Amaga resultats
- close_poll: "Tanca enquesta"
- open_poll: "Obre enquesta"
diff --git a/plugins/poll/config/locales/client.de.yml b/plugins/poll/config/locales/client.de.yml
deleted file mode 100644
index 1021d665373..00000000000
--- a/plugins/poll/config/locales/client.de.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-de:
- js:
- poll:
- voteCount:
- one: "1 Stimme"
- other: "%{count} Stimmen"
- results:
- show: Ergebnisse anzeigen
- hide: Ergebnisse ausblenden
- close_poll: "Umfrage beenden"
- open_poll: "Umfrage starten"
diff --git a/plugins/poll/config/locales/client.en.yml b/plugins/poll/config/locales/client.en.yml
index fbe425be74a..1ef1300c1c4 100644
--- a/plugins/poll/config/locales/client.en.yml
+++ b/plugins/poll/config/locales/client.en.yml
@@ -1,30 +1,41 @@
-# encoding: utf-8
-# This file contains content for the client portion of Discourse, sent out
-# to the Javascript app.
-#
-# To work with us on translations, see:
-# https://www.transifex.com/projects/p/discourse-org/
-#
-# This is a "source" file, which is used by Transifex to get translations for other languages.
-# After this file is changed, it needs to be pushed by a maintainer to Transifex:
-#
-# tx push -s
-#
-# Read more here: https://meta.discourse.org/t/contribute-a-translation-to-discourse/14882
-#
-# To validate this YAML file after you change it, please paste it into
-# http://yamllint.com/
-
en:
js:
poll:
- voteCount:
- one: "1 vote"
- other: "%{count} votes"
+ total_votes:
+ zero: "No votes yet. Want to be the first?"
+ one: "There's only 1 vote."
+ other: "There are %{count} total votes."
- results:
- show: Show Results
- hide: Hide Results
+ average_rating: "Average rating: %{average}."
- close_poll: "Close Poll"
- open_poll: "Open Poll"
+ multiple:
+ help:
+ at_least_min_options: "You may choose at least %{count} options."
+ up_to_max_options: "You may choose up to %{count} options."
+ x_options: "You may choose %{count} options."
+ between_min_and_max_options: "You may choose between %{min} and %{max} options."
+
+ cast-votes:
+ title: "Cast your votes"
+ label: "Vote now!"
+
+ show-results:
+ title: "Display the poll results"
+ label: "Show results"
+
+ hide-results:
+ title: "Back to your votes"
+ label: "Hide results"
+
+ open:
+ title: "Open the poll"
+ label: "Open"
+ confirm: "Are you sure you want to open this poll?"
+
+ close:
+ title: "Close the poll"
+ label: "Close"
+ confirm: "Are you sure you want to close this poll?"
+
+ error_while_toggling_status: "There was an error while toggling the status of this poll."
+ error_while_casting_votes: "There was an error while casting your votes."
diff --git a/plugins/poll/config/locales/client.es.yml b/plugins/poll/config/locales/client.es.yml
deleted file mode 100644
index ec158c6eb18..00000000000
--- a/plugins/poll/config/locales/client.es.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-es:
- js:
- poll:
- voteCount:
- one: "1 voto"
- other: "%{count} votos"
- results:
- show: Mostrar resultados
- hide: Ocultar resultados
- close_poll: "Cerrar encuesta"
- open_poll: "Abrir encuesta"
diff --git a/plugins/poll/config/locales/client.fa_IR.yml b/plugins/poll/config/locales/client.fa_IR.yml
deleted file mode 100644
index 73b8c0869ae..00000000000
--- a/plugins/poll/config/locales/client.fa_IR.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-fa_IR:
- js:
- poll:
- voteCount:
- other: "%{count} آرا"
- results:
- show: نمایش نتایج
- hide: پنهان کرد نتایج
- close_poll: "بستن نظرسنجی"
- open_poll: "باز کردن نظرسنجی"
diff --git a/plugins/poll/config/locales/client.fi.yml b/plugins/poll/config/locales/client.fi.yml
deleted file mode 100644
index 7d8db18d58d..00000000000
--- a/plugins/poll/config/locales/client.fi.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-fi:
- js:
- poll:
- voteCount:
- one: "1 ääni"
- other: "%{count} ääntä"
- results:
- show: Näytä tulokset
- hide: Piilota tulokset
- close_poll: "Sulje kysely"
- open_poll: "Avaa kysely"
diff --git a/plugins/poll/config/locales/client.fr.yml b/plugins/poll/config/locales/client.fr.yml
deleted file mode 100644
index 784ccac332e..00000000000
--- a/plugins/poll/config/locales/client.fr.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-fr:
- js:
- poll:
- voteCount:
- one: "1 vote"
- other: "%{count} votes"
- results:
- show: Voir les résultats
- hide: Cacher les résultats
- close_poll: "Fermer le sondage"
- open_poll: "Réouvrir le sondage"
diff --git a/plugins/poll/config/locales/client.he.yml b/plugins/poll/config/locales/client.he.yml
deleted file mode 100644
index ec8a5155747..00000000000
--- a/plugins/poll/config/locales/client.he.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-he:
- js:
- poll:
- voteCount:
- one: "הצבעה אחת"
- other: "%{count} הצבעות"
- results:
- show: הצגת תוצאות
- hide: הסתרת תוצאות
- close_poll: "סגירת הצבעה"
- open_poll: "פתיחת הצבעה"
diff --git a/plugins/poll/config/locales/client.it.yml b/plugins/poll/config/locales/client.it.yml
deleted file mode 100644
index 1e76d884e41..00000000000
--- a/plugins/poll/config/locales/client.it.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-it:
- js:
- poll:
- voteCount:
- one: "1 voto"
- other: "%{count} voti"
- results:
- show: Mostra Risultati
- hide: Nascondi Risultati
- close_poll: "Chiudi Sondaggio"
- open_poll: "Apri Sondaggio"
diff --git a/plugins/poll/config/locales/client.ko.yml b/plugins/poll/config/locales/client.ko.yml
deleted file mode 100644
index cce2f01902f..00000000000
--- a/plugins/poll/config/locales/client.ko.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-ko:
- js:
- poll:
- voteCount:
- other: "%{count} 표"
- results:
- show: 결과 보기
- hide: 결과 숨기기
- close_poll: "투표 끝내기"
- open_poll: "투표 시작하기"
diff --git a/plugins/poll/config/locales/client.pl_PL.yml b/plugins/poll/config/locales/client.pl_PL.yml
deleted file mode 100644
index 066bfe67c59..00000000000
--- a/plugins/poll/config/locales/client.pl_PL.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-pl_PL:
- js:
- poll:
- voteCount:
- one: "1 głos"
- few: "%{count} głosy"
- other: "%{count} głosów"
- results:
- show: Pokaż wyniki
- hide: Ukryj wyniki
- close_poll: "Zamknij ankietę"
- open_poll: "Otwórz ankietę"
diff --git a/plugins/poll/config/locales/client.pt.yml b/plugins/poll/config/locales/client.pt.yml
deleted file mode 100644
index d3ebf652b8b..00000000000
--- a/plugins/poll/config/locales/client.pt.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-pt:
- js:
- poll:
- voteCount:
- one: "1 voto"
- other: "%{count} votos"
- results:
- show: Mostrar resultados
- hide: Esconder resultados
- close_poll: "Encerrar votação"
- open_poll: "Abrir votação"
diff --git a/plugins/poll/config/locales/client.pt_BR.yml b/plugins/poll/config/locales/client.pt_BR.yml
deleted file mode 100644
index 42280740afd..00000000000
--- a/plugins/poll/config/locales/client.pt_BR.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-pt_BR:
- js:
- poll:
- voteCount:
- one: "1 voto"
- other: "%{count} votos"
- results:
- show: Mostrar Resultados
- hide: Esconder Resultados
- close_poll: "Fechar Enquete "
- open_poll: "Enquete aberta"
diff --git a/plugins/poll/config/locales/client.ru.yml b/plugins/poll/config/locales/client.ru.yml
deleted file mode 100644
index b7af9a300c5..00000000000
--- a/plugins/poll/config/locales/client.ru.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-ru:
- js:
- poll:
- voteCount:
- one: "проголосовал 1"
- few: "проголосовало %{count}"
- other: "проголосовало %{count}"
- results:
- show: Показать результаты
- hide: Скрыть результаты
- close_poll: "Завершить опрос"
- open_poll: "Запустить опрос снова"
diff --git a/plugins/poll/config/locales/client.sq.yml b/plugins/poll/config/locales/client.sq.yml
deleted file mode 100644
index 025a2b0c26b..00000000000
--- a/plugins/poll/config/locales/client.sq.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-sq:
- js:
- poll:
- voteCount:
- one: "1 votë"
- other: "%{count} vota"
- results:
- show: Shfaq Rezultatet
- hide: Fsheh Rezultate
- close_poll: "Mbyll Sondazhin"
- open_poll: "Hap Sondazhin"
diff --git a/plugins/poll/config/locales/client.te.yml b/plugins/poll/config/locales/client.te.yml
deleted file mode 100644
index bc2b7210ed3..00000000000
--- a/plugins/poll/config/locales/client.te.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-te:
- js:
- poll:
- voteCount:
- one: "ఒక ఓటు"
- other: "%{count} ఓట్లు"
- results:
- show: ఫలితాలు చూపించు
- hide: ఫలితాలు దాయు
- close_poll: "ఓటు ముగించు"
- open_poll: "ఓటు తెరువు"
diff --git a/plugins/poll/config/locales/client.tr_TR.yml b/plugins/poll/config/locales/client.tr_TR.yml
deleted file mode 100644
index 721807dd241..00000000000
--- a/plugins/poll/config/locales/client.tr_TR.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-tr_TR:
- js:
- poll:
- voteCount:
- other: "%{count} oy"
- results:
- show: Sonuçları göster
- hide: Sonuçları gizle
- close_poll: "Anketi Bitir"
- open_poll: "Anket Başlat"
diff --git a/plugins/poll/config/locales/client.zh_CN.yml b/plugins/poll/config/locales/client.zh_CN.yml
deleted file mode 100644
index 9bd2ba19b7f..00000000000
--- a/plugins/poll/config/locales/client.zh_CN.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-zh_CN:
- js:
- poll:
- voteCount:
- other: "%{count} 次投票"
- results:
- show: 显示结果
- hide: 隐藏结果
- close_poll: "关闭投票"
- open_poll: "开始投票"
diff --git a/plugins/poll/config/locales/server.ar.yml b/plugins/poll/config/locales/server.ar.yml
deleted file mode 100644
index a66bdc8860a..00000000000
--- a/plugins/poll/config/locales/server.ar.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-ar:
- activerecord:
- attributes:
- post:
- poll_options: "خيارات التصويت"
- poll:
- must_contain_poll_options: "يجب أن يحتوي على قائمة خيارات التصويت"
- cannot_have_modified_options: "التعديل غير ممكن بعد مضي 5 دقائق. اتصل بالمسؤول إذا كنت بحاجة لتغييرها."
- cannot_add_or_remove_options: "تستطيع تعديله فقط ولا يمكنك إضافته أو حذفه. إذا كنت بحاجة لإضافة أو حذف خيارات يجب أن تُقفل هذا العنوان وتنشئ عنوان جديد."
- prefix: "تصويت"
- closed_prefix: "هذا التصويت مغلق"
diff --git a/plugins/poll/config/locales/server.ca.yml b/plugins/poll/config/locales/server.ca.yml
deleted file mode 100644
index 469f282d07e..00000000000
--- a/plugins/poll/config/locales/server.ca.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-ca:
- activerecord:
- attributes:
- post:
- poll_options: "Opcions d'enquesta"
- poll:
- must_contain_poll_options: "cal que contingui una llista d'opcions"
- cannot_have_modified_options: "no es pot modificar quan hagin passat els primers cinc minuts. Contacta un moderador si necessites fer-hi canvis."
- cannot_add_or_remove_options: "només es pot editar, no afegir o treure. Si necessites afegir o treure opcions, hauries de tancar aquest tema i crear-ne un de nou."
- prefix: "Enquesta"
- closed_prefix: "Enquesta tancada"
diff --git a/plugins/poll/config/locales/server.de.yml b/plugins/poll/config/locales/server.de.yml
deleted file mode 100644
index b70cfc0b469..00000000000
--- a/plugins/poll/config/locales/server.de.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-de:
- activerecord:
- attributes:
- post:
- poll_options: "Umfrageoptionen"
- poll:
- must_contain_poll_options: "muss eine Liste mit Umfrageoptionen enthalten"
- cannot_have_modified_options: "können nach den ersten 5 Minuten nicht mehr geändert werden. Kontaktiere einen Moderator, wenn du sie ändern möchtest."
- cannot_add_or_remove_options: "können nur bearbeitet, jedoch nicht hinzugefügt oder entfernt werden. Wenn du Optionen hinzufügen oder entfernen möchtest, solltest du dieses Thema sperren und ein neues erstellen."
- prefix: "Umfrage"
- closed_prefix: "Beendete Umfrage"
diff --git a/plugins/poll/config/locales/server.en.yml b/plugins/poll/config/locales/server.en.yml
index d203242cac4..6ce9e22ebed 100644
--- a/plugins/poll/config/locales/server.en.yml
+++ b/plugins/poll/config/locales/server.en.yml
@@ -1,28 +1,25 @@
-# encoding: utf-8
-#
-# This file contains content for the server portion of Discourse used by Ruby
-#
-# To work with us on translations, see:
-# https://www.transifex.com/projects/p/discourse-org/
-#
-# This is a "source" file, which is used by Transifex to get translations for other languages.
-# After this file is changed, it needs to be pushed by a maintainer to Transifex:
-#
-# tx push -s
-#
-# Read more here: https://meta.discourse.org/t/contribute-a-translation-to-discourse/14882
-#
-# To validate this YAML file after you change it, please paste it into
-# http://yamllint.com/
-
en:
- activerecord:
- attributes:
- post:
- poll_options: "Poll options"
+ site_settings:
+ poll_enabled: "Allow users to create polls?"
+
poll:
- must_contain_poll_options: "must contain a list of poll options"
- 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 topic and create a new one."
- prefix: "Poll"
- closed_prefix: "Closed Poll"
+ multiple_polls_without_name: "There are multiple polls without a name. Use the 'name
' attribute to uniquely identify your polls."
+ multiple_polls_with_same_name: "There are multiple polls with the same name: %{name}. Use the 'name
' attribute to uniquely identify your polls."
+
+ default_poll_must_have_at_least_2_options: "Poll must have at least 2 options."
+ named_poll_must_have_at_least_2_options: "Poll named %{name} must have at least 2 options."
+
+ default_poll_must_have_different_options: "Poll must have different options."
+ named_poll_must_have_different_options: "Poll name %{name} must have different options."
+
+ cannot_change_polls_after_5_minutes: "Polls cannot be changed after the first 5 minutes. Contact a moderator if you need to change them."
+ staff_cannot_add_or_remove_options_after_5_minutes: "Poll options can only be edited after the first 5 minutes. If you need to add or remove options, you should close this topic and create a new one."
+
+ no_polls_associated_with_this_post: "No polls are associated with this post."
+ no_poll_with_this_name: "No poll named %{name} associated with this post."
+
+ topic_must_be_open_to_vote: "The topic must be open to vote."
+ poll_must_be_open_to_vote: "Poll must be open to vote."
+
+ topic_must_be_open_to_toggle_status: "The topic must be open to toggle status."
+ only_staff_or_op_can_toggle_status: "Only a staff member or the original poster can toggle a poll status."
diff --git a/plugins/poll/config/locales/server.es.yml b/plugins/poll/config/locales/server.es.yml
deleted file mode 100644
index a2a783d67ff..00000000000
--- a/plugins/poll/config/locales/server.es.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-es:
- activerecord:
- attributes:
- post:
- poll_options: "Opciones de la encuesta"
- poll:
- must_contain_poll_options: "debe contener una lista con las opciones de la encuesta"
- cannot_have_modified_options: "pasados 5 minutos, no se pueden modificar las opciones de la encuesta. Contacta un moderador si necesitas cambiarlas"
- cannot_add_or_remove_options: "solo se pueden modificar, no añadir ni eliminar. Si necesitas añadir o eliminar opciones deberías cerrar este tema y crear una encuesta nueva."
- prefix: "Encuesta"
- closed_prefix: "Encuesta cerrada"
diff --git a/plugins/poll/config/locales/server.fa_IR.yml b/plugins/poll/config/locales/server.fa_IR.yml
deleted file mode 100644
index bfcb72fcb45..00000000000
--- a/plugins/poll/config/locales/server.fa_IR.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-fa_IR:
- activerecord:
- attributes:
- post:
- poll_options: "گزینههای نظرسنجی"
- poll:
- must_contain_poll_options: "باید فهرستی شامل گزینههای نظرسنجی باشد"
- cannot_have_modified_options: "پس از گذشت ۵ دقیقه دیگر نمیتوان ویرایش کرد. اگر تغییری در آنها نیاز است با یکی از ناظمان تماس بگیرید."
- cannot_add_or_remove_options: "تنها میتواند ویرایش شود، نه افزودنی و نه پاک کردنی. اگر به گزینههای اضافه و پاک کردن نیاز دارید، باید این جستار را قفل کنید و یکی دیگر بسازید."
- prefix: "نظرسنجی"
- closed_prefix: "اتمام نظرسنجی"
diff --git a/plugins/poll/config/locales/server.fi.yml b/plugins/poll/config/locales/server.fi.yml
deleted file mode 100644
index deb66801023..00000000000
--- a/plugins/poll/config/locales/server.fi.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-fi:
- activerecord:
- attributes:
- post:
- poll_options: "Kyselyn vaihtoehtoja"
- poll:
- must_contain_poll_options: "täytyy sisältää lista vastausvaihtoehdoista"
- cannot_have_modified_options: "ei voi muokata kun viisi minuuttia on kulunut kyselyn luomisesta. Ota yhteyttä valvojaan jos sinun tarvitsee muokata vaihtoehtoja."
- cannot_add_or_remove_options: "voi vain muokata, ei lisätä tai poistaa. Jos sinun tarvitsee lisätä tai poistaa vaihtoehtoja, sinun tulee lukita tämä ketju ja luoda uusi."
- prefix: "Kysely"
- closed_prefix: "Suljettu kysely"
diff --git a/plugins/poll/config/locales/server.fr.yml b/plugins/poll/config/locales/server.fr.yml
deleted file mode 100644
index a9a0dd0bad4..00000000000
--- a/plugins/poll/config/locales/server.fr.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-fr:
- activerecord:
- attributes:
- post:
- poll_options: "Les options du sondage"
- poll:
- must_contain_poll_options: "doit contenir une liste d'options pour le sondage"
- cannot_have_modified_options: "ne peuvent pas être modifiés après 5 minutes. Merci de contacter un moderateur, si vous souhaitez les modifier"
- cannot_add_or_remove_options: "peuvent seulement être modifiés. Si vous souhaitez en supprimer ou en ajouter, veuillez créer un nouveau sujet."
- prefix: "Sondage "
- closed_prefix: "Sondage fermé "
diff --git a/plugins/poll/config/locales/server.he.yml b/plugins/poll/config/locales/server.he.yml
deleted file mode 100644
index aee492e7b75..00000000000
--- a/plugins/poll/config/locales/server.he.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-he:
- activerecord:
- attributes:
- post:
- poll_options: "אפשרויות הצבעה"
- poll:
- must_contain_poll_options: "חובה להכיל רשימה של אפשרויות הצבעה"
- cannot_have_modified_options: "לא ניתן לשנות את האפשרויות לאחר 5 הדקות הראשונות. יש לפנות למנהל כדי לבצע שינויים אלו."
- cannot_add_or_remove_options: "ניתן רק לערוך, לא להוסיף או להסיר אפשרויות. כדי להוסיף או להסיר אפשרויות יש לנעול את נושא זה ולפתוח אחד חדש."
- prefix: "הצבעה"
- closed_prefix: "הצבעה סגורה"
diff --git a/plugins/poll/config/locales/server.it.yml b/plugins/poll/config/locales/server.it.yml
deleted file mode 100644
index 393ea643912..00000000000
--- a/plugins/poll/config/locales/server.it.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-it:
- activerecord:
- attributes:
- post:
- poll_options: "Opzioni sondaggio"
- poll:
- must_contain_poll_options: "deve contenere una lista di opzioni per il sondaggio"
- cannot_have_modified_options: "non possono essere modificate dopo i primi cinque minuti. Contatta un moderatore se devi cambiarle."
- cannot_add_or_remove_options: "possono essere solo modificate, ma non aggiunte o rimosse. Se devi aggiungere o rimuovere opzioni, devi prima bloccare questo argomento e crearne uno nuovo."
- prefix: "Sondaggio"
- closed_prefix: "Sondaggio Chiuso"
diff --git a/plugins/poll/config/locales/server.ko.yml b/plugins/poll/config/locales/server.ko.yml
deleted file mode 100644
index c9c6621c35c..00000000000
--- a/plugins/poll/config/locales/server.ko.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-ko:
- activerecord:
- attributes:
- post:
- poll_options: "투표 옵션"
- poll:
- must_contain_poll_options: "투표 옵션 목록 포함 필수"
- cannot_have_modified_options: "5분 뒤에는 수정할 수 없습니다. 바꾸고 싶다면 관리자에게 문의하세요."
- cannot_add_or_remove_options: "수정만 가능하고 추가나 삭제가 불가능 합니다. 선택사항을 추가하거나 삭제하고 싶다면 이 토픽을 잠그고 다른 토픽을 생성해야합니다."
- prefix: "투표"
- closed_prefix: "투표 닫기"
diff --git a/plugins/poll/config/locales/server.pl_PL.yml b/plugins/poll/config/locales/server.pl_PL.yml
deleted file mode 100644
index 0a753c30296..00000000000
--- a/plugins/poll/config/locales/server.pl_PL.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-pl_PL:
- activerecord:
- attributes:
- post:
- poll_options: "Opcje ankiety"
- poll:
- must_contain_poll_options: "musi zawierać listę możliwych wyborów ankiety"
- cannot_have_modified_options: "nie mogą być zmienione po pierwszych pięciu minutach. Skontaktuj się z moderatorem, jeżeli musisz je zmienić."
- cannot_add_or_remove_options: "mogą tylko być edytowane, nie dodawane ani usuwane. Jeśli musisz dodać lub usunąć opcje, powinieneś zamknąć ten temat i utworzyć nowy."
- prefix: "Ankieta"
- closed_prefix: "Zamknięta ankieta"
diff --git a/plugins/poll/config/locales/server.pt.yml b/plugins/poll/config/locales/server.pt.yml
deleted file mode 100644
index 61af6213602..00000000000
--- a/plugins/poll/config/locales/server.pt.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-pt:
- activerecord:
- attributes:
- post:
- poll_options: "Opções da votação"
- poll:
- must_contain_poll_options: "tem que conter uma lista de opções de votação"
- cannot_have_modified_options: "não podem ser modificadas depois dos primeiros cinco minutos. Contacte um moderador se precisar de alterá-las."
- cannot_add_or_remove_options: "podem apenas ser editadas, não podendo ser adicionadas ou removidas. Se precisar de adicionar ou remover opções, deverá bloquear este tópico e criar um novo."
- prefix: "Votação"
- closed_prefix: "Votação encerrada"
diff --git a/plugins/poll/config/locales/server.pt_BR.yml b/plugins/poll/config/locales/server.pt_BR.yml
deleted file mode 100644
index 67b526d1b99..00000000000
--- a/plugins/poll/config/locales/server.pt_BR.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-pt_BR:
- activerecord:
- attributes:
- post:
- poll_options: "Opções de votação "
- poll:
- must_contain_poll_options: "deve conter uma lista de opções de votação"
- cannot_have_modified_options: "não pode ser modificado após os primeiros cinco minutos. Contate o moderador se necessitar fazer alguma mudança."
- cannot_add_or_remove_options: "Só pode ser editado, mas não adicionar nem remover. Se precisar das opções para adicionar ou remover, você deve bloquear este tópico e criar um novo."
- prefix: "Votação"
- closed_prefix: "Votação encerrada"
diff --git a/plugins/poll/config/locales/server.ru.yml b/plugins/poll/config/locales/server.ru.yml
deleted file mode 100644
index cf161bdb5b5..00000000000
--- a/plugins/poll/config/locales/server.ru.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-ru:
- activerecord:
- attributes:
- post:
- poll_options: "Варианты ответов"
- poll:
- must_contain_poll_options: "должен содержать варианты ответов (список)"
- cannot_have_modified_options: "нельзя изменять после первых пяти минут. Если все же нужно их отредактировать, свяжитесь с модератором."
- cannot_add_or_remove_options: "можно редактировать, но не добавлять или удалять. Если нужно добавить или удалить, закройте эту тему и создайте новую."
- prefix: "Опрос"
- closed_prefix: "Завершившийся опрос"
diff --git a/plugins/poll/config/locales/server.sq.yml b/plugins/poll/config/locales/server.sq.yml
deleted file mode 100644
index 7aae7c32a32..00000000000
--- a/plugins/poll/config/locales/server.sq.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-sq:
- activerecord:
- attributes:
- post:
- poll_options: "Opsionet e sondazhit"
- poll:
- must_contain_poll_options: "duhet të përmbajë një listë me pyetje"
- cannot_have_modified_options: "nuk mund të ndryshohet pasi kanë kaluar pesë minuta. Kontakto një moderator nëse nevojiten ndryshime."
- cannot_add_or_remove_options: "nuk mund të redaktohet, shtosh apo fshini pyetje. Nëse dëshironi të shtoni apo fshini pyetje ju duhet ta mbyllni këtë temë dhe të krijoni një të re."
- prefix: "Sondazh"
- closed_prefix: "Sondazh i Mbyllur"
diff --git a/plugins/poll/config/locales/server.te.yml b/plugins/poll/config/locales/server.te.yml
deleted file mode 100644
index 50b7c548c87..00000000000
--- a/plugins/poll/config/locales/server.te.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-te:
- activerecord:
- attributes:
- post:
- poll_options: "ఓటు ఐచ్చికాలు"
- poll:
- must_contain_poll_options: "తప్పనిసరి ఓటు ఐచ్చికాల జాబితా కలిగి ఉండాలి"
- cannot_have_modified_options: "మొదటి ఐదు నిమిషాల తర్వాత మార్చైత కాదు. వీటిని మార్చాలంటే ఒక నిర్వాహకుడిని సంప్రదించండి. "
- cannot_add_or_remove_options: "కేవలం సవరించవచ్చు, కలపైత కాదు, తొలగించైత కాదు. మీరు కలపడం లేదా తొలగించడం చేయాలంటే ఈ విషయానికి తాళం వేసి మరో కొత్త విషయం సృష్టించాలి"
- prefix: "ఓటు"
- closed_prefix: "మూసేసిన ఓటు"
diff --git a/plugins/poll/config/locales/server.tr_TR.yml b/plugins/poll/config/locales/server.tr_TR.yml
deleted file mode 100644
index d32284a4b92..00000000000
--- a/plugins/poll/config/locales/server.tr_TR.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-tr_TR:
- activerecord:
- attributes:
- post:
- poll_options: "Anket seçenekleri"
- poll:
- must_contain_poll_options: "anket seçenekleri listesini içermeli"
- cannot_have_modified_options: "ilk beş dakikadan sonra değişiklik yapılamaz. Değişiklik yapmanız gerekiyorsa, bir moderatör ile iletişime geçin."
- cannot_add_or_remove_options: "sadece düzenlenebilir, ekleme veya çıkarma yapılamaz. Seçenek ekleme veya çıkarmanız gerekiyorsa, bu konuyu kitlemeli ve yeni bir konu oluşturmalısınız."
- prefix: "Anket"
- closed_prefix: "Bitmiş Anket"
diff --git a/plugins/poll/config/locales/server.zh_CN.yml b/plugins/poll/config/locales/server.zh_CN.yml
deleted file mode 100644
index 64e2a1ea244..00000000000
--- a/plugins/poll/config/locales/server.zh_CN.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# encoding: utf-8
-#
-# Never edit this file. It will be overwritten when translations are pulled from Transifex.
-#
-# To work with us on translations, join this project:
-# https://www.transifex.com/projects/p/discourse-org/
-
-zh_CN:
- activerecord:
- attributes:
- post:
- poll_options: "投票选项"
- poll:
- must_contain_poll_options: "必须包含投票选项"
- cannot_have_modified_options: "在开始的五分钟后不能修改。如果需要修改他们,请联系一位版主。"
- cannot_add_or_remove_options: "只能被编辑,不能添加或者删除。如果您需要添加或者删除选项,你需要锁定这个投票并创建新的投票。"
- prefix: "投票"
- closed_prefix: "已关闭的投票:"
diff --git a/plugins/poll/config/settings.yml b/plugins/poll/config/settings.yml
new file mode 100644
index 00000000000..eb5cc0aafe1
--- /dev/null
+++ b/plugins/poll/config/settings.yml
@@ -0,0 +1,3 @@
+plugins:
+ poll_enabled:
+ default: true
diff --git a/plugins/poll/plugin.rb b/plugins/poll/plugin.rb
index 9754629585e..7e835e59781 100644
--- a/plugins/poll/plugin.rb
+++ b/plugins/poll/plugin.rb
@@ -1,205 +1,267 @@
# name: poll
-# about: adds poll support to Discourse
-# version: 0.2
-# authors: Vikhyat Korrapati
+# about: Official poll plugin for Discourse
+# version: 0.9
+# authors: Vikhyat Korrapati (vikhyat), Régis Hanol (zogstrip)
# url: https://github.com/discourse/discourse/tree/master/plugins/poll
-load File.expand_path("../poll.rb", __FILE__)
+register_asset "stylesheets/poll.scss"
+register_asset "javascripts/poll_dialect.js", :server_side
-# Without this line we can't lookup the constant inside the after_initialize blocks,
-# because all of this is instance_eval'd inside an instance of Plugin::Instance.
-PollPlugin = PollPlugin
+PLUGIN_NAME ||= "discourse_poll".freeze
+
+POLLS_CUSTOM_FIELD ||= "polls".freeze
+VOTES_CUSTOM_FIELD ||= "polls-votes".freeze
after_initialize do
- # Rails Engine for accepting votes.
- module PollPlugin
+
+ module ::DiscoursePoll
class Engine < ::Rails::Engine
- engine_name "poll_plugin"
- isolate_namespace PollPlugin
- end
-
- class PollController < ActionController::Base
- include CurrentUser
-
- def vote
- if current_user.nil?
- render status: :forbidden, json: false
- return
- end
-
- if params[:post_id].nil? or params[:option].nil?
- render status: 400, json: false
- return
- end
-
- post = Post.find(params[:post_id])
- poll = PollPlugin::Poll.new(post)
- unless poll.has_poll_details?
- render status: 400, json: false
- return
- end
-
- options = poll.details
-
- unless options.keys.include? params[:option]
- render status: 400, json: false
- return
- end
-
- poll.set_vote!(current_user, params[:option])
-
- MessageBus.publish("/topic/#{post.topic_id}", {
- id: post.id,
- post_number: post.post_number,
- updated_at: Time.now,
- type: "revised"
- },
- group_ids: post.topic.secure_group_ids
- )
-
- render json: poll.serialize(current_user)
- end
-
- def toggle_close
- post = Post.find(params[:post_id])
- topic = post.topic
- poll = PollPlugin::Poll.new(post)
-
- # Make sure the user is allowed to close the poll.
- Guardian.new(current_user).ensure_can_edit!(topic)
-
- # Make sure this is actually a poll.
- unless poll.has_poll_details?
- render status: 400, json: false
- return
- end
-
- # Make sure the topic is not closed.
- if topic.closed?
- render status: 400, json: false
- return
- end
-
- # Modify topic title.
- I18n.with_locale(topic.user.effective_locale) do
- if topic.title =~ /^(#{I18n.t('poll.prefix').strip})\s?:/i
- topic.title = topic.title.gsub(/^(#{I18n.t('poll.prefix').strip})\s?:/i, I18n.t('poll.closed_prefix') + ':')
- elsif topic.title =~ /^(#{I18n.t('poll.closed_prefix').strip})\s?:/i
- topic.title = topic.title.gsub(/^(#{I18n.t('poll.closed_prefix').strip})\s?:/i, I18n.t('poll.prefix') + ':')
- end
- end
-
- topic.acting_user = current_user
- topic.save!
-
- render json: topic, serializer: BasicTopicSerializer
- end
+ engine_name PLUGIN_NAME
+ isolate_namespace DiscoursePoll
end
end
- PollPlugin::Engine.routes.draw do
- put '/' => 'poll#vote'
- put '/toggle_close' => 'poll#toggle_close'
+ require_dependency "application_controller"
+ class DiscoursePoll::PollsController < ::ApplicationController
+ requires_plugin PLUGIN_NAME
+
+ before_filter :ensure_logged_in
+
+ def vote
+ post_id = params.require(:post_id)
+ poll_name = params.require(:poll_name)
+ options = params.require(:options)
+ user_id = current_user.id
+
+ DistributedMutex.synchronize("#{PLUGIN_NAME}-#{post_id}") do
+ post = Post.find(post_id)
+
+ # topic must be open
+ if post.topic.try(:closed) || post.topic.try(:archived)
+ return render_json_error I18n.t("poll.topic_must_be_open_to_vote")
+ end
+
+ polls = post.custom_fields[POLLS_CUSTOM_FIELD]
+
+ return render_json_error I18n.t("poll.no_polls_associated_with_this_post") if polls.blank?
+
+ poll = polls[poll_name]
+
+ return render_json_error I18n.t("poll.no_poll_with_this_name", name: poll_name) if poll.blank?
+ return render_json_error I18n.t("poll.poll_must_be_open_to_vote") if poll["status"] != "open"
+
+ votes = post.custom_fields["#{VOTES_CUSTOM_FIELD}-#{user_id}"] || {}
+ vote = votes[poll_name] || []
+
+ poll["total_votes"] += 1 if vote.size == 0
+
+ poll["options"].each do |option|
+ option["votes"] -= 1 if vote.include?(option["id"])
+ option["votes"] += 1 if options.include?(option["id"])
+ end
+
+ votes[poll_name] = options
+
+ post.custom_fields[POLLS_CUSTOM_FIELD] = polls
+ post.custom_fields["#{VOTES_CUSTOM_FIELD}-#{user_id}"] = votes
+ post.save_custom_fields
+
+ MessageBus.publish("/polls/#{post_id}", { poll: poll })
+
+ render json: { poll: poll, vote: options }
+ end
+ end
+
+ def toggle_status
+ post_id = params.require(:post_id)
+ poll_name = params.require(:poll_name)
+ status = params.require(:status)
+
+ DistributedMutex.synchronize("#{PLUGIN_NAME}-#{post_id}") do
+ post = Post.find(post_id)
+
+ # either staff member or OP
+ unless current_user.try(:staff?) || current_user.try(:id) == post.user_id
+ return render_json_error I18n.t("poll.only_staff_or_op_can_toggle_status")
+ end
+
+ # topic must be open
+ if post.topic.try(:closed) || post.topic.try(:archived)
+ return render_json_error I18n.t("poll.topic_must_be_open_to_toggle_status")
+ end
+
+ polls = post.custom_fields[POLLS_CUSTOM_FIELD]
+
+ return render_json_error I18n.t("poll.no_polls_associated_with_this_post") if polls.blank?
+ return render_json_error I18n.t("poll.no_poll_with_this_name", name: poll_name) if polls[poll_name].blank?
+
+ polls[poll_name]["status"] = status
+
+ post.custom_fields[POLLS_CUSTOM_FIELD] = polls
+ post.save_custom_fields
+
+ MessageBus.publish("/polls/#{post_id}", { poll: polls[poll_name] })
+
+ render json: { poll: polls[poll_name] }
+ end
+ end
+
+ end
+
+ DiscoursePoll::Engine.routes.draw do
+ put "/vote" => "polls#vote"
+ put "/toggle_status" => "polls#toggle_status"
end
Discourse::Application.routes.append do
- mount ::PollPlugin::Engine, at: '/poll'
+ mount ::DiscoursePoll::Engine, at: "/polls"
end
- # 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
- # it we need to raise an error.
Post.class_eval do
- validate :poll_options
- def poll_options
- poll = PollPlugin::Poll.new(self)
+ attr_accessor :polls
- return unless poll.is_poll?
+ # save the polls when the post is created
+ after_save do
+ next if self.polls.blank? || !self.polls.is_a?(Hash)
- if poll.options.length == 0
- self.errors.add(:raw, I18n.t('poll.must_contain_poll_options'))
+ post = self
+ polls = self.polls
+
+ DistributedMutex.synchronize("#{PLUGIN_NAME}-#{post.id}") do
+ post.custom_fields[POLLS_CUSTOM_FIELD] = polls
+ post.save_custom_fields
+ end
+ end
+ end
+
+ DATA_PREFIX ||= "data-poll-".freeze
+ DEFAULT_POLL_NAME ||= "poll".freeze
+
+ validate(:post, :polls) do
+ # only care when raw has changed!
+ return unless self.raw_changed?
+
+ # TODO: we should fix the callback mess so that the cooked version is available
+ # in the validators instead of cooking twice
+ cooked = PrettyText.cook(self.raw, topic_id: self.topic_id)
+ parsed = Nokogiri::HTML(cooked)
+
+ polls = {}
+ extracted_polls = []
+
+ # extract polls
+ parsed.css("div.poll").each do |p|
+ poll = { "options" => [], "total_votes" => 0 }
+
+ # extract attributes
+ p.attributes.values.each do |attribute|
+ if attribute.name.start_with?(DATA_PREFIX)
+ poll[attribute.name[DATA_PREFIX.length..-1]] = attribute.value
+ end
end
- poll.ensure_can_be_edited!
+ # extract options
+ p.css("li[#{DATA_PREFIX}option-id]").each do |o|
+ option_id = o.attributes[DATA_PREFIX + "option-id"].value
+ poll["options"] << { "id" => option_id, "html" => o.inner_html, "votes" => 0 }
+ end
+
+ # add the poll
+ extracted_polls << poll
+ end
+
+ # validate polls
+ extracted_polls.each do |poll|
+ # polls should have a unique name
+ if polls.has_key?(poll["name"])
+ poll["name"] == DEFAULT_POLL_NAME ?
+ self.errors.add(:base, I18n.t("poll.multiple_polls_without_name")) :
+ self.errors.add(:base, I18n.t("poll.multiple_polls_with_same_name", name: poll["name"]))
+ return
+ end
+
+ # options must be unique
+ if poll["options"].map { |o| o["id"] }.uniq.size != poll["options"].size
+ poll["name"] == DEFAULT_POLL_NAME ?
+ self.errors.add(:base, I18n.t("poll.default_poll_must_have_different_options")) :
+ self.errors.add(:base, I18n.t("poll.named_poll_must_have_different_options", name: poll["name"]))
+ return
+ end
+
+ # at least 2 options
+ if poll["options"].size < 2
+ poll["name"] == DEFAULT_POLL_NAME ?
+ self.errors.add(:base, I18n.t("poll.default_poll_must_have_at_least_2_options")) :
+ self.errors.add(:base, I18n.t("poll.named_poll_must_have_at_least_2_options", name: poll["name"]))
+ return
+ end
+
+ # store the valid poll
+ polls[poll["name"]] = poll
+ end
+
+ # are we updating a post outside the 5-minute edit window?
+ if self.id.present? && self.created_at < 5.minutes.ago
+ post = self
+ DistributedMutex.synchronize("#{PLUGIN_NAME}-#{post.id}") do
+ # load previous polls
+ previous_polls = post.custom_fields[POLLS_CUSTOM_FIELD] || {}
+
+ # are the polls different?
+ if polls.keys != previous_polls.keys ||
+ polls.values.map { |p| p["options"] } != previous_polls.values.map { |p| p["options"] }
+
+ # cannot add/remove/change/re-order polls
+ if polls.keys != previous_polls.keys
+ post.errors.add(:base, I18n.t("poll.cannot_change_polls_after_5_minutes"))
+ return
+ end
+
+ # deal with option changes
+ if User.staff.pluck(:id).include?(post.last_editor_id)
+ # staff can only edit options
+ polls.each_key do |poll_name|
+ if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size
+ post.errors.add(:base, I18n.t("poll.staff_cannot_add_or_remove_options_after_5_minutes"))
+ return
+ end
+ end
+ # merge votes
+ polls.each_key do |poll_name|
+ polls[poll_name]["total_votes"] = previous_polls[poll_name]["total_votes"]
+ for o in 0...polls[poll_name]["options"].size
+ polls[poll_name]["options"][o]["votes"] = previous_polls[poll_name]["options"][o]["votes"]
+ end
+ end
+ else
+ # OP cannot change polls after 5 minutes
+ post.errors.add(:base, I18n.t("poll.cannot_change_polls_after_5_minutes"))
+ return
+ end
+ end
+
+ # immediately store the polls
+ post.custom_fields[POLLS_CUSTOM_FIELD] = polls
+ post.save_custom_fields
+ end
+ else
+ # polls will be saved once we have a post id
+ self.polls = polls
end
end
- # Save the list of options to PluginStore after the post is saved.
- Post.class_eval do
- after_save :save_poll_options_to_plugin_store
- def save_poll_options_to_plugin_store
- PollPlugin::Poll.new(self).update_options!
- end
+ Post.register_custom_field_type(POLLS_CUSTOM_FIELD, :json)
+ Post.register_custom_field_type("#{VOTES_CUSTOM_FIELD}-*", :json)
+
+ TopicView.add_post_custom_fields_whitelister do |user|
+ whitelisted = [POLLS_CUSTOM_FIELD]
+ whitelisted << "#{VOTES_CUSTOM_FIELD}-#{user.id}" if user
+ whitelisted
end
- # Add poll details into the post serializer.
- PostSerializer.class_eval do
- attributes :poll_details
- def poll_details
- PollPlugin::Poll.new(object).serialize(scope.user)
- end
- def include_poll_details?
- PollPlugin::Poll.new(object).has_poll_details?
- end
- end
+ add_to_serializer(:post, :polls, false) { post_custom_fields[POLLS_CUSTOM_FIELD] }
+ add_to_serializer(:post, :include_polls?) { post_custom_fields.present? && post_custom_fields[POLLS_CUSTOM_FIELD].present? }
+
+ add_to_serializer(:post, :polls_votes, false) { post_custom_fields["#{VOTES_CUSTOM_FIELD}-#{scope.user.id}"] }
+ add_to_serializer(:post, :include_polls_votes?) { scope.user && post_custom_fields.present? && post_custom_fields["#{VOTES_CUSTOM_FIELD}-#{scope.user.id}"].present? }
end
-
-# Poll UI.
-register_asset "javascripts/models/poll.js.es6"
-register_asset "javascripts/controllers/poll.js.es6"
-register_asset "javascripts/views/poll.js.es6"
-register_asset "javascripts/discourse/templates/poll.hbs"
-register_asset "javascripts/initializers/poll.js.es6"
-register_asset "javascripts/poll_bbcode.js", :server_side
-
-register_css < 1
-
- topic = @post.topic
-
- # Topic is not set in a couple of cases in the Discourse test suite.
- return false if topic.nil? || topic.user.nil?
-
- # New post, but not the first post in the topic.
- return false if @post.post_number.nil? && topic.highest_post_number > 0
-
- I18n.with_locale(topic.user.effective_locale) do
- topic.title =~ /^(#{I18n.t('poll.prefix').strip}|#{I18n.t('poll.closed_prefix').strip})\s?:/i
- end
- end
-
- def has_poll_details?
- self.is_poll?
- 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
- # not staff, tell them to contact one.
- @post.errors.add(:poll_options, I18n.t('poll.cannot_have_modified_options'))
- end
- end
-
- def is_closed?
- topic = @post.topic
- topic.closed? || topic.archived? || (topic.title =~ /^#{I18n.t('poll.closed_prefix', locale: topic.user.effective_locale)}/i) === 0
- end
-
- def options
- cooked = PrettyText.cook(@post.raw, topic_id: @post.topic_id)
- parsed = Nokogiri::HTML(cooked)
- poll_list = parsed.css(".poll-ui ul").first || parsed.css("ul").first
- if poll_list
- poll_list.css("li").map {|x| x.children.to_s.strip }.uniq
- else
- []
- 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
- @details ||= ::PluginStore.get("poll", details_key)
- end
-
- def set_details!(new_details)
- ::PluginStore.set("poll", details_key, new_details)
- @details = new_details
- end
-
- def get_vote(user)
- user.nil? ? nil : ::PluginStore.get("poll", vote_key(user))
- end
-
- def set_vote!(user, option)
- return if is_closed?
-
- # Get the user's current vote.
- DistributedMutex.new(details_key).synchronize do
- vote = get_vote(user)
- vote = nil unless details.keys.include? vote
-
- new_details = details.dup
- new_details[vote] -= 1 if vote
- new_details[option] += 1
-
- ::PluginStore.set("poll", vote_key(user), option)
- set_details! new_details
- end
- end
-
- def serialize(user)
- return nil if details.nil?
- {options: details, selected: get_vote(user), closed: is_closed?}
- end
-
- private
- def details_key
- "poll_options_#{@post.id}"
- end
-
- def vote_key_prefix
- "poll_vote_#{@post.id}_"
- end
-
- def vote_key(user)
- "#{vote_key_prefix}#{user.id}"
- end
- end
-end
diff --git a/plugins/poll/spec/controllers/polls_controller_spec.rb b/plugins/poll/spec/controllers/polls_controller_spec.rb
new file mode 100644
index 00000000000..360efc4575d
--- /dev/null
+++ b/plugins/poll/spec/controllers/polls_controller_spec.rb
@@ -0,0 +1,99 @@
+require "spec_helper"
+
+describe ::DiscoursePoll::PollsController do
+ routes { ::DiscoursePoll::Engine.routes }
+
+ let!(:user) { log_in }
+ let(:topic) { Fabricate(:topic) }
+ let(:poll) { Fabricate(:post, topic_id: topic.id, user_id: user.id, raw: "[poll]\n- A\n- B\n[/poll]") }
+
+ describe "#vote" do
+
+ it "works" do
+ MessageBus.expects(:publish)
+
+ xhr :put, :vote, { post_id: poll.id, poll_name: "poll", options: ["A"] }
+
+ expect(response).to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["poll"]["name"]).to eq("poll")
+ expect(json["poll"]["total_votes"]).to eq(1)
+ expect(json["vote"]).to eq(["A"])
+ end
+
+ it "supports vote changes" do
+ xhr :put, :vote, { post_id: poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"] }
+ expect(response).to be_success
+
+ xhr :put, :vote, { post_id: poll.id, poll_name: "poll", options: ["e89dec30bbd9bf50fabf6a05b4324edf"] }
+ expect(response).to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["poll"]["total_votes"]).to eq(1)
+ expect(json["poll"]["options"][0]["votes"]).to eq(0)
+ expect(json["poll"]["options"][1]["votes"]).to eq(1)
+ end
+
+ it "ensures topic is not closed" do
+ topic.update_attribute(:closed, true)
+ xhr :put, :vote, { post_id: poll.id, poll_name: "poll", options: ["A"] }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.topic_must_be_open_to_vote"))
+ end
+
+ it "ensures topic is not archived" do
+ topic.update_attribute(:archived, true)
+ xhr :put, :vote, { post_id: poll.id, poll_name: "poll", options: ["A"] }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.topic_must_be_open_to_vote"))
+ end
+
+ it "ensures polls are associated with the post" do
+ xhr :put, :vote, { post_id: Fabricate(:post).id, poll_name: "foobar", options: ["A"] }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.no_polls_associated_with_this_post"))
+ end
+
+ it "checks the name of the poll" do
+ xhr :put, :vote, { post_id: poll.id, poll_name: "foobar", options: ["A"] }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.no_poll_with_this_name", name: "foobar"))
+ end
+
+ it "ensures poll is open" do
+ closed_poll = Fabricate(:post, raw: "[poll status=closed]\n- A\n- B\n[/poll]")
+ xhr :put, :vote, { post_id: closed_poll.id, poll_name: "poll", options: ["A"] }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.poll_must_be_open_to_vote"))
+ end
+
+ end
+
+ describe "#toggle_status" do
+
+ it "works for OP" do
+ MessageBus.expects(:publish)
+
+ xhr :put, :toggle_status, { post_id: poll.id, poll_name: "poll", status: "closed" }
+ expect(response).to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["poll"]["status"]).to eq("closed")
+ end
+
+ it "works for staff" do
+ log_in(:moderator)
+ MessageBus.expects(:publish)
+
+ xhr :put, :toggle_status, { post_id: poll.id, poll_name: "poll", status: "closed" }
+ expect(response).to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["poll"]["status"]).to eq("closed")
+ end
+
+ end
+
+end
diff --git a/plugins/poll/spec/controllers/posts_controller_spec.rb b/plugins/poll/spec/controllers/posts_controller_spec.rb
new file mode 100644
index 00000000000..c36befe22f4
--- /dev/null
+++ b/plugins/poll/spec/controllers/posts_controller_spec.rb
@@ -0,0 +1,137 @@
+require "spec_helper"
+
+describe PostsController do
+ let!(:user) { log_in }
+ let!(:title) { "Testing Poll Plugin" }
+
+ describe "polls" do
+
+ it "works" do
+ xhr :post, :create, { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }
+ expect(response).to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["cooked"]).to match("data-poll-")
+ expect(json["polls"]["poll"]).to be
+ end
+
+ it "works on any post" do
+ post = Fabricate(:post)
+ xhr :post, :create, { topic_id: post.topic.id, raw: "[poll]\n- A\n- B\n[/poll]" }
+ expect(response).to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["cooked"]).to match("data-poll-")
+ expect(json["polls"]["poll"]).to be
+ end
+
+ it "should have different options" do
+ xhr :post, :create, { title: title, raw: "[poll]\n- A\n- A[/poll]" }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.default_poll_must_have_different_options"))
+ end
+
+ it "should have at least 2 options" do
+ xhr :post, :create, { title: title, raw: "[poll]\n- A[/poll]" }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.default_poll_must_have_at_least_2_options"))
+ end
+
+ describe "edit window" do
+
+ describe "within the first 5 minutes" do
+
+ let(:post_id) do
+ Timecop.freeze(3.minutes.ago) do
+ xhr :post, :create, { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }
+ ::JSON.parse(response.body)["id"]
+ end
+ end
+
+ it "can be changed" do
+ xhr :put, :update, { id: post_id, post: { raw: "[poll]\n- A\n- B\n- C\n[/poll]" } }
+ expect(response).to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["post"]["polls"]["poll"]["options"][2]["html"]).to eq("C")
+ end
+
+ end
+
+ describe "after the first 5 minutes" do
+
+ let(:post_id) do
+ Timecop.freeze(6.minutes.ago) do
+ xhr :post, :create, { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }
+ ::JSON.parse(response.body)["id"]
+ end
+ end
+
+ let(:new_raw) { "[poll]\n- A\n- C[/poll]" }
+
+ it "cannot be changed by OP" do
+ xhr :put, :update, { id: post_id, post: { raw: new_raw } }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.cannot_change_polls_after_5_minutes"))
+ end
+
+ it "can be edited by staff" do
+ log_in_user(Fabricate(:moderator))
+ xhr :put, :update, { id: post_id, post: { raw: new_raw } }
+ expect(response).to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["post"]["polls"]["poll"]["options"][1]["html"]).to eq("C")
+ end
+
+ end
+
+ end
+
+ end
+
+ describe "named polls" do
+
+ it "should have different options" do
+ xhr :post, :create, { title: title, raw: "[poll name=foo]\n- A\n- A[/poll]" }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.named_poll_must_have_different_options", name: "foo"))
+ end
+
+ it "should have at least 2 options" do
+ xhr :post, :create, { title: title, raw: "[poll name=foo]\n- A[/poll]" }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.named_poll_must_have_at_least_2_options", name: "foo"))
+ end
+
+ end
+
+ describe "multiple polls" do
+
+ it "works" do
+ xhr :post, :create, { title: title, raw: "[poll]\n- A\n- B\n[/poll]\n[poll name=foo]\n- A\n- B\n[/poll]" }
+ expect(response).to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["cooked"]).to match("data-poll-")
+ expect(json["polls"]["poll"]).to be
+ expect(json["polls"]["foo"]).to be
+ end
+
+ it "should have a name" do
+ xhr :post, :create, { title: title, raw: "[poll]\n- A\n- B\n[/poll]\n[poll]\n- A\n- B\n[/poll]" }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.multiple_polls_without_name"))
+ end
+
+ it "should have unique name" do
+ xhr :post, :create, { title: title, raw: "[poll name=foo]\n- A\n- B\n[/poll]\n[poll name=foo]\n- A\n- B\n[/poll]" }
+ expect(response).not_to be_success
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("poll.multiple_polls_with_same_name", name: "foo"))
+ end
+
+ end
+
+end
diff --git a/plugins/poll/spec/poll_plugin/poll_controller_spec.rb b/plugins/poll/spec/poll_plugin/poll_controller_spec.rb
deleted file mode 100644
index 8f912d84920..00000000000
--- a/plugins/poll/spec/poll_plugin/poll_controller_spec.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require 'spec_helper'
-
-describe PollPlugin::PollController, type: :controller do
- routes { PollPlugin::Engine.routes }
-
- let(:topic) { create_topic(title: "Poll: Chitoge vs Onodera") }
- let!(:post) { create_post(topic: topic, raw: "Pick one.\n\n[poll]\n* Chitoge\n* Onodera\n[/poll]") }
- let(:user1) { Fabricate(:user) }
- let(:user2) { Fabricate(:user) }
- let(:admin) { Fabricate(:admin) }
-
- describe 'vote' do
- it "returns 403 if no user is logged in" do
- xhr :put, :vote, post_id: post.id, option: "Chitoge"
- response.should be_forbidden
- end
-
- it "returns 400 if post_id or invalid option is not specified" do
- log_in_user user1
- xhr :put, :vote
- response.status.should eq(400)
- xhr :put, :vote, post_id: post.id
- response.status.should eq(400)
- xhr :put, :vote, option: "Chitoge"
- response.status.should eq(400)
- xhr :put, :vote, post_id: post.id, option: "Tsugumi"
- response.status.should eq(400)
- end
-
- it "returns 400 if post_id doesn't correspond to a poll post" do
- log_in_user user1
- post2 = create_post(topic: topic, raw: "Generic reply")
- xhr :put, :vote, post_id: post2.id, option: "Chitoge"
- end
-
- it "saves votes correctly" do
- MessageBus.expects(:publish).times(3)
-
- log_in_user user1
- xhr :put, :vote, post_id: post.id, option: "Chitoge"
- PollPlugin::Poll.new(post).get_vote(user1).should eq("Chitoge")
-
- log_in_user user2
- xhr :put, :vote, post_id: post.id, option: "Onodera"
- PollPlugin::Poll.new(post).get_vote(user2).should eq("Onodera")
-
- PollPlugin::Poll.new(post).details["Chitoge"].should eq(1)
- PollPlugin::Poll.new(post).details["Onodera"].should eq(1)
-
- xhr :put, :vote, post_id: post.id, option: "Chitoge"
- PollPlugin::Poll.new(post).get_vote(user2).should eq("Chitoge")
-
- PollPlugin::Poll.new(post).details["Chitoge"].should eq(2)
- PollPlugin::Poll.new(post).details["Onodera"].should eq(0)
- end
- end
-
- describe 'toggle_close' do
- it "returns 400 if post_id doesn't correspond to a poll post" do
- log_in_user admin
- post2 = create_post(topic: topic, raw: "Generic reply")
- xhr :put, :toggle_close, post_id: post2.id
- response.status.should eq(400)
- end
-
- it "returns 400 if the topic is locked" do
- log_in_user admin
- topic.update_attributes closed: true
- xhr :put, :toggle_close, post_id: post.id
- response.status.should eq(400)
- end
-
- it "raises Discourse::InvalidAccess is the user is not authorized" do
- log_in_user user1
- expect do
- xhr :put, :toggle_close, post_id: post.id
- end.to raise_error(Discourse::InvalidAccess)
- end
-
- it "renames the topic" do
- I18n.stubs(:t).with('poll.prefix').returns("Poll ")
- I18n.stubs(:t).with('poll.closed_prefix').returns("Closed Poll ")
- log_in_user admin
- xhr :put, :toggle_close, post_id: post.id
- response.status.should eq(200)
- topic.reload.title.should == "Closed Poll : Chitoge vs Onodera"
- xhr :put, :toggle_close, post_id: post.id
- response.status.should eq(200)
- topic.reload.title.should == "Poll : Chitoge vs Onodera"
- end
- end
-end
diff --git a/plugins/poll/spec/poll_plugin/poll_spec.rb b/plugins/poll/spec/poll_plugin/poll_spec.rb
deleted file mode 100644
index 7455f8856a1..00000000000
--- a/plugins/poll/spec/poll_plugin/poll_spec.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-require 'spec_helper'
-
-describe PollPlugin::Poll do
- let(:topic) { create_topic(title: "Poll: Chitoge vs Onodera") }
- let(:post) { create_post(topic: topic, raw: "Pick one.\n\n[poll]\n* Chitoge\n* Onodera\n[/poll]") }
- let(:poll) { PollPlugin::Poll.new(post) }
- let(:user) { Fabricate(:user) }
-
- it "should detect poll post correctly" do
- expect(poll.is_poll?).to be_truthy
- post2 = create_post(topic: topic, raw: "This is a generic reply.")
- expect(PollPlugin::Poll.new(post2).is_poll?).to be_falsey
- post.topic.title = "Not a poll"
- expect(poll.is_poll?).to be_falsey
- end
-
- it "strips whitespace from the prefix translation" do
- topic.title = "Polll: This might be a poll"
- topic.save
- expect(PollPlugin::Poll.new(post).is_poll?).to be_falsey
- I18n.expects(:t).with('poll.prefix').returns("Polll ")
- I18n.expects(:t).with('poll.closed_prefix').returns("Closed Poll ")
- expect(PollPlugin::Poll.new(post).is_poll?).to be_truthy
- end
-
- it "should get options correctly" do
- expect(poll.options).to eq(["Chitoge", "Onodera"])
- end
-
- it "should fall back to using the first list if [poll] markup is not present" do
- topic = create_topic(title: "This is not a poll topic")
- post = create_post(topic: topic, raw: "Pick one.\n\n* Chitoge\n* Onodera")
- poll = PollPlugin::Poll.new(post)
- expect(poll.options).to eq(["Chitoge", "Onodera"])
- end
-
- it "should get details correctly" do
- expect(poll.details).to eq({"Chitoge" => 0, "Onodera" => 0})
- end
-
- it "should set details correctly" do
- poll.set_details!({})
- poll.details.should eq({})
- PollPlugin::Poll.new(post).details.should eq({})
- end
-
- it "should get and set votes correctly" do
- poll.get_vote(user).should eq(nil)
- poll.set_vote!(user, "Onodera")
- poll.get_vote(user).should eq("Onodera")
- poll.details["Onodera"].should eq(1)
- 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
- poll.serialize(user).should eq({options: poll.details, selected: nil, closed: false})
- poll.set_vote!(user, "Onodera")
- poll.serialize(user).should eq({options: poll.details, selected: "Onodera", closed: false})
- poll.serialize(nil).should eq({options: poll.details, selected: nil, closed: false})
-
- topic.title = "Closed Poll: my poll"
- topic.save
-
- post.topic.reload
- poll = PollPlugin::Poll.new(post)
- poll.serialize(nil).should eq({options: poll.details, selected: nil, closed: true})
- end
-
- it "should serialize to nil if there are no poll options" do
- topic = create_topic(title: "This is not a poll topic")
- post = create_post(topic: topic, raw: "no options in the content")
- poll = PollPlugin::Poll.new(post)
- poll.serialize(user).should eq(nil)
- 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
diff --git a/plugins/poll/spec/post_creator_spec.rb b/plugins/poll/spec/post_creator_spec.rb
deleted file mode 100644
index d409bc8731a..00000000000
--- a/plugins/poll/spec/post_creator_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'spec_helper'
-require 'post_creator'
-
-describe PostCreator do
- let(:user) { Fabricate(:user) }
- let(:admin) { Fabricate(:admin) }
-
- 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
- post = PostCreator.create(user, {title: "Poll: This is a poll", raw: "body does not contain a list"})
- post.errors[:raw].should be_present
- end
-
- it "cannot have options changed after 5 minutes" do
- poll_post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n[/poll]"
- poll_post.valid?.should == true
- poll_post.save
- Timecop.freeze(Time.now + 6.minutes) do
- poll_post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n* option 4\n[/poll]"
- poll_post.valid?.should == false
- 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 == true
- poll_post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n[/poll]"
- poll_post.valid?.should == false
- end
- end
- end
-end
diff --git a/spec/components/concern/has_custom_fields_spec.rb b/spec/components/concern/has_custom_fields_spec.rb
index aa36db51099..48ef6110088 100644
--- a/spec/components/concern/has_custom_fields_spec.rb
+++ b/spec/components/concern/has_custom_fields_spec.rb
@@ -5,7 +5,6 @@ describe HasCustomFields do
context "custom_fields" do
before do
-
Topic.exec_sql("create temporary table custom_fields_test_items(id SERIAL primary key)")
Topic.exec_sql("create temporary table custom_fields_test_item_custom_fields(id SERIAL primary key, custom_fields_test_item_id int, name varchar(256) not null, value text)")
@@ -85,10 +84,9 @@ describe HasCustomFields do
# refresh loads from database
expect(test_item.reload.custom_fields["a"]).to eq("1")
expect(test_item.custom_fields["a"]).to eq("1")
-
end
- it "double save actually saves" do
+ it "double save actually saves" do
test_item = CustomFieldsTestItem.new
test_item.custom_fields = {"a" => "b"}
test_item.save
@@ -98,12 +96,9 @@ describe HasCustomFields do
db_item = CustomFieldsTestItem.find(test_item.id)
expect(db_item.custom_fields).to eq({"a" => "b", "c" => "d"})
-
end
-
it "handles arrays properly" do
-
test_item = CustomFieldsTestItem.new
test_item.custom_fields = {"a" => ["b", "c", "d"]}
test_item.save
@@ -125,11 +120,9 @@ describe HasCustomFields do
db_item.custom_fields.delete('a')
expect(db_item.custom_fields).to eq({})
-
end
it "casts integers in arrays properly without error" do
-
test_item = CustomFieldsTestItem.new
test_item.custom_fields = {"a" => ["b", 10, "d"]}
test_item.save
@@ -137,19 +130,19 @@ describe HasCustomFields do
db_item = CustomFieldsTestItem.find(test_item.id)
expect(db_item.custom_fields).to eq({"a" => ["b", "10", "d"]})
-
end
it "supportes type coersion" do
test_item = CustomFieldsTestItem.new
CustomFieldsTestItem.register_custom_field_type("bool", :boolean)
CustomFieldsTestItem.register_custom_field_type("int", :integer)
+ CustomFieldsTestItem.register_custom_field_type("json", :json)
- test_item.custom_fields = {"bool" => true, "int" => 1}
+ test_item.custom_fields = {"bool" => true, "int" => 1, "json" => { "foo" => "bar" }}
test_item.save
test_item.reload
- expect(test_item.custom_fields).to eq({"bool" => true, "int" => 1})
+ expect(test_item.custom_fields).to eq({"bool" => true, "int" => 1, "json" => { "foo" => "bar" }})
end
it "simple modifications don't interfere" do
diff --git a/spec/components/cooked_post_processor_spec.rb b/spec/components/cooked_post_processor_spec.rb
index 02d50f82fce..e9b70107cab 100644
--- a/spec/components/cooked_post_processor_spec.rb
+++ b/spec/components/cooked_post_processor_spec.rb
@@ -51,7 +51,7 @@ describe CookedPostProcessor do
end
context "with image_sizes" do
- let(:post) { build(:post_with_image_urls) }
+ let(:post) { Fabricate(:post_with_image_urls) }
let(:cpp) { CookedPostProcessor.new(post, image_sizes: image_sizes) }
before { cpp.post_process_images }
@@ -87,7 +87,7 @@ describe CookedPostProcessor do
context "with unsized images" do
- let(:post) { build(:post_with_unsized_images) }
+ let(:post) { Fabricate(:post_with_unsized_images) }
let(:cpp) { CookedPostProcessor.new(post) }
it "adds the width and height to images that don't have them" do
@@ -102,7 +102,7 @@ describe CookedPostProcessor do
context "with large images" do
let(:upload) { Fabricate(:upload) }
- let(:post) { build(:post_with_large_image) }
+ let(:post) { Fabricate(:post_with_large_image) }
let(:cpp) { CookedPostProcessor.new(post) }
before do
@@ -129,7 +129,7 @@ describe CookedPostProcessor do
context "with title" do
let(:upload) { Fabricate(:upload) }
- let(:post) { build(:post_with_large_image_and_title) }
+ let(:post) { Fabricate(:post_with_large_image_and_title) }
let(:cpp) { CookedPostProcessor.new(post) }
before do
diff --git a/spec/components/post_creator_spec.rb b/spec/components/post_creator_spec.rb
index bc7137c7037..411e8d71551 100644
--- a/spec/components/post_creator_spec.rb
+++ b/spec/components/post_creator_spec.rb
@@ -62,6 +62,14 @@ describe PostCreator do
expect(creator.spam?).to eq(false)
end
+ it "triggers extensibility events" do
+ DiscourseEvent.expects(:trigger).with(:before_create_post, anything).once
+ DiscourseEvent.expects(:trigger).with(:validate_post, anything).once
+ DiscourseEvent.expects(:trigger).with(:topic_created, anything, anything, user).once
+ DiscourseEvent.expects(:trigger).with(:post_created, anything, anything, user).once
+ creator.create
+ end
+
it "does not notify on system messages" do
admin = Fabricate(:admin)
messages = MessageBus.track_publish do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index d3d3001711b..c857c519e25 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -111,7 +111,7 @@ describe User do
@user.delete_all_posts!(@guardian)
expect(Post.where(id: @posts.map(&:id))).to be_empty
@posts.each do |p|
- if p.post_number == 1
+ if p.is_first_post?
expect(Topic.find_by(id: p.topic_id)).to be_nil
end
end