Ember Upgrade: 1.0

This commit is contained in:
Robin Ward 2013-09-16 14:08:55 -04:00
parent 01075c5e7a
commit be0ce08cc2
110 changed files with 19597 additions and 8477 deletions

View File

@ -1,2 +0,0 @@
//= require list_view.js
//= require_tree ./admin

View File

@ -0,0 +1,10 @@
<%
if Rails.env.development?
require_asset ("development/list-view.js")
else
require_asset ("production/list-view.js")
end
require_asset("main_include_admin.js")
%>

View File

@ -46,5 +46,12 @@ Discourse.AdminDashboardController = Ember.Controller.extend({
updatedTimestamp: function() { updatedTimestamp: function() {
return moment(this.get('updated_at')).format('LLL'); return moment(this.get('updated_at')).format('LLL');
}.property('updated_at') }.property('updated_at'),
actions: {
refreshProblems: function() {
this.loadProblems();
}
}
}); });

View File

@ -24,23 +24,24 @@ Discourse.AdminEmailIndexController = Discourse.Controller.extend({
this.set('sentTestEmail', false); this.set('sentTestEmail', false);
}.observes('testEmailAddress'), }.observes('testEmailAddress'),
actions: {
/**
Sends a test email to the currently entered email address
/** @method sendTestEmail
Sends a test email to the currently entered email address **/
sendTestEmail: function() {
this.set('sentTestEmail', false);
@method sendTestEmail var adminEmailLogsController = this;
**/ Discourse.ajax("/admin/email/test", {
sendTestEmail: function() { type: 'POST',
this.set('sentTestEmail', false); data: { email_address: this.get('testEmailAddress') }
}).then(function () {
var adminEmailLogsController = this; adminEmailLogsController.set('sentTestEmail', true);
Discourse.ajax("/admin/email/test", { });
type: 'POST',
data: { email_address: this.get('testEmailAddress') }
}).then(function () {
adminEmailLogsController.set('sentTestEmail', true);
});
}
} }
}); });

View File

@ -8,14 +8,21 @@
**/ **/
Discourse.AdminEmailPreviewDigestController = Discourse.ObjectController.extend({ Discourse.AdminEmailPreviewDigestController = Discourse.ObjectController.extend({
refresh: function() { actions: {
var model = this.get('model'); refresh: function() {
var controller = this; var model = this.get('model'),
controller.set('loading', true); self = this;
Discourse.EmailPreview.findDigest(this.get('lastSeen')).then(function (email) {
model.setProperties(email.getProperties('html_content', 'text_content')); self.set('loading', true);
controller.set('loading', false); Discourse.EmailPreview.findDigest(this.get('lastSeen')).then(function (email) {
}); model.setProperties(email.getProperties('html_content', 'text_content'));
self.set('loading', false);
});
},
toggleShowHtml: function() {
this.toggleProperty('showHtml');
}
} }
}); });

View File

@ -8,62 +8,64 @@
**/ **/
Discourse.AdminFlagsController = Ember.ArrayController.extend({ Discourse.AdminFlagsController = Ember.ArrayController.extend({
/** actions: {
Clear all flags on a post /**
Clear all flags on a post
@method clearFlags @method clearFlags
@param {Discourse.FlaggedPost} item The post whose flags we want to clear @param {Discourse.FlaggedPost} item The post whose flags we want to clear
**/ **/
disagreeFlags: function(item) { disagreeFlags: function(item) {
var adminFlagsController = this; var adminFlagsController = this;
item.disagreeFlags().then((function() { item.disagreeFlags().then((function() {
adminFlagsController.removeObject(item); adminFlagsController.removeObject(item);
}), function() { }), function() {
bootbox.alert(I18n.t("admin.flags.error")); bootbox.alert(I18n.t("admin.flags.error"));
}); });
}, },
agreeFlags: function(item) { agreeFlags: function(item) {
var adminFlagsController = this; var adminFlagsController = this;
item.agreeFlags().then((function() { item.agreeFlags().then((function() {
adminFlagsController.removeObject(item); adminFlagsController.removeObject(item);
}), function() { }), function() {
bootbox.alert(I18n.t("admin.flags.error")); bootbox.alert(I18n.t("admin.flags.error"));
}); });
}, },
deferFlags: function(item) { deferFlags: function(item) {
var adminFlagsController = this; var adminFlagsController = this;
item.deferFlags().then((function() { item.deferFlags().then((function() {
adminFlagsController.removeObject(item); adminFlagsController.removeObject(item);
}), function() { }), function() {
bootbox.alert(I18n.t("admin.flags.error")); bootbox.alert(I18n.t("admin.flags.error"));
}); });
}, },
/** /**
Deletes a post Deletes a post
@method deletePost @method deletePost
@param {Discourse.FlaggedPost} item The post to delete @param {Discourse.FlaggedPost} item The post to delete
**/ **/
deletePost: function(item) { deletePost: function(item) {
var adminFlagsController = this; var adminFlagsController = this;
item.deletePost().then((function() { item.deletePost().then((function() {
adminFlagsController.removeObject(item); adminFlagsController.removeObject(item);
}), function() { }), function() {
bootbox.alert(I18n.t("admin.flags.error")); bootbox.alert(I18n.t("admin.flags.error"));
}); });
}, },
/** /**
Deletes a user and all posts and topics created by that user. Deletes a user and all posts and topics created by that user.
@method deleteSpammer @method deleteSpammer
@param {Discourse.FlaggedPost} item The post to delete @param {Discourse.FlaggedPost} item The post to delete
**/ **/
deleteSpammer: function(item) { deleteSpammer: function(item) {
item.get('user').deleteAsSpammer(function() { window.location.reload(); }); item.get('user').deleteAsSpammer(function() { window.location.reload(); });
}
}, },
/** /**

View File

@ -1,49 +1,51 @@
Discourse.AdminGroupsController = Ember.Controller.extend({ Discourse.AdminGroupsController = Ember.Controller.extend({
itemController: 'adminGroup', itemController: 'adminGroup',
edit: function(group){ actions: {
this.get('model').select(group); edit: function(group){
group.load(); this.get('model').select(group);
}, group.load();
},
refreshAutoGroups: function(){ refreshAutoGroups: function(){
var controller = this; var self = this;
this.set('refreshingAutoGroups', true); self.set('refreshingAutoGroups', true);
Discourse.ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}) Discourse.ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() {
.then(function() { self.set('model', Discourse.Group.findAll());
controller.set('model', Discourse.Group.findAll()); self.set('refreshingAutoGroups', false);
controller.set('refreshingAutoGroups', false); });
}); },
},
newGroup: function(){ newGroup: function(){
var group = Discourse.Group.create(); var group = Discourse.Group.create();
group.set("loaded", true); group.set("loaded", true);
var model = this.get("model"); var model = this.get("model");
model.addObject(group); model.addObject(group);
model.select(group); model.select(group);
}, },
save: function(group){ save: function(group){
if(!group.get("id")){ if(!group.get("id")){
group.create(); group.create();
} else { } else {
group.save(); group.save();
}
},
destroy: function(group){
var _this = this;
return bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
if (result) {
group.destroy().then(function(deleted) {
if (deleted) {
_this.get("model").removeObject(group);
}
});
} }
}); },
destroy: function(group){
var self = this;
return bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
if (result) {
group.destroy().then(function(deleted) {
if (deleted) {
self.get("model").removeObject(group);
}
});
}
});
}
} }
}); });

View File

@ -4,14 +4,16 @@ Discourse.AdminReportsController = Ember.ObjectController.extend({
viewingTable: Em.computed.equal('viewMode', 'table'), viewingTable: Em.computed.equal('viewMode', 'table'),
viewingBarChart: Em.computed.equal('viewMode', 'barChart'), viewingBarChart: Em.computed.equal('viewMode', 'barChart'),
// Changes the current view mode to 'table' actions: {
viewAsTable: function() { // Changes the current view mode to 'table'
this.set('viewMode', 'table'); viewAsTable: function() {
}, this.set('viewMode', 'table');
},
// Changes the current view mode to 'barChart' // Changes the current view mode to 'barChart'
viewAsBarChart: function() { viewAsBarChart: function() {
this.set('viewMode', 'barChart'); this.set('viewMode', 'barChart');
}
} }
}); });

View File

@ -14,12 +14,15 @@ Discourse.AdminSiteContentEditController = Discourse.Controller.extend({
return false; return false;
}.property('saving', 'content.content'), }.property('saving', 'content.content'),
saveChanges: function() { actions: {
var controller = this; saveChanges: function() {
controller.setProperties({saving: true, saved: false}); var self = this;
this.get('content').save().then(function () { self.setProperties({saving: true, saved: false});
controller.setProperties({saving: false, saved: true}); self.get('content').save().then(function () {
}); self.setProperties({saving: false, saved: true});
});
}
} }
});
}); Discourse.AdminSiteContentsController = Ember.ArrayController.extend({});

View File

@ -39,35 +39,37 @@ Discourse.AdminSiteSettingsController = Ember.ArrayController.extend(Discourse.P
}); });
}.property('filter', 'content.@each', 'onlyOverridden'), }.property('filter', 'content.@each', 'onlyOverridden'),
/** actions: {
Reset a setting to its default value /**
Reset a setting to its default value
@method resetDefault @method resetDefault
@param {Discourse.SiteSetting} setting The setting we want to revert @param {Discourse.SiteSetting} setting The setting we want to revert
**/ **/
resetDefault: function(setting) { resetDefault: function(setting) {
setting.set('value', setting.get('default')); setting.set('value', setting.get('default'));
setting.save(); setting.save();
}, },
/** /**
Save changes to a site setting Save changes to a site setting
@method save @method save
@param {Discourse.SiteSetting} setting The setting we've changed @param {Discourse.SiteSetting} setting The setting we've changed
**/ **/
save: function(setting) { save: function(setting) {
setting.save(); setting.save();
}, },
/** /**
Cancel changes to a site setting Cancel changes to a site setting
@method cancel @method cancel
@param {Discourse.SiteSetting} setting The setting we've changed but want to revert @param {Discourse.SiteSetting} setting The setting we've changed but want to revert
**/ **/
cancel: function(setting) { cancel: function(setting) {
setting.resetValue(); setting.resetValue();
}
} }
}); });

View File

@ -9,22 +9,25 @@
Discourse.AdminUserController = Discourse.ObjectController.extend({ Discourse.AdminUserController = Discourse.ObjectController.extend({
editingTitle: false, editingTitle: false,
toggleTitleEdit: function() {
this.set('editingTitle', !this.editingTitle);
},
saveTitle: function() {
Discourse.ajax("/users/" + this.get('username').toLowerCase(), {
data: {title: this.get('title')},
type: 'PUT'
}).then(null, function(e){
bootbox.alert(I18n.t("generic_error_with_reason", {error: "http: " + e.status + " - " + e.body}));
});
this.toggleTitleEdit();
},
showApproval: function() { showApproval: function() {
return Discourse.SiteSettings.must_approve_users; return Discourse.SiteSettings.must_approve_users;
}.property() }.property(),
actions: {
toggleTitleEdit: function() {
this.toggleProperty('editingTitle');
},
saveTitle: function() {
Discourse.ajax("/users/" + this.get('username').toLowerCase(), {
data: {title: this.get('title')},
type: 'PUT'
}).then(null, function(e){
bootbox.alert(I18n.t("generic_error_with_reason", {error: "http: " + e.status + " - " + e.body}));
});
this.send('toggleTitleEdit');
}
}
}); });

View File

@ -33,7 +33,7 @@ Discourse.AdminLogsStaffActionLogsRoute = Discourse.Route.extend({
return controller.show(); return controller.show();
}, },
events: { actions: {
showDetailsModal: function(logRecord) { showDetailsModal: function(logRecord) {
Discourse.Route.showModal(this, 'admin_staff_action_log_details', logRecord); Discourse.Route.showModal(this, 'admin_staff_action_log_details', logRecord);
this.controllerFor('modal').set('modalClass', 'log-details-modal'); this.controllerFor('modal').set('modalClass', 'log-details-modal');

View File

@ -13,24 +13,11 @@ Discourse.AdminSiteContentEditRoute = Discourse.Route.extend({
}, },
model: function(params) { model: function(params) {
var list = this.controllerFor('adminSiteContents').get('model'); var list = Discourse.SiteContentType.findAll();
// ember routing is fun ... this is what happens return list.then(function(items) {
// return items.findProperty("content_type", params.content_type);
// linkTo creates an Ember.LinkView , it marks an <a> with the class "active" });
// if the "context" of this dynamic route is equal to the model in the linkTo
// the route "context" is set here, so we want to make sure we have the exact
// same object, from Ember we have:
//
// if (handlerInfo.context !== object) { return false; }
//
// we could avoid this hack if Ember just compared .serialize(model) with .serialize(context)
//
// alternatively we could use some sort of identity map
//
// see also: https://github.com/emberjs/ember.js/issues/3005
return list.findProperty("content_type", params.content_type);
}, },
renderTemplate: function() { renderTemplate: function() {

View File

@ -3,21 +3,21 @@
<div class="full-width"> <div class="full-width">
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li>{{#linkTo 'admin.dashboard'}}{{i18n admin.dashboard.title}}{{/linkTo}}</li> <li>{{#link-to 'admin.dashboard'}}{{i18n admin.dashboard.title}}{{/link-to}}</li>
{{#if currentUser.admin}} {{#if currentUser.admin}}
<li>{{#linkTo 'admin.site_settings'}}{{i18n admin.site_settings.title}}{{/linkTo}}</li> <li>{{#link-to 'admin.site_settings'}}{{i18n admin.site_settings.title}}{{/link-to}}</li>
<li>{{#linkTo 'adminSiteContents'}}{{i18n admin.site_content.title}}{{/linkTo}}</li> <li>{{#link-to 'adminSiteContents'}}{{i18n admin.site_content.title}}{{/link-to}}</li>
{{/if}} {{/if}}
<li>{{#linkTo 'adminUsersList'}}{{i18n admin.users.title}}{{/linkTo}}</li> <li>{{#link-to 'adminUsersList'}}{{i18n admin.users.title}}{{/link-to}}</li>
{{#if currentUser.admin}} {{#if currentUser.admin}}
<li>{{#linkTo 'admin.groups'}}{{i18n admin.groups.title}}{{/linkTo}}</li> <li>{{#link-to 'admin.groups'}}{{i18n admin.groups.title}}{{/link-to}}</li>
{{/if}} {{/if}}
<li>{{#linkTo 'adminEmail'}}{{i18n admin.email.title}}{{/linkTo}}</li> <li>{{#link-to 'adminEmail'}}{{i18n admin.email.title}}{{/link-to}}</li>
<li>{{#linkTo 'adminFlags'}}{{i18n admin.flags.title}}{{/linkTo}}</li> <li>{{#link-to 'adminFlags'}}{{i18n admin.flags.title}}{{/link-to}}</li>
<li>{{#linkTo 'adminLogs'}}{{i18n admin.logs.title}}{{/linkTo}}</li> <li>{{#link-to 'adminLogs'}}{{i18n admin.logs.title}}{{/link-to}}</li>
{{#if currentUser.admin}} {{#if currentUser.admin}}
<li>{{#linkTo 'admin.customize'}}{{i18n admin.customize.title}}{{/linkTo}}</li> <li>{{#link-to 'admin.customize'}}{{i18n admin.customize.title}}{{/link-to}}</li>
<li>{{#linkTo 'admin.api'}}{{i18n admin.api.title}}{{/linkTo}}</li> <li>{{#link-to 'admin.api'}}{{i18n admin.api.title}}{{/link-to}}</li>
{{/if}} {{/if}}
</ul> </ul>

View File

@ -13,7 +13,7 @@
</p> </p>
<p class="actions"> <p class="actions">
<small>{{i18n admin.dashboard.last_checked}}: {{problemsTimestamp}}</small> <small>{{i18n admin.dashboard.last_checked}}: {{problemsTimestamp}}</small>
<button {{action loadProblems}} class="btn btn-small"><i class="icon icon-refresh"></i>{{i18n admin.dashboard.refresh_problems}}</button> <button {{action refreshProblems}} class="btn btn-small"><i class="icon icon-refresh"></i>{{i18n admin.dashboard.refresh_problems}}</button>
</p> </p>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
@ -25,7 +25,7 @@
<div class="problem-messages"> <div class="problem-messages">
<p> <p>
{{i18n admin.dashboard.no_problems}} {{i18n admin.dashboard.no_problems}}
<button {{action loadProblems}} class="btn btn-small"><i class="icon icon-refresh"></i>{{i18n admin.dashboard.refresh_problems}}</button> <button {{action refreshProblems}} class="btn btn-small"><i class="icon icon-refresh"></i>{{i18n admin.dashboard.refresh_problems}}</button>
</p> </p>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
@ -121,15 +121,15 @@
<table> <table>
<tr> <tr>
<td class="title"><i class='icon icon-trophy'></i> {{i18n admin.dashboard.admins}}</td> <td class="title"><i class='icon icon-trophy'></i> {{i18n admin.dashboard.admins}}</td>
<td class="value">{{#linkTo 'adminUsersList.admins'}}{{admins}}{{/linkTo}}</td> <td class="value">{{#link-to 'adminUsersList.admins'}}{{admins}}{{/link-to}}</td>
<td class="title"><i class='icon icon-ban-circle'></i> {{i18n admin.dashboard.banned}}</td> <td class="title"><i class='icon icon-ban-circle'></i> {{i18n admin.dashboard.banned}}</td>
<td class="value">{{#linkTo 'adminUsersList.banned'}}{{banned}}{{/linkTo}}</td> <td class="value">{{#link-to 'adminUsersList.banned'}}{{banned}}{{/link-to}}</td>
</tr> </tr>
<tr> <tr>
<td class="title"><i class='icon icon-magic'></i> {{i18n admin.dashboard.moderators}}</td> <td class="title"><i class='icon icon-magic'></i> {{i18n admin.dashboard.moderators}}</td>
<td class="value">{{#linkTo 'adminUsersList.moderators'}}{{moderators}}{{/linkTo}}</td> <td class="value">{{#link-to 'adminUsersList.moderators'}}{{moderators}}{{/link-to}}</td>
<td class="title"><i class='icon icon-ban-circle'></i> {{i18n admin.dashboard.blocked}}</td> <td class="title"><i class='icon icon-ban-circle'></i> {{i18n admin.dashboard.blocked}}</td>
<td class="value">{{#linkTo 'adminUsersList.blocked'}}{{blocked}}{{/linkTo}}</td> <td class="value">{{#link-to 'adminUsersList.blocked'}}{{blocked}}{{/link-to}}</td>
</tr> </tr>
</table> </table>
</div> </div>
@ -265,7 +265,7 @@
{{#each top_referrers.data}} {{#each top_referrers.data}}
<tbody> <tbody>
<tr> <tr>
<td class="title">{{#linkTo 'adminUser' this}}{{unbound username}}{{/linkTo}}</td> <td class="title">{{#link-to 'adminUser' this}}{{unbound username}}{{/link-to}}</td>
<td class="value">{{num_clicks}}</td> <td class="value">{{num_clicks}}</td>
<td class="value">{{num_topics}}</td> <td class="value">{{num_topics}}</td>
</tr> </tr>

View File

@ -1,9 +1,9 @@
<div class='admin-controls'> <div class='admin-controls'>
<div class='span15'> <div class='span15'>
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li>{{#linkTo 'adminEmail.index'}}{{i18n admin.email.settings}}{{/linkTo}}</li> <li>{{#link-to 'adminEmail.index'}}{{i18n admin.email.settings}}{{/link-to}}</li>
<li>{{#linkTo 'adminEmail.logs'}}{{i18n admin.email.logs}}{{/linkTo}}</li> <li>{{#link-to 'adminEmail.logs'}}{{i18n admin.email.logs}}{{/link-to}}</li>
<li>{{#linkTo 'adminEmail.previewDigest'}}{{i18n admin.email.preview_digest}}{{/linkTo}}</li> <li>{{#link-to 'adminEmail.previewDigest'}}{{i18n admin.email.preview_digest}}{{/link-to}}</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -15,8 +15,8 @@
<td>{{date created_at}}</td> <td>{{date created_at}}</td>
<td> <td>
{{#if user}} {{#if user}}
{{#linkTo 'adminUser' user}}{{avatar user imageSize="tiny"}}{{/linkTo}} {{#link-to 'adminUser' user}}{{avatar user imageSize="tiny"}}{{/link-to}}
{{#linkTo 'adminUser' user}}{{user.username}}{{/linkTo}} {{#link-to 'adminUser' user}}{{user.username}}{{/link-to}}
{{else}} {{else}}
&mdash; &mdash;
{{/if}} {{/if}}

View File

@ -11,9 +11,9 @@
<div class="span7 toggle"> <div class="span7 toggle">
<label>{{i18n admin.email.format}}</label> <label>{{i18n admin.email.format}}</label>
{{#if showHtml}} {{#if showHtml}}
<span>{{i18n admin.email.html}}</span> | <a href='#' {{action toggleProperty 'showHtml'}}>{{i18n admin.email.text}}</a> <span>{{i18n admin.email.html}}</span> | <a href='#' {{action toggleShowHtml}}>{{i18n admin.email.text}}</a>
{{else}} {{else}}
<a href='#' {{action toggleProperty 'showHtml'}}>{{i18n admin.email.html}}</a> | <span>{{i18n admin.email.text}}</span> <a href='#' {{action toggleShowHtml}}>{{i18n admin.email.html}}</a> | <span>{{i18n admin.email.text}}</span>
{{/if}} {{/if}}
</div> </div>
</div> </div>

View File

@ -1,8 +1,8 @@
<div class='admin-controls'> <div class='admin-controls'>
<div class='span15'> <div class='span15'>
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li>{{#linkTo 'adminFlags.active'}}{{i18n admin.flags.active}}{{/linkTo}}</li> <li>{{#link-to 'adminFlags.active'}}{{i18n admin.flags.active}}{{/link-to}}</li>
<li>{{#linkTo 'adminFlags.old'}}{{i18n admin.flags.old}}{{/linkTo}}</li> <li>{{#link-to 'adminFlags.old'}}{{i18n admin.flags.old}}{{/link-to}}</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -25,7 +25,7 @@
{{#each flaggedPost in content}} {{#each flaggedPost in content}}
<tr {{bindAttr class="flaggedPost.extraClasses"}}> <tr {{bindAttr class="flaggedPost.extraClasses"}}>
<td class='user'>{{#if flaggedPost.user}}{{#linkTo 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="small"}}{{/linkTo}}{{/if}}</td> <td class='user'>{{#if flaggedPost.user}}{{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="small"}}{{/link-to}}{{/if}}</td>
<td class='excerpt'>{{#if flaggedPost.topicHidden}}<i title='{{i18n topic_statuses.invisible.help}}' class='icon icon-eye-close'></i> {{/if}}<h3><a href='{{unbound flaggedPost.url}}'>{{flaggedPost.title}}</a></h3><br>{{{flaggedPost.excerpt}}} <td class='excerpt'>{{#if flaggedPost.topicHidden}}<i title='{{i18n topic_statuses.invisible.help}}' class='icon icon-eye-close'></i> {{/if}}<h3><a href='{{unbound flaggedPost.url}}'>{{flaggedPost.title}}</a></h3><br>{{{flaggedPost.excerpt}}}
</td> </td>
@ -35,7 +35,7 @@
{{#each flaggedPost.flaggers}} {{#each flaggedPost.flaggers}}
<tr> <tr>
<td> <td>
{{#linkTo 'adminUser' this.user}}{{avatar this.user imageSize="small"}} {{/linkTo}} {{#link-to 'adminUser' this.user}}{{avatar this.user imageSize="small"}} {{/link-to}}
</td> </td>
<td> <td>
{{date this.flaggedAt}} {{date this.flaggedAt}}
@ -54,7 +54,7 @@
<tr> <tr>
<td></td> <td></td>
<td class='message'> <td class='message'>
<div>{{#linkTo 'adminUser' user}}{{avatar user imageSize="small"}}{{/linkTo}} {{message}} <a href="{{unbound permalink}}"><button class='btn'><i class="icon-reply"></i> {{i18n admin.flags.view_message}}</button></a></div> <div>{{#link-to 'adminUser' user}}{{avatar user imageSize="small"}}{{/link-to}} {{message}} <a href="{{unbound permalink}}"><button class='btn'><i class="icon-reply"></i> {{i18n admin.flags.view_message}}</button></a></div>
</td> </td>
<td></td> <td></td>
<td></td> <td></td>

View File

@ -1,9 +1,9 @@
<div class='admin-controls'> <div class='admin-controls'>
<div class='span15'> <div class='span15'>
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li>{{#linkTo 'adminLogs.staffActionLogs'}}{{i18n admin.logs.staff_actions.title}}{{/linkTo}}</li> <li>{{#link-to 'adminLogs.staffActionLogs'}}{{i18n admin.logs.staff_actions.title}}{{/link-to}}</li>
<li>{{#linkTo 'adminLogs.screenedEmails'}}{{i18n admin.logs.screened_emails.title}}{{/linkTo}}</li> <li>{{#link-to 'adminLogs.screenedEmails'}}{{i18n admin.logs.screened_emails.title}}{{/link-to}}</li>
<li>{{#linkTo 'adminLogs.screenedUrls'}}{{i18n admin.logs.screened_urls.title}}{{/linkTo}}</li> <li>{{#link-to 'adminLogs.screenedUrls'}}{{i18n admin.logs.screened_urls.title}}{{/link-to}}</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
<div class="col value first staff_user"> <div class="col value first staff_user">
{{#linkTo 'adminUser' acting_user}}{{avatar acting_user imageSize="tiny"}}{{/linkTo}} {{#link-to 'adminUser' acting_user}}{{avatar acting_user imageSize="tiny"}}{{/link-to}}
<a {{action filterByStaffUser acting_user}}>{{acting_user.username}}</a> <a {{action filterByStaffUser acting_user}}>{{acting_user.username}}</a>
</div> </div>
<div class="col value action"> <div class="col value action">
@ -7,7 +7,7 @@
</div> </div>
<div class="col value subject"> <div class="col value subject">
{{#if target_user}} {{#if target_user}}
{{#linkTo 'adminUser' target_user}}{{avatar target_user imageSize="tiny"}}{{/linkTo}} {{#link-to 'adminUser' target_user}}{{avatar target_user imageSize="tiny"}}{{/link-to}}
<a {{action filterByTargetUser target_user}}>{{target_user.username}}</a> <a {{action filterByTargetUser target_user}}>{{target_user.username}}</a>
{{/if}} {{/if}}
{{#if subject}} {{#if subject}}

View File

@ -1,8 +1,8 @@
<tr> <tr>
<td class="title">{{title}}</td> <td class="title">{{title}}</td>
<td class="value">{{#linkTo 'adminUsersList.newuser'}}{{valueAtTrustLevel data 0}}{{/linkTo}}</td> <td class="value">{{#link-to 'adminUsersList.newuser'}}{{valueAtTrustLevel data 0}}{{/link-to}}</td>
<td class="value">{{#linkTo 'adminUsersList.basic'}}{{valueAtTrustLevel data 1}}{{/linkTo}}</td> <td class="value">{{#link-to 'adminUsersList.basic'}}{{valueAtTrustLevel data 1}}{{/link-to}}</td>
<td class="value">{{#linkTo 'adminUsersList.regular'}}{{valueAtTrustLevel data 2}}{{/linkTo}}</td> <td class="value">{{#link-to 'adminUsersList.regular'}}{{valueAtTrustLevel data 2}}{{/link-to}}</td>
<td class="value">{{#linkTo 'adminUsersList.leaders'}}{{valueAtTrustLevel data 3}}{{/linkTo}}</td> <td class="value">{{#link-to 'adminUsersList.leaders'}}{{valueAtTrustLevel data 3}}{{/link-to}}</td>
<td class="value">{{#linkTo 'adminUsersList.elders'}}{{valueAtTrustLevel data 4}}{{/linkTo}}</td> <td class="value">{{#link-to 'adminUsersList.elders'}}{{valueAtTrustLevel data 4}}{{/link-to}}</td>
</tr> </tr>

View File

@ -4,7 +4,7 @@
<ul> <ul>
{{#each type in model}} {{#each type in model}}
<li> <li>
{{#linkTo 'adminSiteContentEdit' type}}{{type.title}}{{/linkTo}} {{#link-to 'adminSiteContentEdit' type}}{{type.title}}{{/link-to}}
</li> </li>
{{/each}} {{/each}}
</ul> </ul>

View File

@ -4,10 +4,10 @@
<div class='field'>{{i18n user.username.title}}</div> <div class='field'>{{i18n user.username.title}}</div>
<div class='value'>{{username}}</div> <div class='value'>{{username}}</div>
<div class='controls'> <div class='controls'>
{{#linkTo 'userActivity' class="btn"}} {{#link-to 'userActivity' class="btn"}}
<i class='icon icon-user'></i> <i class='icon icon-user'></i>
{{i18n admin.user.show_public_profile}} {{i18n admin.user.show_public_profile}}
{{/linkTo}} {{/link-to}}
{{#if can_impersonate}} {{#if can_impersonate}}
<button class='btn' {{action impersonate target="content"}}> <button class='btn' {{action impersonate target="content"}}>
<i class='icon icon-screenshot'></i> <i class='icon icon-screenshot'></i>
@ -71,8 +71,8 @@
{{#if approved}} {{#if approved}}
{{i18n admin.user.approved_by}} {{i18n admin.user.approved_by}}
{{#linkTo 'adminUser' approved_by}}{{avatar approved_by imageSize="small"}}{{/linkTo}} {{#link-to 'adminUser' approved_by}}{{avatar approved_by imageSize="small"}}{{/link-to}}
{{#linkTo 'adminUser' approved_by}}{{approved_by.username}}{{/linkTo}} {{#link-to 'adminUser' approved_by}}{{approved_by.username}}{{/link-to}}
{{else}} {{else}}
{{i18n no_value}} {{i18n no_value}}
{{/if}} {{/if}}

View File

@ -1,15 +1,15 @@
<div class='admin-controls'> <div class='admin-controls'>
<div class='span15'> <div class='span15'>
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li>{{#linkTo 'adminUsersList.active'}}{{i18n admin.users.nav.active}}{{/linkTo}}</li> <li>{{#link-to 'adminUsersList.active'}}{{i18n admin.users.nav.active}}{{/link-to}}</li>
<li>{{#linkTo 'adminUsersList.new'}}{{i18n admin.users.nav.new}}{{/linkTo}}</li> <li>{{#link-to 'adminUsersList.new'}}{{i18n admin.users.nav.new}}{{/link-to}}</li>
{{#if Discourse.SiteSettings.must_approve_users}} {{#if Discourse.SiteSettings.must_approve_users}}
<li>{{#linkTo 'adminUsersList.pending'}}{{i18n admin.users.nav.pending}}{{/linkTo}}</li> <li>{{#link-to 'adminUsersList.pending'}}{{i18n admin.users.nav.pending}}{{/link-to}}</li>
{{/if}} {{/if}}
<li>{{#linkTo 'adminUsersList.admins'}}{{i18n admin.users.nav.admins}}{{/linkTo}}</li> <li>{{#link-to 'adminUsersList.admins'}}{{i18n admin.users.nav.admins}}{{/link-to}}</li>
<li>{{#linkTo 'adminUsersList.moderators'}}{{i18n admin.users.nav.moderators}}{{/linkTo}}</li> <li>{{#link-to 'adminUsersList.moderators'}}{{i18n admin.users.nav.moderators}}{{/link-to}}</li>
<li>{{#linkTo 'adminUsersList.banned'}}{{i18n admin.users.nav.banned}}{{/linkTo}}</li> <li>{{#link-to 'adminUsersList.banned'}}{{i18n admin.users.nav.banned}}{{/link-to}}</li>
<li>{{#linkTo 'adminUsersList.blocked'}}{{i18n admin.users.nav.blocked}}{{/linkTo}}</li> <li>{{#link-to 'adminUsersList.blocked'}}{{i18n admin.users.nav.blocked}}{{/link-to}}</li>
</ul> </ul>
</div> </div>
<div class='span5 username controls'> <div class='span5 username controls'>
@ -61,8 +61,8 @@
{{/if}} {{/if}}
</td> </td>
{{/if}} {{/if}}
<td>{{#linkTo 'adminUser' this}}{{avatar this imageSize="small"}}{{/linkTo}}</td> <td>{{#link-to 'adminUser' this}}{{avatar this imageSize="small"}}{{/link-to}}</td>
<td>{{#linkTo 'adminUser' this}}{{unbound username}}{{/linkTo}}</td> <td>{{#link-to 'adminUser' this}}{{unbound username}}{{/link-to}}</td>
<td>{{shorten email}}</td> <td>{{shorten email}}</td>
<td>{{{unbound last_emailed_age}}}</td> <td>{{{unbound last_emailed_age}}}</td>
<td>{{{unbound last_seen_age}}}</td> <td>{{{unbound last_seen_age}}}</td>

View File

@ -13,18 +13,18 @@
<% <%
if Rails.env.development? if Rails.env.development?
require_asset ("./external_development/jquery-2.0.3.js") require_asset ("development/jquery-2.0.3.js")
else else
require_asset ("./external_production/jquery-2.0.3.min.js") require_asset ("production/jquery-2.0.3.min.js")
end end
require_asset ("./external/jquery.ui.widget.js") require_asset ("jquery.ui.widget.js")
require_asset ("./external/handlebars.js") require_asset ("handlebars.js")
if Rails.env.development? if Rails.env.development?
require_asset ("./external_development/ember.js") require_asset ("development/ember.js")
else else
require_asset ("./external_production/ember.js") require_asset ("production/ember.js")
end end
require_asset ("./main_include.js") require_asset ("./main_include.js")

View File

@ -28,7 +28,7 @@ Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
return u + url; return u + url;
}, },
resolver: Discourse.Resolver, Resolver: Discourse.Resolver,
titleChanged: function() { titleChanged: function() {
var title = ""; var title = "";
@ -112,7 +112,7 @@ Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
if ($currentTarget.attr('target')) { return; } if ($currentTarget.attr('target')) { return; }
if ($currentTarget.data('auto-route')) { return; } if ($currentTarget.data('auto-route')) { return; }
// If it's an ember #linkTo skip it // If it's an ember #link-to skip it
if ($currentTarget.hasClass('ember-view')) { return; } if ($currentTarget.hasClass('ember-view')) { return; }
if ($currentTarget.hasClass('lightbox')) { return; } if ($currentTarget.hasClass('lightbox')) { return; }

View File

@ -8,15 +8,19 @@
@module Discourse @module Discourse
**/ **/
Discourse.AvatarSelectorController = Discourse.Controller.extend(Discourse.ModalFunctionality, { Discourse.AvatarSelectorController = Discourse.Controller.extend(Discourse.ModalFunctionality, {
useUploadedAvatar: function() {
this.set("use_uploaded_avatar", true);
},
useGravatar: function() { actions: {
this.set("use_uploaded_avatar", false); useUploadedAvatar: function() {
this.set("use_uploaded_avatar", true);
},
useGravatar: function() {
this.set("use_uploaded_avatar", false);
}
}, },
avatarTemplate: function() { avatarTemplate: function() {
return this.get("use_uploaded_avatar") ? this.get("uploaded_avatar_template") : this.get("gravatar_template"); return this.get("use_uploaded_avatar") ? this.get("uploaded_avatar_template") : this.get("gravatar_template");
}.property("use_uploaded_avatar", "uploaded_avatar_template", "gravatar_template") }.property("use_uploaded_avatar", "uploaded_avatar_template", "gravatar_template")
}); });

View File

@ -17,15 +17,6 @@ Discourse.ComposerController = Discourse.Controller.extend({
this.set('similarTopics', Em.A()); this.set('similarTopics', Em.A());
}, },
togglePreview: function() {
this.get('model').togglePreview();
},
// Import a quote from the post
importQuote: function() {
this.get('model').importQuote();
},
updateDraftStatus: function() { updateDraftStatus: function() {
this.get('model').updateDraftStatus(); this.get('model').updateDraftStatus();
}, },
@ -39,84 +30,122 @@ Discourse.ComposerController = Discourse.Controller.extend({
return Discourse.Category.list(); return Discourse.Category.list();
}.property(), }.property(),
save: function(force) { actions: {
var composer = this.get('model'),
composerController = this;
if( composer.get('cantSubmitPost') ) { // Toggle the reply view
this.set('view.showTitleTip', Date.now()); toggle: function() {
this.set('view.showCategoryTip', Date.now()); this.closeAutocomplete();
this.set('view.showReplyTip', Date.now()); switch (this.get('model.composeState')) {
return; case Discourse.Composer.OPEN:
} if (this.blank('model.reply') && this.blank('model.title')) {
this.close();
} else {
this.shrink();
}
break;
case Discourse.Composer.DRAFT:
this.set('model.composeState', Discourse.Composer.OPEN);
break;
case Discourse.Composer.SAVING:
this.close();
}
return false;
},
composer.set('disableDrafts', true);
// for now handle a very narrow use case togglePreview: function() {
// if we are replying to a topic AND not on the topic pop the window up this.get('model').togglePreview();
if(!force && composer.get('replyingToTopic')) { },
var topic = this.get('topic');
if (!topic || topic.get('id') !== composer.get('topic.id'))
{
var message = I18n.t("composer.posting_not_on_topic", {title: this.get('model.topic.title')});
var buttons = [{ // Import a quote from the post
"label": I18n.t("composer.cancel"), importQuote: function() {
"class": "cancel", this.get('model').importQuote();
"link": true },
}];
cancel: function() {
this.cancelComposer();
},
save: function(force) {
var composer = this.get('model'),
composerController = this;
if( composer.get('cantSubmitPost') ) {
this.set('view.showTitleTip', Date.now());
this.set('view.showCategoryTip', Date.now());
this.set('view.showReplyTip', Date.now());
return;
}
composer.set('disableDrafts', true);
// for now handle a very narrow use case
// if we are replying to a topic AND not on the topic pop the window up
if(!force && composer.get('replyingToTopic')) {
var topic = this.get('topic');
if (!topic || topic.get('id') !== composer.get('topic.id'))
{
var message = I18n.t("composer.posting_not_on_topic", {title: this.get('model.topic.title')});
var buttons = [{
"label": I18n.t("composer.cancel"),
"class": "cancel",
"link": true
}];
if(topic) {
buttons.push({
"label": I18n.t("composer.reply_here") + "<br/><div class='topic-title overflow-ellipsis'>" + topic.get('title') + "</div>",
"class": "btn btn-reply-here",
"callback": function(){
composer.set('topic', topic);
composer.set('post', null);
composerController.save(true);
}
});
}
if(topic) {
buttons.push({ buttons.push({
"label": I18n.t("composer.reply_here") + "<br/><div class='topic-title overflow-ellipsis'>" + topic.get('title') + "</div>", "label": I18n.t("composer.reply_original") + "<br/><div class='topic-title overflow-ellipsis'>" + this.get('model.topic.title') + "</div>",
"class": "btn btn-reply-here", "class": "btn-primary btn-reply-on-original",
"callback": function(){ "callback": function(){
composer.set('topic', topic);
composer.set('post', null);
composerController.save(true); composerController.save(true);
} }
}); });
bootbox.dialog(message, buttons, {"classes": "reply-where-modal"});
return;
}
}
return composer.save({
imageSizes: this.get('view').imageSizes()
}).then(function(opts) {
// If we replied as a new topic successfully, remove the draft.
if (composerController.get('replyAsNewTopicDraft')) {
composerController.destroyDraft();
} }
buttons.push({ opts = opts || {};
"label": I18n.t("composer.reply_original") + "<br/><div class='topic-title overflow-ellipsis'>" + this.get('model.topic.title') + "</div>", composerController.close();
"class": "btn-primary btn-reply-on-original",
"callback": function(){
composerController.save(true);
}
});
bootbox.dialog(message, buttons, {"classes": "reply-where-modal"}); var currentUser = Discourse.User.current();
return; if (composer.get('creatingTopic')) {
} currentUser.set('topic_count', currentUser.get('topic_count') + 1);
} else {
currentUser.set('reply_count', currentUser.get('reply_count') + 1);
}
Discourse.URL.routeTo(opts.post.get('url'));
}, function(error) {
composer.set('disableDrafts', false);
bootbox.alert(error);
});
} }
return composer.save({
imageSizes: this.get('view').imageSizes()
}).then(function(opts) {
// If we replied as a new topic successfully, remove the draft.
if (composerController.get('replyAsNewTopicDraft')) {
composerController.destroyDraft();
}
opts = opts || {};
composerController.close();
var currentUser = Discourse.User.current();
if (composer.get('creatingTopic')) {
currentUser.set('topic_count', currentUser.get('topic_count') + 1);
} else {
currentUser.set('reply_count', currentUser.get('reply_count') + 1);
}
Discourse.URL.routeTo(opts.post.get('url'));
}, function(error) {
composer.set('disableDrafts', false);
bootbox.alert(error);
});
}, },
/** /**
Checks to see if a reply has been typed. This is signaled by a keyUp Checks to see if a reply has been typed. This is signaled by a keyUp
event in a view. event in a view.
@ -230,7 +259,7 @@ Discourse.ComposerController = Discourse.Controller.extend({
} else { } else {
opts.tested = true; opts.tested = true;
if (!opts.ignoreIfChanged) { if (!opts.ignoreIfChanged) {
this.cancel().then(function() { composerController.open(opts); }, this.cancelComposer().then(function() { composerController.open(opts); },
function() { return promise.reject(); }); function() { return promise.reject(); });
} }
return promise; return promise;
@ -278,7 +307,7 @@ Discourse.ComposerController = Discourse.Controller.extend({
} }
}, },
cancel: function() { cancelComposer: function() {
var composerController = this; var composerController = this;
return Ember.Deferred.promise(function (promise) { return Ember.Deferred.promise(function (promise) {
@ -332,26 +361,6 @@ Discourse.ComposerController = Discourse.Controller.extend({
$('#wmd-input').autocomplete({ cancel: true }); $('#wmd-input').autocomplete({ cancel: true });
}, },
// Toggle the reply view
toggle: function() {
this.closeAutocomplete();
switch (this.get('model.composeState')) {
case Discourse.Composer.OPEN:
if (this.blank('model.reply') && this.blank('model.title')) {
this.close();
} else {
this.shrink();
}
break;
case Discourse.Composer.DRAFT:
this.set('model.composeState', Discourse.Composer.OPEN);
break;
case Discourse.Composer.SAVING:
this.close();
}
return false;
},
// ESC key hit // ESC key hit
hitEsc: function() { hitEsc: function() {
if (this.get('model.viewOpen')) { if (this.get('model.viewOpen')) {

View File

@ -20,6 +20,18 @@ Discourse.ComposerMessagesController = Ember.ArrayController.extend({
this.reset(); this.reset();
}, },
actions: {
/**
Closes and hides a message.
@method closeMessage
@params {Object} message The message to dismiss
**/
closeMessage: function(message) {
this.removeObject(message);
}
},
/** /**
Displays a new message Displays a new message
@ -37,16 +49,6 @@ Discourse.ComposerMessagesController = Ember.ArrayController.extend({
} }
}, },
/**
Closes and hides a message.
@method closeMessage
@params {Object} message The message to dismiss
**/
closeMessage: function(message) {
this.removeObject(message);
},
/** /**
Resets all active messages. For example if composing a new post. Resets all active messages. For example if composing a new post.

View File

@ -39,18 +39,6 @@ Discourse.EditCategoryController = Discourse.ObjectController.extend(Discourse.M
this.set('controllers.modal.title', this.get('title')); this.set('controllers.modal.title', this.get('title'));
}.observes('title'), }.observes('title'),
selectGeneral: function() {
this.set('selectedTab', 'general');
},
selectSecurity: function() {
this.set('selectedTab', 'security');
},
selectSettings: function() {
this.set('selectedTab', 'settings');
},
disabled: function() { disabled: function() {
if (this.get('saving') || this.get('deleting')) return true; if (this.get('saving') || this.get('deleting')) return true;
if (!this.get('name')) return true; if (!this.get('name')) return true;
@ -103,83 +91,97 @@ Discourse.EditCategoryController = Discourse.ObjectController.extend(Discourse.M
return I18n.t('category.delete'); return I18n.t('category.delete');
}.property(), }.property(),
showCategoryTopic: function() { actions: {
this.send('closeModal');
Discourse.URL.routeTo(this.get('topic_url'));
return false;
},
editPermissions: function(){ selectGeneral: function() {
this.set('editingPermissions', true); this.set('selectedTab', 'general');
}, },
addPermission: function(group, permission_id){ selectSecurity: function() {
this.get('model').addPermission({group_name: group + "", permission: Discourse.PermissionType.create({id: permission_id})}); this.set('selectedTab', 'security');
}, },
removePermission: function(permission){ selectSettings: function() {
this.get('model').removePermission(permission); this.set('selectedTab', 'settings');
}, },
saveCategory: function() { showCategoryTopic: function() {
var categoryController = this; this.send('closeModal');
this.set('saving', true); Discourse.URL.routeTo(this.get('topic_url'));
return false;
},
editPermissions: function(){
this.set('editingPermissions', true);
},
addPermission: function(group, permission_id){
this.get('model').addPermission({group_name: group + "", permission: Discourse.PermissionType.create({id: permission_id})});
},
removePermission: function(permission){
this.get('model').removePermission(permission);
},
saveCategory: function() {
var categoryController = this;
this.set('saving', true);
if( this.get('isUncategorized') ) { if( this.get('isUncategorized') ) {
$.when( $.when(
Discourse.SiteSetting.update('uncategorized_color', this.get('color')), Discourse.SiteSetting.update('uncategorized_color', this.get('color')),
Discourse.SiteSetting.update('uncategorized_text_color', this.get('text_color')), Discourse.SiteSetting.update('uncategorized_text_color', this.get('text_color')),
Discourse.SiteSetting.update('uncategorized_name', this.get('name')) Discourse.SiteSetting.update('uncategorized_name', this.get('name'))
).then(function(result) { ).then(function(result) {
// success
categoryController.send('closeModal');
// We can't redirect to the uncategorized category on save because the slug
// might have changed.
Discourse.URL.redirectTo("/categories");
}, function(errors) {
// errors
if(errors.length === 0) errors.push(I18n.t("category.save_error"));
categoryController.displayErrors(errors);
categoryController.set('saving', false);
});
} else {
this.get('model').save().then(function(result) {
// success
categoryController.send('closeModal');
Discourse.URL.redirectTo("/category/" + Discourse.Category.slugFor(result.category));
}, function(errors) {
// errors
if(errors.length === 0) errors.push(I18n.t("category.creation_error"));
categoryController.displayErrors(errors);
categoryController.set('saving', false);
});
}
},
deleteCategory: function() {
var categoryController = this;
this.set('deleting', true);
$('#discourse-modal').modal('hide');
bootbox.confirm(I18n.t("category.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
if (result) {
categoryController.get('model').destroy().then(function(){
// success // success
categoryController.send('closeModal'); categoryController.send('closeModal');
// We can't redirect to the uncategorized category on save because the slug
// might have changed.
Discourse.URL.redirectTo("/categories"); Discourse.URL.redirectTo("/categories");
}, function(jqXHR){ }, function(errors) {
// error // errors
$('#discourse-modal').modal('show'); if(errors.length === 0) errors.push(I18n.t("category.save_error"));
categoryController.displayErrors([I18n.t("category.delete_error")]); categoryController.displayErrors(errors);
categoryController.set('deleting', false); categoryController.set('saving', false);
}); });
} else { } else {
$('#discourse-modal').modal('show'); this.get('model').save().then(function(result) {
categoryController.set('deleting', false); // success
categoryController.send('closeModal');
Discourse.URL.redirectTo("/category/" + Discourse.Category.slugFor(result.category));
}, function(errors) {
// errors
if(errors.length === 0) errors.push(I18n.t("category.creation_error"));
categoryController.displayErrors(errors);
categoryController.set('saving', false);
});
} }
}); },
deleteCategory: function() {
var categoryController = this;
this.set('deleting', true);
$('#discourse-modal').modal('hide');
bootbox.confirm(I18n.t("category.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
if (result) {
categoryController.get('model').destroy().then(function(){
// success
categoryController.send('closeModal');
Discourse.URL.redirectTo("/categories");
}, function(jqXHR){
// error
$('#discourse-modal').modal('show');
categoryController.displayErrors([I18n.t("category.delete_error")]);
categoryController.set('deleting', false);
});
} else {
$('#discourse-modal').modal('show');
categoryController.set('deleting', false);
}
});
}
} }
}); });

View File

@ -20,12 +20,14 @@ Discourse.EditTopicAutoCloseController = Discourse.ObjectController.extend(Disco
} }
}.observes('details.auto_close_at'), }.observes('details.auto_close_at'),
saveAutoClose: function() { actions: {
this.setAutoClose( parseFloat(this.get('auto_close_days')) ); saveAutoClose: function() {
}, this.setAutoClose( parseFloat(this.get('auto_close_days')) );
},
removeAutoClose: function() { removeAutoClose: function() {
this.setAutoClose(null); this.setAutoClose(null);
}
}, },
setAutoClose: function(days) { setAutoClose: function(days) {

View File

@ -13,10 +13,6 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
this.set('selected', null); this.set('selected', null);
}, },
changePostActionType: function(action) {
this.set('selected', action);
},
submitEnabled: function() { submitEnabled: function() {
var selected = this.get('selected'); var selected = this.get('selected');
if (!selected) return false; if (!selected) return false;
@ -46,25 +42,31 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
} }
}.property('selected.is_custom_flag'), }.property('selected.is_custom_flag'),
takeAction: function() { actions: {
this.createFlag({takeAction: true}); takeAction: function() {
this.set('hidden', true); this.send('createFlag', {takeAction: true});
}, this.set('hidden', true);
},
createFlag: function(opts) { createFlag: function(opts) {
var flagController = this; var flagController = this;
var postAction = this.get('actionByName.' + this.get('selected.name_key')); var postAction = this.get('actionByName.' + this.get('selected.name_key'));
var params = this.get('selected.is_custom_flag') ? {message: this.get('message')} : {}; var params = this.get('selected.is_custom_flag') ? {message: this.get('message')} : {};
if (opts) params = $.extend(params, opts); if (opts) params = $.extend(params, opts);
$('#discourse-modal').modal('hide'); $('#discourse-modal').modal('hide');
postAction.act(params).then(function() { postAction.act(params).then(function() {
flagController.send('closeModal'); flagController.send('closeModal');
}, function(errors) { }, function(errors) {
$('#discourse-modal').modal('show'); $('#discourse-modal').modal('show');
flagController.displayErrors(errors); flagController.displayErrors(errors);
}); });
},
changePostActionType: function(action) {
this.set('selected', action);
}
}, },
canDeleteSpammer: function() { canDeleteSpammer: function() {

View File

@ -10,12 +10,6 @@ Discourse.HeaderController = Discourse.Controller.extend({
topic: null, topic: null,
showExtraInfo: null, showExtraInfo: null,
toggleStar: function() {
var topic = this.get('topic');
if (topic) topic.toggleStar();
return false;
},
categories: function() { categories: function() {
return Discourse.Category.list(); return Discourse.Category.list();
}.property(), }.property(),
@ -36,8 +30,16 @@ Discourse.HeaderController = Discourse.Controller.extend({
return Discourse.SiteSettings.enable_mobile_theme; return Discourse.SiteSettings.enable_mobile_theme;
}.property(), }.property(),
toggleMobileView: function() { actions: {
Discourse.Mobile.toggleMobileView(); toggleStar: function() {
var topic = this.get('topic');
if (topic) topic.toggleStar();
return false;
},
toggleMobileView: function() {
Discourse.Mobile.toggleMobileView();
}
} }
}); });

View File

@ -25,22 +25,25 @@ Discourse.InviteController = Discourse.ObjectController.extend(Discourse.ModalFu
return I18n.t('topic.invite_reply.success', { email: this.get('email') }); return I18n.t('topic.invite_reply.success', { email: this.get('email') });
}.property('email'), }.property('email'),
createInvite: function() { actions: {
if (this.get('disabled')) return; createInvite: function() {
if (this.get('disabled')) return;
var inviteController = this; var inviteController = this;
this.set('saving', true); this.set('saving', true);
this.set('error', false); this.set('error', false);
this.get('model').inviteUser(this.get('email')).then(function() { this.get('model').inviteUser(this.get('email')).then(function() {
// Success // Success
inviteController.set('saving', false); inviteController.set('saving', false);
return inviteController.set('finished', true); return inviteController.set('finished', true);
}, function() { }, function() {
// Failure // Failure
inviteController.set('error', true); inviteController.set('error', true);
return inviteController.set('saving', false); return inviteController.set('saving', false);
}); });
return false; return false;
}
} }
}); });

View File

@ -113,14 +113,16 @@ Discourse.ListController = Discourse.Controller.extend({
}.observes('filterMode', 'category'), }.observes('filterMode', 'category'),
// Create topic button // Create topic button
createTopic: function() { actions: {
this.get('controllers.composer').open({ createTopic: function() {
categoryId: this.get('category.id'), this.get('controllers.composer').open({
action: Discourse.Composer.CREATE_TOPIC, categoryId: this.get('category.id'),
draft: this.get('draft'), action: Discourse.Composer.CREATE_TOPIC,
draftKey: this.get('draft_key'), draft: this.get('draft'),
draftSequence: this.get('draft_sequence') draftKey: this.get('draft_key'),
}); draftSequence: this.get('draft_sequence')
});
}
}, },
canEditCategory: function() { canEditCategory: function() {

View File

@ -27,32 +27,34 @@ Discourse.ListTopicsController = Discourse.ObjectController.extend({
} }
}.observes('content.draft'), }.observes('content.draft'),
// Star a topic actions: {
toggleStar: function(topic) { // Star a topic
topic.toggleStar(); toggleStar: function(topic) {
}, topic.toggleStar();
},
// clear a pinned topic // clear a pinned topic
clearPin: function(topic) { clearPin: function(topic) {
topic.clearPin(); topic.clearPin();
}, },
toggleRankDetails: function() { toggleRankDetails: function() {
this.toggleProperty('rankDetailsVisible'); this.toggleProperty('rankDetailsVisible');
}, },
createTopic: function() { createTopic: function() {
this.get('controllers.list').createTopic(); this.get('controllers.list').send('createTopic');
}, },
// Show newly inserted topics // Show newly inserted topics
showInserted: function(e) { showInserted: function(e) {
var tracker = Discourse.TopicTrackingState.current(); var tracker = Discourse.TopicTrackingState.current();
// Move inserted into topics // Move inserted into topics
this.get('content').loadBefore(tracker.get('newIncoming')); this.get('content').loadBefore(tracker.get('newIncoming'));
tracker.resetTracking(); tracker.resetTracking();
return false; return false;
}
}, },
allLoaded: function() { allLoaded: function() {

View File

@ -46,7 +46,7 @@ Discourse.MergeTopicController = Discourse.ObjectController.extend(Discourse.Sel
promise.then(function(result) { promise.then(function(result) {
// Posts moved // Posts moved
mergeTopicController.send('closeModal'); mergeTopicController.send('closeModal');
mergeTopicController.get('topicController').toggleMultiSelect(); mergeTopicController.get('topicController').send('toggleMultiSelect');
Em.run.next(function() { Discourse.URL.routeTo(result.url); }); Em.run.next(function() { Discourse.URL.routeTo(result.url); });
}, function() { }, function() {
// Error moving posts // Error moving posts

View File

@ -37,53 +37,54 @@ Discourse.PreferencesController = Discourse.ObjectController.extend({
{ name: I18n.t('user.new_topic_duration.after_n_weeks', { count: 1 }), value: 7 * 60 * 24 }, { name: I18n.t('user.new_topic_duration.after_n_weeks', { count: 1 }), value: 7 * 60 * 24 },
{ name: I18n.t('user.new_topic_duration.last_here'), value: -2 }], { name: I18n.t('user.new_topic_duration.last_here'), value: -2 }],
save: function() {
var preferencesController = this;
this.set('saving', true);
this.set('saved', false);
// Cook the bio for preview
var model = this.get('model');
return model.save().then(function() {
// model was saved
preferencesController.set('saving', false);
if (Discourse.User.currentProp('id') === model.get('id')) {
Discourse.User.currentProp('name', model.get('name'));
}
preferencesController.set('bio_cooked',
Discourse.Markdown.cook(preferencesController.get('bio_raw')));
preferencesController.set('saved', true);
}, function() {
// model failed to save
preferencesController.set('saving', false);
alert(I18n.t('generic_error'));
});
},
saveButtonText: function() { saveButtonText: function() {
return this.get('saving') ? I18n.t('saving') : I18n.t('save'); return this.get('saving') ? I18n.t('saving') : I18n.t('save');
}.property('saving'), }.property('saving'),
changePassword: function() { actions: {
var preferencesController = this; save: function() {
if (!this.get('passwordProgress')) { var self = this;
this.set('passwordProgress', I18n.t("user.change_password.in_progress")); this.set('saving', true);
return this.get('model').changePassword().then(function() { this.set('saved', false);
// password changed
preferencesController.setProperties({ // Cook the bio for preview
changePasswordProgress: false, var model = this.get('model');
passwordProgress: I18n.t("user.change_password.success") return model.save().then(function() {
}); // model was saved
self.set('saving', false);
if (Discourse.User.currentProp('id') === model.get('id')) {
Discourse.User.currentProp('name', model.get('name'));
}
self.set('bio_cooked', Discourse.Markdown.cook(self.get('bio_raw')));
self.set('saved', true);
}, function() { }, function() {
// password failed to change // model failed to save
preferencesController.setProperties({ self.set('saving', false);
changePasswordProgress: false, alert(I18n.t('generic_error'));
passwordProgress: I18n.t("user.change_password.error")
});
}); });
},
changePassword: function() {
var self = this;
if (!this.get('passwordProgress')) {
this.set('passwordProgress', I18n.t("user.change_password.in_progress"));
return this.get('model').changePassword().then(function() {
// password changed
self.setProperties({
changePasswordProgress: false,
passwordProgress: I18n.t("user.change_password.success")
});
}, function() {
// password failed to change
self.setProperties({
changePasswordProgress: false,
passwordProgress: I18n.t("user.change_password.error")
});
});
}
} }
} }
}); });

View File

@ -22,17 +22,16 @@ Discourse.PreferencesEmailController = Discourse.ObjectController.extend({
return I18n.t("user.change"); return I18n.t("user.change");
}.property('saving'), }.property('saving'),
changeEmail: function() { actions: {
var preferencesEmailController = this; changeEmail: function() {
this.set('saving', true); var self = this;
return this.get('content').changeEmail(this.get('newEmail')).then(function() { this.set('saving', true);
preferencesEmailController.set('success', true); return this.get('content').changeEmail(this.get('newEmail')).then(function() {
}, function() { self.set('success', true);
preferencesEmailController.setProperties({ }, function() {
error: true, self.setProperties({ error: true, saving: false });
saving: false
}); });
}); }
} }
}); });

View File

@ -41,21 +41,24 @@ Discourse.PreferencesUsernameController = Discourse.ObjectController.extend({
return I18n.t("user.change"); return I18n.t("user.change");
}.property('saving'), }.property('saving'),
changeUsername: function() { actions: {
var preferencesUsernameController = this; changeUsername: function() {
return bootbox.confirm(I18n.t("user.change_username.confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) { var preferencesUsernameController = this;
if (result) { return bootbox.confirm(I18n.t("user.change_username.confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
preferencesUsernameController.set('saving', true); if (result) {
preferencesUsernameController.get('content').changeUsername(preferencesUsernameController.get('newUsername')).then(function() { preferencesUsernameController.set('saving', true);
Discourse.URL.redirectTo("/users/" + preferencesUsernameController.get('newUsername').toLowerCase() + "/preferences"); preferencesUsernameController.get('content').changeUsername(preferencesUsernameController.get('newUsername')).then(function() {
}, function() { Discourse.URL.redirectTo("/users/" + preferencesUsernameController.get('newUsername').toLowerCase() + "/preferences");
// error }, function() {
preferencesUsernameController.set('error', true); // error
preferencesUsernameController.set('saving', false); preferencesUsernameController.set('error', true);
}); preferencesUsernameController.set('saving', false);
} });
}); }
});
}
} }
}); });

View File

@ -62,14 +62,20 @@ Discourse.SearchController = Em.ArrayController.extend(Discourse.Presence, {
}.property('typeFilter', 'loading'), }.property('typeFilter', 'loading'),
termChanged: function() { termChanged: function() {
this.cancelType(); this.cancelTypeFilter();
}.observes('term'), }.observes('term'),
moreOfType: function(type) { actions: {
this.set('typeFilter', type); moreOfType: function(type) {
this.set('typeFilter', type);
},
cancelType: function() {
this.cancelTypeFilter();
}
}, },
cancelType: function() { cancelTypeFilter: function() {
this.set('typeFilter', null); this.set('typeFilter', null);
}, },

View File

@ -11,10 +11,12 @@ Discourse.ShareController = Discourse.Controller.extend({
needs: ['topic'], needs: ['topic'],
// Close the share controller // Close the share controller
close: function() { actions: {
this.set('link', ''); close: function() {
this.set('postNumber', ''); this.set('link', '');
return false; this.set('postNumber', '');
return false;
}
}, },
shareLinks: function() { shareLinks: function() {

View File

@ -42,7 +42,7 @@ Discourse.SplitTopicController = Discourse.ObjectController.extend(Discourse.Sel
}).then(function(result) { }).then(function(result) {
// Posts moved // Posts moved
self.send('closeModal'); self.send('closeModal');
self.get('topicController').toggleMultiSelect(); self.get('topicController').send('toggleMultiSelect');
Em.run.next(function() { Discourse.URL.routeTo(result.url); }); Em.run.next(function() { Discourse.URL.routeTo(result.url); });
}, function() { }, function() {
// Error moving posts // Error moving posts

View File

@ -10,12 +10,14 @@ Discourse.TopicAdminMenuController = Discourse.ObjectController.extend({
menuVisible: false, menuVisible: false,
needs: ['modal'], needs: ['modal'],
show: function() { actions: {
this.set('menuVisible', true); show: function() {
}, this.set('menuVisible', true);
},
hide: function() { hide: function() {
this.set('menuVisible', false); this.set('menuVisible', false);
}
}, },
showRecover: Em.computed.and('deleted', 'details.can_recover') showRecover: Em.computed.and('deleted', 'details.can_recover')

View File

@ -21,6 +21,209 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
this.set('selectedReplies', new Em.Set()); this.set('selectedReplies', new Em.Set());
}, },
actions: {
jumpTop: function() {
Discourse.URL.routeTo(this.get('url'));
},
jumpBottom: function() {
Discourse.URL.routeTo(this.get('lastPostUrl'));
},
toggleSummary: function() {
this.toggleProperty('summaryCollapsed');
},
selectAll: function() {
var posts = this.get('postStream.posts');
var selectedPosts = this.get('selectedPosts');
if (posts) {
selectedPosts.addObjects(posts);
}
this.set('allPostsSelected', true);
},
deselectAll: function() {
this.get('selectedPosts').clear();
this.get('selectedReplies').clear();
this.set('allPostsSelected', false);
},
/**
Toggle a participant for filtering
@method toggleParticipant
**/
toggleParticipant: function(user) {
this.get('postStream').toggleParticipant(Em.get(user, 'username'));
},
editTopic: function() {
if (!this.get('details.can_edit')) return false;
this.setProperties({
editingTopic: true,
newTitle: this.get('title'),
newCategoryId: this.get('category_id')
});
return false;
},
// close editing mode
cancelEditingTopic: function() {
this.set('editingTopic', false);
},
toggleMultiSelect: function() {
this.toggleProperty('multiSelect');
},
finishedEditingTopic: function() {
var topicController = this;
if (this.get('editingTopic')) {
var topic = this.get('model');
// Topic title hasn't been sanitized yet, so the template shouldn't trust it.
this.set('topicSaving', true);
// manually update the titles & category
topic.setProperties({
title: this.get('newTitle'),
category_id: parseInt(this.get('newCategoryId'), 10),
fancy_title: this.get('newTitle')
});
// save the modifications
topic.save().then(function(result){
// update the title if it has been changed (cleaned up) server-side
var title = result.basic_topic.title;
var fancy_title = result.basic_topic.fancy_title;
topic.setProperties({
title: title,
fancy_title: fancy_title
});
topicController.set('topicSaving', false);
}, function(error) {
topicController.set('editingTopic', true);
topicController.set('topicSaving', false);
if (error && error.responseText) {
bootbox.alert($.parseJSON(error.responseText).errors[0]);
} else {
bootbox.alert(I18n.t('generic_error'));
}
});
// close editing mode
topicController.set('editingTopic', false);
}
},
toggledSelectedPost: function(post) {
this.performTogglePost(post);
},
toggledSelectedPostReplies: function(post) {
var selectedReplies = this.get('selectedReplies');
if (this.performTogglePost(post)) {
selectedReplies.addObject(post);
} else {
selectedReplies.removeObject(post);
}
},
deleteSelected: function() {
var self = this;
bootbox.confirm(I18n.t("post.delete.confirm", { count: this.get('selectedPostsCount')}), function(result) {
if (result) {
// If all posts are selected, it's the same thing as deleting the topic
if (self.get('allPostsSelected')) {
return self.deleteTopic();
}
var selectedPosts = self.get('selectedPosts'),
selectedReplies = self.get('selectedReplies'),
postStream = self.get('postStream'),
toRemove = new Ember.Set();
Discourse.Post.deleteMany(selectedPosts, selectedReplies);
postStream.get('posts').forEach(function (p) {
if (self.postSelected(p)) { toRemove.addObject(p); }
});
postStream.removePosts(toRemove);
self.send('toggleMultiSelect');
}
});
},
toggleVisibility: function() {
this.get('content').toggleStatus('visible');
},
toggleClosed: function() {
this.get('content').toggleStatus('closed');
},
togglePinned: function() {
this.get('content').toggleStatus('pinned');
},
toggleArchived: function() {
this.get('content').toggleStatus('archived');
},
convertToRegular: function() {
this.get('content').convertArchetype('regular');
},
// Toggle the star on the topic
toggleStar: function() {
this.get('content').toggleStar();
},
/**
Clears the pin from a topic for the currently logged in user
@method clearPin
**/
clearPin: function() {
this.get('content').clearPin();
},
resetRead: function() {
Discourse.ScreenTrack.current().reset();
this.unsubscribe();
var topicController = this;
this.get('model').resetRead().then(function() {
topicController.set('message', I18n.t("topic.read_position_reset"));
topicController.set('postStream.loaded', false);
});
},
replyAsNewTopic: function(post) {
var composerController = this.get('controllers.composer');
var promise = composerController.open({
action: Discourse.Composer.CREATE_TOPIC,
draftKey: Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY
});
var postUrl = "" + location.protocol + "//" + location.host + (post.get('url'));
var postLink = "[" + (this.get('title')) + "](" + postUrl + ")";
promise.then(function() {
Discourse.Post.loadQuote(post.get('id')).then(function(q) {
composerController.appendText(I18n.t("post.continue_discussion", {
postLink: postLink
}) + "\n\n" + q);
});
});
}
},
jumpTopDisabled: function() { jumpTopDisabled: function() {
return (this.get('progressPosition') === 1); return (this.get('progressPosition') === 1);
}.property('postStream.filteredPostsCount', 'progressPosition'), }.property('postStream.filteredPostsCount', 'progressPosition'),
@ -78,7 +281,7 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
multiSelectChanged: function() { multiSelectChanged: function() {
// Deselect all posts when multi select is turned off // Deselect all posts when multi select is turned off
if (!this.get('multiSelect')) { if (!this.get('multiSelect')) {
this.deselectAll(); this.send('deselectAll');
} }
}.observes('multiSelect'), }.observes('multiSelect'),
@ -109,156 +312,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
return false; return false;
}, },
toggledSelectedPost: function(post) {
var selectedPosts = this.get('selectedPosts');
if (this.postSelected(post)) {
this.deselectPost(post);
return false;
} else {
selectedPosts.addObject(post);
// If the user manually selects all posts, all posts are selected
if (selectedPosts.length === this.get('posts_count')) {
this.set('allPostsSelected', true);
}
return true;
}
},
toggledSelectedPostReplies: function(post) {
var selectedReplies = this.get('selectedReplies');
if (this.toggledSelectedPost(post)) {
selectedReplies.addObject(post);
} else {
selectedReplies.removeObject(post);
}
},
selectAll: function() {
var posts = this.get('postStream.posts');
var selectedPosts = this.get('selectedPosts');
if (posts) {
selectedPosts.addObjects(posts);
}
this.set('allPostsSelected', true);
},
deselectAll: function() {
this.get('selectedPosts').clear();
this.get('selectedReplies').clear();
this.set('allPostsSelected', false);
},
toggleMultiSelect: function() {
this.toggleProperty('multiSelect');
},
toggleSummary: function() {
this.toggleProperty('summaryCollapsed');
},
editTopic: function() {
if (!this.get('details.can_edit')) return false;
this.setProperties({
editingTopic: true,
newTitle: this.get('title'),
newCategoryId: this.get('category_id')
});
return false;
},
// close editing mode
cancelEditingTopic: function() {
this.set('editingTopic', false);
},
finishedEditingTopic: function() {
var topicController = this;
if (this.get('editingTopic')) {
var topic = this.get('model');
// Topic title hasn't been sanitized yet, so the template shouldn't trust it.
this.set('topicSaving', true);
// manually update the titles & category
topic.setProperties({
title: this.get('newTitle'),
category_id: parseInt(this.get('newCategoryId'), 10),
fancy_title: this.get('newTitle')
});
// save the modifications
topic.save().then(function(result){
// update the title if it has been changed (cleaned up) server-side
var title = result.basic_topic.title;
var fancy_title = result.basic_topic.fancy_title;
topic.setProperties({
title: title,
fancy_title: fancy_title
});
topicController.set('topicSaving', false);
}, function(error) {
topicController.set('editingTopic', true);
topicController.set('topicSaving', false);
if (error && error.responseText) {
bootbox.alert($.parseJSON(error.responseText).errors[0]);
} else {
bootbox.alert(I18n.t('generic_error'));
}
});
// close editing mode
topicController.set('editingTopic', false);
}
},
deleteSelected: function() {
var self = this;
bootbox.confirm(I18n.t("post.delete.confirm", { count: this.get('selectedPostsCount')}), function(result) {
if (result) {
// If all posts are selected, it's the same thing as deleting the topic
if (self.get('allPostsSelected')) {
return self.deleteTopic();
}
var selectedPosts = self.get('selectedPosts'),
selectedReplies = self.get('selectedReplies'),
postStream = self.get('postStream'),
toRemove = new Ember.Set();
Discourse.Post.deleteMany(selectedPosts, selectedReplies);
postStream.get('posts').forEach(function (p) {
if (self.postSelected(p)) { toRemove.addObject(p); }
});
postStream.removePosts(toRemove);
self.toggleMultiSelect();
}
});
},
jumpTop: function() {
Discourse.URL.routeTo(this.get('url'));
},
jumpBottom: function() {
Discourse.URL.routeTo(this.get('lastPostUrl'));
},
/**
Toggle a participant for filtering
@method toggleParticipant
**/
toggleParticipant: function(user) {
this.get('postStream').toggleParticipant(Em.get(user, 'username'));
},
showFavoriteButton: function() { showFavoriteButton: function() {
return Discourse.User.current() && !this.get('isPrivateMessage'); return Discourse.User.current() && !this.get('isPrivateMessage');
}.property('isPrivateMessage'), }.property('isPrivateMessage'),
@ -272,52 +325,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
this.get('content').destroy(Discourse.User.current()); this.get('content').destroy(Discourse.User.current());
}, },
resetRead: function() {
Discourse.ScreenTrack.current().reset();
this.unsubscribe();
var topicController = this;
this.get('model').resetRead().then(function() {
topicController.set('message', I18n.t("topic.read_position_reset"));
topicController.set('postStream.loaded', false);
});
},
toggleVisibility: function() {
this.get('content').toggleStatus('visible');
},
toggleClosed: function() {
this.get('content').toggleStatus('closed');
},
togglePinned: function() {
this.get('content').toggleStatus('pinned');
},
toggleArchived: function() {
this.get('content').toggleStatus('archived');
},
convertToRegular: function() {
this.get('content').convertArchetype('regular');
},
// Toggle the star on the topic
toggleStar: function() {
this.get('content').toggleStar();
},
/**
Clears the pin from a topic for the currently logged in user
@method clearPin
**/
clearPin: function() {
this.get('content').clearPin();
},
// Receive notifications for this topic // Receive notifications for this topic
subscribe: function() { subscribe: function() {
@ -383,24 +390,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
return false; return false;
}, },
replyAsNewTopic: function(post) {
var composerController = this.get('controllers.composer');
var promise = composerController.open({
action: Discourse.Composer.CREATE_TOPIC,
draftKey: Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY
});
var postUrl = "" + location.protocol + "//" + location.host + (post.get('url'));
var postLink = "[" + (this.get('title')) + "](" + postUrl + ")";
promise.then(function() {
Discourse.Post.loadQuote(post.get('id')).then(function(q) {
composerController.appendText(I18n.t("post.continue_discussion", {
postLink: postLink
}) + "\n\n" + q);
});
});
},
// Topic related // Topic related
reply: function() { reply: function() {
this.replyToPost(); this.replyToPost();
@ -470,6 +459,22 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
} }
}, },
performTogglePost: function(post) {
var selectedPosts = this.get('selectedPosts');
if (this.postSelected(post)) {
this.deselectPost(post);
return false;
} else {
selectedPosts.addObject(post);
// If the user manually selects all posts, all posts are selected
if (selectedPosts.length === this.get('posts_count')) {
this.set('allPostsSelected', true);
}
return true;
}
},
removeAllowedUser: function(username) { removeAllowedUser: function(username) {
this.get('details').removeAllowedUser(username); this.get('details').removeAllowedUser(username);
} }

View File

@ -11,8 +11,10 @@ Discourse.UploadSelectorController = Discourse.Controller.extend(Discourse.Modal
localSelected: true, localSelected: true,
remoteSelected: Em.computed.not('localSelected'), remoteSelected: Em.computed.not('localSelected'),
selectLocal: function() { this.set('localSelected', true); }, actions: {
selectRemote: function() { this.set('localSelected', false); }, selectLocal: function() { this.set('localSelected', true); },
selectRemote: function() { this.set('localSelected', false); }
},
localTitle: function() { return Discourse.UploadSelectorController.translate("local_title"); }.property(), localTitle: function() { return Discourse.UploadSelectorController.translate("local_title"); }.property(),
remoteTitle: function() { return Discourse.UploadSelectorController.translate("remote_title"); }.property(), remoteTitle: function() { return Discourse.UploadSelectorController.translate("remote_title"); }.property(),

View File

@ -7,10 +7,14 @@
@module Discourse @module Discourse
**/ **/
Discourse.UserInvitedController = Discourse.ObjectController.extend({ Discourse.UserInvitedController = Discourse.ObjectController.extend({
rescind: function(invite) {
invite.rescind(); actions: {
return false; rescind: function(invite) {
invite.rescind();
return false;
}
} }
}); });

View File

@ -8,7 +8,7 @@
**/ **/
Discourse.ApplicationRoute = Em.Route.extend({ Discourse.ApplicationRoute = Em.Route.extend({
events: { actions: {
showLogin: function() { showLogin: function() {
Discourse.Route.showModal(this, 'login'); Discourse.Route.showModal(this, 'login');
}, },

View File

@ -10,7 +10,7 @@ Discourse.ListCategoriesRoute = Discourse.Route.extend({
redirect: function() { Discourse.redirectIfLoginRequired(this); }, redirect: function() { Discourse.redirectIfLoginRequired(this); },
events: { actions: {
createCategory: function() { createCategory: function() {
Discourse.Route.showModal(this, 'editCategory', Discourse.Category.create({ Discourse.Route.showModal(this, 'editCategory', Discourse.Category.create({
color: 'AB9364', text_color: 'FFFFFF', hotness: 5, group_permissions: [{group_name: "everyone", permission_type: 1}], color: 'AB9364', text_color: 'FFFFFF', hotness: 5, group_permissions: [{group_name: "everyone", permission_type: 1}],

View File

@ -15,7 +15,7 @@ Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' }); this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
}, },
events: { actions: {
showAvatarSelector: function() { showAvatarSelector: function() {
Discourse.Route.showModal(this, 'avatarSelector'); Discourse.Route.showModal(this, 'avatarSelector');
// all the properties needed for displaying the avatar selector modal // all the properties needed for displaying the avatar selector modal

View File

@ -10,7 +10,7 @@ Discourse.TopicRoute = Discourse.Route.extend({
redirect: function() { Discourse.redirectIfLoginRequired(this); }, redirect: function() { Discourse.redirectIfLoginRequired(this); },
events: { actions: {
// Modals that can pop up within a topic // Modals that can pop up within a topic
showFlags: function(post) { showFlags: function(post) {

View File

@ -85,7 +85,7 @@
</li> </li>
<li class='current-user'> <li class='current-user'>
{{#if currentUser}} {{#if currentUser}}
{{#linkTo 'userActivity.index' currentUser titleKey="current_user" class="icon"}}{{boundAvatar currentUser imageSize="medium" }}{{/linkTo}} {{#link-to 'userActivity.index' currentUser titleKey="current_user" class="icon"}}{{boundAvatar currentUser imageSize="medium" }}{{/link-to}}
{{else}} {{else}}
<div class="icon not-logged-in-avatar" {{action showLogin}}><i class='icon-user'></i></div> <div class="icon not-logged-in-avatar" {{action showLogin}}><i class='icon-user'></i></div>
{{/if}} {{/if}}
@ -139,7 +139,7 @@
{{#if categories}} {{#if categories}}
<ul class="category-links"> <ul class="category-links">
<li class='heading' title="{{i18n filters.categories.help}}"> <li class='heading' title="{{i18n filters.categories.help}}">
{{#linkTo "list.categories"}}{{i18n filters.categories.title}}{{/linkTo}} {{#link-to "list.categories"}}{{i18n filters.categories.title}}{{/link-to}}
</li> </li>
{{#each categories}} {{#each categories}}

View File

@ -60,7 +60,7 @@
<a href='#' {{action createTopic}}>{{i18n topic.suggest_create_topic}}</a> <a href='#' {{action createTopic}}>{{i18n topic.suggest_create_topic}}</a>
{{/if}} {{/if}}
{{else}} {{else}}
{{#linkTo 'list.categories'}}{{i18n topic.browse_all_categories}}{{/linkTo}} {{i18n or}} {{#linkTo 'list.latest'}}{{i18n topic.view_latest_topics}}{{/linkTo}} {{#link-to 'list.categories'}}{{i18n topic.browse_all_categories}}{{/link-to}} {{i18n or}} {{#link-to 'list.latest'}}{{i18n topic.view_latest_topics}}{{/link-to}}
{{/if}} {{/if}}
{{/if}} {{/if}}
</h3> </h3>

View File

@ -43,7 +43,7 @@
<a href='#' {{action createTopic}}>{{i18n topic.suggest_create_topic}}</a> <a href='#' {{action createTopic}}>{{i18n topic.suggest_create_topic}}</a>
{{/if}} {{/if}}
{{else}} {{else}}
{{#linkTo 'list.categories'}}{{i18n topic.browse_all_categories}}{{/linkTo}} {{i18n or}} {{#linkTo 'list.latest'}}{{i18n topic.view_latest_topics}}{{/linkTo}} {{#link-to 'list.categories'}}{{i18n topic.browse_all_categories}}{{/link-to}} {{i18n or}} {{#link-to 'list.latest'}}{{i18n topic.view_latest_topics}}{{/link-to}}
{{/if}} {{/if}}
{{/if}} {{/if}}
</h3> </h3>

View File

@ -1,7 +1,7 @@
<div id='user-info'> <div id='user-info'>
<nav class='buttons'> <nav class='buttons'>
{{#if can_edit}} {{#if can_edit}}
{{#linkTo "preferences" class="btn"}}{{i18n user.edit}}{{/linkTo}} {{#link-to "preferences" class="btn"}}{{i18n user.edit}}{{/link-to}}
{{/if}} {{/if}}
<br/> <br/>
{{#if can_send_private_message_to_user}} {{#if can_send_private_message_to_user}}
@ -16,13 +16,13 @@
<ul class='action-list nav-stacked side-nav'> <ul class='action-list nav-stacked side-nav'>
{{#if privateMessageView}} {{#if privateMessageView}}
<li {{bindAttr class=":noGlyph privateMessagesActive:active"}}> <li {{bindAttr class=":noGlyph privateMessagesActive:active"}}>
{{#linkTo 'userPrivateMessages.index' model}}{{i18n user.messages.all}}{{/linkTo}} {{#link-to 'userPrivateMessages.index' model}}{{i18n user.messages.all}}{{/link-to}}
</li> </li>
<li {{bindAttr class=":noGlyph privateMessagesMineActive:active"}}> <li {{bindAttr class=":noGlyph privateMessagesMineActive:active"}}>
{{#linkTo 'userPrivateMessages.mine' model}}{{i18n user.messages.mine}}{{/linkTo}} {{#link-to 'userPrivateMessages.mine' model}}{{i18n user.messages.mine}}{{/link-to}}
</li> </li>
<li {{bindAttr class=":noGlyph privateMessagesUnreadActive:active"}}> <li {{bindAttr class=":noGlyph privateMessagesUnreadActive:active"}}>
{{#linkTo 'userPrivateMessages.unread' model}}{{i18n user.messages.unread}}{{/linkTo}} {{#link-to 'userPrivateMessages.unread' model}}{{i18n user.messages.unread}}{{/link-to}}
</li> </li>
{{else}} {{else}}
@ -48,7 +48,7 @@
<dt>{{i18n user.last_seen}}:</dt><dd>{{date last_seen_at}}</dd> <dt>{{i18n user.last_seen}}:</dt><dd>{{date last_seen_at}}</dd>
{{/if}} {{/if}}
{{#if invited_by}} {{#if invited_by}}
<dt>{{i18n user.invited_by}}:</dt><dd>{{#linkTo 'userActivity' invited_by}}{{invited_by.username}}{{/linkTo}}</dd> <dt>{{i18n user.invited_by}}:</dt><dd>{{#link-to 'userActivity' invited_by}}{{invited_by.username}}{{/link-to}}</dd>
{{/if}} {{/if}}
{{#if email}} {{#if email}}
<dt>{{i18n user.email.title}}:</dt><dd {{bindAttr title="email"}}>{{email}}</dd> <dt>{{i18n user.email.title}}:</dt><dd {{bindAttr title="email"}}>{{email}}</dd>

View File

@ -5,7 +5,7 @@
<div class="controls"> <div class="controls">
<span class='static'>{{username}}</span> <span class='static'>{{username}}</span>
{{#if can_edit_username}} {{#if can_edit_username}}
{{#linkTo "preferences.username" class="btn pad-left"}}<i class="icon-pencil"></i>{{/linkTo}} {{#link-to "preferences.username" class="btn pad-left"}}<i class="icon-pencil"></i>{{/link-to}}
{{/if}} {{/if}}
</div> </div>
<div class='instructions'> <div class='instructions'>
@ -28,7 +28,7 @@
<div class="controls"> <div class="controls">
<span class='static'>{{email}}</span> <span class='static'>{{email}}</span>
{{#if can_edit_email}} {{#if can_edit_email}}
{{#linkTo "preferences.email" class="btn pad-left"}}<i class="icon-pencil"></i>{{/linkTo}} {{#link-to "preferences.email" class="btn pad-left"}}<i class="icon-pencil"></i>{{/link-to}}
{{/if}} {{/if}}
</div> </div>
<div class='instructions'> <div class='instructions'>

View File

@ -12,19 +12,19 @@
{{/if}} {{/if}}
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li> <li>
{{#linkTo 'userActivity'}}{{i18n user.activity_stream}}{{/linkTo}} {{#link-to 'userActivity'}}{{i18n user.activity_stream}}{{/link-to}}
</li> </li>
{{#if canSeePrivateMessages}} {{#if canSeePrivateMessages}}
<li> <li>
{{#linkTo 'userPrivateMessages'}}{{i18n user.private_messages}}{{/linkTo}} {{#link-to 'userPrivateMessages'}}{{i18n user.private_messages}}{{/link-to}}
</li> </li>
{{/if}} {{/if}}
<li> <li>
{{#linkTo 'user.invited'}}{{i18n user.invited.title}}{{/linkTo}} {{#link-to 'user.invited'}}{{i18n user.invited.title}}{{/link-to}}
</li> </li>
{{#if can_edit}} {{#if can_edit}}
<li> <li>
{{#linkTo 'preferences'}}{{i18n user.preferences}}{{/linkTo}} {{#link-to 'preferences'}}{{i18n user.preferences}}{{/link-to}}
</li> </li>
{{/if}} {{/if}}
</ul> </ul>

View File

@ -14,7 +14,7 @@ Discourse.FavoriteButton = Discourse.ButtonView.extend({
shouldRerender: Discourse.View.renderIfChanged('controller.starred'), shouldRerender: Discourse.View.renderIfChanged('controller.starred'),
click: function() { click: function() {
this.get('controller').toggleStar(); this.get('controller').send('toggleStar');
}, },
renderIcon: function(buffer) { renderIcon: function(buffer) {

View File

@ -45,7 +45,7 @@ Discourse.ShareView = Discourse.View.extend({
// link is clicked (which is a click event) while the share dialog is showing. // link is clicked (which is a click event) while the share dialog is showing.
if (shareView.$().has(e.target).length !== 0) { return; } if (shareView.$().has(e.target).length !== 0) { return; }
shareView.get('controller').close(); shareView.get('controller').send('close');
return true; return true;
}); });
@ -76,7 +76,7 @@ Discourse.ShareView = Discourse.View.extend({
$('html').on('keydown.share-view', function(e){ $('html').on('keydown.share-view', function(e){
if (e.keyCode === 27) { if (e.keyCode === 27) {
shareView.get('controller').close(); shareView.get('controller').send('close');
} }
}); });
}, },

View File

@ -13,11 +13,11 @@ Discourse.TopicAdminMenuView = Discourse.View.extend({
}, },
didInsertElement: function() { didInsertElement: function() {
var topicAdminMenuView = this; var self = this;
$('html').on('mouseup.discourse-topic-admin-menu', function(e) { $('html').on('mouseup.discourse-topic-admin-menu', function(e) {
var $target = $(e.target); var $target = $(e.target);
if ($target.is('button') || topicAdminMenuView.$().has($target).length === 0) { if ($target.is('button') || self.$().has($target).length === 0) {
topicAdminMenuView.get('controller').hide(); self.get('controller').send('hide');
} }
}); });
} }

View File

@ -3,7 +3,7 @@
"description": "This is the EmberJS client to access a Discourse Server", "description": "This is the EmberJS client to access a Discourse Server",
"url": "http://www.discourse.org/", "url": "http://www.discourse.org/",
"options": { "options": {
"exclude": "external,external_production,defer", "exclude": "development,production,defer",
"outdir": "./build" "outdir": "./build"
} }
} }

View File

@ -1,7 +1,37 @@
//= require_tree ./discourse/ember //= require_tree ./discourse/ember
// The rest of the externals // The Vendored JS
//= require_tree ./external //= require LAB.js
//= require Markdown.Converter.js
//= require Markdown.Editor.js
//= require Markdown.Sanitizer.js
//= require better_markdown.js
//= require bootbox.js
//= require bootstrap-alert.js
//= require bootstrap-button.js
//= require bootstrap-dropdown.js
//= require bootstrap-modal.js
//= require bootstrap-transition.js
//= require browser-update.js
//= require chosen.jquery.js
//= require ember-renderspeed.js
//= require favcount.js
//= require handlebars.js
//= require jquery.ba-replacetext.js
//= require jquery.ba-resize.min.js
//= require jquery.color.js
//= require jquery.cookie.js
//= require jquery.fileupload.js
//= require jquery.iframe-transport.js
//= require jquery.putcursoratend.js
//= require jquery.tagsinput.js
//= require jquery.ui.widget.js
//= require lodash.js
//= require md5.js
//= require modernizr.custom.95264.js
//= require mousetrap.js
//= require rsvp.js
//= require show-html.js
//= require ./discourse/helpers/i18n_helpers //= require ./discourse/helpers/i18n_helpers
//= require ./discourse/mixins/ajax //= require ./discourse/mixins/ajax
@ -39,4 +69,4 @@
//= require_tree ./discourse/templates //= require_tree ./discourse/templates
//= require_tree ./discourse/routes //= require_tree ./discourse/routes
//= require ./external/browser-update.js //= require browser-update.js

View File

@ -0,0 +1 @@
//= require_tree ./admin

View File

@ -7,7 +7,7 @@ window.PagedownCustom = {
description: I18n.t("composer.quote_post_title"), description: I18n.t("composer.quote_post_title"),
execute: function() { execute: function() {
// AWFUL but I can't figure out how to call a controller method from outside our app // AWFUL but I can't figure out how to call a controller method from outside our app
return Discourse.__container__.lookup('controller:composer').importQuote(); return Discourse.__container__.lookup('controller:composer').send('importQuote');
} }
} }
] ]

View File

@ -29,6 +29,7 @@ class TopicsController < ApplicationController
return wordpress if params[:best].present? return wordpress if params[:best].present?
opts = params.slice(:username_filters, :filter, :page, :post_number) opts = params.slice(:username_filters, :filter, :page, :post_number)
begin begin
@topic_view = TopicView.new(params[:id] || params[:topic_id], current_user, opts) @topic_view = TopicView.new(params[:id] || params[:topic_id], current_user, opts)
rescue Discourse::NotFound rescue Discourse::NotFound

View File

@ -113,8 +113,8 @@ module Discourse
# ember stuff only used for asset precompliation, production variant plays up # ember stuff only used for asset precompliation, production variant plays up
config.ember.variant = :development config.ember.variant = :development
config.ember.ember_location = "#{Rails.root}/app/assets/javascripts/external_production/ember.js" config.ember.ember_location = "#{Rails.root}/vendor/assets/javascripts/production/ember.js"
config.ember.handlebars_location = "#{Rails.root}/app/assets/javascripts/external/handlebars.js" config.ember.handlebars_location = "#{Rails.root}/vendor/assets/javascripts/handlebars.js"
# Since we are using strong_parameters, we can disable and remove # Since we are using strong_parameters, we can disable and remove
# attr_accessible. # attr_accessible.

View File

@ -13,9 +13,6 @@ paths:
- test/javascripts/**/*.js - test/javascripts/**/*.js
exclude_paths: exclude_paths:
- app/assets/javascripts/external/*
- app/assets/javascripts/external_production/*
- app/assets/javascripts/external_development/*
- app/assets/javascripts/defer/* - app/assets/javascripts/defer/*
- app/assets/javascripts/locales/i18n.js - app/assets/javascripts/locales/i18n.js

View File

@ -93,11 +93,11 @@ module PrettyText
ctx["helpers"] = Helpers.new ctx["helpers"] = Helpers.new
ctx_load(ctx, ctx_load(ctx,
"app/assets/javascripts/external/md5.js", "vendor/assets/javascripts/md5.js",
"app/assets/javascripts/external/lodash.js", "vendor/assets/javascripts/lodash.js",
"app/assets/javascripts/external/Markdown.Converter.js", "vendor/assets/javascripts/Markdown.Converter.js",
"lib/headless-ember.js", "lib/headless-ember.js",
"app/assets/javascripts/external/rsvp.js", "vendor/assets/javascripts/rsvp.js",
Rails.configuration.ember.handlebars_location) Rails.configuration.ember.handlebars_location)
ctx.eval("var Discourse = {}; Discourse.SiteSettings = #{SiteSetting.client_settings_json};") ctx.eval("var Discourse = {}; Discourse.SiteSettings = #{SiteSetting.client_settings_json};")
@ -107,7 +107,7 @@ module PrettyText
decorate_context(ctx) decorate_context(ctx)
ctx_load(ctx, ctx_load(ctx,
"app/assets/javascripts/external/better_markdown.js", "vendor/assets/javascripts/better_markdown.js",
"app/assets/javascripts/discourse/dialects/dialect.js", "app/assets/javascripts/discourse/dialects/dialect.js",
"app/assets/javascripts/discourse/components/utilities.js", "app/assets/javascripts/discourse/components/utilities.js",
"app/assets/javascripts/discourse/components/markdown.js") "app/assets/javascripts/discourse/components/markdown.js")

View File

@ -14,13 +14,13 @@ test("avatarTemplate", function() {
avatarSelector.get("gravatar_template"), avatarSelector.get("gravatar_template"),
"we are using gravatar by default"); "we are using gravatar by default");
avatarSelectorController.useUploadedAvatar(); avatarSelectorController.send('useUploadedAvatar');
equal(avatarSelectorController.get("avatarTemplate"), equal(avatarSelectorController.get("avatarTemplate"),
avatarSelector.get("uploaded_avatar_template"), avatarSelector.get("uploaded_avatar_template"),
"calling useUploadedAvatar switches to using the uploaded avatar"); "calling useUploadedAvatar switches to using the uploaded avatar");
avatarSelectorController.useGravatar(); avatarSelectorController.send('useGravatar');
equal(avatarSelectorController.get("avatarTemplate"), equal(avatarSelectorController.get("avatarTemplate"),
avatarSelector.get("gravatar_template"), avatarSelector.get("gravatar_template"),

View File

@ -18,16 +18,16 @@ test("editingMode", function() {
ok(!topicController.get('editingTopic'), "we are not editing by default"); ok(!topicController.get('editingTopic'), "we are not editing by default");
topicController.set('model.details.can_edit', false); topicController.set('model.details.can_edit', false);
topicController.editTopic(); topicController.send('editTopic');
ok(!topicController.get('editingTopic'), "calling editTopic doesn't enable editing unless the user can edit"); ok(!topicController.get('editingTopic'), "calling editTopic doesn't enable editing unless the user can edit");
topicController.set('model.details.can_edit', true); topicController.set('model.details.can_edit', true);
topicController.editTopic(); topicController.send('editTopic');
ok(topicController.get('editingTopic'), "calling editTopic enables editing if the user can edit"); ok(topicController.get('editingTopic'), "calling editTopic enables editing if the user can edit");
equal(topicController.get('newTitle'), topic.get('title')); equal(topicController.get('newTitle'), topic.get('title'));
equal(topicController.get('newCategoryId'), topic.get('category_id')); equal(topicController.get('newCategoryId'), topic.get('category_id'));
topicController.cancelEditingTopic(); topicController.send('cancelEditingTopic');
ok(!topicController.get('editingTopic'), "cancelling edit mode reverts the property value"); ok(!topicController.get('editingTopic'), "cancelling edit mode reverts the property value");
}); });
@ -43,12 +43,12 @@ test("toggledSelectedPost", function() {
equal(tc.get('selectedPostsCount'), 0, "there is a selected post count of 0"); equal(tc.get('selectedPostsCount'), 0, "there is a selected post count of 0");
ok(!tc.postSelected(post), "the post is not selected by default"); ok(!tc.postSelected(post), "the post is not selected by default");
tc.toggledSelectedPost(post); tc.send('toggledSelectedPost', post);
present(tc.get('selectedPosts'), "there is a selectedPosts collection"); present(tc.get('selectedPosts'), "there is a selectedPosts collection");
equal(tc.get('selectedPostsCount'), 1, "there is a selected post now"); equal(tc.get('selectedPostsCount'), 1, "there is a selected post now");
ok(tc.postSelected(post), "the post is now selected"); ok(tc.postSelected(post), "the post is now selected");
tc.toggledSelectedPost(post); tc.send('toggledSelectedPost', post);
ok(!tc.postSelected(post), "the post is no longer selected"); ok(!tc.postSelected(post), "the post is no longer selected");
}); });
@ -61,10 +61,10 @@ test("selectAll", function() {
postStream.appendPost(post); postStream.appendPost(post);
ok(!tc.postSelected(post), "the post is not selected by default"); ok(!tc.postSelected(post), "the post is not selected by default");
tc.selectAll(); tc.send('selectAll');
ok(tc.postSelected(post), "the post is now selected"); ok(tc.postSelected(post), "the post is now selected");
ok(tc.get('allPostsSelected'), "all posts are selected"); ok(tc.get('allPostsSelected'), "all posts are selected");
tc.deselectAll(); tc.send('deselectAll');
ok(!tc.postSelected(post), "the post is deselected again"); ok(!tc.postSelected(post), "the post is deselected again");
ok(!tc.get('allPostsSelected'), "all posts are not selected"); ok(!tc.get('allPostsSelected'), "all posts are not selected");
@ -80,10 +80,10 @@ test("Automating setting of allPostsSelected", function() {
postStream.appendPost(post); postStream.appendPost(post);
ok(!tc.get('allPostsSelected'), "all posts are not selected by default"); ok(!tc.get('allPostsSelected'), "all posts are not selected by default");
tc.toggledSelectedPost(post); tc.send('toggledSelectedPost', post);
ok(tc.get('allPostsSelected'), "all posts are selected if we select the only post"); ok(tc.get('allPostsSelected'), "all posts are selected if we select the only post");
tc.toggledSelectedPost(post); tc.send('toggledSelectedPost', post);
ok(!tc.get('allPostsSelected'), "the posts are no longer automatically selected"); ok(!tc.get('allPostsSelected'), "the posts are no longer automatically selected");
}); });
@ -96,20 +96,20 @@ test("Select Replies when present", function() {
postStream = tc.get('postStream'); postStream = tc.get('postStream');
ok(!tc.postSelected(p3), "replies are not selected by default"); ok(!tc.postSelected(p3), "replies are not selected by default");
tc.toggledSelectedPostReplies(p1); tc.send('toggledSelectedPostReplies', p1);
ok(tc.postSelected(p1), "it selects the post"); ok(tc.postSelected(p1), "it selects the post");
ok(!tc.postSelected(p2), "it doesn't select a post that's not a reply"); ok(!tc.postSelected(p2), "it doesn't select a post that's not a reply");
ok(tc.postSelected(p3), "it selects a post that is a reply"); ok(tc.postSelected(p3), "it selects a post that is a reply");
equal(tc.get('selectedPostsCount'), 2, "it has a selected posts count of two"); equal(tc.get('selectedPostsCount'), 2, "it has a selected posts count of two");
// If we deselected the post whose replies are selected... // If we deselected the post whose replies are selected...
tc.toggledSelectedPost(p1); tc.send('toggledSelectedPost', p1);
ok(!tc.postSelected(p1), "it deselects the post"); ok(!tc.postSelected(p1), "it deselects the post");
ok(!tc.postSelected(p3), "it deselects the replies too"); ok(!tc.postSelected(p3), "it deselects the replies too");
// If we deselect a reply, it should deselect the parent's replies selected attribute. Weird but what else would make sense? // If we deselect a reply, it should deselect the parent's replies selected attribute. Weird but what else would make sense?
tc.toggledSelectedPostReplies(p1); tc.send('toggledSelectedPostReplies', p1);
tc.toggledSelectedPost(p3); tc.send('toggledSelectedPost', p3);
ok(tc.postSelected(p1), "the post stays selected"); ok(tc.postSelected(p1), "the post stays selected");
ok(!tc.postSelected(p3), "it deselects the replies too"); ok(!tc.postSelected(p3), "it deselects the replies too");

View File

@ -184,7 +184,5 @@ var jsHintOpts = {
<%= jshint("#{Rails.root}/app/assets/javascripts/**/*.js", <%= jshint("#{Rails.root}/app/assets/javascripts/**/*.js",
"/app/assets/javascripts/", "/app/assets/javascripts/",
[/external\//, [/external\//,
/external_development\//,
/external_production\//,
/defer\//, /defer\//,
/locales\//]) %> /locales\//]) %>

View File

@ -9,10 +9,10 @@
//= require ../../app/assets/javascripts/discourse/components/probes.js //= require ../../app/assets/javascripts/discourse/components/probes.js
// Externals we need to load first // Externals we need to load first
//= require ../../app/assets/javascripts/external_development/jquery-2.0.3.js //= require development/jquery-2.0.3.js
//= require ../../app/assets/javascripts/external/jquery.ui.widget.js //= require jquery.ui.widget.js
//= require ../../app/assets/javascripts/external/handlebars.js //= require handlebars.js
//= require ../../app/assets/javascripts/external_development/ember.js //= require development/ember.js
//= require ../../app/assets/javascripts/locales/i18n //= require ../../app/assets/javascripts/locales/i18n
//= require ../../app/assets/javascripts/discourse/helpers/i18n_helpers //= require ../../app/assets/javascripts/discourse/helpers/i18n_helpers
@ -21,8 +21,36 @@
// Pagedown customizations // Pagedown customizations
//= require ../../app/assets/javascripts/pagedown_custom.js //= require ../../app/assets/javascripts/pagedown_custom.js
// The rest of the externals // The rest of the vendored JS
//= require_tree ../../app/assets/javascripts/external //= require LAB.js
//= require Markdown.Converter.js
//= require Markdown.Editor.js
//= require Markdown.Sanitizer.js
//= require better_markdown.js
//= require bootbox.js
//= require bootstrap-alert.js
//= require bootstrap-button.js
//= require bootstrap-dropdown.js
//= require bootstrap-modal.js
//= require bootstrap-transition.js
//= require browser-update.js
//= require chosen.jquery.js
//= require ember-renderspeed.js
//= require favcount.js
//= require jquery.ba-replacetext.js
//= require jquery.ba-resize.min.js
//= require jquery.color.js
//= require jquery.cookie.js
//= require jquery.fileupload.js
//= require jquery.iframe-transport.js
//= require jquery.putcursoratend.js
//= require jquery.tagsinput.js
//= require lodash.js
//= require md5.js
//= require modernizr.custom.95264.js
//= require mousetrap.js
//= require rsvp.js
//= require show-html.js
// Stuff we need to load first // Stuff we need to load first
//= require main_include //= require main_include

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,6 @@
// Last commit: 1f0c355 (2013-09-18 11:01:11 -0400)
(function() { (function() {
var get = Ember.get, set = Ember.set; var get = Ember.get, set = Ember.set;
@ -18,7 +21,7 @@ function positionElement() {
// TODO: avoid needing this by avoiding unnecessary // TODO: avoid needing this by avoiding unnecessary
// calls to this method in the first place // calls to this method in the first place
if (samePosition(position, _position)) { return; } if (samePosition(position, _position)) { return; }
this._parentView.applyTransform(element, position); this._parentView.applyTransform(element, position.x, position.y);
this._position = position; this._position = position;
}, this); }, this);
@ -186,23 +189,54 @@ Ember.ReusableListItemView = Ember.View.extend(Ember.ListItemViewMixin, {
(function() { (function() {
var el = document.createElement('div'), style = el.style;
var propPrefixes = ['Webkit', 'Moz', 'O', 'ms'];
function testProp(prop) {
if (prop in style) return prop;
var uppercaseProp = prop.charAt(0).toUpperCase() + prop.slice(1);
for (var i=0; i<propPrefixes.length; i++) {
var prefixedProp = propPrefixes[i] + uppercaseProp;
if (prefixedProp in style) {
return prefixedProp;
}
}
return null;
}
var transformProp = testProp('transform');
var perspectiveProp = testProp('perspective');
var supports2D = transformProp !== null;
var supports3D = perspectiveProp !== null;
Ember.ListViewHelper = { Ember.ListViewHelper = {
transformProp: transformProp,
applyTransform: (function(){ applyTransform: (function(){
var element = document.createElement('div'); if (supports2D) {
return function(element, x, y){
if ('webkitTransform' in element.style){ element.style[transformProp] = 'translate(' + x + 'px, ' + y + 'px)';
return function(element, position){
var x = position.x,
y = position.y;
element.style.webkitTransform = 'translate3d(' + x + 'px, ' + y + 'px, 0)';
}; };
}else{ } else {
return function(element, position){ return function(element, x, y){
var x = position.x, element.style.top = y + 'px';
y = position.y; element.style.left = x + 'px';
};
element.style.top = y + 'px'; }
})(),
apply3DTransform: (function(){
if (supports3D) {
return function(element, x, y){
element.style[transformProp] = 'translate3d(' + x + 'px, ' + y + 'px, 0)';
};
} else if (supports2D) {
return function(element, x, y){
element.style[transformProp] = 'translate(' + x + 'px, ' + y + 'px)';
};
} else {
return function(element, x, y){
element.style.top = y + 'px';
element.style.left = x + 'px'; element.style.left = x + 'px';
}; };
} }
@ -239,10 +273,6 @@ function sortByContentIndex (viewOne, viewTwo){
return get(viewOne, 'contentIndex') - get(viewTwo, 'contentIndex'); return get(viewOne, 'contentIndex') - get(viewTwo, 'contentIndex');
} }
function detectListItemViews(childView) {
return Ember.ListItemViewMixin.detect(childView);
}
function notifyMutationListeners() { function notifyMutationListeners() {
if (Ember.View.notifyMutationListeners) { if (Ember.View.notifyMutationListeners) {
Ember.run.once(Ember.View, 'notifyMutationListeners'); Ember.run.once(Ember.View, 'notifyMutationListeners');
@ -296,6 +326,7 @@ function enableProfilingOutput() {
*/ */
Ember.ListViewMixin = Ember.Mixin.create({ Ember.ListViewMixin = Ember.Mixin.create({
itemViewClass: Ember.ListItemView, itemViewClass: Ember.ListItemView,
emptyViewClass: Ember.View,
classNames: ['ember-list-view'], classNames: ['ember-list-view'],
attributeBindings: ['style'], attributeBindings: ['style'],
domManager: domManager, domManager: domManager,
@ -315,13 +346,16 @@ Ember.ListViewMixin = Ember.Mixin.create({
*/ */
init: function() { init: function() {
this._super(); this._super();
enableProfilingOutput();
addContentArrayObserver.call(this);
this._syncChildViews();
this.columnCountDidChange();
this.on('didInsertElement', syncListContainerWidth); this.on('didInsertElement', syncListContainerWidth);
this.columnCountDidChange();
this._syncChildViews();
this._addContentArrayObserver();
}, },
_addContentArrayObserver: Ember.beforeObserver(function() {
addContentArrayObserver.call(this);
}, 'content'),
/** /**
Called on your view when it should push strings of HTML into a Called on your view when it should push strings of HTML into a
`Ember.RenderBuffer`. `Ember.RenderBuffer`.
@ -599,7 +633,7 @@ Ember.ListViewMixin = Ember.Mixin.create({
maxScrollTop: Ember.computed('height', 'totalHeight', function(){ maxScrollTop: Ember.computed('height', 'totalHeight', function(){
var totalHeight, viewportHeight; var totalHeight, viewportHeight;
totalHeight = get(this, 'totalHeight'), totalHeight = get(this, 'totalHeight');
viewportHeight = get(this, 'height'); viewportHeight = get(this, 'height');
return max(0, totalHeight - viewportHeight); return max(0, totalHeight - viewportHeight);
@ -665,7 +699,7 @@ Ember.ListViewMixin = Ember.Mixin.create({
} }
}, 'content'), }, 'content'),
/** /**),
@private @private
@event contentDidChange @event contentDidChange
*/ */
@ -768,7 +802,7 @@ Ember.ListViewMixin = Ember.Mixin.create({
scrollTop = get(this, 'scrollTop'); scrollTop = get(this, 'scrollTop');
contentLength = get(this, 'content.length'); contentLength = get(this, 'content.length');
maxContentIndex = max(contentLength - 1, 0); maxContentIndex = max(contentLength - 1, 0);
childViews = get(this, 'listItemViews'); childViews = this._childViews;
childViewsLength = childViews.length; childViewsLength = childViews.length;
startingIndex = this._startingIndex(); startingIndex = this._startingIndex();
@ -786,24 +820,12 @@ Ember.ListViewMixin = Ember.Mixin.create({
} }
}, },
/**
@private
Returns an array of current ListItemView views in the visible area
when you start to scroll.
@property {Ember.ComputedProperty} listItemViews
*/
listItemViews: Ember.computed('[]', function(){
return this.filter(detectListItemViews);
}),
/** /**
@private @private
@method positionOrderedChildViews @method positionOrderedChildViews
*/ */
positionOrderedChildViews: function() { positionOrderedChildViews: function() {
return get(this, 'listItemViews').sort(sortByContentIndex); return this._childViews.sort(sortByContentIndex);
}, },
arrayWillChange: Ember.K, arrayWillChange: Ember.K,
@ -944,13 +966,7 @@ Ember.ListView = Ember.ContainerView.extend(Ember.ListViewMixin, {
'overflow-scrolling': 'touch' 'overflow-scrolling': 'touch'
}, },
applyTransform: function(element, position){ applyTransform: Ember.ListViewHelper.applyTransform,
var x = position.x,
y = position.y;
element.style.top = y + 'px';
element.style.left = x + 'px';
},
_scrollTo: function(scrollTop) { _scrollTo: function(scrollTop) {
var element = get(this, 'element'); var element = get(this, 'element');
@ -1006,6 +1022,148 @@ Ember.ListView = Ember.ContainerView.extend(Ember.ListViewMixin, {
(function() {
var fieldRegex = /input|textarea|select/i,
hasTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch,
handleStart, handleMove, handleEnd, handleCancel,
startEvent, moveEvent, endEvent, cancelEvent;
if (hasTouch) {
startEvent = 'touchstart';
handleStart = function (e) {
var touch = e.touches[0],
target = touch && touch.target;
// avoid e.preventDefault() on fields
if (target && fieldRegex.test(target.tagName)) {
return;
}
bindWindow(this.scrollerEventHandlers);
this.willBeginScroll(e.touches, e.timeStamp);
e.preventDefault();
};
moveEvent = 'touchmove';
handleMove = function (e) {
this.continueScroll(e.touches, e.timeStamp);
};
endEvent = 'touchend';
handleEnd = function (e) {
// if we didn't end up scrolling we need to
// synthesize click since we did e.preventDefault()
// on touchstart
if (!this._isScrolling) {
synthesizeClick(e);
}
unbindWindow(this.scrollerEventHandlers);
this.endScroll(e.timeStamp);
};
cancelEvent = 'touchcancel';
handleCancel = function (e) {
unbindWindow(this.scrollerEventHandlers);
this.endScroll(e.timeStamp);
};
} else {
startEvent = 'mousedown';
handleStart = function (e) {
if (e.which !== 1) return;
var target = e.target;
// avoid e.preventDefault() on fields
if (target && fieldRegex.test(target.tagName)) {
return;
}
bindWindow(this.scrollerEventHandlers);
this.willBeginScroll([e], e.timeStamp);
e.preventDefault();
};
moveEvent = 'mousemove';
handleMove = function (e) {
this.continueScroll([e], e.timeStamp);
};
endEvent = 'mouseup';
handleEnd = function (e) {
unbindWindow(this.scrollerEventHandlers);
this.endScroll(e.timeStamp);
};
cancelEvent = 'mouseout';
handleCancel = function (e) {
if (e.relatedTarget) return;
unbindWindow(this.scrollerEventHandlers);
this.endScroll(e.timeStamp);
};
}
function handleWheel(e) {
this.mouseWheel(e);
e.preventDefault();
}
function bindElement(el, handlers) {
el.addEventListener(startEvent, handlers.start, false);
el.addEventListener('mousewheel', handlers.wheel, false);
}
function unbindElement(el, handlers) {
el.removeEventListener(startEvent, handlers.start, false);
el.removeEventListener('mousewheel', handlers.wheel, false);
}
function bindWindow(handlers) {
window.addEventListener(moveEvent, handlers.move, true);
window.addEventListener(endEvent, handlers.end, true);
window.addEventListener(cancelEvent, handlers.cancel, true);
}
function unbindWindow(handlers) {
window.removeEventListener(moveEvent, handlers.move, true);
window.removeEventListener(endEvent, handlers.end, true);
window.removeEventListener(cancelEvent, handlers.cancel, true);
}
Ember.VirtualListScrollerEvents = Ember.Mixin.create({
init: function() {
this.on('didInsertElement', this, 'bindScrollerEvents');
this.on('willDestroyElement', this, 'unbindScrollerEvents');
this.scrollerEventHandlers = {
start: bind(this, handleStart),
move: bind(this, handleMove),
end: bind(this, handleEnd),
cancel: bind(this, handleCancel),
wheel: bind(this, handleWheel)
};
return this._super();
},
bindScrollerEvents: function() {
var el = this.get('element'),
handlers = this.scrollerEventHandlers;
bindElement(el, handlers);
},
unbindScrollerEvents: function() {
var el = this.get('element'),
handlers = this.scrollerEventHandlers;
unbindElement(el, handlers);
unbindWindow(handlers);
}
});
function bind(view, handler) {
return function (evt) {
handler.call(view, evt);
};
}
function synthesizeClick(e) {
var point = e.changedTouches[0],
target = point.target,
ev;
if (target && fieldRegex.test(target.tagName)) {
ev = document.createEvent('MouseEvents');
ev.initMouseEvent('click', true, true, e.view, 1, point.screenX, point.screenY, point.clientX, point.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null);
return target.dispatchEvent(ev);
}
}
})();
(function() { (function() {
/*global Scroller*/ /*global Scroller*/
var max = Math.max, get = Ember.get, set = Ember.set; var max = Math.max, get = Ember.get, set = Ember.set;
@ -1029,8 +1187,9 @@ function updateScrollerDimensions(target) {
@class VirtualListView @class VirtualListView
@namespace Ember @namespace Ember
*/ */
Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, { Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, Ember.VirtualListScrollerEvents, {
_isScrolling: false, _isScrolling: false,
_mouseWheel: null,
css: { css: {
position: 'relative', position: 'relative',
overflow: 'hidden' overflow: 'hidden'
@ -1041,7 +1200,7 @@ Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, {
this.setupScroller(); this.setupScroller();
}, },
_scrollerTop: 0, _scrollerTop: 0,
applyTransform: Ember.ListViewHelper.applyTransform, applyTransform: Ember.ListViewHelper.apply3DTransform,
setupScroller: function(){ setupScroller: function(){
var view, y; var view, y;
@ -1052,7 +1211,7 @@ Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, {
if (view.state !== 'inDOM') { return; } if (view.state !== 'inDOM') { return; }
if (view.listContainerElement) { if (view.listContainerElement) {
view.applyTransform(view.listContainerElement, {x: 0, y: -top}); view.applyTransform(view.listContainerElement, 0, -top);
view._scrollerTop = top; view._scrollerTop = top;
view._scrollContentTo(top); view._scrollContentTo(top);
} }
@ -1072,17 +1231,7 @@ Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, {
}, 'width', 'height', 'totalHeight'), }, 'width', 'height', 'totalHeight'),
didInsertElement: function() { didInsertElement: function() {
var that, listContainerElement;
that = this;
this.listContainerElement = this.$('> .ember-list-container')[0]; this.listContainerElement = this.$('> .ember-list-container')[0];
this._mouseWheel = function(e) { that.mouseWheel(e); };
this.$().on('mousewheel', this._mouseWheel);
},
willDestroyElement: function() {
this.$().off('mousewheel', this._mouseWheel);
}, },
willBeginScroll: function(touches, timeStamp) { willBeginScroll: function(touches, timeStamp) {
@ -1113,6 +1262,10 @@ Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, {
} }
}, },
endScroll: function(timeStamp) {
this.scroller.doTouchEnd(timeStamp);
},
// api // api
scrollTo: function(y, animate) { scrollTo: function(y, animate) {
if (animate === undefined) { if (animate === undefined) {
@ -1134,48 +1287,6 @@ Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, {
this.scroller.scrollBy(0, delta, true); this.scroller.scrollBy(0, delta, true);
} }
return false;
},
endScroll: function(timeStamp) {
this.scroller.doTouchEnd(timeStamp);
},
touchStart: function(e){
e = e.originalEvent || e;
this.willBeginScroll(e.touches, e.timeStamp);
return false;
},
touchMove: function(e){
e = e.originalEvent || e;
this.continueScroll(e.touches, e.timeStamp);
return false;
},
touchEnd: function(e){
e = e.originalEvent || e;
this.endScroll(e.timeStamp);
return false;
},
mouseDown: function(e){
this.willBeginScroll([e], e.timeStamp);
return false;
},
mouseMove: function(e){
this.continueScroll([e], e.timeStamp);
return false;
},
mouseUp: function(e){
this.endScroll(e.timeStamp);
return false;
},
mouseLeave: function(e){
this.endScroll(e.timeStamp);
return false; return false;
} }
}); });
@ -1187,3 +1298,4 @@ Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, {
(function() { (function() {
})(); })();

Some files were not shown because too many files have changed in this diff Show More