From 0af0a214b2cef90d07ddf33cc8648d4a416307d2 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 30 May 2013 14:12:33 -0400 Subject: [PATCH] Refactor: Back all modals by controllers --- app/assets/javascripts/discourse.js | 6 +- .../controllers/application_controller.js | 8 - .../controllers/create_account_controller.js | 276 ++++++++++++++++++ .../controllers/edit_category_controller.js | 194 ++++++++++++ .../edit_topic_auto_close_controller.js | 45 +++ .../discourse/controllers/flag_controller.js | 78 +++++ .../controllers/forgot_password_controller.js | 29 ++ .../controllers/history_controller.js | 85 ++++++ .../controllers/image_selector_controller.js | 22 ++ .../controllers/invite_controller.js | 44 +++ .../controllers/invite_private_controller.js | 39 +++ .../controllers/list_categories_controller.js | 5 - .../discourse/controllers/list_controller.js | 12 +- .../controllers/list_topics_controller.js | 8 - .../discourse/controllers/login_controller.js | 156 ++++++++++ .../controllers/merge_topic_controller.js | 56 ++++ .../discourse/controllers/modal_controller.js | 4 +- .../controllers/not_activated_controller.js | 18 ++ .../controllers/split_topic_controller.js | 49 ++++ .../topic_admin_menu_controller.js | 9 - .../discourse/controllers/topic_controller.js | 64 ---- .../discourse/mixins/modal_functionality.js | 28 ++ .../javascripts/discourse/models/archetype.js | 4 +- .../discourse/routes/application_route.js | 47 +++ .../discourse/routes/discourse_route.js | 18 ++ .../discourse/routes/list_categories_route.js | 9 + .../discourse/routes/topic_route.js | 51 ++++ .../templates/excerpt/category.js.handlebars | 4 +- .../discourse/templates/flag.js.handlebars | 36 --- .../templates/image_selector.js.handlebars | 14 +- .../templates/list/list.js.handlebars | 2 +- .../templates/modal/auto_close.js.handlebars | 6 +- .../modal/create_account.js.handlebars | 24 +- .../modal/edit_category.js.handlebars | 44 ++- .../templates/modal/flag.js.handlebars | 36 +++ .../modal/forgot_password.js.handlebars | 4 +- .../{ => modal}/history.js.handlebars | 20 +- .../modal/image_selector.js.handlebars | 36 +++ .../templates/modal/invite.js.handlebars | 12 +- .../modal/invite_private.js.handlebars | 10 +- .../templates/modal/login.js.handlebars | 34 +-- .../templates/modal/merge_topic.js.handlebars | 8 +- .../templates/modal/modal.js.handlebars | 16 + .../modal/modal_errors.js.handlebars | 8 - .../modal/modal_header.js.handlebars | 5 - .../modal/not_activated.js.handlebars | 8 +- .../templates/modal/split_topic.js.handlebars | 8 +- .../modal/topic_rank_details.js.handlebars | 46 --- .../templates/topic_admin_menu.js.handlebars | 2 +- .../private_message.js.handlebars | 2 +- .../discourse/views/choose_topic_view.js | 3 +- .../discourse/views/composer_view.js | 6 +- .../views/modal/color_picker_view.js | 2 + .../views/modal/create_account_view.js | 274 +---------------- .../views/modal/edit_category_view.js | 178 +---------- .../views/modal/edit_topic_auto_close_view.js | 35 +-- .../discourse/views/modal/flag_view.js | 78 +---- .../views/modal/forgot_password_view.js | 20 +- .../discourse/views/modal/history_view.js | 77 +---- .../views/modal/image_selector_view.js | 28 +- .../views/modal/invite_modal_view.js | 59 ---- .../views/modal/invite_private_modal_view.js | 52 ---- .../views/modal/invite_private_view.js | 23 ++ .../discourse/views/modal/invite_view.js | 24 ++ .../discourse/views/modal/login_view.js | 168 +---------- .../discourse/views/modal/merge_topic_view.js | 44 +-- .../discourse/views/modal/modal_body_view.js | 26 +- .../discourse/views/modal/modal_view.js | 26 +- .../views/modal/not_activated_view.js | 8 +- .../discourse/views/modal/split_topic_view.js | 35 +-- .../views/modal/topic_rank_details_view.js | 13 - .../discourse/views/post_menu_view.js | 2 +- .../views/topic_footer_buttons_view.js | 5 +- app/models/topic_list.rb | 2 +- 74 files changed, 1555 insertions(+), 1382 deletions(-) create mode 100644 app/assets/javascripts/discourse/controllers/create_account_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/edit_category_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/edit_topic_auto_close_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/flag_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/forgot_password_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/history_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/image_selector_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/invite_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/invite_private_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/login_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/merge_topic_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/not_activated_controller.js create mode 100644 app/assets/javascripts/discourse/controllers/split_topic_controller.js create mode 100644 app/assets/javascripts/discourse/mixins/modal_functionality.js create mode 100644 app/assets/javascripts/discourse/routes/application_route.js delete mode 100644 app/assets/javascripts/discourse/templates/flag.js.handlebars create mode 100644 app/assets/javascripts/discourse/templates/modal/flag.js.handlebars rename app/assets/javascripts/discourse/templates/{ => modal}/history.js.handlebars (66%) create mode 100644 app/assets/javascripts/discourse/templates/modal/image_selector.js.handlebars create mode 100644 app/assets/javascripts/discourse/templates/modal/modal.js.handlebars delete mode 100644 app/assets/javascripts/discourse/templates/modal/modal_errors.js.handlebars delete mode 100644 app/assets/javascripts/discourse/templates/modal/modal_header.js.handlebars delete mode 100644 app/assets/javascripts/discourse/templates/modal/topic_rank_details.js.handlebars delete mode 100644 app/assets/javascripts/discourse/views/modal/invite_modal_view.js delete mode 100644 app/assets/javascripts/discourse/views/modal/invite_private_modal_view.js create mode 100644 app/assets/javascripts/discourse/views/modal/invite_private_view.js create mode 100644 app/assets/javascripts/discourse/views/modal/invite_view.js delete mode 100644 app/assets/javascripts/discourse/views/modal/topic_rank_details_view.js diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js index b0c9855ec3a..d18190aee54 100644 --- a/app/assets/javascripts/discourse.js +++ b/app/assets/javascripts/discourse.js @@ -155,9 +155,9 @@ Discourse = Ember.Application.createWithMixins({ }, authenticationComplete: function(options) { - // TODO, how to dispatch this to the view without the container? - var loginView = Discourse.__container__.lookup('controller:modal').get('currentView'); - return loginView.authenticationComplete(options); + // TODO, how to dispatch this to the controller without the container? + var loginController = Discourse.__container__.lookup('controller:login'); + return loginController.authenticationComplete(options); }, /** diff --git a/app/assets/javascripts/discourse/controllers/application_controller.js b/app/assets/javascripts/discourse/controllers/application_controller.js index 19e77185ea2..1202464f76d 100644 --- a/app/assets/javascripts/discourse/controllers/application_controller.js +++ b/app/assets/javascripts/discourse/controllers/application_controller.js @@ -9,14 +9,6 @@ @module Discourse **/ Discourse.ApplicationController = Discourse.Controller.extend({ - needs: ['modal'], - - showLogin: function() { - var modalController = this.get('controllers.modal'); - if (modalController) { - modalController.show(Discourse.LoginView.create()) - } - }, routeChanged: function(){ if (window._gaq === undefined) { return; } diff --git a/app/assets/javascripts/discourse/controllers/create_account_controller.js b/app/assets/javascripts/discourse/controllers/create_account_controller.js new file mode 100644 index 00000000000..2e4cff900a5 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/create_account_controller.js @@ -0,0 +1,276 @@ +/** + The modal for creating accounts + + @class CreateAccountController + @extends Discourse.Controller + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.CreateAccountController = Discourse.Controller.extend(Discourse.ModalFunctionality, { + uniqueUsernameValidation: null, + globalNicknameExists: false, + complete: false, + accountPasswordConfirm: 0, + accountChallenge: 0, + formSubmitted: false, + + submitDisabled: function() { + if (this.get('formSubmitted')) return true; + if (this.get('nameValidation.failed')) return true; + if (this.get('emailValidation.failed')) return true; + if (this.get('usernameValidation.failed')) return true; + if (this.get('passwordValidation.failed')) return true; + return false; + }.property('nameValidation.failed', 'emailValidation.failed', 'usernameValidation.failed', 'passwordValidation.failed', 'formSubmitted'), + + passwordRequired: function() { + return this.blank('authOptions.auth_provider'); + }.property('authOptions.auth_provider'), + + // Validate the name + nameValidation: function() { + + // If blank, fail without a reason + if (this.blank('accountName')) return Discourse.InputValidation.create({ failed: true }); + + if (this.get('accountPasswordConfirm') === 0) { + this.fetchConfirmationValue(); + } + + // If too short + if (this.get('accountName').length < 3) { + return Discourse.InputValidation.create({ + failed: true, + reason: Em.String.i18n('user.name.too_short') + }); + } + + // Looks good! + return Discourse.InputValidation.create({ + ok: true, + reason: Em.String.i18n('user.name.ok') + }); + }.property('accountName'), + + // Check the email address + emailValidation: function() { + // If blank, fail without a reason + var email; + if (this.blank('accountEmail')) { + return Discourse.InputValidation.create({ + failed: true + }); + } + + email = this.get("accountEmail"); + if ((this.get('authOptions.email') === email) && this.get('authOptions.email_valid')) { + return Discourse.InputValidation.create({ + ok: true, + reason: Em.String.i18n('user.email.authenticated', { + provider: this.get('authOptions.auth_provider') + }) + }); + } + + if (Discourse.Utilities.emailValid(email)) { + return Discourse.InputValidation.create({ + ok: true, + reason: Em.String.i18n('user.email.ok') + }); + } + + return Discourse.InputValidation.create({ + failed: true, + reason: Em.String.i18n('user.email.invalid') + }); + }.property('accountEmail'), + + usernameMatch: function() { + if (this.usernameNeedsToBeValidatedWithEmail()) { + if (this.get('emailValidation.failed')) { + if (this.shouldCheckUsernameMatch()) { + return this.set('uniqueUsernameValidation', Discourse.InputValidation.create({ + failed: true, + reason: Em.String.i18n('user.username.enter_email') + })); + } else { + return this.set('uniqueUsernameValidation', Discourse.InputValidation.create({ failed: true })); + } + } else if (this.shouldCheckUsernameMatch()) { + this.set('uniqueUsernameValidation', Discourse.InputValidation.create({ + failed: true, + reason: Em.String.i18n('user.username.checking') + })); + return this.checkUsernameAvailability(); + } + } + }.observes('accountEmail'), + + basicUsernameValidation: function() { + this.set('uniqueUsernameValidation', null); + + // If blank, fail without a reason + if (this.blank('accountUsername')) { + return Discourse.InputValidation.create({ + failed: true + }); + } + + // If too short + if (this.get('accountUsername').length < 3) { + return Discourse.InputValidation.create({ + failed: true, + reason: Em.String.i18n('user.username.too_short') + }); + } + + // If too long + if (this.get('accountUsername').length > 15) { + return Discourse.InputValidation.create({ + failed: true, + reason: Em.String.i18n('user.username.too_long') + }); + } + + this.checkUsernameAvailability(); + // Let's check it out asynchronously + return Discourse.InputValidation.create({ + failed: true, + reason: Em.String.i18n('user.username.checking') + }); + }.property('accountUsername'), + + shouldCheckUsernameMatch: function() { + return !this.blank('accountUsername') && this.get('accountUsername').length > 2; + }, + + checkUsernameAvailability: Discourse.debounce(function() { + var _this = this; + if (this.shouldCheckUsernameMatch()) { + return Discourse.User.checkUsername(this.get('accountUsername'), this.get('accountEmail')).then(function(result) { + _this.set('globalNicknameExists', false); + if (result.available) { + if (result.global_match) { + _this.set('globalNicknameExists', true); + return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({ + ok: true, + reason: Em.String.i18n('user.username.global_match') + })); + } else { + return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({ + ok: true, + reason: Em.String.i18n('user.username.available') + })); + } + } else { + if (result.suggestion) { + if (result.global_match !== void 0 && result.global_match === false) { + _this.set('globalNicknameExists', true); + return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({ + failed: true, + reason: Em.String.i18n('user.username.global_mismatch', result) + })); + } else { + return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({ + failed: true, + reason: Em.String.i18n('user.username.not_available', result) + })); + } + } else if (result.errors) { + return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({ + failed: true, + reason: result.errors.join(' ') + })); + } else { + _this.set('globalNicknameExists', true); + return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({ + failed: true, + reason: Em.String.i18n('user.username.enter_email') + })); + } + } + }); + } + }, 500), + + // Actually wait for the async name check before we're 100% sure we're good to go + usernameValidation: function() { + var basicValidation, uniqueUsername; + basicValidation = this.get('basicUsernameValidation'); + uniqueUsername = this.get('uniqueUsernameValidation'); + if (uniqueUsername) { + return uniqueUsername; + } + return basicValidation; + }.property('uniqueUsernameValidation', 'basicUsernameValidation'), + + usernameNeedsToBeValidatedWithEmail: function() { + return( this.get('globalNicknameExists') || false ); + }, + + // Validate the password + passwordValidation: function() { + var password; + if (!this.get('passwordRequired')) { + return Discourse.InputValidation.create({ + ok: true + }); + } + + // If blank, fail without a reason + password = this.get("accountPassword"); + if (this.blank('accountPassword')) { + return Discourse.InputValidation.create({ failed: true }); + } + + // If too short + if (password.length < 6) { + return Discourse.InputValidation.create({ + failed: true, + reason: Em.String.i18n('user.password.too_short') + }); + } + + // Looks good! + return Discourse.InputValidation.create({ + ok: true, + reason: Em.String.i18n('user.password.ok') + }); + }.property('accountPassword'), + + fetchConfirmationValue: function() { + var createAccountController = this; + return Discourse.ajax('/users/hp.json').then(function (json) { + createAccountController.set('accountPasswordConfirm', json.value); + createAccountController.set('accountChallenge', json.challenge.split("").reverse().join("")); + }); + }, + + createAccount: function() { + var createAccountController = this; + this.set('formSubmitted', true); + var name = this.get('accountName'); + var email = this.get('accountEmail'); + var password = this.get('accountPassword'); + var username = this.get('accountUsername'); + var passwordConfirm = this.get('accountPasswordConfirm'); + var challenge = this.get('accountChallenge'); + return Discourse.User.createAccount(name, email, password, username, passwordConfirm, challenge).then(function(result) { + if (result.success) { + createAccountController.flash(result.message); + createAccountController.set('complete', true); + } else { + createAccountController.flash(result.message || Em.String.i18n('create_account.failed'), 'error'); + createAccountController.set('formSubmitted', false); + } + if (result.active) { + return window.location.reload(); + } + }, function() { + createAccountController.set('formSubmitted', false); + return createAccountController.flash(Em.String.i18n('create_account.failed'), 'error'); + }); + } + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/controllers/edit_category_controller.js b/app/assets/javascripts/discourse/controllers/edit_category_controller.js new file mode 100644 index 00000000000..4580d69953d --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/edit_category_controller.js @@ -0,0 +1,194 @@ +/** + Modal for editing / creating a category + + @class EditCategoryController + @extends Discourse.ObjectController + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.EditCategoryController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, { + generalSelected: Ember.computed.equal('selectedTab', 'general'), + securitySelected: Ember.computed.equal('selectedTab', 'security'), + settingsSelected: Ember.computed.equal('selectedTab', 'settings'), + foregroundColors: ['FFFFFF', '000000'], + + descriptionChanged: function() { + if (this.present('description')) { + this.set('controllers.modal.modalClass', 'edit-category-modal full'); + } else { + this.set('controllers.modal.modalClass', 'edit-category-modal small'); + } + }.observes('description'), + + title: function() { + if (this.get('id')) return Em.String.i18n("category.edit_long"); + if (this.get('isUncategorized')) return Em.String.i18n("category.edit_uncategorized"); + return Em.String.i18n("category.create"); + }.property('id'), + + titleChanged: function() { + this.set('controllers.modal.title', this.get('title')); + }.observes('title'), + + selectGeneral: function() { + this.set('selectedTab', 'general'); + }, + + selectSecurity: function() { + this.set('selectedTab', 'security'); + }, + + selectSettings: function() { + this.set('selectedTab', 'settings'); + }, + + disabled: function() { + if (this.get('saving') || this.get('deleting')) return true; + if (!this.get('name')) return true; + if (!this.get('color')) return true; + return false; + }.property('name', 'color', 'deleting'), + + deleteVisible: function() { + return (this.get('id') && this.get('topic_count') === 0); + }.property('id', 'topic_count'), + + deleteDisabled: function() { + return (this.get('deleting') || this.get('saving') || false); + }.property('disabled', 'saving', 'deleting'), + + colorStyle: function() { + return "background-color: #" + (this.get('color')) + "; color: #" + (this.get('text_color')) + ";"; + }.property('color', 'text_color'), + + // background colors are available as a pipe-separated string + backgroundColors: function() { + var categories = Discourse.Category.list(); + return Discourse.SiteSettings.category_colors.split("|").map(function(i) { return i.toUpperCase(); }).concat( + categories.map(function(c) { return c.color.toUpperCase(); }) ).uniq(); + }.property('Discourse.SiteSettings.category_colors'), + + usedBackgroundColors: function() { + var categories = Discourse.Category.list(); + + var currentCat = this.get('model'); + + return categories.map(function(c) { + // If editing a category, don't include its color: + return (currentCat.get('id') && currentCat.get('color').toUpperCase() === c.color.toUpperCase()) ? null : c.color.toUpperCase(); + }, this).compact(); + }.property('id', 'color'), + + categoryName: function() { + var name = this.get('name') || ""; + return name.trim().length > 0 ? name : Em.String.i18n("preview"); + }.property('name'), + + buttonTitle: function() { + if (this.get('saving')) return Em.String.i18n("saving"); + if (this.get('isUncategorized')) return Em.String.i18n("save"); + return (this.get('id') ? Em.String.i18n("category.save") : Em.String.i18n("category.create")); + }.property('saving', 'id'), + + deleteButtonTitle: function() { + return Em.String.i18n('category.delete'); + }.property(), + + didInsertElement: function() { + this._super(); + + if (this.get('id')) { + this.set('loading', true); + var categoryController = this; + + // We need the topic_count to be correct, so get the most up-to-date info about this category from the server. + Discourse.Category.findBySlugOrId( this.get('slug') || this.get('id') ).then( function(cat) { + categoryController.set('category', cat); + Discourse.Site.instance().updateCategory(cat); + categoryController.set('id', categoryController.get('slug')); + categoryController.set('loading', false); + }); + } else if( this.get('isUncategorized') ) { + this.set('category', Discourse.Category.uncategorizedInstance()); + } else { + this.set('category', Discourse.Category.create({ color: 'AB9364', text_color: 'FFFFFF', hotness: 5 })); + } + }, + + showCategoryTopic: function() { + $('#discourse-modal').modal('hide'); + Discourse.URL.routeTo(this.get('topic_url')); + return false; + }, + + addGroup: function(){ + this.get('model').addGroup(this.get("selectedGroup")); + }, + + removeGroup: function(group){ + // OBVIOUS, Ember treats this as Ember.String, we need a real string here + group = group + ""; + this.get('model').removeGroup(group); + }, + + saveCategory: function() { + var categoryController = this; + this.set('saving', true); + + + if( this.get('isUncategorized') ) { + $.when( + Discourse.SiteSetting.update('uncategorized_color', this.get('color')), + Discourse.SiteSetting.update('uncategorized_text_color', this.get('text_color')), + Discourse.SiteSetting.update('uncategorized_name', this.get('name')) + ).then(function(result) { + // success + $('#discourse-modal').modal('hide'); + // We can't redirect to the uncategorized category on save because the slug + // might have changed. + Discourse.URL.redirectTo("/categories"); + }, function(errors) { + // errors + if(errors.length === 0) errors.push(Em.String.i18n("category.save_error")); + categoryController.displayErrors(errors); + categoryController.set('saving', false); + }); + } else { + this.get('model').save().then(function(result) { + // success + $('#discourse-modal').modal('hide'); + Discourse.URL.redirectTo("/category/" + Discourse.Category.slugFor(result.category)); + }, function(errors) { + // errors + if(errors.length === 0) errors.push(Em.String.i18n("category.creation_error")); + categoryController.displayErrors(errors); + categoryController.set('saving', false); + }); + } + }, + + deleteCategory: function() { + var categoryController = this; + this.set('deleting', true); + $('#discourse-modal').modal('hide'); + bootbox.confirm(Em.String.i18n("category.delete_confirm"), Em.String.i18n("no_value"), Em.String.i18n("yes_value"), function(result) { + if (result) { + categoryController.get('category').destroy().then(function(){ + // success + Discourse.URL.redirectTo("/categories"); + }, function(jqXHR){ + // error + $('#discourse-modal').modal('show'); + categoryController.displayErrors([Em.String.i18n("category.delete_error")]); + categoryController.set('deleting', false); + }); + } else { + $('#discourse-modal').modal('show'); + categoryController.set('deleting', false); + } + }); + } + + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/controllers/edit_topic_auto_close_controller.js b/app/assets/javascripts/discourse/controllers/edit_topic_auto_close_controller.js new file mode 100644 index 00000000000..45c9129bb4b --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/edit_topic_auto_close_controller.js @@ -0,0 +1,45 @@ +/** + Modal related to auto closing of topics + + @class EditTopicAutoCloseController + @extends Discourse.ObjectController + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.EditTopicAutoCloseController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, { + + setDays: function() { + if( this.get('auto_close_at') ) { + var closeTime = Date.create( this.get('auto_close_at') ); + if (closeTime.isFuture()) { + this.set('auto_close_days', closeTime.daysSince()); + } + } else { + this.set('auto_close_days', ""); + } + }.observes('auto_close_at'), + + saveAutoClose: function() { + this.setAutoClose( parseFloat(this.get('auto_close_days')) ); + }, + + removeAutoClose: function() { + this.setAutoClose(null); + }, + + setAutoClose: function(days) { + var editTopicAutoCloseController = this; + Discourse.ajax({ + url: "/t/" + this.get('id') + "/autoclose", + type: 'PUT', + dataType: 'json', + data: { auto_close_days: days > 0 ? days : null } + }).then(function(){ + editTopicAutoCloseController.set('auto_close_at', Date.create(days + ' days from now').toJSON()); + }, function (error) { + bootbox.alert(Em.String.i18n('generic_error')); + }); + } + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/controllers/flag_controller.js b/app/assets/javascripts/discourse/controllers/flag_controller.js new file mode 100644 index 00000000000..fe014fb3dbc --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/flag_controller.js @@ -0,0 +1,78 @@ +/** + This controller supports actions related to flagging + + @class FlagController + @extends Discourse.ObjectController + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, { + + // trick to bind user / post to flag + boundFlags: function() { + var _this = this; + var original = this.get('flagsAvailable'); + if(original){ + return $.map(original, function(v){ + var b = Discourse.BoundPostActionType.create(v); + b.set('post', _this.get('model')); + return b; + }); + } + }.property('flagsAvailable.@each'), + + changePostActionType: function(action) { + if (this.get('postActionTypeId') === action.id) return false; + + this.get('boundFlags').setEach('selected', false); + action.set('selected', true); + + this.set('postActionTypeId', action.id); + this.set('isCustomFlag', action.is_custom_flag); + this.set('selected', action); + return false; + }, + + showSubmit: function() { + if (this.get('postActionTypeId')) { + if (this.get('isCustomFlag')) { + var m = this.get('selected.message'); + return m && m.length >= 10 && m.length <= 500; + } else { + return true; + } + } + return false; + }.property('isCustomFlag', 'selected.customMessageLength', 'postActionTypeId'), + + submitText: function(){ + var action = this.get('selected'); + if (this.get('selected.is_custom_flag')) { + return Em.String.i18n("flagging.notify_action"); + } else { + return Em.String.i18n("flagging.action"); + } + }.property('selected'), + + createFlag: function() { + var _this = this; + + var action = this.get('selected'); + var postAction = this.get('actionByName.' + (action.get('name_key'))); + + var actionType = Discourse.Site.instance().postActionTypeById(this.get('postActionTypeId')); + if (postAction) { + postAction.act({ + message: action.get('message') + }).then(function() { + return $('#discourse-modal').modal('hide'); + }, function(errors) { + return _this.displayErrors(errors); + }); + } + return false; + } +}); + + diff --git a/app/assets/javascripts/discourse/controllers/forgot_password_controller.js b/app/assets/javascripts/discourse/controllers/forgot_password_controller.js new file mode 100644 index 00000000000..7fd837e8824 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/forgot_password_controller.js @@ -0,0 +1,29 @@ +/** + The modal for when the user has forgotten their password + + @class ForgotPasswordController + @extends Discourse.Controller + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.ForgotPasswordController = Discourse.Controller.extend(Discourse.ModalFunctionality, { + + // You need a value in the field to submit it. + submitDisabled: function() { + return this.blank('accountEmailOrUsername'); + }.property('accountEmailOrUsername'), + + submit: function() { + + Discourse.ajax("/session/forgot_password", { + data: { login: this.get('accountEmailOrUsername') }, + type: 'POST' + }); + + // don't tell people what happened, this keeps it more secure (ensure same on server) + this.flash(Em.String.i18n('forgot_password.complete')); + return false; + } + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/controllers/history_controller.js b/app/assets/javascripts/discourse/controllers/history_controller.js new file mode 100644 index 00000000000..4057d6fb87e --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/history_controller.js @@ -0,0 +1,85 @@ +/*jshint newcap:false*/ +/*global diff_match_patch:true assetPath:true*/ + +/** + This controller handles displaying of history + + @class HistoryController + @extends Discourse.ObjectController + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.HistoryController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, { + diffLibraryLoaded: false, + diff: null, + + init: function(){ + this._super(); + var historyController = this; + $LAB.script(assetPath('defer/google_diff_match_patch')).wait(function(){ + historyController.set('diffLibraryLoaded', true); + }); + }, + + loadSide: function(side) { + if (this.get("version" + side)) { + var orig = this.get('model'); + var version = this.get("version" + side + ".number"); + if (version === orig.get('version')) { + this.set("post" + side, orig); + } else { + var historyController = this; + Discourse.Post.loadVersion(orig.get('id'), version).then(function(post) { + historyController.set("post" + side, post); + }); + } + } + }, + + changedLeftVersion: function() { + this.loadSide("Left"); + }.observes('versionLeft'), + + changedRightVersion: function() { + this.loadSide("Right"); + }.observes('versionRight'), + + loadedPosts: function() { + if (this.get('diffLibraryLoaded') && this.get('postLeft') && this.get('postRight')) { + var dmp = new diff_match_patch(), + before = this.get("postLeft.cooked"), + after = this.get("postRight.cooked"), + diff = dmp.diff_main(before, after); + dmp.diff_cleanupSemantic(diff); + this.set('diff', dmp.diff_prettyHtml(diff)); + } + }.observes('diffLibraryLoaded', 'postLeft', 'postRight'), + + refresh: function() { + this.setProperties({ + loading: true, + postLeft: null, + postRight: null + }); + + var historyController = this; + this.get('model').loadVersions().then(function(result) { + result.each(function(item) { + item.description = "v" + item.number + " - " + Date.create(item.created_at).relative() + " - " + + Em.String.i18n("changed_by", { author: item.display_username }); + }); + + console.log('wat'); + historyController.setProperties({ + loading: false, + versionLeft: result.first(), + versionRight: result.last(), + versions: result + }); + }); + } + +}); + + diff --git a/app/assets/javascripts/discourse/controllers/image_selector_controller.js b/app/assets/javascripts/discourse/controllers/image_selector_controller.js new file mode 100644 index 00000000000..b169bd612a6 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/image_selector_controller.js @@ -0,0 +1,22 @@ +/** + The modal for inviting a user to a topic + + @class ImageSelectorController + @extends Discourse.Controller + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.ImageSelectorController = Discourse.Controller.extend(Discourse.ModalFunctionality, { + + selectLocal: function() { + this.set('localSelected', true); + }, + + selectRemote: function() { + this.set('localSelected', false); + }, + + remoteSelected: Em.computed.not('localSelected') + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/controllers/invite_controller.js b/app/assets/javascripts/discourse/controllers/invite_controller.js new file mode 100644 index 00000000000..6f5a87cadc4 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/invite_controller.js @@ -0,0 +1,44 @@ +/** + The modal for inviting a user to a topic + + @class InviteController + @extends Discourse.Controller + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.InviteController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, { + + disabled: function() { + if (this.get('saving')) return true; + if (this.blank('email')) return true; + if (!Discourse.Utilities.emailValid(this.get('email'))) return true; + return false; + }.property('email', 'saving'), + + buttonTitle: function() { + if (this.get('saving')) return Em.String.i18n('topic.inviting'); + return Em.String.i18n('topic.invite_reply.action'); + }.property('saving'), + + successMessage: function() { + return Em.String.i18n('topic.invite_reply.success', { email: this.get('email') }); + }.property('email'), + + createInvite: function() { + var inviteController = this; + this.set('saving', true); + this.set('error', false); + this.get('model').inviteUser(this.get('email')).then(function() { + // Success + inviteController.set('saving', false); + return inviteController.set('finished', true); + }, function() { + // Failure + inviteController.set('error', true); + return inviteController.set('saving', false); + }); + return false; + } + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/controllers/invite_private_controller.js b/app/assets/javascripts/discourse/controllers/invite_private_controller.js new file mode 100644 index 00000000000..7e5e0f60a4c --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/invite_private_controller.js @@ -0,0 +1,39 @@ +/** + The modal for inviting a user to a private topic + + @class InvitePrivateController + @extends Discourse.Controller + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.InvitePrivateController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, { + + disabled: function() { + if (this.get('saving')) return true; + return this.blank('emailOrUsername'); + }.property('emailOrUsername', 'saving'), + + buttonTitle: function() { + if (this.get('saving')) return Em.String.i18n('topic.inviting'); + return Em.String.i18n('topic.invite_private.action'); + }.property('saving'), + + invite: function() { + var invitePrivateController = this; + this.set('saving', true); + this.set('error', false); + // Invite the user to the private message + this.get('content').inviteUser(this.get('emailOrUsername')).then(function() { + // Success + invitePrivateController.set('saving', false); + invitePrivateController.set('finished', true); + }, function() { + // Failure + invitePrivateController.set('error', true); + invitePrivateController.set('saving', false); + }); + return false; + } + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/controllers/list_categories_controller.js b/app/assets/javascripts/discourse/controllers/list_categories_controller.js index 7462757ce9b..989403d6d63 100644 --- a/app/assets/javascripts/discourse/controllers/list_categories_controller.js +++ b/app/assets/javascripts/discourse/controllers/list_categories_controller.js @@ -24,11 +24,6 @@ Discourse.ListCategoriesController = Discourse.ObjectController.extend({ }); }.property('categories.@each'), - editCategory: function(category) { - this.get('controllers.modal').show(Discourse.EditCategoryView.create({ category: category })); - return false; - }, - canEdit: function() { var u = Discourse.User.current(); return u && u.admin; diff --git a/app/assets/javascripts/discourse/controllers/list_controller.js b/app/assets/javascripts/discourse/controllers/list_controller.js index 22f017f8ca6..021fa582dc4 100644 --- a/app/assets/javascripts/discourse/controllers/list_controller.js +++ b/app/assets/javascripts/discourse/controllers/list_controller.js @@ -101,11 +101,6 @@ Discourse.ListController = Discourse.Controller.extend({ }); }, - createCategory: function() { - var _ref; - return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.EditCategoryView.create()) : void 0; - }, - canEditCategory: function() { if( this.present('category') ) { var u = Discourse.User.current(); @@ -113,12 +108,7 @@ Discourse.ListController = Discourse.Controller.extend({ } else { return false; } - }.property('category'), - - editCategory: function() { - this.get('controllers.modal').show(Discourse.EditCategoryView.create({ category: this.get('category') })); - return false; - } + }.property('category') }); diff --git a/app/assets/javascripts/discourse/controllers/list_topics_controller.js b/app/assets/javascripts/discourse/controllers/list_topics_controller.js index 90425fe1a35..6dc6df181cc 100644 --- a/app/assets/javascripts/discourse/controllers/list_topics_controller.js +++ b/app/assets/javascripts/discourse/controllers/list_topics_controller.js @@ -41,14 +41,6 @@ Discourse.ListTopicsController = Discourse.ObjectController.extend({ this.toggleProperty('rankDetailsVisible'); }, - // Show rank details - showRankDetails: function(topic) { - var modalController = this.get('controllers.modal'); - if (modalController) { - modalController.show(Discourse.TopicRankDetailsView.create({ topic: topic })); - } - }, - createTopic: function() { this.get('controllers.list').createTopic(); }, diff --git a/app/assets/javascripts/discourse/controllers/login_controller.js b/app/assets/javascripts/discourse/controllers/login_controller.js new file mode 100644 index 00000000000..15906f8933f --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/login_controller.js @@ -0,0 +1,156 @@ +/** + This controller supports actions related to flagging + + @class LoginController + @extends Discourse.Controller + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.LoginController = Discourse.Controller.extend(Discourse.ModalFunctionality, { + needs: ['modal', 'createAccount'], + authenticate: null, + loggingIn: false, + + site: function() { + return Discourse.Site.instance(); + }.property(), + + + /** + Determines whether at least one login button is enabled + **/ + hasAtLeastOneLoginButton: function() { + return Discourse.SiteSettings.enable_google_logins || + Discourse.SiteSettings.enable_facebook_logins || + Discourse.SiteSettings.enable_cas_logins || + Discourse.SiteSettings.enable_twitter_logins || + Discourse.SiteSettings.enable_yahoo_logins || + Discourse.SiteSettings.enable_github_logins || + Discourse.SiteSettings.enable_persona_logins; + }.property(), + + loginButtonText: function() { + return this.get('loggingIn') ? Em.String.i18n('login.logging_in') : Em.String.i18n('login.title'); + }.property('loggingIn'), + + loginDisabled: function() { + return this.get('loggingIn') || this.blank('loginName') || this.blank('loginPassword'); + }.property('loginName', 'loginPassword', 'loggingIn'), + + login: function() { + this.set('loggingIn', true); + + var loginController = this; + Discourse.ajax("/session", { + data: { login: this.get('loginName'), password: this.get('loginPassword') }, + type: 'POST' + }).then(function (result) { + // Successful login + if (result.error) { + loginController.set('loggingIn', false); + if( result.reason === 'not_activated' ) { + loginController.send('showNotActivated', { + username: loginController.get('loginName'), + sentTo: result.sent_to_email, + currentEmail: result.current_email + }); + } + loginController.flash(result.error, 'error'); + } else { + // Trigger the browser's password manager using the hidden static login form: + var $hidden_login_form = $('#hidden-login-form'); + $hidden_login_form.find('input[name=username]').val(loginController.get('loginName')); + $hidden_login_form.find('input[name=password]').val(loginController.get('loginPassword')); + $hidden_login_form.find('input[name=redirect]').val(window.location.href); + $hidden_login_form.find('input[name=authenticity_token]').val($('meta[name=csrf-token]').attr('content')); + $hidden_login_form.submit(); + } + + }, function(result) { + // Failed to login + loginController.flash(Em.String.i18n('login.error'), 'error'); + loginController.set('loggingIn', false); + }) + + return false; + }, + + authMessage: (function() { + if (this.blank('authenticate')) return ""; + return Em.String.i18n("login." + (this.get('authenticate')) + ".message"); + }).property('authenticate'), + + twitterLogin: function() { + this.set('authenticate', 'twitter'); + var left = this.get('lastX') - 400; + var top = this.get('lastY') - 200; + return window.open(Discourse.getURL("/auth/twitter"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top); + }, + + facebookLogin: function() { + this.set('authenticate', 'facebook'); + var left = this.get('lastX') - 400; + var top = this.get('lastY') - 200; + return window.open(Discourse.getURL("/auth/facebook"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top); + }, + + casLogin: function() { + var left, top; + this.set('authenticate', 'cas'); + left = this.get('lastX') - 400; + top = this.get('lastY') - 200; + return window.open("/auth/cas", "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top); + }, + + openidLogin: function(provider) { + var left = this.get('lastX') - 400; + var top = this.get('lastY') - 200; + if (provider === "yahoo") { + this.set("authenticate", 'yahoo'); + return window.open(Discourse.getURL("/auth/yahoo"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top); + } else { + window.open(Discourse.getURL("/auth/google"), "_blank", "menubar=no,status=no,height=500,width=850,left=" + left + ",top=" + top); + return this.set("authenticate", 'google'); + } + }, + + githubLogin: function() { + this.set('authenticate', 'github'); + var left = this.get('lastX') - 400; + var top = this.get('lastY') - 200; + return window.open(Discourse.getURL("/auth/github"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top); + }, + + personaLogin: function() { + navigator.id.request(); + }, + + authenticationComplete: function(options) { + if (options.awaiting_approval) { + this.flash(Em.String.i18n('login.awaiting_approval'), 'success'); + this.set('authenticate', null); + return; + } + if (options.awaiting_activation) { + this.flash(Em.String.i18n('login.awaiting_confirmation'), 'success'); + this.set('authenticate', null); + return; + } + // Reload the page if we're authenticated + if (options.authenticated) { + window.location.reload(); + return; + } + + var createAccountController = this.get('controllers.createAccount'); + createAccountController.setProperties({ + accountEmail: options.email, + accountUsername: options.username, + accountName: options.name, + authOptions: Em.Object.create(options) + }) + this.send('showCreateAccount'); + } + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/controllers/merge_topic_controller.js b/app/assets/javascripts/discourse/controllers/merge_topic_controller.js new file mode 100644 index 00000000000..4f3e87e18e6 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/merge_topic_controller.js @@ -0,0 +1,56 @@ +/** + Modal related to auto closing of topics + + @class MergeTopicController + @extends Discourse.ObjectController + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.MergeTopicController = Discourse.ObjectController.extend(Discourse.SelectedPostsCount, Discourse.ModalFunctionality, { + needs: ['topic'], + + topicController: Em.computed.alias('controllers.topic'), + selectedPosts: Em.computed.alias('topicController.selectedPosts'), + allPostsSelected: Em.computed.alias('topicController.allPostsSelected'), + + buttonDisabled: function() { + if (this.get('saving')) return true; + return this.blank('selectedTopicId'); + }.property('selectedTopicId', 'saving'), + + buttonTitle: function() { + if (this.get('saving')) return Em.String.i18n('saving'); + return Em.String.i18n('topic.merge_topic.title'); + }.property('saving'), + + movePostsToExistingTopic: function() { + this.set('saving', true); + + var moveSelectedView = this; + + var promise = null; + if (this.get('allPostsSelected')) { + promise = Discourse.Topic.mergeTopic(this.get('id'), this.get('selectedTopicId')); + } else { + var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }); + promise = Discourse.Topic.movePosts(this.get('id'), { + destination_topic_id: this.get('selectedTopicId'), + post_ids: postIds + }); + } + + promise.then(function(result) { + // Posts moved + $('#discourse-modal').modal('hide'); + moveSelectedView.get('topicController').toggleMultiSelect(); + Em.run.next(function() { Discourse.URL.routeTo(result.url); }); + }, function() { + // Error moving posts + moveSelectedView.flash(Em.String.i18n('topic.merge_topic.error')); + moveSelectedView.set('saving', false); + }); + return false; + } + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/controllers/modal_controller.js b/app/assets/javascripts/discourse/controllers/modal_controller.js index 50648c2137b..0659c14533c 100644 --- a/app/assets/javascripts/discourse/controllers/modal_controller.js +++ b/app/assets/javascripts/discourse/controllers/modal_controller.js @@ -7,9 +7,7 @@ @module Discourse **/ Discourse.ModalController = Discourse.Controller.extend({ - show: function(view) { - this.set('currentView', view); - } + }); diff --git a/app/assets/javascripts/discourse/controllers/not_activated_controller.js b/app/assets/javascripts/discourse/controllers/not_activated_controller.js new file mode 100644 index 00000000000..e1458697fce --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/not_activated_controller.js @@ -0,0 +1,18 @@ +/** + Modal displayed to a user when they are not active yet. + + @class NotActivatedController + @extends Discourse.Controller + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.NotActivatedController = Discourse.Controller.extend(Discourse.ModalFunctionality, { + emailSent: false, + + sendActivationEmail: function() { + Discourse.ajax('/users/' + this.get('username') + '/send_activation_email'); + this.set('emailSent', true); + } + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/controllers/split_topic_controller.js b/app/assets/javascripts/discourse/controllers/split_topic_controller.js new file mode 100644 index 00000000000..d25b9150924 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/split_topic_controller.js @@ -0,0 +1,49 @@ +/** + Modal related to auto closing of topics + + @class SplitTopicController + @extends Discourse.ObjectController + @namespace Discourse + @uses Discourse.ModalFunctionality + @module Discourse +**/ +Discourse.SplitTopicController = Discourse.ObjectController.extend(Discourse.SelectedPostsCount, Discourse.ModalFunctionality, { + needs: ['topic'], + + topicController: Em.computed.alias('controllers.topic'), + selectedPosts: Em.computed.alias('topicController.selectedPosts'), + saving: false, + + buttonDisabled: function() { + if (this.get('saving')) return true; + return this.blank('topicName'); + }.property('saving', 'topicName'), + + buttonTitle: function() { + if (this.get('saving')) return Em.String.i18n('saving'); + return Em.String.i18n('topic.split_topic.action'); + }.property('saving'), + + movePostsToNewTopic: function() { + this.set('saving', true); + + var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }); + var moveSelectedView = this; + + Discourse.Topic.movePosts(this.get('id'), { + title: this.get('topicName'), + post_ids: postIds + }).then(function(result) { + // Posts moved + $('#discourse-modal').modal('hide'); + moveSelectedView.get('topicController').toggleMultiSelect(); + Em.run.next(function() { Discourse.URL.routeTo(result.url); }); + }, function() { + // Error moving posts + moveSelectedView.flash(Em.String.i18n('topic.split_topic.error')); + moveSelectedView.set('saving', false); + }); + return false; + } + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/controllers/topic_admin_menu_controller.js b/app/assets/javascripts/discourse/controllers/topic_admin_menu_controller.js index f1ff706e81c..45e06232de3 100644 --- a/app/assets/javascripts/discourse/controllers/topic_admin_menu_controller.js +++ b/app/assets/javascripts/discourse/controllers/topic_admin_menu_controller.js @@ -16,15 +16,6 @@ Discourse.TopicAdminMenuController = Discourse.ObjectController.extend({ hide: function() { this.set('visible', false); - }, - - autoClose: function() { - var modalController = this.get('controllers.modal'); - if (modalController) { - var v = Discourse.EditTopicAutoCloseView.create(); - v.set('topic', this.get('content')); - modalController.show(v); - } } }); diff --git a/app/assets/javascripts/discourse/controllers/topic_controller.js b/app/assets/javascripts/discourse/controllers/topic_controller.js index a41d81aa2e5..7231e2601bb 100644 --- a/app/assets/javascripts/discourse/controllers/topic_controller.js +++ b/app/assets/javascripts/discourse/controllers/topic_controller.js @@ -107,29 +107,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected this.toggleProperty('summaryCollapsed'); }, - splitTopic: function() { - var modalController = this.get('controllers.modal'); - if (!modalController) return; - - modalController.show(Discourse.SplitTopicView.create({ - topicController: this, - topic: this.get('content'), - selectedPosts: this.get('selectedPosts') - })); - }, - - mergeTopic: function() { - var modalController = this.get('controllers.modal'); - if (!modalController) return; - - modalController.show(Discourse.MergeTopicView.create({ - topicController: this, - topic: this.get('content'), - allPostsSelected: this.get('allPostsSelected'), - selectedPosts: this.get('selectedPosts') - })); - }, - deleteSelected: function() { var topicController = this; bootbox.confirm(Em.String.i18n("post.delete.confirm", { count: this.get('selectedPostsCount')}), function(result) { @@ -432,47 +409,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected actionType.loadUsers(); }, - showPrivateInviteModal: function() { - var modal = Discourse.InvitePrivateModalView.create({ - topic: this.get('content') - }); - - var modalController = this.get('controllers.modal'); - if (modalController) { - modalController.show(modal); - } - }, - - showInviteModal: function() { - var modalController = this.get('controllers.modal'); - if (modalController) { - modalController.show(Discourse.InviteModalView.create({ - topic: this.get('content') - })); - } - }, - - // Clicked the flag button - showFlags: function(post) { - var modalController = this.get('controllers.modal'); - if (modalController) { - modalController.show(Discourse.FlagView.create({ - post: post, - controller: this - })); - } - }, - - showHistory: function(post) { - var modalController = this.get('controllers.modal'); - - if (modalController) { - modalController.show(Discourse.HistoryView.create({ - originalPost: post - })); - } - }, - recoverPost: function(post) { post.set('deleted_at', null); post.recover(); diff --git a/app/assets/javascripts/discourse/mixins/modal_functionality.js b/app/assets/javascripts/discourse/mixins/modal_functionality.js new file mode 100644 index 00000000000..acb22820496 --- /dev/null +++ b/app/assets/javascripts/discourse/mixins/modal_functionality.js @@ -0,0 +1,28 @@ +/** + This mixin provides functionality to modal controllers + + @class Discourse.ModalFunctionality + @extends Ember.Mixin + @namespace Discourse + @module Discourse +**/ +Discourse.ModalFunctionality = Em.Mixin.create({ + needs: ['modal'], + + /** + Flash a message at the top of the modal + + @method blank + @param {String} name the name of the property we want to check + @return {Boolean} + **/ + flash: function(message, messageClass) { + this.set('flashMessage', Em.Object.create({ + message: message, + messageClass: messageClass + })); + } + +}); + + diff --git a/app/assets/javascripts/discourse/models/archetype.js b/app/assets/javascripts/discourse/models/archetype.js index dd6f07b0051..5073da8c952 100644 --- a/app/assets/javascripts/discourse/models/archetype.js +++ b/app/assets/javascripts/discourse/models/archetype.js @@ -8,10 +8,10 @@ **/ Discourse.Archetype = Discourse.Model.extend({ - hasOptions: (function() { + hasOptions: function() { if (!this.get('options')) return false; return this.get('options').length > 0; - }).property('options.@each'), + }.property('options.@each'), isDefault: function() { return this.get('id') === Discourse.Site.instance().get('default_archetype'); diff --git a/app/assets/javascripts/discourse/routes/application_route.js b/app/assets/javascripts/discourse/routes/application_route.js new file mode 100644 index 00000000000..290af9ce1a5 --- /dev/null +++ b/app/assets/javascripts/discourse/routes/application_route.js @@ -0,0 +1,47 @@ +/** + Application route for Discourse + + @class ApplicationRoute + @extends Ember.Route + @namespace Discourse + @module Discourse +**/ +Discourse.ApplicationRoute = Em.Route.extend({ + + events: { + showLogin: function() { + Discourse.Route.showModal(this, 'login'); + }, + + showCreateAccount: function() { + Discourse.Route.showModal(this, 'createAccount'); + }, + + showForgotPassword: function() { + Discourse.Route.showModal(this, 'forgotPassword'); + }, + + showNotActivated: function(props) { + Discourse.Route.showModal(this, 'notActivated'); + this.controllerFor('notActivated').setProperties(props); + }, + + showImageSelector: function(composerView) { + Discourse.Route.showModal(this, 'imageSelector'); + this.controllerFor('imageSelector').setProperties({ + localSelected: true, + composerView: composerView + }); + }, + + editCategory: function(category) { + var router = this; + Discourse.Category.findBySlugOrId(category.get('slug')).then(function (c) { + Discourse.Route.showModal(router, 'editCategory', c); + router.controllerFor('editCategory').set('selectedTab', 'general'); + }) + } + + } + +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/routes/discourse_route.js b/app/assets/javascripts/discourse/routes/discourse_route.js index cb25975288e..09e88665b74 100644 --- a/app/assets/javascripts/discourse/routes/discourse_route.js +++ b/app/assets/javascripts/discourse/routes/discourse_route.js @@ -27,6 +27,7 @@ Discourse.Route = Em.Route.extend({ var hideDropDownFunction = $('html').data('hide-dropdown'); if (hideDropDownFunction) return hideDropDownFunction(); } + }); @@ -38,6 +39,23 @@ Discourse.Route.reopenClass({ if (oldBuilder) oldBuilder.call(this); return builder.call(this); }; + }, + + /** + Shows a modal + + @method showModal + **/ + showModal: function(router, name, model) { + router.controllerFor('modal').set('modalClass', null); + router.render(name, {into: 'modal', outlet: 'modalBody'}); + var controller = router.controllerFor(name); + if (controller) { + if (model) { + controller.set('model', model); + } + controller.set('flashMessage', null); + } } }); diff --git a/app/assets/javascripts/discourse/routes/list_categories_route.js b/app/assets/javascripts/discourse/routes/list_categories_route.js index a73ad1ba39c..817bc615666 100644 --- a/app/assets/javascripts/discourse/routes/list_categories_route.js +++ b/app/assets/javascripts/discourse/routes/list_categories_route.js @@ -8,6 +8,15 @@ **/ Discourse.ListCategoriesRoute = Discourse.Route.extend({ + events: { + + createCategory: function() { + Discourse.Route.showModal(this, 'editCategory', Discourse.Category.create()); + this.controllerFor('editCategory').set('selectedTab', 'general'); + } + + }, + model: function() { var listTopicsController = this.controllerFor('listTopics'); if (listTopicsController) listTopicsController.set('content', null); diff --git a/app/assets/javascripts/discourse/routes/topic_route.js b/app/assets/javascripts/discourse/routes/topic_route.js index 34eb934158e..5129bdabb22 100644 --- a/app/assets/javascripts/discourse/routes/topic_route.js +++ b/app/assets/javascripts/discourse/routes/topic_route.js @@ -8,6 +8,57 @@ **/ Discourse.TopicRoute = Discourse.Route.extend({ + events: { + // Modals that can pop up within a topic + + showFlags: function(post) { + Discourse.Route.showModal(this, 'flag', post); + this.controllerFor('flag').setProperties({ + postActionTypeId: null + }); + }, + + showAutoClose: function() { + Discourse.Route.showModal(this, 'editTopicAutoClose', this.modelFor('topic')); + this.controllerFor('modal').set('modalClass', 'edit-auto-close-modal'); + }, + + showInvite: function() { + Discourse.Route.showModal(this, 'invite', this.modelFor('topic')); + this.controllerFor('invite').setProperties({ + email: null, + error: false, + saving: false, + finished: false + }); + }, + + showPrivateInvite: function() { + Discourse.Route.showModal(this, 'invitePrivate', this.modelFor('topic')) + this.controllerFor('invitePrivate').setProperties({ + email: null, + error: false, + saving: false, + finished: false + }); + }, + + showHistory: function(post) { + Discourse.Route.showModal(this, 'history', post); + this.controllerFor('history').refresh(); + this.controllerFor('modal').set('modalClass', 'history-modal') + }, + + mergeTopic: function() { + Discourse.Route.showModal(this, 'mergeTopic', this.modelFor('topic')); + }, + + splitTopic: function() { + Discourse.Route.showModal(this, 'splitTopic', this.modelFor('topic')); + } + + }, + model: function(params) { var currentModel, _ref; if (currentModel = (_ref = this.controllerFor('topic')) ? _ref.get('content') : void 0) { diff --git a/app/assets/javascripts/discourse/templates/excerpt/category.js.handlebars b/app/assets/javascripts/discourse/templates/excerpt/category.js.handlebars index 2a57235f3b8..cb6a93cd972 100644 --- a/app/assets/javascripts/discourse/templates/excerpt/category.js.handlebars +++ b/app/assets/javascripts/discourse/templates/excerpt/category.js.handlebars @@ -18,9 +18,9 @@ diff --git a/app/assets/javascripts/discourse/templates/flag.js.handlebars b/app/assets/javascripts/discourse/templates/flag.js.handlebars deleted file mode 100644 index d25569d47bf..00000000000 --- a/app/assets/javascripts/discourse/templates/flag.js.handlebars +++ /dev/null @@ -1,36 +0,0 @@ - - - {{#if view.showSubmit}} - - {{/if}} diff --git a/app/assets/javascripts/discourse/templates/image_selector.js.handlebars b/app/assets/javascripts/discourse/templates/image_selector.js.handlebars index 2b017bf7f06..78e96f064de 100644 --- a/app/assets/javascripts/discourse/templates/image_selector.js.handlebars +++ b/app/assets/javascripts/discourse/templates/image_selector.js.handlebars @@ -1,13 +1,13 @@ -{{#if view.localSelected}} +{{#if localSelected}}