mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 15:48:43 -05:00
FEATURE: unified UI for pinning/banner topics
REFACTOR: ES6ified all the modals
This commit is contained in:
parent
7c1540e5ab
commit
424a3b042a
64 changed files with 410 additions and 230 deletions
|
@ -1,5 +1,4 @@
|
|||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
|
||||
import ObjectController from 'discourse/controllers/object';
|
||||
|
||||
export default ObjectController.extend(ModalFunctionality, {
|
|
@ -1,7 +1,7 @@
|
|||
import ChangeSiteCustomizationDetailsController from "admin/controllers/change-site-customization-details";
|
||||
import ChangeSiteCustomizationDetailsController from "admin/controllers/modals/change-site-customization-details";
|
||||
|
||||
export default ChangeSiteCustomizationDetailsController.extend({
|
||||
onShow: function() {
|
||||
this.selectPrevious();
|
||||
this.send("selectPrevious");
|
||||
}
|
||||
});
|
|
@ -25,7 +25,7 @@ export default Ember.Route.extend({
|
|||
|
||||
editGroupings() {
|
||||
const groupings = this.controllerFor('admin-badges').get('badgeGroupings');
|
||||
showModal('admin_edit_badge_groupings', groupings);
|
||||
showModal('modals/admin-edit-badge-groupings', groupings);
|
||||
},
|
||||
|
||||
preview(badge, explain) {
|
||||
|
@ -40,7 +40,7 @@ export default Ember.Route.extend({
|
|||
}
|
||||
}).then(function(json) {
|
||||
badge.set('preview_loading', false);
|
||||
showModal('admin_badge_preview', json);
|
||||
showModal('modals/admin-badge-preview', json);
|
||||
}).catch(function(error) {
|
||||
badge.set('preview_loading', false);
|
||||
Em.Logger.error(error);
|
||||
|
|
|
@ -13,12 +13,12 @@ export default Discourse.Route.extend({
|
|||
|
||||
actions: {
|
||||
showAgreeFlagModal(flaggedPost) {
|
||||
showModal('admin_agree_flag', flaggedPost);
|
||||
showModal('modals/admin-agree-flag', flaggedPost);
|
||||
this.controllerFor('modal').set('modalClass', 'agree-flag-modal');
|
||||
},
|
||||
|
||||
showDeleteFlagModal(flaggedPost) {
|
||||
showModal('admin_delete_flag', flaggedPost);
|
||||
showModal('modals/admin-delete-flag', flaggedPost);
|
||||
this.controllerFor('modal').set('modalClass', 'delete-flag-modal');
|
||||
}
|
||||
|
||||
|
|
|
@ -13,12 +13,13 @@ export default Discourse.Route.extend({
|
|||
|
||||
actions: {
|
||||
showDetailsModal(logRecord) {
|
||||
showModal('admin_staff_action_log_details', logRecord);
|
||||
showModal('modals/admin-staff-action-log-details', logRecord);
|
||||
this.controllerFor('modal').set('modalClass', 'log-details-modal');
|
||||
},
|
||||
|
||||
showCustomDetailsModal(logRecord) {
|
||||
showModal(logRecord.action_name + '_details', logRecord);
|
||||
const modalName = "modals/" + (logRecord.action_name + '_details').replace("_", "-");
|
||||
showModal(modalName, logRecord);
|
||||
this.controllerFor('modal').set('modalClass', 'tabbed-modal log-details-modal');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ export default Discourse.Route.extend({
|
|||
|
||||
actions: {
|
||||
showSuspendModal(user) {
|
||||
showModal('admin_suspend_user', user);
|
||||
showModal('modals/admin-suspend-user', user);
|
||||
this.controllerFor('modal').set('modalClass', 'suspend-user-modal');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
Discourse.AdminAgreeFlagView = Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'admin/templates/modal/admin_agree_flag',
|
||||
title: I18n.t('admin.flags.agree_flag_modal_title')
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
Discourse.AdminBadgePreviewView = Discourse.ModalBodyView.extend({
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'admin/templates/modal/admin_badge_preview',
|
||||
title: I18n.t('admin.badges.preview.modal_title')
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'admin/templates/modal/admin_delete_flag',
|
||||
title: I18n.t('admin.flags.delete_flag_modal_title')
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
Discourse.AdminEditBadgeGroupingsView = Discourse.ModalBodyView.extend({
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'admin/templates/modal/admin_edit_badge_groupings',
|
||||
title: I18n.t('admin.badges.badge_groupings.modal_title')
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'admin/templates/logs/details_modal',
|
||||
title: I18n.t('admin.logs.staff_actions.modal_title')
|
||||
});
|
|
@ -1,4 +1,6 @@
|
|||
Discourse.AdminStartBackupView = Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'admin/templates/modal/admin_start_backup',
|
||||
title: I18n.t('admin.backups.operations.backup.confirm')
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'admin/templates/modal/admin_suspend_user',
|
||||
title: I18n.t('admin.user.suspend_modal_title')
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
/**
|
||||
A modal view for deleting a flag.
|
||||
|
||||
@class AdminDeleteFlagView
|
||||
@extends Discourse.ModalBodyView
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.AdminDeleteFlagView = Discourse.ModalBodyView.extend({
|
||||
templateName: 'admin/templates/modal/admin_delete_flag',
|
||||
title: I18n.t('admin.flags.delete_flag_modal_title')
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
/**
|
||||
A modal view for details of a staff action log record in a modal.
|
||||
|
||||
@class AdminStaffActionLogDetailsView
|
||||
@extends Discourse.ModalBodyView
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.AdminStaffActionLogDetailsView = Discourse.ModalBodyView.extend({
|
||||
templateName: 'admin/templates/logs/details_modal',
|
||||
title: I18n.t('admin.logs.staff_actions.modal_title')
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
/**
|
||||
A modal view for suspending a user.
|
||||
|
||||
@class AdminSuspendUserView
|
||||
@extends Discourse.ModalBodyView
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.AdminSuspendUserView = Discourse.ModalBodyView.extend({
|
||||
templateName: 'admin/templates/modal/admin_suspend_user',
|
||||
title: I18n.t('admin.user.suspend_modal_title')
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'admin/templates/logs/site_customization_change_modal',
|
||||
title: I18n.t('admin.logs.staff_actions.modal_title')
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
/**
|
||||
A modal view for details of a staff action log record in a modal
|
||||
for when a site customization is created or changed.
|
||||
|
||||
@class ChangeSiteCustomizationDetailsView
|
||||
@extends Discourse.ModalBodyView
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.ChangeSiteCustomizationDetailsView = Discourse.ModalBodyView.extend({
|
||||
templateName: 'admin/templates/logs/site_customization_change_modal',
|
||||
title: I18n.t('admin.logs.staff_actions.modal_title')
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'admin/templates/logs/site_customization_change_modal',
|
||||
title: I18n.t('admin.logs.staff_actions.modal_title')
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
/**
|
||||
A modal view for details of a staff action log record in a modal
|
||||
for when a site customization is deleted.
|
||||
|
||||
@class DeleteSiteCustomizationDetailsView
|
||||
@extends Discourse.ModalBodyView
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.DeleteSiteCustomizationDetailsView = Discourse.ModalBodyView.extend({
|
||||
templateName: 'admin/templates/logs/site_customization_change_modal',
|
||||
title: I18n.t('admin.logs.staff_actions.modal_title')
|
||||
});
|
|
@ -0,0 +1,77 @@
|
|||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import ObjectController from 'discourse/controllers/object';
|
||||
import { categoryLinkHTML } from 'discourse/helpers/category-link';
|
||||
|
||||
export default ObjectController.extend(ModalFunctionality, {
|
||||
needs: ["topic"],
|
||||
|
||||
loading: true,
|
||||
pinnedInCategoryCount: 0,
|
||||
pinnedGloballyCount: 0,
|
||||
bannerCount: 0,
|
||||
|
||||
categoryLink: function() {
|
||||
return categoryLinkHTML(this.get("category"), { allowUncategorized: true });
|
||||
}.property("category"),
|
||||
|
||||
unPinMessage: function() {
|
||||
return this.get("pinned_globally") ?
|
||||
I18n.t("topic.feature_topic.unpin_globally") :
|
||||
I18n.t("topic.feature_topic.unpin", { categoryLink: this.get("categoryLink") });
|
||||
}.property("categoryLink", "pinned_globally"),
|
||||
|
||||
pinMessage: function() {
|
||||
return I18n.t("topic.feature_topic.pin", { categoryLink: this.get("categoryLink") });
|
||||
}.property("categoryLink"),
|
||||
|
||||
alreadyPinnedMessage: function() {
|
||||
return I18n.t("topic.feature_topic.already_pinned", { categoryLink: this.get("categoryLink"), count: this.get("pinnedInCategoryCount") });
|
||||
}.property("categoryLink", "pinnedInCategoryCount"),
|
||||
|
||||
onShow() {
|
||||
const self = this;
|
||||
|
||||
this.set("loading", true);
|
||||
|
||||
return Discourse.ajax("/topics/feature_stats.json", {
|
||||
data: { category_id: this.get("category.id") }
|
||||
}).then(function(result) {
|
||||
if (result) {
|
||||
self.setProperties({
|
||||
pinnedInCategoryCount: result.pinned_in_category_count,
|
||||
pinnedGloballyCount: result.pinned_globally_count,
|
||||
bannerCount: result.banner_count,
|
||||
});
|
||||
}
|
||||
}).finally(function() {
|
||||
self.set("loading", false);
|
||||
});
|
||||
},
|
||||
|
||||
_forwardAction(name) {
|
||||
this.get("controllers.topic").send(name);
|
||||
this.send("closeModal");
|
||||
},
|
||||
|
||||
_confirmBeforePinning(count, name, action) {
|
||||
if (count < 4) {
|
||||
this._forwardAction(action);
|
||||
} else {
|
||||
this.send("hideModal");
|
||||
const message = I18n.t("topic.feature_topic.confirm_" + name, { count: count });
|
||||
bootbox.confirm(
|
||||
message, I18n.t("no_value"), I18n.t("yes_value"),
|
||||
(confirmed) => confirmed ? this._forwardAction(action) : this.send("reopenModal")
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
pin() { this._confirmBeforePinning(this.get("pinnedInCategoryCount"), "pin", "togglePinned"); },
|
||||
pinGlobally() { this._confirmBeforePinning(this.get("pinnedGloballyCount"), "pin_globally", "pinGlobally"); },
|
||||
unpin() { this._forwardAction("togglePinned"); },
|
||||
makeBanner() { this._forwardAction("makeBanner"); },
|
||||
removeBanner() { this._forwardAction("removeBanner"); },
|
||||
}
|
||||
|
||||
});
|
|
@ -94,6 +94,19 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, BufferedCon
|
|||
this.set('selectedReplies', []);
|
||||
}.on('init'),
|
||||
|
||||
_togglePinnedStates(property) {
|
||||
const value = this.get('pinned_at') ? false : true,
|
||||
topic = this.get('content');
|
||||
|
||||
// optimistic update
|
||||
topic.setProperties({
|
||||
pinned_at: value,
|
||||
pinned_globally: value
|
||||
});
|
||||
|
||||
return topic.saveStatus(property, value);
|
||||
},
|
||||
|
||||
actions: {
|
||||
deleteTopic() {
|
||||
this.deleteTopic();
|
||||
|
@ -352,13 +365,28 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, BufferedCon
|
|||
},
|
||||
|
||||
togglePinned() {
|
||||
// Note that this is different than clearPin
|
||||
this.get('content').setStatus('pinned', this.get('pinned_at') ? false : true);
|
||||
const value = this.get('pinned_at') ? false : true,
|
||||
topic = this.get('content');
|
||||
|
||||
// optimistic update
|
||||
topic.setProperties({
|
||||
pinned_at: value ? moment() : null,
|
||||
pinned_globally: false
|
||||
});
|
||||
|
||||
return topic.saveStatus("pinned", value);
|
||||
},
|
||||
|
||||
togglePinnedGlobally() {
|
||||
// Note that this is different than clearPin
|
||||
this.get('content').setStatus('pinned_globally', this.get('pinned_at') ? false : true);
|
||||
pinGlobally() {
|
||||
const topic = this.get('content');
|
||||
|
||||
// optimistic update
|
||||
topic.setProperties({
|
||||
pinned_at: moment(),
|
||||
pinned_globally: true
|
||||
});
|
||||
|
||||
return topic.saveStatus("pinned_globally", true);
|
||||
},
|
||||
|
||||
toggleArchived() {
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
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'});
|
||||
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();
|
||||
}
|
||||
if (model) { controller.set('model', model); }
|
||||
if (controller.onShow) { controller.onShow(); }
|
||||
controller.set('flashMessage', null);
|
||||
}
|
||||
return controller;
|
||||
|
|
|
@ -145,24 +145,16 @@ const Topic = Discourse.Model.extend({
|
|||
|
||||
toggleStatus(property) {
|
||||
this.toggleProperty(property);
|
||||
this.saveStatus(property, this.get(property) ? true : false);
|
||||
},
|
||||
|
||||
setStatus(property, value) {
|
||||
this.set(property, value);
|
||||
this.saveStatus(property, value);
|
||||
this.saveStatus(property, !this.get(property));
|
||||
},
|
||||
|
||||
saveStatus(property, value) {
|
||||
if (property === 'closed' && value === true) {
|
||||
this.set('details.auto_close_at', null);
|
||||
}
|
||||
if (property === 'pinned') {
|
||||
this.set('pinned_at', value ? moment() : null);
|
||||
}
|
||||
return Discourse.ajax(this.get('url') + "/status", {
|
||||
type: 'PUT',
|
||||
data: {status: property, enabled: value ? 'true' : 'false' }
|
||||
data: { status: property, enabled: !!value }
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ const ApplicationRoute = Discourse.Route.extend({
|
|||
|
||||
// Close the current modal, and destroy its state.
|
||||
closeModal() {
|
||||
this.render('hide-modal', {into: 'modal', outlet: 'modalBody'});
|
||||
this.render('hide-modal', { into: 'modal', outlet: 'modalBody' });
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -59,6 +59,11 @@ const TopicRoute = Discourse.Route.extend(ShowFooter, {
|
|||
this.controllerFor('modal').set('modalClass', 'edit-auto-close-modal');
|
||||
},
|
||||
|
||||
showFeatureTopic() {
|
||||
showModal('featureTopic', this.modelFor('topic'));
|
||||
this.controllerFor('modal').set('modalClass', 'feature-topic-modal');
|
||||
},
|
||||
|
||||
showInvite() {
|
||||
showModal('invite', this.modelFor('topic'));
|
||||
this.controllerFor('invite').reset();
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<div class="modal-body">
|
||||
{{#if pinned_at}}
|
||||
<div>
|
||||
{{d-button action="unpin" icon="thumb-tack" label="topic.actions.unpin" class="btn-primary btn-small"}}
|
||||
<p>{{{unPinMessage}}}</p>
|
||||
{{#if pinned_globally}}
|
||||
<p>{{i18n "topic.feature_topic.global_pin_note"}}</p>
|
||||
<p>
|
||||
{{#loading-spinner size="small" condition=loading}}
|
||||
{{{i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount}}}
|
||||
{{/loading-spinner}}
|
||||
</p>
|
||||
{{else}}
|
||||
<p>{{i18n "topic.feature_topic.pin_note"}}</p>
|
||||
<p>
|
||||
{{#loading-spinner size="small" condition=loading}}
|
||||
{{{alreadyPinnedMessage}}}
|
||||
{{/loading-spinner}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div>
|
||||
{{d-button action="pin" icon="thumb-tack" label="topic.actions.pin" class="btn-primary btn-small"}}
|
||||
<p>{{{pinMessage}}}</p>
|
||||
<p>{{i18n "topic.feature_topic.pin_note"}}</p>
|
||||
<p>
|
||||
{{#loading-spinner size="small" condition=loading}}
|
||||
{{{alreadyPinnedMessage}}}
|
||||
{{/loading-spinner}}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{{d-button action="pinGlobally" icon="thumb-tack" label="topic.actions.pin_globally" class="btn-primary btn-small"}}
|
||||
<p>{{i18n "topic.feature_topic.pin_globally"}}</p>
|
||||
<p>{{i18n "topic.feature_topic.global_pin_note"}}</p>
|
||||
<p>
|
||||
{{#loading-spinner size="small" condition=loading}}
|
||||
{{{i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount}}}
|
||||
{{/loading-spinner}}
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div>
|
||||
{{#if isBanner}}
|
||||
{{d-button action="removeBanner" icon="bullhorn" label="topic.actions.remove_banner" class="btn-primary btn-small"}}
|
||||
<p>{{i18n "topic.feature_topic.remove_banner"}}</p>
|
||||
{{else}}
|
||||
{{d-button action="makeBanner" icon="bullhorn" label="topic.actions.make_banner" class="btn-primary btn-small"}}
|
||||
<p>{{i18n "topic.feature_topic.make_banner"}}</p>
|
||||
{{/if}}
|
||||
<p>{{i18n "topic.feature_topic.banner_note"}}</p>
|
||||
<p>
|
||||
{{#loading-spinner size="small" condition=loading}}
|
||||
{{{i18n "topic.feature_topic.already_banner" count=bannerCount}}}
|
||||
{{/loading-spinner}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="pull-right" {{action "closeModal"}}>{{i18n "cancel"}}</a>
|
||||
</div>
|
|
@ -27,26 +27,11 @@
|
|||
</li>
|
||||
|
||||
{{#unless isPrivateMessage}}
|
||||
<li>
|
||||
{{#if isBanner}}
|
||||
{{d-button action="removeBanner" icon="bullhorn" label="topic.actions.remove_banner" class="btn-admin"}}
|
||||
{{else}}
|
||||
{{#if visible}}
|
||||
{{d-button action="makeBanner" icon="bullhorn" label="topic.actions.make_banner" class="btn-admin"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
{{#if pinned_at}}
|
||||
{{d-button action="togglePinned" icon="thumb-tack" label="topic.actions.unpin" class="btn-admin"}}
|
||||
{{else}}
|
||||
{{#if visible}}
|
||||
{{d-button action="togglePinned" icon="thumb-tack" label="topic.actions.pin" class="btn-admin"}}
|
||||
{{d-button action="togglePinnedGlobally" icon="thumb-tack" label="topic.actions.pin_globally" class="btn-admin"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</li>
|
||||
{{#if visible}}
|
||||
<li>
|
||||
{{d-button action="showFeatureTopic" icon="bullhorn" label="topic.actions.feature" class="btn-admin"}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
<li>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/archetype_options',
|
||||
title: I18n.t('topic.options')
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/avatar_selector',
|
||||
classNames: ['avatar-selector'],
|
||||
title: I18n.t('user.change_avatar.title'),
|
||||
|
@ -9,10 +11,6 @@ export default Discourse.ModalBodyView.extend({
|
|||
// *HACK* used to select the proper radio button, cause {{action}}
|
||||
// stops the default behavior
|
||||
selectedChanged: function() {
|
||||
var self = this;
|
||||
Em.run.next(function() {
|
||||
var value = self.get('controller.selected');
|
||||
$('input:radio[name="avatar"]').val([value]);
|
||||
});
|
||||
Em.run.next(() => $('input:radio[name="avatar"]').val([this.get('controller.selected')]) );
|
||||
}.observes('controller.selected')
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/change_owner',
|
||||
title: I18n.t('topic.change_owner.title')
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/create-account',
|
||||
title: I18n.t('create_account.title'),
|
||||
classNames: ['create-account'],
|
||||
|
@ -6,7 +8,7 @@ export default Discourse.ModalBodyView.extend({
|
|||
_setup: function() {
|
||||
// allows the submission the form when pressing 'ENTER' on *any* text input field
|
||||
// but only when the submit button is enabled
|
||||
var createAccountController = this.get('controller');
|
||||
const createAccountController = this.get('controller');
|
||||
Em.run.schedule('afterRender', function() {
|
||||
$("input[type='text'], input[type='password']").keydown(function(e) {
|
||||
if (createAccountController.get('submitDisabled') === false && e.keyCode === 13) {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/edit-category',
|
||||
|
||||
_initializePanels: function() {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/auto_close',
|
||||
title: I18n.t('topic.auto_close_title')
|
||||
});
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/feature-topic',
|
||||
title: I18n.t('topic.feature_topic.title')
|
||||
});
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/flag',
|
||||
|
||||
title: function() {
|
||||
|
@ -6,12 +8,13 @@ export default Discourse.ModalBodyView.extend({
|
|||
}.property('controller.flagTopic'),
|
||||
|
||||
selectedChanged: function() {
|
||||
var self = this;
|
||||
const self = this;
|
||||
|
||||
Em.run.next(function() {
|
||||
self.$("input[type='radio']").prop('checked', false);
|
||||
|
||||
var nameKey = self.get('controller.selected.name_key');
|
||||
if (!nameKey) return;
|
||||
const nameKey = self.get('controller.selected.name_key');
|
||||
if (!nameKey) { return; }
|
||||
|
||||
self.$('#radio_' + nameKey).prop('checked', 'true');
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/forgot_password',
|
||||
title: I18n.t('forgot_password.title'),
|
||||
});
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/history',
|
||||
title: I18n.t('history'),
|
||||
|
||||
resizeModal: function(){
|
||||
var viewPortHeight = $(window).height();
|
||||
const viewPortHeight = $(window).height();
|
||||
this.$(".modal-body").css("max-height", Math.floor(0.8 * viewPortHeight) + "px");
|
||||
}.on("didInsertElement")
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/invite_private',
|
||||
title: I18n.t('topic.invite_private.title')
|
||||
});
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/invite',
|
||||
|
||||
title: function() {
|
||||
if (this.get('controller.invitingToTopic')) {
|
||||
return I18n.t('topic.invite_reply.title');
|
||||
} else {
|
||||
return I18n.t('user.invited.create');
|
||||
}
|
||||
return this.get('controller.invitingToTopic') ?
|
||||
I18n.t('topic.invite_reply.title') :
|
||||
I18n.t('user.invited.create');
|
||||
}.property('controller.invitingToTopic')
|
||||
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/keyboard_shortcuts_help',
|
||||
title: I18n.t('keyboard_shortcuts_help.title')
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/login',
|
||||
title: I18n.t('login.title'),
|
||||
classNames: ['login-modal'],
|
||||
|
@ -9,7 +11,7 @@ export default Discourse.ModalBodyView.extend({
|
|||
},
|
||||
|
||||
_setup: function() {
|
||||
var loginController = this.get('controller');
|
||||
const loginController = this.get('controller');
|
||||
|
||||
// Get username and password from the browser's password manager,
|
||||
// if it filled the hidden static login form:
|
||||
|
@ -18,10 +20,8 @@ export default Discourse.ModalBodyView.extend({
|
|||
|
||||
Em.run.schedule('afterRender', function() {
|
||||
$('#login-account-password, #login-account-name').keydown(function(e) {
|
||||
if (e.keyCode === 13) {
|
||||
if (!loginController.get('loginDisabled')) {
|
||||
loginController.send('login');
|
||||
}
|
||||
if (e.keyCode === 13 && !loginController.get('loginDisabled')) {
|
||||
loginController.send('login');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/merge_topic',
|
||||
title: I18n.t('topic.merge_topic.title')
|
||||
});
|
||||
|
|
30
app/assets/javascripts/discourse/views/modal-body.js.es6
Normal file
30
app/assets/javascripts/discourse/views/modal-body.js.es6
Normal file
|
@ -0,0 +1,30 @@
|
|||
export default Discourse.View.extend({
|
||||
focusInput: true,
|
||||
|
||||
_setupModal: function() {
|
||||
$('#modal-alert').hide();
|
||||
$('#discourse-modal').modal('show');
|
||||
|
||||
// Focus on first element
|
||||
if (!Discourse.Mobile.mobileView && this.get('focusInput')) {
|
||||
Em.run.schedule('afterRender', () => this.$('input:first').focus() );
|
||||
}
|
||||
|
||||
const title = this.get('title');
|
||||
if (title) {
|
||||
this.set('controller.controllers.modal.title', title);
|
||||
}
|
||||
}.on('didInsertElement'),
|
||||
|
||||
flashMessageChanged: function() {
|
||||
const flashMessage = this.get('controller.flashMessage');
|
||||
if (flashMessage) {
|
||||
const messageClass = flashMessage.get('messageClass') || 'success';
|
||||
$('#modal-alert').hide()
|
||||
.removeClass('alert-error', 'alert-success')
|
||||
.addClass("alert alert-" + messageClass).html(flashMessage.get('message'))
|
||||
.fadeIn();
|
||||
}
|
||||
}.observes('controller.flashMessage')
|
||||
|
||||
});
|
|
@ -4,13 +4,13 @@ export default Ember.View.extend({
|
|||
classNameBindings: [':modal', ':hidden', 'controller.modalClass'],
|
||||
|
||||
click: function(e) {
|
||||
var $target = $(e.target);
|
||||
const $target = $(e.target);
|
||||
if ($target.hasClass("modal-middle-container") ||
|
||||
$target.hasClass("modal-outer-container")) {
|
||||
// Delegate click to modal backdrop if clicked outside. We do this
|
||||
// because some CSS of ours seems to cover the backdrop and makes it
|
||||
// unclickable.
|
||||
$('.modal-backdrop').click();
|
||||
// Delegate click to modal close if clicked outside.
|
||||
// We do this because some CSS of ours seems to cover
|
||||
// the backdrop and makes it unclickable.
|
||||
$('.modal-header a.close').click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
/**
|
||||
A base class for helping us display modal content
|
||||
|
||||
@class ModalBodyView
|
||||
@extends Discourse.View
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.ModalBodyView = Discourse.View.extend({
|
||||
focusInput: true,
|
||||
|
||||
_setupModal: function() {
|
||||
var self = this,
|
||||
$discourseModal = $('#discourse-modal');
|
||||
|
||||
$discourseModal.modal('show');
|
||||
$discourseModal.one("hide", function () {
|
||||
self.get("controller").send("closeModal");
|
||||
});
|
||||
|
||||
$('#modal-alert').hide();
|
||||
|
||||
// Focus on first element
|
||||
if (!Discourse.Mobile.mobileView && self.get('focusInput')) {
|
||||
Em.run.schedule('afterRender', function() {
|
||||
self.$('input:first').focus();
|
||||
});
|
||||
}
|
||||
|
||||
var title = this.get('title');
|
||||
if (title) {
|
||||
this.set('controller.controllers.modal.title', title);
|
||||
}
|
||||
}.on('didInsertElement'),
|
||||
|
||||
flashMessageChanged: function() {
|
||||
var flashMessage = this.get('controller.flashMessage');
|
||||
if (flashMessage) {
|
||||
var messageClass = flashMessage.get('messageClass') || 'success';
|
||||
var $alert = $('#modal-alert').hide().removeClass('alert-error', 'alert-success');
|
||||
$alert.addClass("alert alert-" + messageClass).html(flashMessage.get('message'));
|
||||
$alert.fadeIn();
|
||||
}
|
||||
}.observes('controller.flashMessage')
|
||||
|
||||
});
|
||||
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/not_activated',
|
||||
title: I18n.t('log_in')
|
||||
});
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/raw_email',
|
||||
title: I18n.t('raw_email.title'),
|
||||
|
||||
resizeModal: function(){
|
||||
var viewPortHeight = $(window).height();
|
||||
const viewPortHeight = $(window).height();
|
||||
this.$(".modal-body").css("max-height", Math.floor(0.8 * viewPortHeight) + "px");
|
||||
}.on("didInsertElement")
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/search_help',
|
||||
title: I18n.t('search_help.title'),
|
||||
focusInput: false
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend(Discourse.SelectedPostsCount, {
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend(Discourse.SelectedPostsCount, {
|
||||
templateName: 'modal/split_topic',
|
||||
title: I18n.t('topic.split_topic.title')
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/topic-bulk-actions',
|
||||
title: I18n.t('topics.bulk.actions')
|
||||
});
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
function uploadTranslate(key, options) {
|
||||
var opts = options || {};
|
||||
const opts = options || {};
|
||||
if (Discourse.Utilities.allowsAttachments()) { key += "_with_attachments"; }
|
||||
return I18n.t("upload_selector." + key, opts);
|
||||
}
|
||||
|
||||
export default Discourse.ModalBodyView.extend({
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/upload_selector',
|
||||
classNames: ['upload-selector'],
|
||||
|
||||
|
@ -12,17 +14,16 @@ export default Discourse.ModalBodyView.extend({
|
|||
uploadIcon: function() { return Discourse.Utilities.allowsAttachments() ? "fa-upload" : "fa-picture-o"; }.property(),
|
||||
|
||||
tip: function() {
|
||||
var source = this.get("controller.local") ? "local" : "remote";
|
||||
var opts = { authorized_extensions: Discourse.Utilities.authorizedExtensions() };
|
||||
const source = this.get("controller.local") ? "local" : "remote",
|
||||
opts = { authorized_extensions: Discourse.Utilities.authorizedExtensions() };
|
||||
return uploadTranslate(source + "_tip", opts);
|
||||
}.property("controller.local"),
|
||||
|
||||
hint: function() {
|
||||
// cf. http://stackoverflow.com/a/9851769/11983
|
||||
var isChrome = !!window.chrome && !(!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0);
|
||||
var isFirefox = typeof InstallTrigger !== 'undefined';
|
||||
var isSupported = isChrome || isFirefox;
|
||||
|
||||
const isChrome = !!window.chrome && !(!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0),
|
||||
isFirefox = typeof InstallTrigger !== 'undefined',
|
||||
isSupported = isChrome || isFirefox;
|
||||
// chrome is the only browser that support copy & paste of images.
|
||||
return I18n.t("upload_selector.hint" + (isSupported ? "_for_supported_browsers" : ""));
|
||||
}.property(),
|
||||
|
@ -32,7 +33,7 @@ export default Discourse.ModalBodyView.extend({
|
|||
}.on('didInsertElement'),
|
||||
|
||||
selectedChanged: function() {
|
||||
var self = this;
|
||||
const self = this;
|
||||
Em.run.next(function() {
|
||||
// *HACK* to select the proper radio button
|
||||
var value = self.get('controller.local') ? 'local' : 'remote';
|
||||
|
@ -47,9 +48,9 @@ export default Discourse.ModalBodyView.extend({
|
|||
if (this.get("controller.local")) {
|
||||
$('#reply-control').fileupload('add', { fileInput: $('#filename-input') });
|
||||
} else {
|
||||
var imageUrl = $('#fileurl-input').val();
|
||||
var imageLink = $('#link-input').val();
|
||||
var composerView = this.get('controller.composerView');
|
||||
const imageUrl = $('#fileurl-input').val(),
|
||||
imageLink = $('#link-input').val(),
|
||||
composerView = this.get('controller.composerView');
|
||||
if (this.get("controller.showMore") && imageLink.length > 3) {
|
||||
composerView.addMarkdown("[![](" + imageUrl +")](" + imageLink + ")");
|
||||
} else {
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
//= require ./discourse/controllers/navigation/default
|
||||
//= require ./discourse/views/view
|
||||
//= require ./discourse/views/container
|
||||
//= require ./discourse/views/modal_body_view
|
||||
//= require ./discourse/views/modal-body
|
||||
//= require ./discourse/views/flag
|
||||
//= require ./discourse/views/combo-box
|
||||
//= require ./discourse/views/button
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
//= require admin/models/user-field
|
||||
//= require admin/models/site-setting
|
||||
//= require admin/controllers/admin-email-skipped
|
||||
//= require admin/controllers/change-site-customization-details
|
||||
//= require discourse/lib/export-result
|
||||
//= require_tree ./admin
|
||||
|
||||
|
|
|
@ -158,6 +158,19 @@ class TopicsController < ApplicationController
|
|||
render_serialized(topics, BasicTopicSerializer)
|
||||
end
|
||||
|
||||
def feature_stats
|
||||
params.require(:category_id)
|
||||
category_id = params[:category_id].to_i
|
||||
|
||||
topics = Topic.listable_topics.visible
|
||||
|
||||
render json: {
|
||||
pinned_in_category_count: topics.where(category_id: category_id).where(pinned_globally: false).where.not(pinned_at: nil).count,
|
||||
pinned_globally_count: topics.where(pinned_globally: true).where.not(pinned_at: nil).count,
|
||||
banner_count: topics.where(archetype: Archetype.banner).count,
|
||||
}
|
||||
end
|
||||
|
||||
def status
|
||||
params.require(:status)
|
||||
params.require(:enabled)
|
||||
|
@ -492,7 +505,7 @@ class TopicsController < ApplicationController
|
|||
end
|
||||
|
||||
def check_for_status_presence(key, attr)
|
||||
invalid_param(key) unless %w(pinned_globally visible closed pinned archived).include?(attr)
|
||||
invalid_param(key) unless %w(pinned pinned_globally visible closed archived).include?(attr)
|
||||
end
|
||||
|
||||
def invalid_param(key)
|
||||
|
|
|
@ -973,10 +973,11 @@ en:
|
|||
open: "Open Topic"
|
||||
close: "Close Topic"
|
||||
auto_close: "Auto Close"
|
||||
feature: "Feature Topic"
|
||||
make_banner: "Banner Topic"
|
||||
remove_banner: "Remove Banner Topic"
|
||||
unpin: "Un-Pin Topic"
|
||||
pin: "Pin Topic"
|
||||
unpin: "Un-Pin Topic"
|
||||
pin_globally: "Pin Topic Globally"
|
||||
unarchive: "Unarchive Topic"
|
||||
archive: "Archive Topic"
|
||||
|
@ -1002,6 +1003,31 @@ en:
|
|||
help: 'privately flag this topic for attention or send a private notification about it'
|
||||
success_message: 'You successfully flagged this topic.'
|
||||
|
||||
feature_topic:
|
||||
title: "Feature this topic"
|
||||
pin: "Make this topic appear at the top of the {{categoryLink}} category."
|
||||
confirm_pin: "Are you sure? You already have {{count}} pinned topics -- too many pinned topics can obscure other active topics."
|
||||
unpin: "Remove this topic from the topic of the {{categoryLink}} category."
|
||||
pin_note: "Users can unpin the topic individually for themselves."
|
||||
already_pinned:
|
||||
zero: "No topic currently pinned in {{categoryLink}}."
|
||||
one: "Topic currently pinned in {{categoryLink}}: <strong>1.</strong>"
|
||||
other: "Topics currently pinned in {{categoryLink}}: <strong>{{count}}</strong>."
|
||||
pin_globally: "Make this topic appear at the top of all topic lists, until a staff member unpins it."
|
||||
confirm_pin_globally: "Are you sure? You already have {{count}} globally pinned topics -- too many pinned topics can obscure other active topics."
|
||||
unpin_globally: "Remove this topic from the top of all topic lists."
|
||||
global_pin_note: "Users can unpin the topic individually for themselves."
|
||||
already_pinned_globally:
|
||||
zero: "No topic currently pinned globally."
|
||||
one: "Topic currently pinned globally: <strong>1.</strong>"
|
||||
other: "Topics currently pinned globally: <strong>{{count}}</strong>."
|
||||
make_banner: "Make this topic into a banner that appears at the top of all pages."
|
||||
remove_banner: "Remove the banner that appears at the top of all pages."
|
||||
banner_note: "Users can dismiss the banner by closing it. Only one topic can be bannered at any given time."
|
||||
already_banner:
|
||||
zero: "There is currently no banner topic."
|
||||
one: "There is currently a banner topic."
|
||||
|
||||
inviting: "Inviting..."
|
||||
automatically_add_to_groups_optional: "This invite also includes access to these groups: (optional, admin only)"
|
||||
automatically_add_to_groups_required: "This invite also includes access to these groups: (<b>Required</b>, admin only)"
|
||||
|
|
|
@ -392,6 +392,7 @@ Discourse::Application.routes.draw do
|
|||
put "topics/reset-new" => 'topics#reset_new'
|
||||
post "topics/timings"
|
||||
get "topics/similar_to"
|
||||
get "topics/feature_stats"
|
||||
get "topics/created-by/:username" => "list#topics_by", as: "topics_by", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "topics/private-messages/:username" => "list#private_messages", as: "topics_private_messages", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "topics/private-messages-sent/:username" => "list#private_messages_sent", as: "topics_private_messages_sent", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
|
|
|
@ -19,7 +19,7 @@ test("modal", function() {
|
|||
|
||||
click('.login-button');
|
||||
andThen(function() {
|
||||
ok(find('#discourse-modal:visible').length === 1, 'modal should appear');
|
||||
ok(find('#discourse-modal:visible').length === 1, 'modal should reappear');
|
||||
});
|
||||
|
||||
keyEvent('#main-outlet', 'keyup', 27);
|
||||
|
|
Loading…
Reference in a new issue