mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 09:36:19 -05:00
Clean up JS, add YUIDoc support, automatically create IIFE via asset pipeline
This commit is contained in:
parent
0321643636
commit
e461c84253
217 changed files with 11996 additions and 12131 deletions
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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')
|
||||
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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' });
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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, "<");
|
||||
buf = buf.replace(/>/g, ">");
|
||||
buf = buf.replace(/[ ]/g, "​ ​");
|
||||
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, "<");
|
||||
buf = buf.replace(/>/g, ">");
|
||||
buf = buf.replace(/[ ]/g, "​ ​");
|
||||
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()
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
|
@ -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());
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
59
app/assets/javascripts/discourse/components/mention.js
Normal file
59
app/assets/javascripts/discourse/components/mention.js
Normal 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
|
||||
};
|
||||
})();
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
89
app/assets/javascripts/discourse/components/onebox.js
Normal file
89
app/assets/javascripts/discourse/components/onebox.js
Normal 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
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
|
|
@ -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);
|
|
@ -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");
|
||||
*
|
||||
*
|
||||
* */
|
||||
|
|
|
@ -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 );
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, '@')) + "</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, '@')) + "</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);
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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("—");
|
||||
}
|
||||
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("—");
|
||||
}
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue