Use of Blueimp Gallery + Top Navigation Pjax handling + User Follow Api implementation + Stream sort fix + some tests.

This commit is contained in:
buddha87 2016-11-06 17:24:20 +01:00
parent ac1fe194b9
commit 9c82fb1bba
69 changed files with 1126 additions and 492 deletions

1
css/blueimp-gallery.min.css vendored Normal file

File diff suppressed because one or more lines are too long

BIN
img/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

5
img/error.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="64" height="64">
<circle cx="32" cy="32" r="25" stroke="red" stroke-width="7" fill="black" fill-opacity="0.2"/>
<rect x="28" y="7" width="8" height="50" fill="red" transform="rotate(45, 32, 32)"/>
</svg>

After

Width:  |  Height:  |  Size: 306 B

BIN
img/loading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
img/play-pause.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

6
img/play-pause.svg Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="30" height="15">
<polygon points="2,1 2,14 13,7" stroke="black" stroke-width="1" fill="white"/>
<rect x="17" y="2" width="4" height="11" stroke="black" stroke-width="1" fill="white"/>
<rect x="24" y="2" width="4" height="11" stroke="black" stroke-width="1" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 382 B

BIN
img/video-play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

5
img/video-play.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="64" height="64">
<circle cx="32" cy="32" r="25" stroke="white" stroke-width="7" fill="black" fill-opacity="0.2"/>
<polygon points="26,22 26,42 43,32" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 274 B

3
js/blueimp-gallery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,15 +1,16 @@
/**
* Thid module can be used by humhub sub modules for registering handlers and serves as core module for executing actions triggered in the gui.
* A module can either register global handler by using the registerHandler and registerAjaxHandler functions or use the content mechanism.
* A module can either register global handler by using the registerHandler functions or use the component mechanism.
*/
humhub.initModule('action', function (module, require, $) {
var _handler = {};
var object = require('util').object;
var string = require('util').string;
var client = require('client');
var loader = require('ui.loader');
module.initOnPjaxLoad = false;
var BLOCK_NONE = 'none';
var BLOCK_SYNC = 'sync';
var BLOCK_ASYNC = 'async';
var DATA_COMPONENT = 'action-component';
var DATA_COMPONENT_SELECTOR = '[data-' + DATA_COMPONENT + ']';
@ -71,6 +72,10 @@ humhub.initModule('action', function (module, require, $) {
var componentType = $componentRoot.data(DATA_COMPONENT);
if(!componentType) {
return;
}
var ComponentType = require(componentType);
if (ComponentType) {
return new ComponentType($componentRoot);
@ -86,24 +91,13 @@ humhub.initModule('action', function (module, require, $) {
/**
* Handles the given componentAction event. The event should provide the following properties:
*
* $trigger (required) : the trigger node of the event
* handler (required) : the handler functionn name to be executed on the component
* type (optoinal) : the event type 'click', 'change',...
*
* @param {object} event - event object
* @returns {Boolean} true if the componentAction could be executed else false
*/
Component.handleAction = function (event) {
var $trigger = event.$trigger;
var component;
if($trigger.data('action-target')) {
component = Component.getInstance($($trigger.data('action-target')));
} else {
component = Component.getInstance(event.$trigger);
}
var component = Component.getInstance(event.$target);
if (component) {
//Check if the content instance provides this actionhandler
if (event.handler && component[event.handler]) {
@ -117,11 +111,13 @@ humhub.initModule('action', function (module, require, $) {
/**
* Constructor for initializing the module.
*/
module.init = function () {
//Binding default action types
this.bindAction(document, 'click', '[data-action-click]');
this.bindAction(document, 'dblclick', '[data-action-dblclick]');
this.bindAction(document, 'change', '[data-action-mouseout]');
var init = function ($isPjax) {
if(!$isPjax) {
//Binding default action types
this.bindAction(document, 'click', '[data-action-click]');
this.bindAction(document, 'dblclick', '[data-action-dblclick]');
this.bindAction(document, 'change', '[data-action-change]');
}
updateBindings();
};
@ -139,7 +135,7 @@ humhub.initModule('action', function (module, require, $) {
* @param {function} handler function with one event argument
* @returns {undefined}
*/
module.registerHandler = function (id, handler) {
var registerHandler = function (id, handler) {
if (!id) {
return;
}
@ -149,45 +145,6 @@ humhub.initModule('action', function (module, require, $) {
}
};
/**
* Registers an ajax eventhandler.
* The function can either be called with four arguments (id, successhandler, errorhandler, additional config)
* or with two (id, cfg) where tha handlers are contained in the config object itself.
*
* The successhandler will be called only if the response does not contain any errors or errormessages.
* So the errorhandler is called for application and http errors.
*
* The config can contain additional ajax settings.
*
* @param {type} id
* @param {type} success
* @param {type} error
* @param {type} cfg
* @returns {undefined}
*/
module.registerAjaxHandler = function (id, success, error, cfg) {
cfg = cfg || {};
if (!id) {
return;
}
if (object.isFunction(success)) {
cfg.success = success;
cfg.error = error;
} else {
cfg = success;
}
if (success) {
_handler[id] = function (event) {
var path = $(this).data('action-url-' + event.type) || $(this).data('action-url');
client.ajax(path, cfg);
};
}
};
var actionBindings = [];
var updateBindings = function () {
@ -198,7 +155,7 @@ humhub.initModule('action', function (module, require, $) {
module.log.debug('Handle direct trigger action', evt);
return binding.handle(evt, $(this));
});
$targets.data('action-'+binding.event, true);
$targets.data('action-' + binding.event, true);
});
};
@ -225,7 +182,7 @@ humhub.initModule('action', function (module, require, $) {
*
* - Direct-ActionHandler is called if a directHandler was given when binding the action.
* - Component-ActionHandler is called if $trigger is part of a component and the component handler can be resolved
* - Global-ActionHandler is called if we find a handler in the _handler array. See registerHandler, registerAjaxHandler
* - Global-ActionHandler is called if we find a handler in the _handler array. See registerHandler
* - Namespace-ActionHandler is called if we can resolve an action by namespace e.g: data-action-click="myModule.myAction"
*
* Once triggered the handler will block the event for this actionbinding until the actionevents .finish is called.
@ -237,24 +194,23 @@ humhub.initModule('action', function (module, require, $) {
* @param {type} $trigger the jQuery node which triggered the event
* @returns {undefined}
*/
ActionBinding.prototype.handle = function (evt, $trigger) {
if($trigger.data('action-blocked-'+this.eventType)) {
ActionBinding.prototype.handle = function (originalEvent, $trigger) {
if (originalEvent) {
originalEvent.preventDefault();
}
if (this.isBlocked($trigger)) {
return;
}
/**
* TODO: implement data-action-block="none|sync|async"
*/
if(!$trigger.data('action-prevent-bock') && !$trigger.data('action-prevent-bock-'+this.eventType)) {
$trigger.data('action-blocked-'+this.eventType, true);
if(this.isBlockAction($trigger)) {
this.block($trigger);
}
module.log.debug('Handle Action', this);
evt.preventDefault();
var event = this.createActionEvent(evt, $trigger);
// Search and execute a stand alone handler or try to call the content action handler
module.log.debug('Handle Action', this);
var event = this.createActionEvent(originalEvent, $trigger);
try {
// Check for a direct action handler
if (object.isFunction(this.directHandler)) {
@ -278,24 +234,74 @@ humhub.initModule('action', function (module, require, $) {
var handlerAction = splittedNS[splittedNS.length - 1];
var target = require(string.cutsuffix(event.handler, '.' + handlerAction));
if (object.isFunction(target)) {
if (object.isFunction(target[handlerAction])) {
target[handlerAction](event);
} else {
module.log.error('actionHandlerNotFound', this, true);
}
} catch (e) {
module.log.error('error.default', e, true);
if(event.finish) {
event.finish();
}
_removeLoaderFromEventTarget(evt);
event.finish();
} finally {
// Just to get sure the handler is not called twice.
if(evt.originalEvent) {
evt.originalEvent.actionHandled = true;
if (originalEvent) {
originalEvent.actionHandled = true;
}
if (this.isBlockType($trigger, BLOCK_SYNC)) {
event.finish();
}
}
};
/**
* Checks if the trigger should be blocked before running the action.
*
* @param {type} $trigger
* @returns {Boolean}
*/
ActionBinding.prototype.isBlockAction = function($trigger) {
return !this.isBlockType($trigger, BLOCK_NONE);
};
/**
* Checks the given block data setting of $trigger agains a blocktype.
*
* @param {type} $trigger
* @param {type} type
* @returns {Boolean}
*/
ActionBinding.prototype.isBlockType = function($trigger, type) {
var blockTypeData = $trigger.data('action-block-' + this.eventType);
if(blockTypeData) {
return blockTypeData === type;
} if($trigger.data('action-block')) {
return $trigger.data('action-block') !== type;
} else {
return type === BLOCK_ASYNC;
}
};
/**
* Checks if $trigger is currently blocked.
*
* @param {type} $trigger
* @returns {unresolved}
*/
ActionBinding.prototype.isBlocked = function($trigger) {
return $trigger.data('action-blocked-' + this.eventType);
};
/**
* Blocks $trigger, which will disable further action calls.
*
* @param {type} $trigger
* @returns {undefined}
*/
ActionBinding.prototype.block = function($trigger) {
$trigger.data('action-blocked-' + this.eventType, true);
};
ActionBinding.prototype.createActionEvent = function (evt, $trigger) {
var event = $.Event(this.eventType);
@ -304,9 +310,11 @@ humhub.initModule('action', function (module, require, $) {
// Add some additional action related data to our event.
event.$trigger = $trigger;
event.$target = $trigger.data('action-target') ? $trigger.data('action-target') : $trigger;
// If the trigger contains an url setting we add it to the event object, and prefer the typed url over the global data-action-url
event.url = $trigger.data('action-' + this.eventType + '-url') || $trigger.data('action-url');
event.params = $trigger.data('action-' + this.eventType + '-params') || $trigger.data('action-params');
event.params = $trigger.data('action-' + this.eventType + '-params') || $trigger.data('action-params') || {};
//Get the handler id, either a stand alone handler or a content handler function e.g: 'edit'
event.handler = $trigger.data('action' + '-' + this.eventType);
@ -318,7 +326,7 @@ humhub.initModule('action', function (module, require, $) {
var eventType = this.eventType;
event.finish = function () {
_removeLoaderFromEventTarget(evt);
$trigger.data('action-blocked-'+eventType, false);
$trigger.data('action-blocked-' + eventType, false);
};
return event;
@ -348,9 +356,9 @@ humhub.initModule('action', function (module, require, $) {
* @param {string} selector - jQuery selector
* @param {string} selector - jQuery selector
*/
module.bindAction = function (parent, type, selector, directHandler) {
var bindAction = function (parent, type, selector, directHandler) {
parent = parent || document;
var $parent = parent.jquery ? parent : $(parent);
var $parent = (parent instanceof $) ? parent : $(parent);
var actionEvent = type + '.humhub-action';
var actionBinding = new ActionBinding({
@ -364,13 +372,12 @@ humhub.initModule('action', function (module, require, $) {
// Add new ActionBinding with given settings.
actionBindings.push(actionBinding);
$parent.on(actionEvent, selector, function (evt) {
$parent.off(actionEvent).on(actionEvent, selector, function (evt) {
evt.preventDefault();
// Get sure we don't call the handler twice if the event was already handled by trigger.
if ($(this).data('action-'+actionBinding.event)
if ($(this).data('action-' + actionBinding.event)
|| (evt.originalEvent && evt.originalEvent.actionHandled)) {
module.log.info('Blocked action event '+actionEvent, actionBinding);
module.log.info('Blocked action event ' + actionEvent, actionBinding);
module.log.info('Blocked event triggered by', $(this));
return;
}
@ -383,7 +390,43 @@ humhub.initModule('action', function (module, require, $) {
return;
};
/**
* This function can be called to manually trigger an action event of the given $trigger.
* This can be used for example for additional event types without actually binding the
* event to $trigger.
*
* e.g manually trigger a custom data-action-done action of an ui component.
*
* @param {type} $trigger
* @param {type} type
* @param {type} originalEvent
* @returns {undefined}
*/
var trigger = function ($trigger, type, originalEvent, block) {
if(block === false) {
$trigger.data('action-block', BLOCK_NONE);
} else if(object.isString(block)) {
$trigger.data('action-block', block);
}
if (!$trigger.data('action-' + type)) {
return;
}
new ActionBinding({
type: type,
event: type
}).handle(originalEvent, $trigger);
};
module.export({
Component: Component
init: init,
bindAction: bindAction,
registerHandler: registerHandler,
Component: Component,
trigger: trigger,
BLOCK_NONE: BLOCK_NONE,
BLOCK_SYNC: BLOCK_SYNC,
BLOCK_ASYNC: BLOCK_ASYNC
});
});

View File

@ -5,6 +5,8 @@
humhub.initModule('client', function (module, require, $) {
var object = require('util').object;
var event = require('event');
//var clientUpload = require('client.upload');
//var action = require('action');
/**
* Response Wrapper Object for easily accessing common data
@ -68,7 +70,7 @@ humhub.initModule('client', function (module, require, $) {
$form = object.isString($form) ? $($form) : $form;
cfg.type = $form.attr('method') || 'post';
cfg.data = $form.serialize();
var url = cfg.url || $form.attr('action');
var url = cfg.url || originalEvent.url || $form.attr('action');
return ajax(url, cfg, originalEvent);
};
@ -194,12 +196,42 @@ humhub.initModule('client', function (module, require, $) {
originalEvent.finish();
}
};
/**
* Default file upload action
* @param {type} evt
* @returns {undefined}
var upload = function(evt) {
var $target = evt.$target;
clientUpload.upload($target).then(function() {
if($target.data('action-done')) {
action.trigger($target, 'done', evt, false);
} else if(evt.finish) {
evt.finish();
}
}).catch(function(e) {
});
//Check if handler is already active
if(!isSet) {
clientUpload.set(evt);
}
.on('done', function() {
}).trigger('click');
};
*/
module.export({
ajax: ajax,
post: post,
get: get,
submit: submit,
//upload: upload,
Response: Response
});
});

View File

@ -4,8 +4,11 @@ humhub.initModule('client.pjax', function (module, require, $) {
module.initOnPjaxLoad = false;
var init = function () {
pjaxRedirectFix();
module.installLoader();
if (module.config.active) {
$(document).pjax("a", "#layout-content", module.config.options);
pjaxRedirectFix();
module.installLoader();
}
};
var pjaxRedirectFix = function () {
@ -33,28 +36,39 @@ humhub.initModule('client.pjax', function (module, require, $) {
$.ajaxPrefilter('html', function (options, originalOptions, jqXHR) {
var orgErrorHandler = options.error;
options.error = function (xhr, textStatus, errorThrown) {
var redirect = (xhr.status >= 301 && xhr.status <= 303)
if (redirect && xhr.getResponseHeader('X-PJAX-REDIRECT-URL') != "" && xhr.getResponseHeader('X-PJAX-REDIRECT-URL') !== null) {
if (isPjaxRedirect(xhr)) {
options.url = xhr.getResponseHeader('X-PJAX-REDIRECT-URL');
console.log('Handled redirect to: ' + options.url);
options.replace = true;
module.log.info('Handled redirect to: ' + options.url);
$.pjax(options);
} else {
orgErrorHandler(xhr, textStatus, errorThrown);
}
}
};
});
};
var isPjaxRedirect = function (xhr) {
if (!xhr) {
return false;
}
var redirect = (xhr.status >= 301 && xhr.status <= 303);
return redirect && xhr.getResponseHeader('X-PJAX-REDIRECT-URL') != "" && xhr.getResponseHeader('X-PJAX-REDIRECT-URL') !== null;
};
var installLoader = function () {
NProgress.configure({showSpinner: false});
NProgress.configure({template: '<div class="bar" role="bar"></div>'});
$(document).on('pjax:start', function () {
$(document).on('pjax:start', function (evt, xhr, options) {
NProgress.start();
});
$(document).on('pjax:end', function () {
NProgress.done();
$(document).on('pjax:end', function (evt, xhr, options) {
if (!isPjaxRedirect(xhr)) {
NProgress.done();
}
});
};

View File

@ -142,14 +142,15 @@ var humhub = humhub || (function($) {
* require('humhub.modules.ui.modal');
*
* @param {type} moduleId
* @param {boolean} lazy - can be set to require modules which are not yet created.
* @returns object - the module instance if already initialized else undefined
*
* */
var require = function(moduleNS) {
var module = resolveNameSpace(moduleNS);
var require = function(moduleNS, lazy) {
var module = resolveNameSpace(moduleNS, lazy);
if(!module) {
//TODO: load remote module dependencies
console.warn('No module found for id: '+moduleNS);
console.error('No module found for namespace: '+moduleNS);
}
return module;
};
@ -181,7 +182,7 @@ var humhub = humhub || (function($) {
return result;
} catch(e) {
var log = require('log') || console;
log.error('Error while resolving namespace: '+typePathe, e);
log.error('Error while resolving namespace: '+typePath, e);
}
};
@ -324,7 +325,7 @@ var humhub = humhub || (function($) {
var addModuleLogger = function(module, log) {
log = log || require('log');
module.log = log.module(module);
}
};
//Initialize all initial modules
$(document).ready(function() {
@ -356,13 +357,11 @@ var humhub = humhub || (function($) {
event.on('humhub:modules:client:pjax:afterPageLoad', function (evt) {
$.each(pjaxInitModules, function(i, module) {
if(module.initOnPjaxLoad) {
module.init();
module.init(true);
}
});
});
return {
initModule: initModule,
modules: modules,

View File

@ -80,6 +80,10 @@ humhub.initModule('log', function (module, require, $) {
Logger.prototype._log = function (msg, details, setStatus, level) {
try {
if (this.traceLevel > level) {
return;
}
if (object.isBoolean(details)) {
setStatus = details;
details = undefined;
@ -95,10 +99,6 @@ humhub.initModule('log', function (module, require, $) {
msg = this.getMessage(msg, level, (!object.isDefined(msg) && level >= TRACE_WARN));
}
if (this.traceLevel > level) {
return;
}
this._consoleLog(msg, level, details);
if (setStatus) {
@ -133,7 +133,7 @@ humhub.initModule('log', function (module, require, $) {
if (window.console) {
var consoleMsg = traceLevels[level] + ' - ';
consoleMsg += this.moduleId || 'root';
consoleMsg += msg;
consoleMsg += ': '+msg;
switch (level) {
case TRACE_ERROR:
case TRACE_FATAL:

View File

@ -33,7 +33,7 @@ humhub.initModule('ui.additions', function (module, require, $) {
* @returns {undefined}
*/
module.applyTo = function (element) {
var $element = $(element);
var $element = (element instanceof $) ? element : $(element);
$.each(_additions, function (selector, additions) {
$.each(additions, function (i, addition) {
$.each($element.find(selector).addBack(selector), function () {

View File

@ -0,0 +1,25 @@
/**
*
* @param {type} param1
* @param {type} param2
*/
humhub.initModule('ui.gallery', function (module, require, $) {
module.initOnPjaxLoad = false;
var init = function () {
$(document).on('click', '[data-ui-gallery]', function (evt) {
evt.preventDefault();
evt.stopPropagation();
var $this = $(this);
var gallery = $this.data('ui-gallery');
var $links = (gallery) ? $('[data-ui-gallery="' + gallery + '"]') : $this.parent().find('[data-ui-gallery]');
var options = {index: $this[0], event: evt.originalEvent};
blueimp.Gallery($links.get(), options);
});
};
module.export({
init: init
});
});

View File

@ -28,7 +28,7 @@ humhub.initModule('ui.loader', function (module, require, $) {
module.initOnPjaxLoad = false;
var set = function (node, cfg) {
var $node = $(node);
var $node = (node instanceof $) ? node : $(node);
if ($node.length) {
$node.each(function () {
var $this = $(this);
@ -40,14 +40,14 @@ humhub.initModule('ui.loader', function (module, require, $) {
};
var append = function (node, cfg) {
var $node = $(node);
var $node = (node instanceof $) ? node : $(node);
if ($node.length) {
$node.append(getInstance(cfg));
}
};
var prepend = function (node, cfg) {
var $node = $(node);
var $node = (node instanceof $) ? node : $(node);
if ($node.length) {
$node.prepend(getInstance(cfg));
}
@ -58,7 +58,7 @@ humhub.initModule('ui.loader', function (module, require, $) {
};
var reset = function (node) {
var $node = $(node);
var $node = (node instanceof $) ? node : $(node);
var $loader = $node.find('.loader').length;
if (!$loader) {
return;
@ -112,7 +112,7 @@ humhub.initModule('ui.loader', function (module, require, $) {
};
var init = function (cfg) {
$(document).on('click.humhub:modules:ui:loader', 'a[data-ui-loader], button[data-ui-loader]', function (evt) {
$(document).on('click.humhub:modules:ui:loader', '[data-ui-loader]', function (evt) {
return module.initLoaderButton(this, evt);
});
@ -126,7 +126,7 @@ humhub.initModule('ui.loader', function (module, require, $) {
};
var initLoaderButton = function (node, evt) {
var $node = $(node);
var $node = (node instanceof $) ? node : $(node);
var loader = $node.find('.loader').length > 0;
/**

View File

@ -0,0 +1,43 @@
/**
*
* @param {type} param1
* @param {type} param2
*/
humhub.initModule('ui.navigation', function (module, require, $) {
var init = function () {
// Default implementation for topbar. Activate li on click.
$('#top-menu-nav a').on('click', function () {
var $this = $(this);
if (!$this.is('#space-menu')) {
setActiveItem($this);
}
});
// Activate by config
$.each(module.config['active'], function (id, url) {
setActive(id, url);
});
// Reset active config.
module.config['active'] = undefined;
};
var setActive = function (id, url) {
setActiveItem($('#' + id).find('[href="' + url + '"]'));
};
var setActiveItem = function ($item) {
if (!$item.length) {
module.log.warn('Could not activate navigation item', $item);
}
$item.closest('ul').find('li').removeClass('active');
$item.closest('li').addClass('active');
$item.trigger('blur');
};
module.export({
init: init,
setActive: setActive
});
});

View File

@ -127,7 +127,7 @@ humhub.initModule('ui.status', function (module, require, $) {
} catch (e) {
log.error(e);
}
}
};
StatusBar.prototype.show = function (callback) {
// Make the container transparent for beeing able to measure the body height

View File

@ -33,7 +33,7 @@ var checkForMultiSelectDropDowns = function() {
$('.multiselect_dropdown').trigger('update');
}
$(document).ready(function () {
$(document).on('ready pjax:success', function () {
$.fn.select2.defaults = {};
checkForMultiSelectDropDowns();
});

View File

@ -34,6 +34,7 @@ class AppAsset extends AssetBundle
'css/temp.css',
'css/bootstrap-wysihtml5.css',
'css/flatelements.css',
'css/blueimp-gallery.min.css'
];
/**
@ -45,7 +46,8 @@ class AppAsset extends AssetBundle
* @inheritdoc
*/
public $js = [
'js/ekko-lightbox-modified.js',
//'js/ekko-lightbox-modified.js',
'js/blueimp-gallery.min.js',
//'js/modernizr.js', // In use???
'js/jquery.highlight.min.js',
//'js/wysihtml5-0.3.0.js',

View File

@ -50,10 +50,13 @@ class CoreApiAsset extends AssetBundle
'js/humhub/humhub.ui.additions.js',
'js/humhub/humhub.ui.loader.js',
'js/humhub/humhub.ui.modal.js',
'js/humhub/humhub.client.js',
'js/humhub/humhub.client.pjax.js',
'js/humhub/humhub.action.js',
'js/humhub/humhub.ui.status.js'
'js/humhub/humhub.client.js',
'js/humhub/humhub.ui.status.js',
'js/humhub/humhub.ui.navigation.js',
'js/humhub/humhub.ui.gallery.js',
// Note this should stay at last for other click event listeners beeing able to prevent pjax handling (e.g gallery)
'js/humhub/humhub.client.pjax.js',
];
/**

View File

@ -41,6 +41,14 @@ class Controller extends \yii\web\Controller
* @var boolean append page title
*/
public $prependActionTitles = true;
/**
* Can be used to set the active topmenu item.
*
* @see Controller::setActiveTopMenuItem
* @var type
*/
public $topMenuRoute;
/**
* @inheritdoc
@ -134,9 +142,15 @@ class Controller extends \yii\web\Controller
$this->appendPageTitle($this->actionTitlesMap[$this->action->id]);
}
}
if (!empty($this->pageTitle)) {
$this->getView()->pageTitle = $this->pageTitle;
}
if(!empty($this->topMenuRoute)) {
$this->setActiveTopMenuItem(Url::to([$this->topMenuRoute]));
}
return true;
}
return false;
@ -199,5 +213,10 @@ class Controller extends \yii\web\Controller
return Yii::$app->getResponse()->redirect(Url::to($url), $statusCode);
}
public function setActiveTopMenuItem($url)
{
\humhub\widgets\TopMenu::markAsActive($url);
}
}

View File

@ -51,16 +51,17 @@ class View extends \yii\web\View
$jsCode = "var " . $name . " = '" . addslashes($value) . "';\n";
$this->registerJs($jsCode, View::POS_HEAD, $name);
}
public function registerJsConfig($module, $params = null) {
if(is_array($module)) {
foreach($module as $moduleId => $value) {
public function registerJsConfig($module, $params = null)
{
if (is_array($module)) {
foreach ($module as $moduleId => $value) {
$this->registerJsConfig($moduleId, $value);
}
return;
}
if(isset($this->jsConfig[$module])) {
if (isset($this->jsConfig[$module])) {
$this->jsConfig[$module] = yii\helpers\ArrayHelper::merge($this->jsConfig[$module], $params);
} else {
$this->jsConfig[$module] = $params;
@ -140,16 +141,27 @@ class View extends \yii\web\View
*/
public function endBody()
{
$this->registerJs("humhub.config.set(".json_encode($this->jsConfig).");", View::POS_BEGIN, 'jsConfig');
\humhub\widgets\CoreJsConfig::widget();
$this->flushJsConfig();
if (Yii::$app->request->isAjax) {
return parent::endBody();
}
echo \humhub\widgets\LayoutAddons::widget();
// Will add js configuraiton added by layoutaddons.
$this->flushJsConfig();
// Add Layout Addons
return parent::endBody();
}
protected function flushJsConfig($key = null)
{
$this->registerJs("humhub.config.set(" . json_encode($this->jsConfig) . ");", View::POS_BEGIN, $key);
$this->jsConfig = [];
}
}

View File

@ -2,7 +2,7 @@
/**
* This file is generated by the "yii asset" command.
* DO NOT MODIFY THIS FILE DIRECTLY.
* @version 2016-10-21 15:35:07
* @version 2016-10-29 22:30:11
*/
return [
'all' => [
@ -10,10 +10,10 @@ return [
'basePath' => '@webroot',
'baseUrl' => '@web',
'js' => [
'js/all-a366b3723e2189716ce68fbe333d59bd.js',
'js/all-a0250144cce3bef41eb0dedbe8a451f9.js',
],
'css' => [
'css/all-2d605362857c9db6fdb10a1df599e902.css',
'css/all-fe9e5fa22cdeaff0701299704a33505f.css',
],
'sourcePath' => null,
'depends' => [],
@ -134,14 +134,6 @@ return [
'all',
],
],
'humhub\\assets\\JqueryPjaxAsset' => [
'sourcePath' => null,
'js' => [],
'css' => [],
'depends' => [
'all',
],
],
'humhub\\assets\\CaretJsAsset' => [
'sourcePath' => null,
'js' => [],
@ -197,6 +189,39 @@ return [
'all',
],
],
'humhub\\assets\\NProgressAsset' => [
'sourcePath' => null,
'js' => [],
'css' => [],
'depends' => [
'all',
],
],
'humhub\\assets\\IE9FixesAsset' => [
'sourcePath' => null,
'js' => [],
'css' => [],
'depends' => [
'all',
],
],
'humhub\\assets\\Html5shivAsset' => [
'sourcePath' => null,
'js' => [],
'css' => [],
'depends' => [
'all',
],
],
'humhub\\assets\\IEFixesAsset' => [
'sourcePath' => null,
'js' => [],
'css' => [],
'depends' => [
'humhub\\assets\\Html5shivAsset',
'all',
],
],
'humhub\\assets\\AppAsset' => [
'sourcePath' => null,
'js' => [],
@ -215,11 +240,13 @@ return [
'humhub\\assets\\JqueryHighlightAsset',
'humhub\\assets\\JqueryCookieAsset',
'humhub\\assets\\JqueryAutosizeAsset',
'humhub\\assets\\JqueryPjaxAsset',
'humhub\\assets\\AtJsAsset',
'humhub\\assets\\AnimateCssAsset',
'humhub\\assets\\CoreApiAsset',
'humhub\\modules\\content\\assets\\ContentAsset',
'humhub\\assets\\NProgressAsset',
'humhub\\assets\\IE9FixesAsset',
'humhub\\assets\\IEFixesAsset',
'all',
],
],

View File

@ -1,39 +1,64 @@
Javascript frontend
Javascript API
=======
HumHub provides a simple Javascript module system, which enables a similar module structure as the backend.
Instead of using inline code blocks in php, a module (especially complex modules) should add it's frontend logic
as a javascript module to the HumHub module system. The module system provides, an event system for event driven
communication, module configuration, server communication utilities and more under the global namespace `humhub`.
Since version 1.2 HumHub provides a module based Javascript API within the `humhub` namespace.
Instead of using inline script blocks in your views it's highly recommended using the new module system for your
frontend scripts. The core components of this api are described in the following.
## Modules
### Module Asset
Your Module script files should reside within the `asset/js` folder of your backend module and be appended at the bottom of your document by using yii's asset bundles.
Example:
```php
namespace humhub\modules\example\assets;
use yii\web\AssetBundle;
class ExampleAsset extends AssetBundle
{
public $jsOptions = ['position' => \yii\web\View::POS_END];
public $sourcePath = '@example/assets';
public $css = [];
public $js = [
'js/humhub.example.js'
];
}
```
## Core
### Module Registration
Modules can be registered by calling the `humhub.initModule` function. This function
accepts an id and the actual module function and will add the given module to the namespace `humhub.modules`.
Modules are registered by calling the `humhub.initModule`. This function
requires an unique module id and your module function. The module function provides the following arumgents
The module function is provided with three arguments:
1. `module`: Your module instance, for exporting module functions and attributes.
2. `require`: Method for injecting other modules.
3. `$`: jQuery.
- The __module__ object is used to export functions either by appending functions/properties directly to `module` or by calling `module.export`.
- The __require__ function can be used to inject other modules.
- __$__ a references jquery
Modules can export a `init` function, which is called automatically after the document is ready.
The following example shows the implementation of a dummy module `humhub.modules.myModule`.
The following example shows the registraion of module with id 'example':
```javascript
//Initialization of myModule
humhub.initModule('myModule', function(module, require, $) {
//Require at client module at startup
// After registration, all exported functions will be available under the namespace humhub.modules.example
humhub.initModule('example', function(module, require, $) {
// We require the client module
var client = require('client');
//Private property
// Private property
var myProperty;
//Definition of an exported object
// Definition of an exported object
module.myPublicObject = {};
//Export some other functions
// export single function
module.myPublicFunction = function() {
// Some logic
}
// Export multiple values by calling module.export.
module.export({
myFunction: function() {
...
@ -46,30 +71,129 @@ humhub.initModule('myModule', function(module, require, $) {
...
//Calling myFunction within another module
require('myModule').myFunction();
//Calling myFunction within another module (full path)
require('humhub.modules.myModule').myFunction();
//Also a valid call
require('modules.myModule').myFunction();
//Calling myFunction outside of a module
humhub.modules.myModule.myFunction();
// Submodules can be registered as following
humhub.initModule('example.mySubmodule', function(module, require, $) {
...
}
```
> NOTE: If a module requires another module at startup the required module has to be initialized before. The init order of core modules is configured in the Gruntfile.js
Accessing your example module:
> TIP: You can require modules at runtime by calling `require` within exproted functions, this can solve potential startup dependency issues.
```javascript
//Calling myFunction within another module
require('example').myFunction();
> TIP: Code that requires the page to be loaded (dom access, dom event delegation) should be pushed to the `init` function
//Calling myFunction within another module (full path)
require('humhub.modules.example').myFunction();
> NOTE: Module functions should only be called by other modules, since most of them require an initialization.
//Also a valid call
require('modules.example').myFunction();
### Module Config
//Calling myFunction outside of a module
humhub.modules.example.myFunction();
```
The HumHub javascript core provides a mechanism to configure modules. A moduleconfig can be set by calling `humhub.config.set`,
### Module Dependencies
As described before the `require` function can be used to inject other modules into your own module.
Note that you should only require modules at the beginning of your own module, if you are sure the required module is already
registered.
The registration order should be assured by using the Assetbundle's `$depends` mechanism:
```php
// Add this to your ExampleAsset.php file
public $depends = [
'humhub\modules\anotherModule\assets\AnotherModuleAsset'
];
```
If you can't assure the module registration order, but need to require another module, you can either require it within your module function instead of the beginning
of your module or using the `lazy` flag of the require function.
The call to `require('anotherModule', true)` will return an empty namespace object, which will be filled after the required module is available.
>Note: If you use the `lazy` flat to require another module, you can't assure the required module will be initialized within your own module's `init` function.
>Info: All core modules are registrated at the beginning of the body, so they are available very early.
### Module Initialisation
Modules can export a `init` function, which is called automatically after the document is ready.
```javascript
humhub.initModule('example', function(module, require, $) {
...
var init = function() {
// Dom will be ready here.
}
// Export multiple values by calling module.export.
module.export({
init: init,
...
});
});
```
Since HumHub can be operated in [[pjax]] mode as single page application.
By default the `init` function of your module is automatically after a pjax load. This can be deactivated for modules
which do not need to be reinitialized by setting
```javascript
module.initOnPjaxLoad = false
```
This mostly applies to modules which are not dependent on dynamic dom nodes.
If you module needs to implement a special behaviour for pjax reloads, it can also listen to the following event
```javascript
var event = require('event');
...
event.on('humhub:modules:client:pjax:afterPageLoad', function() {
...
}
```
### Module Configuration
If you need to transfer values as texts, flags or urls from your php backend to your frontend module, you can use the `module.config` array which is automatically available
within your module function as in the following example:
```javascript
humhub.initModule('example', function(module, require, $) {
...
var myAction = function() {
if(module.config['showMore']) {
// Do something
}
};
});
```
In your php view you can set the module as follows
```php
// Single module
$this->registerJsVar('example', ['showMore' => true]);
// Multiple modules
$this->registerJsVar([
'example' => [
'showMore' => true
],
'anotherModule' => [
...
]
);
```
Setting configurations in javascript:
```javascript
//Set config values for multiple modules,
@ -90,13 +214,93 @@ humhub.config.set('myModule', {
//You can also call
humhub.config.set('myModule', 'myKey', 'value');
```
>Note: Since the configuration can easily be manipulated, you should not set values which can compromise the security of your application.
> TIP: Module setter are normally called within views or widgets to inject urls or translated text for errors or modals
Modules can retrieve its config through `humhub.config.get`
### Module Texts
Beside the configuration addition, the module instance does furthermore provide a `module.text` function for easily accessing texts of your configuration.
Example of an error text.
```php
//Configurate your text in your php view.
$this->registerJsVar([
'example' => [
'showMore' => true,
'text' => [
'error.notallowed' => Yii::t('ExampleModule.views.example', 'You are not allowed to access example!');
]
]
);
```
Access your text within your module function as this
```javascript
//Retrieves the whole config object of 'myModule'
var config = humhub.config.get('myModule');
module.text('error.notallowed');
// which is a short form of:
module.config['text']['error.notallowed'];
```
### Module Log
Your module is able to create module specific log entries by using the `module.log` object of your module instance.
The log object supports the following log level functions:
1. trace - For detailed trace output
2. debug - For debug output
3. info - Info messages
4. success - Used for success info logs
5. warn - Warnings
6. error - For error messages
7. fatal - Fatal errors
All log functions accept up to three arguments:
1. The actual message
2. Details about the message (or errors in case of warn/error/fatal)
3. A setStatus flag, which will trigger a global `humhub:modules:log:setStatus` event. This can be used to give user-feedback (status bar).
Instead of an actual message, you can also just provide a text key as the first argument.
The following calls are valid:
```javascript
// Log config text 'error.notallowed' and give user feedback.
module.log.error('error.notallowed', true);
// In the following example we received an error response by our humhub.modules.client. The response message will try to resolve a default
// message for the status of your response. Those default messages are configured in the core configuration texts.
module.log.error(response, true);
// The error.default text message is available through the configuration of the log module see humhub\widgets\JSConfig
module.log.error('error.default', new Error('xy'), true);
```
> Info: Your module logger will try resolving your message string to a module or global text.
> Info: The core ui.status module is responsible for triggering the user result.
> Note: The success log will by default trigger a status log event.
```
The trace level of your module can be configured by setting the `traceLevel` of your module configuration.
If your module does not define an own trace level the log modules's traceLevel configuration will be used.
> Info: In production mode the default log level is set to `INFO`, in dev mode its set to `DEBUG`.
> Note: If you change the `traceLevel` of a module at runtime, you'll have to call `module.log.update()`.
## Core Modules
### Config Module
Beside the `module.config` utility you can also use the global configuration as follows
```javascript
// Retrieves the whole config object of 'myModule'
var moduleConfig = require('config').get('myModule');
var myValue = config['myKey'];
//Single value getter with default value
@ -115,8 +319,10 @@ if(humhub.config.is('myModule', 'enabled', 'false')) {
## Client
The `humhub.modules.client` module provides some utilities for calling backend actions. The client is build on top of
jquery and provides some additional functionality as a response wrapper and enhanced error handling. A backend action can
be called by `client.ajax` or `client.post`. Both functions expect an url and jquery like ajax configuration.
jquery's `$.ajax` function and provides some additional functionality as a response wrapper, promises and enhanced error handling.
A backend action can be called by `client.ajax`, `client.get` or `client.post`.
The client module can be used as follows
```javascript
var client = require('client');
@ -128,9 +334,9 @@ client.ajax(url, {
type: 'POST'
}
}).then(function(response) {
handle(response.content);
handleSuccess(response.content);
}).catch(function(errResponse) {
handleError(errResponse.getErrors());
handleError(errResponse);
});
//The same call with forcing a post call
@ -143,28 +349,148 @@ client.post(url, {
}).catch(function(errResponse) {
handleError(errResponse.getErrors());
});
```
> Note: Since Yii urls can't be created on the client side, you'll have to inject them through data attributes or the module config.
> TIP: The action module provides an uniform way of registering ajax actions without the need of calling the client itself.
// The status function can be used to react to specific response status codes
client.post(url, cfg)
.status({
200: function(response) {
// Success handler with user feedback
$('container').html(response.output);
module.log.success('success.edit', true);
},
400: function(response) {
// Validation error user feedback is given by validation errors
$('container').html(response.output);
}
}).catch(function(e) {
// Unexpected error with user feedback
module.log.error(e, true);
});
```
> Note: Since Yii urls can't be created on client side, you'll have to inject them through data attributes or the module config.
> TIP: The `action` mechanism described later, facilitates the mapping of urls and action handlers.
### Response Wrapper
(TBD)
The response object returned by your client contains the following attributes:
## Actions
- url: the url of your call
- status: the result status of the xhr object
- response: the server response, either a json object or html depending of the 'dataType' setting of your call.
- textStatus: In case of error: "timeout", "error", "abort", "parsererror", "application"
- dataType: the datatype of your call
- error: ajax error info
- validationError: flag which is set if status = 400
- If your response is of type 'json' all your json values will also be directly appended to the response object's root.
## Actions (TBD)
The `humhub.modules.action` module can be used to define frontend actions, which are triggered by events like clicking a button or changing an input field.
The action mechanism provides an unified way of binding actions to your ui components.
The following example binds all click event of a button to the `myAction` function of your module.
```html
<!-- In your view file -->
<a href="#" data-action-click="example.myAction" data-action-url="<?= ... ?>">Call my action!</a>
```
```javascript
// within your module
var myAction = function(evt) {
client.get(evt).then(function(response) {
...
evt.$trigger.text(response.output);
module.log.success('success.');
}).catch(function(response) {
...
module.log.error(response, true);
});
}
...
module.export({
...
myAction: myAction
});
```
> Note: don't forget to export your action handler, otherwise they won't be accessible.
### Action Binding
The `humhub.modules.action.bindAction` function can be used to bind an action-handler to a specific event type (e.g. click, change,...) of nodes.
The `humhub.modules.action.bindAction` function is used to bind event types to nodes of a given selector.
The following event type action bindings are available by default:
```javascript
// This line adds the action behavior for all elements with a data-action-click attribute for 'click' events.
this.bindAction(document, 'click', '[data-action-click]');
this.bindAction(document, 'dblclick', '[data-action-dblclick]');
this.bindAction(document, 'change', '[data-action-change]');
```
You can extend the supported event types with custom types as in the following example:
```javascript
// This line adds the action behavior for all elements with a data-action-click attribute for 'click' events.
this.bindAction(document, 'customevent', '[data-action-customevent]');
```
__How does it work:__
In the previous examples the bindAction call will bind a delegate to the `document` e.g. `$(document).on('click', '[data-action-click]', function() {...});`
If the delegate receives an unhandled action event, it will rebind all bindings directly to the trigger elements and run the action.
All upcoming events will directly be handled by the trigger, which prevents the bubbling latency.
### Action Event
All action-handlers are provided with an action event which is a derivate of `$.Event` and provides, beside others, the following attributes:
- `$trigger`: The jquery $trigger object, which was responsible for triggering the event e.g. a button.
- `$target`: Can be set by the data-action-target attribute of your $trigger, by default the $trigger is also the event target. See the action component section for more details.
- `$form`: In case your $trigger is of `type="submit"` or has a `data-action-submit` attribute, the action event will include a jquery object of the sorrounding form.
```html
<form ...>
...
<!-- The url of your action handler will either take the form action url or the data-action-url/data-action-click-url (which will be prefered) -->
<button data-action-submit data-action-click="example.submit">Submit</button>
</form>
```
```javascript
//The client knows how to handle action events, so you just have to call the following to submit evt.$form
var submit = function(evt) {
client.submit(evt).then(...).catch(...);
}
```
- `url`: Contains the `data-action-url` (used for all actions of $trigger) or `data-action-click-url` (will be prefered in case of click events)
- `params`: Can be used to add additional action parameters by setting `data-action-params` or the more specific `data-action-click-params` for click events
```html
<button data-action-click="example.call" data-action-params="{type:'example'}">Call Action!</button>
```
```javascript
var call = function(evt) {
alert(evt.params.type);
}
```
- `originalEvent`: The original event which triggered the action
### Action Handlers
There are different types of action-handlers:
- __Direct__ action-handlers can be directly passed to the `bindAction` function.
- __Registered__ action-handler are registered by the `registerHandler` or `registerAjaxHandler` and can be shared by modules.
- __Content__ action-handlers are used to execute content related actions (see Content) .
- __Registered__ action-handler are registered by the `registerHandler` and can be shared by modules.
- __Component__ action-handlers are used to execute actions of a ui component (see Action Components) .
- __Namespace__ action-handlers will be searched within the humhub namespace if there is no other matching handler.
Example of a `direct-handler`:
@ -172,7 +498,8 @@ Example of a `direct-handler`:
```html
<!-- Somewhere in my view -->
<div id="#myContainer">
<button class="mySendButton" data-action-url="<?= Url::to(...) ?>">Send</button>
<!-- Note, you won't have to define the name of your handler in this case -->
<button class="sendButton" data-action-url="<?= Url::to(...) ?>">Send</button>
</div>
```
@ -181,59 +508,32 @@ Example of a `direct-handler`:
var action = require('action');
//Bind a click handler to all .mySpecialButtons within #myContainer
action.bindAction('#myContainer', 'click', '.mySendButton', function(evt) {
action.bindAction('#myContainer', 'click', '.sendButton', function(evt) {
//this within a handler function always points to the triggered jQuery node
client.post(this.data('action-url').then(function(resp) {...});
client.post(evt).then(function(resp) {...});
});
```
> TIP: Since humhub action binding is based on jquerys event delegation, you can use all event types of jquery.
> TIP: In case of direct action-handlers, there is no need to define a action-handler like data-action-click="myHandler" on the trigger element.
> NOTE: The first argument of the bindAction should be the first static (never removed from dom or lazy loaded) parent node of all nodes you wish to bind. Too many delegated events to the `document` is a performance antipattern.
Example registered `ajax-handler`:
```html
<!-- Somewhere in my view -->
<button data-action-click="humhub.modules.myModule.sendAjax" data-action-url="<?= Url::to(...) ?>">Send</button>
```
```javascript
//Somewhere within myModule
action.registerAjaxHandler('humhub.modules.myModule.sendAjax', {
success: function(response) {
//My success handler
},
error: function(response) {
//My error handler
}
}
//No need to call bindAction, since data-action-click nodes are bound automatically
``
Example a `namepace-handler`:
```html
<!-- A click to this button will execute the exported myFunction of myModule as defined above -->
<button data-action-click="humhub.modules.myModule.myFunction">Do something !</button>
<button data-action-click="myModule.myFunction">Do something !</button>
```
> TIP: The action handler will determine the action url and execute the provides success/error handler automatically
> TIP: If you have multiple actions with different action urls you can specify `data-action-url-click`, `data-action-url-change`,...
data-action-url is always used as fallback
> TIP: The action module binds some default actions like click, dbclick and change to nodes with a data-action-<type> attribute, so these event types do not have to be bound manually.
> TIP: If you have multiple actions with different action urls you can specify `data-action-click-url`, `data-action-change-url`.
### Components
Action components can be used to connect specific dom sections to a javascript action component class. The root of a component is marked with a ´data-action-component´ assignment. This data attribute
contains the component type e.g `humhub.modules.tasks.Task` or short `tasks.Task`. The component class must be dereived from ´humhub.modules.action.Component´.
Action components can be cascaded for to share data between a container and entry components e.g. a `tasks.TaskList` contains multiple `tasks.Task` entries.
Action components can be used to connect specific dom sections to a action component class. The root of a component is marked with a ´data-action-component´ attribute.
This data attribute contains the component type e.g. `tasks.Task`. The component class must be dereived from ´humhub.modules.action.Component´.
Action components can be cascaded to share data between a container and entry components e.g. a `tasks.TaskList` contains multiple `tasks.Task` entries.
The TaskList can provide action urls for all its Task entries and provide additional actions.
For this purpose the components `data` function can be used to search for data values which are either set on the component root itself or a parent component root.
For this purpose the components `data` function can be used to search for data values which are either directly set on the component itself or a parent component.
Example:
```html
<!-- Start of container component TaskList with given data values needed by its entries -->
@ -252,14 +552,19 @@ For this purpose the components `data` function can be used to search for data v
</div>
...
</div>
<!-- By using data-action-target you can run component actions outside of a component -->
<button data-action-click="update" data-action-target="#taskContainer">Update</button>
```
(TBD: component class example)
> TIP: If you want to handle content models as posts which are extending [[humhub\modules\content\components\ContentActiveRecord]] you should extend the content-component described in the next section!
### Content
### Content Components
One of the main tasks of HumHub is the manipulation (create/edit/delete) of content entries as posts, wikis and polls. The `humhub.modules.content` module provides a
interface for representing and handling content entries on the frontend. The following module implements a task module with an Task content component and a Tasklist content component.
One of the main tasks of HumHub is the manipulation (create/edit/delete) of content entries as posts, wikis and polls. The `humhub.modules.content` module provides an
interface for representing and handling content entries in the frontend. The following module implements a task module with an Task content component and a Tasklist content component.
If your content class supports the actions edit and delete, it will have to set a data-content-key attribute with the given content id. This is not necessary if your
implementation does not support these functions as in the TaskList example.
@ -309,8 +614,8 @@ humhub.initModule('tasks', function(module, require, $) {
__How does it work:__
1. An action-event-handler is bound to all dom nodes with a `data-action-click` on startup.
2. When triggered the action-event-handler does check if a direct handler was provided
2. If not it will try to call `Component.handleAction`
2. When triggered, the action event handler does check if a direct handler was provided.
2. If not it will try to call `Component.handleAction`.
3. If this handler does find a sorrounding component it will instantiate the component and try to execute the given handler.
4. If no other handler was found, the handler will try to find a handler in the humhub namespace.
@ -324,6 +629,8 @@ which will overwrite the setting of a parent data-content-base.
## Additions
## Stream
## Modal
## Util

View File

@ -6,10 +6,37 @@ Here you will learn how you can adapt existing themes to working fine with actua
### Stream
Set data-stream attributes for stream
The new stream js rewrite requires some additional data-* attributes, which have to be added in case your theme overwrites either the stream or
streamentry view/layout. Furthermore the frontend modules configuration was added to the `stream.php` file.
Please see the following files for changes:
`protected/humhub/modules/stream/widget/views/stream.php`
`protected/humhub/modules/content/views/layouts/wallEntry.php`
The same applies to the activity stream:
`protected/humhub/modules/activity/widget/views/activityStream.php`
`protected/humhub/modules/activity/views/layouts/web.php`
### Status Bar
We added a new status bar and a loader for pjax loading to the theme.less.
Please see the following file for 1.2 changes (at the buttom):
`themes/HumHub/css/theme.less`
### Layout
// Pjax changes
- Add 'top-menu-nav' to main.php layout.
### Gallery
- Use data-ui-gallery instead of old data-toggle and data-gallery
## Migrate to 1.1
- Make sure to update your themed less file with the latest version.

View File

@ -73,9 +73,20 @@ humhub.initModule('activity', function (module, require, $) {
ActivityStream.prototype.hideLoader = function() {
this.$content.find('#activityLoader').remove();
};
ActivityStream.prototype.onChange = function () {
if(!this.hasEntries()) {
this.$.html('<div id="activityEmpty"><div class="placeholder">'+module.text('activityEmpty')+'</div></div>');
}
};
var getStream = function () {
instance = instance || new ActivityStream($(ACTIVITY_STREAM_SELECTOR));
if(!instance.$.length) {
return;
}
return instance;
};
@ -85,7 +96,7 @@ humhub.initModule('activity', function (module, require, $) {
var stream = getStream();
if (!stream) {
module.log.info('No activity stream found!');
module.log.info('Non-Activity-Stream page!');
return;
}

View File

@ -6,6 +6,15 @@
//$this->registerJsFile('@web/resources/activity/activies.js');
//$this->registerJsVar('activityStreamUrl', $streamUrl);
$this->registerJsVar('activityInfoUrl', $infoUrl);
$this->registerJsConfig([
'activity' => [
'text' => [
'activityEmpty' => Yii::t('ActivityModule.widgets_views_activityStream', 'There are no activities yet.')
]
]
]);
?>
<div class="panel panel-default panel-activities">
@ -13,13 +22,8 @@ $this->registerJsVar('activityInfoUrl', $infoUrl);
<div
class="panel-heading"><?php echo Yii::t('ActivityModule.widgets_views_activityStream', '<strong>Latest</strong> activities'); ?></div>
<div id="activityStream" data-stream="<?= $streamUrl ?>">
<div id="activityEmpty" style="display:none">
<div
class="placeholder"><?php echo Yii::t('ActivityModule.widgets_views_activityStream', 'There are no activities yet.'); ?></div>
</div>
<ul id="activityContents" class="media-list activities" data-stream-content>
</ul>
</div>
</div>

View File

@ -1,5 +1,5 @@
<?php //[STAMP] 25c2b4e8b8fd2158213a3465a3ef3b60
namespace stream\_generated;
<?php //[STAMP] 74c0c3a2840b2412882616390669c863
namespace comment\_generated;
// This class was automatically generated by build task
// You should not change it manually as it will be overwritten on next build

View File

@ -1,5 +1,5 @@
<?php //[STAMP] 0468b7e2480518455b63a22d3aa6f7c2
namespace stream\_generated;
<?php //[STAMP] e93907d5924b39a3cd4134cc6a6ea3a3
namespace comment\_generated;
// This class was automatically generated by build task
// You should not change it manually as it will be overwritten on next build

View File

@ -0,0 +1,24 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\content\assets;
use yii\web\AssetBundle;
class ContentContainerAsset extends AssetBundle
{
public $jsOptions = ['position' => \yii\web\View::POS_END];
public $sourcePath = '@content/assets';
public $css = [];
public $js = [
'js/humhub.content.container.js'
];
}

View File

@ -0,0 +1,34 @@
/**
* This module provides an api for handling content objects e.g. Posts, Polls...
*
* @type undefined|Function
*/
humhub.initModule('content.container', function (module, require, $) {
var client = require('client');
var follow = function(evt) {
var containerId = evt.$trigger.data('content-container-id');
client.post(evt).then(function(response) {
evt.$trigger.hide();
$('[data-content-container-id="'+containerId+'"].unfollowButton').addClass('animated bounceIn').show();
}).catch(function(e) {
module.log.error(e, true);
});
};
var unfollow = function(evt) {
var containerId = evt.$trigger.data('content-container-id');
client.post(evt).then(function(response) {
evt.$trigger.hide();
$('[data-content-container-id="'+containerId+'"].followButton').addClass('animated bounceIn').show();
}).catch(function(e) {
module.log.error(e, true);
});
};
module.export({
follow: follow,
unfollow: unfollow
});
});

View File

@ -161,6 +161,8 @@ class Content extends \humhub\components\ActiveRecord
$this->created_by = Yii::$app->user->id;
}
}
$this->stream_sort_date = new \yii\db\Expression('NOW()');
if ($this->created_by == "")
throw new Exception("Could not save content without created_by!");

View File

@ -13,6 +13,8 @@ use Yii;
class DashboardController extends Controller
{
public $topMenuRoute = '/dashboard/dashboard';
public function init()
{

View File

@ -13,7 +13,7 @@ $object = $this->context->object;
<div class="post-files" id="post-files-<?php echo $object->getUniqueId(); ?>">
<?php foreach ($files as $file): ?>
<?php if ($previewImage->applyFile($file)): ?>
<a data-toggle="lightbox" data-gallery="<?= "gallery-" . $object->getUniqueId(); ?>" href="<?= $file->getUrl(); ?>#.jpeg" data-footer='<button type="button" class="btn btn-primary" data-dismiss="modal"><?= Yii::t('FileModule.widgets_views_showFiles', 'Close'); ?></button>'>
<a data-ui-gallery="<?= "gallery-" . $object->getUniqueId(); ?>" href="<?= $file->getUrl(); ?>#.jpeg">
<img src='<?= $previewImage->getUrl(); ?>'>
</a>
<?php endif; ?>

View File

@ -88,10 +88,10 @@ use yii\helpers\Url;
// change link arrow
$('#access-settings-link i').removeClass('fa-caret-down');
$('#access-settings-link i').addClass('fa-caret-right');
})
});
// prevent enter key and simulate ajax button submit click
$(document).ready(function () {
$(document).on('ready pjax:success', function () {
$(window).keydown(function (event) {
if (event.keyCode == 13) {
event.preventDefault();

View File

@ -98,8 +98,7 @@ if ($space->isAdmin()) {
<?php if ($space->profileImage->hasImage()) : ?>
<!-- profile image output-->
<a data-toggle="lightbox" data-gallery="" href="<?= $space->profileImage->getUrl('_org'); ?>"
data-footer='<button type="button" class="btn btn-primary" data-dismiss="modal"><?php echo Yii::t('SpaceModule.widgets_views_profileHeader', 'Close'); ?></button>'>
<a data-ui-gallery="spaceHeader" href="<?= $space->profileImage->getUrl('_org'); ?>">
<?php echo \humhub\modules\space\widgets\Image::widget(['space' => $space, 'width' => 140]); ?>
</a>
<?php else : ?>

View File

@ -28,7 +28,7 @@
</div>
<script type="text/javascript">
// prevent enter key and simulate ajax button submit click
$(document).ready(function () {
$(document).on('ready pjax:success', function () {
$container = $('#<?= $containerId ?>');
$container.colorpicker({
format: 'hex',

View File

@ -187,8 +187,9 @@ abstract class Stream extends Action
*/
if ($this->sort == self::SORT_UPDATED_AT) {
$this->activeQuery->orderBy('content.stream_sort_date DESC');
if ($this->from != "")
if ($this->from != "") {
$this->activeQuery->andWhere("content.stream_sort_date < (SELECT updated_at FROM content wd WHERE wd.id=" . $this->from . ")");
}
} else {
$this->activeQuery->orderBy('content.id DESC');
if ($this->from != "")

View File

@ -316,10 +316,10 @@ humhub.initModule('stream', function (module, require, $) {
StreamEntry.prototype.stream = function () {
// Just return the parent stream component.
if(!this.$.data('stream')) {
if (!this.$.data('stream')) {
return this.$.data('stream', this.parent());
}
return this.$.data('stream');
};
@ -517,7 +517,6 @@ humhub.initModule('stream', function (module, require, $) {
}
that.loading = false;
that.onChange();
that.$.trigger('humhub:modules:stream:afterLoadEntries', this);
resolve($result);
}).catch(function (err) {
@ -631,22 +630,10 @@ humhub.initModule('stream', function (module, require, $) {
};
/**
* Fired when new entries are shown
* Fired stream entries changed
*/
Stream.prototype.onChange = function () {
var hasEntries = this.hasEntries();
if (!hasEntries && !this.hasFilter()) {
this.$.find('.emptyStreamMessage').show();
this.$filter.hide();
} else if (!hasEntries) {
this.$.find('.emptyFilterStreamMessage').hide();
} else if (!this.isShowSingleEntry()) {
this.$filter.show();
this.$.find('.emptyStreamMessage').hide();
this.$.find('.emptyFilterStreamMessage').hide();
}
this.$entryCache = this.getEntryNodes();
// abstract onChange function
};
/**
@ -788,6 +775,22 @@ humhub.initModule('stream', function (module, require, $) {
object.inherits(WallStream, Stream);
WallStream.prototype.onChange = function () {
var hasEntries = this.hasEntries();
if (!hasEntries && !this.hasFilter()) {
this.$.find('.emptyStreamMessage').show();
this.$filter.hide();
} else if (!hasEntries) {
this.$.find('.emptyFilterStreamMessage').hide();
} else if (!this.isShowSingleEntry()) {
this.$filter.show();
this.$.find('.emptyStreamMessage').hide();
this.$.find('.emptyFilterStreamMessage').hide();
}
this.$entryCache = this.getEntryNodes();
}
/**
* Initializes wall stream
* @returns {undefined}
@ -798,7 +801,7 @@ humhub.initModule('stream', function (module, require, $) {
var stream = getStream();
if (!stream) {
console.log('Non-Stream Page!');
module.log.info('Non-Wall-Stream Page!');
return;
} else {
_initWallStream(stream);

View File

@ -1,4 +1,4 @@
<?php //[STAMP] 890a71cd7f21af7a261c0d3eceef09fb
<?php //[STAMP] 430e094a9cc7da2106caa11ef6778085
namespace stream\_generated;
// This class was automatically generated by build task

View File

@ -18,5 +18,5 @@ modules:
WebDriver:
url: http://localhost:8080/
browser: phantomjs
window_size: 1024x768
window_size: maximize
lang: en

View File

@ -223,8 +223,6 @@ class StreamCest
$I->wait(2);
$I->waitForElementVisible($post4Selector);
$I->wait(20);
$I->see('POST4', '.s2_streamContent > [data-stream-entry]:nth-of-type(1)');
$I->see('POST5', '.s2_streamContent > [data-stream-entry]:nth-of-type(2)');
$I->see('POST3', '.s2_streamContent > [data-stream-entry]:nth-of-type(3)');

View File

@ -43,8 +43,7 @@ $this->registerJs("var profileImageUploaderUrl='" . Url::toRoute('/user/account/
?>
<!-- profile image output-->
<a data-toggle="lightbox" data-gallery="" href="<?php echo $profileImageOrig; ?>#.jpeg"
data-footer='<button type="button" class="btn btn-primary" data-dismiss="modal"><?php echo Yii::t('FileModule.widgets_views_showFiles', 'Close'); ?></button>'>
<a data-ui-gallery="tour" href="<?php echo $profileImageOrig; ?>#.jpeg">
<img class="img-rounded profile-user-photo" id="user-profile-image"
src="<?php echo $user->getProfileImage()->getUrl(); ?>"
data-src="holder.js/140x140" alt="140x140" style="width: 140px; height: 140px;"/>

View File

@ -87,7 +87,8 @@ class ProfileController extends ContentContainerController
$this->getUser()->follow();
if (Yii::$app->request->isAjax) {
return;
Yii::$app->response->format = 'json';
return ['success' => true];
}
return $this->redirect($this->getUser()->getUrl());
@ -99,7 +100,8 @@ class ProfileController extends ContentContainerController
$this->getUser()->unfollow();
if (Yii::$app->request->isAjax) {
return;
Yii::$app->response->format = 'json';
return ['success' => true];
}
return $this->redirect($this->getUser()->getUrl());

View File

@ -1,4 +1,4 @@
<?php //[STAMP] 25c2b4e8b8fd2158213a3465a3ef3b60
<?php //[STAMP] 74c0c3a2840b2412882616390669c863
namespace user\_generated;
// This class was automatically generated by build task

View File

@ -14,8 +14,11 @@ class AccountCest
$I->amOnProfile();
$I->click('Edit account');
$I->waitForText('Account settings');
$I->click('Settings');
$I->waitForText('User settings');
$I->amGoingTo('fill the basic settings form');
$I->fillField('#accountsettings-tags', 'Tester, Actor');
@ -35,7 +38,8 @@ class AccountCest
$I->waitForElementVisible('.data-saved', 5);
*/
$I->wait(3);
$I->seeSuccess('Saved');
$I->amOnProfile();
$I->expectTo('see my user tags');
$I->see('User tags');
@ -52,8 +56,13 @@ class AccountCest
$I->amOnProfile();
$I->click('Edit account');
$I->waitForText('Account settings');
$I->click('Settings');
$I->waitForText('User settings');
$I->click('Notifications'); //Notification tab
$I->waitForText('Send notifications');
$I->expectTo('see the notification settings form');
$I->see('Send notifications?');
$I->see('Send activities?');
@ -63,9 +72,11 @@ class AccountCest
$I->click('Save');
$I->seeSuccess('Saved');
// Refresh page
$I->amOnPage('index-test.php?r=user%2Faccount%2Femailing');
$I->click('Notifications'); //Notification tab
$I->waitForElementVisible('#accountemailing-receive_email_activities');
$I->seeOptionIsSelected('AccountEmailing[receive_email_notifications]', 'Never');
$I->seeOptionIsSelected('AccountEmailing[receive_email_activities]', 'Never');
}

View File

@ -13,6 +13,7 @@ class PasswordRecoveryCest
$I->amGoingTo('request a recovery mail for an invalid user email and wrong captcha');
LoginPage::openBy($I);
$I->click('Create a new one.');
$I->waitForText('Password recovery');
$I->fillField('#email_txt', 'wrong@mail.de');
$I->fillField('#accountrecoverpassword-verifycode', 'wrong');
$I->click('Reset password');

View File

@ -0,0 +1,39 @@
<?php
namespace user\acceptance;
use user\AcceptanceTester;
class UserFollowCest
{
public function testBaseAccountSettings(AcceptanceTester $I)
{
$I->wantTo('test the user follow by directory link');
$I->amUser1();
$I->amOnProfile();
$I->createPost('New User1 profile post');
$I->amUser2(true);
$I->amOnDashboard();
$I->waitForElementVisible('.wall-entry');
$I->dontSee('New User1 profile post', '.wall-entry');
$I->amOnUser1Profile();
$I->see('Follow', '[data-content-container-id="2"].followButton');
$I->click('[data-content-container-id="2"].followButton');
$I->waitForElementVisible('[data-content-container-id="2"].unfollowButton');
$I->amOnDashboard();
$I->waitForElementVisible('.wall-entry');
$I->see('New User1 profile post', '.wall-entry');
}
}

View File

@ -1,7 +1,9 @@
<?php
return [
'fixtures' => ['default']
'fixtures' => ['default',
'user_follow' => 'humhub\modules\user\tests\codeception\fixtures\UserFollowFixture',
'content' => 'humhub\modules\content\tests\codeception\fixtures\ContentFixture']
];

View File

@ -130,7 +130,7 @@ use humhub\modules\user\widgets\AuthChoice;
$('body').find(':checkbox, :radio').flatelements();
$(document).ready(function () {
$(document).on('ready pjax:success', function () {
$('#login_username').focus();
});

View File

@ -101,14 +101,26 @@ class UserFollowButton extends \yii\base\Widget
}
// Add UserId Buttons
$this->followOptions['data-userid'] = $this->user->id;
$this->unfollowOptions['data-userid'] = $this->user->id;
$this->followOptions['data-content-container-id'] = $this->user->id;
$this->unfollowOptions['data-content-container-id'] = $this->user->id;
// Add JS Action
$this->followOptions['data-action-click'] = 'content.container.follow';
$this->unfollowOptions['data-action-click'] = 'content.container.unfollow';
// Add Action Url
$this->followOptions['data-action-url'] = $this->user->createUrl('/user/profile/follow');
$this->unfollowOptions['data-action-url'] = $this->user->createUrl('/user/profile/unfollow');
// Add Action Url
$this->followOptions['data-ui-loader'] = '';
$this->unfollowOptions['data-ui-loader'] = '';
$this->view->registerJsFile('@web/resources/user/followButton.js');
\humhub\modules\content\assets\ContentContainerAsset::register($this->view);
return Html::a($this->unfollowLabel, $this->user->createUrl('/user/profile/unfollow'), $this->unfollowOptions) .
Html::a($this->followLabel, $this->user->createUrl('/user/profile/follow'), $this->followOptions);
return Html::a($this->unfollowLabel, '#', $this->unfollowOptions) .
Html::a($this->followLabel, '#', $this->followOptions);
}
}

View File

@ -95,8 +95,7 @@ if ($isProfileOwner) {
<div class="image-upload-container profile-user-photo-container" style="width: 140px; height: 140px;">
<?php if ($user->profileImage->hasImage()) : ?>
<a data-toggle="lightbox" data-gallery="" href="<?= $user->profileImage->getUrl('_org'); ?>"
data-footer='<button type="button" class="btn btn-primary" data-dismiss="modal"><?php echo Yii::t('FileModule.widgets_views_showFiles', 'Close'); ?></button>'>
<a data-ui-gallery="profileHeader" href="<?= $user->profileImage->getUrl('_org'); ?>">
<img class="img-rounded profile-user-photo" id="user-profile-image"
src="<?php echo $user->getProfileImage()->getUrl(); ?>"
data-src="holder.js/140x140" alt="140x140" style="width: 140px; height: 140px;"/>

View File

@ -69,15 +69,21 @@ class AcceptanceTester extends \Codeception\Actor
$this->click('#post_submit_button');
$this->waitForText($text, 30, '.wall-entry');
}
public function amOnDashboard()
{
tests\codeception\_pages\DashboardPage::openBy($this);
}
public function seeSuccess($text = null)
{
$this->waitForElementVisible('#status-bar .success', 30);
$this->waitForElementVisible('#status-bar .status-bar-close');
if ($text) {
$this->see($text, '#status-bar');
}
$this->waitForElementVisible('#status-bar .status-bar-close');
$this->click('#status-bar .status-bar-close');
$this->waitForElementNotVisible('#status-bar');
}
@ -85,6 +91,8 @@ class AcceptanceTester extends \Codeception\Actor
public function seeWarning($text = null)
{
$this->waitForElementVisible('#status-bar .warning', 20);
$this->waitForElementVisible('#status-bar .status-bar-close');
if ($text) {
$this->see($text, '#status-bar');
}
@ -97,6 +105,8 @@ class AcceptanceTester extends \Codeception\Actor
public function seeError($text = null)
{
$this->waitForElementVisible('#status-bar .error', 20);
$this->waitForElementVisible('#status-bar .status-bar-close');
if ($text) {
$this->see($text, '#status-bar');
}
@ -108,6 +118,8 @@ class AcceptanceTester extends \Codeception\Actor
public function seeInfo($text = null)
{
$this->waitForElementVisible('#status-bar .info', 20);
$this->waitForElementVisible('#status-bar .status-bar-close');
if ($text) {
$this->see($text, '#status-bar');
}

View File

@ -17,8 +17,6 @@
<body>
<?php $this->beginBody() ?>
<?php echo \humhub\widgets\JSConfig::widget(); ?>
<!-- start: first top navigation bar -->
<div id="topbar-first" class="topbar">
<div class="container">
@ -44,7 +42,7 @@
<!-- start: second top navigation bar -->
<div id="topbar-second" class="topbar">
<div class="container">
<ul class="nav ">
<ul class="nav" id="top-menu-nav">
<!-- load space chooser widget -->
<?php echo \humhub\modules\space\widgets\Chooser::widget(); ?>

View File

@ -27,7 +27,7 @@ class BaseMenu extends \yii\base\Widget
* @var string type of the navigation, optional for identifing.
*/
public $type = "";
/**
* @var string dom element id
* @since 1.2
@ -219,7 +219,7 @@ class BaseMenu extends \yii\base\Widget
$this->trigger(self::EVENT_RUN);
return $this->render($this->template, array());
}
/**
* Activates the menu item with the given url
* @param type $url
@ -233,10 +233,11 @@ class BaseMenu extends \yii\base\Widget
}
}
}
/*
* Deactivates the menu item with the given url
*/
public function setInactive($url)
{
foreach ($this->items as $key => $item) {
@ -262,6 +263,12 @@ class BaseMenu extends \yii\base\Widget
\yii\base\Event::on(static::className(), static::EVENT_RUN, function($event) use($url) {
$event->sender->setActive($url);
});
$instance = new static();
if (!empty($instance->id)) {
$instance->view->registerJs('humhub.modules.ui.navigation.setActive("'.$instance->id.'", "'.$url.'");', \yii\web\View::POS_END, 'active-'.$instance->id);
}
}
/**
@ -275,9 +282,9 @@ class BaseMenu extends \yii\base\Widget
if (is_array($url)) {
$url = Url::to($url);
}
\yii\base\Event::on(static::className(), static::EVENT_RUN, function($event) use($url) {
$event->sender->setInactive($url);
$event->sender->setInactive($url);
});
}

View File

@ -0,0 +1,29 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2016 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\widgets;
/**
* BlueimpGallery gallery layout
*
* @see LayoutAddons
* @author buddha
* @since 1.2
*/
class BlueimpGallery extends \yii\base\Widget
{
/**
* @inheritdoc
*/
public function run()
{
return $this->render('blueimpGallery');
}
}

View File

@ -12,11 +12,11 @@ use yii\base\Widget;
use Yii;
/**
* AjaxButton is an replacement for Yii1 CHtml::AjaxButton
* JSConfig LayoutAddition used to configure core js modules.
*
* @author luke
* @author buddha
*/
class JSConfig extends Widget
class CoreJsConfig extends Widget
{
public function run()

View File

@ -24,13 +24,13 @@ class LayoutAddons extends BaseStack
*/
public function init()
{
$this->addWidget(GlobalModal::className());
$this->addWidget(GlobalConfirmModal::className());
$this->addWidget(\humhub\modules\tour\widgets\Tour::className());
$this->addWidget(\humhub\modules\admin\widgets\TrackingWidget::className());
$this->addWidget(LoaderWidget::className(), ['show' => false, 'id' => "humhub-ui-loader-default"]);
$this->addWidget(StatusBar::className());
$this->addWidget(BlueimpGallery::className());
if (Yii::$app->params['enablePjax']) {
$this->addWidget(Pjax::className());

View File

@ -8,6 +8,7 @@
namespace humhub\widgets;
use Yii;
use yii\helpers\Json;
use yii\widgets\PjaxAsset;
@ -44,9 +45,16 @@ class Pjax extends \humhub\components\Widget
{
$view = $this->getView();
PjaxAsset::register($view);
$js = 'jQuery(document).pjax("a", "#layout-content", ' . Json::htmlEncode($this->clientOptions) . ');';
$view->registerJs($js);
$view->registerJsConfig('client.pjax', [
'active' => self::isActive(),
'options' => $this->clientOptions
]);
}
public static function isActive()
{
return Yii::$app->params['enablePjax'];
}
}

View File

@ -34,16 +34,8 @@ class TopMenu extends BaseMenu
* @var String template to use
*/
public $template = "topNavigation";
/**
* Inits the Top Navigation by adding some default items
*/
public function init()
{
parent::init();
}
public $id = 'top-menu-nav';
}
?>

View File

@ -0,0 +1,9 @@
<div id="blueimp-gallery" class="blueimp-gallery blueimp-gallery-controls">
<div class="slides"></div>
<h3 class="title"></h3>
<a class="prev"></a>
<a class="next"></a>
<a class="close">×</a>
<a class="play-pause"></a>
<ol class="indicator"></ol>
</div>

View File

@ -1,117 +0,0 @@
/**
* Click on an Activity wall Entry
*/
function activityShowItem(activityId) {
$.getJSON(activityInfoUrl.replace('-id-', activityId), function (data) {
if (data.success) {
if (typeof mainStream !== "undefined" && data['wallEntryId'] != 0) {
mainStream.showItem(data['wallEntryId']);
} else {
window.location.replace(data['permaLink']);
}
} else {
alert("Error: Could not find activity location!");
}
});
return false;
}
$(document).ready(function () {
var activityLastLoadedEntryId = "";
// save if the last entries are already loaded
var activityLastEntryReached = false;
// listen for scrolling event yes or no
var scrolling = true;
// hide loader
$("#activityLoader").hide();
$('#activityContents').scroll(function () {
// save height of the overflow container
var _containerHeight = $("#activityContents").height();
// save scroll height
var _scrollHeight = $("#activityContents").prop("scrollHeight");
// save current scrollbar position
var _currentScrollPosition = $('#activityContents').scrollTop();
// load more activites if current scroll position is near scroll height
if (_currentScrollPosition >= (_scrollHeight - _containerHeight - 30)) {
// checking if ajax loading is necessary or the last entries are already loaded
if (activityLastEntryReached == false) {
if (scrolling == true) {
// stop listening for scrolling event to load the new activity range only one time
scrolling = false;
// load more activities
loadMoreActivities();
}
}
}
});
/**
* load new activities
*/
function loadMoreActivities() {
// save url for activity reloads
var _url = activityStreamUrl.replace('-from-', activityLastLoadedEntryId);
// show loader
$("#activityLoader").show();
// load json
jQuery.getJSON(_url, function (json) {
if (activityLastLoadedEntryId == "" && json.counter == 0) {
// show placeholder if no results exists
$("#activityEmpty").show();
// hide loader
$("#activityLoader").hide();
} else {
// add new activities
$("#activityLoader").before(json.output);
// save the last activity id for the next reload
activityLastLoadedEntryId = json.lastEntryId;
if (json.counter < 10) {
// prevent the next ajax calls, if there are no more entries
activityLastEntryReached = true;
// hide loader
$("#activityLoader").hide();
}
}
// start listening for the scrolling event
scrolling = true;
});
}
// load the first activities
loadMoreActivities();
});

View File

@ -5,7 +5,7 @@ function resetLogoImage(json) {
$('#text-logo').show();
}
$(document).ready(function () {
$(document).on('ready pjax:success', function () {
// override standard drag and drop behavior
$(document).bind('drop dragover', function (e) {

View File

@ -1,7 +1,10 @@
/**
* Handle Image Upload
*
* TODO: Create API module merge with profileHeaderImage logic...
*
*/
$(function () {
$(document).on('ready pjax:success', function () {
'use strict';
$('.fileupload').each(function () {
@ -84,7 +87,7 @@ $(function () {
$('#banner-image-upload-loader').show();
}).bind('fileuploadstart', function (e) {
$('#space-banner-image').removeClass('animated bounceIn');
})
});
}
@ -92,7 +95,7 @@ $(function () {
});
})
});
/**
@ -124,42 +127,42 @@ function resetProfileImage(json) {
$('.image-upload-buttons').hide();
}
$(document).ready(function () {
$(document).on('ready pjax:success', function () {
// override standard drag and drop behavior
$(document).bind('drop dragover', function (e) {
$(document).off('drop.humhub dragover.humhub').on('drop.humhub dragover.humhub', function (e) {
e.preventDefault();
});
// show buttons at image rollover
$('#profilefileupload').mouseover(function () {
$('#profile-image-upload-buttons').show();
})
});
// show buttons also at buttons rollover (better: prevent the mouseleave event)
$('#profile-image-upload-buttons').mouseover(function () {
$('#profile-image-upload-buttons').show();
})
});
// hide buttons at image mouse leave
$('#profilefileupload').mouseleave(function () {
$('#profile-image-upload-buttons').hide();
})
});
// show buttons at image rollover
$('#bannerfileupload, .img-profile-data').mouseover(function () {
$('#banner-image-upload-buttons').show();
})
});
// show buttons also at buttons rollover (better: prevent the mouseleave event)
$('#banner-image-upload-buttons').mouseover(function () {
$('#banner-image-upload-buttons').show();
})
});
// hide buttons at image mouse leave
$('#bannerfileupload, .img-profile-data').mouseleave(function () {
$('#banner-image-upload-buttons').hide();
})
});
});

View File

@ -1,25 +0,0 @@
$(document).on('click', '.unfollowButton', function (event) {
var userId = $(this).data("userid");
$.ajax({
url: $(this).attr("href"),
type: "POST",
success: function () {
$(".unfollowButton[data-userid='" + userId + "']").hide();
$(".followButton[data-userid='" + userId + "']").show();
}
});
event.preventDefault();
});
$(document).on('click', '.followButton', function (event) {
var userId = $(this).data("userid");
$.ajax({
url: $(this).attr("href"),
type: "POST",
success: function () {
$(".unfollowButton[data-userid='" + userId + "']").show();
$(".followButton[data-userid='" + userId + "']").hide();
}
});
event.preventDefault();
});

View File

@ -1,8 +1,7 @@
/**
* Handle Image Upload
*/
$(function() {
$(document).on('ready pjax:success', function() {
'use strict';
$('.fileupload').each(function() {
@ -122,12 +121,10 @@ function resetProfileImage(json) {
$('.image-upload-buttons').hide();
}
$(document).ready(function() {
$(document).on('ready pjax:success', function() {
// override standard drag and drop behavior
$(document).bind('drop dragover', function(e) {
$(document).off('drop.humhub dragover.humhub').on('drop.humhub dragover.humhub', function(e) {
e.preventDefault();
});

View File

@ -3030,6 +3030,11 @@ img.bounceIn {
background: lighten(@info, 25%) !important;
}
//
// V1.2 Changes:
// --------------------------------------------------
// --------------------------------------------------
#nprogress .bar {
height:2px;
background: @info;