Refactored SiteMap/Header to support more dynamic flag counts
Also fixes deprecation in `plugin-outlet`
This commit is contained in:
parent
dece5a351a
commit
7c9fb5d3fc
|
@ -1,6 +1,6 @@
|
||||||
import DiscourseController from 'discourse/controllers/controller';
|
import DiscourseController from 'discourse/controllers/controller';
|
||||||
|
|
||||||
export default DiscourseController.extend({
|
const HeaderController = DiscourseController.extend({
|
||||||
topic: null,
|
topic: null,
|
||||||
showExtraInfo: null,
|
showExtraInfo: null,
|
||||||
notifications: null,
|
notifications: null,
|
||||||
|
@ -18,9 +18,9 @@ export default DiscourseController.extend({
|
||||||
return Discourse.User.current() && !this.get('topic.isPrivateMessage');
|
return Discourse.User.current() && !this.get('topic.isPrivateMessage');
|
||||||
}.property('topic.isPrivateMessage'),
|
}.property('topic.isPrivateMessage'),
|
||||||
|
|
||||||
_resetCachedNotifications: function(){
|
_resetCachedNotifications: function() {
|
||||||
// a bit hacky, but if we have no focus, hide notifications first
|
// a bit hacky, but if we have no focus, hide notifications first
|
||||||
var visible = $("#notifications-dropdown").is(":visible");
|
const visible = $("#notifications-dropdown").is(":visible");
|
||||||
|
|
||||||
if(!Discourse.get("hasFocus")) {
|
if(!Discourse.get("hasFocus")) {
|
||||||
if(visible){
|
if(visible){
|
||||||
|
@ -37,7 +37,7 @@ export default DiscourseController.extend({
|
||||||
}.observes("currentUser.lastNotificationChange"),
|
}.observes("currentUser.lastNotificationChange"),
|
||||||
|
|
||||||
refreshNotifications: function(){
|
refreshNotifications: function(){
|
||||||
var self = this;
|
const self = this;
|
||||||
if (self.get("loadingNotifications")) { return; }
|
if (self.get("loadingNotifications")) { return; }
|
||||||
|
|
||||||
self.set("loadingNotifications", true);
|
self.set("loadingNotifications", true);
|
||||||
|
@ -56,14 +56,14 @@ export default DiscourseController.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleStar: function() {
|
toggleStar() {
|
||||||
var topic = this.get('topic');
|
const topic = this.get('topic');
|
||||||
if (topic) topic.toggleStar();
|
if (topic) topic.toggleStar();
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
showNotifications: function(headerView) {
|
showNotifications(headerView) {
|
||||||
var self = this;
|
const self = this;
|
||||||
|
|
||||||
if (self.get('currentUser.unread_notifications') || self.get('currentUser.unread_private_messages') || !self.get('notifications')) {
|
if (self.get('currentUser.unread_notifications') || self.get('currentUser.unread_private_messages') || !self.get('notifications')) {
|
||||||
self.refreshNotifications();
|
self.refreshNotifications();
|
||||||
|
@ -71,5 +71,34 @@ export default DiscourseController.extend({
|
||||||
headerView.showDropdownBySelector("#user-notifications");
|
headerView.showDropdownBySelector("#user-notifications");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Allow plugins to add to the sum of "flags" above the site map
|
||||||
|
const _flagProperties = [];
|
||||||
|
function addFlagProperty(prop) {
|
||||||
|
_flagProperties.pushObject(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _appliedFlagProps = false;
|
||||||
|
HeaderController.reopenClass({
|
||||||
|
create() {
|
||||||
|
// We only want to change the class the first time it's created
|
||||||
|
if (!_appliedFlagProps && _flagProperties.length) {
|
||||||
|
_appliedFlagProps = true;
|
||||||
|
|
||||||
|
const args = _flagProperties.slice();
|
||||||
|
args.push(function() {
|
||||||
|
let sum = 0;
|
||||||
|
_flagProperties.forEach((fp) => sum += (this.get(fp) || 0));
|
||||||
|
return sum;
|
||||||
|
});
|
||||||
|
HeaderController.reopen({ flaggedPostsCount: Ember.computed.apply(this, args) });
|
||||||
|
}
|
||||||
|
return this._super.apply(this, Array.prototype.slice.call(arguments));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addFlagProperty('currentUser.site_flagged_posts_count');
|
||||||
|
|
||||||
|
export { addFlagProperty };
|
||||||
|
export default HeaderController;
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
export default Ember.ArrayController.extend({
|
export default Ember.ArrayController.extend({
|
||||||
needs: ['application'],
|
needs: ['application', 'header'],
|
||||||
|
|
||||||
showBadgesLink: function(){return Discourse.SiteSettings.enable_badges;}.property(),
|
showBadgesLink: function(){return Discourse.SiteSettings.enable_badges;}.property(),
|
||||||
showAdminLinks: Em.computed.alias('currentUser.staff'),
|
showAdminLinks: Em.computed.alias('currentUser.staff'),
|
||||||
flaggedPostsCount: Em.computed.alias("currentUser.site_flagged_posts_count"),
|
|
||||||
|
|
||||||
faqUrl: function() {
|
faqUrl: function() {
|
||||||
return Discourse.SiteSettings.faq_url ? Discourse.SiteSettings.faq_url : Discourse.getURL('/faq');
|
return Discourse.SiteSettings.faq_url ? Discourse.SiteSettings.faq_url : Discourse.getURL('/faq');
|
||||||
|
|
|
@ -47,25 +47,24 @@
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var _connectorCache;
|
let _connectorCache;
|
||||||
|
|
||||||
function findOutlets(collection, callback) {
|
function findOutlets(collection, callback) {
|
||||||
|
|
||||||
var disabledPlugins = Discourse.Site.currentProp('disabled_plugins') || [];
|
const disabledPlugins = Discourse.Site.currentProp('disabled_plugins') || [];
|
||||||
|
|
||||||
Ember.keys(collection).forEach(function(res) {
|
Ember.keys(collection).forEach(function(res) {
|
||||||
if (res.indexOf("/connectors/") !== -1) {
|
if (res.indexOf("/connectors/") !== -1) {
|
||||||
// Skip any disabled plugins
|
// Skip any disabled plugins
|
||||||
for (var i=0; i<disabledPlugins.length; i++) {
|
for (let i=0; i<disabledPlugins.length; i++) {
|
||||||
if (res.indexOf("/" + disabledPlugins[i] + "/") !== -1) {
|
if (res.indexOf("/" + disabledPlugins[i] + "/") !== -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var segments = res.split("/"),
|
const segments = res.split("/"),
|
||||||
outletName = segments[segments.length-2],
|
outletName = segments[segments.length-2],
|
||||||
uniqueName = segments[segments.length-1];
|
uniqueName = segments[segments.length-1];
|
||||||
|
|
||||||
|
|
||||||
callback(outletName, res, uniqueName);
|
callback(outletName, res, uniqueName);
|
||||||
}
|
}
|
||||||
|
@ -75,11 +74,11 @@ function findOutlets(collection, callback) {
|
||||||
function buildConnectorCache() {
|
function buildConnectorCache() {
|
||||||
_connectorCache = {};
|
_connectorCache = {};
|
||||||
|
|
||||||
var uniqueViews = {};
|
const uniqueViews = {};
|
||||||
findOutlets(requirejs._eak_seen, function(outletName, resource, uniqueName) {
|
findOutlets(requirejs._eak_seen, function(outletName, resource, uniqueName) {
|
||||||
_connectorCache[outletName] = _connectorCache[outletName] || [];
|
_connectorCache[outletName] = _connectorCache[outletName] || [];
|
||||||
|
|
||||||
var viewClass = require(resource, null, null, true).default;
|
const viewClass = require(resource, null, null, true).default;
|
||||||
uniqueViews[uniqueName] = viewClass;
|
uniqueViews[uniqueName] = viewClass;
|
||||||
_connectorCache[outletName].pushObject(viewClass);
|
_connectorCache[outletName].pushObject(viewClass);
|
||||||
});
|
});
|
||||||
|
@ -87,8 +86,8 @@ function buildConnectorCache() {
|
||||||
findOutlets(Ember.TEMPLATES, function(outletName, resource, uniqueName) {
|
findOutlets(Ember.TEMPLATES, function(outletName, resource, uniqueName) {
|
||||||
_connectorCache[outletName] = _connectorCache[outletName] || [];
|
_connectorCache[outletName] = _connectorCache[outletName] || [];
|
||||||
|
|
||||||
var mixin = {templateName: resource.replace('javascripts/', '')},
|
const mixin = {templateName: resource.replace('javascripts/', '')};
|
||||||
viewClass = uniqueViews[uniqueName];
|
let viewClass = uniqueViews[uniqueName];
|
||||||
|
|
||||||
if (viewClass) {
|
if (viewClass) {
|
||||||
// We are going to add it back with the proper template
|
// We are going to add it back with the proper template
|
||||||
|
@ -104,21 +103,24 @@ export default function(connectionName, options) {
|
||||||
if (!_connectorCache) { buildConnectorCache(); }
|
if (!_connectorCache) { buildConnectorCache(); }
|
||||||
|
|
||||||
if (_connectorCache[connectionName]) {
|
if (_connectorCache[connectionName]) {
|
||||||
var viewClass;
|
const childViews = _connectorCache[connectionName];
|
||||||
var childViews = _connectorCache[connectionName];
|
|
||||||
|
|
||||||
// If there is more than one view, create a container. Otherwise
|
// If there is more than one view, create a container. Otherwise
|
||||||
// just shove it in.
|
// just shove it in.
|
||||||
if (childViews.length > 1) {
|
const viewClass = (childViews.length > 1) ? Ember.ContainerView : childViews[0];
|
||||||
viewClass = Ember.ContainerView.extend({
|
|
||||||
childViews: childViews
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
viewClass = childViews[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
delete options.fn; // we don't need the default template since we have a connector
|
delete options.fn; // we don't need the default template since we have a connector
|
||||||
return Ember.Handlebars.helpers.view.call(this, viewClass, options);
|
Ember.Handlebars.helpers.view.call(this, viewClass, options);
|
||||||
|
|
||||||
|
const cvs = options.data.view._childViews;
|
||||||
|
if (childViews.length > 1 && cvs && cvs.length) {
|
||||||
|
const inserted = cvs[cvs.length-1];
|
||||||
|
if (inserted) {
|
||||||
|
childViews.forEach(function(cv) {
|
||||||
|
inserted.pushObject(cv.create());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (options.fn) {
|
} else if (options.fn) {
|
||||||
// If a block is passed, render its content.
|
// If a block is passed, render its content.
|
||||||
return Ember.Handlebars.helpers.view.call(this,
|
return Ember.Handlebars.helpers.view.call(this,
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
var _decorateId = 0;
|
let _decorateId = 0;
|
||||||
|
|
||||||
function decorate(klass, evt, cb) {
|
function decorate(klass, evt, cb) {
|
||||||
var mixin = {};
|
const mixin = {};
|
||||||
mixin["_decorate_" + (_decorateId++)] = function($elem) { cb($elem); }.on(evt);
|
mixin["_decorate_" + (_decorateId++)] = function($elem) { cb($elem); }.on(evt);
|
||||||
klass.reopen(mixin);
|
klass.reopen(mixin);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decorateCooked(container, cb) {
|
export function decorateCooked(container, cb) {
|
||||||
var postView = container.lookupFactory('view:post');
|
const postView = container.lookupFactory('view:post');
|
||||||
decorate(postView, 'postViewInserted', cb);
|
decorate(postView, 'postViewInserted', cb);
|
||||||
decorate(postView, 'postViewUpdated', cb);
|
decorate(postView, 'postViewUpdated', cb);
|
||||||
decorate(container.lookupFactory('view:composer'), 'previewRefreshed', cb);
|
decorate(container.lookupFactory('view:composer'), 'previewRefreshed', cb);
|
||||||
|
|
|
@ -52,14 +52,14 @@
|
||||||
<a class='icon'
|
<a class='icon'
|
||||||
data-dropdown="site-map-dropdown"
|
data-dropdown="site-map-dropdown"
|
||||||
data-render="renderSiteMap"
|
data-render="renderSiteMap"
|
||||||
href="#"
|
href
|
||||||
title='{{i18n 'site_map'}}'
|
title='{{i18n 'site_map'}}'
|
||||||
id="site-map">
|
id="site-map">
|
||||||
{{fa-icon "bars" label="site_map"}}
|
{{fa-icon "bars" label="site_map"}}
|
||||||
</a>
|
</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if currentUser.site_flagged_posts_count}}
|
{{#if flaggedPostsCount}}
|
||||||
<a href='/admin/flags/active' title='{{i18n 'notifications.total_flagged'}}' class='badge-notification flagged-posts'>{{currentUser.site_flagged_posts_count}}</a>
|
<a href='/admin/flags/active' title='{{i18n 'notifications.total_flagged'}}' class='badge-notification flagged-posts'>{{flaggedPostsCount}}</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</li>
|
</li>
|
||||||
{{#if currentUser}}
|
{{#if currentUser}}
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/flags/active" class="flagged-posts-link">
|
<a href="/admin/flags/active" class="flagged-posts-link">
|
||||||
<i class='fa fa-flag'></i> {{i18n 'flags_title'}}
|
{{fa-icon "flag"}} {{i18n 'flags_title'}}
|
||||||
{{#if flaggedPostsCount}}
|
{{#if currentUser.site_flagged_posts_count}}
|
||||||
<span title='{{i18n 'notifications.total_flagged'}}' class='badge-notification flagged-posts'>{{flaggedPostsCount}}</span>
|
<span title='{{i18n 'notifications.total_flagged'}}' class='badge-notification flagged-posts'>{{currentUser.site_flagged_posts_count}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,13 +1,4 @@
|
||||||
/**
|
let originalZIndex;
|
||||||
This view handles rendering of the header of the site
|
|
||||||
|
|
||||||
@class HeaderView
|
|
||||||
@extends Discourse.View
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
|
|
||||||
var originalZIndex;
|
|
||||||
|
|
||||||
export default Discourse.View.extend({
|
export default Discourse.View.extend({
|
||||||
tagName: 'header',
|
tagName: 'header',
|
||||||
|
@ -19,7 +10,7 @@ export default Discourse.View.extend({
|
||||||
showDropdown: function($target) {
|
showDropdown: function($target) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(!this.get("renderDropdowns")){
|
if (!this.get("renderDropdowns")) {
|
||||||
this.set("renderDropdowns", true);
|
this.set("renderDropdowns", true);
|
||||||
Em.run.next(function(){
|
Em.run.next(function(){
|
||||||
self.showDropdown($target);
|
self.showDropdown($target);
|
||||||
|
@ -27,30 +18,29 @@ export default Discourse.View.extend({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var elementId = $target.data('dropdown') || $target.data('notifications'),
|
const elementId = $target.data('dropdown') || $target.data('notifications'),
|
||||||
$dropdown = $("#" + elementId),
|
$dropdown = $("#" + elementId),
|
||||||
$li = $target.closest('li'),
|
$li = $target.closest('li'),
|
||||||
$ul = $target.closest('ul'),
|
$ul = $target.closest('ul'),
|
||||||
$html = $('html'),
|
$html = $('html'),
|
||||||
$header = $('header'),
|
$header = $('header'),
|
||||||
replyZIndex = parseInt($('#reply-control').css('z-index'), 10);
|
replyZIndex = parseInt($('#reply-control').css('z-index'), 10);
|
||||||
|
|
||||||
|
|
||||||
originalZIndex = originalZIndex || $('header').css('z-index');
|
originalZIndex = originalZIndex || $('header').css('z-index');
|
||||||
|
|
||||||
if(replyZIndex > 0) {
|
if (replyZIndex > 0) {
|
||||||
$header.css("z-index", replyZIndex + 1);
|
$header.css("z-index", replyZIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var controller = self.get('controller');
|
const controller = self.get('controller');
|
||||||
if(controller && !controller.isDestroyed){
|
if (controller && !controller.isDestroyed) {
|
||||||
controller.set('visibleDropdown', elementId);
|
controller.set('visibleDropdown', elementId);
|
||||||
}
|
}
|
||||||
// we need to ensure we are rendered,
|
// we need to ensure we are rendered,
|
||||||
// this optimises the speed of the initial render
|
// this optimises the speed of the initial render
|
||||||
var render = $target.data('render');
|
const render = $target.data('render');
|
||||||
if(render){
|
if (render){
|
||||||
if(!this.get(render)){
|
if (!this.get(render)){
|
||||||
this.set(render, true);
|
this.set(render, true);
|
||||||
Em.run.next(this, function(){
|
Em.run.next(this, function(){
|
||||||
this.showDropdown.apply(self, [$target]);
|
this.showDropdown.apply(self, [$target]);
|
||||||
|
@ -59,20 +49,21 @@ export default Discourse.View.extend({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hideDropdown = function() {
|
const hideDropdown = function() {
|
||||||
$header.css("z-index", originalZIndex);
|
$header.css("z-index", originalZIndex);
|
||||||
$dropdown.fadeOut('fast');
|
$dropdown.fadeOut('fast');
|
||||||
$li.removeClass('active');
|
$li.removeClass('active');
|
||||||
$html.data('hide-dropdown', null);
|
$html.data('hide-dropdown', null);
|
||||||
var controller = self.get('controller');
|
|
||||||
if(controller && !controller.isDestroyed){
|
const controller = self.get('controller');
|
||||||
|
if (controller && !controller.isDestroyed){
|
||||||
controller.set('visibleDropdown', null);
|
controller.set('visibleDropdown', null);
|
||||||
}
|
}
|
||||||
return $html.off('click.d-dropdown');
|
$html.off('click.d-dropdown');
|
||||||
};
|
};
|
||||||
|
|
||||||
// if a dropdown is active and the user clicks on it, close it
|
// if a dropdown is active and the user clicks on it, close it
|
||||||
if($li.hasClass('active')) { return hideDropdown(); }
|
if ($li.hasClass('active')) { return hideDropdown(); }
|
||||||
// otherwhise, mark it as active
|
// otherwhise, mark it as active
|
||||||
$li.addClass('active');
|
$li.addClass('active');
|
||||||
// hide the other dropdowns
|
// hide the other dropdowns
|
||||||
|
@ -129,16 +120,16 @@ export default Discourse.View.extend({
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
willDestroyElement: function() {
|
_tearDown: function() {
|
||||||
$(window).unbind('scroll.discourse-dock');
|
$(window).unbind('scroll.discourse-dock');
|
||||||
$(document).unbind('touchmove.discourse-dock');
|
$(document).unbind('touchmove.discourse-dock');
|
||||||
this.$('a.unread-private-messages, a.unread-notifications, a[data-notifications]').off('click.notifications');
|
this.$('a.unread-private-messages, a.unread-notifications, a[data-notifications]').off('click.notifications');
|
||||||
this.$('a[data-dropdown]').off('click.dropdown');
|
this.$('a[data-dropdown]').off('click.dropdown');
|
||||||
},
|
}.on('willDestroyElement'),
|
||||||
|
|
||||||
didInsertElement: function() {
|
_setup: function() {
|
||||||
|
|
||||||
var self = this;
|
const self = this;
|
||||||
|
|
||||||
this.$('a[data-dropdown]').on('click.dropdown', function(e) {
|
this.$('a[data-dropdown]').on('click.dropdown', function(e) {
|
||||||
self.showDropdown.apply(self, [$(e.currentTarget)]);
|
self.showDropdown.apply(self, [$(e.currentTarget)]);
|
||||||
|
@ -172,7 +163,5 @@ export default Discourse.View.extend({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}.on('didInsertElement')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
var oldMobileView;
|
var oldMobileView;
|
||||||
|
|
||||||
moduleFor("controller:site-map", "controller:site-map", {
|
moduleFor("controller:site-map", "controller:site-map", {
|
||||||
needs: ['controller:application'],
|
needs: ['controller:application', 'controller:header'],
|
||||||
|
|
||||||
setup: function() {
|
setup: function() {
|
||||||
oldMobileView = Discourse.Mobile.mobileView;
|
oldMobileView = Discourse.Mobile.mobileView;
|
||||||
|
@ -21,16 +21,6 @@ test("showAdminLinks", function() {
|
||||||
equal(controller.get("showAdminLinks"), false, "is false when current user is not a staff member");
|
equal(controller.get("showAdminLinks"), false, "is false when current user is not a staff member");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("flaggedPostsCount", function() {
|
|
||||||
const currentUser = Ember.Object.create({ site_flagged_posts_count: 5 });
|
|
||||||
const controller = this.subject({ currentUser });
|
|
||||||
|
|
||||||
equal(controller.get("flaggedPostsCount"), 5, "returns current user's flagged posts count");
|
|
||||||
|
|
||||||
currentUser.set("site_flagged_posts_count", 0);
|
|
||||||
equal(controller.get("flaggedPostsCount"), 0, "is bound (reacts to change of current user's flagged posts count)");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("faqUrl returns faq url configured in site settings if it is set", function() {
|
test("faqUrl returns faq url configured in site settings if it is set", function() {
|
||||||
Discourse.SiteSettings.faq_url = "faq-url";
|
Discourse.SiteSettings.faq_url = "faq-url";
|
||||||
var controller = this.subject();
|
var controller = this.subject();
|
||||||
|
|
Loading…
Reference in New Issue