Merge branch 'master' of github.com:discourse/discourse

This commit is contained in:
Robin Ward 2014-10-29 10:15:06 -04:00
commit fe8d9642a0
40 changed files with 366 additions and 158 deletions

View File

@ -1,43 +0,0 @@
import { daysSinceEpoch } from "discourse/helpers/cold-age-class";
export default Ember.Component.extend({
tagName: 'td',
classNameBindings: [':activity', 'coldness'],
attributeBindings: ['title'],
// returns createdAt if there's no bumped date
bumpedAt: function() {
var bumpedAt = this.get('topic.bumped_at');
if (bumpedAt) {
return new Date(bumpedAt);
} else {
return this.get('createdAt');
}
}.property('topic.bumped_at', 'createdAt'),
createdAt: function() {
return new Date(this.get('topic.created_at'));
}.property('topic.created_at'),
coldness: function() {
var bumpedAt = this.get('bumpedAt'),
createdAt = this.get('createdAt');
if (!bumpedAt) { return; }
var delta = daysSinceEpoch(bumpedAt) - daysSinceEpoch(createdAt);
if (delta > Discourse.SiteSettings.cold_age_days_high) { return 'coldmap-high'; }
if (delta > Discourse.SiteSettings.cold_age_days_medium) { return 'coldmap-med'; }
if (delta > Discourse.SiteSettings.cold_age_days_low) { return 'coldmap-low'; }
}.property('bumpedAt', 'createdAt'),
title: function() {
return I18n.t('first_post') + ": " + Discourse.Formatter.longDate(this.get('createdAt')) + "\n" +
I18n.t('last_post') + ": " + Discourse.Formatter.longDate(this.get('bumpedAt'));
}.property('bumpedAt', 'createdAt'),
render: function(buffer) {
buffer.push("<a href='" + this.get('topic.lastPostUrl') +"'>" + Discourse.Formatter.autoUpdatingRelativeAge(this.get('bumpedAt')) + "</a>");
}
});

View File

@ -57,6 +57,16 @@ export default Ember.Component.extend({
actions: {
expand: function() {
var self = this;
if(!this.get('renderCategories')){
this.set('renderCategories',true);
Em.run.next(function(){
self.send('expand');
});
return;
}
if (this.get('expanded')) {
this.close();
return;
@ -65,8 +75,7 @@ export default Ember.Component.extend({
if (this.get('categories')) {
this.set('expanded', true);
}
var self = this,
$dropdown = this.$()[0];
var $dropdown = this.$()[0];
this.$('a[data-drop-close]').on('click.category-drop', function() {
self.close();

View File

@ -1,4 +1,6 @@
export default Ember.Component.extend({
import VisibleComponent from "discourse/components/visible";
export default VisibleComponent.extend({
visible: function () {
var bannerKey = this.get("banner.key"),
@ -20,6 +22,7 @@ export default Ember.Component.extend({
Discourse.KeyValueStore.set({ key: "dismissed_banner_key", value: this.get("banner.key") });
}
}
}
},
});

View File

@ -43,7 +43,7 @@ export default Ember.Component.extend({
this.sendAction('action', {
topic: topic,
position: this.$('a').position()
position: this.$('a').offset()
});
return false;

View File

@ -0,0 +1,13 @@
export default Ember.Component.extend({
visibleChanged: function(){
this.rerender();
}.observes("visible"),
render: function(buffer){
if(!this.get("visible")){
return;
}
return this._super(buffer);
}
});

View File

@ -121,7 +121,6 @@ var controllerOpts = {
canBulkSelect: Em.computed.alias('currentUser.staff'),
hasTopics: Em.computed.gt('topics.length', 0),
showTable: Em.computed.or('hasTopics', 'topicTrackingState.hasIncoming'),
allLoaded: Em.computed.empty('more_topics_url'),
latest: Discourse.computed.endWith('filter', 'latest'),
new: Discourse.computed.endWith('filter', 'new'),

View File

@ -53,8 +53,8 @@ export default ObjectController.extend(ModalFunctionality, {
displayGoToNext: function() { return this.get("next_revision") && this.get("current_revision") < this.get("next_revision"); }.property("current_revision", "next_revision"),
displayGoToLast: function() { return this.get("current_revision") < this.get("last_revision"); }.property("current_revision", "last_revision"),
displayShow: function() { return !Discourse.Mobile.mobileView && this.get("previous_hidden") && Discourse.User.currentProp('staff'); }.property("previous_hidden"),
displayHide: function() { return !Discourse.Mobile.mobileView && !this.get("previous_hidden") && Discourse.User.currentProp('staff'); }.property("previous_hidden"),
displayShow: function() { return this.get("previous_hidden") && Discourse.User.currentProp('staff') && !this.get("loading"); }.property("previous_hidden", "loading"),
displayHide: function() { return !this.get("previous_hidden") && Discourse.User.currentProp('staff') && !this.get("loading"); }.property("previous_hidden", "loading"),
isEitherRevisionHidden: Em.computed.or("previous_hidden", "current_hidden"),

View File

@ -8,18 +8,25 @@ export function daysSinceEpoch(dt) {
**/
function coldAgeClass(property, options) {
var dt = Em.Handlebars.get(this, property, options);
var className = (options && options.hash && options.hash.class !== undefined) ? options.hash.class : 'age';
if (!dt) { return 'age'; }
if (!dt) { return className; }
var startDate = (options && options.hash && options.hash.startDate) || new Date();
if (typeof startDate === "string") {
startDate = Em.Handlebars.get(this, startDate, options);
}
// Show heat on age
var nowDays = daysSinceEpoch(new Date()),
var nowDays = daysSinceEpoch(startDate),
epochDays = daysSinceEpoch(new Date(dt));
if (nowDays - epochDays > Discourse.SiteSettings.cold_age_days_high) return 'age coldmap-high';
if (nowDays - epochDays > Discourse.SiteSettings.cold_age_days_medium) return 'age coldmap-med';
if (nowDays - epochDays > Discourse.SiteSettings.cold_age_days_low) return 'age coldmap-low';
if (nowDays - epochDays > Discourse.SiteSettings.cold_age_days_high) return className + ' coldmap-high';
if (nowDays - epochDays > Discourse.SiteSettings.cold_age_days_medium) return className + ' coldmap-med';
if (nowDays - epochDays > Discourse.SiteSettings.cold_age_days_low) return className + ' coldmap-low';
return 'age';
return className;
}
Handlebars.registerHelper('cold-age-class', coldAgeClass);

View File

@ -3,19 +3,27 @@
update the dates on a regular interval.
**/
Handlebars.registerHelper('format-date', function(property, options) {
var leaveAgo;
if (property.hash) {
if (property.hash.leaveAgo) {
leaveAgo = property.hash.leaveAgo === "true";
var leaveAgo, format = 'medium', title = true;
var hash = property.hash || (options && options.hash);
if (hash) {
if (hash.leaveAgo) {
leaveAgo = hash.leaveAgo === "true";
}
if (property.hash.path) {
property = property.hash.path;
if (hash.path) {
property = hash.path;
}
if (hash.format) {
format = hash.format;
}
if (hash.noTitle) {
title = false;
}
}
var val = Ember.Handlebars.get(this, property, options);
if (val) {
var date = new Date(val);
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(date, {format: 'medium', title: true, leaveAgo: leaveAgo}));
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(date, {format: format, title: title, leaveAgo: leaveAgo}));
}
});

View File

@ -23,7 +23,7 @@ I18n.toHumanSize = function(number, options) {
@method i18n
@for Handlebars
**/
Ember.Handlebars.registerHelper('i18n', function(property, options) {
Handlebars.registerHelper('i18n', function(property, options) {
// Resolve any properties
var params = options.hash,
self = this;

View File

@ -0,0 +1,15 @@
Handlebars.registerHelper('raw', function(property, options) {
var template = Em.TEMPLATES[property + ".raw"];
var params = options.hash;
if(params) {
for(var prop in params){
if(options.hashTypes[prop] === "ID") {
params[prop] = Em.Handlebars.get(this, params[prop], options);
}
}
}
return new Handlebars.SafeString(template(params));
});

View File

@ -0,0 +1,101 @@
// keep IIF for simpler testing
// EmberCompatHandlebars is a mechanism for quickly rendering templates which is Ember aware
// templates are highly compatible with Ember so you don't need to worry about calling "get"
// and computed properties function, additionally it uses stringParams like Ember does
(function(){
// compat with ie8 in case this gets picked up elsewhere
var objectCreate = Object.create || function(parent) {
function F() {}
F.prototype = parent;
return new F();
};
var RawHandlebars = objectCreate(Handlebars);
RawHandlebars.helper = function() {};
RawHandlebars.helpers = objectCreate(Handlebars.helpers);
RawHandlebars.helpers.get = function(context, options){
var firstContext = options.contexts[0];
var val = firstContext[context];
val = val === undefined ? Em.get(firstContext, context): val;
return val;
};
// adds compatability so this works with stringParams
var stringCompatHelper = function(fn){
var old = RawHandlebars.helpers[fn];
RawHandlebars.helpers[fn] = function(context,options){
return old.apply(this, [
RawHandlebars.helpers.get(context,options),
options
]);
};
};
stringCompatHelper("each");
stringCompatHelper("if");
stringCompatHelper("unless");
stringCompatHelper("with");
RawHandlebars.Compiler = function() {};
RawHandlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
RawHandlebars.Compiler.prototype.compiler = RawHandlebars.Compiler;
RawHandlebars.JavaScriptCompiler = function() {};
RawHandlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
RawHandlebars.JavaScriptCompiler.prototype.compiler = RawHandlebars.JavaScriptCompiler;
RawHandlebars.JavaScriptCompiler.prototype.namespace = "Discourse.EmberCompatHandlebars";
RawHandlebars.Compiler.prototype.mustache = function(mustache) {
if ( !(mustache.params.length || mustache.hash)) {
var id = new Handlebars.AST.IdNode([{ part: 'get' }]);
mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, mustache.escaped);
}
return Handlebars.Compiler.prototype.mustache.call(this, mustache);
};
RawHandlebars.precompile = function(value, asObject) {
var ast = Handlebars.parse(value);
var options = {
knownHelpers: {
get: true
},
data: true,
stringParams: true
};
asObject = asObject === undefined ? true : asObject;
var environment = new RawHandlebars.Compiler().compile(ast, options);
return new RawHandlebars.JavaScriptCompiler().compile(environment, options, undefined, asObject);
};
RawHandlebars.compile = function(string) {
var ast = Handlebars.parse(string);
// this forces us to rewrite helpers
var options = { data: true, stringParams: true };
var environment = new RawHandlebars.Compiler().compile(ast, options);
var templateSpec = new RawHandlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
var template = RawHandlebars.template(templateSpec);
return template;
};
Discourse.EmberCompatHandlebars = RawHandlebars;
})();

View File

@ -1,5 +1,24 @@
Discourse.Topic = Discourse.Model.extend({
// returns createdAt if there's no bumped date
bumpedAt: function() {
var bumpedAt = this.get('bumped_at');
if (bumpedAt) {
return new Date(bumpedAt);
} else {
return this.get('createdAt');
}
}.property('bumped_at', 'createdAt'),
bumpedAtTitle: function() {
return I18n.t('first_post') + ": " + Discourse.Formatter.longDate(this.get('createdAt')) + "\n" +
I18n.t('last_post') + ": " + Discourse.Formatter.longDate(this.get('bumpedAt'));
}.property('bumpedAt'),
createdAt: function() {
return new Date(this.get('created_at'));
}.property('created_at'),
postStream: function() {
return Discourse.PostStream.create({topic: this});
}.property(),

View File

@ -28,11 +28,7 @@
url=topic.lastUnreadUrl}}
</td>
{{#unless controller.hideCategory}}
<td class="category">
{{category-link topic.category showParent=true}}
</td>
{{/unless}}
{{raw "list/category_column" hideCategory=controller.hideCategory category=topic.category}}
{{posts-count-column topic=topic class="num" action="clickedPosts"}}
@ -48,7 +44,7 @@
{{number topic.views numberKey="views_long"}}
</td>
{{activity-column topic=topic class="num"}}
{{raw "list/activity_column" topic=topic class="num" tagName="td"}}
</tr>
{{/grouped-each}}
</tbody>

View File

@ -20,6 +20,8 @@
{{#if subCategory}}
<div class='cat'><a {{bind-attr href=noCategoriesUrl}} data-drop-close="true" class='badge-category home'>{{i18n categories.no_subcategory}}</a></div>
{{/if}}
{{#if renderCategories}}
{{#each categories}}<div class='cat'>{{category-link this allowUncategorized=true}}</div>{{/each}}
{{/if}}
</section>
{{/if}}

View File

@ -1,10 +1,8 @@
{{#if visible}}
<div class="row">
<div id="banner" {{bind-attr class="overlay"}}>
<div id="banner-content">
<div class="close" {{action "dismiss"}}><i class="fa fa-times" title="{{i18n banner.close}}"></i></div>
{{{banner.html}}}
</div>
<div class="row">
<div id="banner" {{bind-attr class="overlay"}}>
<div id="banner-content">
<div class="close" {{action "dismiss"}}><i class="fa fa-times" title="{{i18n banner.close}}"></i></div>
{{{banner.html}}}
</div>
</div>
{{/if}}
</div>

View File

@ -26,53 +26,46 @@
{{top-period-chooser period=period}}
</div>
{{/if}}
{{#if showTable}}
{{#if topicTrackingState.hasIncoming}}
<div class="show-more">
<div class='alert alert-info clickable' {{action "showInserted"}}>
{{countI18n topic_count_ suffix=topicTrackingState.filter count=topicTrackingState.incomingCount}}
{{i18n click_to_show}}
</div>
</div>
{{/if}}
{{#if hasTopics}}
<table class='topic-list'>
<thead>
<tr>
{{#if currentUser}}
<th class='star'>
{{#if canBulkSelect}}
<button class='btn bulk-select' {{action "toggleBulkSelect"}} title="{{i18n topics.bulk.toggle}}"><i class='fa fa-list'></i></button>
<tr>
{{#if currentUser}}
<th class='star'>
{{#if canBulkSelect}}
<button class='btn bulk-select' {{action "toggleBulkSelect"}} title="{{i18n topics.bulk.toggle}}"><i class='fa fa-list'></i></button>
{{/if}}
</th>
{{/if}}
</th>
{{/if}}
{{#sortable-heading class="default"}} {{i18n topic.title}} {{/sortable-heading}}
{{#unless controller.hideCategory}}
{{#sortable-heading sortBy="category" action="changeSort" order=order ascending=ascending}}
{{i18n category_title}}
{{#sortable-heading class="default"}} {{i18n topic.title}} {{/sortable-heading}}
{{#unless controller.hideCategory}}
{{#sortable-heading sortBy="category" action="changeSort" order=order ascending=ascending}}
{{i18n category_title}}
{{/sortable-heading}}
{{/unless}}
{{#sortable-heading class="posters"}} {{i18n users}} {{/sortable-heading}}
{{#sortable-heading sortBy="posts" number=true action="changeSort" order=order ascending=ascending}}
{{i18n posts}}
{{/sortable-heading}}
{{/unless}}
{{#sortable-heading class="posters"}} {{i18n users}} {{/sortable-heading}}
{{#sortable-heading sortBy="posts" number=true action="changeSort" order=order ascending=ascending}}
{{i18n posts}}
{{/sortable-heading}}
{{#sortable-heading sortBy="views" number=true action="changeSort" order=order ascending=ascending}}
{{i18n views}}
{{/sortable-heading}}
{{#sortable-heading sortBy="activity" number=true action="changeSort" order=order ascending=ascending}}
{{i18n activity}}
{{/sortable-heading}}
</tr>
{{#sortable-heading sortBy="views" number=true action="changeSort" order=order ascending=ascending}}
{{i18n views}}
{{/sortable-heading}}
{{#sortable-heading sortBy="activity" number=true action="changeSort" order=order ascending=ascending}}
{{i18n activity}}
{{/sortable-heading}}
</tr>
</thead>
{{#if topicTrackingState.hasIncoming}}
<tbody>
<tr>
<td colspan="9">
<div class='alert alert-info clickable' {{action "showInserted"}}>
{{countI18n topic_count_ suffix=topicTrackingState.filter count=topicTrackingState.incomingCount}}
{{i18n click_to_show}}
</div>
</td>
</tr>
</tbody>
{{/if}}
<tbody>
{{each topics itemController="topic-list-item" itemView="topic-list-item"}}
</tbody>
</table>
{{/if}}
</div>

View File

@ -80,16 +80,20 @@
{{/if}}
</ul>
{{render "search"}}
{{#if view.renderDropdowns}}
{{render "notifications" notifications}}
{{render "search"}}
{{render "notifications" notifications}}
{{#if view.renderSiteMap}}
{{render "siteMap"}}
{{/if}}
{{render "user-dropdown"}}
{{#if view.renderSiteMap}}
{{render "siteMap"}}
{{/if}}
{{render "user-dropdown"}}
</div>
{{#if showExtraInfo}}

View File

@ -0,0 +1 @@
<{{this.tagName}} class="{{class}} {{cold-age-class topic.createdAt startDate=topic.bumpedAt class=""}} activity" title="{{topic.bumpedAtTitle}}"><a href="{{topic.lastPostUrl}}">{{format-date topic.bumpedAt format="tiny" noTitle=true}}</a></{{this.tagName}}>

View File

@ -0,0 +1,3 @@
{{#unless hideCategory}}
<td class='category'>{{category-link category showParent=true}}</td>
{{/unless}}

View File

@ -0,0 +1,5 @@
<td class='posters'>
{{#each posters}}
<a href="{{user.path}}" data-user-card="{{user.username}}" class="{{extras}}">{{avatar this usernamePath="user.username" imageSize="small"}}</a>
{{/each}}
</td>

View File

@ -0,0 +1,8 @@
{{#if topic.hasExcerpt}}
<div class="topic-excerpt">
{{{topic.excerpt}}}
{{#if topic.excerptTruncated}}
<a href="{{topic.url}}">{{i18n read_more}}</a>
{{/if}}
</div>
{{/if}}

View File

@ -10,30 +10,20 @@
{{/if}}
{{/if}}
<td class='main-link clearfix' {{bind-attr colspan="titleColSpan"}}>
<td class='main-link clearfix' colspan="{{unbound titleColSpan}}">
{{topic-status topic=this.model}}
{{topic-link this}}
{{#if showTopicPostBadges}}
{{topic-post-badges unread=unread newPosts=displayNewPosts unseen=unseen url=lastUnreadUrl}}
{{/if}}
{{#if hasExcerpt}}
<div class="topic-excerpt">
{{{excerpt}}}
{{#if excerptTruncated}}
<a href="{{url}}">{{i18n read_more}}</a>
{{/if}}
</div>
{{/if}}
{{raw "list/topic_excerpt" topic=model}}
</td>
{{#unless hideCategory}}
<td class='category'>{{bound-category-link category showParent=true}}</td>
{{/unless}}
{{view 'posters-column' posters=posters}}
{{raw "list/category_column" hideCategory=hideCategory category=category}}
{{raw "list/posters_column" posters=posters}}
{{posts-count-column topic=model class="num" action="showTopicEntrance"}}
<td {{bind-attr class=":num :views viewsHeat"}}>{{number views numberKey="views_long"}}</td>
<td class="num views {{unbound viewsHeat}}">{{number views numberKey="views_long"}}</td>
{{activity-column topic=model class="num"}}
{{raw "list/activity_column" topic=model class="num" tagName="td"}}

View File

@ -27,7 +27,7 @@
<div class="topic-item-stats clearfix">
<div class="pull-right">
{{posts-count-column topic=topic tagName="div" class="num posts" action="clickedPosts"}}
{{activity-column topic=topic tagName="div" class="num activity last"}}
{{raw "list/activity_column" topic=topic tagName="div" class="num activity last"}}
</div>
{{#unless controller.hideCategory}}
<div class='category'>

View File

@ -4,7 +4,7 @@
{{top-period-chooser period=period}}
</div>
{{/if}}
{{#if showTable}}
{{#if hasTopics}}
<table class='topic-list'>
{{#if topicTrackingState.hasIncoming}}
<tbody>

View File

@ -31,7 +31,7 @@
{{posts-count-column topic=this tagName="div" class="num posts" action="showTopicEntrance"}}
<div class='num activity last'>
<a href="{{lastPostUrl}}" title='{{i18n last_post}}: {{{raw-date bumped_at}}}'>{{last_poster_username}}</a>
{{activity-column topic=this tagName="span" class="age"}}
{{raw "list/activity_column" topic=this tagName="span" class="age"}}
</div>
</div>
<div class="clearfix"></div>

View File

@ -14,16 +14,26 @@ export default Discourse.View.extend({
classNames: ['d-header', 'clearfix'],
classNameBindings: ['editingTopic'],
templateName: 'header',
renderDropdowns: false,
showDropdown: function($target) {
var self = this;
if(!this.get("renderDropdowns")){
this.set("renderDropdowns", true);
Em.run.next(function(){
self.showDropdown($target);
});
return;
}
var elementId = $target.data('dropdown') || $target.data('notifications'),
$dropdown = $("#" + elementId),
$li = $target.closest('li'),
$ul = $target.closest('ul'),
$html = $('html'),
$header = $('header'),
replyZIndex = parseInt($('#reply-control').css('z-index'), 10),
self = this;
replyZIndex = parseInt($('#reply-control').css('z-index'), 10);
originalZIndex = originalZIndex || $('header').css('z-index');

View File

@ -131,7 +131,9 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, {
topicId = parseInt(topicId, 10);
Discourse.ajax("/posts/by_number/" + topicId + "/" + postId).then(function (result) {
var parsed = $(result.cooked);
// slightly double escape the cooked html to prevent jQuery from unescaping it
var escaped = result.cooked.replace("&", "&amp;");
var parsed = $(escaped);
parsed.replaceText(originalText, "<span class='highlighted'>" + originalText + "</span>");
$blockQuote.showHtml(parsed, 'fast', finished);
});

View File

@ -5,9 +5,13 @@
// Pagedown customizations
//= require ./pagedown_custom.js
// This is a BUG we should fix
// it is only required here cause preview is not loading it using LAB
//= require highlight.pack.js
//
// Stuff we need to load first
//= require ./discourse/lib/ember_compat_handlebars
//= require ./discourse/lib/computed
//= require ./discourse/mixins/scrolling
//= require_tree ./discourse/mixins
@ -38,6 +42,7 @@
//= require ./discourse/routes/discourse_restricted_user_route
//= require ./discourse/components/top-title
//= require ./discourse/components/text-field
//= require ./discourse/components/visible
//= require ./discourse/helpers/user-avatar
//= require ./discourse/helpers/cold-age-class

View File

@ -1,3 +1,9 @@
.show-more {
position: absolute;
top: 5px;
width: 100%;
}
.list-controls {
#navigation-bar {
.has-icon span:before {

View File

@ -2,6 +2,9 @@
@extend .clearfix;
margin-right: auto;
margin-left: auto;
.contents {
position: relative;
}
}
.full-width {

View File

@ -3,7 +3,7 @@
.modal.history-modal {
#revision-numbers {
display: inline-block;
min-width: 100px;
min-width: 96px;
text-align: center;
}
#revision-loading {

View File

@ -117,14 +117,12 @@
}
.user-main {
width: 100%;
float: left;
margin-bottom: 50px;
table.group-members {
width: 100%;
width: 75%;
float: right;
p {
max-width: 600px;
white-space: nowrap;
@ -191,6 +189,13 @@
overflow: hidden;
color: $secondary;
&.group {
width: 75%;
float: right;
margin-top: 20px;
.details {padding: 15px;}
}
.secondary {
background: dark-light-diff($primary, $secondary, 90%, -65%);
font-size: 13px;

View File

@ -94,7 +94,7 @@ class UsersController < ApplicationController
end
def check_emails
user = fetch_user_from_params
user = fetch_user_from_params(include_inactive: true)
guardian.ensure_can_check_emails!(user)
StaffActionLogger.new(current_user).log_check_email(user, context: params[:context])

View File

@ -96,7 +96,7 @@ Note that pinning topics works a little differently in Discourse:
If a pin isn't visible enough, you can also turn one single topic into a **banner**. The banner topic floats on top of all topics and all primary pages. Users can permanently dismiss this floating banner by clicking the &times; in the upper right corner.
To make (or remove) a pin or a banner, use the admin wrench at the top right of the topic.
To make (or remove) a pin or a banner, use the admin wrench at the top right or bottom of the topic.
### Set the Homepage

View File

@ -7,4 +7,4 @@ The first paragraph of this pinned topic will be visible as a welcome message to
- Why should they come here?
- Where can they read more (links, resources, etc)?
You may want to close this topic via the wrench icon at the upper right, so that replies don't pile up on an announcement.
You may want to close this topic via the admin wrench (at the upper right and bottom), so that replies don't pile up on an announcement.

View File

@ -155,7 +155,7 @@ module Email
def strip_avatars_and_emojis
@fragment.css('img').each do |img|
if img['src'] =~ /user_avatar/
if img['src'] =~ /_avatar/
img.parent['style'] = "vertical-align: top;" if img.parent.name == 'td'
img.remove
end

View File

@ -0,0 +1,36 @@
# barber patches to re-route raw compilation via ember compat handlebars
#
module Barber
class EmberCompatPrecompiler < Barber::Precompiler
def self.call(template)
"Handlebars.template(#{compile(template)})"
end
def sources
[handlebars, precompiler]
end
def precompiler
@precompiler ||= StringIO.new <<END
var Discourse = {};
#{File.read(Rails.root + "app/assets/javascripts/discourse/lib/ember_compat_handlebars.js")}
var Barber = {
precompile: function(string) {
return Discourse.EmberCompatHandlebars.precompile(string).toString();
}
};
END
end
end
end
class Ember::Handlebars::Template
def precompile_handlebars(string)
"Discourse.EmberCompatHandlebars.template(#{Barber::EmberCompatPrecompiler.compile(string)});"
end
def compile_handlebars(string)
"Discourse.EmberCompatHandlebars.compile(#{indent(string).inspect});"
end
end

View File

@ -82,7 +82,7 @@ class ImportScripts::Vanilla < ImportScripts::Base
admin_role_id = @roles.select { |r| r[:name] == "Administrator" }.first[:role_id]
moderator_role_id = @roles.select { |r| r[:name] == "Moderator" }.first[:role_id]
activities = @activities.reject { |a| a[:activity_user_id] != a[:regarding_user_id] }
activities = (@activities || []).reject { |a| a[:activity_user_id] != a[:regarding_user_id] }
create_users(@users) do |user|
next if user[:name] == "[Deleted User]"

View File

@ -521,7 +521,7 @@ describe UsersController do
xhr :post, :create, create_params
json = JSON::parse(response.body)
json["success"].should_not == true
# should not change the session
session["user_created_email"].should be_blank
end
@ -1411,6 +1411,16 @@ describe UsersController do
json["associated_accounts"].should be_present
end
it "works on inactive users" do
inactive_user = Fabricate(:user, active: false)
Guardian.any_instance.expects(:can_check_emails?).returns(true)
xhr :put, :check_emails, username: inactive_user.username
response.should be_success
json = JSON.parse(response.body)
json["email"].should be_present
json["associated_accounts"].should be_present
end
end
end