When a post returns enqueued don't insert it in the stream and notify

- Includes removal of a lot of modal boilerplate
This commit is contained in:
Robin Ward 2015-04-09 18:33:37 -04:00
parent 76f7786d0d
commit 7f501a0c41
34 changed files with 172 additions and 125 deletions

View file

@ -33,6 +33,8 @@
"triggerEvent",
"count",
"exists",
"visible",
"invisible",
"asyncTestDiscourse",
"fixture",
"find",

View file

@ -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);

View file

@ -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');
}

View file

@ -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');
}
}

View file

@ -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');
}
}

View file

@ -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'));
}
}

View file

@ -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) {

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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'});
}
},

View file

@ -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);
}
});

View file

@ -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

View file

@ -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();
},

View file

@ -0,0 +1,6 @@
<div class="modal-body">
<p>{{i18n "queue.approval.description"}}</p>
</div>
<div class="modal-footer">
{{d-button action="closeModal" class="btn-primary" label="queue.approval.ok"}}
</div>

View file

@ -1,6 +0,0 @@
import ModalBodyView from "discourse/views/modal-body";
export default ModalBodyView.extend({
templateName: 'modal/archetype_options',
title: I18n.t('topic.options')
});

View file

@ -1,6 +0,0 @@
import ModalBodyView from "discourse/views/modal-body";
export default ModalBodyView.extend({
templateName: 'modal/change_owner',
title: I18n.t('topic.change_owner.title')
});

View file

@ -1,6 +0,0 @@
import ModalBodyView from "discourse/views/modal-body";
export default ModalBodyView.extend({
templateName: 'modal/auto_close',
title: I18n.t('topic.auto_close_title')
});

View file

@ -1,6 +0,0 @@
import ModalBodyView from "discourse/views/modal-body";
export default ModalBodyView.extend({
templateName: 'modal/feature-topic',
title: I18n.t('topic.feature_topic.title')
});

View file

@ -1,6 +0,0 @@
import ModalBodyView from "discourse/views/modal-body";
export default ModalBodyView.extend({
templateName: 'modal/forgot_password',
title: I18n.t('forgot_password.title'),
});

View file

@ -1,6 +0,0 @@
import ModalBodyView from "discourse/views/modal-body";
export default ModalBodyView.extend({
templateName: 'modal/keyboard_shortcuts_help',
title: I18n.t('keyboard_shortcuts_help.title')
});

View file

@ -1,6 +0,0 @@
import ModalBodyView from "discourse/views/modal-body";
export default ModalBodyView.extend({
templateName: 'modal/merge_topic',
title: I18n.t('topic.merge_topic.title')
});

View file

@ -1,6 +0,0 @@
import ModalBodyView from "discourse/views/modal-body";
export default ModalBodyView.extend({
templateName: 'modal/not_activated',
title: I18n.t('log_in')
});

View file

@ -1,6 +0,0 @@
import ModalBodyView from "discourse/views/modal-body";
export default ModalBodyView.extend({
templateName: 'modal/topic-bulk-actions',
title: I18n.t('topics.bulk.actions')
});

View file

@ -225,6 +225,12 @@ en:
search: "Search for a Topic by name, url or id:"
placeholder: "type the topic title here"
queue:
approval:
title: "Post Needs Approval"
description: "We've received your new post but it needs to be approved by a moderator before it will appear. Please be patient."
ok: "OK"
user_action:
user_posted_topic: "<a href='{{userUrl}}'>{{user}}</a> posted <a href='{{topicUrl}}'>the topic</a>"
you_posted_topic: "<a href='{{userUrl}}'>You</a> posted <a href='{{topicUrl}}'>the topic</a>"

View file

@ -81,9 +81,31 @@ test("Create a Topic", () => {
});
});
test("Create an enqueued Topic", () => {
visit("/");
click('#create-topic');
fillIn('#reply-title', "Internationalization Localization");
fillIn('#wmd-input', "enqueue this content please");
click('#reply-control button.create');
andThen(() => {
ok(visible('#discourse-modal'), 'it pops up a modal');
equal(currentURL(), "/", "it doesn't change routes");
});
click('.modal-footer button');
andThen(() => {
ok(invisible('#discourse-modal'), 'the modal can be dismissed');
});
});
test("Create a Reply", () => {
visit("/t/internationalization-localization/280");
andThen(() => {
ok(!exists('article[data-post-id=12345]'), 'the post is not in the DOM');
});
click('#topic-footer-buttons .btn.create');
andThen(() => {
ok(exists('#wmd-input'), 'the composer input is visible');
@ -93,7 +115,32 @@ test("Create a Reply", () => {
fillIn('#wmd-input', 'this is the content of my reply');
click('#reply-control button.create');
andThen(() => {
exists('#post_12345', 'it inserts the post into the document');
equal(find('.cooked:last p').text(), 'this is the content of my reply');
});
});
test("Create an enqueued Reply", () => {
visit("/t/internationalization-localization/280");
click('#topic-footer-buttons .btn.create');
andThen(() => {
ok(exists('#wmd-input'), 'the composer input is visible');
ok(!exists('#reply-title'), 'there is no title since this is a reply');
});
fillIn('#wmd-input', 'enqueue this content please');
click('#reply-control button.create');
andThen(() => {
ok(find('.cooked:last p').text() !== 'enqueue this content please', "it doesn't insert the post");
});
andThen(() => {
ok(visible('#discourse-modal'), 'it pops up a modal');
});
click('.modal-footer button');
andThen(() => {
ok(invisible('#discourse-modal'), 'the modal can be dismissed');
});
});
@ -118,3 +165,4 @@ test("Edit the first post", () => {
ok(find('.topic-post:eq(0) .cooked').text().indexOf('This is the new text for the post') !== -1, 'it updates the post');
});
});

View file

@ -88,6 +88,8 @@ export default function() {
return [200, {"Content-Type": "text/html"}, "<div class='page-not-found'>not found</div>"];
});
this.delete('/draft.json', success);
this.get('/draft.json', function() {
return response({});
});
@ -148,13 +150,17 @@ export default function() {
if (data.title === "this title triggers an error") {
return response(422, {errors: ['That title has already been taken']});
} else {
return response(200, {
success: true,
action: 'create_post',
post: {id: 12345, topic_id: 280, topic_slug: 'internationalization-localization'}
});
}
if (data.raw === "enqueue this content please") {
return response(200, { success: true, action: 'enqueued' });
}
return response(200, {
success: true,
action: 'create_post',
post: {id: 12345, topic_id: 280, topic_slug: 'internationalization-localization'}
});
});
this.get('/widgets/:widget_id', function(request) {