Clean up JS, add YUIDoc support, automatically create IIFE via asset pipeline

This commit is contained in:
Robin Ward 2013-02-22 15:41:12 -05:00
parent 0321643636
commit e461c84253
217 changed files with 11996 additions and 12131 deletions

View file

@ -1,63 +1,59 @@
(function() {
/**
This controller supports interface for creating custom CSS skins in Discourse.
@class AdminCustomizeController
@extends Ember.Controller
@namespace Discourse
@module Discourse
**/
Discourse.AdminCustomizeController = Ember.Controller.extend({
/**
This controller supports interface for creating custom CSS skins in Discourse.
Create a new customization style
@class AdminCustomizeController
@extends Ember.Controller
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminCustomizeController = Ember.Controller.extend({
@method newCustomization
**/
newCustomization: function() {
var item = Discourse.SiteCustomization.create({name: 'New Style'});
this.get('content').pushObject(item);
this.set('content.selectedItem', item);
},
/**
Create a new customization style
/**
Select a given style
@method newCustomization
**/
newCustomization: function() {
var item = Discourse.SiteCustomization.create({name: 'New Style'});
this.get('content').pushObject(item);
this.set('content.selectedItem', item);
},
@method selectStyle
@param {Discourse.SiteCustomization} style The style we are selecting
**/
selectStyle: function(style) {
this.set('content.selectedItem', style);
},
/**
Select a given style
/**
Save the current customization
@method selectStyle
@param {Discourse.SiteCustomization} style The style we are selecting
**/
selectStyle: function(style) {
this.set('content.selectedItem', style);
},
@method save
**/
save: function() {
this.get('content.selectedItem').save();
},
/**
Save the current customization
/**
Destroy the current customization
@method save
**/
save: function() {
this.get('content.selectedItem').save();
},
@method destroy
**/
destroy: function() {
var _this = this;
return bootbox.confirm(Em.String.i18n("admin.customize.delete_confirm"), Em.String.i18n("no_value"), Em.String.i18n("yes_value"), function(result) {
var selected;
if (result) {
selected = _this.get('content.selectedItem');
selected["delete"]();
_this.set('content.selectedItem', null);
return _this.get('content').removeObject(selected);
}
});
}
/**
Destroy the current customization
@method destroy
**/
destroy: function() {
var _this = this;
return bootbox.confirm(Em.String.i18n("admin.customize.delete_confirm"), Em.String.i18n("no_value"), Em.String.i18n("yes_value"), function(result) {
var selected;
if (result) {
selected = _this.get('content.selectedItem');
selected["delete"]();
_this.set('content.selectedItem', null);
return _this.get('content').removeObject(selected);
}
});
}
});
}).call(this);
});

View file

@ -1,16 +1,12 @@
(function() {
/**
This controller supports the default interface when you enter the admin section.
/**
This controller supports the default interface when you enter the admin section.
@class AdminDashboardController
@extends Ember.Controller
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminDashboardController = Ember.Controller.extend({
loading: true,
versionCheck: null
});
}).call(this);
@class AdminDashboardController
@extends Ember.Controller
@namespace Discourse
@module Discourse
**/
Discourse.AdminDashboardController = Ember.Controller.extend({
loading: true,
versionCheck: null
});

View file

@ -1,43 +1,39 @@
(function() {
/**
This controller supports the interface for reviewing email logs.
@class AdminEmailLogsController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
Discourse.AdminEmailLogsController = Ember.ArrayController.extend(Discourse.Presence, {
/**
Is the "send test email" button disabled?
@property sendTestEmailDisabled
**/
sendTestEmailDisabled: (function() {
return this.blank('testEmailAddress');
}).property('testEmailAddress'),
/**
This controller supports the interface for reviewing email logs.
Sends a test email to the currently entered email address
@class AdminEmailLogsController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminEmailLogsController = Ember.ArrayController.extend(Discourse.Presence, {
/**
Is the "send test email" button disabled?
@property sendTestEmailDisabled
**/
sendTestEmailDisabled: (function() {
return this.blank('testEmailAddress');
}).property('testEmailAddress'),
/**
Sends a test email to the currently entered email address
@method sendTestEmail
**/
sendTestEmail: function() {
var _this = this;
_this.set('sentTestEmail', false);
jQuery.ajax({
url: '/admin/email_logs/test',
type: 'POST',
data: { email_address: this.get('testEmailAddress') },
success: function() {
return _this.set('sentTestEmail', true);
}
});
return false;
}
});
}).call(this);
@method sendTestEmail
**/
sendTestEmail: function() {
var _this = this;
_this.set('sentTestEmail', false);
jQuery.ajax({
url: '/admin/email_logs/test',
type: 'POST',
data: { email_address: this.get('testEmailAddress') },
success: function() {
return _this.set('sentTestEmail', true);
}
});
return false;
}
});

View file

@ -1,63 +1,59 @@
(function() {
/**
This controller supports the interface for dealing with flags in the admin section.
@class AdminFlagsController
@extends Ember.Controller
@namespace Discourse
@module Discourse
**/
Discourse.AdminFlagsController = Ember.Controller.extend({
/**
Clear all flags on a post
@method clearFlags
@param {Discourse.FlaggedPost} item The post whose flags we want to clear
**/
clearFlags: function(item) {
var _this = this;
item.clearFlags().then((function() {
_this.content.removeObject(item);
}), (function() {
bootbox.alert("something went wrong");
}));
},
/**
This controller supports the interface for dealing with flags in the admin section.
Deletes a post
@class AdminFlagsController
@extends Ember.Controller
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminFlagsController = Ember.Controller.extend({
/**
Clear all flags on a post
@method deletePost
@param {Discourse.FlaggedPost} item The post to delete
**/
deletePost: function(item) {
var _this = this;
item.deletePost().then((function() {
_this.content.removeObject(item);
}), (function() {
bootbox.alert("something went wrong");
}));
},
@method clearFlags
@param {Discourse.FlaggedPost} item The post whose flags we want to clear
**/
clearFlags: function(item) {
var _this = this;
item.clearFlags().then((function() {
_this.content.removeObject(item);
}), (function() {
bootbox.alert("something went wrong");
}));
},
/**
Are we viewing the 'old' view?
/**
Deletes a post
@property adminOldFlagsView
**/
adminOldFlagsView: (function() {
return this.query === 'old';
}).property('query'),
@method deletePost
@param {Discourse.FlaggedPost} item The post to delete
**/
deletePost: function(item) {
var _this = this;
item.deletePost().then((function() {
_this.content.removeObject(item);
}), (function() {
bootbox.alert("something went wrong");
}));
},
/**
Are we viewing the 'active' view?
/**
Are we viewing the 'old' view?
@property adminOldFlagsView
**/
adminOldFlagsView: (function() {
return this.query === 'old';
}).property('query'),
/**
Are we viewing the 'active' view?
@property adminActiveFlagsView
**/
adminActiveFlagsView: (function() {
return this.query === 'active';
}).property('query')
});
}).call(this);
@property adminActiveFlagsView
**/
adminActiveFlagsView: (function() {
return this.query === 'active';
}).property('query')
});

View file

@ -1,74 +1,70 @@
(function() {
/**
This controller supports the interface for SiteSettings.
@class AdminSiteSettingsController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
Discourse.AdminSiteSettingsController = Ember.ArrayController.extend(Discourse.Presence, {
filter: null,
onlyOverridden: false,
/**
This controller supports the interface for SiteSettings.
The list of settings based on the current filters
@class AdminSiteSettingsController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminSiteSettingsController = Ember.ArrayController.extend(Discourse.Presence, {
filter: null,
onlyOverridden: false,
@property filteredContent
**/
filteredContent: (function() {
var filter,
_this = this;
if (!this.present('content')) return null;
if (this.get('filter')) {
filter = this.get('filter').toLowerCase();
}
/**
The list of settings based on the current filters
@property filteredContent
**/
filteredContent: (function() {
var filter,
_this = this;
if (!this.present('content')) return null;
if (this.get('filter')) {
filter = this.get('filter').toLowerCase();
return this.get('content').filter(function(item, index, enumerable) {
if (_this.get('onlyOverridden') && !item.get('overridden')) return false;
if (filter) {
if (item.get('setting').toLowerCase().indexOf(filter) > -1) return true;
if (item.get('description').toLowerCase().indexOf(filter) > -1) return true;
if (item.get('value').toLowerCase().indexOf(filter) > -1) return true;
return false;
}
return this.get('content').filter(function(item, index, enumerable) {
if (_this.get('onlyOverridden') && !item.get('overridden')) return false;
if (filter) {
if (item.get('setting').toLowerCase().indexOf(filter) > -1) return true;
if (item.get('description').toLowerCase().indexOf(filter) > -1) return true;
if (item.get('value').toLowerCase().indexOf(filter) > -1) return true;
return false;
}
return true;
});
}).property('filter', 'content.@each', 'onlyOverridden'),
return true;
});
}).property('filter', 'content.@each', 'onlyOverridden'),
/**
Reset a setting to its default value
/**
Reset a setting to its default value
@method resetDefault
@param {Discourse.SiteSetting} setting The setting we want to revert
**/
resetDefault: function(setting) {
setting.set('value', setting.get('default'));
setting.save();
},
@method resetDefault
@param {Discourse.SiteSetting} setting The setting we want to revert
**/
resetDefault: function(setting) {
setting.set('value', setting.get('default'));
setting.save();
},
/**
Save changes to a site setting
/**
Save changes to a site setting
@method save
@param {Discourse.SiteSetting} setting The setting we've changed
**/
save: function(setting) {
setting.save();
},
@method save
@param {Discourse.SiteSetting} setting The setting we've changed
**/
save: function(setting) {
setting.save();
},
/**
Cancel changes to a site setting
/**
Cancel changes to a site setting
@method cancel
@param {Discourse.SiteSetting} setting The setting we've changed but want to revert
**/
cancel: function(setting) {
setting.resetValue();
}
});
}).call(this);
@method cancel
@param {Discourse.SiteSetting} setting The setting we've changed but want to revert
**/
cancel: function(setting) {
setting.resetValue();
}
});

View file

@ -1,111 +1,107 @@
(function() {
/**
This controller supports the interface for listing users in the admin section.
@class AdminUsersListController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListController = Ember.ArrayController.extend(Discourse.Presence, {
username: null,
query: null,
selectAll: false,
content: null,
/**
This controller supports the interface for listing users in the admin section.
Triggered when the selectAll property is changed
@class AdminUsersListController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminUsersListController = Ember.ArrayController.extend(Discourse.Presence, {
username: null,
query: null,
selectAll: false,
content: null,
@event selectAll
**/
selectAllChanged: (function() {
var _this = this;
this.get('content').each(function(user) {
user.set('selected', _this.get('selectAll'));
});
}).observes('selectAll'),
/**
Triggered when the selectAll property is changed
/**
Triggered when the username filter is changed
@event selectAll
**/
selectAllChanged: (function() {
var _this = this;
this.get('content').each(function(user) {
user.set('selected', _this.get('selectAll'));
});
}).observes('selectAll'),
@event filterUsers
**/
filterUsers: Discourse.debounce(function() {
this.refreshUsers();
}, 250).observes('username'),
/**
Triggered when the username filter is changed
/**
Triggered when the order of the users list is changed
@event filterUsers
**/
filterUsers: Discourse.debounce(function() {
@event orderChanged
**/
orderChanged: (function() {
this.refreshUsers();
}).observes('query'),
/**
Do we want to show the approval controls?
@property showApproval
**/
showApproval: (function() {
if (!Discourse.SiteSettings.must_approve_users) return false;
if (this.get('query') === 'new') return true;
if (this.get('query') === 'pending') return true;
}).property('query'),
/**
How many users are currently selected
@property selectedCount
**/
selectedCount: (function() {
if (this.blank('content')) return 0;
return this.get('content').filterProperty('selected').length;
}).property('content.@each.selected'),
/**
Do we have any selected users?
@property hasSelection
**/
hasSelection: (function() {
return this.get('selectedCount') > 0;
}).property('selectedCount'),
/**
Refresh the current list of users.
@method refreshUsers
**/
refreshUsers: function() {
this.set('content', Discourse.AdminUser.findAll(this.get('query'), this.get('username')));
},
/**
Show the list of users.
@method show
**/
show: function(term) {
if (this.get('query') === term) {
this.refreshUsers();
}, 250).observes('username'),
/**
Triggered when the order of the users list is changed
@event orderChanged
**/
orderChanged: (function() {
this.refreshUsers();
}).observes('query'),
/**
Do we want to show the approval controls?
@property showApproval
**/
showApproval: (function() {
if (!Discourse.SiteSettings.must_approve_users) return false;
if (this.get('query') === 'new') return true;
if (this.get('query') === 'pending') return true;
}).property('query'),
/**
How many users are currently selected
@property selectedCount
**/
selectedCount: (function() {
if (this.blank('content')) return 0;
return this.get('content').filterProperty('selected').length;
}).property('content.@each.selected'),
/**
Do we have any selected users?
@property hasSelection
**/
hasSelection: (function() {
return this.get('selectedCount') > 0;
}).property('selectedCount'),
/**
Refresh the current list of users.
@method refreshUsers
**/
refreshUsers: function() {
this.set('content', Discourse.AdminUser.findAll(this.get('query'), this.get('username')));
},
/**
Show the list of users.
@method show
**/
show: function(term) {
if (this.get('query') === term) {
this.refreshUsers();
return;
}
this.set('query', term);
},
/**
Approve all the currently selected users.
@method approveUsers
**/
approveUsers: function() {
Discourse.AdminUser.bulkApprove(this.get('content').filterProperty('selected'));
return;
}
});
this.set('query', term);
},
}).call(this);
/**
Approve all the currently selected users.
@method approveUsers
**/
approveUsers: function() {
Discourse.AdminUser.bulkApprove(this.get('content').filterProperty('selected'));
}
});

View file

@ -1,190 +1,186 @@
(function() {
/**
Our data model for dealing with users from the admin section.
/**
Our data model for dealing with users from the admin section.
@class AdminUser
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.AdminUser = Discourse.Model.extend({
deleteAllPosts: function() {
this.set('can_delete_all_posts', false);
jQuery.ajax("/admin/users/" + (this.get('id')) + "/delete_all_posts", {type: 'PUT'});
},
@class AdminUser
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminUser = Discourse.Model.extend({
deleteAllPosts: function() {
this.set('can_delete_all_posts', false);
jQuery.ajax("/admin/users/" + (this.get('id')) + "/delete_all_posts", {type: 'PUT'});
},
// Revoke the user's admin access
revokeAdmin: function() {
this.set('admin', false);
this.set('can_grant_admin', true);
this.set('can_revoke_admin', false);
return jQuery.ajax("/admin/users/" + (this.get('id')) + "/revoke_admin", {type: 'PUT'});
},
// Revoke the user's admin access
revokeAdmin: function() {
this.set('admin', false);
this.set('can_grant_admin', true);
this.set('can_revoke_admin', false);
return jQuery.ajax("/admin/users/" + (this.get('id')) + "/revoke_admin", {type: 'PUT'});
},
grantAdmin: function() {
this.set('admin', true);
this.set('can_grant_admin', false);
this.set('can_revoke_admin', true);
jQuery.ajax("/admin/users/" + (this.get('id')) + "/grant_admin", {type: 'PUT'});
},
grantAdmin: function() {
this.set('admin', true);
this.set('can_grant_admin', false);
this.set('can_revoke_admin', true);
jQuery.ajax("/admin/users/" + (this.get('id')) + "/grant_admin", {type: 'PUT'});
},
// Revoke the user's moderation access
revokeModeration: function() {
this.set('moderator', false);
this.set('can_grant_moderation', true);
this.set('can_revoke_moderation', false);
return jQuery.ajax("/admin/users/" + (this.get('id')) + "/revoke_moderation", {type: 'PUT'});
},
// Revoke the user's moderation access
revokeModeration: function() {
this.set('moderator', false);
this.set('can_grant_moderation', true);
this.set('can_revoke_moderation', false);
return jQuery.ajax("/admin/users/" + (this.get('id')) + "/revoke_moderation", {type: 'PUT'});
},
grantModeration: function() {
this.set('moderator', true);
this.set('can_grant_moderation', false);
this.set('can_revoke_moderation', true);
jQuery.ajax("/admin/users/" + (this.get('id')) + "/grant_moderation", {type: 'PUT'});
},
grantModeration: function() {
this.set('moderator', true);
this.set('can_grant_moderation', false);
this.set('can_revoke_moderation', true);
jQuery.ajax("/admin/users/" + (this.get('id')) + "/grant_moderation", {type: 'PUT'});
},
refreshBrowsers: function() {
jQuery.ajax("/admin/users/" + (this.get('id')) + "/refresh_browsers", {type: 'POST'});
bootbox.alert("Message sent to all clients!");
},
refreshBrowsers: function() {
jQuery.ajax("/admin/users/" + (this.get('id')) + "/refresh_browsers", {type: 'POST'});
bootbox.alert("Message sent to all clients!");
},
approve: function() {
this.set('can_approve', false);
this.set('approved', true);
this.set('approved_by', Discourse.get('currentUser'));
jQuery.ajax("/admin/users/" + (this.get('id')) + "/approve", {type: 'PUT'});
},
approve: function() {
this.set('can_approve', false);
this.set('approved', true);
this.set('approved_by', Discourse.get('currentUser'));
jQuery.ajax("/admin/users/" + (this.get('id')) + "/approve", {type: 'PUT'});
},
username_lower: (function() {
return this.get('username').toLowerCase();
}).property('username'),
username_lower: (function() {
return this.get('username').toLowerCase();
}).property('username'),
trustLevel: (function() {
return Discourse.get('site.trust_levels').findProperty('id', this.get('trust_level'));
}).property('trust_level'),
trustLevel: (function() {
return Discourse.get('site.trust_levels').findProperty('id', this.get('trust_level'));
}).property('trust_level'),
canBan: (function() {
return !this.admin && !this.moderator;
}).property('admin', 'moderator'),
canBan: (function() {
return !this.admin && !this.moderator;
}).property('admin', 'moderator'),
banDuration: (function() {
var banned_at, banned_till;
banned_at = Date.create(this.banned_at);
banned_till = Date.create(this.banned_till);
return "" + (banned_at.short()) + " - " + (banned_till.short());
}).property('banned_till', 'banned_at'),
banDuration: (function() {
var banned_at, banned_till;
banned_at = Date.create(this.banned_at);
banned_till = Date.create(this.banned_till);
return "" + (banned_at.short()) + " - " + (banned_till.short());
}).property('banned_till', 'banned_at'),
ban: function() {
var duration,
_this = this;
if (duration = parseInt(window.prompt(Em.String.i18n('admin.user.ban_duration')), 10)) {
if (duration > 0) {
return jQuery.ajax("/admin/users/" + this.id + "/ban", {
type: 'PUT',
data: {duration: duration},
success: function() {
window.location.reload();
},
error: function(e) {
var error;
error = Em.String.i18n('admin.user.ban_failed', {
error: "http: " + e.status + " - " + e.body
});
bootbox.alert(error);
}
});
}
}
},
ban: function() {
var duration,
_this = this;
if (duration = parseInt(window.prompt(Em.String.i18n('admin.user.ban_duration')), 10)) {
if (duration > 0) {
return jQuery.ajax("/admin/users/" + this.id + "/ban", {
type: 'PUT',
data: {duration: duration},
success: function() {
window.location.reload();
},
error: function(e) {
var error;
error = Em.String.i18n('admin.user.ban_failed', {
error: "http: " + e.status + " - " + e.body
});
bootbox.alert(error);
}
});
unban: function() {
var _this = this;
return jQuery.ajax("/admin/users/" + this.id + "/unban", {
type: 'PUT',
success: function() {
window.location.reload();
},
error: function(e) {
var error;
error = Em.String.i18n('admin.user.unban_failed', {
error: "http: " + e.status + " - " + e.body
});
bootbox.alert(error);
}
});
},
impersonate: function() {
var _this = this;
return jQuery.ajax("/admin/impersonate", {
type: 'POST',
data: {
username_or_email: this.get('username')
},
success: function() {
document.location = "/";
},
error: function(e) {
_this.set('loading', false);
if (e.status === 404) {
return bootbox.alert(Em.String.i18n('admin.impersonate.not_found'));
} else {
return bootbox.alert(Em.String.i18n('admin.impersonate.invalid'));
}
}
},
});
}
unban: function() {
var _this = this;
return jQuery.ajax("/admin/users/" + this.id + "/unban", {
type: 'PUT',
success: function() {
window.location.reload();
},
error: function(e) {
var error;
error = Em.String.i18n('admin.user.unban_failed', {
error: "http: " + e.status + " - " + e.body
});
bootbox.alert(error);
}
});
},
});
impersonate: function() {
var _this = this;
return jQuery.ajax("/admin/impersonate", {
type: 'POST',
data: {
username_or_email: this.get('username')
},
success: function() {
document.location = "/";
},
error: function(e) {
_this.set('loading', false);
if (e.status === 404) {
return bootbox.alert(Em.String.i18n('admin.impersonate.not_found'));
} else {
return bootbox.alert(Em.String.i18n('admin.impersonate.invalid'));
}
}
});
}
window.Discourse.AdminUser.reopenClass({
});
bulkApprove: function(users) {
users.each(function(user) {
user.set('approved', true);
user.set('can_approve', false);
return user.set('selected', false);
});
return jQuery.ajax("/admin/users/approve-bulk", {
type: 'PUT',
data: {
users: users.map(function(u) {
return u.id;
})
}
});
},
window.Discourse.AdminUser.reopenClass({
find: function(username) {
var promise;
promise = new RSVP.Promise();
jQuery.ajax({
url: "/admin/users/" + username,
success: function(result) {
return promise.resolve(Discourse.AdminUser.create(result));
}
});
return promise;
},
bulkApprove: function(users) {
users.each(function(user) {
user.set('approved', true);
user.set('can_approve', false);
return user.set('selected', false);
});
return jQuery.ajax("/admin/users/approve-bulk", {
type: 'PUT',
data: {
users: users.map(function(u) {
return u.id;
})
}
});
},
find: function(username) {
var promise;
promise = new RSVP.Promise();
jQuery.ajax({
url: "/admin/users/" + username,
success: function(result) {
return promise.resolve(Discourse.AdminUser.create(result));
}
});
return promise;
},
findAll: function(query, filter) {
var result;
result = Em.A();
jQuery.ajax({
url: "/admin/users/list/" + query + ".json",
data: {
filter: filter
},
success: function(users) {
return users.each(function(u) {
return result.pushObject(Discourse.AdminUser.create(u));
});
}
});
return result;
}
});
}).call(this);
findAll: function(query, filter) {
var result;
result = Em.A();
jQuery.ajax({
url: "/admin/users/list/" + query + ".json",
data: {
filter: filter
},
success: function(users) {
return users.each(function(u) {
return result.pushObject(Discourse.AdminUser.create(u));
});
}
});
return result;
}
});

View file

@ -1,38 +1,36 @@
(function() {
/**
Our data model for representing an email log.
/**
Our data model for representing an email log.
@class EmailLog
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.EmailLog = Discourse.Model.extend({});
@class EmailLog
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
window.Discourse.EmailLog = Discourse.Model.extend({});
Discourse.EmailLog.reopenClass({
window.Discourse.EmailLog.reopenClass({
create: function(attrs) {
if (attrs.user) {
attrs.user = Discourse.AdminUser.create(attrs.user);
}
return this._super(attrs);
},
findAll: function(filter) {
var result;
result = Em.A();
jQuery.ajax({
url: "/admin/email_logs.json",
data: { filter: filter },
success: function(logs) {
logs.each(function(log) {
result.pushObject(Discourse.EmailLog.create(log));
});
}
});
return result;
create: function(attrs) {
if (attrs.user) {
attrs.user = Discourse.AdminUser.create(attrs.user);
}
});
return this._super(attrs);
},
findAll: function(filter) {
var result;
result = Em.A();
jQuery.ajax({
url: "/admin/email_logs.json",
data: { filter: filter },
success: function(logs) {
logs.each(function(log) {
result.pushObject(Discourse.EmailLog.create(log));
});
}
});
return result;
}
});
}).call(this);

View file

@ -1,85 +1,56 @@
(function() {
/**
Our data model for interacting with flagged posts.
/**
Our data model for interacting with flagged posts.
@class FlaggedPost
@extends Discourse.Post
@namespace Discourse
@module Discourse
**/
Discourse.FlaggedPost = Discourse.Post.extend({
@class FlaggedPost
@extends Discourse.Post
@namespace Discourse
@module Discourse
**/
window.Discourse.FlaggedPost = Discourse.Post.extend({
flaggers: (function() {
var r,
_this = this;
r = [];
this.post_actions.each(function(a) {
return r.push(_this.userLookup[a.user_id]);
});
return r;
}).property(),
flaggers: (function() {
var r,
_this = this;
r = [];
this.post_actions.each(function(a) {
return r.push(_this.userLookup[a.user_id]);
});
return r;
}).property(),
messages: (function() {
var r,
_this = this;
r = [];
this.post_actions.each(function(a) {
if (a.message) {
return r.push({
user: _this.userLookup[a.user_id],
message: a.message
});
}
});
return r;
}).property(),
lastFlagged: (function() {
return this.post_actions[0].created_at;
}).property(),
user: (function() {
return this.userLookup[this.user_id];
}).property(),
topicHidden: (function() {
return this.get('topic_visible') === 'f';
}).property('topic_hidden'),
deletePost: function() {
var promise;
promise = new RSVP.Promise();
if (this.get('post_number') === "1") {
return jQuery.ajax("/t/" + this.topic_id, {
type: 'DELETE',
cache: false,
success: function() {
promise.resolve();
},
error: function(e) {
promise.reject();
}
});
} else {
return jQuery.ajax("/posts/" + this.id, {
type: 'DELETE',
cache: false,
success: function() {
promise.resolve();
},
error: function(e) {
promise.reject();
}
messages: (function() {
var r,
_this = this;
r = [];
this.post_actions.each(function(a) {
if (a.message) {
return r.push({
user: _this.userLookup[a.user_id],
message: a.message
});
}
},
});
return r;
}).property(),
clearFlags: function() {
var promise;
promise = new RSVP.Promise();
jQuery.ajax("/admin/flags/clear/" + this.id, {
type: 'POST',
lastFlagged: (function() {
return this.post_actions[0].created_at;
}).property(),
user: (function() {
return this.userLookup[this.user_id];
}).property(),
topicHidden: (function() {
return this.get('topic_visible') === 'f';
}).property('topic_hidden'),
deletePost: function() {
var promise;
promise = new RSVP.Promise();
if (this.get('post_number') === "1") {
return jQuery.ajax("/t/" + this.topic_id, {
type: 'DELETE',
cache: false,
success: function() {
promise.resolve();
@ -88,37 +59,64 @@
promise.reject();
}
});
return promise;
},
hiddenClass: (function() {
if (this.get('hidden') === "t") return "hidden-post";
}).property()
});
window.Discourse.FlaggedPost.reopenClass({
findAll: function(filter) {
var result;
result = Em.A();
jQuery.ajax({
url: "/admin/flags/" + filter + ".json",
success: function(data) {
var userLookup;
userLookup = {};
data.users.each(function(u) {
userLookup[u.id] = Discourse.User.create(u);
});
return data.posts.each(function(p) {
var f;
f = Discourse.FlaggedPost.create(p);
f.userLookup = userLookup;
return result.pushObject(f);
});
} else {
return jQuery.ajax("/posts/" + this.id, {
type: 'DELETE',
cache: false,
success: function() {
promise.resolve();
},
error: function(e) {
promise.reject();
}
});
return result;
}
});
},
clearFlags: function() {
var promise;
promise = new RSVP.Promise();
jQuery.ajax("/admin/flags/clear/" + this.id, {
type: 'POST',
cache: false,
success: function() {
promise.resolve();
},
error: function(e) {
promise.reject();
}
});
return promise;
},
hiddenClass: (function() {
if (this.get('hidden') === "t") return "hidden-post";
}).property()
});
Discourse.FlaggedPost.reopenClass({
findAll: function(filter) {
var result;
result = Em.A();
jQuery.ajax({
url: "/admin/flags/" + filter + ".json",
success: function(data) {
var userLookup;
userLookup = {};
data.users.each(function(u) {
userLookup[u.id] = Discourse.User.create(u);
});
return data.posts.each(function(p) {
var f;
f = Discourse.FlaggedPost.create(p);
f.userLookup = userLookup;
return result.pushObject(f);
});
}
});
return result;
}
});
}).call(this);

View file

@ -1,117 +1,112 @@
(function() {
var SiteCustomizations;
/**
Our data model for interacting with site customizations.
/**
Our data model for interacting with site customizations.
@class SiteCustomization
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.SiteCustomization = Discourse.Model.extend({
trackedProperties: ['enabled', 'name', 'stylesheet', 'header', 'override_default_style'],
@class SiteCustomization
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
window.Discourse.SiteCustomization = Discourse.Model.extend({
trackedProperties: ['enabled', 'name', 'stylesheet', 'header', 'override_default_style'],
init: function() {
this._super();
return this.startTrackingChanges();
},
description: (function() {
return "" + this.name + (this.enabled ? ' (*)' : '');
}).property('selected', 'name'),
init: function() {
this._super();
return this.startTrackingChanges();
},
description: (function() {
return "" + this.name + (this.enabled ? ' (*)' : '');
}).property('selected', 'name'),
changed: (function() {
var _this = this;
if (!this.originals) {
return false;
}
return this.trackedProperties.any(function(p) {
return _this.originals[p] !== _this.get(p);
});
}).property('override_default_style', 'enabled', 'name', 'stylesheet', 'header', 'originals'),
startTrackingChanges: function() {
var _this = this;
this.set('originals', {});
return this.trackedProperties.each(function(p) {
_this.originals[p] = _this.get(p);
return true;
});
},
previewUrl: (function() {
return "/?preview-style=" + (this.get('key'));
}).property('key'),
disableSave: (function() {
return !this.get('changed');
}).property('changed'),
save: function() {
var data;
this.startTrackingChanges();
data = {
name: this.name,
enabled: this.enabled,
stylesheet: this.stylesheet,
header: this.header,
override_default_style: this.override_default_style
};
return jQuery.ajax({
url: "/admin/site_customizations" + (this.id ? '/' + this.id : ''),
data: {
site_customization: data
},
type: this.id ? 'PUT' : 'POST'
});
},
"delete": function() {
if (!this.id) return;
return jQuery.ajax({
url: "/admin/site_customizations/" + this.id,
type: 'DELETE'
});
changed: (function() {
var _this = this;
if (!this.originals) {
return false;
}
return this.trackedProperties.any(function(p) {
return _this.originals[p] !== _this.get(p);
});
}).property('override_default_style', 'enabled', 'name', 'stylesheet', 'header', 'originals'),
});
startTrackingChanges: function() {
var _this = this;
this.set('originals', {});
return this.trackedProperties.each(function(p) {
_this.originals[p] = _this.get(p);
return true;
});
},
SiteCustomizations = Ember.ArrayProxy.extend({
selectedItemChanged: (function() {
var selected;
selected = this.get('selectedItem');
return this.get('content').each(function(i) {
return i.set('selected', selected === i);
});
}).observes('selectedItem')
});
previewUrl: (function() {
return "/?preview-style=" + (this.get('key'));
}).property('key'),
Discourse.SiteCustomization.reopenClass({
findAll: function() {
var content,
_this = this;
content = SiteCustomizations.create({
content: [],
loading: true
});
jQuery.ajax({
url: "/admin/site_customizations",
dataType: "json",
success: function(data) {
if (data) {
data.site_customizations.each(function(c) {
var item;
item = Discourse.SiteCustomization.create(c);
return content.pushObject(item);
});
}
return content.set('loading', false);
disableSave: (function() {
return !this.get('changed');
}).property('changed'),
save: function() {
var data;
this.startTrackingChanges();
data = {
name: this.name,
enabled: this.enabled,
stylesheet: this.stylesheet,
header: this.header,
override_default_style: this.override_default_style
};
return jQuery.ajax({
url: "/admin/site_customizations" + (this.id ? '/' + this.id : ''),
data: {
site_customization: data
},
type: this.id ? 'PUT' : 'POST'
});
},
"delete": function() {
if (!this.id) return;
return jQuery.ajax({
url: "/admin/site_customizations/" + this.id,
type: 'DELETE'
});
}
});
var SiteCustomizations = Ember.ArrayProxy.extend({
selectedItemChanged: (function() {
var selected;
selected = this.get('selectedItem');
return this.get('content').each(function(i) {
return i.set('selected', selected === i);
});
}).observes('selectedItem')
});
Discourse.SiteCustomization.reopenClass({
findAll: function() {
var content,
_this = this;
content = SiteCustomizations.create({
content: [],
loading: true
});
jQuery.ajax({
url: "/admin/site_customizations",
dataType: "json",
success: function(data) {
if (data) {
data.site_customizations.each(function(c) {
var item;
item = Discourse.SiteCustomization.create(c);
return content.pushObject(item);
});
}
});
return content;
}
});
}).call(this);
return content.set('loading', false);
}
});
return content;
}
});

View file

@ -1,65 +1,63 @@
(function() {
/**
Our data model for interacting with site settings.
/**
Our data model for interacting with site settings.
@class SiteSetting
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.SiteSetting = Discourse.Model.extend({
// Whether a property is short.
short: (function() {
if (this.blank('value')) return true;
return this.get('value').toString().length < 80;
}).property('value'),
@class SiteSetting
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
window.Discourse.SiteSetting = Discourse.Model.extend({
// Whether a property is short.
short: (function() {
if (this.blank('value')) return true;
return this.get('value').toString().length < 80;
}).property('value'),
// Whether the site setting has changed
dirty: (function() {
return this.get('originalValue') !== this.get('value');
}).property('originalValue', 'value'),
// Whether the site setting has changed
dirty: (function() {
return this.get('originalValue') !== this.get('value');
}).property('originalValue', 'value'),
overridden: (function() {
var defaultVal, val;
val = this.get('value');
defaultVal = this.get('default');
if (val && defaultVal) {
return val.toString() !== defaultVal.toString();
}
return val !== defaultVal;
}).property('value'),
overridden: (function() {
var defaultVal, val;
val = this.get('value');
defaultVal = this.get('default');
if (val && defaultVal) {
return val.toString() !== defaultVal.toString();
resetValue: function() {
this.set('value', this.get('originalValue'));
},
save: function() {
// Update the setting
var _this = this;
return jQuery.ajax("/admin/site_settings/" + (this.get('setting')), {
data: { value: this.get('value') },
type: 'PUT',
success: function() {
_this.set('originalValue', _this.get('value'));
}
return val !== defaultVal;
}).property('value'),
});
}
});
resetValue: function() {
this.set('value', this.get('originalValue'));
},
save: function() {
// Update the setting
var _this = this;
return jQuery.ajax("/admin/site_settings/" + (this.get('setting')), {
data: { value: this.get('value') },
type: 'PUT',
success: function() {
_this.set('originalValue', _this.get('value'));
}
Discourse.SiteSetting.reopenClass({
findAll: function() {
var result;
result = Em.A();
jQuery.get("/admin/site_settings", function(settings) {
return settings.each(function(s) {
s.originalValue = s.value;
return result.pushObject(Discourse.SiteSetting.create(s));
});
}
});
});
return result;
}
});
window.Discourse.SiteSetting.reopenClass({
findAll: function() {
var result;
result = Em.A();
jQuery.get("/admin/site_settings", function(settings) {
return settings.each(function(s) {
s.originalValue = s.value;
return result.pushObject(Discourse.SiteSetting.create(s));
});
});
return result;
}
});
}).call(this);

View file

@ -1,39 +1,35 @@
(function() {
/**
Our data model for determining whether there's a new version of Discourse
/**
Our data model for determining whether there's a new version of Discourse
@class VersionCheck
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.VersionCheck = Discourse.Model.extend({
upToDate: function() {
return this.get('latest_version') === this.get('installed_version');
}.property('latest_version', 'installed_version'),
@class VersionCheck
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
window.Discourse.VersionCheck = Discourse.Model.extend({
upToDate: function() {
return this.get('latest_version') === this.get('installed_version');
}.property('latest_version', 'installed_version'),
gitLink: function() {
return "https://github.com/discourse/discourse/tree/" + this.get('installed_sha');
}.property('installed_sha'),
gitLink: function() {
return "https://github.com/discourse/discourse/tree/" + this.get('installed_sha');
}.property('installed_sha'),
shortSha: function() {
return this.get('installed_sha').substr(0,10);
}.property('installed_sha')
});
shortSha: function() {
return this.get('installed_sha').substr(0,10);
}.property('installed_sha')
});
Discourse.VersionCheck.reopenClass({
find: function() {
var promise = new RSVP.Promise();
jQuery.ajax({
url: '/admin/version_check',
dataType: 'json',
success: function(json) {
promise.resolve(Discourse.VersionCheck.create(json));
}
});
return promise;
}
});
}).call(this);
Discourse.VersionCheck.reopenClass({
find: function() {
var promise = new RSVP.Promise();
jQuery.ajax({
url: '/admin/version_check',
dataType: 'json',
success: function(json) {
promise.resolve(Discourse.VersionCheck.create(json));
}
});
return promise;
}
});

View file

@ -1,21 +1,19 @@
(function() {
/**
Handles routes related to customization
/**
Handles routes related to customization
@class AdminCustomizeRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminCustomizeRoute = Discourse.Route.extend({
model: function() {
return Discourse.SiteCustomization.findAll();
},
@class AdminCustomizeRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminCustomizeRoute = Discourse.Route.extend({
model: function() {
return Discourse.SiteCustomization.findAll();
},
renderTemplate: function() {
this.render({into: 'admin/templates/admin'});
}
});
renderTemplate: function() {
this.render({into: 'admin/templates/admin'});
}
});
}).call(this);

View file

@ -1,33 +1,30 @@
(function() {
/**
Handles the default admin route
/**
Handles the default admin route
@class AdminDashboardRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminDashboardRoute = Discourse.Route.extend({
setupController: function(c) {
if( !c.get('versionCheckedAt') || Date.create('12 hours ago') > c.get('versionCheckedAt') ) {
this.checkVersion(c);
}
},
renderTemplate: function() {
this.render({into: 'admin/templates/admin'});
},
checkVersion: function(c) {
if( Discourse.SiteSettings.version_checks ) {
Discourse.VersionCheck.find().then(function(vc) {
c.set('versionCheck', vc);
c.set('versionCheckedAt', new Date());
c.set('loading', false);
});
}
@class AdminDashboardRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminDashboardRoute = Discourse.Route.extend({
setupController: function(c) {
if( !c.get('versionCheckedAt') || Date.create('12 hours ago') > c.get('versionCheckedAt') ) {
this.checkVersion(c);
}
});
},
renderTemplate: function() {
this.render({into: 'admin/templates/admin'});
},
checkVersion: function(c) {
if( Discourse.SiteSettings.version_checks ) {
Discourse.VersionCheck.find().then(function(vc) {
c.set('versionCheck', vc);
c.set('versionCheckedAt', new Date());
c.set('loading', false);
});
}
}
});
}).call(this);

View file

@ -1,21 +1,19 @@
(function() {
/**
Handles routes related to viewing email logs.
/**
Handles routes related to viewing email logs.
@class AdminEmailLogsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminEmailLogsRoute = Discourse.Route.extend({
model: function() {
return Discourse.EmailLog.findAll();
},
@class AdminEmailLogsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminEmailLogsRoute = Discourse.Route.extend({
model: function() {
return Discourse.EmailLog.findAll();
},
renderTemplate: function() {
this.render('admin/templates/email_logs');
}
});
renderTemplate: function() {
this.render('admin/templates/email_logs');
}
});
}).call(this);

View file

@ -1,25 +1,23 @@
(function() {
/**
Handles routes related to viewing active flags.
/**
Handles routes related to viewing active flags.
@class AdminFlagsActiveRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminFlagsActiveRoute = Discourse.Route.extend({
@class AdminFlagsActiveRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminFlagsActiveRoute = Discourse.Route.extend({
model: function() {
return Discourse.FlaggedPost.findAll('active');
},
model: function() {
return Discourse.FlaggedPost.findAll('active');
},
setupController: function(controller, model) {
var adminFlagsController = this.controllerFor('adminFlags');
adminFlagsController.set('content', model);
adminFlagsController.set('query', 'active');
}
setupController: function(controller, model) {
var adminFlagsController = this.controllerFor('adminFlags');
adminFlagsController.set('content', model);
adminFlagsController.set('query', 'active');
}
});
});
}).call(this);

View file

@ -1,25 +1,23 @@
(function() {
/**
Handles routes related to viewing old flags.
/**
Handles routes related to viewing old flags.
@class AdminFlagsOldRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminFlagsOldRoute = Discourse.Route.extend({
model: function() {
return Discourse.FlaggedPost.findAll('old');
},
@class AdminFlagsOldRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminFlagsOldRoute = Discourse.Route.extend({
model: function() {
return Discourse.FlaggedPost.findAll('old');
},
setupController: function(controller, model) {
var adminFlagsController = this.controllerFor('adminFlags');
adminFlagsController.set('content', model);
adminFlagsController.set('query', 'old');
}
setupController: function(controller, model) {
var adminFlagsController = this.controllerFor('adminFlags');
adminFlagsController.set('content', model);
adminFlagsController.set('query', 'old');
}
});
});
}).call(this);

View file

@ -1,17 +1,15 @@
(function() {
/**
Basic route for admin flags
/**
Basic route for admin flags
@class AdminFlagsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminFlagsRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('admin/templates/flags');
}
});
@class AdminFlagsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminFlagsRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('admin/templates/flags');
}
});
}).call(this);

View file

@ -1,17 +1,15 @@
(function() {
/**
The base admin route
/**
The base admin route
@class AdminRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('admin/templates/admin');
}
});
@class AdminRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('admin/templates/admin');
}
});
}).call(this);

View file

@ -1,31 +1,32 @@
(function() {
/**
Builds the routes for the admin section
/**
Declare all the routes used in the admin section.
**/
Discourse.buildRoutes(function() {
return this.resource('admin', { path: '/admin' }, function() {
this.route('dashboard', { path: '/' });
this.route('site_settings', { path: '/site_settings' });
this.route('email_logs', { path: '/email_logs' });
this.route('customize', { path: '/customize' });
@method buildRoutes
@for Discourse.AdminRoute
**/
Discourse.buildRoutes(function() {
this.resource('admin', { path: '/admin' }, function() {
this.resource('adminFlags', { path: '/flags' }, function() {
this.route('active', { path: '/active' });
this.route('old', { path: '/old' });
});
this.resource('adminUsers', { path: '/users' }, function() {
this.resource('adminUser', { path: '/:username' });
this.resource('adminUsersList', { path: '/list' }, function() {
this.route('active', { path: '/active' });
this.route('new', { path: '/new' });
this.route('pending', { path: '/pending' });
});
});
this.route('dashboard', { path: '/' });
this.route('site_settings', { path: '/site_settings' });
this.route('email_logs', { path: '/email_logs' });
this.route('customize', { path: '/customize' });
this.resource('adminFlags', { path: '/flags' }, function() {
this.route('active', { path: '/active' });
this.route('old', { path: '/old' });
});
});
}).call(this);
this.resource('adminUsers', { path: '/users' }, function() {
this.resource('adminUser', { path: '/:username' });
this.resource('adminUsersList', { path: '/list' }, function() {
this.route('active', { path: '/active' });
this.route('new', { path: '/new' });
this.route('pending', { path: '/pending' });
});
});
});
});

View file

@ -1,21 +1,19 @@
(function() {
/**
Handles routes related to viewing and editing site settings.
/**
Handles routes related to viewing and editing site settings.
@class AdminSiteSettingsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminSiteSettingsRoute = Discourse.Route.extend({
model: function() {
return Discourse.SiteSetting.findAll();
},
@class AdminSiteSettingsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminSiteSettingsRoute = Discourse.Route.extend({
model: function() {
return Discourse.SiteSetting.findAll();
},
renderTemplate: function() {
this.render('admin/templates/site_settings', {into: 'admin/templates/admin'});
}
});
renderTemplate: function() {
this.render('admin/templates/site_settings', {into: 'admin/templates/admin'});
}
});
}).call(this);

View file

@ -1,22 +1,20 @@
(function() {
/**
Handles routes related to users in the admin section.
/**
Handles routes related to users in the admin section.
@class AdminUserRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUserRoute = Discourse.Route.extend({
model: function(params) {
return Discourse.AdminUser.find(params.username);
},
@class AdminUserRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUserRoute = Discourse.Route.extend({
model: function(params) {
return Discourse.AdminUser.find(params.username);
},
renderTemplate: function() {
this.render('admin/templates/user', {into: 'admin/templates/admin'});
}
renderTemplate: function() {
this.render('admin/templates/user', {into: 'admin/templates/admin'});
}
});
});
}).call(this);

View file

@ -1,17 +1,15 @@
(function() {
/**
Handles the route that lists active users.
/**
Handles the route that lists active users.
@class AdminUsersListActiveRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListActiveRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('active');
}
});
@class AdminUsersListActiveRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListActiveRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('active');
}
});
}).call(this);

View file

@ -1,17 +1,15 @@
(function() {
/**
Handles the route that lists new users.
/**
Handles the route that lists new users.
@class AdminUsersListNewRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListNewRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('new');
}
});
@class AdminUsersListNewRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListNewRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('new');
}
});
}).call(this);

View file

@ -1,17 +1,15 @@
(function() {
/**
Handles the route that lists pending users.
/**
Handles the route that lists pending users.
@class AdminUsersListNewRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListPendingRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('pending');
}
});
@class AdminUsersListNewRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListPendingRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('pending');
}
});
}).call(this);

View file

@ -1,17 +1,15 @@
(function() {
/**
Handles the route that deals with listing users
/**
Handles the route that deals with listing users
@class AdminUsersListRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('admin/templates/users_list', {into: 'admin/templates/admin'});
}
});
@class AdminUsersListRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('admin/templates/users_list', {into: 'admin/templates/admin'});
}
});
}).call(this);

View file

@ -1,66 +1,65 @@
/*global ace:true */
(function() {
/**
A view that wraps the ACE editor (http://ace.ajax.org/)
/**
A view that wraps the ACE editor (http://ace.ajax.org/)
@class AceEditorView
@extends Em.View
@namespace Discourse
@module Discourse
**/
Discourse.AceEditorView = Discourse.View.extend({
mode: 'css',
classNames: ['ace-wrapper'],
@class AceEditorView
@extends Em.View
@namespace Discourse
@module Discourse
**/
Discourse.AceEditorView = Discourse.View.extend({
mode: 'css',
classNames: ['ace-wrapper'],
contentChanged: (function() {
if (this.editor && !this.skipContentChangeEvent) {
return this.editor.getSession().setValue(this.get('content'));
}
}).observes('content'),
render: function(buffer) {
buffer.push("<div class='ace'>");
if (this.get('content')) {
buffer.push(Handlebars.Utils.escapeExpression(this.get('content')));
}
return buffer.push("</div>");
},
willDestroyElement: function() {
if (this.editor) {
this.editor.destroy();
this.editor = null;
}
},
didInsertElement: function() {
var initAce,
_this = this;
initAce = function() {
_this.editor = ace.edit(_this.$('.ace')[0]);
_this.editor.setTheme("ace/theme/chrome");
_this.editor.setShowPrintMargin(false);
_this.editor.getSession().setMode("ace/mode/" + (_this.get('mode')));
return _this.editor.on("change", function(e) {
/* amending stuff as you type seems a bit out of scope for now - can revisit after launch
changes = @get('changes')
unless changes
changes = []
@set('changes', changes)
changes.push e.data
*/
_this.skipContentChangeEvent = true;
_this.set('content', _this.editor.getSession().getValue());
_this.skipContentChangeEvent = false;
});
};
if (window.ace) {
return initAce();
} else {
return $LAB.script('http://d1n0x3qji82z53.cloudfront.net/src-min-noconflict/ace.js').wait(initAce);
}
contentChanged: (function() {
if (this.editor && !this.skipContentChangeEvent) {
return this.editor.getSession().setValue(this.get('content'));
}
});
}).observes('content'),
render: function(buffer) {
buffer.push("<div class='ace'>");
if (this.get('content')) {
buffer.push(Handlebars.Utils.escapeExpression(this.get('content')));
}
return buffer.push("</div>");
},
willDestroyElement: function() {
if (this.editor) {
this.editor.destroy();
this.editor = null;
}
},
didInsertElement: function() {
var initAce,
_this = this;
initAce = function() {
_this.editor = ace.edit(_this.$('.ace')[0]);
_this.editor.setTheme("ace/theme/chrome");
_this.editor.setShowPrintMargin(false);
_this.editor.getSession().setMode("ace/mode/" + (_this.get('mode')));
return _this.editor.on("change", function(e) {
/* amending stuff as you type seems a bit out of scope for now - can revisit after launch
changes = @get('changes')
unless changes
changes = []
@set('changes', changes)
changes.push e.data
*/
_this.skipContentChangeEvent = true;
_this.set('content', _this.editor.getSession().getValue());
_this.skipContentChangeEvent = false;
});
};
if (window.ace) {
return initAce();
} else {
return $LAB.script('http://d1n0x3qji82z53.cloudfront.net/src-min-noconflict/ace.js').wait(initAce);
}
}
});
}).call(this);

View file

@ -1,51 +1,50 @@
/*global Mousetrap:true */
(function() {
/**
A view to handle site customizations
/**
A view to handle site customizations
@class AdminCustomizeView
@extends Em.View
@namespace Discourse
@module Discourse
**/
Discourse.AdminCustomizeView = Discourse.View.extend({
templateName: 'admin/templates/customize',
classNames: ['customize'],
@class AdminCustomizeView
@extends Em.View
@namespace Discourse
@module Discourse
**/
Discourse.AdminCustomizeView = Discourse.View.extend({
templateName: 'admin/templates/customize',
classNames: ['customize'],
init: function() {
this._super();
this.set('selected', 'stylesheet');
},
init: function() {
this._super();
this.set('selected', 'stylesheet');
},
headerActive: (function() {
return this.get('selected') === 'header';
}).property('selected'),
headerActive: (function() {
return this.get('selected') === 'header';
}).property('selected'),
stylesheetActive: (function() {
return this.get('selected') === 'stylesheet';
}).property('selected'),
stylesheetActive: (function() {
return this.get('selected') === 'stylesheet';
}).property('selected'),
selectHeader: function() {
this.set('selected', 'header');
},
selectHeader: function() {
this.set('selected', 'header');
},
selectStylesheet: function() {
this.set('selected', 'stylesheet');
},
selectStylesheet: function() {
this.set('selected', 'stylesheet');
},
didInsertElement: function() {
var _this = this;
return Mousetrap.bindGlobal(['meta+s', 'ctrl+s'], function() {
_this.get('controller').save();
return false;
});
},
didInsertElement: function() {
var _this = this;
return Mousetrap.bindGlobal(['meta+s', 'ctrl+s'], function() {
_this.get('controller').save();
return false;
});
},
willDestroyElement: function() {
return Mousetrap.unbindGlobal('meta+s', 'ctrl+s');
}
});
willDestroyElement: function() {
return Mousetrap.unbindGlobal('meta+s', 'ctrl+s');
}
});
}).call(this);

View file

@ -1,33 +1,31 @@
(function() {
/**
The default view in the admin section
/**
The default view in the admin section
@class AdminDashboardView
@extends Em.View
@namespace Discourse
@module Discourse
**/
Discourse.AdminDashboardView = Discourse.View.extend({
templateName: 'admin/templates/dashboard',
@class AdminDashboardView
@extends Em.View
@namespace Discourse
@module Discourse
**/
Discourse.AdminDashboardView = Discourse.View.extend({
templateName: 'admin/templates/dashboard',
updateIconClasses: function() {
var classes;
classes = "icon icon-warning-sign ";
if (this.get('controller.versionCheck.critical_updates')) {
classes += "critical-updates-available";
} else {
classes += "updates-available";
}
return classes;
}.property('controller.versionCheck.critical_updates'),
updateIconClasses: function() {
var classes;
classes = "icon icon-warning-sign ";
if (this.get('controller.versionCheck.critical_updates')) {
classes += "critical-updates-available";
} else {
classes += "updates-available";
}
return classes;
}.property('controller.versionCheck.critical_updates'),
priorityClass: function() {
if (this.get('controller.versionCheck.critical_updates')) {
return 'version-check critical';
}
return 'version-check normal';
}.property('controller.versionCheck.critical_updates')
});
priorityClass: function() {
if (this.get('controller.versionCheck.critical_updates')) {
return 'version-check critical';
}
return 'version-check normal';
}.property('controller.versionCheck.critical_updates')
});
}).call(this);

View file

@ -31,6 +31,7 @@
//= require ./discourse/views/view
//= require ./discourse/components/debounce
//= require ./discourse/controllers/controller
//= require ./discourse/controllers/object_controller
//= require ./discourse/views/modal/modal_body_view
//= require ./discourse/models/model
//= require ./discourse/routes/discourse_route

View file

@ -2,388 +2,387 @@
/*global assetPath:true*/
/*global FastClick:true*/
(function() {
var csrf_token;
var csrf_token;
window.Discourse = Ember.Application.createWithMixins({
rootElement: '#main',
Discourse = Ember.Application.createWithMixins({
rootElement: '#main',
// Data we want to remember for a short period
transient: Em.Object.create(),
// Data we want to remember for a short period
transient: Em.Object.create(),
hasFocus: true,
scrolling: false,
hasFocus: true,
scrolling: false,
// The highest seen post number by topic
highestSeenByTopic: {},
// The highest seen post number by topic
highestSeenByTopic: {},
logoSmall: (function() {
var logo;
logo = Discourse.SiteSettings.logo_small_url;
if (logo && logo.length > 1) {
return "<img src='" + logo + "' width='33' height='33'>";
} else {
return "<i class='icon-home'></i>";
logoSmall: (function() {
var logo;
logo = Discourse.SiteSettings.logo_small_url;
if (logo && logo.length > 1) {
return "<img src='" + logo + "' width='33' height='33'>";
} else {
return "<i class='icon-home'></i>";
}
}).property(),
titleChanged: (function() {
var title;
title = "";
if (this.get('title')) {
title += "" + (this.get('title')) + " - ";
}
title += Discourse.SiteSettings.title;
$('title').text(title);
if (!this.get('hasFocus') && this.get('notify')) {
title = "(*) " + title;
}
// chrome bug workaround see: http://stackoverflow.com/questions/2952384/changing-the-window-title-when-focussing-the-window-doesnt-work-in-chrome
window.setTimeout((function() {
document.title = ".";
document.title = title;
}), 200);
}).observes('title', 'hasFocus', 'notify'),
currentUserChanged: (function() {
var bus, user;
bus = Discourse.MessageBus;
// We don't want to receive any previous user notifications
bus.unsubscribe("/notification/*");
bus.callbackInterval = Discourse.SiteSettings.anon_polling_interval;
bus.enableLongPolling = false;
user = this.get('currentUser');
if (user) {
bus.callbackInterval = Discourse.SiteSettings.polling_interval;
bus.enableLongPolling = true;
if (user.admin) {
bus.subscribe("/flagged_counts", function(data) {
return user.set('site_flagged_posts_count', data.total);
});
}
}).property(),
return bus.subscribe("/notification/" + user.id, (function(data) {
user.set('unread_notifications', data.unread_notifications);
return user.set('unread_private_messages', data.unread_private_messages);
}), user.notification_channel_position);
}
}).observes('currentUser'),
notifyTitle: function() {
return this.set('notify', true);
},
titleChanged: (function() {
var title;
title = "";
if (this.get('title')) {
title += "" + (this.get('title')) + " - ";
// Browser aware replaceState
replaceState: function(path) {
if (window.history &&
window.history.pushState &&
window.history.replaceState &&
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)) {
if (window.location.pathname !== path) {
return history.replaceState({
path: path
}, null, path);
}
title += Discourse.SiteSettings.title;
jQuery('title').text(title);
if (!this.get('hasFocus') && this.get('notify')) {
title = "(*) " + title;
}
// chrome bug workaround see: http://stackoverflow.com/questions/2952384/changing-the-window-title-when-focussing-the-window-doesnt-work-in-chrome
window.setTimeout((function() {
document.title = ".";
document.title = title;
}), 200);
}).observes('title', 'hasFocus', 'notify'),
}
},
currentUserChanged: (function() {
var bus, user;
bus = Discourse.MessageBus;
openComposer: function(opts) {
// TODO, remove container link
var composer = Discourse.__container__.lookup('controller:composer');
if (composer) composer.open(opts);
},
// We don't want to receive any previous user notifications
bus.unsubscribe("/notification/*");
bus.callbackInterval = Discourse.SiteSettings.anon_polling_interval;
bus.enableLongPolling = false;
user = this.get('currentUser');
if (user) {
bus.callbackInterval = Discourse.SiteSettings.polling_interval;
bus.enableLongPolling = true;
if (user.admin) {
bus.subscribe("/flagged_counts", function(data) {
return user.set('site_flagged_posts_count', data.total);
});
}
return bus.subscribe("/notification/" + user.id, (function(data) {
user.set('unread_notifications', data.unread_notifications);
return user.set('unread_private_messages', data.unread_private_messages);
}), user.notification_channel_position);
}
}).observes('currentUser'),
notifyTitle: function() {
return this.set('notify', true);
},
// Like router.route, but allow full urls rather than relative one
// HERE BE HACKS - uses the ember container for now until we can do this nicer.
routeTo: function(path) {
var newMatches, newTopicId, oldMatches, oldTopicId, opts, router, topicController, topicRegexp;
path = path.replace(/https?\:\/\/[^\/]+/, '');
// Browser aware replaceState
replaceState: function(path) {
if (window.history &&
window.history.pushState &&
window.history.replaceState &&
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)) {
if (window.location.pathname !== path) {
return history.replaceState({
path: path
}, null, path);
// If we're in the same topic, don't push the state
topicRegexp = /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/;
newMatches = topicRegexp.exec(path);
newTopicId = newMatches ? newMatches[2] : null;
if (newTopicId) {
oldMatches = topicRegexp.exec(window.location.pathname);
if ((oldTopicId = oldMatches ? oldMatches[2] : void 0) && (oldTopicId === newTopicId)) {
Discourse.replaceState(path);
topicController = Discourse.__container__.lookup('controller:topic');
opts = {
trackVisit: false
};
if (newMatches[3]) {
opts.nearPost = newMatches[3];
}
}
},
openComposer: function(opts) {
// TODO, remove container link
var composer = Discourse.__container__.lookup('controller:composer');
if (composer) composer.open(opts);
},
// Like router.route, but allow full urls rather than relative one
// HERE BE HACKS - uses the ember container for now until we can do this nicer.
routeTo: function(path) {
var newMatches, newTopicId, oldMatches, oldTopicId, opts, router, topicController, topicRegexp;
path = path.replace(/https?\:\/\/[^\/]+/, '');
// If we're in the same topic, don't push the state
topicRegexp = /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/;
newMatches = topicRegexp.exec(path);
newTopicId = newMatches ? newMatches[2] : null;
if (newTopicId) {
oldMatches = topicRegexp.exec(window.location.pathname);
if ((oldTopicId = oldMatches ? oldMatches[2] : void 0) && (oldTopicId === newTopicId)) {
Discourse.replaceState(path);
topicController = Discourse.__container__.lookup('controller:topic');
opts = {
trackVisit: false
};
if (newMatches[3]) {
opts.nearPost = newMatches[3];
}
topicController.get('content').loadPosts(opts);
return;
}
}
// Be wary of looking up the router. In this case, we have links in our
// HTML, say form compiled markdown posts, that need to be routed.
router = Discourse.__container__.lookup('router:main');
router.router.updateURL(path);
return router.handleURL(path);
},
// The classes of buttons to show on a post
postButtons: (function() {
return Discourse.SiteSettings.post_menu.split("|").map(function(i) {
return "" + (i.replace(/\+/, '').capitalize());
});
}).property('Discourse.SiteSettings.post_menu'),
bindDOMEvents: function() {
var $html, hasTouch,
_this = this;
$html = jQuery('html');
/* Add the discourse touch event */
hasTouch = false;
if ($html.hasClass('touch')) {
hasTouch = true;
}
if (Modernizr.prefixed("MaxTouchPoints", navigator) > 1) {
hasTouch = true;
}
if (hasTouch) {
$html.addClass('discourse-touch');
this.touch = true;
this.hasTouch = true;
$LAB.script(assetPath('defer/fastclick'))
.wait(function(){
// work around jshint hating side-effects
// its just the way the FastClick api is
var ignore = new FastClick(document.body);
});
} else {
$html.addClass('discourse-no-touch');
this.touch = false;
}
jQuery('#main').on('click.discourse', '[data-not-implemented=true]', function(e) {
e.preventDefault();
alert(Em.String.i18n('not_implemented'));
return false;
});
jQuery('#main').on('click.discourse', 'a', function(e) {
var $currentTarget, href;
if (e.isDefaultPrevented() || e.metaKey || e.ctrlKey) {
return;
}
$currentTarget = jQuery(e.currentTarget);
href = $currentTarget.attr('href');
if (href === void 0) {
return;
}
if (href === '#') {
return;
}
if ($currentTarget.attr('target')) {
return;
}
if ($currentTarget.data('auto-route')) {
return;
}
if ($currentTarget.hasClass('lightbox')) {
return;
}
if (href.indexOf("mailto:") === 0) {
return;
}
if (href.match(/^http[s]?:\/\//i) && !href.match(new RegExp("^http:\\/\\/" + window.location.hostname, "i"))) {
return;
}
e.preventDefault();
_this.routeTo(href);
return false;
});
return jQuery(window).focus(function() {
_this.set('hasFocus', true);
return _this.set('notify', false);
}).blur(function() {
return _this.set('hasFocus', false);
});
},
logout: function() {
var username,
_this = this;
username = this.get('currentUser.username');
Discourse.KeyValueStore.abandonLocal();
return jQuery.ajax("/session/" + username, {
type: 'DELETE',
success: function(result) {
/* To keep lots of our variables unbound, we can handle a redirect on logging out.
*/
return window.location.reload();
}
});
},
/* fancy probes in ember
*/
insertProbes: function() {
var topLevel;
if (typeof console === "undefined" || console === null) {
topicController.get('content').loadPosts(opts);
return;
}
topLevel = function(fn, name) {
return window.probes.measure(fn, {
name: name,
before: function(data, owner, args) {
if (owner) {
return window.probes.clear();
}
},
after: function(data, owner, args) {
var ary, f, n, v, _ref;
if (owner && data.time > 10) {
f = function(name, data) {
if (data && data.count) {
return "" + name + " - " + data.count + " calls " + ((data.time + 0.0).toFixed(2)) + "ms";
}
};
if (console && console.group) {
console.group(f(name, data));
} else {
console.log("");
console.log(f(name, data));
}
// Be wary of looking up the router. In this case, we have links in our
// HTML, say form compiled markdown posts, that need to be routed.
router = Discourse.__container__.lookup('router:main');
router.router.updateURL(path);
return router.handleURL(path);
},
// The classes of buttons to show on a post
postButtons: (function() {
return Discourse.SiteSettings.post_menu.split("|").map(function(i) {
return "" + (i.replace(/\+/, '').capitalize());
});
}).property('Discourse.SiteSettings.post_menu'),
bindDOMEvents: function() {
var $html, hasTouch,
_this = this;
$html = $('html');
/* Add the discourse touch event */
hasTouch = false;
if ($html.hasClass('touch')) {
hasTouch = true;
}
if (Modernizr.prefixed("MaxTouchPoints", navigator) > 1) {
hasTouch = true;
}
if (hasTouch) {
$html.addClass('discourse-touch');
this.touch = true;
this.hasTouch = true;
$LAB.script(assetPath('defer/fastclick'))
.wait(function(){
// work around jshint hating side-effects
// its just the way the FastClick api is
var ignore = new FastClick(document.body);
});
} else {
$html.addClass('discourse-no-touch');
this.touch = false;
}
$('#main').on('click.discourse', '[data-not-implemented=true]', function(e) {
e.preventDefault();
alert(Em.String.i18n('not_implemented'));
return false;
});
$('#main').on('click.discourse', 'a', function(e) {
var $currentTarget, href;
if (e.isDefaultPrevented() || e.metaKey || e.ctrlKey) {
return;
}
$currentTarget = $(e.currentTarget);
href = $currentTarget.attr('href');
if (href === void 0) {
return;
}
if (href === '#') {
return;
}
if ($currentTarget.attr('target')) {
return;
}
if ($currentTarget.data('auto-route')) {
return;
}
if ($currentTarget.hasClass('lightbox')) {
return;
}
if (href.indexOf("mailto:") === 0) {
return;
}
if (href.match(/^http[s]?:\/\//i) && !href.match(new RegExp("^http:\\/\\/" + window.location.hostname, "i"))) {
return;
}
e.preventDefault();
_this.routeTo(href);
return false;
});
return $(window).focus(function() {
_this.set('hasFocus', true);
return _this.set('notify', false);
}).blur(function() {
return _this.set('hasFocus', false);
});
},
logout: function() {
var username,
_this = this;
username = this.get('currentUser.username');
Discourse.KeyValueStore.abandonLocal();
return jQuery.ajax("/session/" + username, {
type: 'DELETE',
success: function(result) {
/* To keep lots of our variables unbound, we can handle a redirect on logging out.
*/
return window.location.reload();
}
});
},
/* fancy probes in ember
*/
insertProbes: function() {
var topLevel;
if (typeof console === "undefined" || console === null) {
return;
}
topLevel = function(fn, name) {
return window.probes.measure(fn, {
name: name,
before: function(data, owner, args) {
if (owner) {
return window.probes.clear();
}
},
after: function(data, owner, args) {
var ary, f, n, v, _ref;
if (owner && data.time > 10) {
f = function(name, data) {
if (data && data.count) {
return "" + name + " - " + data.count + " calls " + ((data.time + 0.0).toFixed(2)) + "ms";
}
ary = [];
_ref = window.probes;
for (n in _ref) {
v = _ref[n];
if (n === name || v.time < 1) {
continue;
}
ary.push({
k: n,
v: v
};
if (console && console.group) {
console.group(f(name, data));
} else {
console.log("");
console.log(f(name, data));
}
ary = [];
_ref = window.probes;
for (n in _ref) {
v = _ref[n];
if (n === name || v.time < 1) {
continue;
}
ary.push({
k: n,
v: v
});
}
ary.sortBy(function(item) {
if (item.v && item.v.time) {
return -item.v.time;
} else {
return 0;
}
}).each(function(item) {
var output = f("" + item.k, item.v);
if (output) {
return console.log(output);
}
});
if (typeof console !== "undefined" && console !== null) {
if (typeof console.groupEnd === "function") {
console.groupEnd();
}
}
return window.probes.clear();
}
}
});
};
Ember.View.prototype.renderToBuffer = window.probes.measure(Ember.View.prototype.renderToBuffer, "renderToBuffer");
Discourse.routeTo = topLevel(Discourse.routeTo, "Discourse.routeTo");
Ember.run.end = topLevel(Ember.run.end, "Ember.run.end");
},
authenticationComplete: function(options) {
// TODO, how to dispatch this to the view without the container?
var loginView;
loginView = Discourse.__container__.lookup('controller:modal').get('currentView');
return loginView.authenticationComplete(options);
},
buildRoutes: function(builder) {
var oldBuilder;
oldBuilder = Discourse.routeBuilder;
Discourse.routeBuilder = function() {
if (oldBuilder) {
oldBuilder.call(this);
}
return builder.call(this);
};
},
start: function() {
this.bindDOMEvents();
Discourse.SiteSettings = PreloadStore.getStatic('siteSettings');
Discourse.MessageBus.start();
Discourse.KeyValueStore.init("discourse_", Discourse.MessageBus);
Discourse.insertProbes();
// subscribe to any site customizations that are loaded
$('link.custom-css').each(function() {
var id, split, stylesheet,
_this = this;
split = this.href.split("/");
id = split[split.length - 1].split(".css")[0];
stylesheet = this;
return Discourse.MessageBus.subscribe("/file-change/" + id, function(data) {
var orig, sp;
if (!$(stylesheet).data('orig')) {
$(stylesheet).data('orig', stylesheet.href);
}
orig = $(stylesheet).data('orig');
sp = orig.split(".css?");
stylesheet.href = sp[0] + ".css?" + data;
});
});
$('header.custom').each(function() {
var header;
header = $(this);
return Discourse.MessageBus.subscribe("/header-change/" + ($(this).data('key')), function(data) {
return header.html(data);
});
});
// possibly move this to dev only
return Discourse.MessageBus.subscribe("/file-change", function(data) {
Ember.TEMPLATES.empty = Handlebars.compile("<div></div>");
return data.each(function(me) {
var js;
if (me === "refresh") {
return document.location.reload(true);
} else if (me.name.substr(-10) === "handlebars") {
js = me.name.replace(".handlebars", "").replace("app/assets/javascripts", "/assets");
return $LAB.script(js + "?hash=" + me.hash).wait(function() {
var templateName;
templateName = js.replace(".js", "").replace("/assets/", "");
return jQuery.each(Ember.View.views, function() {
var _this = this;
if (this.get('templateName') === templateName) {
this.set('templateName', 'empty');
this.rerender();
return Em.run.next(function() {
_this.set('templateName', templateName);
return _this.rerender();
});
}
ary.sortBy(function(item) {
if (item.v && item.v.time) {
return -item.v.time;
} else {
return 0;
}
}).each(function(item) {
var output = f("" + item.k, item.v);
if (output) {
return console.log(output);
}
});
if (typeof console !== "undefined" && console !== null) {
if (typeof console.groupEnd === "function") {
console.groupEnd();
}
});
});
} else {
return $('link').each(function() {
if (this.href.match(me.name) && me.hash) {
if (!$(this).data('orig')) {
$(this).data('orig', this.href);
}
return window.probes.clear();
this.href = $(this).data('orig') + "&hash=" + me.hash;
}
}
});
};
Ember.View.prototype.renderToBuffer = window.probes.measure(Ember.View.prototype.renderToBuffer, "renderToBuffer");
Discourse.routeTo = topLevel(Discourse.routeTo, "Discourse.routeTo");
Ember.run.end = topLevel(Ember.run.end, "Ember.run.end");
},
authenticationComplete: function(options) {
// TODO, how to dispatch this to the view without the container?
var loginView;
loginView = Discourse.__container__.lookup('controller:modal').get('currentView');
return loginView.authenticationComplete(options);
},
buildRoutes: function(builder) {
var oldBuilder;
oldBuilder = Discourse.routeBuilder;
Discourse.routeBuilder = function() {
if (oldBuilder) {
oldBuilder.call(this);
});
}
return builder.call(this);
};
},
start: function() {
this.bindDOMEvents();
Discourse.SiteSettings = PreloadStore.getStatic('siteSettings');
Discourse.MessageBus.start();
Discourse.KeyValueStore.init("discourse_", Discourse.MessageBus);
Discourse.insertProbes();
// subscribe to any site customizations that are loaded
jQuery('link.custom-css').each(function() {
var id, split, stylesheet,
_this = this;
split = this.href.split("/");
id = split[split.length - 1].split(".css")[0];
stylesheet = this;
return Discourse.MessageBus.subscribe("/file-change/" + id, function(data) {
var orig, sp;
if (!jQuery(stylesheet).data('orig')) {
jQuery(stylesheet).data('orig', stylesheet.href);
}
orig = jQuery(stylesheet).data('orig');
sp = orig.split(".css?");
stylesheet.href = sp[0] + ".css?" + data;
});
});
jQuery('header.custom').each(function() {
var header;
header = jQuery(this);
return Discourse.MessageBus.subscribe("/header-change/" + (jQuery(this).data('key')), function(data) {
return header.html(data);
});
});
});
}
});
// possibly move this to dev only
return Discourse.MessageBus.subscribe("/file-change", function(data) {
Ember.TEMPLATES.empty = Handlebars.compile("<div></div>");
return data.each(function(me) {
var js;
if (me === "refresh") {
return document.location.reload(true);
} else if (me.name.substr(-10) === "handlebars") {
js = me.name.replace(".handlebars", "").replace("app/assets/javascripts", "/assets");
return $LAB.script(js + "?hash=" + me.hash).wait(function() {
var templateName;
templateName = js.replace(".js", "").replace("/assets/", "");
return jQuery.each(Ember.View.views, function() {
var _this = this;
if (this.get('templateName') === templateName) {
this.set('templateName', 'empty');
this.rerender();
return Em.run.next(function() {
_this.set('templateName', templateName);
return _this.rerender();
});
}
});
});
} else {
return jQuery('link').each(function() {
if (this.href.match(me.name) && me.hash) {
if (!jQuery(this).data('orig')) {
jQuery(this).data('orig', this.href);
}
this.href = jQuery(this).data('orig') + "&hash=" + me.hash;
}
});
}
});
});
}
});
Discourse.Router = Discourse.Router.reopen({
location: 'discourse_location'
});
window.Discourse.Router = Discourse.Router.reopen({
location: 'discourse_location'
});
// since we have no jquery-rails these days, hook up csrf token
csrf_token = $('meta[name=csrf-token]').attr('content');
// since we have no jquery-rails these days, hook up csrf token
csrf_token = jQuery('meta[name=csrf-token]').attr('content');
jQuery.ajaxPrefilter(function(options, originalOptions, xhr) {
if (!options.crossDomain) {
xhr.setRequestHeader('X-CSRF-Token', csrf_token);
}
});
jQuery.ajaxPrefilter(function(options, originalOptions, xhr) {
if (!options.crossDomain) {
xhr.setRequestHeader('X-CSRF-Token', csrf_token);
}
});
}).call(this);

View file

@ -1,313 +1,317 @@
(function() {
/**
This is a jQuery plugin to support autocompleting values in our text fields.
(function($) {
var template;
template = null;
$.fn.autocomplete = function(options) {
var addInputSelectedItem, autocompleteOptions, closeAutocomplete, completeEnd, completeStart, completeTerm, div, height;
var inputSelectedItems, isInput, markSelected, me, oldClose, renderAutocomplete, selectedOption, updateAutoComplete, vals;
var width, wrap, _this = this;
if (this.length === 0) {
return;
}
if (options && options.cancel && this.data("closeAutocomplete")) {
this.data("closeAutocomplete")();
return this;
}
if (this.length !== 1) {
alert("only supporting one matcher at the moment");
}
autocompleteOptions = null;
selectedOption = null;
completeStart = null;
completeEnd = null;
me = this;
div = null;
/* input is handled differently
*/
@module $.fn.autocomplete
**/
$.fn.autocomplete = function(options) {
isInput = this[0].tagName === "INPUT";
inputSelectedItems = [];
addInputSelectedItem = function(item) {
var d, prev, transformed;
if (options.transformComplete) {
transformed = options.transformComplete(item);
var addInputSelectedItem, autocompleteOptions, closeAutocomplete, completeEnd, completeStart, completeTerm, div, height;
var inputSelectedItems, isInput, markSelected, me, oldClose, renderAutocomplete, selectedOption, updateAutoComplete, vals;
var width, wrap, _this = this;
if (this.length === 0) return;
if (options && options.cancel && this.data("closeAutocomplete")) {
this.data("closeAutocomplete")();
return this;
}
if (this.length !== 1) {
alert("only supporting one matcher at the moment");
}
autocompleteOptions = null;
selectedOption = null;
completeStart = null;
completeEnd = null;
me = this;
div = null;
// input is handled differently
isInput = this[0].tagName === "INPUT";
inputSelectedItems = [];
addInputSelectedItem = function(item) {
var d, prev, transformed;
if (options.transformComplete) {
transformed = options.transformComplete(item);
}
d = $("<div class='item'><span>" + (transformed || item) + "<a href='#'><i class='icon-remove'></i></a></span></div>");
prev = me.parent().find('.item:last');
if (prev.length === 0) {
me.parent().prepend(d);
} else {
prev.after(d);
}
inputSelectedItems.push(item);
if (options.onChangeItems) {
options.onChangeItems(inputSelectedItems);
}
return d.find('a').click(function() {
closeAutocomplete();
inputSelectedItems.splice(jQuery.inArray(item), 1);
$(this).parent().parent().remove();
if (options.onChangeItems) {
return options.onChangeItems(inputSelectedItems);
}
});
};
if (isInput) {
width = this.width();
height = this.height();
wrap = this.wrap("<div class='ac-wrap clearfix'/>").parent();
wrap.width(width);
this.width(80);
this.attr('name', this.attr('name') + "-renamed");
vals = this.val().split(",");
vals.each(function(x) {
if (x !== "") {
if (options.reverseTransform) {
x = options.reverseTransform(x);
}
d = jQuery("<div class='item'><span>" + (transformed || item) + "<a href='#'><i class='icon-remove'></i></a></span></div>");
prev = me.parent().find('.item:last');
if (prev.length === 0) {
me.parent().prepend(d);
} else {
prev.after(d);
}
inputSelectedItems.push(item);
if (options.onChangeItems) {
options.onChangeItems(inputSelectedItems);
}
return d.find('a').click(function() {
closeAutocomplete();
inputSelectedItems.splice(jQuery.inArray(item), 1);
jQuery(this).parent().parent().remove();
if (options.onChangeItems) {
return options.onChangeItems(inputSelectedItems);
}
});
return addInputSelectedItem(x);
}
});
this.val("");
completeStart = 0;
wrap.click(function() {
_this.focus();
return true;
});
}
markSelected = function() {
var links;
links = div.find('li a');
links.removeClass('selected');
return $(links[selectedOption]).addClass('selected');
};
renderAutocomplete = function() {
var borderTop, mePos, pos, ul;
if (div) {
div.hide().remove();
}
if (autocompleteOptions.length === 0) {
return;
}
div = $(options.template({
options: autocompleteOptions
}));
ul = div.find('ul');
selectedOption = 0;
markSelected();
ul.find('li').click(function() {
selectedOption = ul.find('li').index(this);
completeTerm(autocompleteOptions[selectedOption]);
return false;
});
pos = null;
if (isInput) {
pos = {
left: 0,
top: 0
};
} else {
pos = me.caretPosition({
pos: completeStart,
key: options.key
});
}
div.css({
left: "-1000px"
});
me.parent().append(div);
mePos = me.position();
borderTop = parseInt(me.css('border-top-width'), 10) || 0;
return div.css({
position: 'absolute',
top: (mePos.top + pos.top - div.height() + borderTop) + 'px',
left: (mePos.left + pos.left + 27) + 'px'
});
};
updateAutoComplete = function(r) {
if (completeStart === null) return;
autocompleteOptions = r;
if (!r || r.length === 0) {
return closeAutocomplete();
} else {
return renderAutocomplete();
}
};
closeAutocomplete = function() {
if (div) {
div.hide().remove();
}
div = null;
completeStart = null;
autocompleteOptions = null;
};
// chain to allow multiples
oldClose = me.data("closeAutocomplete");
me.data("closeAutocomplete", function() {
if (oldClose) {
oldClose();
}
return closeAutocomplete();
});
completeTerm = function(term) {
var text;
if (term) {
if (isInput) {
width = this.width();
height = this.height();
wrap = this.wrap("<div class='ac-wrap clearfix'/>").parent();
wrap.width(width);
this.width(80);
this.attr('name', this.attr('name') + "-renamed");
vals = this.val().split(",");
vals.each(function(x) {
if (x !== "") {
if (options.reverseTransform) {
x = options.reverseTransform(x);
}
return addInputSelectedItem(x);
}
});
this.val("");
completeStart = 0;
wrap.click(function() {
_this.focus();
return true;
});
me.val("");
addInputSelectedItem(term);
} else {
if (options.transformComplete) {
term = options.transformComplete(term);
}
text = me.val();
text = text.substring(0, completeStart) + (options.key || "") + term + ' ' + text.substring(completeEnd + 1, text.length);
me.val(text);
Discourse.Utilities.setCaretPosition(me[0], completeStart + 1 + term.length);
}
markSelected = function() {
var links;
links = div.find('li a');
links.removeClass('selected');
return jQuery(links[selectedOption]).addClass('selected');
};
renderAutocomplete = function() {
var borderTop, mePos, pos, ul;
if (div) {
div.hide().remove();
}
return closeAutocomplete();
};
$(this).keypress(function(e) {
var caretPosition, prevChar, term;
if (!options.key) {
return;
}
/* keep hunting backwards till you hit a
*/
if (e.which === options.key.charCodeAt(0)) {
caretPosition = Discourse.Utilities.caretPosition(me[0]);
prevChar = me.val().charAt(caretPosition - 1);
if (!prevChar || /\s/.test(prevChar)) {
completeStart = completeEnd = caretPosition;
term = "";
options.dataSource(term, updateAutoComplete);
}
}
});
return $(this).keydown(function(e) {
var c, caretPosition, i, initial, next, nextIsGood, prev, prevIsGood, stopFound, term, total, userToComplete;
if (!options.key) {
completeStart = 0;
}
if (e.which === 16) {
return;
}
if ((completeStart === null) && e.which === 8 && options.key) {
c = Discourse.Utilities.caretPosition(me[0]);
next = me[0].value[c];
nextIsGood = next === void 0 || /\s/.test(next);
c -= 1;
initial = c;
prevIsGood = true;
while (prevIsGood && c >= 0) {
c -= 1;
prev = me[0].value[c];
stopFound = prev === options.key;
if (stopFound) {
prev = me[0].value[c - 1];
if (!prev || /\s/.test(prev)) {
completeStart = c;
caretPosition = completeEnd = initial;
term = me[0].value.substring(c + 1, initial);
options.dataSource(term, updateAutoComplete);
return true;
}
}
if (autocompleteOptions.length === 0) {
return;
}
div = jQuery(options.template({
options: autocompleteOptions
}));
ul = div.find('ul');
selectedOption = 0;
markSelected();
ul.find('li').click(function() {
selectedOption = ul.find('li').index(this);
completeTerm(autocompleteOptions[selectedOption]);
return false;
});
pos = null;
if (isInput) {
pos = {
left: 0,
top: 0
};
} else {
pos = me.caretPosition({
pos: completeStart,
key: options.key
});
}
div.css({
left: "-1000px"
});
me.parent().append(div);
mePos = me.position();
borderTop = parseInt(me.css('border-top-width'), 10) || 0;
return div.css({
position: 'absolute',
top: (mePos.top + pos.top - div.height() + borderTop) + 'px',
left: (mePos.left + pos.left + 27) + 'px'
});
};
updateAutoComplete = function(r) {
if (completeStart === null) return;
autocompleteOptions = r;
if (!r || r.length === 0) {
return closeAutocomplete();
} else {
return renderAutocomplete();
}
};
closeAutocomplete = function() {
if (div) {
div.hide().remove();
}
div = null;
completeStart = null;
autocompleteOptions = null;
};
/* chain to allow multiples
prevIsGood = /[a-zA-Z\.]/.test(prev);
}
}
if (e.which === 27) {
if (completeStart !== null) {
closeAutocomplete();
return false;
}
return true;
}
if (completeStart !== null) {
caretPosition = Discourse.Utilities.caretPosition(me[0]);
/* If we've backspaced past the beginning, cancel unless no key
*/
oldClose = me.data("closeAutocomplete");
me.data("closeAutocomplete", function() {
if (oldClose) {
oldClose();
}
return closeAutocomplete();
});
completeTerm = function(term) {
var text;
if (term) {
if (isInput) {
me.val("");
addInputSelectedItem(term);
if (caretPosition <= completeStart && options.key) {
closeAutocomplete();
return false;
}
/* Keyboard codes! So 80's.
*/
switch (e.which) {
case 13:
case 39:
case 9:
if (!autocompleteOptions) {
return true;
}
if (selectedOption >= 0 && (userToComplete = autocompleteOptions[selectedOption])) {
completeTerm(userToComplete);
} else {
if (options.transformComplete) {
term = options.transformComplete(term);
}
text = me.val();
text = text.substring(0, completeStart) + (options.key || "") + term + ' ' + text.substring(completeEnd + 1, text.length);
me.val(text);
Discourse.Utilities.setCaretPosition(me[0], completeStart + 1 + term.length);
}
}
return closeAutocomplete();
};
jQuery(this).keypress(function(e) {
var caretPosition, prevChar, term;
if (!options.key) {
return;
}
/* keep hunting backwards till you hit a
*/
/* We're cancelling it, really.
*/
if (e.which === options.key.charCodeAt(0)) {
caretPosition = Discourse.Utilities.caretPosition(me[0]);
prevChar = me.val().charAt(caretPosition - 1);
if (!prevChar || /\s/.test(prevChar)) {
completeStart = completeEnd = caretPosition;
term = "";
options.dataSource(term, updateAutoComplete);
return true;
}
}
});
return jQuery(this).keydown(function(e) {
var c, caretPosition, i, initial, next, nextIsGood, prev, prevIsGood, stopFound, term, total, userToComplete;
if (!options.key) {
completeStart = 0;
}
if (e.which === 16) {
return;
}
if ((completeStart === null) && e.which === 8 && options.key) {
c = Discourse.Utilities.caretPosition(me[0]);
next = me[0].value[c];
nextIsGood = next === void 0 || /\s/.test(next);
c -= 1;
initial = c;
prevIsGood = true;
while (prevIsGood && c >= 0) {
c -= 1;
prev = me[0].value[c];
stopFound = prev === options.key;
if (stopFound) {
prev = me[0].value[c - 1];
if (!prev || /\s/.test(prev)) {
completeStart = c;
caretPosition = completeEnd = initial;
term = me[0].value.substring(c + 1, initial);
options.dataSource(term, updateAutoComplete);
return true;
closeAutocomplete();
return false;
case 38:
selectedOption = selectedOption - 1;
if (selectedOption < 0) {
selectedOption = 0;
}
markSelected();
return false;
case 40:
total = autocompleteOptions.length;
selectedOption = selectedOption + 1;
if (selectedOption >= total) {
selectedOption = total - 1;
}
if (selectedOption < 0) {
selectedOption = 0;
}
markSelected();
return false;
default:
/* otherwise they're typing - let's search for it!
*/
completeEnd = caretPosition;
if (e.which === 8) {
caretPosition--;
}
if (caretPosition < 0) {
closeAutocomplete();
if (isInput) {
i = wrap.find('a:last');
if (i) {
i.click();
}
}
prevIsGood = /[a-zA-Z\.]/.test(prev);
}
}
if (e.which === 27) {
if (completeStart !== null) {
closeAutocomplete();
return false;
}
term = me.val().substring(completeStart + (options.key ? 1 : 0), caretPosition);
if (e.which > 48 && e.which < 90) {
term += String.fromCharCode(e.which);
} else {
if (e.which !== 8) {
term += ",";
}
}
options.dataSource(term, updateAutoComplete);
return true;
}
if (completeStart !== null) {
caretPosition = Discourse.Utilities.caretPosition(me[0]);
/* If we've backspaced past the beginning, cancel unless no key
*/
if (caretPosition <= completeStart && options.key) {
closeAutocomplete();
return false;
}
/* Keyboard codes! So 80's.
*/
switch (e.which) {
case 13:
case 39:
case 9:
if (!autocompleteOptions) {
return true;
}
if (selectedOption >= 0 && (userToComplete = autocompleteOptions[selectedOption])) {
completeTerm(userToComplete);
} else {
/* We're cancelling it, really.
*/
return true;
}
closeAutocomplete();
return false;
case 38:
selectedOption = selectedOption - 1;
if (selectedOption < 0) {
selectedOption = 0;
}
markSelected();
return false;
case 40:
total = autocompleteOptions.length;
selectedOption = selectedOption + 1;
if (selectedOption >= total) {
selectedOption = total - 1;
}
if (selectedOption < 0) {
selectedOption = 0;
}
markSelected();
return false;
default:
/* otherwise they're typing - let's search for it!
*/
completeEnd = caretPosition;
if (e.which === 8) {
caretPosition--;
}
if (caretPosition < 0) {
closeAutocomplete();
if (isInput) {
i = wrap.find('a:last');
if (i) {
i.click();
}
}
return false;
}
term = me.val().substring(completeStart + (options.key ? 1 : 0), caretPosition);
if (e.which > 48 && e.which < 90) {
term += String.fromCharCode(e.which);
} else {
if (e.which !== 8) {
term += ",";
}
}
options.dataSource(term, updateAutoComplete);
return true;
}
}
});
};
return $.fn.autocomplete;
})(jQuery);
}).call(this);
}
}
});
};

View file

@ -1,222 +1,193 @@
/*global HANDLEBARS_TEMPLATES:true*/
(function() {
/**
Support for BBCode rendering
Discourse.BBCode = {
QUOTE_REGEXP: /\[quote=([^\]]*)\]([\s\S]*?)\[\/quote\]/im,
/* Define our replacers
*/
@class BBCode
@namespace Discourse
@module Discourse
**/
Discourse.BBCode = {
replacers: {
base: {
withoutArgs: {
"ol": function(_, content) {
return "<ol>" + content + "</ol>";
},
"li": function(_, content) {
return "<li>" + content + "</li>";
},
"ul": function(_, content) {
return "<ul>" + content + "</ul>";
},
"code": function(_, content) {
return "<pre>" + content + "</pre>";
},
"url": function(_, url) {
return "<a href=\"" + url + "\">" + url + "</a>";
},
"email": function(_, address) {
return "<a href=\"mailto:" + address + "\">" + address + "</a>";
},
"img": function(_, src) {
return "<img src=\"" + src + "\">";
}
},
withArgs: {
"url": function(_, href, title) {
return "<a href=\"" + href + "\">" + title + "</a>";
},
"email": function(_, address, title) {
return "<a href=\"mailto:" + address + "\">" + title + "</a>";
},
"color": function(_, color, content) {
if (!/^(\#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)|(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow)$/.test(color)) {
return content;
}
return "<span style=\"color: " + color + "\">" + content + "</span>";
QUOTE_REGEXP: /\[quote=([^\]]*)\]([\s\S]*?)\[\/quote\]/im,
// Define our replacers
replacers: {
base: {
withoutArgs: {
"ol": function(_, content) { return "<ol>" + content + "</ol>"; },
"li": function(_, content) { return "<li>" + content + "</li>"; },
"ul": function(_, content) { return "<ul>" + content + "</ul>"; },
"code": function(_, content) { return "<pre>" + content + "</pre>"; },
"url": function(_, url) { return "<a href=\"" + url + "\">" + url + "</a>"; },
"email": function(_, address) { return "<a href=\"mailto:" + address + "\">" + address + "</a>"; },
"img": function(_, src) { return "<img src=\"" + src + "\">"; }
},
withArgs: {
"url": function(_, href, title) { return "<a href=\"" + href + "\">" + title + "</a>"; },
"email": function(_, address, title) { return "<a href=\"mailto:" + address + "\">" + title + "</a>"; },
"color": function(_, color, content) {
if (!/^(\#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)|(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow)$/.test(color)) {
return content;
}
return "<span style=\"color: " + color + "\">" + content + "</span>";
}
}
},
// For HTML emails
email: {
withoutArgs: {
"b": function(_, content) { return "<b>" + content + "</b>"; },
"i": function(_, content) { return "<i>" + content + "</i>"; },
"u": function(_, content) { return "<u>" + content + "</u>"; },
"s": function(_, content) { return "<s>" + content + "</s>"; },
"spoiler": function(_, content) { return "<span style='background-color: #000'>" + content + "</span>"; }
},
withArgs: {
"size": function(_, size, content) {
return "<span style=\"font-size: " + size + "px\">" + content + "</span>";
}
}
},
// For sane environments that support CSS
"default": {
withoutArgs: {
"b": function(_, content) { return "<span class='bbcode-b'>" + content + "</span>"; },
"i": function(_, content) { return "<span class='bbcode-i'>" + content + "</span>"; },
"u": function(_, content) { return "<span class='bbcode-u'>" + content + "</span>"; },
"s": function(_, content) { return "<span class='bbcode-s'>" + content + "</span>"; },
"spoiler": function(_, content) { return "<span class=\"spoiler\">" + content + "</span>";
}
},
/* For HTML emails
*/
email: {
withoutArgs: {
"b": function(_, content) {
return "<b>" + content + "</b>";
},
"i": function(_, content) {
return "<i>" + content + "</i>";
},
"u": function(_, content) {
return "<u>" + content + "</u>";
},
"s": function(_, content) {
return "<s>" + content + "</s>";
},
"spoiler": function(_, content) {
return "<span style='background-color: #000'>" + content + "</span>";
}
},
withArgs: {
"size": function(_, size, content) {
return "<span style=\"font-size: " + size + "px\">" + content + "</span>";
}
}
},
/* For sane environments that support CSS
*/
"default": {
withoutArgs: {
"b": function(_, content) {
return "<span class='bbcode-b'>" + content + "</span>";
},
"i": function(_, content) {
return "<span class='bbcode-i'>" + content + "</span>";
},
"u": function(_, content) {
return "<span class='bbcode-u'>" + content + "</span>";
},
"s": function(_, content) {
return "<span class='bbcode-s'>" + content + "</span>";
},
"spoiler": function(_, content) {
return "<span class=\"spoiler\">" + content + "</span>";
}
},
withArgs: {
"size": function(_, size, content) {
return "<span class=\"bbcode-size-" + size + "\">" + content + "</span>";
}
withArgs: {
"size": function(_, size, content) {
return "<span class=\"bbcode-size-" + size + "\">" + content + "</span>";
}
}
},
/* Apply a particular set of replacers */
apply: function(text, environment) {
var replacer;
replacer = Discourse.BBCode.parsedReplacers()[environment];
replacer.forEach(function(r) {
text = text.replace(r.regexp, r.fn);
});
return text;
},
parsedReplacers: function() {
var result;
if (this.parsed) {
return this.parsed;
}
result = {};
Object.keys(Discourse.BBCode.replacers, function(name, rules) {
var parsed;
parsed = result[name] = [];
Object.keys(Object.merge(Discourse.BBCode.replacers.base.withoutArgs, rules.withoutArgs), function(tag, val) {
return parsed.push({
regexp: new RegExp("\\[" + tag + "\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"),
fn: val
});
});
return Object.keys(Object.merge(Discourse.BBCode.replacers.base.withArgs, rules.withArgs), function(tag, val) {
return parsed.push({
regexp: new RegExp("\\[" + tag + "=?(.+?)\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"),
fn: val
});
});
});
this.parsed = result;
return this.parsed;
},
buildQuoteBBCode: function(post, contents) {
var contents_hashed, result, sansQuotes, stripped, stripped_hashed, tmp;
if (!contents) contents = "";
sansQuotes = contents.replace(this.QUOTE_REGEXP, '').trim();
if (sansQuotes.length === 0) return "";
/* Strip the HTML from cooked */
tmp = document.createElement('div');
tmp.innerHTML = post.get('cooked');
stripped = tmp.textContent || tmp.innerText;
/*
Let's remove any non alphanumeric characters as a kind of hash. Yes it's
not accurate but it should work almost every time we need it to. It would be unlikely
that the user would quote another post that matches in exactly this way.
*/
stripped_hashed = stripped.replace(/[^a-zA-Z0-9]/g, '');
contents_hashed = contents.replace(/[^a-zA-Z0-9]/g, '');
result = "[quote=\"" + (post.get('username')) + ", post:" + (post.get('post_number')) + ", topic:" + (post.get('topic_id'));
/* If the quote is the full message, attribute it as such */
if (stripped_hashed === contents_hashed) {
result += ", full:true";
}
result += "\"]\n" + sansQuotes + "\n[/quote]\n\n";
return result;
},
formatQuote: function(text, opts) {
/* Replace quotes with appropriate markup */
var args, matches, params, paramsSplit, paramsString, templateName, username;
while (matches = this.QUOTE_REGEXP.exec(text)) {
paramsString = matches[1];
paramsString = paramsString.replace(/\"/g, '');
paramsSplit = paramsString.split(/\, */);
params = [];
paramsSplit.each(function(p, i) {
var assignment;
if (i > 0) {
assignment = p.split(':');
if (assignment[0] && assignment[1]) {
return params.push({
key: assignment[0],
value: assignment[1].trim()
});
}
}
});
username = paramsSplit[0];
/* Arguments for formatting */
args = {
username: username,
params: params,
quote: matches[2].trim(),
avatarImg: opts.lookupAvatar ? opts.lookupAvatar(username) : void 0
};
templateName = 'quote';
if (opts && opts.environment) {
templateName = "quote_" + opts.environment;
}
text = text.replace(matches[0], "</p>" + HANDLEBARS_TEMPLATES[templateName](args) + "<p>");
}
return text;
},
format: function(text, opts) {
var environment;
if (opts && opts.environment) environment = opts.environment;
if (!environment) environment = 'default';
text = Discourse.BBCode.apply(text, environment);
// Add quotes
text = Discourse.BBCode.formatQuote(text, opts);
return text;
}
};
},
}).call(this);
// Apply a particular set of replacers
apply: function(text, environment) {
var replacer;
replacer = Discourse.BBCode.parsedReplacers()[environment];
replacer.forEach(function(r) {
text = text.replace(r.regexp, r.fn);
});
return text;
},
parsedReplacers: function() {
var result;
if (this.parsed) return this.parsed;
result = {};
Object.keys(Discourse.BBCode.replacers, function(name, rules) {
var parsed;
parsed = result[name] = [];
Object.keys(Object.merge(Discourse.BBCode.replacers.base.withoutArgs, rules.withoutArgs), function(tag, val) {
return parsed.push({
regexp: new RegExp("\\[" + tag + "\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"),
fn: val
});
});
return Object.keys(Object.merge(Discourse.BBCode.replacers.base.withArgs, rules.withArgs), function(tag, val) {
return parsed.push({
regexp: new RegExp("\\[" + tag + "=?(.+?)\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"),
fn: val
});
});
});
this.parsed = result;
return this.parsed;
},
buildQuoteBBCode: function(post, contents) {
var contents_hashed, result, sansQuotes, stripped, stripped_hashed, tmp;
if (!contents) contents = "";
sansQuotes = contents.replace(this.QUOTE_REGEXP, '').trim();
if (sansQuotes.length === 0) return "";
/* Strip the HTML from cooked */
tmp = document.createElement('div');
tmp.innerHTML = post.get('cooked');
stripped = tmp.textContent || tmp.innerText;
/*
Let's remove any non alphanumeric characters as a kind of hash. Yes it's
not accurate but it should work almost every time we need it to. It would be unlikely
that the user would quote another post that matches in exactly this way.
*/
stripped_hashed = stripped.replace(/[^a-zA-Z0-9]/g, '');
contents_hashed = contents.replace(/[^a-zA-Z0-9]/g, '');
result = "[quote=\"" + (post.get('username')) + ", post:" + (post.get('post_number')) + ", topic:" + (post.get('topic_id'));
/* If the quote is the full message, attribute it as such */
if (stripped_hashed === contents_hashed) {
result += ", full:true";
}
result += "\"]\n" + sansQuotes + "\n[/quote]\n\n";
return result;
},
formatQuote: function(text, opts) {
/* Replace quotes with appropriate markup */
var args, matches, params, paramsSplit, paramsString, templateName, username;
while (matches = this.QUOTE_REGEXP.exec(text)) {
paramsString = matches[1];
paramsString = paramsString.replace(/\"/g, '');
paramsSplit = paramsString.split(/\, */);
params = [];
paramsSplit.each(function(p, i) {
var assignment;
if (i > 0) {
assignment = p.split(':');
if (assignment[0] && assignment[1]) {
return params.push({
key: assignment[0],
value: assignment[1].trim()
});
}
}
});
username = paramsSplit[0];
/* Arguments for formatting */
args = {
username: username,
params: params,
quote: matches[2].trim(),
avatarImg: opts.lookupAvatar ? opts.lookupAvatar(username) : void 0
};
templateName = 'quote';
if (opts && opts.environment) {
templateName = "quote_" + opts.environment;
}
text = text.replace(matches[0], "</p>" + HANDLEBARS_TEMPLATES[templateName](args) + "<p>");
}
return text;
},
/**
Format a text string using BBCode
@method format
@param {String} text The text we want to format
@param {Object} opts Rendering options
**/
format: function(text, opts) {
var environment;
if (opts && opts.environment) environment = opts.environment;
if (!environment) environment = 'default';
text = Discourse.BBCode.apply(text, environment);
// Add quotes
text = Discourse.BBCode.formatQuote(text, opts);
return text;
}
};

View file

@ -1,135 +1,134 @@
// http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
var clone, getCaret;
getCaret = function(el) {
var r, rc, re;
if (el.selectionStart) {
return el.selectionStart;
} else if (document.selection) {
el.focus();
r = document.selection.createRange();
if (!r) return 0;
re = el.createTextRange();
rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint("EndToStart", re);
return rc.text.length;
}
return 0;
};
/* caret position in textarea ... very hacky ... sorry
*/
clone = null;
/**
This is a jQuery plugin to retrieve the caret position in a textarea
(function() {
@module $.fn.caretPosition
**/
$.fn.caretPosition = function(options) {
var after, before, getStyles, guard, html, important, insertSpaceAfterBefore, letter, makeCursor, p, pPos, pos, span, styles, textarea, val;
if (clone) {
clone.remove();
}
span = $("#pos span");
textarea = $(this);
(function($) {
/* http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
*/
getStyles = function(el, prop) {
if (el.currentStyle) {
return el.currentStyle;
} else {
return document.defaultView.getComputedStyle(el, "");
}
};
var clone, getCaret;
getCaret = function(el) {
var r, rc, re;
if (el.selectionStart) {
return el.selectionStart;
} else if (document.selection) {
el.focus();
r = document.selection.createRange();
if (!r) return 0;
re = el.createTextRange();
rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint("EndToStart", re);
return rc.text.length;
}
return 0;
};
clone = null;
$.fn.caretPosition = function(options) {
var after, before, getStyles, guard, html, important, insertSpaceAfterBefore, letter, makeCursor, p, pPos, pos, span, styles, textarea, val;
if (clone) {
clone.remove();
}
span = jQuery("#pos span");
textarea = jQuery(this);
getStyles = function(el, prop) {
if (el.currentStyle) {
return el.currentStyle;
} else {
return document.defaultView.getComputedStyle(el, "");
}
};
styles = getStyles(textarea[0]);
clone = jQuery("<div><p></p></div>").appendTo("body");
p = clone.find("p");
clone.width(textarea.width());
clone.height(textarea.height());
important = function(prop) {
return styles.getPropertyValue(prop);
};
clone.css({
border: "1px solid black",
padding: important("padding"),
resize: important("resize"),
"max-height": textarea.height() + "px",
"overflow-y": "auto",
"word-wrap": "break-word",
position: "absolute",
left: "-7000px"
});
p.css({
margin: 0,
padding: 0,
"word-wrap": "break-word",
"letter-spacing": important("letter-spacing"),
"font-family": important("font-family"),
"font-size": important("font-size"),
"line-height": important("line-height")
});
before = void 0;
after = void 0;
pos = options && (options.pos || options.pos === 0) ? options.pos : getCaret(textarea[0]);
val = textarea.val().replace("\r", "");
if (options && options.key) {
val = val.substring(0, pos) + options.key + val.substring(pos);
}
before = pos - 1;
after = pos;
insertSpaceAfterBefore = false;
/* if before and after are \n insert a space
*/
styles = getStyles(textarea[0]);
clone = $("<div><p></p></div>").appendTo("body");
p = clone.find("p");
clone.width(textarea.width());
clone.height(textarea.height());
if (val[before] === "\n" && val[after] === "\n") {
insertSpaceAfterBefore = true;
}
guard = function(v) {
var buf;
buf = v.replace(/</g, "&lt;");
buf = buf.replace(/>/g, "&gt;");
buf = buf.replace(/[ ]/g, "&#x200b;&nbsp;&#x200b;");
return buf.replace(/\n/g, "<br />");
};
makeCursor = function(pos, klass, color) {
var l;
l = val.substring(pos, pos + 1);
if (l === "\n") {
return "<br>";
}
return "<span class='" + klass + "' style='background-color:" + color + "; margin:0; padding: 0'>" + guard(l) + "</span>";
};
html = "";
if (before >= 0) {
html += guard(val.substring(0, pos - 1)) + makeCursor(before, "before", "#d0ffff");
if (insertSpaceAfterBefore) {
html += makeCursor(0, "post-before", "#d0ffff");
}
}
if (after >= 0) {
html += makeCursor(after, "after", "#ffd0ff");
if (after - 1 < val.length) {
html += guard(val.substring(after + 1));
}
}
p.html(html);
clone.scrollTop(textarea.scrollTop());
letter = p.find("span:first");
pos = letter.offset();
if (letter.hasClass("before")) {
pos.left = pos.left + letter.width();
}
pPos = p.offset();
return {
/*clone.hide().remove()
*/
important = function(prop) {
return styles.getPropertyValue(prop);
};
left: pos.left - pPos.left,
top: (pos.top - pPos.top) - clone.scrollTop()
};
};
return $.fn.caretPosition;
})(jQuery);
clone.css({
border: "1px solid black",
padding: important("padding"),
resize: important("resize"),
"max-height": textarea.height() + "px",
"overflow-y": "auto",
"word-wrap": "break-word",
position: "absolute",
left: "-7000px"
});
}).call(this);
p.css({
margin: 0,
padding: 0,
"word-wrap": "break-word",
"letter-spacing": important("letter-spacing"),
"font-family": important("font-family"),
"font-size": important("font-size"),
"line-height": important("line-height")
});
before = void 0;
after = void 0;
pos = options && (options.pos || options.pos === 0) ? options.pos : getCaret(textarea[0]);
val = textarea.val().replace("\r", "");
if (options && options.key) {
val = val.substring(0, pos) + options.key + val.substring(pos);
}
before = pos - 1;
after = pos;
insertSpaceAfterBefore = false;
// if before and after are \n insert a space
if (val[before] === "\n" && val[after] === "\n") {
insertSpaceAfterBefore = true;
}
guard = function(v) {
var buf;
buf = v.replace(/</g, "&lt;");
buf = buf.replace(/>/g, "&gt;");
buf = buf.replace(/[ ]/g, "&#x200b;&nbsp;&#x200b;");
return buf.replace(/\n/g, "<br />");
};
makeCursor = function(pos, klass, color) {
var l;
l = val.substring(pos, pos + 1);
if (l === "\n") return "<br>";
return "<span class='" + klass + "' style='background-color:" + color + "; margin:0; padding: 0'>" + guard(l) + "</span>";
};
html = "";
if (before >= 0) {
html += guard(val.substring(0, pos - 1)) + makeCursor(before, "before", "#d0ffff");
if (insertSpaceAfterBefore) {
html += makeCursor(0, "post-before", "#d0ffff");
}
}
if (after >= 0) {
html += makeCursor(after, "after", "#ffd0ff");
if (after - 1 < val.length) {
html += guard(val.substring(after + 1));
}
}
p.html(html);
clone.scrollTop(textarea.scrollTop());
letter = p.find("span:first");
pos = letter.offset();
if (letter.hasClass("before")) {
pos.left = pos.left + letter.width();
}
pPos = p.offset();
return {
left: pos.left - pPos.left,
top: (pos.top - pPos.top) - clone.scrollTop()
};
};

View file

@ -1,108 +1,101 @@
/**
Used for tracking when the user clicks on a link
/* We use this object to keep track of click counts.
*/
@class ClickTrack
@namespace Discourse
@module Discourse
**/
Discourse.ClickTrack = {
/**
Track a click on a link
(function() {
@method trackClick
@param {jQuery.Event} e The click event that occurred
**/
trackClick: function(e) {
var $a, $article, $badge, count, destination, href, ownLink, postId, topicId, trackingUrl, userId;
$a = $(e.currentTarget);
if ($a.hasClass('lightbox')) {
return;
}
e.preventDefault();
window.Discourse.ClickTrack = {
/* Pass the event of the click here and we'll do the magic!
*/
// We don't track clicks on quote back buttons
if ($a.hasClass('back') || $a.hasClass('quote-other-topic')) return true;
trackClick: function(e) {
var $a, $article, $badge, count, destination, href, ownLink, postId, topicId, trackingUrl, userId;
$a = jQuery(e.currentTarget);
if ($a.hasClass('lightbox')) {
return;
// Remove the href, put it as a data attribute
if (!$a.data('href')) {
$a.addClass('no-href');
$a.data('href', $a.attr('href'));
$a.attr('href', null);
// Don't route to this URL
$a.data('auto-route', true);
}
href = $a.data('href');
$article = $a.closest('article');
postId = $article.data('post-id');
topicId = $('#topic').data('topic-id');
userId = $a.data('user-id');
if (!userId) {
userId = $article.data('user-id');
}
ownLink = userId && (userId === Discourse.get('currentUser.id'));
// Build a Redirect URL
trackingUrl = "/clicks/track?url=" + encodeURIComponent(href);
if (postId && (!$a.data('ignore-post-id'))) {
trackingUrl += "&post_id=" + encodeURI(postId);
}
if (topicId) {
trackingUrl += "&topic_id=" + encodeURI(topicId);
}
// Update badge clicks unless it's our own
if (!ownLink) {
$badge = $('span.badge', $a);
if ($badge.length === 1) {
count = parseInt($badge.html(), 10);
$badge.html(count + 1);
}
e.preventDefault();
/* We don't track clicks on quote back buttons
*/
}
if ($a.hasClass('back') || $a.hasClass('quote-other-topic')) {
return true;
}
/* Remove the href, put it as a data attribute
*/
// If they right clicked, change the destination href
if (e.which === 3) {
destination = Discourse.SiteSettings.track_external_right_clicks ? trackingUrl : href;
$a.attr('href', destination);
return true;
}
if (!$a.data('href')) {
$a.addClass('no-href');
$a.data('href', $a.attr('href'));
$a.attr('href', null);
/* Don't route to this URL
*/
$a.data('auto-route', true);
}
href = $a.data('href');
$article = $a.closest('article');
postId = $article.data('post-id');
topicId = jQuery('#topic').data('topic-id');
userId = $a.data('user-id');
if (!userId) {
userId = $article.data('user-id');
}
ownLink = userId && (userId === Discourse.get('currentUser.id'));
/* Build a Redirect URL
*/
trackingUrl = "/clicks/track?url=" + encodeURIComponent(href);
if (postId && (!$a.data('ignore-post-id'))) {
trackingUrl += "&post_id=" + encodeURI(postId);
}
if (topicId) {
trackingUrl += "&topic_id=" + encodeURI(topicId);
}
/* Update badge clicks unless it's our own
*/
if (!ownLink) {
$badge = jQuery('span.badge', $a);
if ($badge.length === 1) {
count = parseInt($badge.html(), 10);
$badge.html(count + 1);
}
}
/* If they right clicked, change the destination href
*/
if (e.which === 3) {
destination = Discourse.SiteSettings.track_external_right_clicks ? trackingUrl : href;
$a.attr('href', destination);
return true;
}
/* if they want to open in a new tab, do an AJAX request
*/
if (e.metaKey || e.ctrlKey || e.which === 2) {
jQuery.get("/clicks/track", {
url: href,
post_id: postId,
topic_id: topicId,
redirect: false
});
window.open(href, '_blank');
return false;
}
/* If we're on the same site, use the router and track via AJAX
*/
if (href.indexOf(window.location.origin) === 0) {
jQuery.get("/clicks/track", {
url: href,
post_id: postId,
topic_id: topicId,
redirect: false
});
Discourse.routeTo(href);
return false;
}
/* Otherwise, use a custom URL with a redirect
*/
window.location = trackingUrl;
// if they want to open in a new tab, do an AJAX request
if (e.metaKey || e.ctrlKey || e.which === 2) {
jQuery.get("/clicks/track", {
url: href,
post_id: postId,
topic_id: topicId,
redirect: false
});
window.open(href, '_blank');
return false;
}
};
}).call(this);
// If we're on the same site, use the router and track via AJAX
if (href.indexOf(window.location.origin) === 0) {
jQuery.get("/clicks/track", {
url: href,
post_id: postId,
topic_id: topicId,
redirect: false
});
Discourse.routeTo(href);
return false;
}
// Otherwise, use a custom URL with a redirect
window.location = trackingUrl;
return false;
}
};

View file

@ -1,4 +1,14 @@
window.Discourse.debounce = function(func, wait, trickle) {
/**
Debounce a Javascript function. This means if it's called many times in a time limit it
should only be executed once.
@method debounce
@module Discourse
@param {function} func The function to debounce
@param {Numbers} wait how long to wait
@param {Boolean} trickle
**/
Discourse.debounce = function(func, wait, trickle) {
var timeout;
timeout = null;
@ -12,7 +22,7 @@ window.Discourse.debounce = function(func, wait, trickle) {
};
if (timeout && trickle) {
/* already queued, let it through */
// already queued, let it through
return;
}

View file

@ -1,10 +0,0 @@
(function() {
Discourse.TextField = Ember.TextField.extend({
attributeBindings: ['autocorrect', 'autocapitalize'],
placeholder: (function() {
return Em.String.i18n(this.get('placeholderKey'));
}).property('placeholderKey')
});
}).call(this);

View file

@ -1,92 +1,91 @@
/**
This is a jQuery plugin to support resizing text areas.
/*based off text area resizer by Ryan O'Dell : http://plugins.jquery.com/misc/textarea.js
*/
Originally based off text area resizer by Ryan O'Dell : http://plugins.jquery.com/misc/textarea.js
@module $.fn.DivResizer
**/
(function() {
var div, endDrag, grip, lastMousePos, min, mousePosition, originalDivHeight, originalPos, performDrag, startDrag, wrappedEndDrag, wrappedPerformDrag;
div = void 0;
originalPos = void 0;
originalDivHeight = void 0;
lastMousePos = 0;
min = 230;
grip = void 0;
wrappedEndDrag = void 0;
wrappedPerformDrag = void 0;
(function($) {
var div, endDrag, grip, lastMousePos, min, mousePosition, originalDivHeight, originalPos, performDrag, startDrag, wrappedEndDrag, wrappedPerformDrag;
div = void 0;
originalPos = void 0;
originalDivHeight = void 0;
lastMousePos = 0;
min = 230;
grip = void 0;
wrappedEndDrag = void 0;
wrappedPerformDrag = void 0;
startDrag = function(e, opts) {
div = jQuery(e.data.el);
div.addClass('clear-transitions');
div.blur();
lastMousePos = mousePosition(e).y;
originalPos = lastMousePos;
originalDivHeight = div.height();
wrappedPerformDrag = (function() {
return function(e) {
return performDrag(e, opts);
};
})();
wrappedEndDrag = (function() {
return function(e) {
return endDrag(e, opts);
};
})();
jQuery(document).mousemove(wrappedPerformDrag).mouseup(wrappedEndDrag);
return false;
startDrag = function(e, opts) {
div = $(e.data.el);
div.addClass('clear-transitions');
div.blur();
lastMousePos = mousePosition(e).y;
originalPos = lastMousePos;
originalDivHeight = div.height();
wrappedPerformDrag = (function() {
return function(e) {
return performDrag(e, opts);
};
performDrag = function(e, opts) {
var size, sizePx, thisMousePos;
thisMousePos = mousePosition(e).y;
size = originalDivHeight + (originalPos - thisMousePos);
lastMousePos = thisMousePos;
size = Math.min(size, jQuery(window).height());
size = Math.max(min, size);
sizePx = size + "px";
if (typeof opts.onDrag === "function") {
opts.onDrag(sizePx);
}
div.height(sizePx);
if (size < min) {
endDrag(e, opts);
}
return false;
})();
wrappedEndDrag = (function() {
return function(e) {
return endDrag(e, opts);
};
endDrag = function(e, opts) {
jQuery(document).unbind("mousemove", wrappedPerformDrag).unbind("mouseup", wrappedEndDrag);
div.removeClass('clear-transitions');
div.focus();
if (typeof opts.resize === "function") {
opts.resize();
}
div = null;
};
mousePosition = function(e) {
return {
x: e.clientX + document.documentElement.scrollLeft,
y: e.clientY + document.documentElement.scrollTop
})();
$(document).mousemove(wrappedPerformDrag).mouseup(wrappedEndDrag);
return false;
};
performDrag = function(e, opts) {
var size, sizePx, thisMousePos;
thisMousePos = mousePosition(e).y;
size = originalDivHeight + (originalPos - thisMousePos);
lastMousePos = thisMousePos;
size = Math.min(size, $(window).height());
size = Math.max(min, size);
sizePx = size + "px";
if (typeof opts.onDrag === "function") {
opts.onDrag(sizePx);
}
div.height(sizePx);
if (size < min) {
endDrag(e, opts);
}
return false;
};
endDrag = function(e, opts) {
$(document).unbind("mousemove", wrappedPerformDrag).unbind("mouseup", wrappedEndDrag);
div.removeClass('clear-transitions');
div.focus();
if (typeof opts.resize === "function") {
opts.resize();
}
div = null;
};
mousePosition = function(e) {
return {
x: e.clientX + document.documentElement.scrollLeft,
y: e.clientY + document.documentElement.scrollTop
};
};
$.fn.DivResizer = function(opts) {
return this.each(function() {
var grippie, start, staticOffset;
div = $(this);
if (div.hasClass("processed")) return;
div.addClass("processed");
staticOffset = null;
start = function() {
return function(e) {
return startDrag(e, opts);
};
};
$.fn.DivResizer = function(opts) {
return this.each(function() {
var grippie, start, staticOffset;
div = jQuery(this);
if (div.hasClass("processed")) {
return;
}
div.addClass("processed");
staticOffset = null;
start = function() {
return function(e) {
return startDrag(e, opts);
};
};
grippie = div.prepend("<div class='grippie'></div>").find('.grippie').bind("mousedown", {
el: this
}, start());
});
};
return $.fn.DivResizer;
})(jQuery);
}).call(this);
grippie = div.prepend("<div class='grippie'></div>").find('.grippie').bind("mousedown", {
el: this
}, start());
});
};

View file

@ -1,129 +1,97 @@
/**
Track visible elemnts on the screen.
/* Track visible elements on the screen
*/
You can register for triggers on:
`focusChanged` the top element we're focusing on
/* You can register for triggers on:
*/
`seenElement` if we've seen the element
@class Eyeline
@namespace Discourse
@module Discourse
@uses RSVP.EventTarget
**/
Discourse.Eyeline = function Eyeline(selector) {
this.selector = selector;
}
/* focusChanged: -> the top element we're focusing on
*/
/**
Call this whenever you want to consider what is being seen by the browser
@method update
**/
Discourse.Eyeline.prototype.update = function() {
var $elements, $results, atBottom, bottomOffset, docViewBottom, docViewTop, documentHeight, foundElement, windowHeight,
_this = this;
/* seenElement: -> if we've seen the element
*/
docViewTop = $(window).scrollTop();
windowHeight = $(window).height();
docViewBottom = docViewTop + windowHeight;
documentHeight = $(document).height();
$elements = $(this.selector);
atBottom = false;
if (bottomOffset = $elements.last().offset()) {
atBottom = (bottomOffset.top <= docViewBottom) && (bottomOffset.top >= docViewTop);
}
(function() {
// Whether we've seen any elements in this search
foundElement = false;
$results = $(this.selector);
return $results.each(function(i, elem) {
var $elem, elemBottom, elemTop, markSeen;
$elem = $(elem);
elemTop = $elem.offset().top;
elemBottom = elemTop + $elem.height();
markSeen = false;
// It's seen if...
Discourse.Eyeline = (function() {
// ...the element is vertically within the top and botom
if ((elemTop <= docViewBottom) && (elemTop >= docViewTop)) markSeen = true;
function Eyeline(selector) {
this.selector = selector;
}
// ...the element top is above the top and the bottom is below the bottom (large elements)
if ((elemTop <= docViewTop) && (elemBottom >= docViewBottom)) markSeen = true;
/* Call this whenever we want to consider what is currently being seen by the browser
*/
// ...we're at the bottom and the bottom of the element is visible (large bottom elements)
if (atBottom && (elemBottom >= docViewTop)) markSeen = true;
if (!markSeen) return true;
Eyeline.prototype.update = function() {
var $elements, $results, atBottom, bottomOffset, docViewBottom, docViewTop, documentHeight, foundElement, windowHeight,
_this = this;
docViewTop = jQuery(window).scrollTop();
windowHeight = jQuery(window).height();
docViewBottom = docViewTop + windowHeight;
documentHeight = jQuery(document).height();
$elements = jQuery(this.selector);
atBottom = false;
if (bottomOffset = $elements.last().offset()) {
atBottom = (bottomOffset.top <= docViewBottom) && (bottomOffset.top >= docViewTop);
// If you hit the bottom we mark all the elements as seen. Otherwise, just the first one
if (!atBottom) {
_this.trigger('saw', {
detail: $elem
});
if (i === 0) {
_this.trigger('sawTop', { detail: $elem });
}
/* Whether we've seen any elements in this search
*/
foundElement = false;
$results = jQuery(this.selector);
return $results.each(function(i, elem) {
var $elem, elemBottom, elemTop, markSeen;
$elem = jQuery(elem);
elemTop = $elem.offset().top;
elemBottom = elemTop + $elem.height();
markSeen = false;
/* It's seen if...
*/
/* ...the element is vertically within the top and botom
*/
if ((elemTop <= docViewBottom) && (elemTop >= docViewTop)) {
markSeen = true;
}
/* ...the element top is above the top and the bottom is below the bottom (large elements)
*/
if ((elemTop <= docViewTop) && (elemBottom >= docViewBottom)) {
markSeen = true;
}
/* ...we're at the bottom and the bottom of the element is visible (large bottom elements)
*/
if (atBottom && (elemBottom >= docViewTop)) {
markSeen = true;
}
if (!markSeen) {
return true;
}
/* If you hit the bottom we mark all the elements as seen. Otherwise, just the first one
*/
if (!atBottom) {
_this.trigger('saw', {
detail: $elem
});
if (i === 0) {
_this.trigger('sawTop', {
detail: $elem
});
}
return false;
}
if (i === 0) {
_this.trigger('sawTop', {
detail: $elem
});
}
if (i === ($results.length - 1)) {
return _this.trigger('sawBottom', {
detail: $elem
});
}
});
};
/* Call this when we know aren't loading any more elements. Mark the rest
*/
return false;
}
if (i === 0) {
_this.trigger('sawTop', { detail: $elem });
}
if (i === ($results.length - 1)) {
return _this.trigger('sawBottom', { detail: $elem });
}
});
};
/* as seen
*/
/**
Call this when we know aren't loading any more elements. Mark the rest as seen
@method flushRest
**/
Discourse.Eyeline.prototype.flushRest = function() {
var _this = this;
return $(this.selector).each(function(i, elem) {
var $elem;
$elem = $(elem);
return _this.trigger('saw', { detail: $elem });
});
};
RSVP.EventTarget.mixin(Discourse.Eyeline.prototype);
Eyeline.prototype.flushRest = function() {
var _this = this;
return jQuery(this.selector).each(function(i, elem) {
var $elem;
$elem = jQuery(elem);
return _this.trigger('saw', {
detail: $elem
});
});
};
return Eyeline;
})();
RSVP.EventTarget.mixin(Discourse.Eyeline.prototype);
}).call(this);

View file

@ -1,50 +1,51 @@
/**
A simple key value store that uses LocalStorage
/* key value store
*/
@class KeyValueStore
@namespace Discourse
@module Discourse
**/
Discourse.KeyValueStore = {
initialized: false,
context: "",
init: function(ctx, messageBus) {
initialized = true;
context = ctx;
},
(function() {
window.Discourse.KeyValueStore = (function() {
var context, initialized;
initialized = false;
context = "";
return {
init: function(ctx, messageBus) {
initialized = true;
context = ctx;
},
abandonLocal: function() {
var i, k;
if (!(localStorage && initialized)) {
return;
}
i = localStorage.length - 1;
while (i >= 0) {
k = localStorage.key(i);
if (k.substring(0, context.length) === context) {
localStorage.removeItem(k);
}
i--;
}
return true;
},
remove: function(key) {
return localStorage.removeItem(context + key);
},
set: function(opts) {
if (!(localStorage && initialized)) {
return false;
}
localStorage[context + opts.key] = opts.value;
},
get: function(key) {
if (!localStorage) {
return null;
}
return localStorage[context + key];
abandonLocal: function() {
var i, k;
if (!(localStorage && initialized)) {
return;
}
i = localStorage.length - 1;
while (i >= 0) {
k = localStorage.key(i);
if (k.substring(0, context.length) === context) {
localStorage.removeItem(k);
}
};
})();
i--;
}
return true;
},
remove: function(key) {
return localStorage.removeItem(context + key);
},
set: function(opts) {
if (!(localStorage && initialized)) {
return false;
}
localStorage[context + opts.key] = opts.value;
},
get: function(key) {
if (!localStorage) {
return null;
}
return localStorage[context + key];
}
}
}).call(this);

View file

@ -1,23 +1,19 @@
/**
Helper object for lightboxes.
/* Helper object for light boxes. Uses highlight.js which is loaded
*/
/* on demand.
*/
(function() {
window.Discourse.Lightbox = {
apply: function($elem) {
var _this = this;
return jQuery('a.lightbox', $elem).each(function(i, e) {
return $LAB.script("/javascripts/jquery.colorbox-min.js").wait(function() {
return jQuery(e).colorbox();
});
@class Lightbox
@namespace Discourse
@module Discourse
**/
Discourse.Lightbox = {
apply: function($elem) {
var _this = this;
return $('a.lightbox', $elem).each(function(i, e) {
return $LAB.script("/javascripts/jquery.colorbox-min.js").wait(function() {
return $(e).colorbox();
});
}
};
});
}
}
}).call(this);

View file

@ -0,0 +1,59 @@
/**
Helps us determine whether someone has been mentioned by looking up their username.
@class Mention
@namespace Discourse
@module Discourse
**/
Discourse.Mention = (function() {
var cache, load, localCache, lookup, lookupCache;
localCache = {};
cache = function(name, valid) {
localCache[name] = valid;
};
lookupCache = function(name) {
return localCache[name];
};
lookup = function(name, callback) {
var cached;
cached = lookupCache(name);
if (cached === true || cached === false) {
callback(cached);
return false;
} else {
jQuery.get("/users/is_local_username", {
username: name
}, function(r) {
cache(name, r.valid);
return callback(r.valid);
});
return true;
}
};
load = function(e) {
var $elem, loading, username;
$elem = $(e);
if ($elem.data('mention-tested')) {
return;
}
username = $elem.text();
username = username.substr(1);
loading = lookup(username, function(valid) {
if (valid) {
return $elem.replaceWith("<a href='/users/" + (username.toLowerCase()) + "' class='mention'>@" + username + "</a>");
} else {
return $elem.removeClass('mention-loading').addClass('mention-tested');
}
});
if (loading) {
return $elem.addClass('mention-loading');
}
};
return {
load: load,
lookup: lookup,
lookupCache: lookupCache
};
})();

View file

@ -1,159 +1,157 @@
/*jshint bitwise: false*/
(function() {
window.Discourse.MessageBus = (function() {
/* http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
*/
/**
Message Bus functionality.
var callbacks, clientId, failCount, interval, isHidden, queue, responseCallbacks, uniqueId;
uniqueId = function() {
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r, v;
r = Math.random() * 16 | 0;
v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
clientId = uniqueId();
responseCallbacks = {};
callbacks = [];
queue = [];
interval = null;
failCount = 0;
isHidden = function() {
if (document.hidden !== void 0) {
return document.hidden;
} else if (document.webkitHidden !== void 0) {
return document.webkitHidden;
} else if (document.msHidden !== void 0) {
return document.msHidden;
} else if (document.mozHidden !== void 0) {
return document.mozHidden;
} else {
/* fallback to problamatic window.focus
*/
@class MessageBus
@namespace Discourse
@module Discourse
**/
Discourse.MessageBus = (function() {
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
var callbacks, clientId, failCount, interval, isHidden, queue, responseCallbacks, uniqueId;
return !Discourse.get('hasFocus');
}
};
return {
enableLongPolling: true,
callbackInterval: 60000,
maxPollInterval: 3 * 60 * 1000,
callbacks: callbacks,
clientId: clientId,
/*TODO
*/
uniqueId = function() {
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r, v;
r = Math.random() * 16 | 0;
v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
stop: false,
/* Start polling
*/
clientId = uniqueId();
responseCallbacks = {};
callbacks = [];
queue = [];
interval = null;
failCount = 0;
start: function(opts) {
var poll,
_this = this;
if (!opts) opts = {};
poll = function() {
var data, gotData;
if (callbacks.length === 0) {
setTimeout(poll, 500);
return;
}
data = {};
callbacks.each(function(c) {
data[c.channel] = c.last_id === void 0 ? -1 : c.last_id;
});
gotData = false;
_this.longPoll = jQuery.ajax("/message-bus/" + clientId + "/poll?" + (isHidden() || !_this.enableLongPolling ? "dlp=t" : ""), {
data: data,
cache: false,
dataType: 'json',
type: 'POST',
headers: {
'X-SILENCE-LOGGER': 'true'
},
success: function(messages) {
failCount = 0;
return messages.each(function(message) {
gotData = true;
return callbacks.each(function(callback) {
if (callback.channel === message.channel) {
callback.last_id = message.message_id;
callback.func(message.data);
isHidden = function() {
if (document.hidden !== void 0) {
return document.hidden;
} else if (document.webkitHidden !== void 0) {
return document.webkitHidden;
} else if (document.msHidden !== void 0) {
return document.msHidden;
} else if (document.mozHidden !== void 0) {
return document.mozHidden;
} else {
// fallback to problamatic window.focus
return !Discourse.get('hasFocus');
}
};
return {
enableLongPolling: true,
callbackInterval: 60000,
maxPollInterval: 3 * 60 * 1000,
callbacks: callbacks,
clientId: clientId,
stop: false,
// Start polling
start: function(opts) {
var poll,
_this = this;
if (!opts) opts = {};
poll = function() {
var data, gotData;
if (callbacks.length === 0) {
setTimeout(poll, 500);
return;
}
data = {};
callbacks.each(function(c) {
data[c.channel] = c.last_id === void 0 ? -1 : c.last_id;
});
gotData = false;
_this.longPoll = jQuery.ajax("/message-bus/" + clientId + "/poll?" + (isHidden() || !_this.enableLongPolling ? "dlp=t" : ""), {
data: data,
cache: false,
dataType: 'json',
type: 'POST',
headers: {
'X-SILENCE-LOGGER': 'true'
},
success: function(messages) {
failCount = 0;
return messages.each(function(message) {
gotData = true;
return callbacks.each(function(callback) {
if (callback.channel === message.channel) {
callback.last_id = message.message_id;
callback.func(message.data);
}
if (message.channel === "/__status") {
if (message.data[callback.channel] !== void 0) {
callback.last_id = message.data[callback.channel];
}
if (message.channel === "/__status") {
if (message.data[callback.channel] !== void 0) {
callback.last_id = message.data[callback.channel];
}
}
});
}
});
},
error: failCount += 1,
complete: function() {
if (gotData) {
setTimeout(poll, 100);
} else {
interval = _this.callbackInterval;
if (failCount > 2) {
interval = interval * failCount;
} else if (isHidden()) {
/* slowning down stuff a lot when hidden
*/
});
},
error: failCount += 1,
complete: function() {
if (gotData) {
setTimeout(poll, 100);
} else {
interval = _this.callbackInterval;
if (failCount > 2) {
interval = interval * failCount;
} else if (isHidden()) {
/* slowning down stuff a lot when hidden
*/
/* we will need to add a lot of fine tuning here
*/
/* we will need to add a lot of fine tuning here
*/
interval = interval * 4;
}
if (interval > _this.maxPollInterval) {
interval = _this.maxPollInterval;
}
setTimeout(poll, interval);
interval = interval * 4;
}
_this.longPoll = null;
if (interval > _this.maxPollInterval) {
interval = _this.maxPollInterval;
}
setTimeout(poll, interval);
}
});
};
poll();
},
/* Subscribe to a channel
*/
subscribe: function(channel, func, lastId) {
callbacks.push({
channel: channel,
func: func,
last_id: lastId
});
if (this.longPoll) {
return this.longPoll.abort();
}
},
/* Unsubscribe from a channel
*/
unsubscribe: function(channel) {
/* TODO proper globbing
*/
var glob;
if (channel.endsWith("*")) {
channel = channel.substr(0, channel.length - 1);
glob = true;
}
callbacks = callbacks.filter(function(callback) {
if (glob) {
return callback.channel.substr(0, channel.length) !== channel;
} else {
return callback.channel !== channel;
_this.longPoll = null;
}
});
if (this.longPoll) {
return this.longPoll.abort();
}
}
};
})();
};
poll();
},
}).call(this);
// Subscribe to a channel
subscribe: function(channel, func, lastId) {
callbacks.push({
channel: channel,
func: func,
last_id: lastId
});
if (this.longPoll) {
return this.longPoll.abort();
}
},
// Unsubscribe from a channel
unsubscribe: function(channel) {
// TODO proper globbing
var glob;
if (channel.endsWith("*")) {
channel = channel.substr(0, channel.length - 1);
glob = true;
}
callbacks = callbacks.filter(function(callback) {
if (glob) {
return callback.channel.substr(0, channel.length) !== channel;
} else {
return callback.channel !== channel;
}
});
if (this.longPoll) {
return this.longPoll.abort();
}
}
};
})();

View file

@ -0,0 +1,89 @@
/**
A helper for looking up oneboxes and displaying them
For now it only stores in a var, in future we can change it so it uses localStorage.
@class Notification
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Onebox = (function() {
var cache, load, localCache, lookup, lookupCache;
localCache = {};
cache = function(url, contents) {
localCache[url] = contents;
return null;
};
lookupCache = function(url) {
var cached;
cached = localCache[url];
if (cached && cached.then) {
return null;
} else {
return cached;
}
};
lookup = function(url, refresh, callback) {
var cached;
cached = localCache[url];
if (refresh && cached && !cached.then) {
cached = null;
}
if (cached) {
if (cached.then) {
cached.then(callback(lookupCache(url)));
} else {
callback(cached);
}
return false;
} else {
cache(url, jQuery.get("/onebox", {
url: url,
refresh: refresh
}, function(html) {
cache(url, html);
return callback(html);
}));
return true;
}
};
load = function(e, refresh) {
var $elem, loading, url;
if (!refresh) refresh = false;
url = e.href;
$elem = $(e);
if ($elem.data('onebox-loaded')) {
return;
}
loading = lookup(url, refresh, function(html) {
$elem.removeClass('loading-onebox');
$elem.data('onebox-loaded');
if (!html) {
return;
}
if (html.trim().length === 0) {
return;
}
return $elem.replaceWith(html);
});
if (loading) {
return $elem.addClass('loading-onebox');
}
};
return {
load: load,
lookup: lookup,
lookupCache: lookupCache
};
})();

View file

@ -1,38 +0,0 @@
/*global Markdown:true*/
(function() {
window.Discourse.PagedownEditor = Ember.ContainerView.extend({
elementId: 'pagedown-editor',
init: function() {
this._super();
/* Add a button bar
*/
this.pushObject(Em.View.create({
elementId: 'wmd-button-bar'
}));
this.pushObject(Em.TextArea.create({
valueBinding: 'parentView.value',
elementId: 'wmd-input'
}));
return this.pushObject(Em.View.createWithMixins(Discourse.Presence, {
elementId: 'wmd-preview',
classNameBindings: [':preview', 'hidden'],
hidden: (function() {
return this.blank('parentView.value');
}).property('parentView.value')
}));
},
didInsertElement: function() {
var $wmdInput;
$wmdInput = jQuery('#wmd-input');
$wmdInput.data('init', true);
this.editor = new Markdown.Editor(Discourse.Utilities.markdownConverter({
sanitize: true
}));
return this.editor.run();
}
});
}).call(this);

View file

@ -5,27 +5,26 @@
*
* Examples:
*
someFunction = window.probes.measure(someFunction, {
name: "somename" // or function(args) { return "name"; },
before: function(data, owner, args) {
// if owner is true, we are not in a recursive function call.
//
// data contains the bucker of data already measuer
// data.count >= 0
// data.time is the total time measured till now
//
// arguments contains the original arguments sent to the function
},
after: function(data, owner, args) {
// same format as before
}
});
// minimal
someFunction = window.probes.measure(someFunction, "someFunction");
*
* someFunction = window.probes.measure(someFunction, {
* name: "somename" // or function(args) { return "name"; },
* before: function(data, owner, args) {
* // if owner is true, we are not in a recursive function call.
* //
* // data contains the bucker of data already measuer
* // data.count >= 0
* // data.time is the total time measured till now
* //
* // arguments contains the original arguments sent to the function
* },
* after: function(data, owner, args) {
* // same format as before
* }
* });
*
*
* // minimal
* someFunction = window.probes.measure(someFunction, "someFunction");
*
*
* */

View file

@ -1,92 +0,0 @@
// Sam: I wrote this but it is totally unsafe so I ported Google Cajole
// Thing is Cajole is old and complex (albeit super duper fast)
//
// I would like this ported to: https://github.com/tautologistics/node-htmlparser , perf tested
// and move off cajole
//
// See also: http://stackoverflow.com/questions/14971083/is-jquerys-safe-from-xss
//
// (function( $ ) {
//
// var elements = ["a", "abbr", "aside", "b", "bdo", "blockquote", "br",
// "caption", "cite", "code", "col", "colgroup", "dd", "div",
// "del", "dfn", "dl", "dt", "em", "hr", "figcaption", "figure",
// "h1", "h2", "h3", "h4", "h5", "h6", "hgroup", "i", "img", "ins",
// "kbd", "li", "mark", "ol", "p", "pre", "q", "rp", "rt", "ruby",
// "s", "samp", "small", "span", "strike", "strong", "sub", "sup",
// "table", "tbody", "td", "tfoot", "th", "thead", "time", "tr", "u",
// "ul", "var", "wbr"];
//
// var attributes = {
// 'all' : ['dir', 'lang', 'title', 'class'],
// 'aside' : ['data-post', 'data-full', 'data-topic'],
// 'a' : ['href'],
// 'blockquote' : ['cite'],
// 'col' : ['span', 'width'],
// 'colgroup' : ['span', 'width'],
// 'del' : ['cite', 'datetime'],
// 'img' : ['align', 'alt', 'height', 'src', 'width'],
// 'ins' : ['cite', 'datetime'],
// 'ol' : ['start', 'reversed', 'type'],
// 'q' : ['cite'],
// 'span' : ['style'],
// 'table' : ['summary', 'width', 'style', 'cellpadding', 'cellspacing'],
// 'td' : ['abbr', 'axis', 'colspan', 'rowspan', 'width', 'style'],
// 'th' : ['abbr', 'axis', 'colspan', 'rowspan', 'scope', 'width', 'style'],
// 'time' : ['datetime', 'pubdate'],
// 'ul' : ['type']
//
// };
//
// var elementMap = {};
// jQuery.each(elements, function(idx,e){
// elementMap[e] = true;
// });
//
// var scrubAttributes = function(e){
// jQuery.each(e.attributes, function(idx, attr){
//
// if(jQuery.inArray(attr.name, attributes.all) === -1 &&
// jQuery.inArray(attr.name, attributes[e.tagName.toLowerCase()]) === -1) {
// e.removeAttribute(attr.name);
// }
// });
// return(e);
// };
//
// var scrubNode = function(e){
// if (!e.tagName) { return(e); }
// if(elementMap[e.tagName.toLowerCase()]){
// return scrubAttributes(e);
// }
// else
// {
// return null;
// }
// };
//
// var scrubTree = function(e) {
// if (!e) { return; }
//
// var clean = scrubNode(e);
// if(!clean){
// e.parentNode.removeChild(e);
// }
// else {
// jQuery.each(clean.children, function(idx, inner){
// scrubTree(inner);
// });
// }
// };
//
// $.fn.sanitize = function() {
// clean = this.filter(function(){
// return scrubNode(this);
// }).each(function(){
// scrubTree(this);
// });
//
// return clean;
// };
// })( jQuery );

View file

@ -1,169 +1,159 @@
/**
We use this class to track how long posts in a topic are on the screen.
/* We use this class to track how long posts in a topic are on the screen.
*/
@class ScreenTrack
@extends Ember.Object
@namespace Discourse
@module Discourse
**/
Discourse.ScreenTrack = Ember.Object.extend({
// Don't send events if we haven't scrolled in a long time
PAUSE_UNLESS_SCROLLED: 1000 * 60 * 3,
/* This could be a potentially awesome metric to keep track of.
*/
// After 6 minutes stop tracking read position on post
MAX_TRACKING_TIME: 1000 * 60 * 6,
totalTimings: {},
(function() {
// Elements to track
timings: {},
topicTime: 0,
cancelled: false,
window.Discourse.ScreenTrack = Ember.Object.extend({
/* Don't send events if we haven't scrolled in a long time
*/
track: function(elementId, postNumber) {
this.timings["#" + elementId] = {
time: 0,
postNumber: postNumber
};
},
PAUSE_UNLESS_SCROLLED: 1000 * 60 * 3,
/* After 6 minutes stop tracking read position on post
*/
MAX_TRACKING_TIME: 1000 * 60 * 6,
totalTimings: {},
/* Elements to track
*/
timings: {},
topicTime: 0,
cancelled: false,
track: function(elementId, postNumber) {
this.timings["#" + elementId] = {
time: 0,
postNumber: postNumber
};
},
guessedSeen: function(postNumber) {
if (postNumber > (this.highestSeen || 0)) {
this.highestSeen = postNumber;
}
},
/* Reset our timers
*/
reset: function() {
this.lastTick = new Date().getTime();
this.lastFlush = 0;
this.cancelled = false;
},
/* Start tracking
*/
start: function() {
var _this = this;
this.reset();
this.lastScrolled = new Date().getTime();
this.interval = setInterval(function() {
return _this.tick();
}, 1000);
},
/* Cancel and eject any tracking we have buffered
*/
cancel: function() {
this.cancelled = true;
this.timings = {};
this.topicTime = 0;
clearInterval(this.interval);
this.interval = null;
},
/* Stop tracking and flush buffered read records
*/
stop: function() {
clearInterval(this.interval);
this.interval = null;
return this.flush();
},
scrolled: function() {
this.lastScrolled = new Date().getTime();
},
flush: function() {
var highestSeenByTopic, newTimings, topicId,
_this = this;
if (this.cancelled) {
return;
}
/* We don't log anything unless we're logged in
*/
if (!Discourse.get('currentUser')) {
return;
}
newTimings = {};
Object.values(this.timings, function(timing) {
if (!_this.totalTimings[timing.postNumber])
_this.totalTimings[timing.postNumber] = 0;
if (timing.time > 0 && _this.totalTimings[timing.postNumber] < _this.MAX_TRACKING_TIME) {
_this.totalTimings[timing.postNumber] += timing.time;
newTimings[timing.postNumber] = timing.time;
}
timing.time = 0;
});
topicId = this.get('topic_id');
highestSeenByTopic = Discourse.get('highestSeenByTopic');
if ((highestSeenByTopic[topicId] || 0) < this.highestSeen) {
highestSeenByTopic[topicId] = this.highestSeen;
}
if (!Object.isEmpty(newTimings)) {
jQuery.ajax('/topics/timings', {
data: {
timings: newTimings,
topic_time: this.topicTime,
highest_seen: this.highestSeen,
topic_id: topicId
},
cache: false,
type: 'POST',
headers: {
'X-SILENCE-LOGGER': 'true'
}
});
this.topicTime = 0;
}
this.lastFlush = 0;
},
tick: function() {
/* If the user hasn't scrolled the browser in a long time, stop tracking time read
*/
var diff, docViewBottom, docViewTop, sinceScrolled,
_this = this;
sinceScrolled = new Date().getTime() - this.lastScrolled;
if (sinceScrolled > this.PAUSE_UNLESS_SCROLLED) {
this.reset();
return;
}
diff = new Date().getTime() - this.lastTick;
this.lastFlush += diff;
this.lastTick = new Date().getTime();
if (this.lastFlush > (Discourse.SiteSettings.flush_timings_secs * 1000)) {
this.flush();
}
/* Don't track timings if we're not in focus
*/
if (!Discourse.get("hasFocus")) {
return;
}
this.topicTime += diff;
docViewTop = jQuery(window).scrollTop() + jQuery('header').height();
docViewBottom = docViewTop + jQuery(window).height();
return Object.keys(this.timings, function(id) {
var $element, elemBottom, elemTop, timing;
$element = jQuery(id);
if ($element.length === 1) {
elemTop = $element.offset().top;
elemBottom = elemTop + $element.height();
/* If part of the element is on the screen, increase the counter
*/
if (((docViewTop <= elemTop && elemTop <= docViewBottom)) || ((docViewTop <= elemBottom && elemBottom <= docViewBottom))) {
timing = _this.timings[id];
timing.time = timing.time + diff;
}
}
});
guessedSeen: function(postNumber) {
if (postNumber > (this.highestSeen || 0)) {
this.highestSeen = postNumber;
}
});
},
// Reset our timers
reset: function() {
this.lastTick = new Date().getTime();
this.lastFlush = 0;
this.cancelled = false;
},
// Start tracking
start: function() {
var _this = this;
this.reset();
this.lastScrolled = new Date().getTime();
this.interval = setInterval(function() {
return _this.tick();
}, 1000);
},
// Cancel and eject any tracking we have buffered
cancel: function() {
this.cancelled = true;
this.timings = {};
this.topicTime = 0;
clearInterval(this.interval);
this.interval = null;
},
// Stop tracking and flush buffered read records
stop: function() {
clearInterval(this.interval);
this.interval = null;
return this.flush();
},
scrolled: function() {
this.lastScrolled = new Date().getTime();
},
flush: function() {
var highestSeenByTopic, newTimings, topicId,
_this = this;
if (this.cancelled) {
return;
}
// We don't log anything unless we're logged in
if (!Discourse.get('currentUser')) {
return;
}
newTimings = {};
Object.values(this.timings, function(timing) {
if (!_this.totalTimings[timing.postNumber])
_this.totalTimings[timing.postNumber] = 0;
if (timing.time > 0 && _this.totalTimings[timing.postNumber] < _this.MAX_TRACKING_TIME) {
_this.totalTimings[timing.postNumber] += timing.time;
newTimings[timing.postNumber] = timing.time;
}
timing.time = 0;
});
topicId = this.get('topic_id');
highestSeenByTopic = Discourse.get('highestSeenByTopic');
if ((highestSeenByTopic[topicId] || 0) < this.highestSeen) {
highestSeenByTopic[topicId] = this.highestSeen;
}
if (!Object.isEmpty(newTimings)) {
jQuery.ajax('/topics/timings', {
data: {
timings: newTimings,
topic_time: this.topicTime,
highest_seen: this.highestSeen,
topic_id: topicId
},
cache: false,
type: 'POST',
headers: {
'X-SILENCE-LOGGER': 'true'
}
});
this.topicTime = 0;
}
this.lastFlush = 0;
},
tick: function() {
// If the user hasn't scrolled the browser in a long time, stop tracking time read
var diff, docViewBottom, docViewTop, sinceScrolled,
_this = this;
sinceScrolled = new Date().getTime() - this.lastScrolled;
if (sinceScrolled > this.PAUSE_UNLESS_SCROLLED) {
this.reset();
return;
}
diff = new Date().getTime() - this.lastTick;
this.lastFlush += diff;
this.lastTick = new Date().getTime();
if (this.lastFlush > (Discourse.SiteSettings.flush_timings_secs * 1000)) {
this.flush();
}
// Don't track timings if we're not in focus
if (!Discourse.get("hasFocus")) return;
this.topicTime += diff;
docViewTop = $(window).scrollTop() + $('header').height();
docViewBottom = docViewTop + $(window).height();
return Object.keys(this.timings, function(id) {
var $element, elemBottom, elemTop, timing;
$element = $(id);
if ($element.length === 1) {
elemTop = $element.offset().top;
elemBottom = elemTop + $element.height();
// If part of the element is on the screen, increase the counter
if (((docViewTop <= elemTop && elemTop <= docViewBottom)) || ((docViewTop <= elemBottom && elemBottom <= docViewBottom))) {
timing = _this.timings[id];
timing.time = timing.time + diff;
}
}
});
}
});
}).call(this);

View file

@ -1,18 +1,28 @@
/*global hljs:true */
/* Helper object for syntax highlighting. Uses highlight.js which is loaded
on demand. */
(function() {
/**
Helper object for syntax highlighting. Uses highlight.js which is loaded on demand.
window.Discourse.SyntaxHighlighting = {
apply: function($elem) {
var _this = this;
return jQuery('pre code[class]', $elem).each(function(i, e) {
return $LAB.script("/javascripts/highlight-handlebars.pack.js").wait(function() {
return hljs.highlightBlock(e);
});
@class SyntaxHighlighting
@namespace Discourse
@module Discourse
**/
Discourse.SyntaxHighlighting = {
/**
Apply syntax highlighting to a jQuery element
@method apply
@param {jQuery.selector} $elem The element we want to apply our highlighting to
**/
apply: function($elem) {
var _this = this;
return $('pre code[class]', $elem).each(function(i, e) {
return $LAB.script("/javascripts/highlight-handlebars.pack.js").wait(function() {
return hljs.highlightBlock(e);
});
}
};
});
}
};
}).call(this);

View file

@ -1,45 +1,43 @@
/**
CSS transitions are a PITA, often we need to queue some js after a transition, this helper ensures
it happens after the transition.
/* CSS transitions are a PITA, often we need to queue some js after a transition, this helper ensures
*/
SO: http://stackoverflow.com/questions/9943435/css3-animation-end-techniques
/* it happens after the transition
*/
@class TransitionHelper
@namespace Discourse
@module Discourse
**/
var dummy, eventNameHash, transitionEnd, _getTransitionEndEventName;
/* SO: http://stackoverflow.com/questions/9943435/css3-animation-end-techniques
*/
dummy = document.createElement("div");
eventNameHash = {
webkit: "webkitTransitionEnd",
Moz: "transitionend",
O: "oTransitionEnd",
ms: "MSTransitionEnd"
};
(function() {
var dummy, eventNameHash, transitionEnd, _getTransitionEndEventName;
dummy = document.createElement("div");
eventNameHash = {
webkit: "webkitTransitionEnd",
Moz: "transitionend",
O: "oTransitionEnd",
ms: "MSTransitionEnd"
};
_getTransitionEndEventName = function() {
var retValue;
retValue = "transitionend";
Object.keys(eventNameHash).some(function(vendor) {
if (vendor + "TransitionProperty" in dummy.style) {
retValue = eventNameHash[vendor];
return true;
}
});
return retValue;
};
transitionEnd = _getTransitionEndEventName();
window.Discourse.TransitionHelper = {
after: function(element, callback) {
return jQuery(element).on(transitionEnd, callback);
_getTransitionEndEventName = function() {
var retValue;
retValue = "transitionend";
Object.keys(eventNameHash).some(function(vendor) {
if (vendor + "TransitionProperty" in dummy.style) {
retValue = eventNameHash[vendor];
return true;
}
};
});
return retValue;
};
transitionEnd = _getTransitionEndEventName();
window.Discourse.TransitionHelper = {
after: function(element, callback) {
return $(element).on(transitionEnd, callback);
}
};
}).call(this);

View file

@ -1,76 +1,81 @@
(function() {
var cache, cacheTime, cacheTopicId, debouncedSearch, doSearch;
/**
Helper for searching for Users
cache = {};
@class UserSearch
@namespace Discourse
@module Discourse
**/
var cache, cacheTime, cacheTopicId, debouncedSearch, doSearch;
cacheTopicId = null;
cache = {};
cacheTime = null;
cacheTopicId = null;
doSearch = function(term, topicId, success) {
return jQuery.ajax({
url: '/users/search/users',
dataType: 'JSON',
data: {
term: term,
topic_id: topicId
},
success: function(r) {
cache[term] = r;
cacheTime = new Date();
return success(r);
}
});
};
cacheTime = null;
debouncedSearch = Discourse.debounce(doSearch, 200);
doSearch = function(term, topicId, success) {
return jQuery.ajax({
url: '/users/search/users',
dataType: 'JSON',
data: {
term: term,
topic_id: topicId
},
success: function(r) {
cache[term] = r;
cacheTime = new Date();
return success(r);
}
});
};
window.Discourse.UserSearch = {
search: function(options) {
var callback, exclude, limit, success, term, topicId;
term = options.term || "";
callback = options.callback;
exclude = options.exclude || [];
topicId = options.topicId;
limit = options.limit || 5;
if (!callback) {
throw "missing callback";
}
/*TODO site setting for allowed regex in username ?
*/
debouncedSearch = Discourse.debounce(doSearch, 200);
if (term.match(/[^a-zA-Z0-9\_\.]/)) {
callback([]);
return true;
}
if ((new Date() - cacheTime) > 30000) {
cache = {};
}
if (cacheTopicId !== topicId) {
cache = {};
}
cacheTopicId = topicId;
success = function(r) {
var result;
result = [];
r.users.each(function(u) {
if (exclude.indexOf(u.username) === -1) {
result.push(u);
}
if (result.length > limit) {
return false;
}
return true;
});
return callback(result);
};
if (cache[term]) {
success(cache[term]);
} else {
debouncedSearch(term, topicId, success);
}
Discourse.UserSearch = {
search: function(options) {
var callback, exclude, limit, success, term, topicId;
term = options.term || "";
callback = options.callback;
exclude = options.exclude || [];
topicId = options.topicId;
limit = options.limit || 5;
if (!callback) {
throw "missing callback";
}
// TODO site setting for allowed regex in username
if (term.match(/[^a-zA-Z0-9\_\.]/)) {
callback([]);
return true;
}
};
if ((new Date() - cacheTime) > 30000) {
cache = {};
}
if (cacheTopicId !== topicId) {
cache = {};
}
cacheTopicId = topicId;
success = function(r) {
var result;
result = [];
r.users.each(function(u) {
if (exclude.indexOf(u.username) === -1) {
result.push(u);
}
if (result.length > limit) {
return false;
}
return true;
});
return callback(result);
};
if (cache[term]) {
success(cache[term]);
} else {
debouncedSearch(term, topicId, success);
}
return true;
}
};
}).call(this);

View file

@ -1,277 +1,266 @@
/*global sanitizeHtml:true Markdown:true */
(function() {
var baseUrl, site;
/**
General utility functions
baseUrl = null;
@class Utilities
@namespace Discourse
@module Discourse
**/
Discourse.Utilities = {
site = null;
translateSize: function(size) {
switch (size) {
case 'tiny':
size = 20;
break;
case 'small':
size = 25;
break;
case 'medium':
size = 32;
break;
case 'large':
size = 45;
}
return size;
},
Discourse.Utilities = {
translateSize: function(size) {
switch (size) {
case 'tiny':
size = 20;
break;
case 'small':
size = 25;
break;
case 'medium':
size = 32;
break;
case 'large':
size = 45;
}
return size;
},
categoryUrlId: function(category) {
var id, slug;
if (!category) {
return "";
}
id = Em.get(category, 'id');
slug = Em.get(category, 'slug');
if ((!slug) || slug.isBlank()) {
return "" + id + "-category";
}
return slug;
},
/* Create a badge like category link
*/
categoryUrlId: function(category) {
var id, slug;
if (!category) {
return "";
}
id = Em.get(category, 'id');
slug = Em.get(category, 'slug');
if ((!slug) || slug.isBlank()) {
return "" + id + "-category";
}
return slug;
},
categoryLink: function(category) {
var color, name, description, result;
if (!category) return "";
// Create a badge like category link
categoryLink: function(category) {
var color, name, description, result;
if (!category) return "";
color = Em.get(category, 'color');
name = Em.get(category, 'name');
description = Em.get(category, 'description');
color = Em.get(category, 'color');
name = Em.get(category, 'name');
description = Em.get(category, 'description');
// Build the HTML link
result = "<a href=\"/category/" +
this.categoryUrlId(category) +
"\" class=\"badge-category excerptable\" data-excerpt-size=\"medium\" ";
// Build the HTML link
result = "<a href=\"/category/" + this.categoryUrlId(category) + "\" class=\"badge-category\" ";
// Add description if we have it
if (description) result += "title=\"" + description + "\" ";
// Add description if we have it
if (description) result += "title=\"" + description + "\" ";
return result + "style=\"background-color: #" + color + "\">" + name + "</a>";
},
avatarUrl: function(username, size, template) {
var rawSize;
if (!username) {
return "";
}
size = Discourse.Utilities.translateSize(size);
rawSize = (size * (window.devicePixelRatio || 1)).toFixed();
if (template) {
return template.replace(/\{size\}/g, rawSize);
}
return "/users/" + (username.toLowerCase()) + "/avatar/" + rawSize + "?__ws=" + (encodeURIComponent(Discourse.BaseUrl || ""));
},
avatarImg: function(options) {
var extraClasses, size, title, url;
size = Discourse.Utilities.translateSize(options.size);
title = options.title || "";
extraClasses = options.extraClasses || "";
url = Discourse.Utilities.avatarUrl(options.username, options.size, options.avatarTemplate);
return "<img width='" + size + "' height='" + size + "' src='" + url + "' class='avatar " +
(extraClasses || "") + "' title='" + (Handlebars.Utils.escapeExpression(title || "")) + "'>";
},
postUrl: function(slug, topicId, postNumber) {
var url;
url = "/t/";
if (slug) {
url += slug + "/";
}
url += topicId;
if (postNumber > 1) {
url += "/" + postNumber;
}
return url;
},
emailValid: function(email) {
/* see: http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
*/
return result + "style=\"background-color: #" + color + "\">" + name + "</a>";
},
var re;
re = /^[a-zA-Z0-9!#$%&'*+\/=?\^_`{|}~\-]+(?:\.[a-zA-Z0-9!#$%&'\*+\/=?\^_`{|}~\-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?$/;
return re.test(email);
},
selectedText: function() {
var t;
t = '';
if (window.getSelection) {
t = window.getSelection().toString();
} else if (document.getSelection) {
t = document.getSelection().toString();
} else if (document.selection) {
t = document.selection.createRange().text;
}
return String(t).trim();
},
/* Determine the position of the caret in an element
*/
avatarUrl: function(username, size, template) {
var rawSize;
if (!username) {
return "";
}
size = Discourse.Utilities.translateSize(size);
rawSize = (size * (window.devicePixelRatio || 1)).toFixed();
if (template) {
return template.replace(/\{size\}/g, rawSize);
}
return "/users/" + (username.toLowerCase()) + "/avatar/" + rawSize + "?__ws=" + (encodeURIComponent(Discourse.BaseUrl || ""));
},
caretPosition: function(el) {
var r, rc, re;
if (el.selectionStart) {
return el.selectionStart;
}
if (document.selection) {
el.focus();
r = document.selection.createRange();
if (!r) return 0;
re = el.createTextRange();
rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
return rc.text.length;
}
return 0;
},
/* Set the caret's position
*/
avatarImg: function(options) {
var extraClasses, size, title, url;
size = Discourse.Utilities.translateSize(options.size);
title = options.title || "";
extraClasses = options.extraClasses || "";
url = Discourse.Utilities.avatarUrl(options.username, options.size, options.avatarTemplate);
return "<img width='" + size + "' height='" + size + "' src='" + url + "' class='avatar " +
(extraClasses || "") + "' title='" + (Handlebars.Utils.escapeExpression(title || "")) + "'>";
},
setCaretPosition: function(ctrl, pos) {
var range;
if (ctrl.setSelectionRange) {
ctrl.focus();
ctrl.setSelectionRange(pos, pos);
return;
}
if (ctrl.createTextRange) {
range = ctrl.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
return range.select();
}
},
markdownConverter: function(opts) {
var converter, mentionLookup,
_this = this;
converter = new Markdown.Converter();
if (opts) {
mentionLookup = opts.mentionLookup;
}
mentionLookup = mentionLookup || Discourse.Mention.lookupCache;
/* Before cooking callbacks
*/
postUrl: function(slug, topicId, postNumber) {
var url;
url = "/t/";
if (slug) {
url += slug + "/";
}
url += topicId;
if (postNumber > 1) {
url += "/" + postNumber;
}
return url;
},
converter.hooks.chain("preConversion", function(text) {
_this.trigger('beforeCook', {
detail: text,
opts: opts
});
return _this.textResult || text;
emailValid: function(email) {
// see: http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
var re;
re = /^[a-zA-Z0-9!#$%&'*+\/=?\^_`{|}~\-]+(?:\.[a-zA-Z0-9!#$%&'\*+\/=?\^_`{|}~\-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?$/;
return re.test(email);
},
selectedText: function() {
var t;
t = '';
if (window.getSelection) {
t = window.getSelection().toString();
} else if (document.getSelection) {
t = document.getSelection().toString();
} else if (document.selection) {
t = document.selection.createRange().text;
}
return String(t).trim();
},
// Determine the position of the caret in an element
caretPosition: function(el) {
var r, rc, re;
if (el.selectionStart) {
return el.selectionStart;
}
if (document.selection) {
el.focus();
r = document.selection.createRange();
if (!r) return 0;
re = el.createTextRange();
rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
return rc.text.length;
}
return 0;
},
// Set the caret's position
setCaretPosition: function(ctrl, pos) {
var range;
if (ctrl.setSelectionRange) {
ctrl.focus();
ctrl.setSelectionRange(pos, pos);
return;
}
if (ctrl.createTextRange) {
range = ctrl.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
return range.select();
}
},
markdownConverter: function(opts) {
var converter, mentionLookup,
_this = this;
converter = new Markdown.Converter();
if (opts) {
mentionLookup = opts.mentionLookup;
}
mentionLookup = mentionLookup || Discourse.Mention.lookupCache;
// Before cooking callbacks
converter.hooks.chain("preConversion", function(text) {
_this.trigger('beforeCook', {
detail: text,
opts: opts
});
/* Support autolinking of www.something.com
*/
return _this.textResult || text;
});
// Support autolinking of www.something.com
converter.hooks.chain("preConversion", function(text) {
return text.replace(/(^|[\s\n])(www\.[a-z\.\-\_\(\)\/\?\=\%0-9]+)/gim, function(full, _, rest) {
return " <a href=\"http://" + rest + "\">" + rest + "</a>";
});
});
// newline prediction in trivial cases
if (!Discourse.SiteSettings.traditional_markdown_linebreaks) {
converter.hooks.chain("preConversion", function(text) {
return text.replace(/(^|[\s\n])(www\.[a-z\.\-\_\(\)\/\?\=\%0-9]+)/gim, function(full, _, rest) {
return " <a href=\"http://" + rest + "\">" + rest + "</a>";
return text.replace(/(^[\w<][^\n]*\n+)/gim, function(t) {
if (t.match(/\n{2}/gim)) {
return t;
}
return t.replace("\n", " \n");
});
});
/* newline prediction in trivial cases
*/
}
if (!Discourse.SiteSettings.traditional_markdown_linebreaks) {
converter.hooks.chain("preConversion", function(text) {
return text.replace(/(^[\w<][^\n]*\n+)/gim, function(t) {
if (t.match(/\n{2}/gim)) {
return t;
}
return t.replace("\n", " \n");
});
});
}
/* github style fenced code
*/
converter.hooks.chain("preConversion", function(text) {
return text.replace(/^`{3}(?:(.*$)\n)?([\s\S]*?)^`{3}/gm, function(wholeMatch, m1, m2) {
var escaped;
escaped = Handlebars.Utils.escapeExpression(m2);
return "<pre><code class='" + (m1 || 'lang-auto') + "'>" + escaped + "</code></pre>";
});
// github style fenced code
converter.hooks.chain("preConversion", function(text) {
return text.replace(/^`{3}(?:(.*$)\n)?([\s\S]*?)^`{3}/gm, function(wholeMatch, m1, m2) {
var escaped;
escaped = Handlebars.Utils.escapeExpression(m2);
return "<pre><code class='" + (m1 || 'lang-auto') + "'>" + escaped + "</code></pre>";
});
});
converter.hooks.chain("postConversion", function(text) {
if (!text) return "";
// don't to mention voodoo in pres
text = text.replace(/<pre>([\s\S]*@[\s\S]*)<\/pre>/gi, function(wholeMatch, inner) {
return "<pre>" + (inner.replace(/@/g, '&#64;')) + "</pre>";
});
// Add @mentions of names
text = text.replace(/([\s\t>,:'|";\]])(@[A-Za-z0-9_-|\.]*[A-Za-z0-9_-|]+)(?=[\s\t<\!:|;',"\?\.])/g, function(x, pre, name) {
if (mentionLookup(name.substr(1))) {
return "" + pre + "<a href='/users/" + (name.substr(1).toLowerCase()) + "' class='mention'>" + name + "</a>";
} else {
return "" + pre + "<span class='mention'>" + name + "</span>";
}
});
// a primitive attempt at oneboxing, this regex gives me much eye sores
text = text.replace(/(<li>)?((<p>|<br>)[\s\n\r]*)(<a href=["]([^"]+)[^>]*)>([^<]+<\/a>[\s\n\r]*(?=<\/p>|<br>))/gi, function() {
// We don't onebox items in a list
var onebox, url;
if (arguments[1]) {
return arguments[0];
}
url = arguments[5];
if (Discourse && Discourse.Onebox) {
onebox = Discourse.Onebox.lookupCache(url);
}
if (onebox && !onebox.isBlank()) {
return arguments[2] + onebox;
} else {
return arguments[2] + arguments[4] + " class=\"onebox\" target=\"_blank\">" + arguments[6];
}
});
return(text);
});
converter.hooks.chain("postConversion", function(text) {
return Discourse.BBCode.format(text, opts);
});
if (opts.sanitize) {
converter.hooks.chain("postConversion", function(text) {
if (!text) {
if (!window.sanitizeHtml) {
return "";
}
/* don't to mention voodoo in pres
*/
text = text.replace(/<pre>([\s\S]*@[\s\S]*)<\/pre>/gi, function(wholeMatch, inner) {
return "<pre>" + (inner.replace(/@/g, '&#64;')) + "</pre>";
});
/* Add @mentions of names
*/
text = text.replace(/([\s\t>,:'|";\]])(@[A-Za-z0-9_-|\.]*[A-Za-z0-9_-|]+)(?=[\s\t<\!:|;',"\?\.])/g, function(x, pre, name) {
if (mentionLookup(name.substr(1))) {
return "" + pre + "<a href='/users/" + (name.substr(1).toLowerCase()) + "' class='mention'>" + name + "</a>";
} else {
return "" + pre + "<span class='mention'>" + name + "</span>";
}
});
/* a primitive attempt at oneboxing, this regex gives me much eye sores
*/
text = text.replace(/(<li>)?((<p>|<br>)[\s\n\r]*)(<a href=["]([^"]+)[^>]*)>([^<]+<\/a>[\s\n\r]*(?=<\/p>|<br>))/gi, function() {
/* We don't onebox items in a list
*/
var onebox, url;
if (arguments[1]) {
return arguments[0];
}
url = arguments[5];
if (Discourse && Discourse.Onebox) {
onebox = Discourse.Onebox.lookupCache(url);
}
if (onebox && !onebox.isBlank()) {
return arguments[2] + onebox;
} else {
return arguments[2] + arguments[4] + " class=\"onebox\" target=\"_blank\">" + arguments[6];
}
});
return(text);
return sanitizeHtml(text);
});
converter.hooks.chain("postConversion", function(text) {
return Discourse.BBCode.format(text, opts);
});
if (opts.sanitize) {
converter.hooks.chain("postConversion", function(text) {
if (!window.sanitizeHtml) {
return "";
}
return sanitizeHtml(text);
});
}
return converter;
},
/* Takes raw input and cooks it to display nicely (mostly markdown)
*/
cook: function(raw, opts) {
if (!opts) opts = {};
// Make sure we've got a string
if (!raw) return "";
if (raw.length === 0) return "";
this.converter = this.markdownConverter(opts);
return this.converter.makeHtml(raw);
}
};
return converter;
},
RSVP.EventTarget.mixin(Discourse.Utilities);
// Takes raw input and cooks it to display nicely (mostly markdown)
cook: function(raw, opts) {
if (!opts) opts = {};
}).call(this);
// Make sure we've got a string
if (!raw) return "";
if (raw.length === 0) return "";
this.converter = this.markdownConverter(opts);
return this.converter.makeHtml(raw);
}
};
RSVP.EventTarget.mixin(Discourse.Utilities);

View file

@ -1,7 +1,16 @@
/*global _gaq:true */
window.Discourse.ApplicationController = Ember.Controller.extend({
/**
The base controller for all things Discourse
@class ApplicationController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.ApplicationController = Discourse.Controller.extend({
needs: ['modal'],
showLogin: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.LoginView.create()) : void 0;
@ -18,5 +27,5 @@ window.Discourse.ApplicationController = Ember.Controller.extend({
this.afterFirstHit = true;
}
}.observes('currentPath')
});

View file

@ -1,261 +1,271 @@
(function() {
/**
This controller supports composing new posts and topics.
window.Discourse.ComposerController = Ember.Controller.extend(Discourse.Presence, {
needs: ['modal', 'topic'],
hasReply: false,
togglePreview: function() {
return this.get('content').togglePreview();
},
/* Import a quote from the post
*/
@class ComposerController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.ComposerController = Discourse.Controller.extend({
needs: ['modal', 'topic'],
hasReply: false,
importQuote: function() {
return this.get('content').importQuote();
},
appendText: function(text) {
var c;
c = this.get('content');
if (c) {
return c.appendText(text);
}
},
save: function() {
var composer,
_this = this;
composer = this.get('content');
composer.set('disableDrafts', true);
return composer.save({
imageSizes: this.get('view').imageSizes()
}).then(function(opts) {
opts = opts || {};
_this.close();
if (composer.get('creatingTopic')) {
Discourse.set('currentUser.topic_count', Discourse.get('currentUser.topic_count') + 1);
} else {
Discourse.set('currentUser.reply_count', Discourse.get('currentUser.reply_count') + 1);
}
return Discourse.routeTo(opts.post.get('url'));
}, function(error) {
composer.set('disableDrafts', false);
return bootbox.alert(error);
});
},
checkReplyLength: function() {
if (this.present('content.reply')) {
return this.set('hasReply', true);
togglePreview: function() {
return this.get('content').togglePreview();
},
// Import a quote from the post
importQuote: function() {
return this.get('content').importQuote();
},
appendText: function(text) {
var c;
c = this.get('content');
if (c) {
return c.appendText(text);
}
},
save: function() {
var composer,
_this = this;
composer = this.get('content');
composer.set('disableDrafts', true);
return composer.save({
imageSizes: this.get('view').imageSizes()
}).then(function(opts) {
opts = opts || {};
_this.close();
if (composer.get('creatingTopic')) {
Discourse.set('currentUser.topic_count', Discourse.get('currentUser.topic_count') + 1);
} else {
return this.set('hasReply', false);
Discourse.set('currentUser.reply_count', Discourse.get('currentUser.reply_count') + 1);
}
},
saveDraft: function() {
var model;
model = this.get('content');
if (model) {
return model.saveDraft();
}
},
/*
Open the reply view
Discourse.routeTo(opts.post.get('url'));
}, function(error) {
composer.set('disableDrafts', false);
bootbox.alert(error);
});
},
opts:
action - The action we're performing: edit, reply or createTopic
post - The post we're replying to, if present
topic - The topic we're replying to, if present
quote - If we're opening a reply from a quote, the quote we're making
*/
open: function(opts) {
var composer, promise, view,
_this = this;
if (!opts) opts = {};
opts.promise = promise = opts.promise || new RSVP.Promise();
checkReplyLength: function() {
if (this.present('content.reply')) {
this.set('hasReply', true);
} else {
this.set('hasReply', false);
if (!opts.draftKey) {
alert("composer was opened without a draft key");
throw "composer opened without a proper draft key";
}
/* ensure we have a view now, without it transitions are going to be messed
*/
}
},
view = this.get('view');
if (!view) {
view = Discourse.ComposerView.create({
controller: this
});
view.appendTo(jQuery('#main'));
this.set('view', view);
/* the next runloop is too soon, need to get the control rendered and then
*/
saveDraft: function() {
var model;
model = this.get('content');
if (model) model.saveDraft();
},
/* we need to change stuff, otherwise css animations don't kick in
*/
/**
Open the composer view
Em.run.next(function() {
return Em.run.next(function() {
return _this.open(opts);
});
});
return promise;
}
composer = this.get('content');
if (composer && opts.draftKey !== composer.draftKey && composer.composeState === Discourse.Composer.DRAFT) {
this.close();
composer = null;
}
if (composer && !opts.tested && composer.wouldLoseChanges()) {
if (composer.composeState === Discourse.Composer.DRAFT && composer.draftKey === opts.draftKey && composer.action === opts.action) {
composer.set('composeState', Discourse.Composer.OPEN);
promise.resolve();
return promise;
} else {
opts.tested = true;
if (!opts.ignoreIfChanged) {
this.cancel((function() {
return _this.open(opts);
}), (function() {
return promise.reject();
}));
}
return promise;
}
}
/* we need a draft sequence, without it drafts are bust
*/
@method open
@param {Object} opts Options for creating a post
@param {String} opts.action The action we're performing: edit, reply or createTopic
@param {Discourse.Post} [opts.post] The post we're replying to
@param {Discourse.Topic} [opts.topic] The topic we're replying to
@param {String} [opts.quote] If we're opening a reply from a quote, the quote we're making
**/
open: function(opts) {
var composer, promise, view,
_this = this;
if (!opts) opts = {};
if (opts.draftSequence === void 0) {
Discourse.Draft.get(opts.draftKey).then(function(data) {
opts.draftSequence = data.draft_sequence;
opts.draft = data.draft;
opts.promise = promise = opts.promise || new RSVP.Promise();
this.set('hasReply', false);
if (!opts.draftKey) {
alert("composer was opened without a draft key");
throw "composer opened without a proper draft key";
}
// ensure we have a view now, without it transitions are going to be messed
view = this.get('view');
if (!view) {
view = Discourse.ComposerView.create({
controller: this
});
view.appendTo($('#main'));
this.set('view', view);
// the next runloop is too soon, need to get the control rendered and then
// we need to change stuff, otherwise css animations don't kick in
Em.run.next(function() {
return Em.run.next(function() {
return _this.open(opts);
});
});
return promise;
}
composer = this.get('content');
if (composer && opts.draftKey !== composer.draftKey && composer.composeState === Discourse.Composer.DRAFT) {
this.close();
composer = null;
}
if (composer && !opts.tested && composer.wouldLoseChanges()) {
if (composer.composeState === Discourse.Composer.DRAFT && composer.draftKey === opts.draftKey && composer.action === opts.action) {
composer.set('composeState', Discourse.Composer.OPEN);
promise.resolve();
return promise;
} else {
opts.tested = true;
if (!opts.ignoreIfChanged) {
this.cancel((function() {
return _this.open(opts);
}), (function() {
return promise.reject();
}));
}
return promise;
}
if (opts.draft) {
composer = Discourse.Composer.loadDraft(opts.draftKey, opts.draftSequence, opts.draft);
if (composer) {
composer.set('topic', opts.topic);
}
}
composer = composer || Discourse.Composer.open(opts);
this.set('content', composer);
this.set('view.content', composer);
promise.resolve();
return promise;
},
wouldLoseChanges: function() {
var composer;
composer = this.get('content');
return composer && composer.wouldLoseChanges();
},
/* View a new reply we've made
*/
viewNewReply: function() {
Discourse.routeTo(this.get('createdPost.url'));
this.close();
return false;
},
destroyDraft: function() {
var key;
key = this.get('content.draftKey');
if (key) {
return Discourse.Draft.clear(key, this.get('content.draftSequence'));
}
},
cancel: function(success, fail) {
var _this = this;
if (this.get('content.hasMetaData') || ((this.get('content.reply') || "") !== (this.get('content.originalText') || ""))) {
bootbox.confirm(Em.String.i18n("post.abandon"), Em.String.i18n("no_value"), Em.String.i18n("yes_value"), function(result) {
if (result) {
_this.destroyDraft();
_this.close();
if (typeof success === "function") {
return success();
}
} else {
if (typeof fail === "function") {
return fail();
}
}
});
} else {
/* it is possible there is some sort of crazy draft with no body ... just give up on it
*/
this.destroyDraft();
this.close();
if (typeof success === "function") {
success();
}
}
},
click: function() {
if (this.get('content.composeState') === Discourse.Composer.DRAFT) {
return this.set('content.composeState', Discourse.Composer.OPEN);
}
},
shrink: function() {
if (this.get('content.reply') === this.get('content.originalText')) {
return this.close();
} else {
return this.collapse();
}
},
collapse: function() {
this.saveDraft();
return this.set('content.composeState', Discourse.Composer.DRAFT);
},
close: function() {
this.set('content', null);
return this.set('view.content', null);
},
closeIfCollapsed: function() {
if (this.get('content.composeState') === Discourse.Composer.DRAFT) {
return this.close();
}
},
closeAutocomplete: function() {
return jQuery('#wmd-input').autocomplete({
cancel: true
});
},
/* Toggle the reply view
*/
toggle: function() {
this.closeAutocomplete();
switch (this.get('content.composeState')) {
case Discourse.Composer.OPEN:
if (this.blank('content.reply') && this.blank('content.title')) {
this.close();
} else {
this.shrink();
}
break;
case Discourse.Composer.DRAFT:
this.set('content.composeState', Discourse.Composer.OPEN);
break;
case Discourse.Composer.SAVING:
this.close();
}
return false;
},
/* ESC key hit
*/
hitEsc: function() {
if (this.get('content.composeState') === Discourse.Composer.OPEN) {
return this.shrink();
}
},
showOptions: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.ArchetypeOptionsModalView.create({
archetype: this.get('content.archetype'),
metaData: this.get('content.metaData')
})) : void 0;
}
});
}).call(this);
// we need a draft sequence, without it drafts are bust
if (opts.draftSequence === void 0) {
Discourse.Draft.get(opts.draftKey).then(function(data) {
opts.draftSequence = data.draft_sequence;
opts.draft = data.draft;
return _this.open(opts);
});
return promise;
}
if (opts.draft) {
composer = Discourse.Composer.loadDraft(opts.draftKey, opts.draftSequence, opts.draft);
if (composer) {
composer.set('topic', opts.topic);
}
}
composer = composer || Discourse.Composer.open(opts);
this.set('content', composer);
this.set('view.content', composer);
promise.resolve();
return promise;
},
wouldLoseChanges: function() {
var composer;
composer = this.get('content');
return composer && composer.wouldLoseChanges();
},
// View a new reply we've made
viewNewReply: function() {
Discourse.routeTo(this.get('createdPost.url'));
this.close();
return false;
},
destroyDraft: function() {
var key;
key = this.get('content.draftKey');
if (key) {
return Discourse.Draft.clear(key, this.get('content.draftSequence'));
}
},
cancel: function(success, fail) {
var _this = this;
if (this.get('content.hasMetaData') || ((this.get('content.reply') || "") !== (this.get('content.originalText') || ""))) {
bootbox.confirm(Em.String.i18n("post.abandon"), Em.String.i18n("no_value"), Em.String.i18n("yes_value"), function(result) {
if (result) {
_this.destroyDraft();
_this.close();
if (typeof success === "function") {
return success();
}
} else {
if (typeof fail === "function") {
return fail();
}
}
});
} else {
// it is possible there is some sort of crazy draft with no body ... just give up on it
this.destroyDraft();
this.close();
if (typeof success === "function") {
success();
}
}
},
click: function() {
if (this.get('content.composeState') === Discourse.Composer.DRAFT) {
return this.set('content.composeState', Discourse.Composer.OPEN);
}
},
shrink: function() {
if (this.get('content.reply') === this.get('content.originalText')) {
return this.close();
} else {
return this.collapse();
}
},
collapse: function() {
this.saveDraft();
this.set('content.composeState', Discourse.Composer.DRAFT);
},
close: function() {
this.set('content', null);
this.set('view.content', null);
},
closeIfCollapsed: function() {
if (this.get('content.composeState') === Discourse.Composer.DRAFT) {
this.close();
}
},
closeAutocomplete: function() {
$('#wmd-input').autocomplete({ cancel: true });
},
// Toggle the reply view
toggle: function() {
this.closeAutocomplete();
switch (this.get('content.composeState')) {
case Discourse.Composer.OPEN:
if (this.blank('content.reply') && this.blank('content.title')) {
this.close();
} else {
this.shrink();
}
break;
case Discourse.Composer.DRAFT:
this.set('content.composeState', Discourse.Composer.OPEN);
break;
case Discourse.Composer.SAVING:
this.close();
}
return false;
},
// ESC key hit
hitEsc: function() {
if (this.get('content.composeState') === Discourse.Composer.OPEN) {
this.shrink();
}
},
showOptions: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.ArchetypeOptionsModalView.create({
archetype: this.get('content.archetype'),
metaData: this.get('content.metaData')
})) : void 0;
}
});

View file

@ -1,5 +1,12 @@
(function() {
/**
A base controller for Discourse that includes Presence support.
@class Controller
@extends Ember.Controller
@namespace Discourse
@uses Discourse.Presence
@module Discourse
**/
Discourse.Controller = Ember.Controller.extend(Discourse.Presence);
Discourse.Controller = Ember.Controller.extend(Discourse.Presence);
}).call(this);

View file

@ -1,15 +1,21 @@
(function() {
/**
This controller supports actions on the site header
@class HeaderController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.HeaderController = Discourse.Controller.extend({
topic: null,
showExtraInfo: false,
toggleStar: function() {
var topic = this.get('topic');
if (topic) topic.toggleStar();
return false;
}
});
Discourse.HeaderController = Ember.Controller.extend(Discourse.Presence, {
topic: null,
showExtraInfo: false,
toggleStar: function() {
var _ref;
if (_ref = this.get('topic')) {
_ref.toggleStar();
}
return false;
}
});
}).call(this);

View file

@ -1,34 +1,43 @@
(function() {
/**
This controller supports actions when listing categories
@class ListCategoriesController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.ListCategoriesController = Discourse.ObjectController.extend({
needs: ['modal'],
categoriesEven: (function() {
if (this.blank('categories')) {
return Em.A();
}
return this.get('categories').filter(function(item, index) {
return (index % 2) === 0;
});
}).property('categories.@each'),
categoriesOdd: (function() {
if (this.blank('categories')) {
return Em.A();
}
return this.get('categories').filter(function(item, index) {
return (index % 2) === 1;
});
}).property('categories.@each'),
editCategory: function(category) {
this.get('controllers.modal').show(Discourse.EditCategoryView.create({ category: category }));
return false;
},
canEdit: (function() {
var u;
u = Discourse.get('currentUser');
return u && u.admin;
}).property()
});
Discourse.ListCategoriesController = Ember.ObjectController.extend(Discourse.Presence, {
needs: ['modal'],
categoriesEven: (function() {
if (this.blank('categories')) {
return Em.A();
}
return this.get('categories').filter(function(item, index) {
return (index % 2) === 0;
});
}).property('categories.@each'),
categoriesOdd: (function() {
if (this.blank('categories')) {
return Em.A();
}
return this.get('categories').filter(function(item, index) {
return (index % 2) === 1;
});
}).property('categories.@each'),
editCategory: function(category) {
this.get('controllers.modal').show(Discourse.EditCategoryView.create({
category: category
}));
return false;
},
canEdit: (function() {
var u;
u = Discourse.get('currentUser');
return u && u.admin;
}).property()
});
}).call(this);

View file

@ -1,97 +1,105 @@
(function() {
/**
This controller supports actions when listing topics or categories
Discourse.ListController = Ember.Controller.extend(Discourse.Presence, {
currentUserBinding: 'Discourse.currentUser',
categoriesBinding: 'Discourse.site.categories',
categoryBinding: 'topicList.category',
canCreateCategory: false,
canCreateTopic: false,
needs: ['composer', 'modal', 'listTopics'],
availableNavItems: (function() {
var hasCategories, loggedOn, summary;
summary = this.get('filterSummary');
loggedOn = !!Discourse.get('currentUser');
hasCategories = !!this.get('categories');
return Discourse.SiteSettings.top_menu.split("|").map(function(i) {
return Discourse.NavItem.fromText(i, {
loggedOn: loggedOn,
hasCategories: hasCategories,
countSummary: summary
});
}).filter(function(i) {
return i !== null;
@class ListController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.ListController = Discourse.Controller.extend({
currentUserBinding: 'Discourse.currentUser',
categoriesBinding: 'Discourse.site.categories',
categoryBinding: 'topicList.category',
canCreateCategory: false,
canCreateTopic: false,
needs: ['composer', 'modal', 'listTopics'],
availableNavItems: (function() {
var hasCategories, loggedOn, summary;
summary = this.get('filterSummary');
loggedOn = !!Discourse.get('currentUser');
hasCategories = !!this.get('categories');
return Discourse.SiteSettings.top_menu.split("|").map(function(i) {
return Discourse.NavItem.fromText(i, {
loggedOn: loggedOn,
hasCategories: hasCategories,
countSummary: summary
});
}).property('filterSummary'),
load: function(filterMode) {
var current,
_this = this;
this.set('loading', true);
if (filterMode === 'categories') {
return Ember.Deferred.promise(function(deferred) {
return Discourse.CategoryList.list(filterMode).then(function(items) {
_this.set('loading', false);
_this.set('filterMode', filterMode);
_this.set('categoryMode', true);
return deferred.resolve(items);
});
});
} else {
current = (this.get('availableNavItems').filter(function(f) {
return f.name === filterMode;
}))[0];
if (!current) {
current = Discourse.NavItem.create({
name: filterMode
});
}
return Ember.Deferred.promise(function(deferred) {
return Discourse.TopicList.list(current).then(function(items) {
_this.set('filterSummary', items.filter_summary);
_this.set('filterMode', filterMode);
_this.set('loading', false);
return deferred.resolve(items);
});
});
}
},
/* Put in the appropriate page title based on our view
*/
}).filter(function(i) {
return i !== null;
});
}).property('filterSummary'),
updateTitle: (function() {
if (this.get('filterMode') === 'categories') {
return Discourse.set('title', Em.String.i18n('categories_list'));
} else {
if (this.present('category')) {
return Discourse.set('title', "" + (this.get('category.name').capitalize()) + " " + (Em.String.i18n('topic.list')));
} else {
return Discourse.set('title', Em.String.i18n('topic.list'));
}
}
}).observes('filterMode', 'category'),
/* Create topic button
*/
createTopic: function() {
var topicList;
topicList = this.get('controllers.listTopics.content');
if (!topicList) {
return;
}
return this.get('controllers.composer').open({
categoryName: this.get('category.name'),
action: Discourse.Composer.CREATE_TOPIC,
draftKey: topicList.get('draft_key'),
draftSequence: topicList.get('draft_sequence')
load: function(filterMode) {
var current,
_this = this;
this.set('loading', true);
if (filterMode === 'categories') {
return Ember.Deferred.promise(function(deferred) {
return Discourse.CategoryList.list(filterMode).then(function(items) {
_this.set('loading', false);
_this.set('filterMode', filterMode);
_this.set('categoryMode', true);
return deferred.resolve(items);
});
});
} else {
current = (this.get('availableNavItems').filter(function(f) {
return f.name === filterMode;
}))[0];
if (!current) {
current = Discourse.NavItem.create({
name: filterMode
});
}
return Ember.Deferred.promise(function(deferred) {
return Discourse.TopicList.list(current).then(function(items) {
_this.set('filterSummary', items.filter_summary);
_this.set('filterMode', filterMode);
_this.set('loading', false);
return deferred.resolve(items);
});
});
},
createCategory: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.EditCategoryView.create()) : void 0;
}
});
},
// Put in the appropriate page title based on our view
updateTitle: (function() {
if (this.get('filterMode') === 'categories') {
return Discourse.set('title', Em.String.i18n('categories_list'));
} else {
if (this.present('category')) {
return Discourse.set('title', "" + (this.get('category.name').capitalize()) + " " + (Em.String.i18n('topic.list')));
} else {
return Discourse.set('title', Em.String.i18n('topic.list'));
}
}
}).observes('filterMode', 'category'),
// Create topic button
createTopic: function() {
var topicList;
topicList = this.get('controllers.listTopics.content');
if (!topicList) {
return;
}
return this.get('controllers.composer').open({
categoryName: this.get('category.name'),
action: Discourse.Composer.CREATE_TOPIC,
draftKey: topicList.get('draft_key'),
draftSequence: topicList.get('draft_sequence')
});
},
createCategory: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.EditCategoryView.create()) : void 0;
}
});
Discourse.ListController.reopenClass({
filters: ['popular', 'favorited', 'read', 'unread', 'new', 'posted']
});
Discourse.ListController.reopenClass({
filters: ['popular', 'favorited', 'read', 'unread', 'new', 'posted']
});
}).call(this);

View file

@ -1,73 +1,73 @@
(function() {
/**
This controller supports actions when listing topics or categories
Discourse.ListTopicsController = Ember.ObjectController.extend({
needs: ['list', 'composer'],
/* If we're changing our channel
*/
@class ListTopicsController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.ListTopicsController = Discourse.ObjectController.extend({
needs: ['list', 'composer'],
// If we're changing our channel
previousChannel: null,
previousChannel: null,
popular: (function() {
return this.get('content.filter') === 'popular';
}).property('content.filter'),
filterModeChanged: (function() {
/* Unsubscribe from a previous channel if necessary
*/
popular: (function() {
return this.get('content.filter') === 'popular';
}).property('content.filter'),
var channel, filterMode, previousChannel,
_this = this;
if (previousChannel = this.get('previousChannel')) {
Discourse.MessageBus.unsubscribe("/" + previousChannel);
this.set('previousChannel', null);
}
filterMode = this.get('controllers.list.filterMode');
if (!filterMode) {
return;
}
channel = filterMode;
Discourse.MessageBus.subscribe("/" + channel, function(data) {
return _this.get('content').insert(data);
});
return this.set('previousChannel', channel);
}).observes('controllers.list.filterMode'),
draftLoaded: (function() {
var draft;
draft = this.get('content.draft');
if (draft) {
return this.get('controllers.composer').open({
draft: draft,
draftKey: this.get('content.draft_key'),
draftSequence: this.get('content.draft_sequence'),
ignoreIfChanged: true
});
}
}).observes('content.draft'),
/* Star a topic
*/
toggleStar: function(topic) {
topic.toggleStar();
return false;
},
createTopic: function() {
this.get('controllers.list').createTopic();
return false;
},
observer: (function() {
return this.set('filterMode', this.get('controllser.list.filterMode'));
}).observes('controller.list.filterMode'),
/* Show newly inserted topics
*/
showInserted: function(e) {
/* Move inserted into topics
*/
this.get('content.topics').unshiftObjects(this.get('content.inserted'));
/* Clear inserted
*/
this.set('content.inserted', Em.A());
return false;
filterModeChanged: (function() {
// Unsubscribe from a previous channel if necessary
var channel, filterMode, previousChannel,
_this = this;
if (previousChannel = this.get('previousChannel')) {
Discourse.MessageBus.unsubscribe("/" + previousChannel);
this.set('previousChannel', null);
}
});
filterMode = this.get('controllers.list.filterMode');
if (!filterMode) {
return;
}
channel = filterMode;
Discourse.MessageBus.subscribe("/" + channel, function(data) {
return _this.get('content').insert(data);
});
return this.set('previousChannel', channel);
}).observes('controllers.list.filterMode'),
draftLoaded: (function() {
var draft;
draft = this.get('content.draft');
if (draft) {
return this.get('controllers.composer').open({
draft: draft,
draftKey: this.get('content.draft_key'),
draftSequence: this.get('content.draft_sequence'),
ignoreIfChanged: true
});
}
}).observes('content.draft'),
// Star a topic
toggleStar: function(topic) {
topic.toggleStar();
},
createTopic: function() {
this.get('controllers.list').createTopic();
},
observer: (function() {
return this.set('filterMode', this.get('controllser.list.filterMode'));
}).observes('controller.list.filterMode'),
// Show newly inserted topics
showInserted: function(e) {
// Move inserted into topics
this.get('content.topics').unshiftObjects(this.get('content.inserted'));
// Clear inserted
this.set('content.inserted', Em.A());
return false;
}
});
}).call(this);

View file

@ -1,9 +1,15 @@
(function() {
/**
This controller supports actions related to showing modals
@class ModalController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.ModalController = Discourse.Controller.extend({
show: function(view) {
this.set('currentView', view);
}
});
Discourse.ModalController = Ember.Controller.extend(Discourse.Presence, {
show: function(view) {
return this.set('currentView', view);
}
});
}).call(this);

View file

@ -0,0 +1,12 @@
/**
A custom object controller for Discourse
@class ObjectController
@extends Ember.ObjectController
@namespace Discourse
@uses Discourse.Presence
@module Discourse
**/
Discourse.ObjectController = Ember.ObjectController.extend(Discourse.Presence);

View file

@ -1,158 +1,89 @@
(function() {
/**
This controller supports actions related to updating one's preferences
Discourse.PreferencesController = Ember.ObjectController.extend(Discourse.Presence, {
/* By default we haven't saved anything
*/
@class PreferencesController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesController = Discourse.ObjectController.extend({
// By default we haven't saved anything
saved: false,
saved: false,
saveDisabled: (function() {
if (this.get('saving')) {
return true;
}
if (this.blank('content.name')) {
return true;
}
if (this.blank('content.email')) {
return true;
}
return false;
}).property('saving', 'content.name', 'content.email'),
digestFrequencies: (function() {
var freqs;
freqs = Em.A();
freqs.addObject({
name: Em.String.i18n('user.email_digests.daily'),
value: 1
});
freqs.addObject({
name: Em.String.i18n('user.email_digests.weekly'),
value: 7
});
freqs.addObject({
name: Em.String.i18n('user.email_digests.bi_weekly'),
value: 14
});
return freqs;
}).property(),
autoTrackDurations: (function() {
var freqs;
freqs = Em.A();
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.never'),
value: -1
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.always'),
value: 0
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_seconds', {
count: 30
}),
value: 30000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 1
}),
value: 60000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 2
}),
value: 120000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 3
}),
value: 180000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 4
}),
value: 240000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 5
}),
value: 300000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 10
}),
value: 600000
});
return freqs;
}).property(),
considerNewTopicOptions: (function() {
var opts;
opts = Em.A();
opts.addObject({
name: Em.String.i18n('user.new_topic_duration.not_viewed'),
value: -1
});
opts.addObject({
name: Em.String.i18n('user.new_topic_duration.after_n_days', {
count: 1
}),
value: 60 * 24
});
opts.addObject({
name: Em.String.i18n('user.new_topic_duration.after_n_days', {
count: 2
}),
value: 60 * 48
});
opts.addObject({
name: Em.String.i18n('user.new_topic_duration.after_n_weeks', {
count: 1
}),
value: 7 * 60 * 24
});
opts.addObject({
name: Em.String.i18n('user.new_topic_duration.last_here'),
value: -2
});
return opts;
}).property(),
save: function() {
var _this = this;
this.set('saving', true);
this.set('saved', false);
/* Cook the bio for preview
*/
saveDisabled: (function() {
if (this.get('saving')) return true;
if (this.blank('content.name')) return true;
if (this.blank('content.email')) return true;
return false;
}).property('saving', 'content.name', 'content.email'),
return this.get('content').save(function(result) {
_this.set('saving', false);
if (result) {
_this.set('content.bio_cooked', Discourse.Utilities.cook(_this.get('content.bio_raw')));
return _this.set('saved', true);
} else {
return alert('failed');
}
digestFrequencies: (function() {
var freqs;
freqs = Em.A();
freqs.addObject({ name: Em.String.i18n('user.email_digests.daily'), value: 1 });
freqs.addObject({ name: Em.String.i18n('user.email_digests.weekly'), value: 7 });
freqs.addObject({ name: Em.String.i18n('user.email_digests.bi_weekly'), value: 14 });
return freqs;
}).property(),
autoTrackDurations: (function() {
var freqs;
freqs = Em.A();
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.never'), value: -1 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.always'), value: 0 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_seconds', { count: 30 }), value: 30000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 1 }), value: 60000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 2 }), value: 120000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 3 }), value: 180000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 4 }), value: 240000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 5 }), value: 300000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 10 }), value: 600000 });
return freqs;
}).property(),
considerNewTopicOptions: (function() {
var opts;
opts = Em.A();
opts.addObject({ name: Em.String.i18n('user.new_topic_duration.not_viewed'), value: -1 });
opts.addObject({ name: Em.String.i18n('user.new_topic_duration.after_n_days', { count: 1 }), value: 60 * 24 });
opts.addObject({ name: Em.String.i18n('user.new_topic_duration.after_n_days', { count: 2 }), value: 60 * 48 });
opts.addObject({ name: Em.String.i18n('user.new_topic_duration.after_n_weeks', { count: 1 }), value: 7 * 60 * 24 });
opts.addObject({ name: Em.String.i18n('user.new_topic_duration.last_here'), value: -2 });
return opts;
}).property(),
save: function() {
var _this = this;
this.set('saving', true);
this.set('saved', false);
// Cook the bio for preview
return this.get('content').save(function(result) {
_this.set('saving', false);
if (result) {
_this.set('content.bio_cooked', Discourse.Utilities.cook(_this.get('content.bio_raw')));
return _this.set('saved', true);
} else {
return alert('failed');
}
});
},
saveButtonText: (function() {
if (this.get('saving')) return Em.String.i18n('saving');
return Em.String.i18n('save');
}).property('saving'),
changePassword: function() {
var _this = this;
if (!this.get('passwordProgress')) {
this.set('passwordProgress', '(generating email)');
return this.get('content').changePassword(function(message) {
_this.set('changePasswordProgress', false);
return _this.set('passwordProgress', "(" + message + ")");
});
},
saveButtonText: (function() {
if (this.get('saving')) {
return Em.String.i18n('saving');
}
return Em.String.i18n('save');
}).property('saving'),
changePassword: function() {
var _this = this;
if (!this.get('passwordProgress')) {
this.set('passwordProgress', '(generating email)');
return this.get('content').changePassword(function(message) {
_this.set('changePasswordProgress', false);
return _this.set('passwordProgress', "(" + message + ")");
});
}
}
});
}
});
}).call(this);

View file

@ -1,48 +1,50 @@
(function() {
/**
This controller supports actions related to updating one's email address
@class PreferencesEmailController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesEmailController = Discourse.ObjectController.extend({
taken: false,
saving: false,
error: false,
success: false,
saveDisabled: (function() {
if (this.get('saving')) return true;
if (this.blank('newEmail')) return true;
if (this.get('taken')) return true;
if (this.get('unchanged')) return true;
}).property('newEmail', 'taken', 'unchanged', 'saving'),
unchanged: (function() {
return this.get('newEmail') === this.get('content.email');
}).property('newEmail', 'content.email'),
initializeEmail: (function() {
this.set('newEmail', this.get('content.email'));
}).observes('content.email'),
saveButtonText: (function() {
if (this.get('saving')) return Em.String.i18n("saving");
return Em.String.i18n("user.change_email.action");
}).property('saving'),
changeEmail: function() {
var _this = this;
this.set('saving', true);
return this.get('content').changeEmail(this.get('newEmail')).then(function() {
return _this.set('success', true);
}, function() {
/* Error
*/
_this.set('error', true);
return _this.set('saving', false);
});
}
});
Discourse.PreferencesEmailController = Ember.ObjectController.extend(Discourse.Presence, {
taken: false,
saving: false,
error: false,
success: false,
saveDisabled: (function() {
if (this.get('saving')) {
return true;
}
if (this.blank('newEmail')) {
return true;
}
if (this.get('taken')) {
return true;
}
if (this.get('unchanged')) {
return true;
}
}).property('newEmail', 'taken', 'unchanged', 'saving'),
unchanged: (function() {
return this.get('newEmail') === this.get('content.email');
}).property('newEmail', 'content.email'),
initializeEmail: (function() {
return this.set('newEmail', this.get('content.email'));
}).observes('content.email'),
saveButtonText: (function() {
if (this.get('saving')) {
return Em.String.i18n("saving");
}
return Em.String.i18n("user.change_email.action");
}).property('saving'),
changeEmail: function() {
var _this = this;
this.set('saving', true);
return this.get('content').changeEmail(this.get('newEmail')).then(function() {
return _this.set('success', true);
}, function() {
/* Error
*/
_this.set('error', true);
return _this.set('saving', false);
});
}
});
}).call(this);

View file

@ -1,71 +1,66 @@
(function() {
/**
This controller supports actions related to updating one's username
@class PreferencesUsernameController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesUsernameController = Discourse.ObjectController.extend({
taken: false,
saving: false,
error: false,
errorMessage: null,
saveDisabled: (function() {
if (this.get('saving')) return true;
if (this.blank('newUsername')) return true;
if (this.get('taken')) return true;
if (this.get('unchanged')) return true;
if (this.get('errorMessage')) return true;
return false;
}).property('newUsername', 'taken', 'errorMessage', 'unchanged', 'saving'),
unchanged: (function() {
return this.get('newUsername') === this.get('content.username');
}).property('newUsername', 'content.username'),
checkTaken: (function() {
var _this = this;
this.set('taken', false);
this.set('errorMessage', null);
if (this.blank('newUsername')) return;
if (this.get('unchanged')) return;
Discourse.User.checkUsername(this.get('newUsername')).then(function(result) {
if (result.errors) {
return _this.set('errorMessage', result.errors.join(' '));
} else if (result.available === false) {
return _this.set('taken', true);
}
});
}).observes('newUsername'),
saveButtonText: (function() {
if (this.get('saving')) return Em.String.i18n("saving");
return Em.String.i18n("user.change_username.action");
}).property('saving'),
changeUsername: function() {
var _this = this;
return bootbox.confirm(Em.String.i18n("user.change_username.confirm"), Em.String.i18n("no_value"), Em.String.i18n("yes_value"), function(result) {
if (result) {
_this.set('saving', true);
return _this.get('content').changeUsername(_this.get('newUsername')).then(function() {
window.location = "/users/" + (_this.get('newUsername').toLowerCase()) + "/preferences";
}, function() {
/* Error
*/
_this.set('error', true);
return _this.set('saving', false);
});
}
});
}
});
Discourse.PreferencesUsernameController = Ember.ObjectController.extend(Discourse.Presence, {
taken: false,
saving: false,
error: false,
errorMessage: null,
saveDisabled: (function() {
if (this.get('saving')) {
return true;
}
if (this.blank('newUsername')) {
return true;
}
if (this.get('taken')) {
return true;
}
if (this.get('unchanged')) {
return true;
}
if (this.get('errorMessage')) {
return true;
}
return false;
}).property('newUsername', 'taken', 'errorMessage', 'unchanged', 'saving'),
unchanged: (function() {
return this.get('newUsername') === this.get('content.username');
}).property('newUsername', 'content.username'),
checkTaken: (function() {
var _this = this;
this.set('taken', false);
this.set('errorMessage', null);
if (this.blank('newUsername')) {
return;
}
if (this.get('unchanged')) {
return;
}
return Discourse.User.checkUsername(this.get('newUsername')).then(function(result) {
if (result.errors) {
return _this.set('errorMessage', result.errors.join(' '));
} else if (result.available === false) {
return _this.set('taken', true);
}
});
}).observes('newUsername'),
saveButtonText: (function() {
if (this.get('saving')) {
return Em.String.i18n("saving");
}
return Em.String.i18n("user.change_username.action");
}).property('saving'),
changeUsername: function() {
var _this = this;
return bootbox.confirm(Em.String.i18n("user.change_username.confirm"), Em.String.i18n("no_value"), Em.String.i18n("yes_value"), function(result) {
if (result) {
_this.set('saving', true);
return _this.get('content').changeUsername(_this.get('newUsername')).then(function() {
window.location = "/users/" + (_this.get('newUsername').toLowerCase()) + "/preferences";
}, function() {
/* Error
*/
_this.set('error', true);
return _this.set('saving', false);
});
}
});
}
});
}).call(this);

View file

@ -1,86 +1,86 @@
(function() {
/**
This controller supports the pop up quote button
Discourse.QuoteButtonController = Discourse.Controller.extend({
needs: ['topic', 'composer'],
started: null,
/* If the buffer is cleared, clear out other state (post)
*/
@class QuoteButtonController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.QuoteButtonController = Discourse.Controller.extend({
needs: ['topic', 'composer'],
started: null,
bufferChanged: (function() {
if (this.blank('buffer')) {
return this.set('post', null);
}
}).observes('buffer'),
mouseDown: function(e) {
this.started = [e.pageX, e.pageY];
},
mouseUp: function(e) {
if (this.started[1] > e.pageY) {
this.started = [e.pageX, e.pageY];
}
},
selectText: function(e) {
var $quoteButton, left, selectedText, top;
if (!Discourse.get('currentUser')) {
return;
}
if (!this.get('controllers.topic.content.can_create_post')) {
return;
}
selectedText = Discourse.Utilities.selectedText();
if (this.get('buffer') === selectedText) {
return;
}
if (this.get('lastSelected') === selectedText) {
return;
}
this.set('post', e.context);
this.set('buffer', selectedText);
top = e.pageY + 5;
left = e.pageX + 5;
$quoteButton = jQuery('.quote-button');
if (this.started) {
top = this.started[1] - 50;
left = ((left - this.started[0]) / 2) + this.started[0] - ($quoteButton.width() / 2);
}
$quoteButton.css({
top: top,
left: left
});
this.started = null;
return false;
},
quoteText: function(e) {
var buffer, composerController, composerOpts, composerPost, post, quotedText,
_this = this;
e.stopPropagation();
post = this.get('post');
composerController = this.get('controllers.composer');
composerOpts = {
post: post,
action: Discourse.Composer.REPLY,
draftKey: this.get('post.topic.draft_key')
};
/* If the composer is associated with a different post, we don't change it.
*/
if (composerPost = composerController.get('content.post')) {
if (composerPost.get('id') !== this.get('post.id')) {
composerOpts.post = composerPost;
}
}
buffer = this.get('buffer');
quotedText = Discourse.BBCode.buildQuoteBBCode(post, buffer);
if (composerController.wouldLoseChanges()) {
composerController.appendText(quotedText);
} else {
composerController.open(composerOpts).then(function() {
return composerController.appendText(quotedText);
});
}
this.set('buffer', '');
return false;
// If the buffer is cleared, clear out other state (post)
bufferChanged: (function() {
if (this.blank('buffer')) {
return this.set('post', null);
}
});
}).observes('buffer'),
}).call(this);
mouseDown: function(e) {
this.started = [e.pageX, e.pageY];
},
mouseUp: function(e) {
if (this.started[1] > e.pageY) {
this.started = [e.pageX, e.pageY];
}
},
selectText: function(e) {
var $quoteButton, left, selectedText, top;
if (!Discourse.get('currentUser')) return;
if (!this.get('controllers.topic.content.can_create_post')) return;
selectedText = Discourse.Utilities.selectedText();
if (this.get('buffer') === selectedText) return;
if (this.get('lastSelected') === selectedText) return;
this.set('post', e.context);
this.set('buffer', selectedText);
top = e.pageY + 5;
left = e.pageX + 5;
$quoteButton = $('.quote-button');
if (this.started) {
top = this.started[1] - 50;
left = ((left - this.started[0]) / 2) + this.started[0] - ($quoteButton.width() / 2);
}
$quoteButton.css({
top: top,
left: left
});
this.started = null;
return false;
},
quoteText: function(e) {
var buffer, composerController, composerOpts, composerPost, post, quotedText,
_this = this;
e.stopPropagation();
post = this.get('post');
composerController = this.get('controllers.composer');
composerOpts = {
post: post,
action: Discourse.Composer.REPLY,
draftKey: this.get('post.topic.draft_key')
};
// If the composer is associated with a different post, we don't change it.
if (composerPost = composerController.get('content.post')) {
if (composerPost.get('id') !== this.get('post.id')) {
composerOpts.post = composerPost;
}
}
buffer = this.get('buffer');
quotedText = Discourse.BBCode.buildQuoteBBCode(post, buffer);
if (composerController.wouldLoseChanges()) {
composerController.appendText(quotedText);
} else {
composerController.open(composerOpts).then(function() {
return composerController.appendText(quotedText);
});
}
this.set('buffer', '');
return false;
}
});

View file

@ -1,29 +1,33 @@
(function() {
/**
This controller supports the "share" link controls
Discourse.ShareController = Ember.Controller.extend({
/* When the user clicks the post number, we pop up a share box
*/
@class ShareController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.ShareController = Discourse.Controller.extend({
shareLink: function(e, url) {
var x;
x = e.pageX - 150;
if (x < 25) {
x = 25;
}
jQuery('#share-link').css({
left: "" + x + "px",
top: "" + (e.pageY - 100) + "px"
});
this.set('link', url);
return false;
},
/* Close the share controller
*/
close: function() {
this.set('link', '');
return false;
// When the user clicks the post number, we pop up a share box
shareLink: function(e, url) {
var x;
x = e.pageX - 150;
if (x < 25) {
x = 25;
}
});
$('#share-link').css({
left: "" + x + "px",
top: "" + (e.pageY - 100) + "px"
});
this.set('link', url);
return false;
},
// Close the share controller
close: function() {
this.set('link', '');
return false;
}
});
}).call(this);

View file

@ -1,32 +1,38 @@
(function() {
/**
This controller supports displaying static content.
Discourse.StaticController = Ember.Controller.extend({
content: null,
loadPath: function(path) {
var $preloaded, text,
_this = this;
this.set('content', null);
/* Load from <noscript> if we have it.
*/
@class StaticController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.StaticController = Discourse.Controller.extend({
content: null,
$preloaded = jQuery("noscript[data-path=\"" + path + "\"]");
if ($preloaded.length) {
text = $preloaded.text();
text = text.replace(/<header[\s\S]*<\/header\>/, '');
return this.set('content', text);
} else {
return jQuery.ajax({
url: "" + path + ".json",
success: function(result) {
return _this.set('content', result);
}
});
}
loadPath: function(path) {
var $preloaded, text,
_this = this;
this.set('content', null);
// Load from <noscript> if we have it.
$preloaded = $("noscript[data-path=\"" + path + "\"]");
if ($preloaded.length) {
text = $preloaded.text();
text = text.replace(/<header[\s\S]*<\/header\>/, '');
return this.set('content', text);
} else {
return jQuery.ajax({
url: "" + path + ".json",
success: function(result) {
return _this.set('content', result);
}
});
}
});
}
});
Discourse.StaticController.reopenClass({
pages: ['faq', 'tos', 'privacy']
});
Discourse.StaticController.reopenClass({
pages: ['faq', 'tos', 'privacy']
});
}).call(this);

View file

@ -1,13 +1,20 @@
(function() {
/**
This controller supports the admin menu on topics
Discourse.TopicAdminMenuController = Ember.ObjectController.extend({
visible: false,
show: function() {
return this.set('visible', true);
},
hide: function() {
return this.set('visible', false);
}
});
@class TopicAdminMenuController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.TopicAdminMenuController = Discourse.ObjectController.extend({
visible: false,
}).call(this);
show: function() {
this.set('visible', true);
},
hide: function() {
this.set('visible', false);
}
});

View file

@ -1,419 +1,414 @@
(function() {
/**
This controller supports all actions related to a topic
Discourse.TopicController = Ember.ObjectController.extend(Discourse.Presence, {
/* A list of usernames we want to filter by
*/
@class TopicController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.TopicController = Discourse.ObjectController.extend({
userFilters: new Em.Set(),
multiSelect: false,
bestOf: false,
showExtraHeaderInfo: false,
needs: ['header', 'modal', 'composer', 'quoteButton'],
userFilters: new Em.Set(),
multiSelect: false,
bestOf: false,
showExtraHeaderInfo: false,
needs: ['header', 'modal', 'composer', 'quoteButton'],
filter: (function() {
if (this.get('bestOf') === true) {
return 'best_of';
}
if (this.get('userFilters').length > 0) {
return 'user';
}
return null;
}).property('userFilters.[]', 'bestOf'),
filterDesc: (function() {
var filter;
if (!(filter = this.get('filter'))) {
return null;
}
return Em.String.i18n("topic.filters." + filter);
}).property('filter'),
selectedPosts: (function() {
var posts;
if (!(posts = this.get('content.posts'))) {
return null;
}
return posts.filterProperty('selected');
}).property('content.posts.@each.selected'),
selectedCount: (function() {
if (!this.get('selectedPosts')) {
return 0;
}
return this.get('selectedPosts').length;
}).property('selectedPosts'),
canMoveSelected: (function() {
if (!this.get('content.can_move_posts')) {
filter: (function() {
if (this.get('bestOf') === true) return 'best_of';
if (this.get('userFilters').length > 0) return 'user';
return null;
}).property('userFilters.[]', 'bestOf'),
filterDesc: (function() {
var filter;
if (!(filter = this.get('filter'))) return null;
return Em.String.i18n("topic.filters." + filter);
}).property('filter'),
selectedPosts: (function() {
var posts;
if (!(posts = this.get('content.posts'))) return null;
return posts.filterProperty('selected');
}).property('content.posts.@each.selected'),
selectedCount: (function() {
if (!this.get('selectedPosts')) return 0;
return this.get('selectedPosts').length;
}).property('selectedPosts'),
canMoveSelected: (function() {
if (!this.get('content.can_move_posts')) return false;
// For now, we can move it if we can delete it since the posts need to be deleted.
return this.get('canDeleteSelected');
}).property('canDeleteSelected'),
showExtraHeaderInfoChanged: (function() {
this.set('controllers.header.showExtraInfo', this.get('showExtraHeaderInfo'));
}).observes('showExtraHeaderInfo'),
canDeleteSelected: (function() {
var canDelete, selectedPosts;
selectedPosts = this.get('selectedPosts');
if (!(selectedPosts && selectedPosts.length > 0)) return false;
canDelete = true;
selectedPosts.each(function(p) {
if (!p.get('can_delete')) {
canDelete = false;
return false;
}
/* For now, we can move it if we can delete it since the posts
*/
});
return canDelete;
}).property('selectedPosts'),
/* need to be deleted.
*/
return this.get('canDeleteSelected');
}).property('canDeleteSelected'),
showExtraHeaderInfoChanged: (function() {
return this.set('controllers.header.showExtraInfo', this.get('showExtraHeaderInfo'));
}).observes('showExtraHeaderInfo'),
canDeleteSelected: (function() {
var canDelete, selectedPosts;
selectedPosts = this.get('selectedPosts');
if (!(selectedPosts && selectedPosts.length > 0)) {
return false;
}
canDelete = true;
selectedPosts.each(function(p) {
if (!p.get('can_delete')) {
canDelete = false;
return false;
}
});
return canDelete;
}).property('selectedPosts'),
multiSelectChanged: (function() {
/* Deselect all posts when multi select is turned off
*/
var posts;
if (!this.get('multiSelect')) {
if (posts = this.get('content.posts')) {
return posts.forEach(function(p) {
return p.set('selected', false);
});
}
}
}).observes('multiSelect'),
hideProgress: (function() {
if (!this.get('content.loaded')) {
return true;
}
if (!this.get('currentPost')) {
return true;
}
if (this.get('content.highest_post_number') < 2) {
return true;
}
return this.present('filter');
}).property('filter', 'content.loaded', 'currentPost'),
selectPost: function(post) {
return post.toggleProperty('selected');
},
toggleMultiSelect: function() {
return this.toggleProperty('multiSelect');
},
moveSelected: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.MoveSelectedView.create({
topic: this.get('content'),
selectedPosts: this.get('selectedPosts')
})) : void 0;
},
deleteSelected: function() {
var _this = this;
return bootbox.confirm(Em.String.i18n("post.delete.confirm", {
count: this.get('selectedCount')
}), function(result) {
if (result) {
Discourse.Post.deleteMany(_this.get('selectedPosts'));
return _this.get('content.posts').removeObjects(_this.get('selectedPosts'));
}
});
},
jumpTop: function() {
return Discourse.routeTo(this.get('content.url'));
},
jumpBottom: function() {
return Discourse.routeTo(this.get('content.lastPostUrl'));
},
cancelFilter: function() {
this.set('bestOf', false);
return this.get('userFilters').clear();
},
replyAsNewTopic: function(post) {
var composerController, postLink, postUrl, promise;
composerController = this.get('controllers.composer');
/*TODO shut down topic draft cleanly if it exists ...
*/
promise = composerController.open({
action: Discourse.Composer.CREATE_TOPIC,
draftKey: Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY
});
postUrl = "" + location.protocol + "//" + location.host + (post.get('url'));
postLink = "[" + (this.get('title')) + "](" + postUrl + ")";
return promise.then(function() {
return Discourse.Post.loadQuote(post.get('id')).then(function(q) {
return composerController.appendText("" + (Em.String.i18n("post.continue_discussion", {
postLink: postLink
})) + "\n\n" + q);
multiSelectChanged: (function() {
// Deselect all posts when multi select is turned off
var posts;
if (!this.get('multiSelect')) {
if (posts = this.get('content.posts')) {
return posts.forEach(function(p) {
return p.set('selected', false);
});
});
},
/* Topic related
*/
}
}
}).observes('multiSelect'),
reply: function() {
var composerController;
composerController = this.get('controllers.composer');
return composerController.open({
topic: this.get('content'),
action: Discourse.Composer.REPLY,
draftKey: this.get('content.draft_key'),
draftSequence: this.get('content.draft_sequence')
hideProgress: (function() {
if (!this.get('content.loaded')) return true;
if (!this.get('currentPost')) return true;
if (this.get('content.highest_post_number') < 2) return true;
return this.present('filter');
}).property('filter', 'content.loaded', 'currentPost'),
selectPost: function(post) {
post.toggleProperty('selected');
},
toggleMultiSelect: function() {
this.toggleProperty('multiSelect');
},
moveSelected: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.MoveSelectedView.create({
topic: this.get('content'),
selectedPosts: this.get('selectedPosts')
})) : void 0;
},
deleteSelected: function() {
var _this = this;
return bootbox.confirm(Em.String.i18n("post.delete.confirm", {
count: this.get('selectedCount')
}), function(result) {
if (result) {
Discourse.Post.deleteMany(_this.get('selectedPosts'));
return _this.get('content.posts').removeObjects(_this.get('selectedPosts'));
}
});
},
jumpTop: function() {
Discourse.routeTo(this.get('content.url'));
},
jumpBottom: function() {
Discourse.routeTo(this.get('content.lastPostUrl'));
},
cancelFilter: function() {
this.set('bestOf', false);
this.get('userFilters').clear();
},
replyAsNewTopic: function(post) {
var composerController, postLink, postUrl, promise;
composerController = this.get('controllers.composer');
// TODO shut down topic draft cleanly if it exists ...
promise = composerController.open({
action: Discourse.Composer.CREATE_TOPIC,
draftKey: Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY
});
postUrl = "" + location.protocol + "//" + location.host + (post.get('url'));
postLink = "[" + (this.get('title')) + "](" + postUrl + ")";
return promise.then(function() {
return Discourse.Post.loadQuote(post.get('id')).then(function(q) {
return composerController.appendText("" + (Em.String.i18n("post.continue_discussion", {
postLink: postLink
})) + "\n\n" + q);
});
},
toggleParticipant: function(user) {
var userFilters, username;
this.set('bestOf', false);
username = Em.get(user, 'username');
userFilters = this.get('userFilters');
if (userFilters.contains(username)) {
userFilters.remove(username);
} else {
userFilters.add(username);
}
});
},
// Topic related
reply: function() {
var composerController;
composerController = this.get('controllers.composer');
return composerController.open({
topic: this.get('content'),
action: Discourse.Composer.REPLY,
draftKey: this.get('content.draft_key'),
draftSequence: this.get('content.draft_sequence')
});
},
toggleParticipant: function(user) {
var userFilters, username;
this.set('bestOf', false);
username = Em.get(user, 'username');
userFilters = this.get('userFilters');
if (userFilters.contains(username)) {
userFilters.remove(username);
} else {
userFilters.add(username);
}
return false;
},
enableBestOf: function(e) {
this.set('bestOf', true);
this.get('userFilters').clear();
return false;
},
showBestOf: (function() {
if (this.get('bestOf') === true) {
return false;
},
enableBestOf: function(e) {
this.set('bestOf', true);
this.get('userFilters').clear();
return false;
},
showBestOf: (function() {
if (this.get('bestOf') === true) {
return false;
}
return this.get('content.has_best_of') === true;
}).property('bestOf', 'content.has_best_of'),
postFilters: (function() {
if (this.get('bestOf') === true) {
return {
bestOf: true
};
}
}
return this.get('content.has_best_of') === true;
}).property('bestOf', 'content.has_best_of'),
postFilters: (function() {
if (this.get('bestOf') === true) {
return {
userFilters: this.get('userFilters')
bestOf: true
};
}).property('userFilters.[]', 'bestOf'),
reloadTopics: (function() {
var posts, topic,
_this = this;
topic = this.get('content');
if (!topic) {
}
return {
userFilters: this.get('userFilters')
};
}).property('userFilters.[]', 'bestOf'),
reloadTopics: (function() {
var posts, topic,
_this = this;
topic = this.get('content');
if (!topic) return;
posts = topic.get('posts');
if (!posts) return;
posts.clear();
this.set('content.loaded', false);
return Discourse.Topic.find(this.get('content.id'), this.get('postFilters')).then(function(result) {
var first;
first = result.posts.first();
if (first) {
_this.set('currentPost', first.post_number);
}
$('#topic-progress .solid').data('progress', false);
result.posts.each(function(p) {
return posts.pushObject(Discourse.Post.create(p, topic));
});
return _this.set('content.loaded', true);
});
}).observes('postFilters'),
deleteTopic: function(e) {
var _this = this;
this.unsubscribe();
this.get('content')["delete"](function() {
_this.set('message', "The topic has been deleted");
_this.set('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');
},
startTracking: function() {
var screenTrack;
screenTrack = Discourse.ScreenTrack.create({ topic_id: this.get('content.id') });
screenTrack.start();
return this.set('content.screenTrack', screenTrack);
},
stopTracking: function() {
var screenTrack = this.get('content.screenTrack');
if (screenTrack) screenTrack.stop();
this.set('content.screenTrack', null);
},
// Toggle the star on the topic
toggleStar: function(e) {
this.get('content').toggleStar();
},
// Receive notifications for this topic
subscribe: function() {
var bus,
_this = this;
bus = Discourse.MessageBus;
// there is a condition where the view never calls unsubscribe, navigate to a topic from a topic
bus.unsubscribe('/topic/*');
return bus.subscribe("/topic/" + (this.get('content.id')), function(data) {
var posts, topic;
topic = _this.get('content');
if (data.notification_level_change) {
topic.set('notification_level', data.notification_level_change);
topic.set('notifications_reason_id', data.notifications_reason_id);
return;
}
posts = topic.get('posts');
if (!posts) {
if (posts.some(function(p) {
return p.get('post_number') === data.post_number;
})) {
return;
}
posts.clear();
this.set('content.loaded', false);
return Discourse.Topic.find(this.get('content.id'), this.get('postFilters')).then(function(result) {
var first;
first = result.posts.first();
if (first) {
_this.set('currentPost', first.post_number);
}
jQuery('#topic-progress .solid').data('progress', false);
result.posts.each(function(p) {
return posts.pushObject(Discourse.Post.create(p, topic));
});
return _this.set('content.loaded', true);
});
}).observes('postFilters'),
deleteTopic: function(e) {
var _this = this;
this.unsubscribe();
return this.get('content')["delete"](function() {
_this.set('message', "The topic has been deleted");
return _this.set('loaded', false);
});
},
toggleVisibility: function() {
return this.get('content').toggleStatus('visible');
},
toggleClosed: function() {
return this.get('content').toggleStatus('closed');
},
togglePinned: function() {
return this.get('content').toggleStatus('pinned');
},
toggleArchived: function() {
return this.get('content').toggleStatus('archived');
},
convertToRegular: function() {
return this.get('content').convertArchetype('regular');
},
startTracking: function() {
var screenTrack;
screenTrack = Discourse.ScreenTrack.create({
topic_id: this.get('content.id')
});
screenTrack.start();
return this.set('content.screenTrack', screenTrack);
},
stopTracking: function() {
var _ref;
if (_ref = this.get('content.screenTrack')) {
_ref.stop();
}
return this.set('content.screenTrack', null);
},
/* Toggle the star on the topic
*/
topic.set('posts_count', topic.get('posts_count') + 1);
topic.set('highest_post_number', data.post_number);
topic.set('last_poster', data.user);
topic.set('last_posted_at', data.created_at);
return Discourse.notifyTitle();
});
},
toggleStar: function(e) {
return this.get('content').toggleStar();
},
/* Receive notifications for this topic
*/
unsubscribe: function() {
var bus, topicId;
topicId = this.get('content.id');
if (!topicId) {
return;
}
bus = Discourse.MessageBus;
return bus.unsubscribe("/topic/" + topicId);
},
subscribe: function() {
var bus,
_this = this;
bus = Discourse.MessageBus;
/* there is a condition where the view never calls unsubscribe, navigate to a topic from a topic
*/
bus.unsubscribe('/topic/*');
return bus.subscribe("/topic/" + (this.get('content.id')), function(data) {
var posts, topic;
topic = _this.get('content');
if (data.notification_level_change) {
topic.set('notification_level', data.notification_level_change);
topic.set('notifications_reason_id', data.notifications_reason_id);
return;
}
posts = topic.get('posts');
if (posts.some(function(p) {
return p.get('post_number') === data.post_number;
})) {
return;
}
topic.set('posts_count', topic.get('posts_count') + 1);
topic.set('highest_post_number', data.post_number);
topic.set('last_poster', data.user);
topic.set('last_posted_at', data.created_at);
return Discourse.notifyTitle();
});
},
unsubscribe: function() {
var bus, topicId;
topicId = this.get('content.id');
if (!topicId) {
return;
}
bus = Discourse.MessageBus;
return bus.unsubscribe("/topic/" + topicId);
},
/* Post related methods
*/
replyToPost: function(post) {
var composerController, promise, quoteController, quotedText,
_this = this;
composerController = this.get('controllers.composer');
quoteController = this.get('controllers.quoteButton');
quotedText = Discourse.BBCode.buildQuoteBBCode(quoteController.get('post'), quoteController.get('buffer'));
quoteController.set('buffer', '');
if (composerController.get('content.topic.id') === post.get('topic.id') && composerController.get('content.action') === Discourse.Composer.REPLY) {
composerController.set('content.post', post);
composerController.set('content.composeState', Discourse.Composer.OPEN);
composerController.appendText(quotedText);
} else {
promise = composerController.open({
post: post,
action: Discourse.Composer.REPLY,
draftKey: post.get('topic.draft_key'),
draftSequence: post.get('topic.draft_sequence')
});
promise.then(function() {
return composerController.appendText(quotedText);
});
}
return false;
},
/* Edits a post
*/
editPost: function(post) {
return this.get('controllers.composer').open({
// Post related methods
replyToPost: function(post) {
var composerController, promise, quoteController, quotedText,
_this = this;
composerController = this.get('controllers.composer');
quoteController = this.get('controllers.quoteButton');
quotedText = Discourse.BBCode.buildQuoteBBCode(quoteController.get('post'), quoteController.get('buffer'));
quoteController.set('buffer', '');
if (composerController.get('content.topic.id') === post.get('topic.id') && composerController.get('content.action') === Discourse.Composer.REPLY) {
composerController.set('content.post', post);
composerController.set('content.composeState', Discourse.Composer.OPEN);
composerController.appendText(quotedText);
} else {
promise = composerController.open({
post: post,
action: Discourse.Composer.EDIT,
action: Discourse.Composer.REPLY,
draftKey: post.get('topic.draft_key'),
draftSequence: post.get('topic.draft_sequence')
});
},
toggleBookmark: function(post) {
if (!Discourse.get('currentUser')) {
alert(Em.String.i18n("bookmarks.not_bookmarked"));
return;
}
post.toggleProperty('bookmarked');
return false;
},
clearFlags: function(actionType) {
return actionType.clearFlags();
},
/* Who acted on a particular post / action type
*/
whoActed: function(actionType) {
actionType.loadUsers();
return false;
},
showPrivateInviteModal: function() {
var modal, _ref;
modal = Discourse.InvitePrivateModalView.create({
topic: this.get('content')
});
if (_ref = this.get('controllers.modal')) {
_ref.show(modal);
}
return false;
},
showInviteModal: function() {
var _ref;
if (_ref = this.get('controllers.modal')) {
_ref.show(Discourse.InviteModalView.create({
topic: this.get('content')
}));
}
return false;
},
// Clicked the flag button
showFlags: function(post) {
var flagView, _ref;
flagView = Discourse.FlagView.create({
post: post,
controller: this
});
return (_ref = this.get('controllers.modal')) ? _ref.show(flagView) : void 0;
},
showHistory: function(post) {
var view, _ref;
view = Discourse.HistoryView.create({
originalPost: post
});
if (_ref = this.get('controllers.modal')) {
_ref.show(view);
}
return false;
},
recoverPost: function(post) {
post.set('deleted_at', null);
return post.recover();
},
deletePost: function(post) {
/* Moderators can delete posts. Regular users can only create a deleted at message.
*/
if (Discourse.get('currentUser.moderator')) {
post.set('deleted_at', new Date());
} else {
post.set('cooked', Discourse.Utilities.cook(Em.String.i18n("post.deleted_by_author")));
post.set('can_delete', false);
post.set('version', post.get('version') + 1);
}
return post["delete"]();
promise.then(function() { return composerController.appendText(quotedText); });
}
});
return false;
},
// Edits a post
editPost: function(post) {
return this.get('controllers.composer').open({
post: post,
action: Discourse.Composer.EDIT,
draftKey: post.get('topic.draft_key'),
draftSequence: post.get('topic.draft_sequence')
});
},
toggleBookmark: function(post) {
if (!Discourse.get('currentUser')) {
alert(Em.String.i18n("bookmarks.not_bookmarked"));
return;
}
post.toggleProperty('bookmarked');
return false;
},
clearFlags: function(actionType) {
actionType.clearFlags();
},
// Who acted on a particular post / action type
whoActed: function(actionType) {
actionType.loadUsers();
return false;
},
showPrivateInviteModal: function() {
var modal, _ref;
modal = Discourse.InvitePrivateModalView.create({
topic: this.get('content')
});
if (_ref = this.get('controllers.modal')) {
_ref.show(modal);
}
return false;
},
showInviteModal: function() {
var _ref;
if (_ref = this.get('controllers.modal')) {
_ref.show(Discourse.InviteModalView.create({
topic: this.get('content')
}));
}
return false;
},
// Clicked the flag button
showFlags: function(post) {
var flagView, _ref;
flagView = Discourse.FlagView.create({
post: post,
controller: this
});
return (_ref = this.get('controllers.modal')) ? _ref.show(flagView) : void 0;
},
showHistory: function(post) {
var view, _ref;
view = Discourse.HistoryView.create({ originalPost: post });
if (_ref = this.get('controllers.modal')) {
_ref.show(view);
}
return false;
},
recoverPost: function(post) {
post.set('deleted_at', null);
return post.recover();
},
deletePost: function(post) {
// Moderators can delete posts. Regular users can only create a deleted at message.
if (Discourse.get('currentUser.moderator')) {
post.set('deleted_at', new Date());
} else {
post.set('cooked', Discourse.Utilities.cook(Em.String.i18n("post.deleted_by_author")));
post.set('can_delete', false);
post.set('version', post.get('version') + 1);
}
return post["delete"]();
}
});
}).call(this);

View file

@ -1,20 +1,28 @@
(function() {
/**
This controller supports all actions on a user's activity stream
Discourse.UserActivityController = Ember.ObjectController.extend({
needs: ['composer'],
kickOffPrivateMessage: (function() {
if (this.get('content.openPrivateMessage')) {
return this.composePrivateMessage();
}
}).observes('content.openPrivateMessage'),
composePrivateMessage: function() {
return this.get('controllers.composer').open({
action: Discourse.Composer.PRIVATE_MESSAGE,
usernames: this.get('content').username,
archetypeId: 'private_message',
draftKey: 'new_private_message'
});
@class UserActivityController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.UserActivityController = Discourse.ObjectController.extend({
needs: ['composer'],
kickOffPrivateMessage: (function() {
if (this.get('content.openPrivateMessage')) {
this.composePrivateMessage();
}
});
}).observes('content.openPrivateMessage'),
composePrivateMessage: function() {
return this.get('controllers.composer').open({
action: Discourse.Composer.PRIVATE_MESSAGE,
usernames: this.get('content').username,
archetypeId: 'private_message',
draftKey: 'new_private_message'
});
}
});
}).call(this);

View file

@ -1,12 +1,21 @@
(function() {
/**
This controller handles general user actions
@class UserController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.UserController = Discourse.ObjectController.extend({
viewingSelf: (function() {
return this.get('content.username') === Discourse.get('currentUser.username');
}).property('content.username', 'Discourse.currentUser.username'),
canSeePrivateMessages: (function() {
return this.get('viewingSelf') || Discourse.get('currentUser.admin');
}).property('viewingSelf', 'Discourse.currentUser')
});
Discourse.UserController = Ember.ObjectController.extend({
viewingSelf: (function() {
return this.get('content.username') === Discourse.get('currentUser.username');
}).property('content.username', 'Discourse.currentUser.username'),
canSeePrivateMessages: (function() {
return this.get('viewingSelf') || Discourse.get('currentUser.admin');
}).property('viewingSelf', 'Discourse.currentUser')
});
}).call(this);

View file

@ -1,10 +1,16 @@
(function() {
/**
This controller handles actions related to a user's invitations
@class UserInvitedController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.UserInvitedController = Discourse.ObjectController.extend({
rescind: function(invite) {
invite.rescind();
return false;
}
});
Discourse.UserInvitedController = Ember.ObjectController.extend({
rescind: function(invite) {
invite.rescind();
return false;
}
});
}).call(this);

View file

@ -1,18 +1,25 @@
(function() {
/**
This controller handles actions related to a user's private messages.
Discourse.UserPrivateMessagesController = Ember.ObjectController.extend({
editPreferences: function() {
return Discourse.routeTo("/users/" + (this.get('content.username_lower')) + "/preferences");
},
composePrivateMessage: function() {
var composerController;
composerController = Discourse.get('router.composerController');
return composerController.open({
action: Discourse.Composer.PRIVATE_MESSAGE,
archetypeId: 'private_message',
draftKey: 'new_private_message'
});
}
});
@class UserPrivateMessagesController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.UserPrivateMessagesController = Discourse.ObjectController.extend({
}).call(this);
editPreferences: function() {
return Discourse.routeTo("/users/" + (this.get('content.username_lower')) + "/preferences");
},
composePrivateMessage: function() {
var composerController;
composerController = Discourse.get('router.composerController');
return composerController.open({
action: Discourse.Composer.PRIVATE_MESSAGE,
archetypeId: 'private_message',
draftKey: 'new_private_message'
});
}
});

View file

@ -1,190 +1,260 @@
/*global humaneDate:true */
(function() {
/**
Breaks up a long string
Handlebars.registerHelper('breakUp', function(property, options) {
var prop, result, tokens;
prop = Ember.Handlebars.get(this, property, options);
if (!prop) {
return "";
}
tokens = prop.match(new RegExp(".{1,14}", 'g'));
if (tokens.length === 1) {
return prop;
}
result = "";
tokens.each(function(token, index) {
result += token;
if (token.indexOf(' ') === -1 && (index < tokens.length - 1)) {
result += "- ";
}
});
return result;
});
Handlebars.registerHelper('shorten', function(property, options) {
var str;
str = Ember.Handlebars.get(this, property, options);
return str.truncate(35);
});
Handlebars.registerHelper('topicLink', function(property, options) {
var title, topic;
topic = Ember.Handlebars.get(this, property, options);
title = topic.get('fancy_title') || topic.get('title');
return "<a href='" + (topic.get('lastReadUrl')) + "' class='title excerptable'>" + title + "</a>";
});
Handlebars.registerHelper('categoryLink', function(property, options) {
var category;
category = Ember.Handlebars.get(this, property, options);
return new Handlebars.SafeString(Discourse.Utilities.categoryLink(category));
});
Handlebars.registerHelper('titledLinkTo', function(name, object) {
var options;
options = [].slice.call(arguments, -1)[0];
if (options.hash.titleKey) {
options.hash.title = Em.String.i18n(options.hash.titleKey);
}
if (arguments.length === 3) {
return Ember.Handlebars.helpers.linkTo.call(this, name, object, options);
} else {
return Ember.Handlebars.helpers.linkTo.call(this, name, options);
@method breakUp
@for Handlebars
**/
Handlebars.registerHelper('breakUp', function(property, options) {
var prop, result, tokens;
prop = Ember.Handlebars.get(this, property, options);
if (!prop) {
return "";
}
tokens = prop.match(new RegExp(".{1,14}", 'g'));
if (tokens.length === 1) {
return prop;
}
result = "";
tokens.each(function(token, index) {
result += token;
if (token.indexOf(' ') === -1 && (index < tokens.length - 1)) {
result += "- ";
}
});
return result;
});
Handlebars.registerHelper('shortenUrl', function(property, options) {
var url;
url = Ember.Handlebars.get(this, property, options);
/* Remove trailing slash if it's a top level URL
*/
/**
Truncates long strings
if (url.match(/\//g).length === 3) {
url = url.replace(/\/$/, '');
}
url = url.replace(/^https?:\/\//, '');
url = url.replace(/^www\./, '');
return url.truncate(80);
});
@method shorten
@for Handlebars
**/
Handlebars.registerHelper('shorten', function(property, options) {
return Ember.Handlebars.get(this, property, options).truncate(35);
});
Handlebars.registerHelper('lower', function(property, options) {
var o;
o = Ember.Handlebars.get(this, property, options);
if (o && typeof o === 'string') {
return o.toLowerCase();
} else {
return "";
}
});
/**
Produces a link to a topic
Handlebars.registerHelper('avatar', function(user, options) {
var title, username;
if (typeof user === 'string') {
user = Ember.Handlebars.get(this, user, options);
}
username = Em.get(user, 'username');
if (!username) {
username = Em.get(user, options.hash.usernamePath);
}
if (!options.hash.ignoreTitle) {
title = Em.get(user, 'title') || Em.get(user, 'description');
}
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
size: options.hash.imageSize,
extraClasses: Em.get(user, 'extras') || options.hash.extraClasses,
username: username,
title: title || username,
avatarTemplate: Ember.get(user, 'avatar_template') || options.hash.avatarTemplate
}));
});
@method topicLink
@for Handlebars
**/
Handlebars.registerHelper('topicLink', function(property, options) {
var title, topic;
topic = Ember.Handlebars.get(this, property, options);
title = topic.get('fancy_title') || topic.get('title');
return "<a href='" + (topic.get('lastReadUrl')) + "' class='title'>" + title + "</a>";
});
Handlebars.registerHelper('unboundDate', function(property, options) {
var dt;
dt = new Date(Ember.Handlebars.get(this, property, options));
/**
Produces a link to a category
@method categoryLink
@for Handlebars
**/
Handlebars.registerHelper('categoryLink', function(property, options) {
var category;
category = Ember.Handlebars.get(this, property, options);
return new Handlebars.SafeString(Discourse.Utilities.categoryLink(category));
});
/**
Produces a link to a route with support for i18n on the title
@method titledLinkTo
@for Handlebars
**/
Handlebars.registerHelper('titledLinkTo', function(name, object) {
var options;
options = [].slice.call(arguments, -1)[0];
if (options.hash.titleKey) {
options.hash.title = Em.String.i18n(options.hash.titleKey);
}
if (arguments.length === 3) {
return Ember.Handlebars.helpers.linkTo.call(this, name, object, options);
} else {
return Ember.Handlebars.helpers.linkTo.call(this, name, options);
}
});
/**
Shorten a URL for display by removing common components
@method shortenUrl
@for Handlebars
**/
Handlebars.registerHelper('shortenUrl', function(property, options) {
var url;
url = Ember.Handlebars.get(this, property, options);
// Remove trailing slash if it's a top level URL
if (url.match(/\//g).length === 3) {
url = url.replace(/\/$/, '');
}
url = url.replace(/^https?:\/\//, '');
url = url.replace(/^www\./, '');
return url.truncate(80);
});
/**
Display a property in lower case
@method lower
@for Handlebars
**/
Handlebars.registerHelper('lower', function(property, options) {
var o;
o = Ember.Handlebars.get(this, property, options);
if (o && typeof o === 'string') {
return o.toLowerCase();
} else {
return "";
}
});
/**
Show an avatar for a user, intelligently making use of available properties
@method avatar
@for Handlebars
**/
Handlebars.registerHelper('avatar', function(user, options) {
var title, username;
if (typeof user === 'string') {
user = Ember.Handlebars.get(this, user, options);
}
username = Em.get(user, 'username');
if (!username) {
username = Em.get(user, options.hash.usernamePath);
}
if (!options.hash.ignoreTitle) {
title = Em.get(user, 'title') || Em.get(user, 'description');
}
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
size: options.hash.imageSize,
extraClasses: Em.get(user, 'extras') || options.hash.extraClasses,
username: username,
title: title || username,
avatarTemplate: Ember.get(user, 'avatar_template') || options.hash.avatarTemplate
}));
});
/**
Nicely format a date without a binding since the date doesn't need to change.
@method unboundDate
@for Handlebars
**/
Handlebars.registerHelper('unboundDate', function(property, options) {
var dt;
dt = new Date(Ember.Handlebars.get(this, property, options));
return dt.format("{d} {Mon}, {yyyy} {hh}:{mm}");
});
/**
Display a date related to an edit of a post
@method editDate
@for Handlebars
**/
Handlebars.registerHelper('editDate', function(property, options) {
var dt, yesterday;
dt = Date.create(Ember.Handlebars.get(this, property, options));
yesterday = new Date() - (60 * 60 * 24 * 1000);
if (yesterday > dt.getTime()) {
return dt.format("{d} {Mon}, {yyyy} {hh}:{mm}");
});
} else {
return humaneDate(dt);
}
});
Handlebars.registerHelper('editDate', function(property, options) {
var dt, yesterday;
dt = Date.create(Ember.Handlebars.get(this, property, options));
yesterday = new Date() - (60 * 60 * 24 * 1000);
if (yesterday > dt.getTime()) {
return dt.format("{d} {Mon}, {yyyy} {hh}:{mm}");
/**
Display logic for numbers.
@method number
@for Handlebars
**/
Handlebars.registerHelper('number', function(property, options) {
var n, orig, title;
orig = parseInt(Ember.Handlebars.get(this, property, options), 10);
if (isNaN(orig)) {
orig = 0;
}
title = orig;
if (options.hash.numberKey) {
title = Em.String.i18n(options.hash.numberKey, {
number: orig
});
}
// Round off the thousands to one decimal place
n = orig;
if (orig > 999) {
n = (orig / 1000).toFixed(1) + "K";
}
return new Handlebars.SafeString("<span class='number' title='" + title + "'>" + n + "</span>");
});
/**
Display logic for dates.
@method date
@for Handlebars
**/
Handlebars.registerHelper('date', function(property, options) {
var displayDate, dt, fiveDaysAgo, fullReadable, humanized, leaveAgo, val;
if (property.hash) {
if (property.hash.leaveAgo) {
leaveAgo = property.hash.leaveAgo === "true";
}
if (property.hash.path) {
property = property.hash.path;
}
}
val = Ember.Handlebars.get(this, property, options);
if (!val) {
return new Handlebars.SafeString("&mdash;");
}
dt = new Date(val);
fullReadable = dt.format("{d} {Mon}, {yyyy} {hh}:{mm}");
displayDate = "";
fiveDaysAgo = (new Date()) - 432000000;
if (fiveDaysAgo > (dt.getTime())) {
if ((new Date()).getFullYear() !== dt.getFullYear()) {
displayDate = dt.format("{d} {Mon} '{yy}");
} else {
return humaneDate(dt);
displayDate = dt.format("{d} {Mon}");
}
});
} else {
humanized = humaneDate(dt);
if (!humanized) {
return "";
}
displayDate = humanized;
if (!leaveAgo) {
displayDate = displayDate.replace(' ago', '');
}
}
return new Handlebars.SafeString("<span class='date' title='" + fullReadable + "'>" + displayDate + "</span>");
});
Handlebars.registerHelper('number', function(property, options) {
var n, orig, title;
orig = parseInt(Ember.Handlebars.get(this, property, options), 10);
if (isNaN(orig)) {
orig = 0;
}
title = orig;
if (options.hash.numberKey) {
title = Em.String.i18n(options.hash.numberKey, {
number: orig
});
}
/* Round off the thousands to one decimal place
*/
/**
A personalized name for display
n = orig;
if (orig > 999) {
n = (orig / 1000).toFixed(1) + "K";
}
return new Handlebars.SafeString("<span class='number' title='" + title + "'>" + n + "</span>");
});
@method personalizedName
@for Handlebars
**/
Handlebars.registerHelper('personalizedName', function(property, options) {
var name, username;
name = Ember.Handlebars.get(this, property, options);
if (options.hash.usernamePath) {
username = Ember.Handlebars.get(this, options.hash.usernamePath, options);
}
if (username !== Discourse.get('currentUser.username')) {
return name;
}
return Em.String.i18n('you');
});
Handlebars.registerHelper('date', function(property, options) {
var displayDate, dt, fiveDaysAgo, fullReadable, humanized, leaveAgo, val;
if (property.hash) {
if (property.hash.leaveAgo) {
leaveAgo = property.hash.leaveAgo === "true";
}
if (property.hash.path) {
property = property.hash.path;
}
}
val = Ember.Handlebars.get(this, property, options);
if (!val) {
return new Handlebars.SafeString("&mdash;");
}
dt = new Date(val);
fullReadable = dt.format("{d} {Mon}, {yyyy} {hh}:{mm}");
displayDate = "";
fiveDaysAgo = (new Date()) - 432000000;
if (fiveDaysAgo > (dt.getTime())) {
if ((new Date()).getFullYear() !== dt.getFullYear()) {
displayDate = dt.format("{d} {Mon} '{yy}");
} else {
displayDate = dt.format("{d} {Mon}");
}
} else {
humanized = humaneDate(dt);
if (!humanized) {
return "";
}
displayDate = humanized;
if (!leaveAgo) {
displayDate = displayDate.replace(' ago', '');
}
}
return new Handlebars.SafeString("<span class='date' title='" + fullReadable + "'>" + displayDate + "</span>");
});
Handlebars.registerHelper('personalizedName', function(property, options) {
var name, username;
name = Ember.Handlebars.get(this, property, options);
if (options.hash.usernamePath) {
username = Ember.Handlebars.get(this, options.hash.usernamePath, options);
}
if (username !== Discourse.get('currentUser.username')) {
return name;
}
return Em.String.i18n('you');
});
}).call(this);

View file

@ -1,50 +1,59 @@
(function() {
/**
Look up a translation for an i18n key in our dictionary.
Ember.Handlebars.registerHelper('i18n', function(property, options) {
/* Resolve any properties
*/
var params,
_this = this;
params = options.hash;
Object.keys(params, function(key, value) {
params[key] = Em.Handlebars.get(_this, value, options);
});
return Ember.String.i18n(property, params);
@method i18n
@for Handlebars
**/
Ember.Handlebars.registerHelper('i18n', function(property, options) {
// Resolve any properties
var params,
_this = this;
params = options.hash;
Object.keys(params, function(key, value) {
params[key] = Em.Handlebars.get(_this, value, options);
});
return Ember.String.i18n(property, params);
});
/* We always prefix with .js to select exactly what we want passed through to the front end.
*/
/* We always prefix with .js to select exactly what we want passed through to the front end.
*/
/**
Look up a translation for an i18n key in our dictionary.
Ember.String.i18n = function(scope, options) {
return I18n.translate("js." + scope, options);
@method i18n
@for Ember.String
**/
Ember.String.i18n = function(scope, options) {
return I18n.translate("js." + scope, options);
};
/**
Set up an i18n binding that will update as a count changes, complete with pluralization.
@method countI18n
@for Handlebars
**/
Ember.Handlebars.registerHelper('countI18n', function(key, options) {
var view;
view = Discourse.View.extend({
tagName: 'span',
render: function(buffer) {
return buffer.push(Ember.String.i18n(key, {
count: this.get('count')
}));
},
countChanged: (function() {
return this.rerender();
}).observes('count')
});
return Ember.Handlebars.helpers.view.call(this, view, options);
});
if (Ember.EXTEND_PROTOTYPES) {
String.prototype.i18n = function(options) {
return Ember.String.i18n(String(this), options);
};
/* Bind an i18n count
*/
}
Ember.Handlebars.registerHelper('countI18n', function(key, options) {
var view;
view = Discourse.View.extend({
tagName: 'span',
render: function(buffer) {
return buffer.push(Ember.String.i18n(key, {
count: this.get('count')
}));
},
countChanged: (function() {
return this.rerender();
}).observes('count')
});
return Ember.Handlebars.helpers.view.call(this, view, options);
});
if (Ember.EXTEND_PROTOTYPES) {
String.prototype.i18n = function(options) {
return Ember.String.i18n(String(this), options);
};
}
}).call(this);

View file

@ -1,48 +1,46 @@
(function() {
/**
This mixin provides `blank` and `present` to determine whether properties are
there, accounting for more cases than just null and undefined.
@class Discourse.Presence
@extends Ember.Mixin
@namespace Discourse
@module Discourse
**/
Discourse.Presence = Em.Mixin.create({
/**
This mixin provides `blank` and `present` to determine whether properties are
there, accounting for more cases than just null and undefined.
Returns whether a property is blank. It considers empty arrays, string, objects, undefined and null
to be blank, otherwise true.
@class Discourse.Presence
@extends Ember.Mixin
@namespace Discourse
@module Discourse
**/
window.Discourse.Presence = Em.Mixin.create({
@method blank
@param {String} name the name of the property we want to check
@return {Boolean}
*/
blank: function(name) {
var prop;
prop = this.get(name);
if (!prop) return true;
/**
Returns whether a property is blank. It considers empty arrays, string, objects, undefined and null
to be blank, otherwise true.
@method blank
@param {String} name the name of the property we want to check
@return {Boolean}
*/
blank: function(name) {
var prop;
prop = this.get(name);
if (!prop) return true;
switch (typeof prop) {
case "string":
return prop.trim().isBlank();
case "object":
return Object.isEmpty(prop);
}
return false;
},
/**
Returns whether a property is present. A present property is the opposite of a `blank` one.
@method present
@param {String} name the name of the property we want to check
@return {Boolean}
*/
present: function(name) {
return !this.blank(name);
switch (typeof prop) {
case "string":
return prop.trim().isBlank();
case "object":
return Object.isEmpty(prop);
}
});
return false;
},
/**
Returns whether a property is present. A present property is the opposite of a `blank` one.
@method present
@param {String} name the name of the property we want to check
@return {Boolean}
*/
present: function(name) {
return !this.blank(name);
}
});
}).call(this);

View file

@ -1,24 +1,37 @@
/**
This mixin adds support for being notified every time the browser window
is scrolled.
/* Use this mixin if you want to be notified every time the user scrolls the window
*/
@class Discourse.Scrolling
@extends Ember.Mixin
@namespace Discourse
@module Discourse
**/
Discourse.Scrolling = Em.Mixin.create({
/**
Begin watching for scroll events. They will be called at max every 100ms.
@method bindScrolling
*/
bindScrolling: function() {
var onScroll,
_this = this;
onScroll = Discourse.debounce(function() { return _this.scrolled(); }, 100);
$(document).bind('touchmove.discourse', onScroll);
$(window).bind('scroll.discourse', onScroll);
},
/**
Begin watching for scroll events. They will be called at max every 100ms.
@method unbindScrolling
*/
unbindScrolling: function() {
$(window).unbind('scroll.discourse');
$(document).unbind('touchmove.discourse');
}
});
(function() {
window.Discourse.Scrolling = Em.Mixin.create({
bindScrolling: function() {
var onScroll,
_this = this;
onScroll = Discourse.debounce(function() {
return _this.scrolled();
}, 100);
jQuery(document).bind('touchmove.discourse', onScroll);
return jQuery(window).bind('scroll.discourse', onScroll);
},
unbindScrolling: function() {
jQuery(window).unbind('scroll.discourse');
return jQuery(document).unbind('touchmove.discourse');
}
});
}).call(this);

View file

@ -1,123 +1,123 @@
(function() {
/**
A data model for summarizing actions a user has taken, for example liking a post.
window.Discourse.ActionSummary = Discourse.Model.extend({
/* Description for the action
*/
@class ActionSummary
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.ActionSummary = Discourse.Model.extend({
description: (function() {
if (this.get('acted')) {
return Em.String.i18n('post.actions.by_you_and_others', {
count: this.get('count') - 1,
long_form: this.get('actionType.long_form')
});
} else {
return Em.String.i18n('post.actions.by_others', {
count: this.get('count'),
long_form: this.get('actionType.long_form')
});
}
}).property('count', 'acted', 'actionType'),
canAlsoAction: (function() {
if (this.get('hidden')) {
return false;
}
return this.get('can_act');
}).property('can_act', 'hidden'),
/* Remove it
*/
removeAction: function() {
this.set('acted', false);
this.set('count', this.get('count') - 1);
this.set('can_act', true);
return this.set('can_undo', false);
},
/* Perform this action
*/
act: function(opts) {
/* Mark it as acted
*/
var promise,
_this = this;
this.set('acted', true);
this.set('count', this.get('count') + 1);
this.set('can_act', false);
this.set('can_undo', true);
/* Add ourselves to the users who liked it if present
*/
if (this.present('users')) {
this.users.pushObject(Discourse.get('currentUser'));
}
/* Create our post action
*/
promise = new RSVP.Promise();
jQuery.ajax({
url: "/post_actions",
type: 'POST',
data: {
id: this.get('post.id'),
post_action_type_id: this.get('id'),
message: (opts ? opts.message : void 0) || ""
},
error: function(error) {
var errors;
_this.removeAction();
errors = jQuery.parseJSON(error.responseText).errors;
return promise.reject(errors);
},
success: function() {
return promise.resolve();
}
// Description for the action
description: (function() {
if (this.get('acted')) {
return Em.String.i18n('post.actions.by_you_and_others', {
count: this.get('count') - 1,
long_form: this.get('actionType.long_form')
});
return promise;
},
/* Undo this action
*/
undo: function() {
this.removeAction();
/* Remove our post action
*/
return jQuery.ajax({
url: "/post_actions/" + (this.get('post.id')),
type: 'DELETE',
data: {
post_action_type_id: this.get('id')
}
});
},
clearFlags: function() {
var _this = this;
return jQuery.ajax({
url: "/post_actions/clear_flags",
type: "POST",
data: {
post_action_type_id: this.get('id'),
id: this.get('post.id')
},
success: function(result) {
_this.set('post.hidden', result.hidden);
return _this.set('count', 0);
}
});
},
loadUsers: function() {
var _this = this;
return jQuery.getJSON("/post_actions/users", {
id: this.get('post.id'),
post_action_type_id: this.get('id')
}, function(result) {
_this.set('users', Em.A());
return result.each(function(u) {
return _this.get('users').pushObject(Discourse.User.create(u));
});
} else {
return Em.String.i18n('post.actions.by_others', {
count: this.get('count'),
long_form: this.get('actionType.long_form')
});
}
});
}).property('count', 'acted', 'actionType'),
canAlsoAction: (function() {
if (this.get('hidden')) return false;
return this.get('can_act');
}).property('can_act', 'hidden'),
// Remove it
removeAction: function() {
this.set('acted', false);
this.set('count', this.get('count') - 1);
this.set('can_act', true);
return this.set('can_undo', false);
},
// Perform this action
act: function(opts) {
// Mark it as acted
var promise,
_this = this;
this.set('acted', true);
this.set('count', this.get('count') + 1);
this.set('can_act', false);
this.set('can_undo', true);
// Add ourselves to the users who liked it if present
if (this.present('users')) {
this.users.pushObject(Discourse.get('currentUser'));
}
// Create our post action
promise = new RSVP.Promise();
jQuery.ajax({
url: "/post_actions",
type: 'POST',
data: {
id: this.get('post.id'),
post_action_type_id: this.get('id'),
message: (opts ? opts.message : void 0) || ""
},
error: function(error) {
var errors;
_this.removeAction();
errors = jQuery.parseJSON(error.responseText).errors;
return promise.reject(errors);
},
success: function() {
return promise.resolve();
}
});
return promise;
},
// Undo this action
undo: function() {
this.removeAction();
// Remove our post action
return jQuery.ajax({
url: "/post_actions/" + (this.get('post.id')),
type: 'DELETE',
data: {
post_action_type_id: this.get('id')
}
});
},
clearFlags: function() {
var _this = this;
return jQuery.ajax({
url: "/post_actions/clear_flags",
type: "POST",
data: {
post_action_type_id: this.get('id'),
id: this.get('post.id')
},
success: function(result) {
_this.set('post.hidden', result.hidden);
return _this.set('count', 0);
}
});
},
loadUsers: function() {
var _this = this;
return jQuery.getJSON("/post_actions/users", {
id: this.get('post.id'),
post_action_type_id: this.get('id')
}, function(result) {
_this.set('users', Em.A());
return result.each(function(u) {
return _this.get('users').pushObject(Discourse.User.create(u));
});
});
}
});
}).call(this);

View file

@ -1,15 +1,22 @@
(function() {
/**
A data model for archetypes such as polls, tasks, etc.
@class Archetype
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Archetype = Discourse.Model.extend({
hasOptions: (function() {
if (!this.get('options')) return false;
return this.get('options').length > 0;
}).property('options.@each'),
isDefault: (function() {
return this.get('id') === Discourse.get('site.default_archetype');
}).property('id')
});
window.Discourse.Archetype = Discourse.Model.extend({
hasOptions: (function() {
if (!this.get('options')) {
return false;
}
return this.get('options').length > 0;
}).property('options.@each'),
isDefault: (function() {
return this.get('id') === Discourse.get('site.default_archetype');
}).property('id')
});
}).call(this);

View file

@ -1,45 +1,55 @@
(function() {
/**
A data model that represents a category
window.Discourse.Category = Discourse.Model.extend({
url: (function() {
return "/category/" + (this.get('slug'));
}).property('name'),
style: (function() {
return "background-color: #" + (this.get('color'));
}).property('color'),
moreTopics: (function() {
return this.get('topic_count') > Discourse.SiteSettings.category_featured_topics;
}).property('topic_count'),
save: function(args) {
var url,
_this = this;
url = "/categories";
if (this.get('id')) {
url = "/categories/" + (this.get('id'));
}
return this.ajax(url, {
data: {
name: this.get('name'),
color: this.get('color')
},
type: this.get('id') ? 'PUT' : 'POST',
success: function(result) {
return args.success(result);
},
error: function(errors) {
return args.error(errors);
}
});
},
"delete": function(callback) {
var _this = this;
return jQuery.ajax("/categories/" + (this.get('slug')), {
type: 'DELETE',
success: function() {
return callback();
}
});
@class Category
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Category = Discourse.Model.extend({
url: (function() {
return "/category/" + (this.get('slug'));
}).property('name'),
style: (function() {
return "background-color: #" + (this.get('color'));
}).property('color'),
moreTopics: (function() {
return this.get('topic_count') > Discourse.SiteSettings.category_featured_topics;
}).property('topic_count'),
save: function(args) {
var url,
_this = this;
url = "/categories";
if (this.get('id')) {
url = "/categories/" + (this.get('id'));
}
});
}).call(this);
return this.ajax(url, {
data: {
name: this.get('name'),
color: this.get('color')
},
type: this.get('id') ? 'PUT' : 'POST',
success: function(result) { return args.success(result); },
error: function(errors) { return args.error(errors); }
});
},
"delete": function(callback) {
var _this = this;
return jQuery.ajax("/categories/" + (this.get('slug')), {
type: 'DELETE',
success: function() {
return callback();
}
});
}
});

View file

@ -1,41 +1,50 @@
(function() {
/**
A data model for containing a list of categories
window.Discourse.CategoryList = Discourse.Model.extend({});
@class CategoryList
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
window.Discourse.CategoryList = Discourse.Model.extend({});
window.Discourse.CategoryList.reopenClass({
categoriesFrom: function(result) {
var categories, users;
categories = Em.A();
users = this.extractByKey(result.featured_users, Discourse.User);
result.category_list.categories.each(function(c) {
if (c.featured_user_ids) {
c.featured_users = c.featured_user_ids.map(function(u) {
return users[u];
});
}
if (c.topics) {
c.topics = c.topics.map(function(t) {
return Discourse.Topic.create(t);
});
}
return categories.pushObject(Discourse.Category.create(c));
});
return categories;
},
list: function(filter) {
var promise,
_this = this;
promise = new RSVP.Promise();
jQuery.getJSON("/" + filter + ".json").then(function(result) {
var categoryList;
categoryList = Discourse.TopicList.create();
categoryList.set('can_create_category', result.category_list.can_create_category);
categoryList.set('categories', _this.categoriesFrom(result));
categoryList.set('loaded', true);
return promise.resolve(categoryList);
});
return promise;
}
});
window.Discourse.CategoryList.reopenClass({
categoriesFrom: function(result) {
var categories, users;
categories = Em.A();
users = this.extractByKey(result.featured_users, Discourse.User);
result.category_list.categories.each(function(c) {
if (c.featured_user_ids) {
c.featured_users = c.featured_user_ids.map(function(u) {
return users[u];
});
}
if (c.topics) {
c.topics = c.topics.map(function(t) {
return Discourse.Topic.create(t);
});
}
return categories.pushObject(Discourse.Category.create(c));
});
return categories;
},
list: function(filter) {
var promise,
_this = this;
promise = new RSVP.Promise();
jQuery.getJSON("/" + filter + ".json").then(function(result) {
var categoryList;
categoryList = Discourse.TopicList.create();
categoryList.set('can_create_category', result.category_list.can_create_category);
categoryList.set('categories', _this.categoriesFrom(result));
categoryList.set('loaded', true);
return promise.resolve(categoryList);
});
return promise;
}
});
}).call(this);

File diff suppressed because it is too large Load diff

View file

@ -1,80 +1,78 @@
(function() {
/**
A data model representing a draft post
window.Discourse.Draft = Discourse.Model.extend({});
@class Draft
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Draft = Discourse.Model.extend({});
Discourse.Draft.reopenClass({
clear: function(key, sequence) {
return jQuery.ajax({
type: 'DELETE',
url: "/draft",
data: {
draft_key: key,
sequence: sequence
}
});
/* Discourse.KeyValueStore.remove("draft_#{key}")
*/
Discourse.Draft.reopenClass({
},
get: function(key) {
var promise,
_this = this;
promise = new RSVP.Promise();
jQuery.ajax({
url: '/draft',
data: {
draft_key: key
},
dataType: 'json',
success: function(data) {
return promise.resolve(data);
}
});
return promise;
},
getLocal: function(key, current) {
var local;
return current;
/* disabling for now to see if it helps with siracusa issue.
local = Discourse.KeyValueStore.get("draft_" + key);
if (!current || (local && local.length > current.length)) {
return local;
} else {
return current;
}
*/
},
save: function(key, sequence, data) {
var promise;
promise = new RSVP.Promise();
data = typeof data === "string" ? data : JSON.stringify(data);
jQuery.ajax({
type: 'POST',
url: "/draft",
data: {
draft_key: key,
data: data,
sequence: sequence
},
success: function() {
/* don't keep local
*/
clear: function(key, sequence) {
return jQuery.ajax({
type: 'DELETE',
url: "/draft",
data: {
draft_key: key,
sequence: sequence
}
});
},
/* Discourse.KeyValueStore.remove("draft_#{key}")
*/
return promise.resolve();
},
error: function() {
/* save local
*/
get: function(key) {
var promise,
_this = this;
promise = new RSVP.Promise();
jQuery.ajax({
url: '/draft',
data: {
draft_key: key
},
dataType: 'json',
success: function(data) {
return promise.resolve(data);
}
});
return promise;
},
/* Discourse.KeyValueStore.set(key: "draft_#{key}", value: data)
*/
return promise.reject();
}
});
return promise;
}
});
getLocal: function(key, current) {
var local;
return current;
},
}).call(this);
save: function(key, sequence, data) {
var promise;
promise = new RSVP.Promise();
data = typeof data === "string" ? data : JSON.stringify(data);
jQuery.ajax({
type: 'POST',
url: "/draft",
data: {
draft_key: key,
data: data,
sequence: sequence
},
success: function() {
/* don't keep local
*/
/* Discourse.KeyValueStore.remove("draft_#{key}")
*/
return promise.resolve();
},
error: function() {
/* save local
*/
/* Discourse.KeyValueStore.set(key: "draft_#{key}", value: data)
*/
return promise.reject();
}
});
return promise;
}
});

View file

@ -1,5 +1,11 @@
(function() {
/**
A trivial model we use to handle input validation
@class InputValidation
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
window.Discourse.InputValidation = Discourse.Model.extend({});
window.Discourse.InputValidation = Discourse.Model.extend({});
}).call(this);

View file

@ -1,26 +1,35 @@
(function() {
/**
A data model representing an Invite
window.Discourse.Invite = Discourse.Model.extend({
rescind: function() {
jQuery.ajax('/invites', {
type: 'DELETE',
data: {
email: this.get('email')
}
});
return this.set('rescinded', true);
@class Invite
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Invite = Discourse.Model.extend({
rescind: function() {
jQuery.ajax('/invites', {
type: 'DELETE',
data: { email: this.get('email') }
});
this.set('rescinded', true);
}
});
Discourse.Invite.reopenClass({
create: function(invite) {
var result;
result = this._super(invite);
if (result.user) {
result.user = Discourse.User.create(result.user);
}
});
return result;
}
});
window.Discourse.Invite.reopenClass({
create: function(invite) {
var result;
result = this._super(invite);
if (result.user) {
result.user = Discourse.User.create(result.user);
}
return result;
}
});
}).call(this);

View file

@ -1,36 +1,44 @@
(function() {
/**
A data model representing a list of Invites
window.Discourse.InviteList = Discourse.Model.extend({
empty: (function() {
return this.blank('pending') && this.blank('redeemed');
}).property('pending.@each', 'redeemed.@each')
});
@class InviteList
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.InviteList = Discourse.Model.extend({
empty: (function() {
return this.blank('pending') && this.blank('redeemed');
}).property('pending.@each', 'redeemed.@each')
});
window.Discourse.InviteList.reopenClass({
findInvitedBy: function(user) {
var promise;
promise = new RSVP.Promise();
jQuery.ajax({
url: "/users/" + (user.get('username_lower')) + "/invited.json",
success: function(result) {
var invitedList;
invitedList = result.invited_list;
if (invitedList.pending) {
invitedList.pending = invitedList.pending.map(function(i) {
return Discourse.Invite.create(i);
});
}
if (invitedList.redeemed) {
invitedList.redeemed = invitedList.redeemed.map(function(i) {
return Discourse.Invite.create(i);
});
}
invitedList.user = user;
return promise.resolve(Discourse.InviteList.create(invitedList));
Discourse.InviteList.reopenClass({
findInvitedBy: function(user) {
var promise;
promise = new RSVP.Promise();
jQuery.ajax({
url: "/users/" + (user.get('username_lower')) + "/invited.json",
success: function(result) {
var invitedList;
invitedList = result.invited_list;
if (invitedList.pending) {
invitedList.pending = invitedList.pending.map(function(i) {
return Discourse.Invite.create(i);
});
}
});
return promise;
}
});
if (invitedList.redeemed) {
invitedList.redeemed = invitedList.redeemed.map(function(i) {
return Discourse.Invite.create(i);
});
}
invitedList.user = user;
return promise.resolve(Discourse.InviteList.create(invitedList));
}
});
return promise;
}
});
}).call(this);

View file

@ -1,54 +0,0 @@
(function() {
Discourse.Mention = (function() {
var cache, load, localCache, lookup, lookupCache;
localCache = {};
cache = function(name, valid) {
localCache[name] = valid;
};
lookupCache = function(name) {
return localCache[name];
};
lookup = function(name, callback) {
var cached;
cached = lookupCache(name);
if (cached === true || cached === false) {
callback(cached);
return false;
} else {
jQuery.get("/users/is_local_username", {
username: name
}, function(r) {
cache(name, r.valid);
return callback(r.valid);
});
return true;
}
};
load = function(e) {
var $elem, loading, username;
$elem = jQuery(e);
if ($elem.data('mention-tested')) {
return;
}
username = $elem.text();
username = username.substr(1);
loading = lookup(username, function(valid) {
if (valid) {
return $elem.replaceWith("<a href='/users/" + (username.toLowerCase()) + "' class='mention'>@" + username + "</a>");
} else {
return $elem.removeClass('mention-loading').addClass('mention-tested');
}
});
if (loading) {
return $elem.addClass('mention-loading');
}
};
return {
load: load,
lookup: lookup,
lookupCache: lookupCache
};
})();
}).call(this);

View file

@ -1,80 +1,78 @@
(function() {
/**
A base object we can use to handle models in the Discourse client application.
@class Model
@extends Ember.Object
@uses Discourse.Presence
@namespace Discourse
@module Discourse
**/
Discourse.Model = Ember.Object.extend(Discourse.Presence, {
/**
A base object we can use to handle models in the Discourse client application.
Our own AJAX handler that handles erronous responses
@class Model
@extends Ember.Object
@uses Discourse.Presence
@namespace Discourse
@module Discourse
@method ajax
@param {String} url The url to contact
@param {Object} args The arguments to pass to jQuery.ajax
**/
window.Discourse.Model = Ember.Object.extend(Discourse.Presence, {
ajax: function(url, args) {
var oldError = args.error;
args.error = function(xhr) {
return oldError(jQuery.parseJSON(xhr.responseText).errors);
};
return jQuery.ajax(url, args);
},
/**
Our own AJAX handler that handles erronous responses
/**
Update our object from another object
@method ajax
@param {String} url The url to contact
@param {Object} args The arguments to pass to jQuery.ajax
**/
ajax: function(url, args) {
var oldError = args.error;
args.error = function(xhr) {
return oldError(jQuery.parseJSON(xhr.responseText).errors);
};
return jQuery.ajax(url, args);
},
/**
Update our object from another object
@method mergeAttributes
@param {Object} attrs The attributes we want to merge with
@param {Object} builders Optional builders to use when merging attributes
**/
mergeAttributes: function(attrs, builders) {
var _this = this;
return Object.keys(attrs, function(k, v) {
// If they're in a builder we use that
var builder, col;
if (typeof v === 'object' && builders && (builder = builders[k])) {
if (!_this.get(k)) {
_this.set(k, Em.A());
}
col = _this.get(k);
return v.each(function(obj) {
col.pushObject(builder.create(obj));
});
} else {
_this.set(k, v);
@method mergeAttributes
@param {Object} attrs The attributes we want to merge with
@param {Object} builders Optional builders to use when merging attributes
**/
mergeAttributes: function(attrs, builders) {
var _this = this;
return Object.keys(attrs, function(k, v) {
// If they're in a builder we use that
var builder, col;
if (typeof v === 'object' && builders && (builder = builders[k])) {
if (!_this.get(k)) {
_this.set(k, Em.A());
}
});
}
});
window.Discourse.Model.reopenClass({
/**
Given an array of values, return them in a hash
@method extractByKey
@param {Object} collection The collection of values
@param {Object} klass Optional The class to instantiate
**/
extractByKey: function(collection, klass) {
var retval;
retval = {};
if (!collection) {
return retval;
col = _this.get(k);
return v.each(function(obj) {
col.pushObject(builder.create(obj));
});
} else {
_this.set(k, v);
}
collection.each(function(c) {
var obj;
obj = klass.create(c);
retval[c.id] = obj;
});
});
}
});
Discourse.Model.reopenClass({
/**
Given an array of values, return them in a hash
@method extractByKey
@param {Object} collection The collection of values
@param {Object} klass Optional The class to instantiate
**/
extractByKey: function(collection, klass) {
var retval;
retval = {};
if (!collection) {
return retval;
}
});
collection.each(function(c) {
var obj;
obj = klass.create(c);
retval[c.id] = obj;
});
return retval;
}
});
}).call(this);

View file

@ -1,72 +1,68 @@
/**
A data model representing a navigation item on the list views
/* closure wrapping means this does not leak into global context
*/
@class InviteList
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
var validAnon, validNavNames;
validNavNames = ['read', 'popular', 'categories', 'favorited', 'category', 'unread', 'new', 'posted'];
validAnon = ['popular', 'category', 'categories'];
(function() {
var validAnon, validNavNames;
validNavNames = ['read', 'popular', 'categories', 'favorited', 'category', 'unread', 'new', 'posted'];
validAnon = ['popular', 'category', 'categories'];
window.Discourse.NavItem = Discourse.Model.extend({
categoryName: (function() {
var split;
split = this.get('name').split('/');
if (split[0] === 'category') {
return split[1];
} else {
return null;
}
}).property(),
href: (function() {
/* href from this item
*/
var name;
name = this.get('name');
if (name === 'category') {
return "/" + name + "/" + (this.get('categoryName'));
} else {
return "/" + name;
}
}).property()
});
Discourse.NavItem.reopenClass({
/* create a nav item from the text, will return null if there is not valid nav item for this particular text
Discourse.NavItem = Discourse.Model.extend({
categoryName: (function() {
var split;
split = this.get('name').split('/');
if (split[0] === 'category') {
return split[1];
} else {
return null;
}
}).property(),
href: (function() {
/* href from this item
*/
fromText: function(text, opts) {
var countSummary, hasCategories, loggedOn, name, split, testName;
countSummary = opts.countSummary;
loggedOn = opts.loggedOn;
hasCategories = opts.hasCategories;
split = text.split(",");
name = split[0];
testName = name.split("/")[0];
if (!loggedOn && !validAnon.contains(testName)) {
return null;
}
if (!hasCategories && testName === "categories") {
return null;
}
if (!validNavNames.contains(testName)) {
return null;
}
opts = {
name: name,
hasIcon: name === "unread" || name === "favorited",
filters: split.splice(1)
};
if (countSummary) {
if (countSummary && countSummary[name]) {
opts.count = countSummary[name];
}
}
return Discourse.NavItem.create(opts);
var name;
name = this.get('name');
if (name === 'category') {
return "/" + name + "/" + (this.get('categoryName'));
} else {
return "/" + name;
}
});
}).property()
});
Discourse.NavItem.reopenClass({
// create a nav item from the text, will return null if there is not valid nav item for this particular text
fromText: function(text, opts) {
var countSummary, hasCategories, loggedOn, name, split, testName;
countSummary = opts.countSummary;
loggedOn = opts.loggedOn;
hasCategories = opts.hasCategories;
split = text.split(",");
name = split[0];
testName = name.split("/")[0];
if (!loggedOn && !validAnon.contains(testName)) return null;
if (!hasCategories && testName === "categories") return null;
if (!validNavNames.contains(testName)) return null;
opts = {
name: name,
hasIcon: name === "unread" || name === "favorited",
filters: split.splice(1)
};
if (countSummary) {
if (countSummary && countSummary[name]) {
opts.count = countSummary[name];
}
}
return Discourse.NavItem.create(opts);
}
});
}).call(this);

View file

@ -1,40 +1,45 @@
(function() {
/**
A data model representing a notification a user receives
window.Discourse.Notification = Discourse.Model.extend({
readClass: (function() {
if (this.read) {
return 'read';
} else {
return '';
}
}).property('read'),
url: (function() {
var slug;
if (this.blank('data.topic_title')) {
return "";
}
slug = this.get('slug');
return "/t/" + slug + "/" + (this.get('topic_id')) + "/" + (this.get('post_number'));
}).property(),
rendered: (function() {
var notificationName;
notificationName = Discourse.get('site.notificationLookup')[this.notification_type];
return Em.String.i18n("notifications." + notificationName, {
username: this.data.display_username,
link: "<a href='" + (this.get('url')) + "'>" + this.data.topic_title + "</a>"
});
}).property()
});
@class Notification
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Notification = Discourse.Model.extend({
window.Discourse.Notification.reopenClass({
create: function(obj) {
var result;
result = this._super(obj);
if (obj.data) {
result.set('data', Em.Object.create(obj.data));
}
return result;
readClass: (function() {
if (this.read) return 'read';
return '';
}).property('read'),
url: (function() {
var slug;
if (this.blank('data.topic_title')) return "";
slug = this.get('slug');
return "/t/" + slug + "/" + (this.get('topic_id')) + "/" + (this.get('post_number'));
}).property(),
rendered: (function() {
var notificationName;
notificationName = Discourse.get('site.notificationLookup')[this.notification_type];
return Em.String.i18n("notifications." + notificationName, {
username: this.data.display_username,
link: "<a href='" + (this.get('url')) + "'>" + this.data.topic_title + "</a>"
});
}).property()
});
Discourse.Notification.reopenClass({
create: function(obj) {
var result;
result = this._super(obj);
if (obj.data) {
result.set('data', Em.Object.create(obj.data));
}
});
return result;
}
});
}).call(this);

View file

@ -1,83 +0,0 @@
(function() {
Discourse.Onebox = (function() {
/* for now it only stores in a var, in future we can change it so it uses localStorage,
*/
/* trouble with localStorage is that expire semantics need some thinking
*/
/*cacheKey = "__onebox__"
*/
var cache, load, localCache, lookup, lookupCache;
localCache = {};
cache = function(url, contents) {
localCache[url] = contents;
return null;
};
lookupCache = function(url) {
var cached;
cached = localCache[url];
if (cached && cached.then) {
return null;
} else {
return cached;
}
};
lookup = function(url, refresh, callback) {
var cached;
cached = localCache[url];
if (refresh && cached && !cached.then) {
cached = null;
}
if (cached) {
if (cached.then) {
cached.then(callback(lookupCache(url)));
} else {
callback(cached);
}
return false;
} else {
cache(url, jQuery.get("/onebox", {
url: url,
refresh: refresh
}, function(html) {
cache(url, html);
return callback(html);
}));
return true;
}
};
load = function(e, refresh) {
var $elem, loading, url;
if (!refresh) refresh = false;
url = e.href;
$elem = jQuery(e);
if ($elem.data('onebox-loaded')) {
return;
}
loading = lookup(url, refresh, function(html) {
$elem.removeClass('loading-onebox');
$elem.data('onebox-loaded');
if (!html) {
return;
}
if (html.trim().length === 0) {
return;
}
return $elem.replaceWith(html);
});
if (loading) {
return $elem.addClass('loading-onebox');
}
};
return {
load: load,
lookup: lookup,
lookupCache: lookupCache
};
})();
}).call(this);

View file

@ -1,370 +1,341 @@
(function() {
/**
A data model representing a post in a topic
window.Discourse.Post = Discourse.Model.extend({
/* Url to this post
*/
@class Post
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Post = Discourse.Model.extend({
url: (function() {
return Discourse.Utilities.postUrl(this.get('topic.slug') || this.get('topic_slug'), this.get('topic_id'), this.get('post_number'));
}).property('post_number', 'topic_id', 'topic.slug'),
originalPostUrl: (function() {
return "/t/" + (this.get('topic_id')) + "/" + (this.get('reply_to_post_number'));
}).property('reply_to_post_number'),
showUserReplyTab: (function() {
return this.get('reply_to_user') && (this.get('reply_to_post_number') < (this.get('post_number') - 1));
}).property('reply_to_user', 'reply_to_post_number', 'post_number'),
firstPost: (function() {
if (this.get('bestOfFirst') === true) {
url: (function() {
return Discourse.Utilities.postUrl(this.get('topic.slug') || this.get('topic_slug'), this.get('topic_id'), this.get('post_number'));
}).property('post_number', 'topic_id', 'topic.slug'),
originalPostUrl: (function() {
return "/t/" + (this.get('topic_id')) + "/" + (this.get('reply_to_post_number'));
}).property('reply_to_post_number'),
showUserReplyTab: (function() {
return this.get('reply_to_user') && (this.get('reply_to_post_number') < (this.get('post_number') - 1));
}).property('reply_to_user', 'reply_to_post_number', 'post_number'),
firstPost: (function() {
if (this.get('bestOfFirst') === true) return true;
return this.get('post_number') === 1;
}).property('post_number'),
hasHistory: (function() {
return this.get('version') > 1;
}).property('version'),
postElementId: (function() {
return "post_" + (this.get('post_number'));
}).property(),
// The class for the read icon of the post. It starts with read-icon then adds 'seen' or
// 'last-read' if the post has been seen or is the highest post number seen so far respectively.
bookmarkClass: (function() {
var result, topic;
result = 'read-icon';
if (this.get('bookmarked')) return result + ' bookmarked';
topic = this.get('topic');
if (topic && topic.get('last_read_post_number') === this.get('post_number')) {
result += ' last-read';
} else {
if (this.get('read')) {
result += ' seen';
}
}
return result;
}).property('read', 'topic.last_read_post_number', 'bookmarked'),
// Custom tooltips for the bookmark icons
bookmarkTooltip: (function() {
var topic;
if (this.get('bookmarked')) return Em.String.i18n('bookmarks.created');
if (!this.get('read')) return "";
topic = this.get('topic');
if (topic && topic.get('last_read_post_number') === this.get('post_number')) {
return Em.String.i18n('bookmarks.last_read');
}
return Em.String.i18n('bookmarks.not_bookmarked');
}).property('read', 'topic.last_read_post_number', 'bookmarked'),
bookmarkedChanged: (function() {
var _this = this;
return jQuery.ajax({
url: "/posts/" + (this.get('id')) + "/bookmark",
type: 'PUT',
data: {
bookmarked: this.get('bookmarked') ? true : false
},
error: function(error) {
var errors;
errors = jQuery.parseJSON(error.responseText).errors;
bootbox.alert(errors[0]);
return _this.toggleProperty('bookmarked');
}
});
}).observes('bookmarked'),
internalLinks: (function() {
if (this.blank('link_counts')) return null;
return this.get('link_counts').filterProperty('internal').filterProperty('title');
}).property('link_counts.@each.internal'),
// Edits are the version - 1, so version 2 = 1 edit
editCount: (function() {
return this.get('version') - 1;
}).property('version'),
historyHeat: (function() {
var rightNow, updatedAt, updatedAtDate;
if (!(updatedAt = this.get('updated_at'))) return;
rightNow = new Date().getTime();
// Show heat on age
updatedAtDate = Date.create(updatedAt).getTime();
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 12)) return 'heatmap-high';
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 24)) return 'heatmap-med';
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 48)) return 'heatmap-low';
}).property('updated_at'),
flagsAvailable: (function() {
var _this = this;
return Discourse.get('site.flagTypes').filter(function(item) {
return _this.get("actionByName." + (item.get('name_key')) + ".can_act");
});
}).property('Discourse.site.flagTypes', 'actions_summary.@each.can_act'),
actionsHistory: (function() {
if (!this.present('actions_summary')) return null;
return this.get('actions_summary').filter(function(i) {
if (i.get('count') === 0) {
return false;
}
if (i.get('users') && i.get('users').length > 0) {
return true;
}
return this.get('post_number') === 1;
}).property('post_number'),
hasHistory: (function() {
return this.get('version') > 1;
}).property('version'),
postElementId: (function() {
return "post_" + (this.get('post_number'));
}).property(),
/*
The class for the read icon of the post. It starts with read-icon then adds 'seen' or
'last-read' if the post has been seen or is the highest post number seen so far respectively.
*/
return !i.get('hidden');
});
}).property('actions_summary.@each.users', 'actions_summary.@each.count'),
bookmarkClass: (function() {
var result, topic;
result = 'read-icon';
if (this.get('bookmarked')) {
return result + ' bookmarked';
}
topic = this.get('topic');
if (topic && topic.get('last_read_post_number') === this.get('post_number')) {
result += ' last-read';
} else {
if (this.get('read')) {
result += ' seen';
}
}
return result;
}).property('read', 'topic.last_read_post_number', 'bookmarked'),
/* Custom tooltips for the bookmark icons
*/
bookmarkTooltip: (function() {
var topic;
if (this.get('bookmarked')) {
return Em.String.i18n('bookmarks.created');
}
if (!this.get('read')) {
return "";
}
topic = this.get('topic');
if (topic && topic.get('last_read_post_number') === this.get('post_number')) {
return Em.String.i18n('bookmarks.last_read');
}
return Em.String.i18n('bookmarks.not_bookmarked');
}).property('read', 'topic.last_read_post_number', 'bookmarked'),
bookmarkedChanged: (function() {
var _this = this;
// Save a post and call the callback when done.
save: function(complete, error) {
var data, metaData;
if (!this.get('newPost')) {
// We're updating a post
return jQuery.ajax({
url: "/posts/" + (this.get('id')) + "/bookmark",
url: "/posts/" + (this.get('id')),
type: 'PUT',
data: {
bookmarked: this.get('bookmarked') ? true : false
post: { raw: this.get('raw') },
image_sizes: this.get('imageSizes')
},
error: function(error) {
var errors;
errors = jQuery.parseJSON(error.responseText).errors;
bootbox.alert(errors[0]);
return _this.toggleProperty('bookmarked');
}
});
}).observes('bookmarked'),
internalLinks: (function() {
if (this.blank('link_counts')) {
return null;
}
return this.get('link_counts').filterProperty('internal').filterProperty('title');
}).property('link_counts.@each.internal'),
/* Edits are the version - 1, so version 2 = 1 edit
*/
editCount: (function() {
return this.get('version') - 1;
}).property('version'),
historyHeat: (function() {
var rightNow, updatedAt, updatedAtDate;
if (!(updatedAt = this.get('updated_at'))) {
return;
}
rightNow = new Date().getTime();
/* Show heat on age
*/
updatedAtDate = Date.create(updatedAt).getTime();
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 12)) {
return 'heatmap-high';
}
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 24)) {
return 'heatmap-med';
}
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 48)) {
return 'heatmap-low';
}
}).property('updated_at'),
flagsAvailable: (function() {
var _this = this;
return Discourse.get('site.flagTypes').filter(function(item) {
return _this.get("actionByName." + (item.get('name_key')) + ".can_act");
});
}).property('Discourse.site.flagTypes', 'actions_summary.@each.can_act'),
actionsHistory: (function() {
if (!this.present('actions_summary')) {
return null;
}
return this.get('actions_summary').filter(function(i) {
if (i.get('count') === 0) {
return false;
}
if (i.get('users') && i.get('users').length > 0) {
return true;
}
return !i.get('hidden');
});
}).property('actions_summary.@each.users', 'actions_summary.@each.count'),
/* Save a post and call the callback when done.
*/
save: function(complete, error) {
var data, metaData;
if (!this.get('newPost')) {
/* We're updating a post
*/
return jQuery.ajax({
url: "/posts/" + (this.get('id')),
type: 'PUT',
data: {
post: { raw: this.get('raw') },
image_sizes: this.get('imageSizes')
},
success: function(result) {
console.log(result)
// If we received a category update, update it
if (result.category) Discourse.get('site').updateCategory(result.category);
return typeof complete === "function" ? complete(Discourse.Post.create(result.post)) : void 0;
},
error: function(result) {
return typeof error === "function" ? error(result) : void 0;
}
});
} else {
// We're saving a post
data = {
post: this.getProperties('raw', 'topic_id', 'reply_to_post_number', 'category'),
archetype: this.get('archetype'),
title: this.get('title'),
image_sizes: this.get('imageSizes'),
target_usernames: this.get('target_usernames')
};
/* Put the metaData into the request
*/
if (metaData = this.get('metaData')) {
data.meta_data = {};
Ember.keys(metaData).forEach(function(key) {
data.meta_data[key] = metaData.get(key);
});
}
return jQuery.ajax({
type: 'POST',
url: "/posts",
data: data,
success: function(result) {
return typeof complete === "function" ? complete(Discourse.Post.create(result)) : void 0;
},
error: function(result) {
return typeof error === "function" ? error(result) : void 0;
}
});
}
},
recover: function() {
return jQuery.ajax("/posts/" + (this.get('id')) + "/recover", {
type: 'PUT',
cache: false
});
},
"delete": function(complete) {
return jQuery.ajax("/posts/" + (this.get('id')), {
type: 'DELETE',
success: function(result) {
return typeof complete === "function" ? complete() : void 0;
// If we received a category update, update it
if (result.category) Discourse.get('site').updateCategory(result.category);
return typeof complete === "function" ? complete(Discourse.Post.create(result.post)) : void 0;
},
error: function(result) { return typeof error === "function" ? error(result) : void 0; }
});
} else {
// We're saving a post
data = {
post: this.getProperties('raw', 'topic_id', 'reply_to_post_number', 'category'),
archetype: this.get('archetype'),
title: this.get('title'),
image_sizes: this.get('imageSizes'),
target_usernames: this.get('target_usernames')
};
// Put the metaData into the request
if (metaData = this.get('metaData')) {
data.meta_data = {};
Ember.keys(metaData).forEach(function(key) { data.meta_data[key] = metaData.get(key); });
}
return jQuery.ajax({
type: 'POST',
url: "/posts",
data: data,
success: function(result) {
return typeof complete === "function" ? complete(Discourse.Post.create(result)) : void 0;
},
error: function(result) {
return typeof error === "function" ? error(result) : void 0;
}
});
},
/*
Update the properties of this post from an obj, ignoring cooked as we should already
have that rendered.
*/
updateFromSave: function(obj) {
var lookup,
_this = this;
if (!obj) {
return;
}
Object.each(obj, function(key, val) {
if (key === 'actions_summary') {
return false;
}
if (val) {
return _this.set(key, val);
}
});
/* Rebuild actions summary
*/
this.set('actions_summary', Em.A());
if (obj.actions_summary) {
lookup = Em.Object.create();
obj.actions_summary.each(function(a) {
var actionSummary;
a.post = _this;
a.actionType = Discourse.get("site").postActionTypeById(a.id);
actionSummary = Discourse.ActionSummary.create(a);
_this.get('actions_summary').pushObject(actionSummary);
return lookup.set(a.actionType.get('name_key'), actionSummary);
});
return this.set('actionByName', lookup);
}
},
// Load replies to this post
loadReplies: function() {
var promise,
_this = this;
promise = new RSVP.Promise();
this.set('loadingReplies', true);
this.set('replies', []);
jQuery.getJSON("/posts/" + (this.get('id')) + "/replies", function(loaded) {
loaded.each(function(reply) {
var post;
post = Discourse.Post.create(reply);
post.set('topic', _this.get('topic'));
return _this.get('replies').pushObject(post);
});
_this.set('loadingReplies', false);
return promise.resolve();
});
return promise;
},
loadVersions: function(callback) {
return jQuery.get("/posts/" + (this.get('id')) + "/versions.json", function(result) {
return callback(result);
});
},
// Whether to show replies directly below
showRepliesBelow: (function() {
var reply_count, _ref;
reply_count = this.get('reply_count');
/* We don't show replies if there aren't any
*/
if (reply_count === 0) {
return false;
}
/* Always show replies if the setting `supress_reply_directly_below` is false.
*/
if (!Discourse.SiteSettings.supress_reply_directly_below) {
return true;
}
/*Always show replies if there's more than one
*/
if (reply_count > 1) {
return true;
}
/* If we have *exactly* one reply, we have to consider if it's directly below us
*/
if ((_ref = this.get('topic')) ? _ref.isReplyDirectlyBelow(this) : void 0) {
return false;
}
return true;
}).property('reply_count')
});
window.Discourse.Post.reopenClass({
createActionSummary: function(result) {
var lookup;
if (result.actions_summary) {
lookup = Em.Object.create();
result.actions_summary = result.actions_summary.map(function(a) {
var actionSummary;
a.post = result;
a.actionType = Discourse.get("site").postActionTypeById(a.id);
actionSummary = Discourse.ActionSummary.create(a);
lookup.set(a.actionType.get('name_key'), actionSummary);
return actionSummary;
});
return result.set('actionByName', lookup);
}
},
create: function(obj, topic) {
var result;
result = this._super(obj);
this.createActionSummary(result);
if (obj.reply_to_user) {
result.set('reply_to_user', Discourse.User.create(obj.reply_to_user));
}
result.set('topic', topic);
return result;
},
deleteMany: function(posts) {
return jQuery.ajax("/posts/destroy_many", {
type: 'DELETE',
data: {
post_ids: posts.map(function(p) {
return p.get('id');
})
}
});
},
loadVersion: function(postId, version, callback) {
var _this = this;
return jQuery.getJSON("/posts/" + postId + ".json?version=" + version, function(result) {
return callback(Discourse.Post.create(result));
});
},
loadByPostNumber: function(topicId, postId, callback) {
var _this = this;
return jQuery.getJSON("/posts/by_number/" + topicId + "/" + postId + ".json", function(result) {
return callback(Discourse.Post.create(result));
});
},
loadQuote: function(postId) {
var promise,
_this = this;
promise = new RSVP.Promise();
jQuery.getJSON("/posts/" + postId + ".json", function(result) {
var post;
post = Discourse.Post.create(result);
return promise.resolve(Discourse.BBCode.buildQuoteBBCode(post, post.get('raw')));
});
return promise;
},
load: function(postId, callback) {
var _this = this;
return jQuery.getJSON("/posts/" + postId + ".json", function(result) {
return callback(Discourse.Post.create(result));
});
}
});
},
recover: function() {
return jQuery.ajax("/posts/" + (this.get('id')) + "/recover", { type: 'PUT', cache: false });
},
"delete": function(complete) {
return jQuery.ajax("/posts/" + (this.get('id')), {
type: 'DELETE',
success: function(result) {
return typeof complete === "function" ? complete() : void 0;
}
});
},
// Update the properties of this post from an obj, ignoring cooked as we should already
// have that rendered.
updateFromSave: function(obj) {
var lookup,
_this = this;
if (!obj) {
return;
}
Object.each(obj, function(key, val) {
if (key === 'actions_summary') {
return false;
}
if (val) {
return _this.set(key, val);
}
});
// Rebuild actions summary
this.set('actions_summary', Em.A());
if (obj.actions_summary) {
lookup = Em.Object.create();
obj.actions_summary.each(function(a) {
var actionSummary;
a.post = _this;
a.actionType = Discourse.get("site").postActionTypeById(a.id);
actionSummary = Discourse.ActionSummary.create(a);
_this.get('actions_summary').pushObject(actionSummary);
return lookup.set(a.actionType.get('name_key'), actionSummary);
});
return this.set('actionByName', lookup);
}
},
// Load replies to this post
loadReplies: function() {
var promise,
_this = this;
promise = new RSVP.Promise();
this.set('loadingReplies', true);
this.set('replies', []);
jQuery.getJSON("/posts/" + (this.get('id')) + "/replies", function(loaded) {
loaded.each(function(reply) {
var post;
post = Discourse.Post.create(reply);
post.set('topic', _this.get('topic'));
return _this.get('replies').pushObject(post);
});
_this.set('loadingReplies', false);
return promise.resolve();
});
return promise;
},
loadVersions: function(callback) {
return jQuery.get("/posts/" + (this.get('id')) + "/versions.json", function(result) {
return callback(result);
});
},
// Whether to show replies directly below
showRepliesBelow: (function() {
var reply_count, _ref;
reply_count = this.get('reply_count');
// We don't show replies if there aren't any
if (reply_count === 0) return false;
// Always show replies if the setting `supress_reply_directly_below` is false.
if (!Discourse.SiteSettings.supress_reply_directly_below) return true;
// Always show replies if there's more than one
if (reply_count > 1) return true;
// If we have *exactly* one reply, we have to consider if it's directly below us
if ((_ref = this.get('topic')) ? _ref.isReplyDirectlyBelow(this) : void 0) return false;
return true;
}).property('reply_count')
});
window.Discourse.Post.reopenClass({
createActionSummary: function(result) {
var lookup;
if (result.actions_summary) {
lookup = Em.Object.create();
result.actions_summary = result.actions_summary.map(function(a) {
var actionSummary;
a.post = result;
a.actionType = Discourse.get("site").postActionTypeById(a.id);
actionSummary = Discourse.ActionSummary.create(a);
lookup.set(a.actionType.get('name_key'), actionSummary);
return actionSummary;
});
return result.set('actionByName', lookup);
}
},
create: function(obj, topic) {
var result;
result = this._super(obj);
this.createActionSummary(result);
if (obj.reply_to_user) {
result.set('reply_to_user', Discourse.User.create(obj.reply_to_user));
}
result.set('topic', topic);
return result;
},
deleteMany: function(posts) {
return jQuery.ajax("/posts/destroy_many", {
type: 'DELETE',
data: {
post_ids: posts.map(function(p) {
return p.get('id');
})
}
});
},
loadVersion: function(postId, version, callback) {
var _this = this;
return jQuery.getJSON("/posts/" + postId + ".json?version=" + version, function(result) {
return callback(Discourse.Post.create(result));
});
},
loadByPostNumber: function(topicId, postId, callback) {
var _this = this;
return jQuery.getJSON("/posts/by_number/" + topicId + "/" + postId + ".json", function(result) {
return callback(Discourse.Post.create(result));
});
},
loadQuote: function(postId) {
var promise,
_this = this;
promise = new RSVP.Promise();
jQuery.getJSON("/posts/" + postId + ".json", function(result) {
var post;
post = Discourse.Post.create(result);
return promise.resolve(Discourse.BBCode.buildQuoteBBCode(post, post.get('raw')));
});
return promise;
},
load: function(postId, callback) {
var _this = this;
return jQuery.getJSON("/posts/" + postId + ".json", function(result) {
return callback(Discourse.Post.create(result));
});
}
});
}).call(this);

View file

@ -1,16 +1,23 @@
(function() {
/**
A data model representing action types (flags, likes) against a Post
@class PostActionType
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.PostActionType = Discourse.Model.extend({
alsoName: (function() {
if (this.get('is_flag')) return Em.String.i18n('post.actions.flag');
return this.get('name');
}).property('is_flag', 'name'),
alsoNameLower: (function() {
var _ref;
return (_ref = this.get('alsoName')) ? _ref.toLowerCase() : void 0;
}).property('alsoName')
});
window.Discourse.PostActionType = Discourse.Model.extend({
alsoName: (function() {
if (this.get('is_flag')) {
return Em.String.i18n('post.actions.flag');
}
return this.get('name');
}).property('is_flag', 'name'),
alsoNameLower: (function() {
var _ref;
return (_ref = this.get('alsoName')) ? _ref.toLowerCase() : void 0;
}).property('alsoName')
});
}).call(this);

View file

@ -1,60 +1,66 @@
(function() {
/**
A data model representing the site (instance of Discourse)
window.Discourse.Site = Discourse.Model.extend({
@class Site
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Site = Discourse.Model.extend({
notificationLookup: (function() {
var result;
result = [];
Object.keys(this.get('notification_types'), function(k, v) {
result[v] = k;
});
return result;
}).property('notification_types'),
notificationLookup: (function() {
var result;
result = [];
Object.keys(this.get('notification_types'), function(k, v) {
result[v] = k;
});
return result;
}).property('notification_types'),
flagTypes: (function() {
var postActionTypes;
postActionTypes = this.get('post_action_types');
if (!postActionTypes) {
return [];
flagTypes: (function() {
var postActionTypes;
postActionTypes = this.get('post_action_types');
if (!postActionTypes) {
return [];
}
return postActionTypes.filterProperty('is_flag', true);
}).property('post_action_types.@each'),
postActionTypeById: function(id) {
return this.get("postActionByIdLookup.action" + id);
},
updateCategory: function(newCategory) {
var existingCategory = this.get('categories').findProperty('id', Em.get(newCategory, 'id'));
if (existingCategory) existingCategory.mergeAttributes(newCategory);
}
});
Discourse.Site.reopenClass({
create: function(obj) {
var _this = this;
return Object.tap(this._super(obj), function(result) {
if (result.categories) {
result.categories = result.categories.map(function(c) {
return Discourse.Category.create(c);
});
}
return postActionTypes.filterProperty('is_flag', true);
}).property('post_action_types.@each'),
if (result.post_action_types) {
result.postActionByIdLookup = Em.Object.create();
result.post_action_types = result.post_action_types.map(function(p) {
var actionType;
actionType = Discourse.PostActionType.create(p);
result.postActionByIdLookup.set("action" + p.id, actionType);
return actionType;
});
}
if (result.archetypes) {
result.archetypes = result.archetypes.map(function(a) {
return Discourse.Archetype.create(a);
});
}
});
}
});
postActionTypeById: function(id) {
return this.get("postActionByIdLookup.action" + id);
},
updateCategory: function(newCategory) {
var existingCategory = this.get('categories').findProperty('id', Em.get(newCategory, 'id'));
if (existingCategory) existingCategory.mergeAttributes(newCategory);
}
});
window.Discourse.Site.reopenClass({
create: function(obj) {
var _this = this;
return Object.tap(this._super(obj), function(result) {
if (result.categories) {
result.categories = result.categories.map(function(c) {
return Discourse.Category.create(c);
});
}
if (result.post_action_types) {
result.postActionByIdLookup = Em.Object.create();
result.post_action_types = result.post_action_types.map(function(p) {
var actionType;
actionType = Discourse.PostActionType.create(p);
result.postActionByIdLookup.set("action" + p.id, actionType);
return actionType;
});
}
if (result.archetypes) {
result.archetypes = result.archetypes.map(function(a) {
return Discourse.Archetype.create(a);
});
}
});
}
});
}).call(this);

View file

@ -1,468 +1,428 @@
(function() {
/**
A data model representing a Topic
Discourse.Topic = Discourse.Model.extend({
categoriesBinding: 'Discourse.site.categories',
fewParticipants: (function() {
if (!this.present('participants')) {
return null;
}
return this.get('participants').slice(0, 3);
}).property('participants'),
canConvertToRegular: (function() {
var a;
a = this.get('archetype');
return a !== 'regular' && a !== 'private_message';
}).property('archetype'),
convertArchetype: function(archetype) {
var a;
a = this.get('archetype');
if (a !== 'regular' && a !== 'private_message') {
this.set('archetype', 'regular');
return jQuery.post(this.get('url'), {
_method: 'put',
archetype: 'regular'
});
}
},
category: (function() {
if (this.get('categories')) {
return this.get('categories').findProperty('name', this.get('categoryName'));
}
}).property('categoryName', 'categories'),
url: (function() {
var slug;
slug = this.get('slug');
if (slug.isBlank()) {
slug = "topic";
}
return "/t/" + slug + "/" + (this.get('id'));
}).property('id', 'slug'),
/* Helper to build a Url with a post number
*/
@class Topic
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Topic = Discourse.Model.extend({
categoriesBinding: 'Discourse.site.categories',
urlForPostNumber: function(postNumber) {
var url;
url = this.get('url');
if (postNumber && (postNumber > 1)) {
url += "/" + postNumber;
}
return url;
},
lastReadUrl: (function() {
return this.urlForPostNumber(this.get('last_read_post_number'));
}).property('url', 'last_read_post_number'),
lastPostUrl: (function() {
return this.urlForPostNumber(this.get('highest_post_number'));
}).property('url', 'highest_post_number'),
/* The last post in the topic
*/
fewParticipants: (function() {
if (!this.present('participants')) return null;
return this.get('participants').slice(0, 3);
}).property('participants'),
lastPost: function() {
return this.get('posts').last();
},
postsChanged: (function() {
var last, posts;
posts = this.get('posts');
last = posts.last();
if (!(last && last.set && !last.lastPost)) {
return;
}
posts.each(function(p) {
if (p.lastPost) {
return p.set('lastPost', false);
}
});
last.set('lastPost', true);
return true;
}).observes('posts.@each', 'posts'),
/* The amount of new posts to display. It might be different than what the server
*/
canConvertToRegular: (function() {
var a = this.get('archetype');
return a !== 'regular' && a !== 'private_message';
}).property('archetype'),
/* tells us if we are still asynchronously flushing our "recently read" data.
*/
/* So take what the browser has seen into consideration.
*/
displayNewPosts: (function() {
var delta, highestSeen, result;
if (highestSeen = Discourse.get('highestSeenByTopic')[this.get('id')]) {
delta = highestSeen - this.get('last_read_post_number');
if (delta > 0) {
result = this.get('new_posts') - delta;
if (result < 0) {
result = 0;
}
return result;
}
}
return this.get('new_posts');
}).property('new_posts', 'id'),
/* The coldmap class for the age of the topic
*/
ageCold: (function() {
var createdAt, createdAtDays, daysSinceEpoch, lastPost, nowDays;
if (!(lastPost = this.get('last_posted_at'))) {
return;
}
if (!(createdAt = this.get('created_at'))) {
return;
}
daysSinceEpoch = function(dt) {
/* 1000 * 60 * 60 * 24 = days since epoch
*/
return dt.getTime() / 86400000;
};
/* Show heat on age
*/
nowDays = daysSinceEpoch(new Date());
createdAtDays = daysSinceEpoch(new Date(createdAt));
if (daysSinceEpoch(new Date(lastPost)) > nowDays - 90) {
if (createdAtDays < nowDays - 60) {
return 'coldmap-high';
}
if (createdAtDays < nowDays - 30) {
return 'coldmap-med';
}
if (createdAtDays < nowDays - 14) {
return 'coldmap-low';
}
}
return null;
}).property('age', 'created_at'),
archetypeObject: (function() {
return Discourse.get('site.archetypes').findProperty('id', this.get('archetype'));
}).property('archetype'),
isPrivateMessage: (function() {
return this.get('archetype') === 'private_message';
}).property('archetype'),
/* Does this topic only have a single post?
*/
singlePost: (function() {
return this.get('posts_count') === 1;
}).property('posts_count'),
toggleStatus: function(property) {
this.toggleProperty(property);
return jQuery.post("" + (this.get('url')) + "/status", {
_method: 'put',
status: property,
enabled: this.get(property) ? 'true' : 'false'
});
},
toggleStar: function() {
var _this = this;
this.toggleProperty('starred');
return jQuery.ajax({
url: "" + (this.get('url')) + "/star",
type: 'PUT',
data: {
starred: this.get('starred') ? true : false
},
error: function(error) {
var errors;
_this.toggleProperty('starred');
errors = jQuery.parseJSON(error.responseText).errors;
return bootbox.alert(errors[0]);
}
});
},
/* Save any changes we've made to the model
*/
save: function() {
/* Don't save unless we can
*/
if (!this.get('can_edit')) {
return;
}
convertArchetype: function(archetype) {
var a;
a = this.get('archetype');
if (a !== 'regular' && a !== 'private_message') {
this.set('archetype', 'regular');
return jQuery.post(this.get('url'), {
_method: 'put',
title: this.get('title'),
category: this.get('category.name')
archetype: 'regular'
});
},
/* Reset our read data for this topic
*/
}
},
resetRead: function(callback) {
return jQuery.ajax("/t/" + (this.get('id')) + "/timings", {
type: 'DELETE',
success: function() {
return typeof callback === "function" ? callback() : void 0;
category: (function() {
if (this.get('categories')) {
return this.get('categories').findProperty('name', this.get('categoryName'));
}
}).property('categoryName', 'categories'),
url: (function() {
var slug = this.get('slug');
if (slug.isBlank()) {
slug = "topic";
}
return "/t/" + slug + "/" + (this.get('id'));
}).property('id', 'slug'),
// Helper to build a Url with a post number
urlForPostNumber: function(postNumber) {
var url;
url = this.get('url');
if (postNumber && (postNumber > 1)) {
url += "/" + postNumber;
}
return url;
},
lastReadUrl: (function() {
return this.urlForPostNumber(this.get('last_read_post_number'));
}).property('url', 'last_read_post_number'),
lastPostUrl: (function() {
return this.urlForPostNumber(this.get('highest_post_number'));
}).property('url', 'highest_post_number'),
// The last post in the topic
lastPost: function() {
return this.get('posts').last();
},
postsChanged: (function() {
var last, posts;
posts = this.get('posts');
last = posts.last();
if (!(last && last.set && !last.lastPost)) return;
posts.each(function(p) {
if (p.lastPost) return p.set('lastPost', false);
});
last.set('lastPost', true);
return true;
}).observes('posts.@each', 'posts'),
// The amount of new posts to display. It might be different than what the server
// tells us if we are still asynchronously flushing our "recently read" data.
// So take what the browser has seen into consideration.
displayNewPosts: (function() {
var delta, highestSeen, result;
if (highestSeen = Discourse.get('highestSeenByTopic')[this.get('id')]) {
delta = highestSeen - this.get('last_read_post_number');
if (delta > 0) {
result = this.get('new_posts') - delta;
if (result < 0) {
result = 0;
}
});
},
/* Invite a user to this topic
*/
inviteUser: function(user) {
return jQuery.ajax({
type: 'POST',
url: "/t/" + (this.get('id')) + "/invite",
data: {
user: user
}
});
},
/* Delete this topic
*/
"delete": function(callback) {
return jQuery.ajax("/t/" + (this.get('id')), {
type: 'DELETE',
success: function() {
return typeof callback === "function" ? callback() : void 0;
}
});
},
/* Load the posts for this topic
*/
loadPosts: function(opts) {
var _this = this;
if (!opts) {
opts = {};
return result;
}
/* Load the first post by default
*/
}
return this.get('new_posts');
}).property('new_posts', 'id'),
if (!opts.bestOf) {
if (!opts.nearPost) opts.nearPost = 1
// The coldmap class for the age of the topic
ageCold: (function() {
var createdAt, createdAtDays, daysSinceEpoch, lastPost, nowDays;
if (!(lastPost = this.get('last_posted_at'))) return;
if (!(createdAt = this.get('created_at'))) return;
daysSinceEpoch = function(dt) {
// 1000 * 60 * 60 * 24 = days since epoch
return dt.getTime() / 86400000;
};
// Show heat on age
nowDays = daysSinceEpoch(new Date());
createdAtDays = daysSinceEpoch(new Date(createdAt));
if (daysSinceEpoch(new Date(lastPost)) > nowDays - 90) {
if (createdAtDays < nowDays - 60) return 'coldmap-high';
if (createdAtDays < nowDays - 30) return 'coldmap-med';
if (createdAtDays < nowDays - 14) return 'coldmap-low';
}
return null;
}).property('age', 'created_at'),
archetypeObject: (function() {
return Discourse.get('site.archetypes').findProperty('id', this.get('archetype'));
}).property('archetype'),
isPrivateMessage: (function() {
return this.get('archetype') === 'private_message';
}).property('archetype'),
// Does this topic only have a single post?
singlePost: (function() {
return this.get('posts_count') === 1;
}).property('posts_count'),
toggleStatus: function(property) {
this.toggleProperty(property);
return jQuery.post("" + (this.get('url')) + "/status", {
_method: 'put',
status: property,
enabled: this.get(property) ? 'true' : 'false'
});
},
toggleStar: function() {
var _this = this;
this.toggleProperty('starred');
return jQuery.ajax({
url: "" + (this.get('url')) + "/star",
type: 'PUT',
data: {
starred: this.get('starred') ? true : false
},
error: function(error) {
var errors;
_this.toggleProperty('starred');
errors = jQuery.parseJSON(error.responseText).errors;
return bootbox.alert(errors[0]);
}
/* If we already have that post in the DOM, jump to it
*/
});
},
if (Discourse.TopicView.scrollTo(this.get('id'), opts.nearPost)) {
return;
// Save any changes we've made to the model
save: function() {
// Don't save unless we can
if (!this.get('can_edit')) return;
return jQuery.post(this.get('url'), {
_method: 'put',
title: this.get('title'),
category: this.get('category.name')
});
},
// Reset our read data for this topic
resetRead: function(callback) {
return jQuery.ajax("/t/" + (this.get('id')) + "/timings", {
type: 'DELETE',
success: function() {
return typeof callback === "function" ? callback() : void 0;
}
return Discourse.Topic.find(this.get('id'), {
nearPost: opts.nearPost,
bestOf: opts.bestOf,
trackVisit: opts.trackVisit
}).then(function(result) {
/* If loading the topic succeeded...
*/
});
},
/* Update the slug if different
*/
// Invite a user to this topic
inviteUser: function(user) {
return jQuery.ajax({
type: 'POST',
url: "/t/" + (this.get('id')) + "/invite",
data: {
user: user
}
});
},
var closestPostNumber, lastPost, postDiff;
if (result.slug) {
_this.set('slug', result.slug);
}
/* If we want to scroll to a post that doesn't exist, just pop them to the closest
*/
// Delete this topic
"delete": function(callback) {
return jQuery.ajax("/t/" + (this.get('id')), {
type: 'DELETE',
success: function() {
return typeof callback === "function" ? callback() : void 0;
}
});
},
/* one instead. This is likely happening due to a deleted post.
*/
// Load the posts for this topic
loadPosts: function(opts) {
var _this = this;
if (!opts) {
opts = {};
}
opts.nearPost = parseInt(opts.nearPost, 10);
closestPostNumber = 0;
postDiff = Number.MAX_VALUE;
result.posts.each(function(p) {
var diff;
diff = Math.abs(p.post_number - opts.nearPost);
if (diff < postDiff) {
postDiff = diff;
closestPostNumber = p.post_number;
if (diff === 0) {
return false;
}
// Load the first post by default
if (!opts.bestOf) {
if (!opts.nearPost) opts.nearPost = 1
}
// If we already have that post in the DOM, jump to it
if (Discourse.TopicView.scrollTo(this.get('id'), opts.nearPost)) return;
return Discourse.Topic.find(this.get('id'), {
nearPost: opts.nearPost,
bestOf: opts.bestOf,
trackVisit: opts.trackVisit
}).then(function(result) {
// If loading the topic succeeded...
// Update the slug if different
var closestPostNumber, lastPost, postDiff;
if (result.slug) {
_this.set('slug', result.slug);
}
// If we want to scroll to a post that doesn't exist, just pop them to the closest
// one instead. This is likely happening due to a deleted post.
opts.nearPost = parseInt(opts.nearPost, 10);
closestPostNumber = 0;
postDiff = Number.MAX_VALUE;
result.posts.each(function(p) {
var diff;
diff = Math.abs(p.post_number - opts.nearPost);
if (diff < postDiff) {
postDiff = diff;
closestPostNumber = p.post_number;
if (diff === 0) {
return false;
}
});
opts.nearPost = closestPostNumber;
if (_this.get('participants')) {
_this.get('participants').clear();
}
if (result.suggested_topics) {
_this.set('suggested_topics', Em.A());
}
_this.mergeAttributes(result, {
suggested_topics: Discourse.Topic
});
_this.set('posts', Em.A());
if (opts.trackVisit && result.draft && result.draft.length > 0) {
Discourse.openComposer({
draft: Discourse.Draft.getLocal(result.draft_key, result.draft),
draftKey: result.draft_key,
draftSequence: result.draft_sequence,
topic: _this,
ignoreIfChanged: true
});
}
/* Okay this is weird, but let's store the length of the next post
*/
/* when there
*/
lastPost = null;
result.posts.each(function(p) {
var post;
p.scrollToAfterInsert = opts.nearPost;
post = Discourse.Post.create(p);
post.set('topic', _this);
_this.get('posts').pushObject(post);
lastPost = post;
});
return _this.set('loaded', true);
}, function(result) {
_this.set('missing', true);
return _this.set('message', Em.String.i18n('topic.not_found.description'));
});
},
notificationReasonText: (function() {
var locale_string;
locale_string = "topic.notifications.reasons." + this.notification_level;
if (typeof this.notifications_reason_id === 'number') {
locale_string += "_" + this.notifications_reason_id;
opts.nearPost = closestPostNumber;
if (_this.get('participants')) {
_this.get('participants').clear();
}
return Em.String.i18n(locale_string, {
username: Discourse.currentUser.username.toLowerCase()
});
}).property('notifications_reason_id'),
updateNotifications: function(v) {
this.set('notification_level', v);
this.set('notifications_reason_id', null);
return jQuery.ajax({
url: "/t/" + (this.get('id')) + "/notifications",
type: 'POST',
data: {
notification_level: v
}
});
},
/* use to add post to topics protecting from dupes
*/
pushPosts: function(newPosts) {
var map, posts;
map = {};
posts = this.get('posts');
posts.each(function(p) {
map["" + p.post_number] = true;
});
return newPosts.each(function(p) {
if (!map[p.get('post_number')]) {
return posts.pushObject(p);
}
});
},
/* Is the reply to a post directly below it?
*/
isReplyDirectlyBelow: function(post) {
var postBelow, posts;
posts = this.get('posts');
if (!posts) {
return;
if (result.suggested_topics) {
_this.set('suggested_topics', Em.A());
}
postBelow = posts[posts.indexOf(post) + 1];
/* If the post directly below's reply_to_post_number is our post number, it's
considered directly below. */
return (postBelow ? postBelow.get('reply_to_post_number') : void 0) === post.get('post_number');
_this.mergeAttributes(result, {
suggested_topics: Discourse.Topic
});
_this.set('posts', Em.A());
if (opts.trackVisit && result.draft && result.draft.length > 0) {
Discourse.openComposer({
draft: Discourse.Draft.getLocal(result.draft_key, result.draft),
draftKey: result.draft_key,
draftSequence: result.draft_sequence,
topic: _this,
ignoreIfChanged: true
});
}
// Okay this is weird, but let's store the length of the next post when there
lastPost = null;
result.posts.each(function(p) {
var post;
p.scrollToAfterInsert = opts.nearPost;
post = Discourse.Post.create(p);
post.set('topic', _this);
_this.get('posts').pushObject(post);
lastPost = post;
});
return _this.set('loaded', true);
}, function(result) {
_this.set('missing', true);
return _this.set('message', Em.String.i18n('topic.not_found.description'));
});
},
notificationReasonText: (function() {
var locale_string;
locale_string = "topic.notifications.reasons." + this.notification_level;
if (typeof this.notifications_reason_id === 'number') {
locale_string += "_" + this.notifications_reason_id;
}
});
return Em.String.i18n(locale_string, { username: Discourse.currentUser.username.toLowerCase() });
}).property('notifications_reason_id'),
window.Discourse.Topic.reopenClass({
NotificationLevel: {
WATCHING: 3,
TRACKING: 2,
REGULAR: 1,
MUTE: 0
},
/* Load a topic, but accepts a set of filters
*/
/* options:
*/
/* onLoad - the callback after the topic is loaded
*/
find: function(topicId, opts) {
var data, promise, url,
_this = this;
url = "/t/" + topicId;
if (opts.nearPost) {
url += "/" + opts.nearPost;
updateNotifications: function(v) {
this.set('notification_level', v);
this.set('notifications_reason_id', null);
return jQuery.ajax({
url: "/t/" + (this.get('id')) + "/notifications",
type: 'POST',
data: {
notification_level: v
}
data = {};
if (opts.postsAfter) {
data.posts_after = opts.postsAfter;
}
if (opts.postsBefore) {
data.posts_before = opts.postsBefore;
}
if (opts.trackVisit) {
data.track_visit = true;
}
/* Add username filters if we have them
*/
});
},
if (opts.userFilters && opts.userFilters.length > 0) {
data.username_filters = [];
opts.userFilters.forEach(function(username) {
return data.username_filters.push(username);
});
// use to add post to topics protecting from dupes
pushPosts: function(newPosts) {
var map, posts;
map = {};
posts = this.get('posts');
posts.each(function(p) {
map["" + p.post_number] = true;
});
return newPosts.each(function(p) {
if (!map[p.get('post_number')]) {
return posts.pushObject(p);
}
/* Add the best of filter if we have it
*/
});
},
if (opts.bestOf === true) {
data.best_of = true;
}
/* Check the preload store. If not, load it via JSON
*/
// Is the reply to a post directly below it?
isReplyDirectlyBelow: function(post) {
var postBelow, posts;
posts = this.get('posts');
if (!posts) {
return;
}
postBelow = posts[posts.indexOf(post) + 1];
// If the post directly below's reply_to_post_number is our post number, it's
// considered directly below.
return (postBelow ? postBelow.get('reply_to_post_number') : void 0) === post.get('post_number');
}
});
promise = new RSVP.Promise();
PreloadStore.get("topic_" + topicId, function() {
return jQuery.getJSON(url + ".json", data);
}).then(function(result) {
var first;
first = result.posts.first();
if (first && opts && opts.bestOf) {
first.bestOfFirst = true;
}
return promise.resolve(result);
}, function(result) {
return promise.reject(result);
});
return promise;
},
/* Create a topic from posts
*/
window.Discourse.Topic.reopenClass({
NotificationLevel: {
WATCHING: 3,
TRACKING: 2,
REGULAR: 1,
MUTE: 0
},
movePosts: function(topicId, title, postIds) {
return jQuery.ajax("/t/" + topicId + "/move-posts", {
type: 'POST',
data: {
title: title,
post_ids: postIds
}
});
},
create: function(obj, topicView) {
var _this = this;
return Object.tap(this._super(obj), function(result) {
if (result.participants) {
result.participants = result.participants.map(function(u) {
return Discourse.User.create(u);
});
result.fewParticipants = Em.A();
return result.participants.each(function(p) {
if (result.fewParticipants.length >= 8) {
return false;
}
result.fewParticipants.pushObject(p);
return true;
});
}
// Load a topic, but accepts a set of filters
// options:
// onLoad - the callback after the topic is loaded
find: function(topicId, opts) {
var data, promise, url,
_this = this;
url = "/t/" + topicId;
if (opts.nearPost) {
url += "/" + opts.nearPost;
}
data = {};
if (opts.postsAfter) {
data.posts_after = opts.postsAfter;
}
if (opts.postsBefore) {
data.posts_before = opts.postsBefore;
}
if (opts.trackVisit) {
data.track_visit = true;
}
// Add username filters if we have them
if (opts.userFilters && opts.userFilters.length > 0) {
data.username_filters = [];
opts.userFilters.forEach(function(username) {
return data.username_filters.push(username);
});
}
});
}).call(this);
// Add the best of filter if we have it
if (opts.bestOf === true) {
data.best_of = true;
}
// Check the preload store. If not, load it via JSON
promise = new RSVP.Promise();
PreloadStore.get("topic_" + topicId, function() {
return jQuery.getJSON(url + ".json", data);
}).then(function(result) {
var first;
first = result.posts.first();
if (first && opts && opts.bestOf) {
first.bestOfFirst = true;
}
return promise.resolve(result);
}, function(result) {
return promise.reject(result);
});
return promise;
},
// Create a topic from posts
movePosts: function(topicId, title, postIds) {
return jQuery.ajax("/t/" + topicId + "/move-posts", {
type: 'POST',
data: {
title: title,
post_ids: postIds
}
});
},
create: function(obj, topicView) {
var _this = this;
return Object.tap(this._super(obj), function(result) {
if (result.participants) {
result.participants = result.participants.map(function(u) {
return Discourse.User.create(u);
});
result.fewParticipants = Em.A();
return result.participants.each(function(p) {
if (result.fewParticipants.length >= 8) {
return false;
}
result.fewParticipants.pushObject(p);
return true;
});
}
});
}
});

View file

@ -1,119 +1,130 @@
(function() {
/**
A data model representing a list of topics
window.Discourse.TopicList = Discourse.Model.extend({
loadMoreTopics: function() {
var moreUrl, promise,
_this = this;
promise = new RSVP.Promise();
if (moreUrl = this.get('more_topics_url')) {
Discourse.replaceState("/" + (this.get('filter')) + "/more");
jQuery.ajax(moreUrl, {
success: function(result) {
var newTopics, topicIds, topics;
if (result) {
newTopics = Discourse.TopicList.topicsFrom(result);
topics = _this.get('topics');
topicIds = [];
topics.each(function(t) {
topicIds[t.get('id')] = true;
});
newTopics.each(function(t) {
if (!topicIds[t.get('id')]) {
return topics.pushObject(t);
}
});
_this.set('more_topics_url', result.topic_list.more_topics_url);
Discourse.set('transient.topicsList', _this);
}
return promise.resolve(result.topic_list.more_topics_url ? true : false);
@class TopicList
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.TopicList = Discourse.Model.extend({
loadMoreTopics: function() {
var moreUrl, promise,
_this = this;
promise = new RSVP.Promise();
if (moreUrl = this.get('more_topics_url')) {
Discourse.replaceState("/" + (this.get('filter')) + "/more");
jQuery.ajax(moreUrl, {
success: function(result) {
var newTopics, topicIds, topics;
if (result) {
newTopics = Discourse.TopicList.topicsFrom(result);
topics = _this.get('topics');
topicIds = [];
topics.each(function(t) {
topicIds[t.get('id')] = true;
});
newTopics.each(function(t) {
if (!topicIds[t.get('id')]) {
return topics.pushObject(t);
}
});
_this.set('more_topics_url', result.topic_list.more_topics_url);
Discourse.set('transient.topicsList', _this);
}
});
} else {
promise.resolve(false);
}
return promise;
},
insert: function(json) {
var newTopic;
newTopic = Discourse.TopicList.decodeTopic(json);
/* New Topics are always unseen
*/
newTopic.set('unseen', true);
newTopic.set('highlightAfterInsert', true);
return this.get('inserted').unshiftObject(newTopic);
}
});
window.Discourse.TopicList.reopenClass({
decodeTopic: function(result) {
var categories, topic, users;
categories = this.extractByKey(result.categories, Discourse.Category);
users = this.extractByKey(result.users, Discourse.User);
topic = result.topic_list_item;
topic.category = categories[topic.category];
topic.posters.each(function(p) {
p.user = users[p.user_id] || users[p.user];
});
return Discourse.Topic.create(topic);
},
topicsFrom: function(result) {
/* Stitch together our side loaded data
*/
var categories, topics, users;
categories = this.extractByKey(result.categories, Discourse.Category);
users = this.extractByKey(result.users, Discourse.User);
topics = Em.A();
result.topic_list.topics.each(function(ft) {
ft.category = categories[ft.category_id];
ft.posters.each(function(p) {
p.user = users[p.user_id];
});
return topics.pushObject(Discourse.Topic.create(ft));
});
return topics;
},
list: function(menuItem) {
var filter, found, list, promise, topic_list, url;
filter = menuItem.name;
topic_list = Discourse.TopicList.create();
topic_list.set('inserted', Em.A());
topic_list.set('filter', filter);
url = "/" + filter + ".json";
if (menuItem.filters && menuItem.filters.length > 0) {
url += "?exclude_category=" + menuItem.filters[0].substring(1);
}
if (list = Discourse.get('transient.topicsList')) {
if ((list.get('filter') === filter) && window.location.pathname.indexOf('more') > 0) {
promise = new RSVP.Promise();
list.set('loaded', true);
promise.resolve(list);
return promise;
return promise.resolve(result.topic_list.more_topics_url ? true : false);
}
}
Discourse.set('transient.topicsList', null);
Discourse.set('transient.topicListScrollPos', null);
promise = new RSVP.Promise();
found = PreloadStore.contains('topic_list');
PreloadStore.get("topic_list", function() {
return jQuery.getJSON(url);
}).then(function(result) {
topic_list.set('topics', Discourse.TopicList.topicsFrom(result));
topic_list.set('can_create_topic', result.topic_list.can_create_topic);
topic_list.set('more_topics_url', result.topic_list.more_topics_url);
topic_list.set('filter_summary', result.topic_list.filter_summary);
topic_list.set('draft_key', result.topic_list.draft_key);
topic_list.set('draft_sequence', result.topic_list.draft_sequence);
topic_list.set('draft', result.topic_list.draft);
if (result.topic_list.filtered_category) {
topic_list.set('category', Discourse.Category.create(result.topic_list.filtered_category));
}
topic_list.set('loaded', true);
return promise.resolve(topic_list);
});
return promise;
} else {
promise.resolve(false);
}
});
return promise;
},
insert: function(json) {
var newTopic;
newTopic = Discourse.TopicList.decodeTopic(json);
/* New Topics are always unseen
*/
newTopic.set('unseen', true);
newTopic.set('highlightAfterInsert', true);
return this.get('inserted').unshiftObject(newTopic);
}
});
Discourse.TopicList.reopenClass({
decodeTopic: function(result) {
var categories, topic, users;
categories = this.extractByKey(result.categories, Discourse.Category);
users = this.extractByKey(result.users, Discourse.User);
topic = result.topic_list_item;
topic.category = categories[topic.category];
topic.posters.each(function(p) {
p.user = users[p.user_id] || users[p.user];
});
return Discourse.Topic.create(topic);
},
topicsFrom: function(result) {
// Stitch together our side loaded data
var categories, topics, users;
categories = this.extractByKey(result.categories, Discourse.Category);
users = this.extractByKey(result.users, Discourse.User);
topics = Em.A();
result.topic_list.topics.each(function(ft) {
ft.category = categories[ft.category_id];
ft.posters.each(function(p) {
p.user = users[p.user_id];
});
return topics.pushObject(Discourse.Topic.create(ft));
});
return topics;
},
list: function(menuItem) {
var filter, found, list, promise, topic_list, url;
filter = menuItem.name;
topic_list = Discourse.TopicList.create();
topic_list.set('inserted', Em.A());
topic_list.set('filter', filter);
url = "/" + filter + ".json";
if (menuItem.filters && menuItem.filters.length > 0) {
url += "?exclude_category=" + menuItem.filters[0].substring(1);
}
if (list = Discourse.get('transient.topicsList')) {
if ((list.get('filter') === filter) && window.location.pathname.indexOf('more') > 0) {
promise = new RSVP.Promise();
list.set('loaded', true);
promise.resolve(list);
return promise;
}
}
Discourse.set('transient.topicsList', null);
Discourse.set('transient.topicListScrollPos', null);
promise = new RSVP.Promise();
found = PreloadStore.contains('topic_list');
PreloadStore.get("topic_list", function() {
return jQuery.getJSON(url);
}).then(function(result) {
topic_list.set('topics', Discourse.TopicList.topicsFrom(result));
topic_list.set('can_create_topic', result.topic_list.can_create_topic);
topic_list.set('more_topics_url', result.topic_list.more_topics_url);
topic_list.set('filter_summary', result.topic_list.filter_summary);
topic_list.set('draft_key', result.topic_list.draft_key);
topic_list.set('draft_sequence', result.topic_list.draft_sequence);
topic_list.set('draft', result.topic_list.draft);
if (result.topic_list.filtered_category) {
topic_list.set('category', Discourse.Category.create(result.topic_list.filtered_category));
}
topic_list.set('loaded', true);
return promise.resolve(topic_list);
});
return promise;
}
});
}).call(this);

View file

@ -1,311 +1,322 @@
(function() {
/**
A data model representing a user on Discourse
window.Discourse.User = Discourse.Model.extend({
avatarLarge: (function() {
return Discourse.Utilities.avatarUrl(this.get('username'), 'large', this.get('avatar_template'));
}).property('username'),
avatarSmall: (function() {
return Discourse.Utilities.avatarUrl(this.get('username'), 'small', this.get('avatar_template'));
}).property('username'),
websiteName: (function() {
return this.get('website').split("/")[2];
}).property('website'),
path: (function() {
return "/users/" + (this.get('username_lower'));
}).property('username'),
username_lower: (function() {
return this.get('username').toLowerCase();
}).property('username'),
trustLevel: (function() {
return Discourse.get('site.trust_levels').findProperty('id', this.get('trust_level'));
}).property('trust_level'),
changeUsername: function(newUsername) {
return jQuery.ajax({
url: "/users/" + (this.get('username_lower')) + "/preferences/username",
type: 'PUT',
data: {
new_username: newUsername
}
});
},
changeEmail: function(email) {
return jQuery.ajax({
url: "/users/" + (this.get('username_lower')) + "/preferences/email",
type: 'PUT',
data: {
email: email
}
});
},
copy: function(deep) {
return Discourse.User.create(this.getProperties(Ember.keys(this)));
},
save: function(finished) {
var _this = this;
return jQuery.ajax("/users/" + this.get('username').toLowerCase(), {
data: this.getProperties('auto_track_topics_after_msecs',
'bio_raw',
'website',
'name',
'email_digests',
'email_direct',
'email_private_messages',
'digest_after_days',
'new_topic_duration_minutes'),
type: 'PUT',
success: function() {
return finished(true);
},
error: function() {
return finished(false);
}
});
},
changePassword: function(callback) {
var good;
good = false;
return jQuery.ajax({
url: '/session/forgot_password',
dataType: 'json',
data: {
username: this.get('username')
},
type: 'POST',
success: function() {
good = true;
},
complete: function() {
var message;
message = "error";
if (good) {
message = "email sent";
}
return callback(message);
}
});
},
filterStream: function(filter) {
if (Discourse.UserAction.statGroups[filter]) {
filter = Discourse.UserAction.statGroups[filter].join(",");
}
this.set('streamFilter', filter);
this.set('stream', Em.A());
return this.loadMoreUserActions();
},
loadUserAction: function(id) {
var stream,
_this = this;
stream = this.get('stream');
return jQuery.ajax({
url: "/user_actions/" + id + ".json",
dataType: 'json',
cache: 'false',
success: function(result) {
if (result) {
var action;
if ((_this.get('streamFilter') || result.action_type) !== result.action_type) {
return;
}
action = Em.A();
action.pushObject(Discourse.UserAction.create(result));
action = Discourse.UserAction.collapseStream(action);
return stream.insertAt(0, action[0]);
}
}
});
},
loadMoreUserActions: function(callback) {
var stream, url,
_this = this;
stream = this.get('stream');
if (!stream) {
return;
}
url = "/user_actions?offset=" + stream.length + "&user_id=" + (this.get("id"));
if (this.get('streamFilter')) {
url += "&filter=" + (this.get('streamFilter'));
}
return jQuery.ajax({
url: url,
dataType: 'json',
cache: 'false',
success: function(result) {
var copy;
if (result && result.user_actions && result.user_actions.each) {
copy = Em.A();
result.user_actions.each(function(i) {
return copy.pushObject(Discourse.UserAction.create(i));
});
copy = Discourse.UserAction.collapseStream(copy);
stream.pushObjects(copy);
_this.set('stream', stream);
}
if (callback) {
return callback();
}
}
});
},
statsCountNonPM: (function() {
var stats, total;
total = 0;
if (!(stats = this.get('stats'))) {
return 0;
}
this.get('stats').each(function(s) {
if (!s.get("isPM")) {
total += parseInt(s.count, 10);
}
});
return total;
}).property('stats.@each'),
statsExcludingPms: (function() {
var r;
r = [];
if (this.blank('stats')) {
return r;
}
this.get('stats').each(function(s) {
if (!s.get('isPM')) {
return r.push(s);
}
});
return r;
}).property('stats.@each'),
statsPmsOnly: (function() {
var r;
r = [];
if (this.blank('stats')) {
return r;
}
this.get('stats').each(function(s) {
if (s.get('isPM')) {
return r.push(s);
}
});
return r;
}).property('stats.@each'),
inboxCount: (function() {
var r;
r = 0;
this.get('stats').each(function(s) {
if (s.action_type === Discourse.UserAction.GOT_PRIVATE_MESSAGE) {
r = s.count;
return false;
}
});
return r;
}).property('stats.@each'),
sentItemsCount: (function() {
var r;
r = 0;
this.get('stats').each(function(s) {
if (s.action_type === Discourse.UserAction.NEW_PRIVATE_MESSAGE) {
r = s.count;
return false;
}
});
return r;
}).property('stats.@each')
});
@class User
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.User = Discourse.Model.extend({
window.Discourse.User.reopenClass({
checkUsername: function(username, email) {
return jQuery.ajax({
url: '/users/check_username',
type: 'GET',
data: {
username: username,
email: email
}
});
},
groupStats: function(stats) {
var g,
_this = this;
g = {};
stats.each(function(s) {
var c, found, k, v, _ref;
found = false;
_ref = Discourse.UserAction.statGroups;
for (k in _ref) {
v = _ref[k];
if (v.contains(s.action_type)) {
found = true;
if (!g[k]) {
g[k] = Em.Object.create({
description: Em.String.i18n("user_action_descriptions." + k),
count: 0,
action_type: parseInt(k, 10)
});
}
g[k].count += parseInt(s.count, 10);
c = g[k].count;
if (s.action_type === k) {
g[k] = s;
s.count = c;
}
}
}
if (!found) {
g[s.action_type] = s;
}
});
return stats.map(function(s) {
return g[s.action_type];
}).exclude(function(s) {
return !s;
});
},
find: function(username) {
var promise,
_this = this;
promise = new RSVP.Promise();
jQuery.ajax({
url: "/users/" + username + '.json',
success: function(json) {
/* todo: decompose to object
*/
avatarLarge: (function() {
return Discourse.Utilities.avatarUrl(this.get('username'), 'large', this.get('avatar_template'));
}).property('username'),
var user;
json.user.stats = _this.groupStats(json.user.stats.map(function(s) {
var obj;
obj = Em.Object.create(s);
obj.isPM = obj.action_type === Discourse.UserAction.NEW_PRIVATE_MESSAGE || obj.action_type === Discourse.UserAction.GOT_PRIVATE_MESSAGE;
return obj;
}));
if (json.user.stream) {
json.user.stream = Discourse.UserAction.collapseStream(json.user.stream.map(function(ua) {
return Discourse.UserAction.create(ua);
}));
}
user = Discourse.User.create(json.user);
return promise.resolve(user);
},
error: function(xhr) {
return promise.reject(xhr);
avatarSmall: (function() {
return Discourse.Utilities.avatarUrl(this.get('username'), 'small', this.get('avatar_template'));
}).property('username'),
websiteName: (function() {
return this.get('website').split("/")[2];
}).property('website'),
path: (function() {
return "/users/" + (this.get('username_lower'));
}).property('username'),
username_lower: (function() {
return this.get('username').toLowerCase();
}).property('username'),
trustLevel: (function() {
return Discourse.get('site.trust_levels').findProperty('id', this.get('trust_level'));
}).property('trust_level'),
changeUsername: function(newUsername) {
return jQuery.ajax({
url: "/users/" + (this.get('username_lower')) + "/preferences/username",
type: 'PUT',
data: {
new_username: newUsername
}
});
},
changeEmail: function(email) {
return jQuery.ajax({
url: "/users/" + (this.get('username_lower')) + "/preferences/email",
type: 'PUT',
data: {
email: email
}
});
},
copy: function(deep) {
return Discourse.User.create(this.getProperties(Ember.keys(this)));
},
save: function(finished) {
var _this = this;
return jQuery.ajax("/users/" + this.get('username').toLowerCase(), {
data: this.getProperties('auto_track_topics_after_msecs',
'bio_raw',
'website',
'name',
'email_digests',
'email_direct',
'email_private_messages',
'digest_after_days',
'new_topic_duration_minutes'),
type: 'PUT',
success: function() { return finished(true); },
error: function() { return finished(false); }
});
},
changePassword: function(callback) {
var good;
good = false;
return jQuery.ajax({
url: '/session/forgot_password',
dataType: 'json',
data: {
username: this.get('username')
},
type: 'POST',
success: function() { good = true; },
complete: function() {
var message;
message = "error";
if (good) {
message = "email sent";
}
});
return promise;
},
createAccount: function(name, email, password, username, passwordConfirm, challenge) {
return jQuery.ajax({
url: '/users',
dataType: 'json',
data: {
name: name,
email: email,
password: password,
username: username,
password_confirmation: passwordConfirm,
challenge: challenge
},
type: 'POST'
});
return callback(message);
}
});
},
filterStream: function(filter) {
if (Discourse.UserAction.statGroups[filter]) {
filter = Discourse.UserAction.statGroups[filter].join(",");
}
});
this.set('streamFilter', filter);
this.set('stream', Em.A());
return this.loadMoreUserActions();
},
}).call(this);
loadUserAction: function(id) {
var stream,
_this = this;
stream = this.get('stream');
return jQuery.ajax({
url: "/user_actions/" + id + ".json",
dataType: 'json',
cache: 'false',
success: function(result) {
if (result) {
var action;
if ((_this.get('streamFilter') || result.action_type) !== result.action_type) {
return;
}
action = Em.A();
action.pushObject(Discourse.UserAction.create(result));
action = Discourse.UserAction.collapseStream(action);
return stream.insertAt(0, action[0]);
}
}
});
},
loadMoreUserActions: function(callback) {
var stream, url,
_this = this;
stream = this.get('stream');
if (!stream) return;
url = "/user_actions?offset=" + stream.length + "&user_id=" + (this.get("id"));
if (this.get('streamFilter')) {
url += "&filter=" + (this.get('streamFilter'));
}
return jQuery.ajax({
url: url,
dataType: 'json',
cache: 'false',
success: function(result) {
var copy;
if (result && result.user_actions && result.user_actions.each) {
copy = Em.A();
result.user_actions.each(function(i) {
return copy.pushObject(Discourse.UserAction.create(i));
});
copy = Discourse.UserAction.collapseStream(copy);
stream.pushObjects(copy);
_this.set('stream', stream);
}
if (callback) {
return callback();
}
}
});
},
statsCountNonPM: (function() {
var stats, total;
total = 0;
if (!(stats = this.get('stats'))) return 0;
this.get('stats').each(function(s) {
if (!s.get("isPM")) {
total += parseInt(s.count, 10);
}
});
return total;
}).property('stats.@each'),
statsExcludingPms: (function() {
var r;
r = [];
if (this.blank('stats')) return r;
this.get('stats').each(function(s) {
if (!s.get('isPM')) {
return r.push(s);
}
});
return r;
}).property('stats.@each'),
statsPmsOnly: (function() {
var r;
r = [];
if (this.blank('stats')) return r;
this.get('stats').each(function(s) {
if (s.get('isPM')) return r.push(s);
});
return r;
}).property('stats.@each'),
inboxCount: (function() {
var r;
r = 0;
this.get('stats').each(function(s) {
if (s.action_type === Discourse.UserAction.GOT_PRIVATE_MESSAGE) {
r = s.count;
return false;
}
});
return r;
}).property('stats.@each'),
sentItemsCount: (function() {
var r;
r = 0;
this.get('stats').each(function(s) {
if (s.action_type === Discourse.UserAction.NEW_PRIVATE_MESSAGE) {
r = s.count;
return false;
}
});
return r;
}).property('stats.@each')
});
Discourse.User.reopenClass({
checkUsername: function(username, email) {
return jQuery.ajax({
url: '/users/check_username',
type: 'GET',
data: {
username: username,
email: email
}
});
},
groupStats: function(stats) {
var g,
_this = this;
g = {};
stats.each(function(s) {
var c, found, k, v, _ref;
found = false;
_ref = Discourse.UserAction.statGroups;
for (k in _ref) {
v = _ref[k];
if (v.contains(s.action_type)) {
found = true;
if (!g[k]) {
g[k] = Em.Object.create({
description: Em.String.i18n("user_action_descriptions." + k),
count: 0,
action_type: parseInt(k, 10)
});
}
g[k].count += parseInt(s.count, 10);
c = g[k].count;
if (s.action_type === k) {
g[k] = s;
s.count = c;
}
}
}
if (!found) {
g[s.action_type] = s;
}
});
return stats.map(function(s) {
return g[s.action_type];
}).exclude(function(s) {
return !s;
});
},
find: function(username) {
var promise,
_this = this;
promise = new RSVP.Promise();
jQuery.ajax({
url: "/users/" + username + '.json',
success: function(json) {
// todo: decompose to object
var user;
json.user.stats = _this.groupStats(json.user.stats.map(function(s) {
var obj;
obj = Em.Object.create(s);
obj.isPM = obj.action_type === Discourse.UserAction.NEW_PRIVATE_MESSAGE || obj.action_type === Discourse.UserAction.GOT_PRIVATE_MESSAGE;
return obj;
}));
if (json.user.stream) {
json.user.stream = Discourse.UserAction.collapseStream(json.user.stream.map(function(ua) {
return Discourse.UserAction.create(ua);
}));
}
user = Discourse.User.create(json.user);
return promise.resolve(user);
},
error: function(xhr) {
return promise.reject(xhr);
}
});
return promise;
},
createAccount: function(name, email, password, username, passwordConfirm, challenge) {
return jQuery.ajax({
url: '/users',
dataType: 'json',
data: {
name: name,
email: email,
password: password,
username: username,
password_confirmation: passwordConfirm,
challenge: challenge
},
type: 'POST'
});
}
});

View file

@ -1,139 +1,150 @@
(function() {
/**
A data model representing actions users have taken
window.Discourse.UserAction = Discourse.Model.extend({
postUrl: (function() {
return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('post_number'));
}).property(),
replyUrl: (function() {
return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('reply_to_post_number'));
}).property(),
isPM: (function() {
var a;
a = this.get('action_type');
return a === Discourse.UserAction.NEW_PRIVATE_MESSAGE || a === Discourse.UserAction.GOT_PRIVATE_MESSAGE;
}).property(),
isPostAction: (function() {
var a;
a = this.get('action_type');
return a === Discourse.UserAction.RESPONSE || a === Discourse.UserAction.POST || a === Discourse.UserAction.NEW_TOPIC;
}).property(),
addChild: function(action) {
var bucket, current, groups, ua;
groups = this.get("childGroups");
if (!groups) {
groups = {
likes: Discourse.UserActionGroup.create({
icon: "icon-heart"
}),
stars: Discourse.UserActionGroup.create({
icon: "icon-star"
}),
edits: Discourse.UserActionGroup.create({
icon: "icon-pencil"
}),
bookmarks: Discourse.UserActionGroup.create({
icon: "icon-bookmark"
})
};
}
this.set("childGroups", groups);
ua = Discourse.UserAction;
bucket = (function() {
switch (action.action_type) {
case ua.LIKE:
case ua.WAS_LIKED:
return "likes";
case ua.STAR:
return "stars";
case ua.EDIT:
return "edits";
case ua.BOOKMARK:
return "bookmarks";
}
})();
current = groups[bucket];
if (current) {
current.push(action);
}
},
children: (function() {
var g, rval;
g = this.get("childGroups");
rval = [];
if (g) {
rval = [g.likes, g.stars, g.edits, g.bookmarks].filter(function(i) {
return i.get("items") && i.get("items").length > 0;
});
}
return rval;
}).property("childGroups"),
switchToActing: function() {
this.set('username', this.get('acting_username'));
this.set('avatar_template', this.get('acting_avatar_template'));
return this.set('name', this.get('acting_name'));
@class UserAction
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.UserAction = Discourse.Model.extend({
postUrl: (function() {
return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('post_number'));
}).property(),
replyUrl: (function() {
return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('reply_to_post_number'));
}).property(),
isPM: (function() {
var a = this.get('action_type');
return a === Discourse.UserAction.NEW_PRIVATE_MESSAGE || a === Discourse.UserAction.GOT_PRIVATE_MESSAGE;
}).property(),
isPostAction: (function() {
var a;
a = this.get('action_type');
return a === Discourse.UserAction.RESPONSE || a === Discourse.UserAction.POST || a === Discourse.UserAction.NEW_TOPIC;
}).property(),
addChild: function(action) {
var bucket, current, groups, ua;
groups = this.get("childGroups");
if (!groups) {
groups = {
likes: Discourse.UserActionGroup.create({
icon: "icon-heart"
}),
stars: Discourse.UserActionGroup.create({
icon: "icon-star"
}),
edits: Discourse.UserActionGroup.create({
icon: "icon-pencil"
}),
bookmarks: Discourse.UserActionGroup.create({
icon: "icon-bookmark"
})
};
}
});
this.set("childGroups", groups);
ua = Discourse.UserAction;
bucket = (function() {
switch (action.action_type) {
case ua.LIKE:
case ua.WAS_LIKED:
return "likes";
case ua.STAR:
return "stars";
case ua.EDIT:
return "edits";
case ua.BOOKMARK:
return "bookmarks";
}
})();
current = groups[bucket];
if (current) {
current.push(action);
}
},
window.Discourse.UserAction.reopenClass({
collapseStream: function(stream) {
var collapse, collapsed, pos, uniq;
collapse = [this.LIKE, this.WAS_LIKED, this.STAR, this.EDIT, this.BOOKMARK];
uniq = {};
collapsed = Em.A();
pos = 0;
stream.each(function(item) {
var current, found, key;
key = "" + item.topic_id + "-" + item.post_number;
found = uniq[key];
if (found === void 0) {
if (collapse.indexOf(item.action_type) >= 0) {
current = Discourse.UserAction.create(item);
current.set('action_type', null);
current.set('description', null);
item.switchToActing();
current.addChild(item);
} else {
current = item;
}
uniq[key] = pos;
collapsed[pos] = current;
pos += 1;
} else {
if (collapse.indexOf(item.action_type) >= 0) {
item.switchToActing();
return collapsed[found].addChild(item);
} else {
collapsed[found].set('action_type', item.get('action_type'));
return collapsed[found].set('description', item.get('description'));
}
}
children: (function() {
var g, rval;
g = this.get("childGroups");
rval = [];
if (g) {
rval = [g.likes, g.stars, g.edits, g.bookmarks].filter(function(i) {
return i.get("items") && i.get("items").length > 0;
});
return collapsed;
},
/* in future we should be sending this through from the server
*/
}
return rval;
}).property("childGroups"),
LIKE: 1,
WAS_LIKED: 2,
BOOKMARK: 3,
NEW_TOPIC: 4,
POST: 5,
RESPONSE: 6,
MENTION: 7,
QUOTE: 9,
STAR: 10,
EDIT: 11,
NEW_PRIVATE_MESSAGE: 12,
GOT_PRIVATE_MESSAGE: 13
});
switchToActing: function() {
this.set('username', this.get('acting_username'));
this.set('avatar_template', this.get('acting_avatar_template'));
this.set('name', this.get('acting_name'));
}
});
Discourse.UserAction.reopenClass({
collapseStream: function(stream) {
var collapse, collapsed, pos, uniq;
collapse = [this.LIKE, this.WAS_LIKED, this.STAR, this.EDIT, this.BOOKMARK];
uniq = {};
collapsed = Em.A();
pos = 0;
stream.each(function(item) {
var current, found, key;
key = "" + item.topic_id + "-" + item.post_number;
found = uniq[key];
if (found === void 0) {
if (collapse.indexOf(item.action_type) >= 0) {
current = Discourse.UserAction.create(item);
current.set('action_type', null);
current.set('description', null);
item.switchToActing();
current.addChild(item);
} else {
current = item;
}
uniq[key] = pos;
collapsed[pos] = current;
pos += 1;
} else {
if (collapse.indexOf(item.action_type) >= 0) {
item.switchToActing();
return collapsed[found].addChild(item);
} else {
collapsed[found].set('action_type', item.get('action_type'));
return collapsed[found].set('description', item.get('description'));
}
}
});
return collapsed;
},
// in future we should be sending this through from the server
LIKE: 1,
WAS_LIKED: 2,
BOOKMARK: 3,
NEW_TOPIC: 4,
POST: 5,
RESPONSE: 6,
MENTION: 7,
QUOTE: 9,
STAR: 10,
EDIT: 11,
NEW_PRIVATE_MESSAGE: 12,
GOT_PRIVATE_MESSAGE: 13
});
window.Discourse.UserAction.reopenClass({
statGroups: (function() {
var g;
g = {};
g[Discourse.UserAction.RESPONSE] = [Discourse.UserAction.RESPONSE, Discourse.UserAction.MENTION, Discourse.UserAction.QUOTE];
return g;
})()
});
window.Discourse.UserAction.reopenClass({
statGroups: (function() {
var g;
g = {};
g[Discourse.UserAction.RESPONSE] = [Discourse.UserAction.RESPONSE, Discourse.UserAction.MENTION, Discourse.UserAction.QUOTE];
return g;
})()
});
}).call(this);

View file

@ -1,12 +1,18 @@
(function() {
/**
A data model representing a group of UserActions
window.Discourse.UserActionGroup = Discourse.Model.extend({
push: function(item) {
if (!this.items) {
this.items = [];
}
return this.items.push(item);
@class UserActionGroup
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.UserActionGroup = Discourse.Model.extend({
push: function(item) {
if (!this.items) {
this.items = [];
}
});
return this.items.push(item);
}
});
}).call(this);

View file

@ -1,5 +1,11 @@
(function() {
/**
A data model representing a statistic on a UserAction
@class UserActionStat
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.UserActionStat = Discourse.Model.extend({});
window.Discourse.UserActionStat = Discourse.Model.extend({});
}).call(this);

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