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';
export default DiscourseController.extend({
const HeaderController = DiscourseController.extend({
topic: null,
showExtraInfo: null,
notifications: null,
@ -18,9 +18,9 @@ export default DiscourseController.extend({
return Discourse.User.current() && !this.get('topic.isPrivateMessage');
}.property('topic.isPrivateMessage'),
_resetCachedNotifications: function(){
_resetCachedNotifications: function() {
// 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(visible){
@ -37,7 +37,7 @@ export default DiscourseController.extend({
}.observes("currentUser.lastNotificationChange"),
refreshNotifications: function(){
var self = this;
const self = this;
if (self.get("loadingNotifications")) { return; }
self.set("loadingNotifications", true);
@ -56,14 +56,14 @@ export default DiscourseController.extend({
},
actions: {
toggleStar: function() {
var topic = this.get('topic');
toggleStar() {
const topic = this.get('topic');
if (topic) topic.toggleStar();
return false;
},
showNotifications: function(headerView) {
var self = this;
showNotifications(headerView) {
const self = this;
if (self.get('currentUser.unread_notifications') || self.get('currentUser.unread_private_messages') || !self.get('notifications')) {
self.refreshNotifications();
@ -71,5 +71,34 @@ export default DiscourseController.extend({
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({
needs: ['application'],
needs: ['application', 'header'],
showBadgesLink: function(){return Discourse.SiteSettings.enable_badges;}.property(),
showAdminLinks: Em.computed.alias('currentUser.staff'),
flaggedPostsCount: Em.computed.alias("currentUser.site_flagged_posts_count"),
faqUrl: function() {
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) {
var disabledPlugins = Discourse.Site.currentProp('disabled_plugins') || [];
const disabledPlugins = Discourse.Site.currentProp('disabled_plugins') || [];
Ember.keys(collection).forEach(function(res) {
if (res.indexOf("/connectors/") !== -1) {
// 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) {
return;
}
}
var segments = res.split("/"),
outletName = segments[segments.length-2],
uniqueName = segments[segments.length-1];
const segments = res.split("/"),
outletName = segments[segments.length-2],
uniqueName = segments[segments.length-1];
callback(outletName, res, uniqueName);
}
@ -75,11 +74,11 @@ function findOutlets(collection, callback) {
function buildConnectorCache() {
_connectorCache = {};
var uniqueViews = {};
const uniqueViews = {};
findOutlets(requirejs._eak_seen, function(outletName, resource, uniqueName) {
_connectorCache[outletName] = _connectorCache[outletName] || [];
var viewClass = require(resource, null, null, true).default;
const viewClass = require(resource, null, null, true).default;
uniqueViews[uniqueName] = viewClass;
_connectorCache[outletName].pushObject(viewClass);
});
@ -87,8 +86,8 @@ function buildConnectorCache() {
findOutlets(Ember.TEMPLATES, function(outletName, resource, uniqueName) {
_connectorCache[outletName] = _connectorCache[outletName] || [];
var mixin = {templateName: resource.replace('javascripts/', '')},
viewClass = uniqueViews[uniqueName];
const mixin = {templateName: resource.replace('javascripts/', '')};
let viewClass = uniqueViews[uniqueName];
if (viewClass) {
// 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[connectionName]) {
var viewClass;
var childViews = _connectorCache[connectionName];
const childViews = _connectorCache[connectionName];
// If there is more than one view, create a container. Otherwise
// just shove it in.
if (childViews.length > 1) {
viewClass = Ember.ContainerView.extend({
childViews: childViews
});
} else {
viewClass = childViews[0];
}
const viewClass = (childViews.length > 1) ? Ember.ContainerView : childViews[0];
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) {
// If a block is passed, render its content.
return Ember.Handlebars.helpers.view.call(this,

View File

@ -1,13 +1,13 @@
var _decorateId = 0;
let _decorateId = 0;
function decorate(klass, evt, cb) {
var mixin = {};
const mixin = {};
mixin["_decorate_" + (_decorateId++)] = function($elem) { cb($elem); }.on(evt);
klass.reopen(mixin);
}
export function decorateCooked(container, cb) {
var postView = container.lookupFactory('view:post');
const postView = container.lookupFactory('view:post');
decorate(postView, 'postViewInserted', cb);
decorate(postView, 'postViewUpdated', cb);
decorate(container.lookupFactory('view:composer'), 'previewRefreshed', cb);

View File

@ -52,14 +52,14 @@
<a class='icon'
data-dropdown="site-map-dropdown"
data-render="renderSiteMap"
href="#"
href
title='{{i18n 'site_map'}}'
id="site-map">
{{fa-icon "bars" label="site_map"}}
</a>
{{/if}}
{{#if currentUser.site_flagged_posts_count}}
<a href='/admin/flags/active' title='{{i18n 'notifications.total_flagged'}}' class='badge-notification flagged-posts'>{{currentUser.site_flagged_posts_count}}</a>
{{#if flaggedPostsCount}}
<a href='/admin/flags/active' title='{{i18n 'notifications.total_flagged'}}' class='badge-notification flagged-posts'>{{flaggedPostsCount}}</a>
{{/if}}
</li>
{{#if currentUser}}

View File

@ -6,9 +6,9 @@
</li>
<li>
<a href="/admin/flags/active" class="flagged-posts-link">
<i class='fa fa-flag'></i> {{i18n 'flags_title'}}
{{#if flaggedPostsCount}}
<span title='{{i18n 'notifications.total_flagged'}}' class='badge-notification flagged-posts'>{{flaggedPostsCount}}</span>
{{fa-icon "flag"}} {{i18n 'flags_title'}}
{{#if currentUser.site_flagged_posts_count}}
<span title='{{i18n 'notifications.total_flagged'}}' class='badge-notification flagged-posts'>{{currentUser.site_flagged_posts_count}}</span>
{{/if}}
</a>
</li>

View File

@ -1,13 +1,4 @@
/**
This view handles rendering of the header of the site
@class HeaderView
@extends Discourse.View
@namespace Discourse
@module Discourse
**/
var originalZIndex;
let originalZIndex;
export default Discourse.View.extend({
tagName: 'header',
@ -19,7 +10,7 @@ export default Discourse.View.extend({
showDropdown: function($target) {
var self = this;
if(!this.get("renderDropdowns")){
if (!this.get("renderDropdowns")) {
this.set("renderDropdowns", true);
Em.run.next(function(){
self.showDropdown($target);
@ -27,30 +18,29 @@ export default Discourse.View.extend({
return;
}
var elementId = $target.data('dropdown') || $target.data('notifications'),
$dropdown = $("#" + elementId),
$li = $target.closest('li'),
$ul = $target.closest('ul'),
$html = $('html'),
$header = $('header'),
replyZIndex = parseInt($('#reply-control').css('z-index'), 10);
const elementId = $target.data('dropdown') || $target.data('notifications'),
$dropdown = $("#" + elementId),
$li = $target.closest('li'),
$ul = $target.closest('ul'),
$html = $('html'),
$header = $('header'),
replyZIndex = parseInt($('#reply-control').css('z-index'), 10);
originalZIndex = originalZIndex || $('header').css('z-index');
if(replyZIndex > 0) {
if (replyZIndex > 0) {
$header.css("z-index", replyZIndex + 1);
}
var controller = self.get('controller');
if(controller && !controller.isDestroyed){
const controller = self.get('controller');
if (controller && !controller.isDestroyed) {
controller.set('visibleDropdown', elementId);
}
// we need to ensure we are rendered,
// this optimises the speed of the initial render
var render = $target.data('render');
if(render){
if(!this.get(render)){
const render = $target.data('render');
if (render){
if (!this.get(render)){
this.set(render, true);
Em.run.next(this, function(){
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);
$dropdown.fadeOut('fast');
$li.removeClass('active');
$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);
}
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($li.hasClass('active')) { return hideDropdown(); }
if ($li.hasClass('active')) { return hideDropdown(); }
// otherwhise, mark it as active
$li.addClass('active');
// hide the other dropdowns
@ -129,16 +120,16 @@ export default Discourse.View.extend({
},
willDestroyElement: function() {
_tearDown: function() {
$(window).unbind('scroll.discourse-dock');
$(document).unbind('touchmove.discourse-dock');
this.$('a.unread-private-messages, a.unread-notifications, a[data-notifications]').off('click.notifications');
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) {
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;
moduleFor("controller:site-map", "controller:site-map", {
needs: ['controller:application'],
needs: ['controller:application', 'controller:header'],
setup: function() {
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");
});
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() {
Discourse.SiteSettings.faq_url = "faq-url";
var controller = this.subject();