Discourse Macro Helpers + Minor Fix to Admin User View

This commit is contained in:
Robin Ward 2013-07-11 19:35:52 -04:00
parent 89152116c6
commit 5eaae063f0
13 changed files with 136 additions and 69 deletions

View File

@ -78,9 +78,7 @@ Discourse.AdminUsersListController = Ember.ArrayController.extend(Discourse.Pres
@property hasSelection @property hasSelection
**/ **/
hasSelection: function() { hasSelection: Em.computed.gt('selectedCount', 0),
return this.get('selectedCount') > 0;
}.property('selectedCount'),
/** /**
Refresh the current list of users. Refresh the current list of users.

View File

@ -98,19 +98,14 @@ Discourse.AdminUser = Discourse.User.extend({
this.set('trustLevel.id', this.get('originalTrustLevel')); this.set('trustLevel.id', this.get('originalTrustLevel'));
}, },
isBanned: (function() { isBanned: Em.computed.equal('is_banned', true),
return this.get('is_banned') === true; canBan: Em.computed.not('staff'),
}).property('is_banned'),
canBan: (function() { banDuration: function() {
return !this.get('admin') && !this.get('moderator');
}).property('admin', 'moderator'),
banDuration: (function() {
var banned_at = moment(this.banned_at); var banned_at = moment(this.banned_at);
var banned_till = moment(this.banned_till); var banned_till = moment(this.banned_till);
return banned_at.format('L') + " - " + banned_till.format('L'); return banned_at.format('L') + " - " + banned_till.format('L');
}).property('banned_till', 'banned_at'), }.property('banned_till', 'banned_at'),
ban: function() { ban: function() {
var duration = parseInt(window.prompt(I18n.t('admin.user.ban_duration')), 10); var duration = parseInt(window.prompt(I18n.t('admin.user.ban_duration')), 10);

View File

@ -16,11 +16,6 @@ Discourse.AdminUserRoute = Discourse.Route.extend(Discourse.ModelReady, {
return Discourse.AdminUser.find(Em.get(params, 'username').toLowerCase()); return Discourse.AdminUser.find(Em.get(params, 'username').toLowerCase());
}, },
setupController: function(controller, model) {
controller.set('model', model);
model.setOriginalTrustLevel();
},
renderTemplate: function() { renderTemplate: function() {
this.render({into: 'admin/templates/admin'}); this.render({into: 'admin/templates/admin'});
}, },
@ -28,6 +23,7 @@ Discourse.AdminUserRoute = Discourse.Route.extend(Discourse.ModelReady, {
modelReady: function(controller, adminUser) { modelReady: function(controller, adminUser) {
adminUser.loadDetails(); adminUser.loadDetails();
controller.set('model', adminUser); controller.set('model', adminUser);
adminUser.setOriginalTrustLevel();
} }
}); });

View File

@ -12,6 +12,49 @@ Discourse.computed = {
return Ember.computed(function() { return Ember.computed(function() {
return this.get(p1) === this.get(p2); return this.get(p1) === this.get(p2);
}).property(p1, p2); }).property(p1, p2);
},
/**
Uses an Ember String `fmt` call to format a string. See:
http://emberjs.com/api/classes/Ember.String.html#method_fmt
@method fmt
@params {String} properties* to format
@params {String} format the format string
@return {Function} computedProperty function
**/
fmt: function() {
var args = Array.prototype.slice.call(arguments, 0);
var format = args.pop();
var computed = Ember.computed(function() {
var context = this;
return format.fmt.apply(format, args.map(function (a) {
return context.get(a);
}));
})
return computed.property.apply(computed, args);
},
/**
Creates a URL using Discourse.getURL. It takes a fmt string just like
fmt does.
@method url
@params {String} properties* to format
@params {String} format the format string for the URL
@return {Function} computedProperty function returning a URL
**/
url: function() {
var args = Array.prototype.slice.call(arguments, 0);
var format = args.pop();
var computed = Ember.computed(function() {
var context = this;
return Discourse.getURL(format.fmt.apply(format, args.map(function (a) {
return context.get(a);
})));
})
return computed.property.apply(computed, args);
} }
}; };

View File

@ -25,8 +25,7 @@ Discourse.ListCategoriesController = Discourse.ObjectController.extend({
}.property('categories.@each'), }.property('categories.@each'),
canEdit: function() { canEdit: function() {
var u = Discourse.User.current(); Discourse.User.current('staff');
return u && u.staff;
}.property(), }.property(),
// clear a pinned topic // clear a pinned topic

View File

@ -198,8 +198,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
Discourse.URL.routeTo(this.get('lastPostUrl')); Discourse.URL.routeTo(this.get('lastPostUrl'));
}, },
replyAsNewTopic: function(post) { replyAsNewTopic: function(post) {
// TODO shut down topic draft cleanly if it exists ... // TODO shut down topic draft cleanly if it exists ...
var composerController = this.get('controllers.composer'); var composerController = this.get('controllers.composer');

View File

@ -21,13 +21,7 @@ Discourse.Post = Discourse.Model.extend({
return Discourse.Utilities.postUrl(this.get('topic.slug') || this.get('topic_slug'), this.get('topic_id'), this.get('post_number')); return Discourse.Utilities.postUrl(this.get('topic.slug') || this.get('topic_slug'), this.get('topic_id'), this.get('post_number'));
}.property('post_number', 'topic_id', 'topic.slug'), }.property('post_number', 'topic_id', 'topic.slug'),
originalPostUrl: function() { usernameUrl: Discourse.computed.url('username', '/users/%@'),
return Discourse.getURL("/t/") + (this.get('topic_id')) + "/" + (this.get('reply_to_post_number'));
}.property('reply_to_post_number'),
usernameUrl: function() {
return Discourse.getURL("/users/" + this.get('username'));
}.property('username'),
showUserReplyTab: function() { showUserReplyTab: function() {
return this.get('reply_to_user') && ( return this.get('reply_to_user') && (
@ -36,15 +30,9 @@ Discourse.Post = Discourse.Model.extend({
); );
}.property('reply_to_user', 'reply_to_post_number', 'post_number'), }.property('reply_to_user', 'reply_to_post_number', 'post_number'),
byTopicCreator: function() { byTopicCreator: Discourse.computed.propertyEqual('topic.details.created_by.id', 'user_id'),
return this.get('topic.details.created_by.id') === this.get('user_id');
}.property('topic.details.created_by.id', 'user_id'),
hasHistory: Em.computed.gt('version', 1), hasHistory: Em.computed.gt('version', 1),
postElementId: Discourse.computed.fmt('post_number', 'post_%@'),
postElementId: function() {
return "post_" + (this.get('post_number'));
}.property('post_number'),
// The class for the read icon of the post. It starts with read-icon then adds 'seen' or // The class for the read icon of the post. It starts with read-icon then adds 'seen' or
// 'last-read' if the post has been seen or is the highest post number seen so far respectively. // 'last-read' if the post has been seen or is the highest post number seen so far respectively.

View File

@ -132,7 +132,6 @@ Discourse.Topic = Discourse.Model.extend({
archetypeObject: function() { archetypeObject: function() {
return Discourse.Site.instance().get('archetypes').findProperty('id', this.get('archetype')); return Discourse.Site.instance().get('archetypes').findProperty('id', this.get('archetype'));
}.property('archetype'), }.property('archetype'),
isPrivateMessage: Em.computed.equal('archetype', 'private_message'), isPrivateMessage: Em.computed.equal('archetype', 'private_message'),
toggleStatus: function(property) { toggleStatus: function(property) {
@ -225,7 +224,6 @@ Discourse.Topic = Discourse.Model.extend({
@method clearPin @method clearPin
**/ **/
clearPin: function() { clearPin: function() {
var topic = this; var topic = this;
// Clear the pin optimistically from the object // Clear the pin optimistically from the object
@ -241,29 +239,27 @@ Discourse.Topic = Discourse.Model.extend({
// Is the reply to a post directly below it? // Is the reply to a post directly below it?
isReplyDirectlyBelow: function(post) { isReplyDirectlyBelow: function(post) {
var postBelow, posts; var posts = this.get('postStream.posts');
posts = this.get('postStream.posts');
if (!posts) return; if (!posts) return;
postBelow = posts[posts.indexOf(post) + 1]; var postBelow = posts[posts.indexOf(post) + 1];
// If the post directly below's reply_to_post_number is our post number, it's // If the post directly below's reply_to_post_number is our post number, it's
// considered directly below. // considered directly below.
return postBelow && postBelow.get('reply_to_post_number') === post.get('post_number'); return postBelow && postBelow.get('reply_to_post_number') === post.get('post_number');
}, },
hasExcerpt: function() { excerptNotEmpty: Em.computed.notEmpty('excerpt'),
return this.get('pinned') && this.get('excerpt') && this.get('excerpt').length > 0; hasExcerpt: Em.computed.and('pinned', 'excerptNotEmpty'),
}.property('pinned', 'excerpt'),
excerptTruncated: function() { excerptTruncated: function() {
var e = this.get('excerpt'); var e = this.get('excerpt');
return( e && e.substr(e.length - 8,8) === '…' ); return( e && e.substr(e.length - 8,8) === '…' );
}.property('excerpt'), }.property('excerpt'),
canClearPin: function() { readLastPost: Discourse.computed.propertyEqual('last_read_post_number', 'highest_post_number'),
return this.get('pinned') && (this.get('last_read_post_number') === this.get('highest_post_number')); canCleanPin: Em.computed.and('pinned', 'readLastPost')
}.property('pinned', 'last_read_post_number', 'highest_post_number')
}); });
Discourse.Topic.reopenClass({ Discourse.Topic.reopenClass({

View File

@ -8,15 +8,23 @@
**/ **/
Discourse.User = Discourse.Model.extend({ Discourse.User = Discourse.Model.extend({
/**
Is this user a member of staff?
@property staff
@type {Boolean}
**/
staff: Em.computed.or('admin', 'moderator'),
/** /**
Large version of this user's avatar. Large version of this user's avatar.
@property avatarLarge @property avatarLarge
@type {String} @type {String}
**/ **/
avatarLarge: (function() { avatarLarge: function() {
return Discourse.Utilities.avatarUrl(this.get('username'), 'large', this.get('avatar_template')); return Discourse.Utilities.avatarUrl(this.get('username'), 'large', this.get('avatar_template'));
}).property('username'), }.property('username'),
/** /**
Small version of this user's avatar. Small version of this user's avatar.
@ -39,11 +47,10 @@ Discourse.User = Discourse.Model.extend({
@type {String} @type {String}
**/ **/
websiteName: function() { websiteName: function() {
return this.get('website').split("/")[2]; var website = this.get('website');
}.property('website'), if (Em.isEmpty(website)) { return; }
hasWebsite: function() { return this.get('website').split("/")[2];
return this.present('website');
}.property('website'), }.property('website'),
statusIcon: function() { statusIcon: function() {
@ -65,9 +72,7 @@ Discourse.User = Discourse.Model.extend({
@property path @property path
@type {String} @type {String}
**/ **/
path: function() { path: Discourse.computed.url('username_lower', "/users/%@"),
return Discourse.getURL("/users/") + (this.get('username_lower'));
}.property('username'),
/** /**
Path to this user's administration Path to this user's administration
@ -75,9 +80,7 @@ Discourse.User = Discourse.Model.extend({
@property adminPath @property adminPath
@type {String} @type {String}
**/ **/
adminPath: function() { adminPath: Discourse.computed.url('username_lower', "/admin/users/%@"),
return Discourse.getURL("/admin/users/") + (this.get('username_lower'));
}.property('username'),
/** /**
This user's username in lowercase. This user's username in lowercase.

View File

@ -22,7 +22,7 @@
</ul> </ul>
<div class='show'> <div class='show'>
<dl> <dl>
{{#if hasWebsite}} {{#if websiteName}}
<dt>{{i18n user.website}}:</dt><dd><a {{bindAttr href="website"}} target="_blank">{{websiteName}}</a></dd> <dt>{{i18n user.website}}:</dt><dd><a {{bindAttr href="website"}} target="_blank">{{websiteName}}</a></dd>
{{/if}} {{/if}}
{{#if created_at}} {{#if created_at}}

View File

@ -192,15 +192,15 @@ class PostsController < ApplicationController
def create_params def create_params
permitted = [ permitted = [
:raw, :raw,
:topic_id, :topic_id,
:title, :title,
:archetype, :archetype,
:category, :category,
:target_usernames, :target_usernames,
:reply_to_post_number, :reply_to_post_number,
:image_sizes, :image_sizes,
:auto_close_days :auto_close_days
] ]
if api_key_valid? if api_key_valid?

View File

@ -0,0 +1,50 @@
module("Discourse.Computed");
var testClass = Em.Object.extend({
same: Discourse.computed.propertyEqual('cookies', 'biscuits'),
exclaimyUsername: Discourse.computed.fmt('username', "!!! %@ !!!"),
multiple: Discourse.computed.fmt('username', 'mood', "%@ is %@"),
userUrl: Discourse.computed.url('username', "/users/%@")
});
test("propertyEqual", function() {
var t = testClass.create({
cookies: 10,
biscuits: 10
});
ok(t.get('same'), "it is true when the properties are the same");
t.set('biscuits', 9);
ok(!t.get('same'), "it isn't true when one property is different");
});
test("fmt", function() {
var t = testClass.create({
username: 'eviltrout',
mood: "happy"
});
equal(t.get('exclaimyUsername'), '!!! eviltrout !!!', "it inserts the string");
equal(t.get('multiple'), "eviltrout is happy");
t.set('username', 'codinghorror');
equal(t.get('multiple'), "codinghorror is happy", "supports changing proerties");
t.set('mood', 'ecstatic');
equal(t.get('multiple'), "codinghorror is ecstatic", "supports changing another property");
});
test("url without a prefix", function() {
var t = testClass.create({ username: 'eviltrout' });
equal(t.get('userUrl'), "/users/eviltrout");
});
test("url with a prefix", function() {
Discourse.BaseUri = "/prefixed/";
var t = testClass.create({ username: 'eviltrout' });
equal(t.get('userUrl'), "/prefixed/users/eviltrout");
});

View File

@ -80,5 +80,6 @@ Discourse.Router.map(function() {
QUnit.testStart(function() { QUnit.testStart(function() {
// Allow our tests to change site settings and have them reset before the next test // Allow our tests to change site settings and have them reset before the next test
Discourse.SiteSettings = jQuery.extend(true, {}, Discourse.SiteSettingsOriginal); Discourse.SiteSettings = jQuery.extend(true, {}, Discourse.SiteSettingsOriginal);
Discourse.BaseUri = "/";
}) })