diff --git a/app/assets/javascripts/admin/controllers/admin_customize_controller.js b/app/assets/javascripts/admin/controllers/admin_customize_controller.js index dfa0fb9054b..ca5ad7f731f 100644 --- a/app/assets/javascripts/admin/controllers/admin_customize_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_customize_controller.js @@ -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); +}); diff --git a/app/assets/javascripts/admin/controllers/admin_dashboard_controller.js b/app/assets/javascripts/admin/controllers/admin_dashboard_controller.js index 5c00923dcb9..8ec0442a3ba 100644 --- a/app/assets/javascripts/admin/controllers/admin_dashboard_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_dashboard_controller.js @@ -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 +}); diff --git a/app/assets/javascripts/admin/controllers/admin_email_logs_controller.js b/app/assets/javascripts/admin/controllers/admin_email_logs_controller.js index fbe31fed562..ef088cbaa23 100644 --- a/app/assets/javascripts/admin/controllers/admin_email_logs_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_email_logs_controller.js @@ -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; + } + +}); diff --git a/app/assets/javascripts/admin/controllers/admin_flags_controller.js b/app/assets/javascripts/admin/controllers/admin_flags_controller.js index a3a6107ed3f..8663e347329 100644 --- a/app/assets/javascripts/admin/controllers/admin_flags_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_flags_controller.js @@ -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') + +}); diff --git a/app/assets/javascripts/admin/controllers/admin_site_settings_controller.js b/app/assets/javascripts/admin/controllers/admin_site_settings_controller.js index da618df7895..3891f72aa8e 100644 --- a/app/assets/javascripts/admin/controllers/admin_site_settings_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_site_settings_controller.js @@ -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(); + } + +}); diff --git a/app/assets/javascripts/admin/controllers/admin_users_list_controller.js b/app/assets/javascripts/admin/controllers/admin_users_list_controller.js index 535602a60da..f9c40a79d7d 100644 --- a/app/assets/javascripts/admin/controllers/admin_users_list_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_users_list_controller.js @@ -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')); + } + +}); diff --git a/app/assets/javascripts/admin/models/admin_user.js b/app/assets/javascripts/admin/models/admin_user.js index fdc69957ec9..2886f927827 100644 --- a/app/assets/javascripts/admin/models/admin_user.js +++ b/app/assets/javascripts/admin/models/admin_user.js @@ -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; + } +}); diff --git a/app/assets/javascripts/admin/models/email_log.js b/app/assets/javascripts/admin/models/email_log.js index 4169397ae00..1fd292567b4 100644 --- a/app/assets/javascripts/admin/models/email_log.js +++ b/app/assets/javascripts/admin/models/email_log.js @@ -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); diff --git a/app/assets/javascripts/admin/models/flagged_post.js b/app/assets/javascripts/admin/models/flagged_post.js index ef8fc8cfd6e..cf937dbc883 100644 --- a/app/assets/javascripts/admin/models/flagged_post.js +++ b/app/assets/javascripts/admin/models/flagged_post.js @@ -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); diff --git a/app/assets/javascripts/admin/models/site_customization.js b/app/assets/javascripts/admin/models/site_customization.js index 9820ecaf8c3..19f2a3bacfa 100644 --- a/app/assets/javascripts/admin/models/site_customization.js +++ b/app/assets/javascripts/admin/models/site_customization.js @@ -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; + } +}); diff --git a/app/assets/javascripts/admin/models/site_setting.js b/app/assets/javascripts/admin/models/site_setting.js index 155871f7218..86eeaac39f1 100644 --- a/app/assets/javascripts/admin/models/site_setting.js +++ b/app/assets/javascripts/admin/models/site_setting.js @@ -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); diff --git a/app/assets/javascripts/admin/models/version_check.js b/app/assets/javascripts/admin/models/version_check.js index 5692b7d2885..6a42b1eff51 100644 --- a/app/assets/javascripts/admin/models/version_check.js +++ b/app/assets/javascripts/admin/models/version_check.js @@ -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; + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/admin/routes/admin_customize_route.js b/app/assets/javascripts/admin/routes/admin_customize_route.js index cd5f60f5513..e085791561b 100644 --- a/app/assets/javascripts/admin/routes/admin_customize_route.js +++ b/app/assets/javascripts/admin/routes/admin_customize_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_dashboard_route.js b/app/assets/javascripts/admin/routes/admin_dashboard_route.js index 7293f341bc0..49a745eadb2 100644 --- a/app/assets/javascripts/admin/routes/admin_dashboard_route.js +++ b/app/assets/javascripts/admin/routes/admin_dashboard_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_email_logs_route.js b/app/assets/javascripts/admin/routes/admin_email_logs_route.js index 3b763c3f575..f1fa425131d 100644 --- a/app/assets/javascripts/admin/routes/admin_email_logs_route.js +++ b/app/assets/javascripts/admin/routes/admin_email_logs_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_flags_active_route.js b/app/assets/javascripts/admin/routes/admin_flags_active_route.js index 8623feb576e..9ae171a0a3e 100644 --- a/app/assets/javascripts/admin/routes/admin_flags_active_route.js +++ b/app/assets/javascripts/admin/routes/admin_flags_active_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_flags_old_route.js b/app/assets/javascripts/admin/routes/admin_flags_old_route.js index 0ac9079287e..df967f53c83 100644 --- a/app/assets/javascripts/admin/routes/admin_flags_old_route.js +++ b/app/assets/javascripts/admin/routes/admin_flags_old_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_flags_route.js b/app/assets/javascripts/admin/routes/admin_flags_route.js index c78f0caafc8..7def67540d9 100644 --- a/app/assets/javascripts/admin/routes/admin_flags_route.js +++ b/app/assets/javascripts/admin/routes/admin_flags_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_route.js b/app/assets/javascripts/admin/routes/admin_route.js index a168172a3dc..a28ffff2eba 100644 --- a/app/assets/javascripts/admin/routes/admin_route.js +++ b/app/assets/javascripts/admin/routes/admin_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_routes.js b/app/assets/javascripts/admin/routes/admin_routes.js index bebaddcbaaf..215a7ba14c1 100644 --- a/app/assets/javascripts/admin/routes/admin_routes.js +++ b/app/assets/javascripts/admin/routes/admin_routes.js @@ -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' }); + }); + }); + + }); +}); + + diff --git a/app/assets/javascripts/admin/routes/admin_site_settings_route.js b/app/assets/javascripts/admin/routes/admin_site_settings_route.js index 66d51d77919..5043b40a0c7 100644 --- a/app/assets/javascripts/admin/routes/admin_site_settings_route.js +++ b/app/assets/javascripts/admin/routes/admin_site_settings_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_user_route.js b/app/assets/javascripts/admin/routes/admin_user_route.js index 1e67210d82b..343dce69549 100644 --- a/app/assets/javascripts/admin/routes/admin_user_route.js +++ b/app/assets/javascripts/admin/routes/admin_user_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_users_list_active_route.js b/app/assets/javascripts/admin/routes/admin_users_list_active_route.js index 9d80ed52e91..46b23537bf7 100644 --- a/app/assets/javascripts/admin/routes/admin_users_list_active_route.js +++ b/app/assets/javascripts/admin/routes/admin_users_list_active_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_users_list_new_route.js b/app/assets/javascripts/admin/routes/admin_users_list_new_route.js index bc6008f1a4e..48f22a38e22 100644 --- a/app/assets/javascripts/admin/routes/admin_users_list_new_route.js +++ b/app/assets/javascripts/admin/routes/admin_users_list_new_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_users_list_pending_route.js b/app/assets/javascripts/admin/routes/admin_users_list_pending_route.js index 3b5376a64f1..bd2efaf6a4c 100644 --- a/app/assets/javascripts/admin/routes/admin_users_list_pending_route.js +++ b/app/assets/javascripts/admin/routes/admin_users_list_pending_route.js @@ -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); diff --git a/app/assets/javascripts/admin/routes/admin_users_list_route.js b/app/assets/javascripts/admin/routes/admin_users_list_route.js index a08336be0df..757c7efa2ca 100644 --- a/app/assets/javascripts/admin/routes/admin_users_list_route.js +++ b/app/assets/javascripts/admin/routes/admin_users_list_route.js @@ -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); diff --git a/app/assets/javascripts/admin/views/ace_editor_view.js b/app/assets/javascripts/admin/views/ace_editor_view.js index f31c19d57aa..fc9849ab16b 100644 --- a/app/assets/javascripts/admin/views/ace_editor_view.js +++ b/app/assets/javascripts/admin/views/ace_editor_view.js @@ -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("
"); - if (this.get('content')) { - buffer.push(Handlebars.Utils.escapeExpression(this.get('content'))); - } - return buffer.push("
"); - }, - - 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("
"); + if (this.get('content')) { + buffer.push(Handlebars.Utils.escapeExpression(this.get('content'))); + } + return buffer.push("
"); + }, + + 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); diff --git a/app/assets/javascripts/admin/views/admin_customize_view.js b/app/assets/javascripts/admin/views/admin_customize_view.js index 1ab5532b57d..90bb032c2d5 100644 --- a/app/assets/javascripts/admin/views/admin_customize_view.js +++ b/app/assets/javascripts/admin/views/admin_customize_view.js @@ -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); diff --git a/app/assets/javascripts/admin/views/admin_dashboard_view.js b/app/assets/javascripts/admin/views/admin_dashboard_view.js index 48c352cbefb..0d0f6faf5c2 100644 --- a/app/assets/javascripts/admin/views/admin_dashboard_view.js +++ b/app/assets/javascripts/admin/views/admin_dashboard_view.js @@ -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); diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index ad5d61fa552..50d65abb64f 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -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 diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js index a91b730eeb8..3097e212f3a 100644 --- a/app/assets/javascripts/discourse.js +++ b/app/assets/javascripts/discourse.js @@ -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 ""; - } else { - return ""; + logoSmall: (function() { + var logo; + logo = Discourse.SiteSettings.logo_small_url; + if (logo && logo.length > 1) { + return ""; + } else { + return ""; + } + }).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("
"); + 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("
"); - 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); diff --git a/app/assets/javascripts/discourse/components/autocomplete.js b/app/assets/javascripts/discourse/components/autocomplete.js index bd3a56054d0..70c8c29a0b2 100644 --- a/app/assets/javascripts/discourse/components/autocomplete.js +++ b/app/assets/javascripts/discourse/components/autocomplete.js @@ -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 = $("
" + (transformed || item) + "
"); + 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("
").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("
" + (transformed || item) + "
"); - 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("
").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); + } + } + }); +}; diff --git a/app/assets/javascripts/discourse/components/bbcode.js b/app/assets/javascripts/discourse/components/bbcode.js index eff4240707e..dd474985d01 100644 --- a/app/assets/javascripts/discourse/components/bbcode.js +++ b/app/assets/javascripts/discourse/components/bbcode.js @@ -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 "
    " + content + "
"; - }, - "li": function(_, content) { - return "
  • " + content + "
  • "; - }, - "ul": function(_, content) { - return ""; - }, - "code": function(_, content) { - return "
    " + content + "
    "; - }, - "url": function(_, url) { - return "" + url + ""; - }, - "email": function(_, address) { - return "" + address + ""; - }, - "img": function(_, src) { - return ""; - } - }, - withArgs: { - "url": function(_, href, title) { - return "" + title + ""; - }, - "email": function(_, address, title) { - return "" + title + ""; - }, - "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 "" + content + ""; + QUOTE_REGEXP: /\[quote=([^\]]*)\]([\s\S]*?)\[\/quote\]/im, + + // Define our replacers + replacers: { + base: { + withoutArgs: { + "ol": function(_, content) { return "
      " + content + "
    "; }, + "li": function(_, content) { return "
  • " + content + "
  • "; }, + "ul": function(_, content) { return ""; }, + "code": function(_, content) { return "
    " + content + "
    "; }, + "url": function(_, url) { return "" + url + ""; }, + "email": function(_, address) { return "" + address + ""; }, + "img": function(_, src) { return ""; } + }, + withArgs: { + "url": function(_, href, title) { return "" + title + ""; }, + "email": function(_, address, title) { return "" + title + ""; }, + "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 "" + content + ""; + } + } + }, + + // For HTML emails + email: { + withoutArgs: { + "b": function(_, content) { return "" + content + ""; }, + "i": function(_, content) { return "" + content + ""; }, + "u": function(_, content) { return "" + content + ""; }, + "s": function(_, content) { return "" + content + ""; }, + "spoiler": function(_, content) { return "" + content + ""; } + }, + withArgs: { + "size": function(_, size, content) { + return "" + content + ""; + } + } + }, + + // For sane environments that support CSS + "default": { + withoutArgs: { + "b": function(_, content) { return "" + content + ""; }, + "i": function(_, content) { return "" + content + ""; }, + "u": function(_, content) { return "" + content + ""; }, + "s": function(_, content) { return "" + content + ""; }, + "spoiler": function(_, content) { return "" + content + ""; } }, - /* For HTML emails - */ - - email: { - withoutArgs: { - "b": function(_, content) { - return "" + content + ""; - }, - "i": function(_, content) { - return "" + content + ""; - }, - "u": function(_, content) { - return "" + content + ""; - }, - "s": function(_, content) { - return "" + content + ""; - }, - "spoiler": function(_, content) { - return "" + content + ""; - } - }, - withArgs: { - "size": function(_, size, content) { - return "" + content + ""; - } - } - }, - /* For sane environments that support CSS - */ - - "default": { - withoutArgs: { - "b": function(_, content) { - return "" + content + ""; - }, - "i": function(_, content) { - return "" + content + ""; - }, - "u": function(_, content) { - return "" + content + ""; - }, - "s": function(_, content) { - return "" + content + ""; - }, - "spoiler": function(_, content) { - return "" + content + ""; - } - }, - withArgs: { - "size": function(_, size, content) { - return "" + content + ""; - } + withArgs: { + "size": function(_, size, content) { + return "" + content + ""; } } - }, - - /* 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], "

    " + HANDLEBARS_TEMPLATES[templateName](args) + "

    "); - } - 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], "

    " + HANDLEBARS_TEMPLATES[templateName](args) + "

    "); + } + 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; + } +}; \ No newline at end of file diff --git a/app/assets/javascripts/discourse/components/caret_position.js b/app/assets/javascripts/discourse/components/caret_position.js index 5081d1f30e5..4bdb9b316ab 100644 --- a/app/assets/javascripts/discourse/components/caret_position.js +++ b/app/assets/javascripts/discourse/components/caret_position.js @@ -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("

    ").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 = $("

    ").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, "​ ​"); - return buf.replace(/\n/g, "
    "); - }; - makeCursor = function(pos, klass, color) { - var l; - l = val.substring(pos, pos + 1); - if (l === "\n") { - return "
    "; - } - return "" + guard(l) + ""; - }; - 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, "​ ​"); + return buf.replace(/\n/g, "
    "); + }; + + makeCursor = function(pos, klass, color) { + var l; + l = val.substring(pos, pos + 1); + if (l === "\n") return "
    "; + return "" + guard(l) + ""; + }; + + 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() + }; + +}; diff --git a/app/assets/javascripts/discourse/components/click_track.js b/app/assets/javascripts/discourse/components/click_track.js index e3b8d669e06..7f2e66cdf07 100644 --- a/app/assets/javascripts/discourse/components/click_track.js +++ b/app/assets/javascripts/discourse/components/click_track.js @@ -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; + } +}; + + diff --git a/app/assets/javascripts/discourse/components/debounce.js b/app/assets/javascripts/discourse/components/debounce.js index 00163158150..2c081cf4825 100644 --- a/app/assets/javascripts/discourse/components/debounce.js +++ b/app/assets/javascripts/discourse/components/debounce.js @@ -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; } diff --git a/app/assets/javascripts/discourse/components/discourse_text_field.js b/app/assets/javascripts/discourse/components/discourse_text_field.js deleted file mode 100644 index 7e2a0b7061a..00000000000 --- a/app/assets/javascripts/discourse/components/discourse_text_field.js +++ /dev/null @@ -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); diff --git a/app/assets/javascripts/discourse/components/div_resizer.js b/app/assets/javascripts/discourse/components/div_resizer.js index 823e04c2719..fbcca76beb8 100644 --- a/app/assets/javascripts/discourse/components/div_resizer.js +++ b/app/assets/javascripts/discourse/components/div_resizer.js @@ -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("
    ").find('.grippie').bind("mousedown", { - el: this - }, start()); - }); - }; - return $.fn.DivResizer; - })(jQuery); - -}).call(this); + grippie = div.prepend("
    ").find('.grippie').bind("mousedown", { + el: this + }, start()); + }); +}; diff --git a/app/assets/javascripts/discourse/components/eyeline.js b/app/assets/javascripts/discourse/components/eyeline.js index fb73272fd93..a5b0f1cd3b1 100644 --- a/app/assets/javascripts/discourse/components/eyeline.js +++ b/app/assets/javascripts/discourse/components/eyeline.js @@ -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); diff --git a/app/assets/javascripts/discourse/components/key_value_store.js b/app/assets/javascripts/discourse/components/key_value_store.js index 94c6b5ad232..9ddda67f802 100644 --- a/app/assets/javascripts/discourse/components/key_value_store.js +++ b/app/assets/javascripts/discourse/components/key_value_store.js @@ -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); diff --git a/app/assets/javascripts/discourse/components/lightbox.js b/app/assets/javascripts/discourse/components/lightbox.js index 117178a9a06..3dddb89a5de 100644 --- a/app/assets/javascripts/discourse/components/lightbox.js +++ b/app/assets/javascripts/discourse/components/lightbox.js @@ -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); diff --git a/app/assets/javascripts/discourse/components/mention.js b/app/assets/javascripts/discourse/components/mention.js new file mode 100644 index 00000000000..e2c5a1f9378 --- /dev/null +++ b/app/assets/javascripts/discourse/components/mention.js @@ -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("@" + username + ""); + } else { + return $elem.removeClass('mention-loading').addClass('mention-tested'); + } + }); + if (loading) { + return $elem.addClass('mention-loading'); + } + }; + return { + load: load, + lookup: lookup, + lookupCache: lookupCache + }; +})(); + + diff --git a/app/assets/javascripts/discourse/components/message_bus.js b/app/assets/javascripts/discourse/components/message_bus.js index 835fb64ea05..ffb142e70f2 100644 --- a/app/assets/javascripts/discourse/components/message_bus.js +++ b/app/assets/javascripts/discourse/components/message_bus.js @@ -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(); + } + } + }; +})(); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/components/onebox.js b/app/assets/javascripts/discourse/components/onebox.js new file mode 100644 index 00000000000..372deb5f08d --- /dev/null +++ b/app/assets/javascripts/discourse/components/onebox.js @@ -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 + }; + +})(); + + diff --git a/app/assets/javascripts/discourse/components/pagedown_editor.js b/app/assets/javascripts/discourse/components/pagedown_editor.js deleted file mode 100644 index 5c46b770759..00000000000 --- a/app/assets/javascripts/discourse/components/pagedown_editor.js +++ /dev/null @@ -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); diff --git a/app/assets/javascripts/discourse/components/probes.js b/app/assets/javascripts/discourse/components/probes.js index a1b6c34571f..e2a497904f9 100644 --- a/app/assets/javascripts/discourse/components/probes.js +++ b/app/assets/javascripts/discourse/components/probes.js @@ -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"); * * * */ diff --git a/app/assets/javascripts/discourse/components/sanitize.js b/app/assets/javascripts/discourse/components/sanitize.js deleted file mode 100644 index 3bce16c96a1..00000000000 --- a/app/assets/javascripts/discourse/components/sanitize.js +++ /dev/null @@ -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 ); diff --git a/app/assets/javascripts/discourse/components/screen_track.js b/app/assets/javascripts/discourse/components/screen_track.js index ad251a50d07..3d50638bff6 100644 --- a/app/assets/javascripts/discourse/components/screen_track.js +++ b/app/assets/javascripts/discourse/components/screen_track.js @@ -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); diff --git a/app/assets/javascripts/discourse/components/syntax_highlighting.js b/app/assets/javascripts/discourse/components/syntax_highlighting.js index d138263b60b..930ae516979 100644 --- a/app/assets/javascripts/discourse/components/syntax_highlighting.js +++ b/app/assets/javascripts/discourse/components/syntax_highlighting.js @@ -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); diff --git a/app/assets/javascripts/discourse/components/transition_helper.js b/app/assets/javascripts/discourse/components/transition_helper.js index 31518db00d3..67398b6e1c2 100644 --- a/app/assets/javascripts/discourse/components/transition_helper.js +++ b/app/assets/javascripts/discourse/components/transition_helper.js @@ -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); diff --git a/app/assets/javascripts/discourse/components/user_search.js b/app/assets/javascripts/discourse/components/user_search.js index 8a3fbe8a1ce..e6f2a60fbed 100644 --- a/app/assets/javascripts/discourse/components/user_search.js +++ b/app/assets/javascripts/discourse/components/user_search.js @@ -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); diff --git a/app/assets/javascripts/discourse/components/utilities.js b/app/assets/javascripts/discourse/components/utilities.js index fc6b1040d87..2712d102728 100644 --- a/app/assets/javascripts/discourse/components/utilities.js +++ b/app/assets/javascripts/discourse/components/utilities.js @@ -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 = "" + name + ""; - }, - 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 ""; - }, - 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 + ""; + }, - 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 ""; + }, - 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 " " + rest + ""; + }); + }); + + // 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 " " + rest + ""; + 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 "
    " + escaped + "
    "; - }); + // 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 "
    " + escaped + "
    "; }); + }); + + converter.hooks.chain("postConversion", function(text) { + if (!text) return ""; + + // don't to mention voodoo in pres + text = text.replace(/
    ([\s\S]*@[\s\S]*)<\/pre>/gi, function(wholeMatch, inner) {
    +        return "
    " + (inner.replace(/@/g, '@')) + "
    "; + }); + + // 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 + "" + name + ""; + } else { + return "" + pre + "" + name + ""; + } + }); + + // a primitive attempt at oneboxing, this regex gives me much eye sores + text = text.replace(/(
  • )?((

    |
    )[\s\n\r]*)(]*)>([^<]+<\/a>[\s\n\r]*(?=<\/p>|
    ))/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(/

    ([\s\S]*@[\s\S]*)<\/pre>/gi, function(wholeMatch, inner) {
    -          return "
    " + (inner.replace(/@/g, '@')) + "
    "; - }); - /* 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 + "
    " + name + ""; - } else { - return "" + pre + "" + name + ""; - } - }); - /* a primitive attempt at oneboxing, this regex gives me much eye sores - */ - - text = text.replace(/(
  • )?((

    |
    )[\s\n\r]*)(]*)>([^<]+<\/a>[\s\n\r]*(?=<\/p>|
    ))/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); diff --git a/app/assets/javascripts/discourse/controllers/application_controller.js b/app/assets/javascripts/discourse/controllers/application_controller.js index 5b35781f13c..dee288bcc00 100644 --- a/app/assets/javascripts/discourse/controllers/application_controller.js +++ b/app/assets/javascripts/discourse/controllers/application_controller.js @@ -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') - + }); diff --git a/app/assets/javascripts/discourse/controllers/composer_controller.js b/app/assets/javascripts/discourse/controllers/composer_controller.js index 6dd25b939ba..d19b946a855 100644 --- a/app/assets/javascripts/discourse/controllers/composer_controller.js +++ b/app/assets/javascripts/discourse/controllers/composer_controller.js @@ -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; + } +}); + + diff --git a/app/assets/javascripts/discourse/controllers/controller.js b/app/assets/javascripts/discourse/controllers/controller.js index 8ace63d0ab8..73e1e696623 100644 --- a/app/assets/javascripts/discourse/controllers/controller.js +++ b/app/assets/javascripts/discourse/controllers/controller.js @@ -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); diff --git a/app/assets/javascripts/discourse/controllers/header_controller.js b/app/assets/javascripts/discourse/controllers/header_controller.js index 1938ba9653d..716282bebe7 100644 --- a/app/assets/javascripts/discourse/controllers/header_controller.js +++ b/app/assets/javascripts/discourse/controllers/header_controller.js @@ -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); diff --git a/app/assets/javascripts/discourse/controllers/list_categories_controller.js b/app/assets/javascripts/discourse/controllers/list_categories_controller.js index a0953661f86..acac8d914b1 100644 --- a/app/assets/javascripts/discourse/controllers/list_categories_controller.js +++ b/app/assets/javascripts/discourse/controllers/list_categories_controller.js @@ -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); diff --git a/app/assets/javascripts/discourse/controllers/list_controller.js b/app/assets/javascripts/discourse/controllers/list_controller.js index b40d04ae37b..1bcb0cfb880 100644 --- a/app/assets/javascripts/discourse/controllers/list_controller.js +++ b/app/assets/javascripts/discourse/controllers/list_controller.js @@ -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); diff --git a/app/assets/javascripts/discourse/controllers/list_topics_controller.js b/app/assets/javascripts/discourse/controllers/list_topics_controller.js index d966c1ac021..a154a890618 100644 --- a/app/assets/javascripts/discourse/controllers/list_topics_controller.js +++ b/app/assets/javascripts/discourse/controllers/list_topics_controller.js @@ -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); diff --git a/app/assets/javascripts/discourse/controllers/modal_controller.js b/app/assets/javascripts/discourse/controllers/modal_controller.js index 5064c67f21d..987b8c5545a 100644 --- a/app/assets/javascripts/discourse/controllers/modal_controller.js +++ b/app/assets/javascripts/discourse/controllers/modal_controller.js @@ -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); diff --git a/app/assets/javascripts/discourse/controllers/object_controller.js b/app/assets/javascripts/discourse/controllers/object_controller.js new file mode 100644 index 00000000000..32ae3f9ab2a --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/object_controller.js @@ -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); + + diff --git a/app/assets/javascripts/discourse/controllers/preferences_controller.js b/app/assets/javascripts/discourse/controllers/preferences_controller.js index 6bc4b92a21a..782269f620c 100644 --- a/app/assets/javascripts/discourse/controllers/preferences_controller.js +++ b/app/assets/javascripts/discourse/controllers/preferences_controller.js @@ -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); diff --git a/app/assets/javascripts/discourse/controllers/preferences_email_controller.js b/app/assets/javascripts/discourse/controllers/preferences_email_controller.js index 46ff3bd2587..e6dda6c8aaf 100644 --- a/app/assets/javascripts/discourse/controllers/preferences_email_controller.js +++ b/app/assets/javascripts/discourse/controllers/preferences_email_controller.js @@ -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); diff --git a/app/assets/javascripts/discourse/controllers/preferences_username_controller.js b/app/assets/javascripts/discourse/controllers/preferences_username_controller.js index 61e112343e8..c6d79a6ceac 100644 --- a/app/assets/javascripts/discourse/controllers/preferences_username_controller.js +++ b/app/assets/javascripts/discourse/controllers/preferences_username_controller.js @@ -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); diff --git a/app/assets/javascripts/discourse/controllers/quote_button_controller.js b/app/assets/javascripts/discourse/controllers/quote_button_controller.js index 5318186bf97..3c4e9d5e1b7 100644 --- a/app/assets/javascripts/discourse/controllers/quote_button_controller.js +++ b/app/assets/javascripts/discourse/controllers/quote_button_controller.js @@ -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; + } +}); diff --git a/app/assets/javascripts/discourse/controllers/share_controller.js b/app/assets/javascripts/discourse/controllers/share_controller.js index 2ca342f2fff..93811b37e55 100644 --- a/app/assets/javascripts/discourse/controllers/share_controller.js +++ b/app/assets/javascripts/discourse/controllers/share_controller.js @@ -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); diff --git a/app/assets/javascripts/discourse/controllers/static_controller.js b/app/assets/javascripts/discourse/controllers/static_controller.js index 0011e522058..9856f880db7 100644 --- a/app/assets/javascripts/discourse/controllers/static_controller.js +++ b/app/assets/javascripts/discourse/controllers/static_controller.js @@ -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