diff --git a/app/assets/javascripts/discourse/components/mount-widget.js.es6 b/app/assets/javascripts/discourse/components/mount-widget.js.es6
index da9897312..f47ce4688 100644
--- a/app/assets/javascripts/discourse/components/mount-widget.js.es6
+++ b/app/assets/javascripts/discourse/components/mount-widget.js.es6
@@ -21,6 +21,8 @@ export default Ember.Component.extend({
this._super();
const name = this.get('widget');
+ (this.get('delegated') || []).forEach(m => this.set(m, m));
+
this._widgetClass = queryRegistry(name) || this.container.lookupFactory(`widget:${name}`);
if (!this._widgetClass) {
diff --git a/app/assets/javascripts/discourse/components/show-popup-button.js.es6 b/app/assets/javascripts/discourse/components/show-popup-button.js.es6
deleted file mode 100644
index 0f1a40734..000000000
--- a/app/assets/javascripts/discourse/components/show-popup-button.js.es6
+++ /dev/null
@@ -1,17 +0,0 @@
-import DButton from 'discourse/components/d-button';
-
-export default DButton.extend({
- click() {
- const $target = this.$(),
- position = $target.position(),
- width = $target.innerWidth(),
- loc = {
- position: this.get('position') || "fixed",
- left: position.left + width,
- top: position.top
- };
-
- this.appEvents.trigger("popup-menu:open", loc);
- this.sendAction("action");
- }
-});
diff --git a/app/assets/javascripts/discourse/components/topic-admin-menu-button.js.es6 b/app/assets/javascripts/discourse/components/topic-admin-menu-button.js.es6
new file mode 100644
index 000000000..732e70beb
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/topic-admin-menu-button.js.es6
@@ -0,0 +1,10 @@
+import MountWidget from 'discourse/components/mount-widget';
+
+export default MountWidget.extend({
+ tagName: 'span',
+ widget: "topic-admin-menu-button",
+
+ buildArgs() {
+ return this.getProperties('topic', 'fixed');
+ }
+});
diff --git a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6
index 5fba8abcd..1b41ae04d 100644
--- a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6
@@ -1,16 +1,15 @@
import ContainerView from 'discourse/views/container';
-import { on } from 'ember-addons/ember-computed-decorators';
export default ContainerView.extend({
elementId: 'topic-footer-buttons',
- @on('init')
- createButtons() {
- const topic = this.get('topic');
- const currentUser = this.get('controller.currentUser');
+ init() {
+ this._super();
+
+ if (this.currentUser) {
+ const viewArgs = this.getProperties('topic', 'topicDelegated');
+ viewArgs.currentUser = this.currentUser;
- if (currentUser) {
- const viewArgs = { topic, currentUser };
this.attachViewWithArgs(viewArgs, 'topic-footer-main-buttons');
this.attachViewWithArgs(viewArgs, 'pinned-button');
this.attachViewWithArgs(viewArgs, 'topic-notifications-button');
diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6
index ab614e9db..7e67aa00c 100644
--- a/app/assets/javascripts/discourse/controllers/topic.js.es6
+++ b/app/assets/javascripts/discourse/controllers/topic.js.es6
@@ -17,13 +17,28 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
selectedPosts: null,
selectedReplies: null,
queryParams: ['filter', 'username_filters', 'show_deleted'],
- loadedAllPosts: Em.computed.or('model.postStream.loadedAllPosts', 'model.postStream.loadingLastPost'),
+ loadedAllPosts: Ember.computed.or('model.postStream.loadedAllPosts', 'model.postStream.loadingLastPost'),
enteredAt: null,
retrying: false,
- adminMenuVisible: false,
- showRecover: Em.computed.and('model.deleted', 'model.details.can_recover'),
- isFeatured: Em.computed.or("model.pinned_at", "model.isBanner"),
+ topicDelegated: [
+ 'toggleMultiSelect',
+ 'deleteTopic',
+ 'recoverTopic',
+ 'toggleClosed',
+ 'showAutoClose',
+ 'showFeatureTopic',
+ 'showChangeTimestamp',
+ 'toggleArchived',
+ 'toggleVisibility',
+ 'convertToPublicTopic',
+ 'convertToPrivateMessage',
+ 'jumpTop',
+ 'jumpToPost',
+ 'jumpToIndex',
+ 'jumpBottom',
+ 'replyToPost'
+ ],
@computed
showTimeline() {
@@ -237,14 +252,6 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
return this.get('model.details').removeAllowedUser(user);
},
- showTopicAdminMenu() {
- this.set('adminMenuVisible', true);
- },
-
- hideTopicAdminMenu() {
- this.set('adminMenuVisible', false);
- },
-
deleteTopic() {
this.deleteTopic();
},
diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs
index 7f307113f..cea89bd9f 100644
--- a/app/assets/javascripts/discourse/templates/topic.hbs
+++ b/app/assets/javascripts/discourse/templates/topic.hbs
@@ -72,19 +72,10 @@
{{#if showTimeline}}
- {{topic-timeline topic=model
- loading=model.postStream.loading
- jumpTop="jumpTop"
- jumpToPost="jumpToPost"
- jumpToIndex="jumpToIndex"
- jumpBottom="jumpBottom"
- replyToPost="replyToPost"}}
+ {{topic-timeline topic=model loading=model.postStream.loading delegated=topicDelegated}}
{{else}}
- {{topic-progress topic=model
- jumpTop="jumpTop"
- jumpToPost="jumpToPost"
- jumpToIndex="jumpToIndex"
- jumpBottom="jumpBottom"}}
+ {{topic-progress topic=model delegated=topicDelegated}}
+ {{topic-admin-menu-button topic=model fixed="true" delegated=topicDelegated}}
{{/if}}
{{conditional-loading-spinner condition=model.postStream.loadingAbove}}
@@ -136,7 +127,7 @@
{{! replace "Log In to Reply" with the infobox }}
{{signup-cta}}
{{else}}
- {{topic-footer-buttons topic=model}}
+ {{topic-footer-buttons topic=model topicDelegated=topicDelegated}}
{{/if}}
{{#if model.pending_posts_count}}
@@ -212,78 +203,3 @@
{{render "quote-button"}}
{{/if}}
-{{#if currentUser.canManageTopic}}
- {{show-popup-button action="showTopicAdminMenu" class="show-topic-admin" title="topic_admin_menu" icon="wrench"}}
- {{#popup-menu visible=adminMenuVisible hide="hideTopicAdminMenu" title="admin_title" extraClasses="topic-admin-popup-menu"}}
-
- {{d-button action="toggleMultiSelect" icon="tasks" label="topic.actions.multi_select"}}
-
-
- {{#if model.details.can_delete}}
-
- {{d-button action="deleteTopic" icon="trash-o" label="topic.actions.delete" class="btn-danger"}}
-
- {{/if}}
-
- {{#if showRecover}}
-
- {{d-button action="recoverTopic" icon="undo" label="topic.actions.recover"}}
-
- {{/if}}
-
-
- {{#if model.closed}}
- {{d-button action="toggleClosed" icon="unlock" label="topic.actions.open"}}
- {{else}}
- {{d-button action="toggleClosed" icon="lock" label="topic.actions.close"}}
- {{d-button action="showAutoClose" icon="clock-o" label="topic.actions.auto_close"}}
- {{/if}}
-
-
- {{#unless model.isPrivateMessage}}
- {{#if model.visible}}
-
- {{#if isFeatured}}
- {{d-button action="showFeatureTopic" icon="thumb-tack" label="topic.actions.unpin"}}
- {{else}}
- {{d-button action="showFeatureTopic" icon="thumb-tack" label="topic.actions.pin"}}
- {{/if}}
-
- {{/if}}
- {{/unless}}
-
-
- {{d-button action="showChangeTimestamp" icon="calendar" label="topic.change_timestamp.title"}}
-
-
-
- {{#if model.archived}}
- {{d-button action="toggleArchived" icon="folder" label="topic.actions.unarchive"}}
- {{else}}
- {{#unless model.isPrivateMessage}}
- {{d-button action="toggleArchived" icon="folder" label="topic.actions.archive"}}
- {{/unless}}
- {{/if}}
-
-
-
- {{#if model.visible}}
- {{d-button action="toggleVisibility" icon="eye-slash" label="topic.actions.invisible"}}
- {{else}}
- {{d-button action="toggleVisibility" icon="eye" label="topic.actions.visible"}}
- {{/if}}
-
-
- {{#if currentUser.staff}}
-
- {{#if model.isPrivateMessage}}
- {{d-button action="convertToPublicTopic" icon="comment" label="topic.actions.make_public"}}
- {{else}}
- {{d-button action="convertToPrivateMessage" icon="envelope" label="topic.actions.make_private"}}
- {{/if}}
-
- {{/if}}
-
- {{plugin-outlet "topic-admin-menu-buttons"}}
- {{/popup-menu}}
-{{/if}}
diff --git a/app/assets/javascripts/discourse/views/topic-footer-main-buttons.js.es6 b/app/assets/javascripts/discourse/views/topic-footer-main-buttons.js.es6
index 8852d3558..25a7e8a63 100644
--- a/app/assets/javascripts/discourse/views/topic-footer-main-buttons.js.es6
+++ b/app/assets/javascripts/discourse/views/topic-footer-main-buttons.js.es6
@@ -8,12 +8,13 @@ export default ContainerView.extend({
createButtons() {
const mobileView = this.site.mobileView;
+ const topic = this.get('topic');
+
if (!mobileView && this.currentUser.get('staff')) {
- const viewArgs = {action: 'showTopicAdminMenu', title: 'topic_admin_menu', icon: 'wrench', position: 'absolute'};
- this.attachViewWithArgs(viewArgs, 'show-popup-button');
+ const viewArgs = { topic, delegated: this.get('topicDelegated') };
+ this.attachViewWithArgs(viewArgs, 'topic-admin-menu-button');
}
- const topic = this.get('topic');
if (!topic.get('isPrivateMessage')) {
if (mobileView) {
this.attachViewWithArgs({ topic }, 'topic-footer-mobile-dropdown');
diff --git a/app/assets/javascripts/discourse/widgets/button.js.es6 b/app/assets/javascripts/discourse/widgets/button.js.es6
index 656fc4c1e..e9fd29d70 100644
--- a/app/assets/javascripts/discourse/widgets/button.js.es6
+++ b/app/assets/javascripts/discourse/widgets/button.js.es6
@@ -40,11 +40,18 @@ export default createWidget('button', {
return contents;
},
- click() {
+ click(e) {
const attrs = this.attrs;
if (attrs.disabled) { return; }
$(`button.widget-button`).removeClass('d-hover').blur();
+ if (attrs.secondaryAction) {
+ this.sendWidgetAction(attrs.secondaryAction);
+ }
+
+ if (attrs.sendActionEvent) {
+ return this.sendWidgetAction(attrs.action, e);
+ }
return this.sendWidgetAction(attrs.action);
}
});
diff --git a/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6 b/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
new file mode 100644
index 000000000..29460ed92
--- /dev/null
+++ b/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
@@ -0,0 +1,160 @@
+import { createWidget, applyDecorators } from 'discourse/widgets/widget';
+import { h } from 'virtual-dom';
+
+createWidget('admin-menu-button', {
+ html(attrs) {
+ let className = 'btn';
+ if (attrs.buttonClass) { className += ' ' + attrs.buttonClass; }
+
+ return h('li', { className: attrs.className }, this.attach('button', {
+ className,
+ action: attrs.action,
+ icon: attrs.icon,
+ label: `topic.${attrs.label}`,
+ secondaryAction: 'hideAdminMenu'
+ }));
+ }
+});
+
+createWidget('topic-admin-menu-button', {
+ tagName: 'span',
+ buildKey: () => `topic-admin-menu-button`,
+
+ defaultState() {
+ return { expanded: false, position: null };
+ },
+
+ html(attrs, state) {
+ if (!this.currentUser || !this.currentUser.get('canManageTopic')) { return; }
+
+ const result = [];
+ result.push(this.attach('button', {
+ className: 'btn no-text' + (attrs.fixed ? " show-topic-admin" : ""),
+ title: 'topic_admin_menu',
+ icon: 'wrench',
+ action: 'showAdminMenu',
+ sendActionEvent: true
+ }));
+
+ if (state.expanded) {
+ result.push(this.attach('topic-admin-menu', { position: state.position,
+ fixed: attrs.fixed,
+ topic: attrs.topic }));
+ }
+
+ return result;
+ },
+
+ hideAdminMenu() {
+ this.state.expanded = false;
+ this.state.position = null;
+ },
+
+ showAdminMenu(e) {
+ this.state.expanded = true;
+
+ const $button = $(e.target).closest('button');
+ const position = $button.position();
+ position.left = position.left;
+
+ if (this.attrs.fixed) {
+ position.left += $button.width() - 203;
+ }
+ this.state.position = position;
+ }
+});
+
+export default createWidget('topic-admin-menu', {
+ tagName: 'div.popup-menu.topic-admin-popup-menu',
+
+ buildAttributes(attrs) {
+ const { top, left } = attrs.position;
+ const position = attrs.fixed ? 'fixed' : 'absolute';
+
+ return { style: `position: ${position}; top: ${top}px; left: ${left}px;` };
+ },
+
+ html(attrs) {
+ const buttons = [];
+ buttons.push({ className: 'topic-admin-multi-select',
+ action: 'toggleMultiSelect',
+ icon: 'tasks',
+ label: 'actions.multi_select' });
+
+ const topic = attrs.topic;
+ const details = topic.get('details');
+ if (details.get('can_delete')) {
+ buttons.push({ className: 'topic-admin-delete',
+ buttonClass: 'btn-danger',
+ action: 'deleteTopic',
+ icon: 'trash-o',
+ label: 'actions.delete' });
+ }
+
+ if (topic.get('deleted') && details.get('can_recover')) {
+ buttons.push({ className: 'topic-admin-recover',
+ action: 'recoverTopic',
+ icon: 'undo',
+ label: 'actions.recover' });
+ }
+
+ if (topic.get('closed')) {
+ buttons.push({ className: 'topic-admin-open',
+ action: 'toggleClosed',
+ icon: 'unlock',
+ label: 'actions.open' });
+ } else {
+ buttons.push({ className: 'topic-admin-close',
+ action: 'toggleClosed',
+ icon: 'lock',
+ label: 'actions.close' });
+ buttons.push({ className: 'topic-admin-autoclose',
+ action: 'showAutoClose',
+ icon: 'clock-o',
+ label: 'actions.auto_close' });
+ }
+
+ const isPrivateMessage = topic.get('isPrivateMessage');
+
+ if (!isPrivateMessage && topic.get('visible')) {
+ const featured = topic.get('pinned_at') || topic.get('isBanner');
+ buttons.push({ className: 'topic-admin-pin',
+ action: 'showFeatureTopic',
+ icon: 'thumb-tack',
+ label: featured ? 'actions.unpin' : 'actions.pin' });
+ }
+ buttons.push({ className: 'topic-admin-change-timestamp',
+ action: 'showChangeTimestamp',
+ icon: 'calendar',
+ label: 'change_timestamp.title' });
+
+ if (!isPrivateMessage) {
+ buttons.push({ className: 'topic-admin-archive',
+ action: 'toggleArchived',
+ icon: 'folder',
+ label: topic.get('archived') ? 'actions.unarchive' : 'actions.archive' });
+ }
+
+ const visible = topic.get('visible');
+ buttons.push({ className: 'topic-admin-visible',
+ action: 'toggleVisibility',
+ icon: visible ? 'eye' : 'eye-slash',
+ label: visible ? 'actions.invisible' : 'actions.visible' });
+
+ if (this.currentUser.get('staff')) {
+ buttons.push({ className: 'topic-admin-convert',
+ action: isPrivateMessage ? 'convertToPublicTopic' : 'convertToPrivateMessage',
+ icon: isPrivateMessage ? 'comment' : 'envelope',
+ label: isPrivateMessage ? 'actions.make_public' : 'actions.make_private' });
+ }
+
+ const extraButtons = applyDecorators(this, 'adminMenuButtons', this.attrs, this.state);
+
+ return [ h('h3', I18n.t('admin_title')),
+ h('ul', buttons.concat(extraButtons).map(b => this.attach('admin-menu-button', b))) ];
+ },
+
+ clickOutside() {
+ this.sendWidgetAction('hideAdminMenu');
+ }
+});
diff --git a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6
index 3cd19939d..2ab684a14 100644
--- a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6
+++ b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6
@@ -210,6 +210,10 @@ export default createWidget('topic-timeline', {
}));
}
+ if (this.currentUser.get('canManageTopic')) {
+ controls.push(this.attach('topic-admin-menu-button', { topic }));
+ }
+
const result = [ h('div.timeline-controls', controls) ];
const stream = attrs.topic.get('postStream.stream');
if (stream.length > 2) {
diff --git a/app/assets/stylesheets/desktop/topic-timeline.scss b/app/assets/stylesheets/desktop/topic-timeline.scss
index 92a9b4fa6..9a82d6194 100644
--- a/app/assets/stylesheets/desktop/topic-timeline.scss
+++ b/app/assets/stylesheets/desktop/topic-timeline.scss
@@ -19,6 +19,10 @@
.timeline-controls {
margin-bottom: 2em;
+
+ button {
+ margin-right: 0.5em;
+ }
}
.start-date {