Refactored SiteMap/Header to support more dynamic flag counts

Also fixes deprecation in `plugin-outlet`
This commit is contained in:
Robin Ward 2015-03-11 13:38:36 -04:00
parent dece5a351a
commit 7c9fb5d3fc
8 changed files with 98 additions and 89 deletions

View File

@ -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;

View File

@ -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');

View File

@ -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,

View File

@ -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);

View File

@ -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}}

View File

@ -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>

View File

@ -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')
}); });

View File

@ -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();