Split out bulk operations modal and Discourse.Route.showModal

This makes it easier to share bulk topic operations, for example
from a plugin's custom topic list.
This commit is contained in:
Robin Ward 2015-03-10 15:01:15 -04:00
parent e9b04170c6
commit f50280a889
27 changed files with 313 additions and 307 deletions

View File

@ -19,7 +19,7 @@ export default ObjectController.extend(ModalFunctionality, {
window.location.reload(); window.location.reload();
}, function(e) { }, function(e) {
var error = I18n.t('admin.user.suspend_failed', { error: "http: " + e.status + " - " + e.body }); var error = I18n.t('admin.user.suspend_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error, function() { self.send('showModal'); }); bootbox.alert(error, function() { self.send('reopenModal'); });
}); });
} }
} }

View File

@ -1,12 +1,14 @@
Discourse.AdminBackupsRoute = Discourse.Route.extend({ import showModal from 'discourse/lib/show-modal';
export default Discourse.Route.extend({
LOG_CHANNEL: "/admin/backups/logs", LOG_CHANNEL: "/admin/backups/logs",
activate: function() { activate() {
Discourse.MessageBus.subscribe(this.LOG_CHANNEL, this._processLogMessage.bind(this)); Discourse.MessageBus.subscribe(this.LOG_CHANNEL, this._processLogMessage.bind(this));
}, },
_processLogMessage: function(log) { _processLogMessage(log) {
if (log.message === "[STARTED]") { if (log.message === "[STARTED]") {
this.controllerFor("adminBackups").set("isOperationRunning", true); this.controllerFor("adminBackups").set("isOperationRunning", true);
this.controllerFor("adminBackupsLogs").clear(); this.controllerFor("adminBackupsLogs").clear();
@ -25,7 +27,7 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
} }
}, },
model: function() { model() {
return PreloadStore.getAndRemove("operations_status", function() { return PreloadStore.getAndRemove("operations_status", function() {
return Discourse.ajax("/admin/backups/status.json"); return Discourse.ajax("/admin/backups/status.json");
}).then(function (status) { }).then(function (status) {
@ -37,35 +39,24 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
}); });
}, },
deactivate: function() { deactivate() {
Discourse.MessageBus.unsubscribe(this.LOG_CHANNEL); Discourse.MessageBus.unsubscribe(this.LOG_CHANNEL);
}, },
actions: { actions: {
/** startBackup() {
Starts a backup and redirect the user to the logs tab showModal('admin_start_backup');
@method startBackup
**/
startBackup: function() {
Discourse.Route.showModal(this, 'admin_start_backup');
this.controllerFor('modal').set('modalClass', 'start-backup-modal'); this.controllerFor('modal').set('modalClass', 'start-backup-modal');
}, },
backupStarted: function () { backupStarted() {
this.modelFor("adminBackups").set("isOperationRunning", true); this.modelFor("adminBackups").set("isOperationRunning", true);
this.transitionTo("admin.backups.logs"); this.transitionTo("admin.backups.logs");
this.send("closeModal"); this.send("closeModal");
}, },
/** destroyBackup(backup) {
Destroys a backup const self = this;
@method destroyBackup
@param {Discourse.Backup} backup the backup to destroy
**/
destroyBackup: function(backup) {
var self = this;
bootbox.confirm( bootbox.confirm(
I18n.t("admin.backups.operations.destroy.confirm"), I18n.t("admin.backups.operations.destroy.confirm"),
I18n.t("no_value"), I18n.t("no_value"),
@ -80,14 +71,8 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
); );
}, },
/** startRestore(backup) {
Start a restore and redirect the user to the logs tab const self = this;
@method startRestore
@param {Discourse.Backup} backup the backup to restore
**/
startRestore: function(backup) {
var self = this;
bootbox.confirm( bootbox.confirm(
I18n.t("admin.backups.operations.restore.confirm"), I18n.t("admin.backups.operations.restore.confirm"),
I18n.t("no_value"), I18n.t("no_value"),
@ -105,13 +90,8 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
); );
}, },
/** cancelOperation() {
Cancels the current operation const self = this;
@method cancelOperation
**/
cancelOperation: function() {
var self = this;
bootbox.confirm( bootbox.confirm(
I18n.t("admin.backups.operations.cancel.confirm"), I18n.t("admin.backups.operations.cancel.confirm"),
I18n.t("no_value"), I18n.t("no_value"),
@ -126,12 +106,7 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
); );
}, },
/** rollback() {
Rollback to previous working state
@method rollback
**/
rollback: function() {
bootbox.confirm( bootbox.confirm(
I18n.t("admin.backups.operations.rollback.confirm"), I18n.t("admin.backups.operations.rollback.confirm"),
I18n.t("no_value"), I18n.t("no_value"),
@ -142,8 +117,8 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
); );
}, },
uploadSuccess: function(filename) { uploadSuccess(filename) {
var self = this; const self = this;
bootbox.alert(I18n.t("admin.backups.upload.success", { filename: filename }), function() { bootbox.alert(I18n.t("admin.backups.upload.success", { filename: filename }), function() {
Discourse.Backup.find().then(function (backups) { Discourse.Backup.find().then(function (backups) {
self.controllerFor("adminBackupsIndex").set("model", backups); self.controllerFor("adminBackupsIndex").set("model", backups);
@ -151,7 +126,7 @@ Discourse.AdminBackupsRoute = Discourse.Route.extend({
}); });
}, },
uploadError: function(filename, message) { uploadError(filename, message) {
bootbox.alert(I18n.t("admin.backups.upload.error", { filename: filename, message: message })); bootbox.alert(I18n.t("admin.backups.upload.error", { filename: filename, message: message }));
} }
} }

View File

@ -1,9 +1,11 @@
import showModal from 'discourse/lib/show-modal';
export default Ember.Route.extend({ export default Ember.Route.extend({
serialize: function(m) { serialize(m) {
return {badge_id: Em.get(m, 'id') || 'new'}; return {badge_id: Em.get(m, 'id') || 'new'};
}, },
model: function(params) { model(params) {
if (params.badge_id === "new") { if (params.badge_id === "new") {
return Discourse.Badge.create({ return Discourse.Badge.create({
name: I18n.t('admin.badges.new_badge') name: I18n.t('admin.badges.new_badge')
@ -13,22 +15,20 @@ export default Ember.Route.extend({
}, },
actions: { actions: {
saveError: function(e) { saveError(e) {
var msg = I18n.t("generic_error"); let msg = I18n.t("generic_error");
if (e.responseJSON && e.responseJSON.errors) { if (e.responseJSON && e.responseJSON.errors) {
msg = I18n.t("generic_error_with_reason", {error: e.responseJSON.errors.join('. ')}); msg = I18n.t("generic_error_with_reason", {error: e.responseJSON.errors.join('. ')});
} }
bootbox.alert(msg); bootbox.alert(msg);
}, },
editGroupings: function() { editGroupings() {
var groupings = this.controllerFor('admin-badges').get('badgeGroupings'); const groupings = this.controllerFor('admin-badges').get('badgeGroupings');
Discourse.Route.showModal(this, 'admin_edit_badge_groupings', groupings); showModal('admin_edit_badge_groupings', groupings);
}, },
preview: function(badge, explain) { preview(badge, explain) {
var self = this;
badge.set('preview_loading', true); badge.set('preview_loading', true);
Discourse.ajax('/admin/badges/preview.json', { Discourse.ajax('/admin/badges/preview.json', {
method: 'post', method: 'post',
@ -36,11 +36,11 @@ export default Ember.Route.extend({
sql: badge.get('query'), sql: badge.get('query'),
target_posts: !!badge.get('target_posts'), target_posts: !!badge.get('target_posts'),
trigger: badge.get('trigger'), trigger: badge.get('trigger'),
explain: explain explain
} }
}).then(function(json) { }).then(function(json) {
badge.set('preview_loading', false); badge.set('preview_loading', false);
Discourse.Route.showModal(self, 'admin_badge_preview', json); showModal('admin_badge_preview', json);
}).catch(function(error) { }).catch(function(error) {
badge.set('preview_loading', false); badge.set('preview_loading', false);
Em.Logger.error(error); Em.Logger.error(error);

View File

@ -1,22 +1,24 @@
import showModal from 'discourse/lib/show-modal';
export default Discourse.Route.extend({ export default Discourse.Route.extend({
model: function(params) { model(params) {
this.filter = params.filter; this.filter = params.filter;
return Discourse.FlaggedPost.findAll(params.filter); return Discourse.FlaggedPost.findAll(params.filter);
}, },
setupController: function(controller, model) { setupController(controller, model) {
controller.set('model', model); controller.set('model', model);
controller.set('query', this.filter); controller.set('query', this.filter);
}, },
actions: { actions: {
showAgreeFlagModal: function (flaggedPost) { showAgreeFlagModal(flaggedPost) {
Discourse.Route.showModal(this, 'admin_agree_flag', flaggedPost); showModal('admin_agree_flag', flaggedPost);
this.controllerFor('modal').set('modalClass', 'agree-flag-modal'); this.controllerFor('modal').set('modalClass', 'agree-flag-modal');
}, },
showDeleteFlagModal: function (flaggedPost) { showDeleteFlagModal(flaggedPost) {
Discourse.Route.showModal(this, 'admin_delete_flag', flaggedPost); showModal('admin_delete_flag', flaggedPost);
this.controllerFor('modal').set('modalClass', 'delete-flag-modal'); this.controllerFor('modal').set('modalClass', 'delete-flag-modal');
} }

View File

@ -1,3 +1,5 @@
import showModal from 'discourse/lib/show-modal';
export default Discourse.Route.extend({ export default Discourse.Route.extend({
// TODO: make this automatic using an `{{outlet}}` // TODO: make this automatic using an `{{outlet}}`
renderTemplate: function() { renderTemplate: function() {
@ -10,13 +12,13 @@ export default Discourse.Route.extend({
}, },
actions: { actions: {
showDetailsModal: function(logRecord) { showDetailsModal(logRecord) {
Discourse.Route.showModal(this, 'admin_staff_action_log_details', logRecord); showModal('admin_staff_action_log_details', logRecord);
this.controllerFor('modal').set('modalClass', 'log-details-modal'); this.controllerFor('modal').set('modalClass', 'log-details-modal');
}, },
showCustomDetailsModal: function(logRecord) { showCustomDetailsModal(logRecord) {
Discourse.Route.showModal(this, logRecord.action_name + '_details', logRecord); showModal(logRecord.action_name + '_details', logRecord);
this.controllerFor('modal').set('modalClass', 'tabbed-modal log-details-modal'); this.controllerFor('modal').set('modalClass', 'tabbed-modal log-details-modal');
} }
} }

View File

@ -0,0 +1,32 @@
import showModal from 'discourse/lib/show-modal';
export default Discourse.Route.extend({
model() {
return this.modelFor('adminUser');
},
afterModel(model) {
if (this.currentUser.get('admin')) {
const self = this;
return Discourse.Group.findAll().then(function(groups){
self._availableGroups = groups.filterBy('automatic', false);
return model;
});
}
},
setupController(controller, model) {
controller.setProperties({
originalPrimaryGroupId: model.get('primary_group_id'),
availableGroups: this._availableGroups,
model
});
},
actions: {
showSuspendModal(user) {
showModal('admin_suspend_user', user);
this.controllerFor('modal').set('modalClass', 'suspend-user-modal');
}
}
});

View File

@ -0,0 +1,20 @@
export default Discourse.Route.extend({
serialize(model) {
return { username: model.get('username').toLowerCase() };
},
model(params) {
return Discourse.AdminUser.find(Em.get(params, 'username').toLowerCase());
},
renderTemplate() {
this.render({into: 'admin'});
},
afterModel(adminUser) {
return adminUser.loadDetails().then(function () {
adminUser.setOriginalTrustLevel();
return adminUser;
});
}
});

View File

@ -1,51 +0,0 @@
Discourse.AdminUserRoute = Discourse.Route.extend({
serialize: function(model) {
return { username: model.get('username').toLowerCase() };
},
model: function(params) {
return Discourse.AdminUser.find(Em.get(params, 'username').toLowerCase());
},
renderTemplate: function() {
this.render({into: 'admin'});
},
afterModel: function(adminUser) {
return adminUser.loadDetails().then(function () {
adminUser.setOriginalTrustLevel();
return adminUser;
});
}
});
Discourse.AdminUserIndexRoute = Discourse.Route.extend({
model: function() {
return this.modelFor('adminUser');
},
afterModel: function(model) {
if(Discourse.User.currentProp('admin')) {
var self = this;
return Discourse.Group.findAll().then(function(groups){
self._availableGroups = groups.filterBy('automatic', false);
return model;
});
}
},
setupController: function(controller, model) {
controller.setProperties({
originalPrimaryGroupId: model.get('primary_group_id'),
availableGroups: this._availableGroups,
model: model
});
},
actions: {
showSuspendModal: function(user) {
Discourse.Route.showModal(this, 'admin_suspend_user', user);
this.controllerFor('modal').set('modalClass', 'suspend-user-modal');
}
}
});

View File

@ -0,0 +1,10 @@
import showModal from 'discourse/lib/show-modal';
export default Ember.Component.extend({
actions: {
showBulkActions() {
const controller = showModal('topicBulkActions', this.get('selected'));
controller.set('refreshTarget', this.get('refreshTarget'));
}
}
});

View File

@ -1,11 +1,9 @@
import DiscoveryController from 'discourse/controllers/discovery'; import DiscoveryController from 'discourse/controllers/discovery';
import { queryParams } from 'discourse/controllers/discovery-sortable'; import { queryParams } from 'discourse/controllers/discovery-sortable';
import NotificationLevels from 'discourse/lib/notification-levels'; import BulkTopicSelection from 'discourse/mixins/bulk-topic-selection';
var controllerOpts = { var controllerOpts = {
needs: ['discovery'], needs: ['discovery'],
bulkSelectEnabled: false,
selected: [],
period: null, period: null,
canStar: Em.computed.alias('controllers.discovery/topics.currentUser.id'), canStar: Em.computed.alias('controllers.discovery/topics.currentUser.id'),
@ -53,7 +51,8 @@ var controllerOpts = {
Discourse.TopicList.find(filter).then(function(list) { Discourse.TopicList.find(filter).then(function(list) {
Discourse.TopicList.hideUniformCategory(list, self.get('category')); Discourse.TopicList.hideUniformCategory(list, self.get('category'));
self.setProperties({ model: list, selected: [] }); self.setProperties({ model: list });
self.resetSelected();
var tracking = Discourse.TopicTrackingState.current(); var tracking = Discourse.TopicTrackingState.current();
if (tracking) { if (tracking) {
@ -64,10 +63,6 @@ var controllerOpts = {
}); });
}, },
toggleBulkSelect: function() {
this.toggleProperty('bulkSelectEnabled');
this.get('selected').clear();
},
resetNew: function() { resetNew: function() {
var self = this; var self = this;
@ -76,40 +71,9 @@ var controllerOpts = {
Discourse.Topic.resetNew().then(function() { Discourse.Topic.resetNew().then(function() {
self.send('refresh'); self.send('refresh');
}); });
},
dismissRead: function(operationType) {
var self = this,
selected = this.get('selected'),
operation;
if(operationType === "posts"){
operation = { type: 'dismiss_posts' };
} else {
operation = { type: 'change_notification_level',
notification_level_id: NotificationLevels.REGULAR };
}
var promise;
if (selected.length > 0) {
promise = Discourse.Topic.bulkOperation(selected, operation);
} else {
promise = Discourse.Topic.bulkOperationByFilter('unread', operation, this.get('category.id'));
}
promise.then(function(result) {
if (result && result.topic_ids) {
var tracker = Discourse.TopicTrackingState.current();
result.topic_ids.forEach(function(t) {
tracker.removeTopic(t);
});
tracker.incrementMessageCount();
}
self.send('refresh');
});
} }
}, },
topicTrackingState: function() { topicTrackingState: function() {
return Discourse.TopicTrackingState.current(); return Discourse.TopicTrackingState.current();
}.property(), }.property(),
@ -132,7 +96,6 @@ var controllerOpts = {
this.get('topics.length') >= 30; this.get('topics.length') >= 30;
}.property('filter', 'topics.length'), }.property('filter', 'topics.length'),
canBulkSelect: Em.computed.alias('currentUser.staff'),
hasTopics: Em.computed.gt('topics.length', 0), hasTopics: Em.computed.gt('topics.length', 0),
allLoaded: Em.computed.empty('more_topics_url'), allLoaded: Em.computed.empty('more_topics_url'),
latest: Discourse.computed.endWith('filter', 'latest'), latest: Discourse.computed.endWith('filter', 'latest'),
@ -187,4 +150,4 @@ Ember.keys(queryParams).forEach(function(p) {
} }
}); });
export default DiscoveryController.extend(controllerOpts); export default DiscoveryController.extend(controllerOpts, BulkTopicSelection);

View File

@ -174,12 +174,12 @@ export default ObjectController.extend(ModalFunctionality, {
self.flash(I18n.t('generic_error')); self.flash(I18n.t('generic_error'));
} }
self.send('showModal'); self.send('reopenModal');
self.displayErrors([I18n.t("category.delete_error")]); self.displayErrors([I18n.t("category.delete_error")]);
self.set('deleting', false); self.set('deleting', false);
}); });
} else { } else {
self.send('showModal'); self.send('reopenModal');
self.set('deleting', false); self.set('deleting', false);
} }
}); });

View File

@ -43,10 +43,10 @@ export default ObjectController.extend(ModalFunctionality, {
self.set('details.auto_close_at', result.auto_close_at); self.set('details.auto_close_at', result.auto_close_at);
self.set('details.auto_close_hours', result.auto_close_hours); self.set('details.auto_close_hours', result.auto_close_hours);
} else { } else {
bootbox.alert(I18n.t('composer.auto_close.error'), function() { self.send('showModal'); } ); bootbox.alert(I18n.t('composer.auto_close.error'), function() { self.send('reopenModal'); } );
} }
}, function () { }, function () {
bootbox.alert(I18n.t('composer.auto_close.error'), function() { self.send('showModal'); } ); bootbox.alert(I18n.t('composer.auto_close.error'), function() { self.send('reopenModal'); } );
}); });
} }

View File

@ -1,11 +1,8 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality'; import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseController from 'discourse/controllers/controller'; import DiscourseController from 'discourse/controllers/controller';
import showModal from 'discourse/lib/show-modal';
// This is happening outside of the app via popup // This is happening outside of the app via popup
function showModal(modal) {
const route = Discourse.__container__.lookup('route:application');
Discourse.Route.showModal(route, modal);
}
const AuthErrors = const AuthErrors =
['requires_invite', 'awaiting_approval', 'awaiting_confirmation', 'admin_not_allowed_from_ip_address', ['requires_invite', 'awaiting_approval', 'awaiting_confirmation', 'admin_not_allowed_from_ip_address',
'not_allowed_from_ip_address']; 'not_allowed_from_ip_address'];

View File

@ -16,7 +16,6 @@ addBulkButton('resetRead', 'reset_read');
// Modal for performing bulk actions on topics // Modal for performing bulk actions on topics
export default Ember.ArrayController.extend(ModalFunctionality, { export default Ember.ArrayController.extend(ModalFunctionality, {
needs: ['discovery/topics'],
buttonRows: null, buttonRows: null,
onShow: function() { onShow: function() {
@ -34,6 +33,7 @@ export default Ember.ArrayController.extend(ModalFunctionality, {
if (row.length) { buttonRows.push(row); } if (row.length) { buttonRows.push(row); }
this.set('buttonRows', buttonRows); this.set('buttonRows', buttonRows);
this.send('changeBulkTemplate', 'modal/bulk_actions_buttons');
}, },
perform: function(operation) { perform: function(operation) {
@ -66,9 +66,10 @@ export default Ember.ArrayController.extend(ModalFunctionality, {
}, },
performAndRefresh: function(operation) { performAndRefresh: function(operation) {
var self = this; const self = this;
return this.perform(operation).then(function() { return this.perform(operation).then(function() {
self.get('controllers.discovery/topics').send('refresh'); const refreshTarget = self.get('refreshTarget');
if (refreshTarget) { refreshTarget.send('refresh'); }
self.send('closeModal'); self.send('closeModal');
}); });
}, },
@ -107,7 +108,8 @@ export default Ember.ArrayController.extend(ModalFunctionality, {
topics.forEach(function(t) { topics.forEach(function(t) {
t.set('category', category); t.set('category', category);
}); });
self.get('controllers.discovery/topics').send('refresh'); const refreshTarget = self.get('refreshTarget');
if (refreshTarget) { refreshTarget.send('refresh'); }
self.send('closeModal'); self.send('closeModal');
}); });
}, },

View File

@ -0,0 +1,20 @@
export default function showModal(name, model) {
// 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');
route.controllerFor('modal').set('modalClass', null);
route.render(name, {into: 'modal', outlet: 'modalBody'});
const controller = route.controllerFor(name);
if (controller) {
if (model) {
controller.set('model', model);
}
if (controller.onShow) {
controller.onShow();
}
controller.set('flashMessage', null);
}
return controller;
}

View File

@ -0,0 +1,49 @@
import NotificationLevels from 'discourse/lib/notification-levels';
export default Ember.Mixin.create({
bulkSelectEnabled: false,
selected: null,
canBulkSelect: Em.computed.alias('currentUser.staff'),
resetSelected: function() {
this.set('selected', []);
}.on('init'),
actions: {
toggleBulkSelect() {
this.toggleProperty('bulkSelectEnabled');
this.get('selected').clear();
},
dismissRead(operationType) {
const self = this,
selected = this.get('selected');
let operation;
if(operationType === "posts"){
operation = { type: 'dismiss_posts' };
} else {
operation = { type: 'change_notification_level',
notification_level_id: NotificationLevels.REGULAR };
}
let promise;
if (selected.length > 0) {
promise = Discourse.Topic.bulkOperation(selected, operation);
} else {
promise = Discourse.Topic.bulkOperationByFilter('unread', operation, this.get('category.id'));
}
promise.then(function(result) {
if (result && result.topic_ids) {
const tracker = Discourse.TopicTrackingState.current();
result.topic_ids.forEach(function(t) {
tracker.removeTopic(t);
});
tracker.incrementMessageCount();
}
self.send('refresh');
});
}
}
});

View File

@ -1,3 +1,5 @@
import showModal from 'discourse/lib/show-modal';
function unlessReadOnly(method) { function unlessReadOnly(method) {
return function() { return function() {
if (this.site.get("isReadOnly")) { if (this.site.get("isReadOnly")) {
@ -74,31 +76,28 @@ const ApplicationRoute = Discourse.Route.extend({
showCreateAccount: unlessReadOnly('handleShowCreateAccount'), showCreateAccount: unlessReadOnly('handleShowCreateAccount'),
showForgotPassword() { showForgotPassword() {
Discourse.Route.showModal(this, 'forgotPassword'); showModal('forgotPassword');
}, },
showNotActivated(props) { showNotActivated(props) {
Discourse.Route.showModal(this, 'notActivated'); showModal('notActivated');
this.controllerFor('notActivated').setProperties(props); this.controllerFor('notActivated').setProperties(props);
}, },
showUploadSelector(composerView) { showUploadSelector(composerView) {
Discourse.Route.showModal(this, 'uploadSelector'); showModal('uploadSelector');
this.controllerFor('upload-selector').setProperties({ composerView: composerView }); this.controllerFor('upload-selector').setProperties({ composerView: composerView });
}, },
showKeyboardShortcutsHelp() { showKeyboardShortcutsHelp() {
Discourse.Route.showModal(this, 'keyboardShortcutsHelp'); showModal('keyboardShortcutsHelp');
}, },
showSearchHelp() { showSearchHelp() {
const self = this;
// TODO: @EvitTrout how do we get a loading indicator here? // TODO: @EvitTrout how do we get a loading indicator here?
Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then(function(html){ Discourse.ajax("/static/search_help.html", { dataType: 'html' }).then(function(html){
Discourse.Route.showModal(self, 'searchHelp', html); showModal('searchHelp', html);
}); });
}, },
// Close the current modal, and destroy its state. // Close the current modal, and destroy its state.
@ -109,13 +108,13 @@ const ApplicationRoute = Discourse.Route.extend({
/** /**
Hide the modal, but keep it with all its state so that it can be shown again later. Hide the modal, but keep it with all its state so that it can be shown again later.
This is useful if you want to prompt for confirmation. hideModal, ask "Are you sure?", This is useful if you want to prompt for confirmation. hideModal, ask "Are you sure?",
user clicks "No", showModal. If user clicks "Yes", be sure to call closeModal. user clicks "No", reopenModal. If user clicks "Yes", be sure to call closeModal.
**/ **/
hideModal() { hideModal() {
$('#discourse-modal').modal('hide'); $('#discourse-modal').modal('hide');
}, },
showModal() { reopenModal() {
$('#discourse-modal').modal('show'); $('#discourse-modal').modal('show');
}, },
@ -123,7 +122,7 @@ const ApplicationRoute = Discourse.Route.extend({
const self = this; const self = this;
Discourse.Category.reloadById(category.get('id')).then(function (c) { Discourse.Category.reloadById(category.get('id')).then(function (c) {
self.site.updateCategory(c); self.site.updateCategory(c);
Discourse.Route.showModal(self, 'editCategory', c); showModal(self, 'editCategory', c);
self.controllerFor('editCategory').set('selectedTab', 'general'); self.controllerFor('editCategory').set('selectedTab', 'general');
}); });
}, },
@ -135,6 +134,13 @@ const ApplicationRoute = Discourse.Route.extend({
checkEmail: function (user) { checkEmail: function (user) {
user.checkEmail(); user.checkEmail();
},
changeBulkTemplate(w) {
const controllerName = w.replace('modal/', ''),
factory = this.container.lookupFactory('controller:' + controllerName);
this.render(w, {into: 'topicBulkActions', outlet: 'bulkOutlet', controller: factory ? controllerName : 'topic-bulk-actions'});
} }
}, },
@ -164,7 +170,7 @@ const ApplicationRoute = Discourse.Route.extend({
if (!this.siteSettings.enable_local_logins && methods.length === 1) { if (!this.siteSettings.enable_local_logins && methods.length === 1) {
this.controllerFor('login').send('externalLogin', methods[0]); this.controllerFor('login').send('externalLogin', methods[0]);
} else { } else {
Discourse.Route.showModal(this, modal); showModal(modal);
if (notAuto) { notAuto(); } if (notAuto) { notAuto(); }
} }
}, },

View File

@ -1,24 +1,45 @@
/** import showModal from 'discourse/lib/show-modal';
The base route for all routes on Discourse. Includes global enter functionality.
@class Route const DiscourseRoute = Ember.Route.extend({
@extends Em.Route
@namespace Discourse
@module Discourse
**/
Discourse.Route = Ember.Route.extend({
/** /**
NOT called every time we enter a route on Discourse. NOT called every time we enter a route on Discourse.
Only called the FIRST time we enter a route. Only called the FIRST time we enter a route.
So, when going from one topic to another, activate will only be called on the So, when going from one topic to another, activate will only be called on the
TopicRoute for the first topic. TopicRoute for the first topic.
@method activate
**/ **/
activate: function() { activate: function() {
this._super(); this._super();
Em.run.scheduleOnce('afterRender', Discourse.Route, 'cleanDOM'); Em.run.scheduleOnce('afterRender', Ember.Route, this._cleanDOM);
},
_cleanDOM() {
// Close mini profiler
$('.profiler-results .profiler-result').remove();
// Close some elements that may be open
$('.d-dropdown').hide();
$('header ul.icons li').removeClass('active');
$('[data-toggle="dropdown"]').parent().removeClass('open');
// close the lightbox
if ($.magnificPopup && $.magnificPopup.instance) {
$.magnificPopup.instance.close();
$('body').removeClass('mfp-zoom-out-cur');
}
// Remove any link focus
// NOTE: the '.not("body")' is here to prevent a bug in IE10 on Win7
// cf. https://stackoverflow.com/questions/5657371/ie9-window-loses-focus-due-to-jquery-mobile
$(document.activeElement).not("body").blur();
Discourse.set('notifyCount',0);
$('#discourse-modal').modal('hide');
var hideDropDownFunction = $('html').data('hide-dropdown');
if (hideDropDownFunction) { hideDropDownFunction(); }
// TODO: Avoid container lookup here
var appEvents = Discourse.__container__.lookup('app-events:main');
appEvents.trigger('dom:clean');
}, },
_refreshTitleOnce: function() { _refreshTitleOnce: function() {
@ -79,7 +100,7 @@ Discourse.Route = Ember.Route.extend({
var routeBuilder; var routeBuilder;
Discourse.Route.reopenClass({ DiscourseRoute.reopenClass({
buildRoutes: function(builder) { buildRoutes: function(builder) {
var oldBuilder = routeBuilder; var oldBuilder = routeBuilder;
@ -174,48 +195,11 @@ Discourse.Route.reopenClass({
}); });
}, },
cleanDOM: function() {
// Close mini profiler
$('.profiler-results .profiler-result').remove();
// Close some elements that may be open
$('.d-dropdown').hide();
$('header ul.icons li').removeClass('active');
$('[data-toggle="dropdown"]').parent().removeClass('open');
// close the lightbox
if ($.magnificPopup && $.magnificPopup.instance) {
$.magnificPopup.instance.close();
$('body').removeClass('mfp-zoom-out-cur');
}
// Remove any link focus
// NOTE: the '.not("body")' is here to prevent a bug in IE10 on Win7
// cf. https://stackoverflow.com/questions/5657371/ie9-window-loses-focus-due-to-jquery-mobile
$(document.activeElement).not("body").blur();
Discourse.set('notifyCount',0);
$('#discourse-modal').modal('hide');
var hideDropDownFunction = $('html').data('hide-dropdown');
if (hideDropDownFunction) { hideDropDownFunction(); }
// TODO: Avoid container lookup here
var appEvents = Discourse.__container__.lookup('app-events:main');
appEvents.trigger('dom:clean');
},
showModal: function(route, name, model) { showModal: function(route, name, model) {
route.controllerFor('modal').set('modalClass', null); Ember.warn('DEPRECATED `Discourse.Route.showModal` - use `showModal` instead');
route.render(name, {into: 'modal', outlet: 'modalBody'}); showModal(name, model);
var controller = route.controllerFor(name);
if (controller) {
if (model) {
controller.set('model', model);
}
if(controller && controller.onShow) {
controller.onShow();
}
controller.set('flashMessage', null);
}
} }
}); });
export default DiscourseRoute;

View File

@ -1,4 +1,5 @@
import ShowFooter from "discourse/mixins/show-footer"; import ShowFooter from 'discourse/mixins/show-footer';
import showModal from 'discourse/lib/show-modal';
Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(Discourse.OpenComposer, ShowFooter, { Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(Discourse.OpenComposer, ShowFooter, {
renderTemplate() { renderTemplate() {
@ -45,7 +46,7 @@ Discourse.DiscoveryCategoriesRoute = Discourse.Route.extend(Discourse.OpenCompos
const groups = this.site.groups, const groups = this.site.groups,
everyoneName = groups.findBy('id', 0).name; everyoneName = groups.findBy('id', 0).name;
Discourse.Route.showModal(this, 'editCategory', Discourse.Category.create({ showModal('editCategory', Discourse.Category.create({
color: 'AB9364', text_color: 'FFFFFF', group_permissions: [{group_name: everyoneName, permission_type: 1}], color: 'AB9364', text_color: 'FFFFFF', group_permissions: [{group_name: everyoneName, permission_type: 1}],
available_groups: groups.map(g => g.name), available_groups: groups.map(g => g.name),
allow_badges: true allow_badges: true

View File

@ -5,7 +5,7 @@
import ShowFooter from "discourse/mixins/show-footer"; import ShowFooter from "discourse/mixins/show-footer";
Discourse.DiscoveryRoute = Discourse.Route.extend(Discourse.ScrollTop, Discourse.OpenComposer, ShowFooter, { const DiscoveryRoute = Discourse.Route.extend(Discourse.ScrollTop, Discourse.OpenComposer, ShowFooter, {
redirect: function() { return this.redirectIfLoginRequired(); }, redirect: function() { return this.redirectIfLoginRequired(); },
beforeModel: function(transition) { beforeModel: function(transition) {
@ -42,22 +42,9 @@ Discourse.DiscoveryRoute = Discourse.Route.extend(Discourse.ScrollTop, Discourse
createTopic: function() { createTopic: function() {
this.openComposer(this.controllerFor('discovery/topics')); this.openComposer(this.controllerFor('discovery/topics'));
},
changeBulkTemplate: function(w) {
var controllerName = w.replace('modal/', ''),
factory = this.container.lookupFactory('controller:' + controllerName);
this.render(w, {into: 'topicBulkActions', outlet: 'bulkOutlet', controller: factory ? controllerName : 'topic-bulk-actions'});
},
showBulkActions: function() {
var selected = this.controllerFor('discovery/topics').get('selected');
Discourse.Route.showModal(this, 'topicBulkActions', selected);
this.send('changeBulkTemplate', 'modal/bulk_actions_buttons');
} }
} }
}); });
export default Discourse.DiscoveryRoute; export default DiscoveryRoute;

View File

@ -1,23 +1,24 @@
import ShowFooter from "discourse/mixins/show-footer"; import ShowFooter from "discourse/mixins/show-footer";
import RestrictedUserRoute from "discourse/routes/restricted-user"; import RestrictedUserRoute from "discourse/routes/restricted-user";
import showModal from 'discourse/lib/show-modal';
export default RestrictedUserRoute.extend(ShowFooter, { export default RestrictedUserRoute.extend(ShowFooter, {
model: function() { model() {
return this.modelFor('user'); return this.modelFor('user');
}, },
setupController: function(controller, user) { setupController(controller, user) {
controller.setProperties({ model: user, newNameInput: user.get('name') }); controller.setProperties({ model: user, newNameInput: user.get('name') });
}, },
actions: { actions: {
showAvatarSelector: function() { showAvatarSelector() {
Discourse.Route.showModal(this, 'avatar-selector'); showModal('avatar-selector');
// all the properties needed for displaying the avatar selector modal // all the properties needed for displaying the avatar selector modal
var controller = this.controllerFor('avatar-selector'); const controller = this.controllerFor('avatar-selector');
var user = this.modelFor('user'); const user = this.modelFor('user');
var props = user.getProperties( const props = user.getProperties(
'username', 'email', 'username', 'email',
'uploaded_avatar_id', 'uploaded_avatar_id',
'system_avatar_upload_id', 'system_avatar_upload_id',
@ -40,8 +41,8 @@ export default RestrictedUserRoute.extend(ShowFooter, {
}, },
saveAvatarSelection: function() { saveAvatarSelection: function() {
var user = this.modelFor('user'); const user = this.modelFor('user');
var avatarSelector = this.controllerFor('avatar-selector'); const avatarSelector = this.controllerFor('avatar-selector');
// sends the information to the server if it has changed // sends the information to the server if it has changed
if (avatarSelector.get('selectedUploadId') !== user.get('uploaded_avatar_id')) { if (avatarSelector.get('selectedUploadId') !== user.get('uploaded_avatar_id')) {

View File

@ -1,12 +1,14 @@
var isTransitioning = false, let isTransitioning = false,
scheduledReplace = null, scheduledReplace = null,
lastScrollPos = null, lastScrollPos = null;
SCROLL_DELAY = 500;
const SCROLL_DELAY = 500;
import ShowFooter from "discourse/mixins/show-footer"; import ShowFooter from "discourse/mixins/show-footer";
import Topic from 'discourse/models/topic'; import Topic from 'discourse/models/topic';
import showModal from 'discourse/lib/show-modal';
var TopicRoute = Discourse.Route.extend(ShowFooter, { const TopicRoute = Discourse.Route.extend(ShowFooter, {
redirect() { return this.redirectIfLoginRequired(); }, redirect() { return this.redirectIfLoginRequired(); },
queryParams: { queryParams: {
@ -16,16 +18,16 @@ var TopicRoute = Discourse.Route.extend(ShowFooter, {
}, },
titleToken() { titleToken() {
var model = this.modelFor('topic'); const model = this.modelFor('topic');
if (model) { if (model) {
var result = model.get('title'), const result = model.get('title'),
cat = model.get('category'); cat = model.get('category');
// Only display uncategorized in the title tag if it was renamed // Only display uncategorized in the title tag if it was renamed
if (cat && !(cat.get('isUncategorizedCategory') && cat.get('name').toLowerCase() === "uncategorized")) { if (cat && !(cat.get('isUncategorizedCategory') && cat.get('name').toLowerCase() === "uncategorized")) {
var catName = cat.get('name'), let catName = cat.get('name');
parentCategory = cat.get('parentCategory');
const parentCategory = cat.get('parentCategory');
if (parentCategory) { if (parentCategory) {
catName = parentCategory.get('name') + " / " + catName; catName = parentCategory.get('name') + " / " + catName;
} }
@ -43,27 +45,27 @@ var TopicRoute = Discourse.Route.extend(ShowFooter, {
}, },
showFlags(post) { showFlags(post) {
Discourse.Route.showModal(this, 'flag', post); showModal('flag', post);
this.controllerFor('flag').setProperties({ selected: null }); this.controllerFor('flag').setProperties({ selected: null });
}, },
showFlagTopic(topic) { showFlagTopic(topic) {
Discourse.Route.showModal(this, 'flag', topic); showModal('flag', topic);
this.controllerFor('flag').setProperties({ selected: null, flagTopic: true }); this.controllerFor('flag').setProperties({ selected: null, flagTopic: true });
}, },
showAutoClose() { showAutoClose() {
Discourse.Route.showModal(this, 'editTopicAutoClose', this.modelFor('topic')); showModal('editTopicAutoClose', this.modelFor('topic'));
this.controllerFor('modal').set('modalClass', 'edit-auto-close-modal'); this.controllerFor('modal').set('modalClass', 'edit-auto-close-modal');
}, },
showInvite() { showInvite() {
Discourse.Route.showModal(this, 'invite', this.modelFor('topic')); showModal('invite', this.modelFor('topic'));
this.controllerFor('invite').reset(); this.controllerFor('invite').reset();
}, },
showPrivateInvite() { showPrivateInvite() {
Discourse.Route.showModal(this, 'invitePrivate', this.modelFor('topic')); showModal('invitePrivate', this.modelFor('topic'));
this.controllerFor('invitePrivate').setProperties({ this.controllerFor('invitePrivate').setProperties({
email: null, email: null,
error: false, error: false,
@ -73,26 +75,26 @@ var TopicRoute = Discourse.Route.extend(ShowFooter, {
}, },
showHistory(post) { showHistory(post) {
Discourse.Route.showModal(this, 'history', post); showModal('history', post);
this.controllerFor('history').refresh(post.get("id"), "latest"); this.controllerFor('history').refresh(post.get("id"), "latest");
this.controllerFor('modal').set('modalClass', 'history-modal'); this.controllerFor('modal').set('modalClass', 'history-modal');
}, },
showRawEmail(post) { showRawEmail(post) {
Discourse.Route.showModal(this, 'raw-email', post); showModal('raw-email', post);
this.controllerFor('raw_email').loadRawEmail(post.get("id")); this.controllerFor('raw_email').loadRawEmail(post.get("id"));
}, },
mergeTopic() { mergeTopic() {
Discourse.Route.showModal(this, 'mergeTopic', this.modelFor('topic')); showModal('mergeTopic', this.modelFor('topic'));
}, },
splitTopic() { splitTopic() {
Discourse.Route.showModal(this, 'split-topic', this.modelFor('topic')); showModal('split-topic', this.modelFor('topic'));
}, },
changeOwner() { changeOwner() {
Discourse.Route.showModal(this, 'changeOwner', this.modelFor('topic')); showModal('changeOwner', this.modelFor('topic'));
}, },
// Use replaceState to update the URL once it changes // Use replaceState to update the URL once it changes
@ -100,9 +102,9 @@ var TopicRoute = Discourse.Route.extend(ShowFooter, {
// do nothing if we are transitioning to another route // do nothing if we are transitioning to another route
if (isTransitioning || Discourse.TopicRoute.disableReplaceState) { return; } if (isTransitioning || Discourse.TopicRoute.disableReplaceState) { return; }
var topic = this.modelFor('topic'); const topic = this.modelFor('topic');
if (topic && currentPost) { if (topic && currentPost) {
var postUrl = topic.get('url'); let postUrl = topic.get('url');
if (currentPost > 1) { postUrl += "/" + currentPost; } if (currentPost > 1) { postUrl += "/" + currentPost; }
Em.run.cancel(scheduledReplace); Em.run.cancel(scheduledReplace);
@ -128,7 +130,7 @@ var TopicRoute = Discourse.Route.extend(ShowFooter, {
// replaceState can be very slow on Android Chrome. This function debounces replaceState // replaceState can be very slow on Android Chrome. This function debounces replaceState
// within a topic until scrolling stops // within a topic until scrolling stops
_replaceUnlessScrolling(url) { _replaceUnlessScrolling(url) {
var currentPos = parseInt($(document).scrollTop(), 10); const currentPos = parseInt($(document).scrollTop(), 10);
if (currentPos === lastScrollPos) { if (currentPos === lastScrollPos) {
Discourse.URL.replaceState(url); Discourse.URL.replaceState(url);
return; return;
@ -138,11 +140,11 @@ var TopicRoute = Discourse.Route.extend(ShowFooter, {
}, },
setupParams(topic, params) { setupParams(topic, params) {
var postStream = topic.get('postStream'); const postStream = topic.get('postStream');
postStream.set('summary', Em.get(params, 'filter') === 'summary'); postStream.set('summary', Em.get(params, 'filter') === 'summary');
postStream.set('show_deleted', !!Em.get(params, 'show_deleted')); postStream.set('show_deleted', !!Em.get(params, 'show_deleted'));
var usernames = Em.get(params, 'username_filters'), const usernames = Em.get(params, 'username_filters'),
userFilters = postStream.get('userFilters'); userFilters = postStream.get('userFilters');
userFilters.clear(); userFilters.clear();
@ -154,9 +156,9 @@ var TopicRoute = Discourse.Route.extend(ShowFooter, {
}, },
model(params, transition) { model(params, transition) {
var queryParams = transition.queryParams; const queryParams = transition.queryParams;
var topic = this.modelFor('topic'); const topic = this.modelFor('topic');
if (topic && (topic.get('id') === parseInt(params.id, 10))) { if (topic && (topic.get('id') === parseInt(params.id, 10))) {
this.setupParams(topic, queryParams); this.setupParams(topic, queryParams);
// If we have the existing model, refresh it // If we have the existing model, refresh it
@ -172,7 +174,7 @@ var TopicRoute = Discourse.Route.extend(ShowFooter, {
this._super(); this._super();
isTransitioning = false; isTransitioning = false;
var topic = this.modelFor('topic'); const topic = this.modelFor('topic');
this.session.set('lastTopicIdViewed', parseInt(topic.get('id'), 10)); this.session.set('lastTopicIdViewed', parseInt(topic.get('id'), 10));
this.controllerFor('search').set('searchContext', topic.get('searchContext')); this.controllerFor('search').set('searchContext', topic.get('searchContext'));
}, },
@ -184,7 +186,7 @@ var TopicRoute = Discourse.Route.extend(ShowFooter, {
this.controllerFor('search').set('searchContext', null); this.controllerFor('search').set('searchContext', null);
this.controllerFor('user-card').set('visible', false); this.controllerFor('user-card').set('visible', false);
var topicController = this.controllerFor('topic'), const topicController = this.controllerFor('topic'),
postStream = topicController.get('postStream'); postStream = topicController.get('postStream');
postStream.cancelFilter(); postStream.cancelFilter();
@ -193,8 +195,8 @@ var TopicRoute = Discourse.Route.extend(ShowFooter, {
this.controllerFor('composer').set('topic', null); this.controllerFor('composer').set('topic', null);
Discourse.ScreenTrack.current().stop(); Discourse.ScreenTrack.current().stop();
var headerController; const headerController = this.controllerFor('header');
if (headerController = this.controllerFor('header')) { if (headerController) {
headerController.set('topic', null); headerController.set('topic', null);
headerController.set('showExtraInfo', false); headerController.set('showExtraInfo', false);
} }

View File

@ -1,15 +1,16 @@
import ShowFooter from "discourse/mixins/show-footer"; import ShowFooter from 'discourse/mixins/show-footer';
import showModal from 'discourse/lib/show-modal';
export default Discourse.Route.extend(ShowFooter, { export default Discourse.Route.extend(ShowFooter, {
renderTemplate: function() { renderTemplate() {
this.render({ into: 'user' }); this.render({ into: 'user' });
}, },
model: function() { model() {
return Discourse.Invite.findInvitedBy(this.modelFor('user')); return Discourse.Invite.findInvitedBy(this.modelFor('user'));
}, },
setupController: function(controller, model) { setupController(controller, model) {
controller.setProperties({ controller.setProperties({
model: model, model: model,
user: this.controllerFor('user').get('model'), user: this.controllerFor('user').get('model'),
@ -19,16 +20,16 @@ export default Discourse.Route.extend(ShowFooter, {
}, },
actions: { actions: {
showInvite: function() { showInvite() {
Discourse.Route.showModal(this, 'invite', Discourse.User.current()); showModal('invite', Discourse.User.current());
this.controllerFor('invite').reset(); this.controllerFor('invite').reset();
}, },
uploadSuccess: function(filename) { uploadSuccess(filename) {
bootbox.alert(I18n.t("user.invited.bulk_invite.success", { filename: filename })); bootbox.alert(I18n.t("user.invited.bulk_invite.success", { filename: filename }));
}, },
uploadError: function(filename, message) { uploadError(filename, message) {
bootbox.alert(I18n.t("user.invited.bulk_invite.error", { filename: filename, message: message })); bootbox.alert(I18n.t("user.invited.bulk_invite.error", { filename: filename, message: message }));
} }
} }

View File

@ -0,0 +1,5 @@
{{#if selected}}
<div id='bulk-select'>
{{d-button action="showBulkActions" icon="wrench" class="no-text"}}
</div>
{{/if}}

View File

@ -14,11 +14,7 @@
</div> </div>
{{/if}} {{/if}}
{{#if selected}} {{bulk-select-button selected=selected refreshTarget=controller}}
<div id='bulk-select'>
{{d-button action="showBulkActions" icon="wrench" class="no-text"}}
</div>
{{/if}}
<div class='contents'> <div class='contents'>
{{#if top}} {{#if top}}

View File

@ -49,7 +49,8 @@
//= require ./discourse/components/notifications-button //= require ./discourse/components/notifications-button
//= require ./discourse/components/topic-notifications-button //= require ./discourse/components/topic-notifications-button
//= require ./discourse/views/composer //= require ./discourse/views/composer
//= require ./discourse/routes/discourse_route //= require ./discourse/lib/show-modal
//= require ./discourse/routes/discourse
//= require ./discourse/routes/build-topic-route //= require ./discourse/routes/build-topic-route
//= require ./discourse/routes/restricted-user //= require ./discourse/routes/restricted-user
//= require ./discourse/routes/user-topic-list //= require ./discourse/routes/user-topic-list

View File

@ -106,6 +106,7 @@ module Tilt
# HAX # HAX
result = "Controller" if result == "ControllerController" result = "Controller" if result == "ControllerController"
result = "Route" if result == "DiscourseRoute"
result.gsub!(/Mixin$/, '') result.gsub!(/Mixin$/, '')
result.gsub!(/Model$/, '') result.gsub!(/Model$/, '')