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 {