mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 15:48:43 -05:00
Merge branch 'master' of github.com:discourse/discourse
This commit is contained in:
commit
8bea398cc1
24 changed files with 328 additions and 124 deletions
|
@ -67,7 +67,7 @@
|
|||
<h3>{{title}}</h3>
|
||||
{{/if}}
|
||||
<h3>
|
||||
{{#if location}}{{fa-icon "map-maker"}}{{location}}{{/if}}
|
||||
{{#if location}}{{fa-icon "map-marker"}}{{location}}{{/if}}
|
||||
{{#if websiteName}}
|
||||
{{fa-icon "globe"}}
|
||||
{{#if linkWebsite}}
|
||||
|
|
|
@ -3,7 +3,7 @@ require_dependency 'email/message_builder'
|
|||
class RejectionMailer < ActionMailer::Base
|
||||
include Email::BuildEmailHelper
|
||||
|
||||
DISALLOWED_TEMPLATE_ARGS = [:to, :from, :site_name, :base_url,
|
||||
DISALLOWED_TEMPLATE_ARGS = [:to, :from, :base_url,
|
||||
:user_preferences_url,
|
||||
:include_respond_instructions, :html_override,
|
||||
:add_unsubscribe_link, :respond_instructions,
|
||||
|
|
|
@ -850,11 +850,13 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def send_approval_email
|
||||
Jobs.enqueue(:user_email,
|
||||
type: :signup_after_approval,
|
||||
user_id: id,
|
||||
email_token: email_tokens.first.token
|
||||
)
|
||||
if SiteSetting.must_approve_users
|
||||
Jobs.enqueue(:user_email,
|
||||
type: :signup_after_approval,
|
||||
user_id: id,
|
||||
email_token: email_tokens.first.token
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def set_default_email_digest
|
||||
|
@ -887,7 +889,7 @@ class User < ActiveRecord::Base
|
|||
to_destroy.each do |u|
|
||||
begin
|
||||
destroyer.destroy(u, context: I18n.t(:purge_reason))
|
||||
rescue Discourse::InvalidAccess
|
||||
rescue Discourse::InvalidAccess, UserDestroyer::PostsExistError
|
||||
# if for some reason the user can't be deleted, continue on to the next one
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,9 +36,10 @@
|
|||
<%=form_tag({}, method: :put) do %>
|
||||
<p>
|
||||
<span style="display: none;"><input name="username" type="text" value="<%= @user.username %>"></span>
|
||||
<input id="user_password" name="password" size="30" type="password" maxlength="<%= User.max_password_length %>">
|
||||
<input id="user_password" name="password" size="30" type="password" maxlength="<%= User.max_password_length %>" onkeypress="capsLock(event)">
|
||||
<label><%= t('js.user.password.instructions', count: SiteSetting.min_password_length) %></label>
|
||||
</p>
|
||||
<div id="capsLockWarning" class="caps-lock-warning" style="visibility:hidden"><i class="fa fa-exclamation-triangle"></i> <%= t 'js.login.caps_lock_warning' %></div>
|
||||
<p>
|
||||
<%=submit_tag( @user.has_password? ? t('password_reset.update') : t('password_reset.save'), class: 'btn')%>
|
||||
</p>
|
||||
|
@ -48,5 +49,11 @@
|
|||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.getElementById('user_password').focus()
|
||||
document.getElementById('user_password').focus();
|
||||
|
||||
function capsLock(e) {
|
||||
kc = e.keyCode?e.keyCode:e.which;
|
||||
sk = e.shiftKey?e.shiftKey:((kc == 16)?true:false);
|
||||
(((kc >= 65 && kc <= 90) && !sk)||((kc >= 97 && kc <= 122) && sk)) ? document.getElementById('capsLockWarning').style.visibility = 'visible' : document.getElementById('capsLockWarning').style.visibility = 'hidden';
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -375,7 +375,7 @@ en:
|
|||
dismiss_notifications: "Mark all as Read"
|
||||
dismiss_notifications_tooltip: "Mark all unread notifications as read"
|
||||
disable_jump_reply: "Don't jump to my post after I reply"
|
||||
dynamic_favicon: "Show incoming message notifications on favicon (experimental)"
|
||||
dynamic_favicon: "Show new / updated topic count on browser icon"
|
||||
edit_history_public: "Let other users view my post revisions"
|
||||
external_links_in_new_tab: "Open all external links in a new tab"
|
||||
enable_quoting: "Enable quote reply for highlighted text"
|
||||
|
|
|
@ -823,7 +823,7 @@ en:
|
|||
|
||||
traditional_markdown_linebreaks: "Use traditional linebreaks in Markdown, which require two trailing spaces for a linebreak."
|
||||
post_undo_action_window_mins: "Number of minutes users are allowed to undo recent actions on a post (like, flag, etc)."
|
||||
must_approve_users: "Staff must approve all new user accounts before they are allowed to access the site."
|
||||
must_approve_users: "Staff must approve all new user accounts before they are allowed to access the site. WARNING: enabling this for a live site will revoke access for existing non-staff users!"
|
||||
ga_tracking_code: "Google analytics (ga.js) tracking code code, eg: UA-12345678-9; see http://google.com/analytics"
|
||||
ga_domain_name: "Google analytics (ga.js) domain name, eg: mysite.com; see http://google.com/analytics"
|
||||
ga_universal_tracking_code: "Google Universal Analytics (analytics.js) tracking code code, eg: UA-12345678-9; see http://google.com/analytics"
|
||||
|
@ -1443,7 +1443,7 @@ en:
|
|||
|
||||
%{flag_reason}
|
||||
|
||||
Multiple community members flagged this post before it was hidden, so please consider how you might revise your post to reflect their feedback. **You can edit your post after %{edit_delay} minutes, and it will be automatically unhidden.** This will increase your trust level.
|
||||
Multiple community members flagged this post before it was hidden, so please consider how you might revise your post to reflect their feedback. **You can edit your post after %{edit_delay} minutes, and it will be automatically unhidden.**
|
||||
|
||||
However, if the post is hidden by the community a second time, it will remain hidden until handled by staff – and there may be further action, including the possible suspension of your account.
|
||||
|
||||
|
|
|
@ -50,6 +50,9 @@ class NewPostManager
|
|||
|
||||
def perform
|
||||
|
||||
# We never queue private messages
|
||||
return perform_create_post if @args[:archetype] == Archetype.private_message
|
||||
|
||||
# Perform handlers until one returns a result
|
||||
handled = NewPostManager.handlers.any? do |handler|
|
||||
result = handler.call(self)
|
||||
|
|
|
@ -217,9 +217,6 @@ class Plugin::Instance
|
|||
DiscoursePluginRegistry.register_glob(root_path, 'hbs')
|
||||
end
|
||||
|
||||
# Automatically include all rake tasks
|
||||
Rake.add_rakelib "#{File.dirname(path)}/lib/tasks"
|
||||
|
||||
self.instance_eval File.read(path), path
|
||||
if auto_assets = generate_automatic_assets!
|
||||
assets.concat auto_assets.map{|a| [a]}
|
||||
|
@ -227,10 +224,18 @@ class Plugin::Instance
|
|||
|
||||
register_assets! unless assets.blank?
|
||||
|
||||
# TODO possibly amend this to a rails engine
|
||||
# TODO: possibly amend this to a rails engine
|
||||
|
||||
# Automatically include assets
|
||||
Rails.configuration.assets.paths << auto_generated_path
|
||||
Rails.configuration.assets.paths << File.dirname(path) + "/assets"
|
||||
|
||||
# Automatically include rake tasks
|
||||
Rake.add_rakelib(File.dirname(path) + "/lib/tasks")
|
||||
|
||||
# Automatically include migrations
|
||||
Rails.configuration.paths["db/migrate"] << File.dirname(path) + "/db/migrate"
|
||||
|
||||
public_data = File.dirname(path) + "/public"
|
||||
if Dir.exists?(public_data)
|
||||
target = Rails.root.to_s + "/public/plugins/"
|
||||
|
|
|
@ -12,9 +12,9 @@ export default Em.Component.extend({
|
|||
}.property("poll.options.@each.{html,votes}"),
|
||||
|
||||
average: function() {
|
||||
const total_votes = this.get("poll.total_votes");
|
||||
return total_votes == 0 ? 0 : round(this.get("totalScore") / total_votes, -2);
|
||||
}.property("totalScore", "poll.total_votes"),
|
||||
const voters = this.get("poll.voters");
|
||||
return voters === 0 ? 0 : round(this.get("totalScore") / voters, -2);
|
||||
}.property("totalScore", "poll.voters"),
|
||||
|
||||
averageRating: function() {
|
||||
return I18n.t("poll.average_rating", { average: this.get("average") });
|
||||
|
|
|
@ -3,11 +3,11 @@ export default Em.Component.extend({
|
|||
classNames: ["results"],
|
||||
|
||||
options: function() {
|
||||
const totalVotes = this.get("poll.total_votes"),
|
||||
const voters = this.get("poll.voters"),
|
||||
backgroundColor = this.get("poll.background");
|
||||
|
||||
this.get("poll.options").forEach(option => {
|
||||
const percentage = totalVotes == 0 ? 0 : Math.floor(100 * option.get("votes") / totalVotes),
|
||||
const percentage = voters === 0 ? 0 : Math.floor(100 * option.get("votes") / voters),
|
||||
styles = ["width: " + percentage + "%"];
|
||||
|
||||
if (backgroundColor) { styles.push("background: " + backgroundColor); }
|
||||
|
@ -20,6 +20,6 @@ export default Em.Component.extend({
|
|||
});
|
||||
|
||||
return this.get("poll.options");
|
||||
}.property("poll.total_votes", "poll.options.[]")
|
||||
}.property("poll.voters", "poll.options.[]")
|
||||
|
||||
});
|
||||
|
|
|
@ -4,16 +4,13 @@ export default Em.Controller.extend({
|
|||
isRandom : Em.computed.equal("poll.order", "random"),
|
||||
isClosed: Em.computed.equal("poll.status", "closed"),
|
||||
|
||||
// immediately shows the results when the user has already voted
|
||||
showResults: Em.computed.gt("vote.length", 0),
|
||||
|
||||
// shows the results when
|
||||
// - poll is closed
|
||||
// - topic is archived/closed
|
||||
// - user wants to see the results
|
||||
showingResults: Em.computed.or("isClosed", "post.topic.closed", "post.topic.archived", "showResults"),
|
||||
|
||||
showResultsDisabled: Em.computed.equal("poll.total_votes", 0),
|
||||
showResultsDisabled: Em.computed.equal("poll.voters", 0),
|
||||
hideResultsDisabled: Em.computed.alias("isClosed"),
|
||||
|
||||
poll: function() {
|
||||
|
@ -37,10 +34,6 @@ export default Em.Controller.extend({
|
|||
return _.map(this.get("poll.options").filterBy("selected"), o => o.get("id"));
|
||||
}.property("poll.options.@each.selected"),
|
||||
|
||||
totalVotesText: function() {
|
||||
return I18n.t("poll.total_votes", { count: this.get("poll.total_votes") });
|
||||
}.property("poll.total_votes"),
|
||||
|
||||
min: function() {
|
||||
let min = parseInt(this.get("poll.min"), 10);
|
||||
if (isNaN(min) || min < 1) { min = 1; }
|
||||
|
@ -54,6 +47,20 @@ export default Em.Controller.extend({
|
|||
return max;
|
||||
}.property("poll.max", "poll.options.length"),
|
||||
|
||||
votersText: function() {
|
||||
return I18n.t("poll.voters", { count: this.get("poll.voters") });
|
||||
}.property("poll.voters"),
|
||||
|
||||
totalVotes: function() {
|
||||
return _.reduce(this.get("poll.options"), function(total, o) {
|
||||
return total + parseInt(o.get("votes"), 10);
|
||||
}, 0);
|
||||
}.property("poll.options.@each.votes"),
|
||||
|
||||
totalVotesText: function() {
|
||||
return I18n.t("poll.total_votes", { count: this.get("totalVotes") });
|
||||
}.property("totalVotes"),
|
||||
|
||||
multipleHelpText: function() {
|
||||
const options = this.get("poll.options.length"),
|
||||
min = this.get("min"),
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<tbody>
|
||||
{{#each option in options}}
|
||||
<tr>
|
||||
<td class="option">{{{option.html}}}</td>
|
||||
<td class="percentage">{{option.percentage}}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="bar-back">
|
||||
<div class="bar" {{bind-attr style=option.style}}></div>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
{{#each option in options}}
|
||||
<tr>
|
||||
<td class="option">{{{option.html}}}</td>
|
||||
<td class="percentage">{{option.percentage}}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="bar-back">
|
||||
<div class="bar" {{bind-attr style=option.style}}></div>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
|
|
@ -1,36 +1,53 @@
|
|||
<div class="poll-container">
|
||||
{{#if showingResults}}
|
||||
{{#if isNumber}}
|
||||
{{poll-results-number poll=poll}}
|
||||
{{else}}
|
||||
{{poll-results-standard poll=poll}}
|
||||
<div>
|
||||
<div class="poll-info">
|
||||
<p>
|
||||
<span class="info-number">{{poll.voters}}</span>
|
||||
<span class="info-text">{{votersText}}</span>
|
||||
</p>
|
||||
{{#if isMultiple}}
|
||||
{{#if showingResults}}
|
||||
<p>
|
||||
<span class="info-number">{{totalVotes}}</span>
|
||||
<span class="info-text">{{totalVotesText}}</span>
|
||||
</p>
|
||||
{{else}}
|
||||
<p>{{{multipleHelpText}}}</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<ul>
|
||||
{{#each option in poll.options}}
|
||||
{{poll-option option=option color=poll.color background=poll.background toggle="toggleOption"}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="poll-container">
|
||||
{{#if showingResults}}
|
||||
{{#if isNumber}}
|
||||
{{poll-results-number poll=poll}}
|
||||
{{else}}
|
||||
{{poll-results-standard poll=poll}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<ul>
|
||||
{{#each option in poll.options}}
|
||||
{{poll-option option=option color=poll.color background=poll.background toggle="toggleOption"}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>{{totalVotesText}}</p>
|
||||
|
||||
{{#if isMultiple}}
|
||||
<p>{{multipleHelpText}}</p>
|
||||
{{d-button class="cast-votes" title="poll.cast-votes.title" label="poll.cast-votes.label" disabled=castVotesDisabled action="castVotes"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showingResults}}
|
||||
{{d-button class="toggle-results" title="poll.hide-results.title" label="poll.hide-results.label" icon="eye-slash" disabled=hideResultsDisabled action="toggleResults"}}
|
||||
{{else}}
|
||||
{{d-button class="toggle-results" title="poll.show-results.title" label="poll.show-results.label" icon="eye" disabled=showResultsDisabled action="toggleResults"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if canToggleStatus}}
|
||||
{{#if isClosed}}
|
||||
{{d-button class="toggle-status" title="poll.open.title" label="poll.open.label" icon="unlock-alt" action="toggleStatus"}}
|
||||
{{else}}
|
||||
{{d-button class="toggle-status btn-danger" title="poll.close.title" label="poll.close.label" icon="lock" action="toggleStatus"}}
|
||||
<div class="poll-buttons">
|
||||
{{#if isMultiple}}
|
||||
{{d-button class="cast-votes" title="poll.cast-votes.title" label="poll.cast-votes.label" disabled=castVotesDisabled action="castVotes"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showingResults}}
|
||||
{{d-button class="toggle-results" title="poll.hide-results.title" label="poll.hide-results.label" icon="eye-slash" disabled=hideResultsDisabled action="toggleResults"}}
|
||||
{{else}}
|
||||
{{d-button class="toggle-results" title="poll.show-results.title" label="poll.show-results.label" icon="eye" disabled=showResultsDisabled action="toggleResults"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if canToggleStatus}}
|
||||
{{#if isClosed}}
|
||||
{{d-button class="toggle-status" title="poll.open.title" label="poll.open.label" icon="unlock-alt" action="toggleStatus"}}
|
||||
{{else}}
|
||||
{{d-button class="toggle-status btn-danger" title="poll.close.title" label="poll.close.label" icon="lock" action="toggleStatus"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -97,13 +97,25 @@
|
|||
contents[0][o].splice(1, 0, attr);
|
||||
}
|
||||
|
||||
// that's our poll!
|
||||
var result = ["div", attributes].concat(contents);
|
||||
// // add some information when type is "multiple"
|
||||
// if (attributes[DATA_PREFIX + "type"] === "multiple") {
|
||||
|
||||
// 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"
|
||||
// }
|
||||
|
||||
var result = ["div", attributes],
|
||||
poll = ["div"];
|
||||
|
||||
// 1 - POLL INFO
|
||||
var info = ["div", { "class": "poll-info" }];
|
||||
|
||||
// # of voters
|
||||
info.push(["p",
|
||||
["span", { "class": "info-number" }, "0"],
|
||||
["span", { "class": "info-text"}, I18n.t("poll.voters", { count: 0 })]
|
||||
]);
|
||||
|
||||
// multiple help text
|
||||
if (attributes[DATA_PREFIX + "type"] === "multiple") {
|
||||
var optionCount = contents[0].length - 1;
|
||||
|
||||
|
@ -130,22 +142,40 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (help) { result.push(["p", help]); }
|
||||
if (help) { info.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")]);
|
||||
poll.push(info);
|
||||
|
||||
// 2 - POLL CONTAINER
|
||||
var container = ["div", { "class": "poll-container" }].concat(contents);
|
||||
poll.push(container);
|
||||
|
||||
// 3 - BUTTONS
|
||||
var buttons = ["div", { "class": "poll-buttons" }];
|
||||
|
||||
// add "cast-votes" button
|
||||
if (attributes[DATA_PREFIX + "type"] === "multiple") {
|
||||
buttons.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")]);
|
||||
buttons.push(["a", { "class": "button toggle-results", "title": I18n.t("poll.show-results.title") }, I18n.t("poll.show-results.label")]);
|
||||
|
||||
// 4 - MIX IT ALL UP
|
||||
result.push(poll);
|
||||
result.push(buttons)
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
Discourse.Markdown.whiteListTag("div", "class", "poll");
|
||||
Discourse.Markdown.whiteListTag("div", "class", /^poll-(info|container|buttons)/);
|
||||
Discourse.Markdown.whiteListTag("div", "data-*");
|
||||
|
||||
Discourse.Markdown.whiteListTag("span", "class", /^info-(number|text)/);
|
||||
|
||||
Discourse.Markdown.whiteListTag("a", "class", /^button (cast-votes|toggle-results)/);
|
||||
|
||||
Discourse.Markdown.whiteListTag("li", "data-*");
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
$border-color: rgb(219,219,219);
|
||||
$text-color: #9E9E9E;
|
||||
|
||||
$option-foreground: $primary;
|
||||
$option-background: dark-light-diff($primary, $secondary, 90%, -65%);
|
||||
$option-shadow: dark-light-diff($option-background, $primary, 10%, -10%);
|
||||
|
||||
div.poll {
|
||||
|
||||
display: table;
|
||||
border: 1px solid $border-color;
|
||||
width: 500px;
|
||||
|
||||
ul, ol {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
max-width: 90%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
li, .option {
|
||||
|
@ -15,31 +26,29 @@ div.poll {
|
|||
}
|
||||
|
||||
li[data-poll-option-id] {
|
||||
color: $secondary;
|
||||
background: $primary;
|
||||
position: relative;
|
||||
color: $option-foreground;
|
||||
background: $option-background;
|
||||
box-shadow: 0 6px $option-shadow;
|
||||
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);
|
||||
border-radius: 4px;
|
||||
|
||||
&: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);
|
||||
top: 2px;
|
||||
box-shadow: 0 4px $option-shadow;
|
||||
}
|
||||
|
||||
&: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);
|
||||
top: 6px;
|
||||
box-shadow: 0 0 $option-shadow;
|
||||
}
|
||||
|
||||
&[data-poll-selected="selected"] {
|
||||
background: green !important;
|
||||
background: $success !important;
|
||||
box-shadow: 0 6px darken($success, 10%);
|
||||
&:hover { box-shadow: 0 4px darken($success, 10%); }
|
||||
&:active { box-shadow: 0 0 darken($success, 10%); }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,11 +67,45 @@ div.poll {
|
|||
}
|
||||
}
|
||||
|
||||
.poll-info {
|
||||
color: $text-color;
|
||||
width: 150px;
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
border-right: 1px solid $border-color;
|
||||
|
||||
p {
|
||||
margin: 40px 20px;
|
||||
}
|
||||
|
||||
.info-number {
|
||||
font-size: 3.5em;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
display: block;
|
||||
font-size: 1.7em;
|
||||
}
|
||||
}
|
||||
|
||||
.poll-container {
|
||||
margin: 0;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
padding: 10px;
|
||||
width: 330px;
|
||||
|
||||
span {
|
||||
font-size: 1.125em;
|
||||
line-height: 2
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.poll-buttons {
|
||||
border-top: 1px solid $border-color;
|
||||
padding: 10px;
|
||||
|
||||
.toggle-status {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,16 +123,16 @@ div.poll {
|
|||
font-size: 1.7em;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
color: #9E9E9E;
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
.bar-back {
|
||||
background: rgb(219,219,219);
|
||||
background: $border-color;
|
||||
}
|
||||
|
||||
.bar {
|
||||
height: 10px;
|
||||
background: $primary;
|
||||
background: $option-foreground;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -98,7 +141,7 @@ div.poll {
|
|||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin-right: .7em;
|
||||
margin: 0 12px 15px 5px;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,19 +8,23 @@
|
|||
en:
|
||||
js:
|
||||
poll:
|
||||
voters:
|
||||
zero: "voters"
|
||||
one: "voter"
|
||||
other: "voters"
|
||||
total_votes:
|
||||
zero: "No votes yet. Want to be the first?"
|
||||
one: "There's only 1 vote."
|
||||
other: "There are %{count} total votes."
|
||||
zero: "total votes"
|
||||
one: "total vote"
|
||||
other: "total votes"
|
||||
|
||||
average_rating: "Average rating: <strong>%{average}</strong>."
|
||||
|
||||
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."
|
||||
at_least_min_options: "You may choose at least <strong>%{count}</strong> options."
|
||||
up_to_max_options: "You may choose up to <strong>%{count}</strong> options."
|
||||
x_options: "You may choose <strong>%{count}</strong> options."
|
||||
between_min_and_max_options: "You may choose between <strong>%{min}</strong> and <strong>%{max}</strong> options."
|
||||
|
||||
cast-votes:
|
||||
title: "Cast your votes"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
es:
|
||||
js:
|
||||
poll:
|
||||
total_votes:
|
||||
voters:
|
||||
zero: "Sin votos todavía. ¿Quieres ser el primero?"
|
||||
one: "Sólo hay 1 voto."
|
||||
other: "Hay %{count} votos en total."
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
fr:
|
||||
js:
|
||||
poll:
|
||||
total_votes:
|
||||
voters:
|
||||
zero: "Pas encore de votes. Soyez le premier !"
|
||||
one: "Il n'y a qu'un vote."
|
||||
other: "Il y a au total %{count} votes."
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
class RenameTotalVotesToVoters < ActiveRecord::Migration
|
||||
|
||||
def up
|
||||
PostCustomField.where(name: "polls").find_each do |pcf|
|
||||
polls = ::JSON.parse(pcf.value)
|
||||
polls.each_value do |poll|
|
||||
next if poll.has_key?("voters")
|
||||
poll["voters"] = poll["total_votes"]
|
||||
poll.delete("total_votes")
|
||||
end
|
||||
pcf.value = polls.to_json
|
||||
pcf.save
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
PostCustomField.where(name: "polls").find_each do |pcf|
|
||||
polls = ::JSON.parse(pcf.value)
|
||||
polls.each_value do |poll|
|
||||
next if poll.has_key?("total_votes")
|
||||
poll["total_votes"] = poll["voters"]
|
||||
poll.delete("voters")
|
||||
end
|
||||
pcf.value = polls.to_json
|
||||
pcf.save
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -57,7 +57,7 @@ after_initialize do
|
|||
vote = votes[poll_name] || []
|
||||
|
||||
# increment counters only when the user hasn't casted a vote yet
|
||||
poll["total_votes"] += 1 if vote.size == 0
|
||||
poll["voters"] += 1 if vote.size == 0
|
||||
|
||||
poll["options"].each do |option|
|
||||
option["votes"] -= 1 if vote.include?(option["id"])
|
||||
|
@ -116,7 +116,7 @@ after_initialize do
|
|||
|
||||
# extract polls
|
||||
parsed.css("div.poll").each do |p|
|
||||
poll = { "options" => [], "total_votes" => 0 }
|
||||
poll = { "options" => [], "voters" => 0 }
|
||||
|
||||
# extract attributes
|
||||
p.attributes.values.each do |attribute|
|
||||
|
@ -208,9 +208,10 @@ after_initialize do
|
|||
# only care when raw has changed!
|
||||
return unless self.raw_changed?
|
||||
|
||||
extracted_polls = DiscoursePoll::Poll::extract(self.raw, self.topic_id)
|
||||
polls = {}
|
||||
|
||||
extracted_polls = DiscoursePoll::Poll::extract(self.raw, self.topic_id)
|
||||
|
||||
extracted_polls.each do |poll|
|
||||
# polls should have a unique name
|
||||
if polls.has_key?(poll["name"])
|
||||
|
@ -288,7 +289,7 @@ after_initialize do
|
|||
next unless previous_polls.has_key?(poll_name)
|
||||
next unless polls[poll_name]["options"].size == previous_polls[poll_name]["options"].size
|
||||
|
||||
polls[poll_name]["total_votes"] = previous_polls[poll_name]["total_votes"]
|
||||
polls[poll_name]["voters"] = previous_polls[poll_name]["voters"]
|
||||
for o in 0...polls[poll_name]["options"].size
|
||||
polls[poll_name]["options"][o]["votes"] = previous_polls[poll_name]["options"][o]["votes"]
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ describe ::DiscoursePoll::PollsController do
|
|||
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["poll"]["voters"]).to eq(1)
|
||||
expect(json["vote"]).to eq(["5c24fc1df56d764b550ceae1b9319125"])
|
||||
end
|
||||
|
||||
|
@ -36,7 +36,7 @@ describe ::DiscoursePoll::PollsController do
|
|||
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"]["voters"]).to eq(1)
|
||||
expect(json["poll"]["options"][0]["votes"]).to eq(0)
|
||||
expect(json["poll"]["options"][1]["votes"]).to eq(1)
|
||||
end
|
||||
|
|
|
@ -17,6 +17,27 @@ describe NewPostManager do
|
|||
end
|
||||
end
|
||||
|
||||
context "default action" do
|
||||
let(:other_user) { Fabricate(:user) }
|
||||
|
||||
it "doesn't enqueue private messages" do
|
||||
manager = NewPostManager.new(topic.user,
|
||||
raw: 'this is a new post',
|
||||
title: 'this is a new title',
|
||||
archetype: Archetype.private_message,
|
||||
target_usernames: other_user.username)
|
||||
|
||||
SiteSetting.approve_unless_trust_level = 4
|
||||
result = manager.perform
|
||||
|
||||
expect(result.action).to eq(:create_post)
|
||||
expect(result).to be_success
|
||||
expect(result.post).to be_present
|
||||
expect(result.post.topic.private_message?).to eq(true)
|
||||
expect(result.post).to be_a(Post)
|
||||
end
|
||||
end
|
||||
|
||||
context "default handler" do
|
||||
let(:manager) { NewPostManager.new(topic.user, raw: 'this is new post content', topic_id: topic.id) }
|
||||
|
||||
|
@ -54,6 +75,7 @@ describe NewPostManager do
|
|||
expect(result.action).to eq(:enqueued)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "extensibility priority" do
|
||||
|
|
29
spec/mailers/rejection_mailer_spec.rb
Normal file
29
spec/mailers/rejection_mailer_spec.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe RejectionMailer do
|
||||
|
||||
describe "send_rejection" do
|
||||
|
||||
context 'sends rejection email' do
|
||||
let (:user) { Fabricate(:user) }
|
||||
let (:template_args) { {former_title: "Mail Subject", destination: user.email, site_name: SiteSetting.title} }
|
||||
let (:reject_mail) { RejectionMailer.send_rejection("email_reject_topic_not_found", user.email, template_args) }
|
||||
|
||||
it 'renders the senders email' do
|
||||
expect(reject_mail.to).to eql([user.email])
|
||||
end
|
||||
|
||||
it 'renders the subject' do
|
||||
expect(reject_mail.subject).to be_present
|
||||
end
|
||||
|
||||
it 'renders site title in subject' do
|
||||
expect(reject_mail.subject).to match(SiteSetting.title)
|
||||
end
|
||||
|
||||
it 'renders the body' do
|
||||
expect(reject_mail.body).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -45,13 +45,20 @@ describe User do
|
|||
let(:user) { Fabricate(:user) }
|
||||
let(:admin) { Fabricate(:admin) }
|
||||
|
||||
it "enqueues a 'signup after approval' email" do
|
||||
it "enqueues a 'signup after approval' email if must_approve_users is true" do
|
||||
SiteSetting.stubs(:must_approve_users).returns(true)
|
||||
Jobs.expects(:enqueue).with(
|
||||
:user_email, has_entries(type: :signup_after_approval)
|
||||
)
|
||||
user.approve(admin)
|
||||
end
|
||||
|
||||
it "doesn't enqueue a 'signup after approval' email if must_approve_users is false" do
|
||||
SiteSetting.stubs(:must_approve_users).returns(false)
|
||||
Jobs.expects(:enqueue).never
|
||||
user.approve(admin)
|
||||
end
|
||||
|
||||
context 'after approval' do
|
||||
before do
|
||||
user.approve(admin)
|
||||
|
|
Loading…
Reference in a new issue