diff --git a/.jshintrc b/.jshintrc index 962847d7232..71726a3bd81 100644 --- a/.jshintrc +++ b/.jshintrc @@ -33,6 +33,8 @@ "triggerEvent", "count", "exists", + "visible", + "invisible", "asyncTestDiscourse", "fixture", "find", diff --git a/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 b/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 index 56d0f93dafe..648730d9b0a 100644 --- a/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 @@ -24,8 +24,8 @@ export default Ember.Route.extend({ }, editGroupings() { - const groupings = this.controllerFor('admin-badges').get('badgeGroupings'); - showModal('modals/admin-edit-badge-groupings', groupings); + const model = this.controllerFor('admin-badges').get('badgeGroupings'); + showModal('modals/admin-edit-badge-groupings', { model }); }, preview(badge, explain) { @@ -38,9 +38,9 @@ export default Ember.Route.extend({ trigger: badge.get('trigger'), explain } - }).then(function(json) { + }).then(function(model) { badge.set('preview_loading', false); - showModal('modals/admin-badge-preview', json); + showModal('modals/admin-badge-preview', { model }); }).catch(function(error) { badge.set('preview_loading', false); Em.Logger.error(error); diff --git a/app/assets/javascripts/admin/routes/admin-flags-list.js.es6 b/app/assets/javascripts/admin/routes/admin-flags-list.js.es6 index 99ceba77178..e277ef4417a 100644 --- a/app/assets/javascripts/admin/routes/admin-flags-list.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-flags-list.js.es6 @@ -12,13 +12,13 @@ export default Discourse.Route.extend({ }, actions: { - showAgreeFlagModal(flaggedPost) { - showModal('modals/admin-agree-flag', flaggedPost); + showAgreeFlagModal(model) { + showModal('modals/admin-agree-flag', { model }); this.controllerFor('modal').set('modalClass', 'agree-flag-modal'); }, - showDeleteFlagModal(flaggedPost) { - showModal('modals/admin-delete-flag', flaggedPost); + showDeleteFlagModal(model) { + showModal('modals/admin-delete-flag', { model }); this.controllerFor('modal').set('modalClass', 'delete-flag-modal'); } diff --git a/app/assets/javascripts/admin/routes/admin-logs-staff-action-logs.js.es6 b/app/assets/javascripts/admin/routes/admin-logs-staff-action-logs.js.es6 index f0f08ab2f53..6ef900e8436 100644 --- a/app/assets/javascripts/admin/routes/admin-logs-staff-action-logs.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-logs-staff-action-logs.js.es6 @@ -12,14 +12,14 @@ export default Discourse.Route.extend({ }, actions: { - showDetailsModal(logRecord) { - showModal('modals/admin-staff-action-log-details', logRecord); + showDetailsModal(model) { + showModal('modals/admin-staff-action-log-details', { model }); this.controllerFor('modal').set('modalClass', 'log-details-modal'); }, - showCustomDetailsModal(logRecord) { - const modalName = "modals/" + (logRecord.action_name + '_details').replace("_", "-"); - showModal(modalName, logRecord); + showCustomDetailsModal(model) { + const modalName = "modals/" + (model.action_name + '_details').replace("_", "-"); + showModal(modalName, { model }); this.controllerFor('modal').set('modalClass', 'tabbed-modal log-details-modal'); } } diff --git a/app/assets/javascripts/admin/routes/admin-user-index.js.es6 b/app/assets/javascripts/admin/routes/admin-user-index.js.es6 index 07cbfa8fc32..fdcf844abd8 100644 --- a/app/assets/javascripts/admin/routes/admin-user-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-user-index.js.es6 @@ -24,8 +24,8 @@ export default Discourse.Route.extend({ }, actions: { - showSuspendModal(user) { - showModal('modals/admin-suspend-user', user); + showSuspendModal(model) { + showModal('modals/admin-suspend-user', { model }); this.controllerFor('modal').set('modalClass', 'suspend-user-modal'); } } diff --git a/app/assets/javascripts/discourse/components/bulk-select-button.js.es6 b/app/assets/javascripts/discourse/components/bulk-select-button.js.es6 index ec47bbd56ff..8c50f5e0e4d 100644 --- a/app/assets/javascripts/discourse/components/bulk-select-button.js.es6 +++ b/app/assets/javascripts/discourse/components/bulk-select-button.js.es6 @@ -3,7 +3,7 @@ import showModal from 'discourse/lib/show-modal'; export default Ember.Component.extend({ actions: { showBulkActions() { - const controller = showModal('topicBulkActions', this.get('selected')); + const controller = showModal('topic-bulk-actions', { model: this.get('selected'), title: 'topics.bulk.actions' }); controller.set('refreshTarget', this.get('refreshTarget')); } } diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index c7df6841578..7a97eda7a91 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -218,14 +218,20 @@ export default DiscourseController.extend({ const promise = composer.save({ imageSizes: this.get('view').imageSizes(), editReason: this.get("editReason") - }).then(function(opts) { + }).then(function(result) { + + if (result.responseJson.action === "enqueued") { + self.send('postWasEnqueued'); + self.destroyDraft(); + self.close(); + return result; + } // If we replied as a new topic successfully, remove the draft. if (self.get('replyAsNewTopicDraft')) { self.destroyDraft(); } - opts = opts || {}; self.close(); const currentUser = Discourse.User.current(); @@ -238,8 +244,9 @@ export default DiscourseController.extend({ // TODO disableJumpReply is super crude, it needs to provide some sort // of notification to the end user if (!composer.get('replyingToTopic') || !disableJumpReply) { - if (opts.post && !staged) { - Discourse.URL.routeTo(opts.post.get('url')); + const post = result.target; + if (post && !staged) { + Discourse.URL.routeTo(post.get('url')); } } }).catch(function(error) { diff --git a/app/assets/javascripts/discourse/lib/show-modal.js.es6 b/app/assets/javascripts/discourse/lib/show-modal.js.es6 index 60077009302..91713ca7076 100644 --- a/app/assets/javascripts/discourse/lib/show-modal.js.es6 +++ b/app/assets/javascripts/discourse/lib/show-modal.js.es6 @@ -1,13 +1,33 @@ -export default (name, model) => { +export default (name, opts) => { + opts = opts || {}; + + const container = Discourse.__container__; + // We use the container here because modals are like singletons // in Discourse. Only one can be shown with a particular state. - const route = Discourse.__container__.lookup('route:application'); + const route = container.lookup('route:application'); + const modalController = route.controllerFor('modal'); - route.controllerFor('modal').set('modalClass', null); - route.render(name, { into: 'modal', outlet: 'modalBody' }); + modalController.set('modalClass', null); + + const viewClass = container.lookupFactory('view:' + name); + const controller = container.lookup('controller:' + name); + if (viewClass) { + route.render(name, { into: 'modal', outlet: 'modalBody' }); + } else { + const templateName = Ember.String.dasherize(name); + + const renderArgs = { into: 'modal', outlet: 'modalBody', view: 'modal-body'}; + if (controller) { renderArgs.controller = name; } + + route.render('modal/' + templateName, renderArgs); + if (opts.title) { + modalController.set('title', I18n.t(opts.title)); + } + } - const controller = route.controllerFor(name); if (controller) { + const model = opts.model; if (model) { controller.set('model', model); } if (controller.onShow) { controller.onShow(); } controller.set('flashMessage', null); diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index a8ffe86735b..1b75c77e94b 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -440,8 +440,9 @@ const Composer = RestModel.extend({ this.set('composeState', CLOSED); return promise.then(function() { - return post.save(props).then(function() { + return post.save(props).then(function(result) { self.clearState(); + return result; }).catch(throwAjaxError(function() { post.set('cooked', oldCooked); self.set('composeState', OPEN); @@ -526,9 +527,13 @@ const Composer = RestModel.extend({ composer.set("stagedPost", state === "staged" && createdPost); return createdPost.save().then(function(result) { - let saving = true; + if (result.responseJson.action === "enqueued") { + if (postStream) { postStream.undoPost(createdPost); } + return result; + } + if (topic) { // It's no longer a new post topic.set('draft_sequence', result.target.draft_sequence); @@ -554,7 +559,7 @@ const Composer = RestModel.extend({ composer.set('composeState', SAVING); } - return { post: createdPost }; + return result; }).catch(throwAjaxError(function() { if (postStream) { postStream.undoPost(createdPost); diff --git a/app/assets/javascripts/discourse/models/rest.js.es6 b/app/assets/javascripts/discourse/models/rest.js.es6 index 37afe11eb9c..a35da6edf41 100644 --- a/app/assets/javascripts/discourse/models/rest.js.es6 +++ b/app/assets/javascripts/discourse/models/rest.js.es6 @@ -30,9 +30,13 @@ const RestModel = Ember.Object.extend(Presence, { const self = this; return adapter.createRecord(store, type, props).then(function(res) { if (!res) { throw "Received no data back from createRecord"; } - self.setProperties(self.__munge(res.payload)); - self.set('__state', 'created'); + // We can get a response back without properties, for example + // when a post is queued. + if (res.payload) { + self.setProperties(self.__munge(res.payload)); + self.set('__state', 'created'); + } res.target = self; return res; diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6 index 45dbfbf77c3..16bbdbebc5a 100644 --- a/app/assets/javascripts/discourse/routes/application.js.es6 +++ b/app/assets/javascripts/discourse/routes/application.js.es6 @@ -37,6 +37,10 @@ const ApplicationRoute = Discourse.Route.extend({ this.controllerFor('topic-entrance').send('show', data); }, + postWasEnqueued() { + showModal('post-enqueued', {title: 'queue.approval.title' }); + }, + composePrivateMessage(user, post) { const self = this; this.transitionTo('userActivity', user).then(function () { @@ -76,12 +80,12 @@ const ApplicationRoute = Discourse.Route.extend({ showCreateAccount: unlessReadOnly('handleShowCreateAccount'), showForgotPassword() { - showModal('forgotPassword'); + showModal('forgotPassword', { title: 'forgot_password.title' }); }, showNotActivated(props) { - showModal('notActivated'); - this.controllerFor('notActivated').setProperties(props); + const controller = showModal('not-activated', {title: 'log_in' }); + controller.setProperties(props); }, showUploadSelector(composerView) { @@ -90,13 +94,13 @@ const ApplicationRoute = Discourse.Route.extend({ }, showKeyboardShortcutsHelp() { - showModal('keyboardShortcutsHelp'); + showModal('keyboard-shortcuts-help', { title: 'keyboard_shortcuts_help.title'}); }, showSearchHelp() { // TODO: @EvitTrout how do we get a loading indicator here? - Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then(function(html){ - showModal('searchHelp', html); + Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then(function(model){ + showModal('searchHelp', { model }); }); }, @@ -120,9 +124,9 @@ const ApplicationRoute = Discourse.Route.extend({ editCategory(category) { const self = this; - Discourse.Category.reloadById(category.get('id')).then(function (c) { - self.site.updateCategory(c); - showModal('editCategory', c); + Discourse.Category.reloadById(category.get('id')).then(function (model) { + self.site.updateCategory(model); + showModal('editCategory', { model }); self.controllerFor('editCategory').set('selectedTab', 'general'); }); }, @@ -140,7 +144,7 @@ const ApplicationRoute = Discourse.Route.extend({ const controllerName = w.replace('modal/', ''), factory = this.container.lookupFactory('controller:' + controllerName); - this.render(w, {into: 'topicBulkActions', outlet: 'bulkOutlet', controller: factory ? controllerName : 'topic-bulk-actions'}); + this.render(w, {into: 'modal/topic-bulk-actions', outlet: 'bulkOutlet', controller: factory ? controllerName : 'topic-bulk-actions'}); } }, diff --git a/app/assets/javascripts/discourse/routes/discourse.js.es6 b/app/assets/javascripts/discourse/routes/discourse.js.es6 index 90611244605..1f5bdab25f5 100644 --- a/app/assets/javascripts/discourse/routes/discourse.js.es6 +++ b/app/assets/javascripts/discourse/routes/discourse.js.es6 @@ -1,5 +1,3 @@ -import showModal from 'discourse/lib/show-modal'; - const DiscourseRoute = Ember.Route.extend({ // Set to true to refresh a model without a transition if a query param @@ -210,11 +208,6 @@ DiscourseRoute.reopenClass({ this.route('unknown', {path: '*path'}); }); - }, - - showModal: function(route, name, model) { - Ember.warn('DEPRECATED `Discourse.Route.showModal` - use `showModal` instead'); - showModal(name, model); } }); diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6 index 0f23333859a..381692afea1 100644 --- a/app/assets/javascripts/discourse/routes/topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic.js.es6 @@ -43,52 +43,52 @@ const TopicRoute = Discourse.Route.extend(ShowFooter, { this.controllerFor("topic-admin-menu").send("show"); }, - showFlags(post) { - showModal('flag', post); + showFlags(model) { + showModal('flag', { model }); this.controllerFor('flag').setProperties({ selected: null }); }, - showFlagTopic(topic) { - showModal('flag', topic); + showFlagTopic(model) { + showModal('flag', { model }); this.controllerFor('flag').setProperties({ selected: null, flagTopic: true }); }, showAutoClose() { - showModal('editTopicAutoClose', this.modelFor('topic')); + showModal('edit-topic-auto-close', { model: this.modelFor('topic'), title: 'topic.auto_close_title' }); this.controllerFor('modal').set('modalClass', 'edit-auto-close-modal'); }, showFeatureTopic() { - showModal('featureTopic', this.modelFor('topic')); + showModal('featureTopic', { model: this.modelFor('topic'), title: 'topic.feature_topic.title' }); this.controllerFor('modal').set('modalClass', 'feature-topic-modal'); }, showInvite() { - showModal('invite', this.modelFor('topic')); + showModal('invite', { model: this.modelFor('topic') }); this.controllerFor('invite').reset(); }, - showHistory(post) { - showModal('history', post); - this.controllerFor('history').refresh(post.get("id"), "latest"); + showHistory(model) { + showModal('history', { model }); + this.controllerFor('history').refresh(model.get("id"), "latest"); this.controllerFor('modal').set('modalClass', 'history-modal'); }, - showRawEmail(post) { - showModal('raw-email', post); - this.controllerFor('raw_email').loadRawEmail(post.get("id")); + showRawEmail(model) { + showModal('raw-email', { model }); + this.controllerFor('raw_email').loadRawEmail(model.get("id")); }, mergeTopic() { - showModal('mergeTopic', this.modelFor('topic')); + showModal('merge-topic', { model: this.modelFor('topic'), title: 'topic.merge_topic.title' }); }, splitTopic() { - showModal('split-topic', this.modelFor('topic')); + showModal('split-topic', { model: this.modelFor('topic') }); }, changeOwner() { - showModal('changeOwner', this.modelFor('topic')); + showModal('change-owner', { model: this.modelFor('topic'), title: 'topic.change_owner.title' }); }, // Use replaceState to update the URL once it changes diff --git a/app/assets/javascripts/discourse/routes/user-invited.js.es6 b/app/assets/javascripts/discourse/routes/user-invited.js.es6 index b0e03559e6c..a36f49af455 100644 --- a/app/assets/javascripts/discourse/routes/user-invited.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-invited.js.es6 @@ -21,7 +21,7 @@ export default Discourse.Route.extend(ShowFooter, { actions: { showInvite() { - showModal('invite', Discourse.User.current()); + showModal('invite', { model: this.currentUser }); this.controllerFor('invite').reset(); }, diff --git a/app/assets/javascripts/discourse/templates/modal/change_owner.hbs b/app/assets/javascripts/discourse/templates/modal/change-owner.hbs similarity index 100% rename from app/assets/javascripts/discourse/templates/modal/change_owner.hbs rename to app/assets/javascripts/discourse/templates/modal/change-owner.hbs diff --git a/app/assets/javascripts/discourse/templates/modal/auto_close.hbs b/app/assets/javascripts/discourse/templates/modal/edit-topic-auto-close.hbs similarity index 100% rename from app/assets/javascripts/discourse/templates/modal/auto_close.hbs rename to app/assets/javascripts/discourse/templates/modal/edit-topic-auto-close.hbs diff --git a/app/assets/javascripts/discourse/templates/modal/forgot_password.hbs b/app/assets/javascripts/discourse/templates/modal/forgot-password.hbs similarity index 100% rename from app/assets/javascripts/discourse/templates/modal/forgot_password.hbs rename to app/assets/javascripts/discourse/templates/modal/forgot-password.hbs diff --git a/app/assets/javascripts/discourse/templates/modal/keyboard_shortcuts_help.hbs b/app/assets/javascripts/discourse/templates/modal/keyboard-shortcuts-help.hbs similarity index 100% rename from app/assets/javascripts/discourse/templates/modal/keyboard_shortcuts_help.hbs rename to app/assets/javascripts/discourse/templates/modal/keyboard-shortcuts-help.hbs diff --git a/app/assets/javascripts/discourse/templates/modal/merge_topic.hbs b/app/assets/javascripts/discourse/templates/modal/merge-topic.hbs similarity index 100% rename from app/assets/javascripts/discourse/templates/modal/merge_topic.hbs rename to app/assets/javascripts/discourse/templates/modal/merge-topic.hbs diff --git a/app/assets/javascripts/discourse/templates/modal/not_activated.hbs b/app/assets/javascripts/discourse/templates/modal/not-activated.hbs similarity index 100% rename from app/assets/javascripts/discourse/templates/modal/not_activated.hbs rename to app/assets/javascripts/discourse/templates/modal/not-activated.hbs diff --git a/app/assets/javascripts/discourse/templates/modal/post-enqueued.hbs b/app/assets/javascripts/discourse/templates/modal/post-enqueued.hbs new file mode 100644 index 00000000000..050d02dcf19 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/modal/post-enqueued.hbs @@ -0,0 +1,6 @@ +
{{i18n "queue.approval.description"}}
+