UX: add topic admin button to the bottom of the topic
This commit is contained in:
parent
b1271ed44b
commit
67c10a7eab
12 changed files with 167 additions and 98 deletions
app/assets
javascripts/discourse
components
controllers
routes
templates
views
stylesheets
config/locales
|
@ -0,0 +1,24 @@
|
||||||
|
export default Em.Component.extend({
|
||||||
|
tagName: "button",
|
||||||
|
classNames: ["btn", "no-text", "show-topic-admin"],
|
||||||
|
attributeBindings: ["title"],
|
||||||
|
title: I18n.t("topic_admin_menu"),
|
||||||
|
|
||||||
|
render: function(buffer) {
|
||||||
|
buffer.push("<i class='fa fa-wrench'></i>");
|
||||||
|
},
|
||||||
|
|
||||||
|
click: function() {
|
||||||
|
var $target = this.$(),
|
||||||
|
position = $target.position(),
|
||||||
|
width = $target.innerWidth();
|
||||||
|
var location = {
|
||||||
|
position: "fixed",
|
||||||
|
left: position.left + width,
|
||||||
|
top: position.top,
|
||||||
|
};
|
||||||
|
this.appEvents.trigger("topic-admin-menu:open", location);
|
||||||
|
this.sendAction("show");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
|
@ -10,17 +10,11 @@ import ObjectController from 'discourse/controllers/object';
|
||||||
**/
|
**/
|
||||||
export default ObjectController.extend({
|
export default ObjectController.extend({
|
||||||
menuVisible: false,
|
menuVisible: false,
|
||||||
needs: ['modal'],
|
showRecover: Em.computed.and('deleted', 'details.can_recover'),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
show: function() {
|
show: function() { this.set('menuVisible', true); },
|
||||||
this.set('menuVisible', true);
|
hide: function() { this.set('menuVisible', false); }
|
||||||
},
|
}
|
||||||
|
|
||||||
hide: function() {
|
|
||||||
this.set('menuVisible', false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showRecover: Em.computed.and('deleted', 'details.can_recover')
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,10 @@ Discourse.TopicRoute = Discourse.Route.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
showTopicAdminMenu: function() {
|
||||||
|
this.controllerFor("topic-admin-menu").send("show");
|
||||||
|
},
|
||||||
|
|
||||||
// Modals that can pop up within a topic
|
// Modals that can pop up within a topic
|
||||||
expandPostUser: function(post) {
|
expandPostUser: function(post) {
|
||||||
this.controllerFor('user-expansion').show(post.get('username'), post.get('uploaded_avatar_id'));
|
this.controllerFor('user-expansion').show(post.get('username'), post.get('uploaded_avatar_id'));
|
||||||
|
|
|
@ -1,74 +1,68 @@
|
||||||
{{#if menuVisible}}
|
<h3>{{i18n admin_title}}</h3>
|
||||||
<div class="topic-admin-menu">
|
|
||||||
<h3>{{i18n admin_title}}</h3>
|
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<button {{action "toggleMultiSelect"}} class='btn btn-admin'><i class='fa fa-tasks'></i> {{i18n topic.actions.multi_select}}</button>
|
<button {{action "toggleMultiSelect"}} class='btn btn-admin'>{{fa-icon tasks}} {{i18n topic.actions.multi_select}}</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{{#if details.can_delete}}
|
{{#if details.can_delete}}
|
||||||
<li>
|
<li>
|
||||||
<button {{action "deleteTopic"}} class='btn btn-admin btn-danger'><i class='fa fa-trash-o'></i> {{i18n topic.actions.delete}}</button>
|
<button {{action "deleteTopic"}} class='btn btn-admin btn-danger'>{{fa-icon trash-o}} {{i18n topic.actions.delete}}</button>
|
||||||
</li>
|
</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if showRecover}}
|
{{#if showRecover}}
|
||||||
<li>
|
<li>
|
||||||
<button {{action "recoverTopic"}} class='btn btn-admin'><i class='fa fa-undo'></i> {{i18n topic.actions.recover}}</button>
|
<button {{action "recoverTopic"}} class='btn btn-admin'>{{fa-icon undo}} {{i18n topic.actions.recover}}</button>
|
||||||
</li>
|
</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
{{#if closed}}
|
{{#if closed}}
|
||||||
<button {{action "toggleClosed"}} class='btn btn-admin'><i class='fa fa-unlock'></i> {{i18n topic.actions.open}}</button>
|
<button {{action "toggleClosed"}} class='btn btn-admin'>{{fa-icon unlock}} {{i18n topic.actions.open}}</button>
|
||||||
{{else}}
|
{{else}}
|
||||||
<button {{action "toggleClosed"}} class='btn btn-admin'><i class='fa fa-lock'></i> {{i18n topic.actions.close}}</button>
|
<button {{action "toggleClosed"}} class='btn btn-admin'>{{fa-icon lock}} {{i18n topic.actions.close}}</button>
|
||||||
<button {{action "showAutoClose"}} class='btn btn-admin'><i class='fa fa-clock-o'></i> {{i18n topic.actions.auto_close}}</button>
|
<button {{action "showAutoClose"}} class='btn btn-admin'>{{fa-icon clock-o}} {{i18n topic.actions.auto_close}}</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{{#unless isPrivateMessage}}
|
{{#unless isPrivateMessage}}
|
||||||
<li>
|
<li>
|
||||||
{{#if isBanner}}
|
{{#if isBanner}}
|
||||||
<button {{action "removeBanner"}} class='btn btn-admin'><i class='fa fa-bullhorn'></i> {{i18n topic.actions.remove_banner}}</button>
|
<button {{action "removeBanner"}} class='btn btn-admin'>{{fa-icon bullhorn}} {{i18n topic.actions.remove_banner}}</button>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if visible}}
|
|
||||||
<button {{action "makeBanner"}} class='btn btn-admin'><i class='fa fa-bullhorn'></i> {{i18n topic.actions.make_banner}}</button>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
{{#if pinned_at}}
|
|
||||||
<button {{action "togglePinned"}} class='btn btn-admin'><i class='fa fa-thumb-tack'></i> {{i18n topic.actions.unpin}}</button>
|
|
||||||
{{else}}
|
|
||||||
{{#if visible}}
|
|
||||||
<button {{action "togglePinned"}} class='btn btn-admin'><i class='fa fa-thumb-tack'></i> {{i18n topic.actions.pin}}</button>
|
|
||||||
<button {{action "togglePinnedGlobally"}} class='btn btn-admin'><i class='fa fa-thumb-tack'></i> {{i18n topic.actions.pin_globally}}</button>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
</li>
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
<li>
|
|
||||||
{{#if archived}}
|
|
||||||
<button {{action "toggleArchived"}} class='btn btn-admin'><i class='fa fa-folder'></i> {{i18n topic.actions.unarchive}}</button>
|
|
||||||
{{else}}
|
|
||||||
<button {{action "toggleArchived"}} class='btn btn-admin'><i class='fa fa-folder'></i> {{i18n topic.actions.archive}}</button>
|
|
||||||
{{/if}}
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
{{#if visible}}
|
{{#if visible}}
|
||||||
<button {{action "toggleVisibility"}} class='btn btn-admin'><i class='fa fa-eye-slash'></i> {{i18n topic.actions.invisible}}</button>
|
<button {{action "makeBanner"}} class='btn btn-admin'>{{fa-icon bullhorn}} {{i18n topic.actions.make_banner}}</button>
|
||||||
{{else}}
|
|
||||||
<button {{action "toggleVisibility"}} class='btn btn-admin'><i class='fa fa-eye'></i> {{i18n topic.actions.visible}}</button>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</li>
|
{{/if}}
|
||||||
|
</li>
|
||||||
|
|
||||||
</ul>
|
<li>
|
||||||
</div>
|
{{#if pinned_at}}
|
||||||
{{else}}
|
<button {{action "togglePinned"}} class='btn btn-admin'>{{fa-icon thumb-tack}} {{i18n topic.actions.unpin}}</button>
|
||||||
<button class='btn no-text' id='show-topic-admin' {{action "show"}}><i class='fa fa-wrench'></i></button>
|
{{else}}
|
||||||
{{/if}}
|
{{#if visible}}
|
||||||
|
<button {{action "togglePinned"}} class='btn btn-admin'>{{fa-icon thumb-tack}} {{i18n topic.actions.pin}}</button>
|
||||||
|
<button {{action "togglePinnedGlobally"}} class='btn btn-admin'>{{fa-icon thumb-tack}} {{i18n topic.actions.pin_globally}}</button>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</li>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
<li>
|
||||||
|
{{#if archived}}
|
||||||
|
<button {{action "toggleArchived"}} class='btn btn-admin'>{{fa-icon folder}} {{i18n topic.actions.unarchive}}</button>
|
||||||
|
{{else}}
|
||||||
|
<button {{action "toggleArchived"}} class='btn btn-admin'>{{fa-icon folder}} {{i18n topic.actions.archive}}</button>
|
||||||
|
{{/if}}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
{{#if visible}}
|
||||||
|
<button {{action "toggleVisibility"}} class='btn btn-admin'>{{fa-icon eye-slash}} {{i18n topic.actions.invisible}}</button>
|
||||||
|
{{else}}
|
||||||
|
<button {{action "toggleVisibility"}} class='btn btn-admin'>{{fa-icon eye}} {{i18n topic.actions.visible}}</button>
|
||||||
|
{{/if}}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
|
@ -25,12 +25,12 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{text-field id='edit-title' value=newTitle maxLength=maxTitleLength}}
|
{{text-field id='edit-title' value=newTitle maxLength=maxTitleLength}}
|
||||||
|
|
||||||
<button class='btn btn-primary btn-small no-text' {{action "finishedEditingTopic"}}><i class='fa fa-check'></i></button>
|
<button class='btn btn-primary btn-small no-text' {{action "finishedEditingTopic"}}>{{fa-icon check}}</button>
|
||||||
<button class='btn btn-small no-text' {{action "cancelEditingTopic"}}><i class='fa fa-times'></i></button>
|
<button class='btn btn-small no-text' {{action "cancelEditingTopic"}}>{{fa-icon times}}</button>
|
||||||
{{else}}
|
{{else}}
|
||||||
<h1>
|
<h1>
|
||||||
{{#unless is_warning}}
|
{{#unless is_warning}}
|
||||||
<span class="private-message-glyph"><i class='fa fa-envelope'></i></span>
|
<span class="private-message-glyph">{{fa-icon envelope}}</span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
{{#if details.loaded}}
|
{{#if details.loaded}}
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
|
|
||||||
|
|
||||||
{{#if details.can_edit}}
|
{{#if details.can_edit}}
|
||||||
<a href='#' {{action "editTopic"}} class='edit-topic' title='{{i18n edit}}'><i class="fa fa-pencil"></i></a>
|
<a href='#' {{action "editTopic"}} class='edit-topic' title='{{i18n edit}}'>{{fa-icon pencil}}</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
@ -118,7 +118,6 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -134,7 +133,7 @@
|
||||||
{{message}}
|
{{message}}
|
||||||
{{#if noRetry}}
|
{{#if noRetry}}
|
||||||
{{#unless currentUser}}
|
{{#unless currentUser}}
|
||||||
<button {{action "showLogin"}} class='btn btn-primary topic-retry'><i class="fa fa-user"></i>{{i18n log_in}}</button>
|
<button {{action "showLogin"}} class='btn btn-primary topic-retry'>{{fa-icon user}} {{i18n log_in}}</button>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<button class="btn btn-primary topic-retry" {{action "retryLoading"}}>{{i18n errors.buttons.again}}</button>
|
<button class="btn btn-primary topic-retry" {{action "retryLoading"}}>{{i18n errors.buttons.again}}</button>
|
||||||
|
@ -159,5 +158,6 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if currentUser.canManageTopic}}
|
{{#if currentUser.canManageTopic}}
|
||||||
|
{{show-topic-admin show="showTopicAdminMenu"}}
|
||||||
{{render "topic-admin-menu"}}
|
{{render "topic-admin-menu"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -5,9 +5,10 @@ export default Discourse.View.extend({
|
||||||
|
|
||||||
title: function() {
|
title: function() {
|
||||||
return I18n.t(this.get('helpKey') || this.get('textKey'));
|
return I18n.t(this.get('helpKey') || this.get('textKey'));
|
||||||
}.property('helpKey'),
|
}.property('helpKey', 'textKey'),
|
||||||
|
|
||||||
text: function() {
|
text: function() {
|
||||||
|
if (Em.empty(this.get('textKey'))) { return ""; }
|
||||||
return I18n.t(this.get('textKey'));
|
return I18n.t(this.get('textKey'));
|
||||||
}.property('textKey'),
|
}.property('textKey'),
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import ButtonView from "discourse/views/button";
|
||||||
|
|
||||||
|
export default ButtonView.extend({
|
||||||
|
classNameBindings: [":no-text"],
|
||||||
|
helpKey: "topic_admin_menu",
|
||||||
|
|
||||||
|
renderIcon: function(buffer) {
|
||||||
|
buffer.push("<i class='fa fa-wrench'></i>");
|
||||||
|
},
|
||||||
|
|
||||||
|
click: function() {
|
||||||
|
var offset = this.$().offset();
|
||||||
|
var location = {
|
||||||
|
position: "absolute",
|
||||||
|
left: offset.left,
|
||||||
|
top: offset.top,
|
||||||
|
};
|
||||||
|
this.get("controller").appEvents.trigger("topic-admin-menu:open", location);
|
||||||
|
return this.get("controller").send("showTopicAdminMenu");
|
||||||
|
}
|
||||||
|
});
|
|
@ -7,21 +7,47 @@
|
||||||
@module Discourse
|
@module Discourse
|
||||||
**/
|
**/
|
||||||
export default Discourse.View.extend({
|
export default Discourse.View.extend({
|
||||||
|
classNameBindings: ["controller.menuVisible::hidden", ":topic-admin-menu"],
|
||||||
|
|
||||||
willDestroyElement: function() {
|
_setup: function() {
|
||||||
$('html').off('mouseup.discourse-topic-admin-menu');
|
|
||||||
},
|
|
||||||
|
|
||||||
didInsertElement: function() {
|
|
||||||
var self = this;
|
var self = this;
|
||||||
$('html').on('mouseup.discourse-topic-admin-menu', function(e) {
|
|
||||||
|
this.appEvents.on("topic-admin-menu:open", this, "_changeLocation");
|
||||||
|
|
||||||
|
$("html").on("mouseup.discourse-topic-admin-menu", function(e) {
|
||||||
var $target = $(e.target);
|
var $target = $(e.target);
|
||||||
if ($target.is('button') || self.$().has($target).length === 0) {
|
if ($target.is("button") || self.$().has($target).length === 0) {
|
||||||
self.get('controller').send('hide');
|
self.get("controller").send("hide");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}.on("didInsertElement"),
|
||||||
|
|
||||||
|
_changeLocation: function(location) {
|
||||||
|
console.log("_changeLocation", location);
|
||||||
|
var $this = this.$();
|
||||||
|
switch (location.position) {
|
||||||
|
case "absolute": {
|
||||||
|
$this.css({
|
||||||
|
position: "absolute",
|
||||||
|
top: location.top - $this.innerHeight() + 5,
|
||||||
|
left: location.left,
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "fixed": {
|
||||||
|
$this.css({
|
||||||
|
position: "fixed",
|
||||||
|
top: location.top,
|
||||||
|
left: location.left - $this.innerWidth(),
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_cleanup: function() {
|
||||||
|
$("html").off("mouseup.discourse-topic-admin-menu");
|
||||||
|
this.appEvents.off("topic-admin-menu:open");
|
||||||
|
}.on("willDestroyElement"),
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import TopicAdminMenuButton from 'discourse/views/topic-admin-menu-button';
|
||||||
import LoginReplyButton from 'discourse/views/login-reply-button';
|
import LoginReplyButton from 'discourse/views/login-reply-button';
|
||||||
import FlagTopicButton from 'discourse/views/flag-topic-button';
|
import FlagTopicButton from 'discourse/views/flag-topic-button';
|
||||||
import StarButton from 'discourse/views/star-button';
|
import StarButton from 'discourse/views/star-button';
|
||||||
|
@ -21,6 +22,9 @@ export default DiscourseContainerView.extend({
|
||||||
createButtons: function() {
|
createButtons: function() {
|
||||||
var topic = this.get('topic');
|
var topic = this.get('topic');
|
||||||
if (Discourse.User.current()) {
|
if (Discourse.User.current()) {
|
||||||
|
if (Discourse.User.currentProp("staff")) {
|
||||||
|
this.attachViewClass(TopicAdminMenuButton);
|
||||||
|
}
|
||||||
if (!topic.get('isPrivateMessage')) {
|
if (!topic.get('isPrivateMessage')) {
|
||||||
// We hide some controls from private messages
|
// We hide some controls from private messages
|
||||||
if (this.get('topic.details.can_invite_to')) {
|
if (this.get('topic.details.can_invite_to')) {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
// Styles for the topic admin menu
|
// Styles for the topic admin menu
|
||||||
|
|
||||||
#show-topic-admin {
|
.show-topic-admin {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 70px;
|
top: 70px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-admin-menu {
|
.topic-admin-menu {
|
||||||
|
@ -12,9 +13,6 @@
|
||||||
width: 205px;
|
width: 205px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: 1px solid scale-color-diff();
|
border: 1px solid scale-color-diff();
|
||||||
position: fixed;
|
|
||||||
top: 70px;
|
|
||||||
right: 10px;
|
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
|
|
|
@ -473,6 +473,7 @@ a.star {
|
||||||
color: $primary;
|
color: $primary;
|
||||||
}
|
}
|
||||||
.btn {
|
.btn {
|
||||||
|
outline: 0;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
.fa-star {margin-right: 5px;}
|
.fa-star {margin-right: 5px;}
|
||||||
|
|
|
@ -101,6 +101,8 @@ en:
|
||||||
google+: 'share this link on Google+'
|
google+: 'share this link on Google+'
|
||||||
email: 'send this link in an email'
|
email: 'send this link in an email'
|
||||||
|
|
||||||
|
topic_admin_menu: "topic admin actions"
|
||||||
|
|
||||||
edit: 'edit the title and category of this topic'
|
edit: 'edit the title and category of this topic'
|
||||||
not_implemented: "That feature hasn't been implemented yet, sorry!"
|
not_implemented: "That feature hasn't been implemented yet, sorry!"
|
||||||
no_value: "No"
|
no_value: "No"
|
||||||
|
|
Reference in a new issue