Major js api enhancements + stream rewrite

This commit is contained in:
buddha87 2016-10-29 01:12:52 +02:00
parent c889256aa3
commit ae65760599
68 changed files with 2628 additions and 3206 deletions

1862
js/dist/humhub.all.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

367
js/humhub/humhub.action.js Normal file
View File

@ -0,0 +1,367 @@
/**
* 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.
*/
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 DATA_COMPONENT = 'action-component';
var DATA_COMPONENT_SELECTOR = '[data-' + DATA_COMPONENT + ']';
var Component = function (container) {
if (!container) {
return;
}
this.$ = (object.isString(container)) ? $('#' + container) : container;
this.base = this.$.data(DATA_COMPONENT);
};
Component.prototype.data = function (dataSuffix) {
var result = this.$.data(dataSuffix);
if (!result) {
var parentComponent = this.parent();
if (parentComponent) {
return parentComponent.data(dataSuffix);
}
}
return result;
};
Component.prototype.parent = function () {
var $parent = this.$.parent().closest(DATA_COMPONENT_SELECTOR);
if ($parent.length) {
try {
var ParentType = require($parent.data(DATA_COMPONENT));
return new ParentType($parent);
} catch (err) {
console.error('Could not instantiate parent component: ' + $parent.data(DATA_COMPONENT), err);
}
}
};
Component.prototype.children = function () {
var result = [];
this.$.find(DATA_COMPONENT_SELECTOR).each(function () {
var component = Component.getInstance($(this));
if (component) {
result.push(component);
}
});
return result;
};
Component.prototype.hasAction = function (action) {
return this.actions().indexOf(action) >= 0;
};
Component.prototype.actions = function () {
return [];
};
Component.getInstance = function ($node) {
//Determine closest component node (parent or or given node)
$node = (object.isString($node)) ? $('#' + $node) : $node;
var $componentRoot = ($node.data(DATA_COMPONENT)) ? $node : Component.getClosestComponentNode($node);
var componentType = $componentRoot.data(DATA_COMPONENT);
var ComponentType = require(componentType);
if (ComponentType) {
return new ComponentType($componentRoot);
} else {
module.log.error('Tried to instantiate component with invalid type: ' + componentType);
}
};
Component.getClosestComponentNode = function ($element) {
return $element.closest(DATA_COMPONENT_SELECTOR);
};
/**
* 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 component = Component.getInstance(event.$trigger);
if (component) {
//Check if the content instance provides this actionhandler
if (event.handler && component[event.handler]) {
component[event.handler](event);
return true;
}
}
return false;
};
/**
* 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]');
updateBindings();
//Add addition for loader buttons
require('ui.additions').registerAddition('[data-action-load-button]', function () {
var that = this;
this.on('click.humhub-action-load-button', function (evt) {
if (!that.find('.action-loader').length) {
that.append('<span class="action-loader"><i class="fa fa-spinner fa-pulse"></i></span>');
}
});
});
};
/**
* Registers a given handler with the given id.
*
* This handler will be called e.g. after clicking a button with the handler id as
* data-action-click attribute.
*
* The handler can access additional event information through the argument event.
* The this object within the handler will be the trigger of the event.
*
* @param {string} id handler id should contain the module namespace
* @param {function} handler function with one event argument
* @returns {undefined}
*/
module.registerHandler = function (id, handler) {
if (!id) {
return;
}
if (handler) {
_handler[id] = handler;
}
};
/**
* 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 () {
module.log.debug('Update bindings');
$.each(actionBindings, function (i, binding) {
var $targets = $(binding.selector);
$targets.off(binding.event).on(binding.event, function (evt) {
module.log.debug('Handle direct trigger action', evt);
return binding.handle(evt, $(this));
});
$targets.data('action-'+binding.event, true);
});
};
/**
* ActionBinding instances are used to store the binding settings and handling
* binding events.
*
* @param {type} cfg
* @returns {humhub_action_L5.ActionBinding}
*/
var ActionBinding = function (cfg) {
cfg = cfg || {};
this.parent = cfg.parent;
this.eventType = cfg.type;
this.event = cfg.event;
this.selector = cfg.selector;
this.directHandler = cfg.directHandler;
};
/**
* Handles an action event for the given $trigger node.
*
* This handler searches for a valid handler, by checking the following handler types in the given order:
*
* - 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
* - Namespace-ActionHandler is called if we can resolve an action by namespace e.g: data-action-click="myModule.myAction"
*
* @param {type} evt the originalEvent
* @param {type} $trigger the jQuery node which triggered the event
* @returns {undefined}
*/
ActionBinding.prototype.handle = function (evt, $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
try {
// Check for a direct action handler
if (object.isFunction(this.directHandler)) {
this.directHandler.apply($trigger, [event]);
return;
}
// Check for a component action handler
if (Component.handleAction(event)) {
return;
}
// Check for global registered handlers
if (_handler[this.handler]) {
_handler[this.handler].apply($trigger, [event]);
return;
}
// As last resort we try to call the action by namespace for handlers like humhub.modules.myModule.myAction
var splittedNS = this.handler.split('.');
var handlerAction = splittedNS[splittedNS.length - 1];
var target = require(string.cutsuffix(this.handler, '.' + handlerAction));
if (object.isFunction(target)) {
target[handlerAction](event);
} else {
module.log.error('actionHandlerNotFound', this, true);
}
} catch (e) {
module.log.error('default', e, true);
_removeLoaderFromEventTarget(evt);
} finally {
// Just to get sure the handler is not called twice.
if(evt.originalEvent) {
evt.originalEvent.actionHandled = true;
}
}
};
ActionBinding.prototype.createActionEvent = function (evt, $trigger) {
var event = $.Event(this.eventType);
event.originalEvent = evt;
// Add some additional action related data to our event.
event.$trigger = $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');
//Get the handler id, either a stand alone handler or a content handler function e.g: 'edit'
event.handler = $trigger.data('action' + '-' + this.eventType);
if ($trigger.is(':submit')) {
event.$form = $trigger.closest('form');
}
event.finish = function () {
_removeLoaderFromEventTarget(evt);
};
return event;
};
var _removeLoaderFromEventTarget = function (evt) {
if (evt && evt.target) {
loader.reset(evt.target);
}
};
/**
* Binds an delegate wrapper event handler to the parent node. This is used to detect action handlers like
* data-action-click events and map the call to either a stand alone handler or a content
* action handler. The trigger of a contentAction has to be contained in a data-content-base node.
*
* This function uses the jQuery event delegation:
*
* $(parent).on(type, selector, function(){...});
*
* This assures the event binding for dynamic content (ajax content etc..)
*
* @see {@link humhub.modules.content.handleAction}
* @param {Node|jQuery} parent - the event target
* @param {string} type - event type e.g. click, change,...
* @param {string} selector - jQuery selector
* @param {string} selector - jQuery selector
*/
module.bindAction = function (parent, type, selector, directHandler) {
parent = parent || document;
var $parent = parent.jquery ? parent : $(parent);
var actionEvent = type + '.humhub-action';
var actionBinding = new ActionBinding({
parent: parent,
type: type,
event: actionEvent,
selector: selector,
directHandler: directHandler
});
// Add new ActionBinding with given settings.
actionBindings.push(actionBinding);
$parent.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)
|| evt.originalEvent && evt.originalEvent.actionHandled) {
return;
}
module.log.debug('Detected unhandled action', actionBinding);
updateBindings();
actionBinding.handle(evt, $(this));
});
return;
};
module.export({
Component: Component
});
});

View File

@ -1,274 +0,0 @@
/**
* 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.
*/
humhub.initModule('actions', function (module, require, $) {
var _handler = {};
var object = require('util').object;
var string = require('util').string;
var client = require('client');
var DATA_COMPONENT = 'action-component';
var DATA_COMPONENT_SELECTOR = '[data-'+DATA_COMPONENT+']';
var Component = function(container) {
if(!container) {
return;
}
this.$ = (object.isString(container)) ? $('#' + container) : container;
this.base = this.$.data(DATA_COMPONENT);
};
Component.prototype.data = function(dataSuffix) {
var result = this.$.data(dataSuffix);
if(!result) {
var parentComponent = this.parent();
if(parentComponent) {
return parentComponent.data(dataSuffix);
}
}
return result;
};
Component.prototype.parent = function() {
var $parent = this.$.parent().closest(DATA_COMPONENT_SELECTOR);
if($parent.length) {
try {
var ParentType = require($parent.data(DATA_COMPONENT));
return new ParentType($parent);
} catch(err) {
console.error('Could not instantiate parent component: '+$parent.data(DATA_COMPONENT));
}
}
};
Component.prototype.children = function() {
var result = [];
this.$.find(DATA_COMPONENT_SELECTOR).each(function() {
var component = Component.getInstance($(this));
if(component) {
result.push(component);
}
});
return result;
};
Component.prototype.hasAction = function(action) {
return this.actions().indexOf(action) >= 0;
};
Component.prototype.actions = function() {
return [];
};
Component.getInstance = function($node) {
//Determine closest component node (parent or or given node)
$node = (object.isString($node)) ? $('#'+$node) : $node;
var $componentRoot = ($node.data(DATA_COMPONENT)) ? $node : Component.getClosestComponentNode($node);
var componentType = $componentRoot.data(DATA_COMPONENT);
var ComponentType = require(componentType);
if(ComponentType) {
return new ComponentType($componentRoot);
} else {
console.error('Tried to instantiate component with invalid type: '+componentType);
}
};
Component.getClosestComponentNode = function($element) {
return $element.closest(DATA_COMPONENT_SELECTOR);
};
/**
* 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 component = Component.getInstance(event.$trigger);
if(component) {
//Check if the content instance provides this actionhandler
if(event.handler && component[event.handler]) {
component[event.handler](event);
return true;
}
}
return false;
};
/**
* 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]');
//Add addition for loader buttons
require('additions').registerAddition('[data-action-load-button]', function () {
var that = this;
this.on('click.humhub-action-load-button', function (evt) {
if (!that.find('.action-loader').length) {
that.append('<span class="action-loader"><i class="fa fa-spinner fa-pulse"></i></span>');
}
});
});
};
/**
* Registers a given handler with the given id.
*
* This handler will be called e.g. after clicking a button with the handler id as
* data-action-click attribute.
*
* The handler can access additional event information through the argument event.
* The this object within the handler will be the trigger of the event.
*
* @param {string} id handler id should contain the module namespace
* @param {function} handler function with one event argument
* @returns {undefined}
*/
module.registerHandler = function (id, handler) {
if (!id) {
return;
}
if (handler) {
_handler[id] = handler;
}
};
/**
* 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);
};
}
};
/**
* Binds an delegate wrapper event handler to the parent node. This is used to detect action handlers like
* data-action-click events and map the call to either a stand alone handler or a content
* action handler. The trigger of a contentAction has to be contained in a data-content-base node.
*
* This function uses the jQuery event delegation:
*
* $(parent).on(type, selector, function(){...});
*
* This assures the event binding for dynamic content (ajax content etc..)
*
* @see {@link humhub.modules.content.handleAction}
* @param {Node|jQuery} parent - the event target
* @param {string} type - event type e.g. click, change,...
* @param {string} selector - jQuery selector
* @param {string} selector - jQuery selector
*/
module.bindAction = function (parent, type, selector, directHandler) {
parent = parent || document;
var $parent = parent.jquery ? parent : $(parent);
$parent.on(type+'.humhub-action', selector, function (evt) {
evt.preventDefault();
//The element which triggered the action e.g. a button or link
$trigger = $(this);
//Get the handler id, either a stand alone handler or a content handler function e.g: 'edit'
var handlerId = $trigger.data('action' + '-' + type);
var event = {type: type, $trigger: $trigger, handler: handlerId};
event.finish = function() {
_removeLoaderFromEventTarget(evt);
};
//TODO: handle with $.Event
//var event = $.Event(type, {$trigger: $trigger});
//event.originalEvent = evt;
//Search and execute a stand alone handler or try to call the content action handler
try {
//Direct action handler
if(object.isFunction(directHandler)) {
directHandler.apply($trigger, [event]);
return;
}
//Component handler
if(Component.handleAction(event)) {
return;
}
//Registered handler
if(_handler[handlerId]) {
//Registered action handler
var handler = _handler[handlerId];
handler.apply($trigger, [event]);
return;
}
//As last resort we try to call the action by namespace handler
var splittedNS = handlerId.split('.');
var handler = splittedNS[splittedNS.length - 1];
var target = require(string.cutsuffix(handlerId, '.' + handler));
if(object.isFunction(target)) {
target[handler]({type: type, $trigger: $trigger});
} else {
console.error('Could not determine actionhandler for: '+handlerId);
}
} catch (e) {
//TODO: handle error !
console.error('Error while handling action event for handler "' + handlerId+'"', e);
_removeLoaderFromEventTarget(evt);
}
});
};
var _removeLoaderFromEventTarget = function (evt) {
if (evt.target) {
$target = $(evt.target);
$loader = $target.find('.action-loader');
if ($loader.length) {
$loader.remove();
}
}
};
module.export({
Component: Component
});
});

View File

@ -5,32 +5,17 @@
humhub.initModule('client', function (module, require, $) {
var object = require('util').object;
var init = function () {
/*$.ajaxPrefilter('html', function(options, originalOptions, jqXHR) {
debugger;
console.log(options);
var pjaxHandler = options.success;
options.success = function(result, textStatus, xhr) {
console.log(result);
pjaxHandler(result, textStatus, xhr);
};
options.error = function(err) {
debugger;
};
});
$.pjax.defaults.maxCacheLength = 0;
$('a.dashboard').on('click', function(evt) {
debugger;
evt.preventDefault();
$.pjax({url:$(this).attr('href'), container: '#main-content', maxCacheLength:0, timeout:2000});
});*/
}
/**
* Response Wrapper Object for easily accessing common data
*/
var Response = function (data) {
$.extend(this, data);
var Response = function (data, dataType) {
if(!dataType || dataType === 'json') {
$.extend(this, data);
} else if(dataType) {
this[dataType] = data;
} else {
this.data = data;
}
};
/**
@ -88,7 +73,8 @@ humhub.initModule('client', function (module, require, $) {
$form = object.isString($form) ? $($form) : $form;
cfg.type = $form.attr('method') || 'post';
cfg.data = $form.serialize();
ajax($form.attr('action'), cfg);
var url = cfg['url'] || $form.attr('action');
return ajax(url, cfg);
};
var post = function (path, cfg) {
@ -98,27 +84,38 @@ humhub.initModule('client', function (module, require, $) {
return ajax(path, cfg);
};
var get = function (path, cfg) {
var cfg = cfg || {};
cfg.type = 'GET';
cfg.method = 'GET';
return ajax(path, cfg);
};
var ajax = function (path, cfg) {
if(object.isFunction(cfg)) {
cfg = {'success' : cfg};
}
var promise = new Promise(function (resolve, reject) {
cfg = cfg || {};
//Wrap the actual error handler with our own and call
var errorHandler = cfg.error;
var error = function (xhr, textStatus, errorThrown, data, status) {
var error = function (xhr, textStatus, errorThrown, data) {
//Textstatus = "timeout", "error", "abort", "parsererror", "application"
if (errorHandler && object.isFunction(errorHandler)) {
var response = new Response();
response.setAjaxError(xhr, errorThrown, textStatus, data, status);
response.setAjaxError(xhr, errorThrown, textStatus, data, xhr.status);
errorHandler(response);
}
reject(xhr, textStatus, errorThrown, data, status);
reject({'textStatus': textStatus, 'response': xhr.responseJSON, 'error': errorThrown, 'data': data, 'status': xhr.status});
};
var successHandler = cfg.success;
var success = function (json, textStatus, xhr) {
var response = new Response(json);
var success = function (data, textStatus, xhr) {
var response = new Response(data, cfg.dataType);
if (response.isError()) { //Application errors
return error(xhr, "application", response.getErrors(), json, response.getStatus());
return error(xhr, "application", response.getErrors(), data, response.getStatus());
} else if (successHandler) {
response.textStatus = textStatus;
response.xhr = xhr;
@ -129,7 +126,7 @@ humhub.initModule('client', function (module, require, $) {
promise.then(function() {
// If content with <link> tags are inserted in resolve, the ajaxComplete handler in yii.js
// makes shure redundant stylesheets are removed.
// makes sure redundant stylesheets are removed. Here we get sure it is called after inserting the response.
$(document).trigger('ajaxComplete');
});
@ -152,8 +149,8 @@ humhub.initModule('client', function (module, require, $) {
module.export({
ajax: ajax,
post: post,
submit: submit,
init: init
get: get,
submit: submit
});
});

View File

@ -1,10 +1,12 @@
humhub.initModule('client.pjax', function (module, require, $) {
var object = require('util').object;
var event = require('event');
module.initOnPjaxLoad = false;
var init = function () {
pjaxRedirectFix();
installLoader();
}
module.installLoader();
};
var pjaxRedirectFix = function () {
var pjaxXhr;
@ -18,8 +20,18 @@ humhub.initModule('client.pjax', function (module, require, $) {
}
});
$(document).on("pjax:success", function (evt, data, status, xhr, options) {
event.trigger('humhub:modules:client:pjax:afterPageLoad', {
'originalEvent': evt,
'data': data,
'status': status,
'xhr': xhr,
'options': options
});
});
$.ajaxPrefilter('html', function (options, originalOptions, jqXHR) {
orgErrorHandler = options.error;
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') != "") {
@ -31,19 +43,20 @@ humhub.initModule('client.pjax', function (module, require, $) {
}
}
});
}
};
var installLoader = function () {
NProgress.configure({showSpinner: false});
NProgress.configure({template: '<div class="bar" role="bar"></div>'});
jQuery(document).on('pjax:start', function () {
$(document).on('pjax:start', function () {
NProgress.start();
});
jQuery(document).on('pjax:end', function () {
$(document).on('pjax:end', function () {
NProgress.done();
});
}
};
module.export({
init: init,

View File

@ -13,6 +13,12 @@ var humhub = humhub || (function($) {
*/
var modules = {};
/**
* Flat array with all registered modules.
* @type Array
*/
var moduleArr = [];
/**
* Used to collect modules added while initial page load.
* These modules will be intitialized after the document is ready.
@ -20,6 +26,13 @@ var humhub = humhub || (function($) {
*/
var initialModules = [];
/**
* Contains all modules which needs to be reinitialized after a pjax reload
* @type Array
*/
var pjaxInitModules = [];
/**
* Is set wehen document is ready
* @type Boolean
@ -27,7 +40,8 @@ var humhub = humhub || (function($) {
var initialized = false;
/**
* Adds an module to the namespace. And initializes either after dom is ready.
* Adds a module to the namespace. And initializes after dom is ready.
*
* The id can be provided either as
*
* - full namespace humhub.modules.ui.modal
@ -81,6 +95,14 @@ var humhub = humhub || (function($) {
var instance = resolveNameSpace(id, true);
instance.id = 'humhub.modules.'+_cutModulePrefix(id);
instance.require = require;
instance.initOnPjaxLoad = true;
instance.config = require('config').module(instance);
instance.isModule = true;
instance.text = function($key) {
return instance.config['text'][$key];
};
instance.export = function(exports) {
$.extend(instance, exports);
};
@ -92,10 +114,17 @@ var humhub = humhub || (function($) {
console.error('Error while creating module: '+id, err);
}
moduleArr.push(instance);
if(instance.init && instance.initOnPjaxLoad) {
pjaxInitModules.push(instance);
}
//Initialize the module when document is ready
if(!initialized) {
initialModules.push(instance);
} else {
addModuleLogger(instance);
instance.init();
}
};
@ -148,25 +177,29 @@ var humhub = humhub || (function($) {
});
return result;
} catch(e) {
//TODO: handle could not resolve type/namespace error
return;
var log = require('log') || console;
log.error('Error while resolving namespace: '+typePathe, e);
}
};
/**
/**
* Config implementation
*/
var config = modules['config'] = {
id : 'config',
var config = {
get : function(module, key, defaultVal) {
if(arguments.length === 1) {
return this.getModuleConfig(module);
return this.module(module);
} else if(_isDefined(key)) {
var result = this.getModuleConfig(module)[key];
var result = this.module(module)[key];
return (_isDefined(result)) ? result : defaultVal;
}
},
getModuleConfig: function(module) {
module: function(module) {
module = (module.id) ? module.id : module;
module = _cutModulePrefix(module);
if(!this[module]) {
this[module] = {};
}
@ -185,13 +218,29 @@ var humhub = humhub || (function($) {
that.set(moduleKey, config);
});
}else if(arguments.length === 2) {
$.extend(this.getModuleConfig(moduleId), key);
$.extend(this.module(moduleId), key);
} else if(arguments.length === 3) {
this.getModuleConfig(moduleId)[key] = value;
this.module(moduleId)[key] = value;
}
}
};
var event = modules['event'] = {
events : $({}),
on : function(event, selector, data, handler) {
this.events.on(event , selector, data, handler);
return this;
},
trigger : function(eventType, extraParameters) {
this.events.trigger(eventType, extraParameters);
return this;
},
one : function(event, selector, data, handler) {
this.events.one(event , selector, data, handler);
return this;
}
};
/**
* Cuts the prefix humub.modules or modules. from the given value.
* @param {type} value
@ -233,18 +282,43 @@ var humhub = humhub || (function($) {
return typeof obj !== 'undefined';
};
var addModuleLogger = function(module, log) {
log = log || require('log');
module.log = log.module(module);
}
//Initialize all initial modules
$(document).ready(function() {
var log = require('log');
$.each(moduleArr, function(i, module) {
addModuleLogger(module, log);
});
$.each(initialModules, function(i, module) {
event.trigger('humhub:beforeInitModule', module);
if(module.init) {
try {
event.trigger(module.id)
module.init();
} catch(err) {
console.error('Could not initialize module: '+module.id, err);
log.error('Could not initialize module: '+module.id, err);
}
}
initialized = true;
console.log('Module initialized: '+module.id);
event.trigger('humhub:afterInitModule', module);
log.debug('Module initialized: '+module.id);
});
event.trigger('humhub:afterInit');
initialized = true;
});
event.on('humhub:modules:client:pjax:afterPageLoad', function (evt) {
$.each(pjaxInitModules, function(i, module) {
if(module.initOnPjaxLoad) {
module.init();
}
});
});
@ -253,6 +327,7 @@ var humhub = humhub || (function($) {
return {
initModule: initModule,
modules: modules,
config: config
config: config,
event: event,
};
})($);

View File

@ -1,331 +0,0 @@
var humhub = humhub || {};
humhub.util = (function(module, $) {
module.object = {
isFunction: function(obj) {
return Object.prototype.toString.call(obj) === '[object Function]';
},
isObject: function(obj) {
return $.isPlainObject(obj);
},
isJQuery: function(obj) {
return obj.jquery;
},
isString: function(obj) {
return typeof obj === 'string';
},
isNumber: function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
},
isBoolean: function(obj) {
return typeof obj === 'boolean';
},
isDefined: function(obj) {
if(arguments.length > 1) {
var result = true;
var that = this;
this.each(arguments, function(index, value) {
if(!that.isDefined(value)) {
result = false;
return false;
}
});
return result;
}
return typeof obj !== 'undefined';
}
};
return module;
})(humhub.util || {}, $);
humhub.modules = (function(module, $) {
var _handler = {};
var _errorHandler = {};
var object = humhub.util.object;
var DATA_ACTION = 'action';
module.registerHandler = function(id, handler) {
if(!id) {
return;
}
if(handler) {
_handler[id] = handler;
}
};
module.registerAjaxHandler = function(id, success, error, cfg) {
debugger;
cfg = cfg || {};
if(!id) {
return;
}
if(object.isFunction(success)) {
cfg.success = success;
cfg.error = error;
} else {
cfg = success;
}
if(success) {
var that = this;
_handler[id] = function(event) {
var path = $(this).data('url-'+event.type) || $(this).data('url');
that.ajax(path, cfg, event);
};
}
if(error) {
_errorHandler[id] = success;
}
};
module.bindAction = function(parent, type, selector) {
parent = parent || document;
var $parent = parent.jquery ? parent : $(parent);
$parent.on(type, selector, function(evt) {
evt.preventDefault();
//The element which triggered the action e.g. a button or link
$trigger = $(this);
var handlerId = $trigger.data(DATA_ACTION+'-'+type);
var handler = _handler[handlerId];
var event = {type:type, $trigger:$trigger};
handler.apply($trigger, [event]);
});
};
module.bindAction(document, 'click', '[data-action-click]');
/**
* Response Wrapper Object for
* easily accessing common data
*/
var Response = function(data) {
this.data = data;
};
Response.prototype.isConfirmation = function() {
return this.data && (this.data.status === 0);
};
Response.prototype.isError = function() {
return this.data && this.data.status && (this.data.status > 0);
};
Response.prototype.getErrors = function() {
return this.data.errors;
};
Response.prototype.getErrorCode = function() {
return this.data.errorCode;
};
Response.prototype.toString = function() {
return "{ status: "+this.data.status+" error: "+this.data.error+" data: "+this.data.data+" }";
};
var errorHandler = function(cfg, xhr,type,errorThrown, errorCode, path) {
errorCode = (xhr) ? xhr.status : parseInt(errorCode);
console.warn("AjaxError: "+type+" "+errorThrown+" - "+errorCode);
if(cfg.error && object.isFunction(cfg.error)) {
// "timeout", "error", "abort", "parsererror" or "application"
//TODO: den trigger als this verwenden
cfg.error(errorThrown, errorCode, type);
} else {
console.warn('Unhandled ajax error: '+path+" type"+type+" error: "+errorThrown);
}
};
module.ajax = function(path, cfg) {
var cfg = cfg || {};
var async = cfg.async || true;
var dataType = cfg.dataType || "json";
var error = function(xhr,type,errorThrown, errorCode) {
errorHandler(cfg, xhr,type,errorThrown, errorCode, path);
};
var success = function(response) {
var responseWrapper = new Response(response);
if(responseWrapper.isError()) { //Application errors
return error(undefined,"application",responseWrapper.getError(), responseWrapper.getErrorCode());
} else if(cfg.success) {
cfg.success(responseWrapper);
}
};
$.ajax({
url: path,
//crossDomain: true, //TODO: read from config
type : cfg.type,
processData : cfg.processData,
contentType: cfg.contentType,
async : async,
dataType: dataType,
success: success,
error: error
});
};
return module;
})(humhub.modules || {}, $);
humhub.modules.stream = (function(module, $) {
var ENTRY_ID_SELECTOR_PREFIX = '#wallEntry_';
var WALLSTREAM_ID = 'wallStream';
module.Entry = function(id) {
if(typeof id === 'string') {
this.id = id;
this.$ = $('#'+id);
} else if(id.jquery) {
this.$ = id;
this.id = this.$.attr('id');
}
};
module.Entry.prototype.remove = function() {
this.$.remove();
};
module.Entry.prototype.highlightContent = function() {
var $content = this.getContent();
$content.addClass('highlight');
$content.delay(200).animate({backgroundColor: 'transparent'}, 1000, function() {
$content.removeClass('highlight');
$content.css('backgroundColor', '');
});
};
module.Entry.prototype.getContent = function() {
return this.$.find('.content');
};
module.Stream = function(id) {
this.id = id;
this.$ = $('#'+id);
};
module.Stream.prototype.getEntry = function(id) {
return new module.Entry(this.$.find(ENTRY_ID_SELECTOR_PREFIX+id));
};
module.Stream.prototype.wallStick = function(url) {
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
currentStream.deleteEntry(wallEntryId);
currentStream.prependEntry(wallEntryId);
});
$('html, body').animate({scrollTop: 0}, 'slow');
}
} else {
alert(data.errorMessage);
}
});
};
module.Stream.prototype.wallUnstick = function(url) {
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
//Reload the whole stream, since we have to reorder the entries
currentStream.showStream();
}
});
};
/**
* Click Handler for Archive Link of Wall Posts
* (archiveLink.php)
*
* @param {type} className
* @param {type} id
*/
module.Stream.prototype.wallArchive = function(id) {
url = wallArchiveLinkUrl.replace('-id-', id);
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
//currentStream.reloadWallEntry(wallEntryId);
// fade out post
setInterval(fadeOut(), 1000);
function fadeOut() {
// fade out current archived post
$('#wallEntry_' + wallEntryId).fadeOut('slow');
}
});
}
}
});
};
/**
* Click Handler for Un Archive Link of Wall Posts
* (archiveLink.php)
*
* @param {type} className
* @param {type} id
*/
module.Stream.prototype.wallUnarchive = function(id) {
url = wallUnarchiveLinkUrl.replace('-id-', id);
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
currentStream.reloadWallEntry(wallEntryId);
});
}
}
});
};
module.getStream = function() {
if(!module.mainStream) {
module.mainStream = new module.Stream(WALLSTREAM_ID);
}
return module.mainStream;
};
module.getEntry = function(id) {
return module.getStream().getEntry(id);
};
return module;
})(humhub.core || {}, $);

230
js/humhub/humhub.log.js Normal file
View File

@ -0,0 +1,230 @@
/**
*
* @param {type} param1
* @param {type} param2
*/
humhub.initModule('log', function (module, require, $) {
var event = require('event');
var TRACE_TRACE = 0;
var TRACE_DEBUG = 1;
var TRACE_INFO = 2;
var TRACE_SUCCESS = 3;
var TRACE_WARN = 4;
var TRACE_ERROR = 5;
var TRACE_FATAL = 6;
var TRACE_OFF = 7;
var traceLevels = ['TRACE', 'DEBUG', 'INFO', 'SUCCESS', 'WARN', 'ERROR', 'FATAL', 'OFF'];
var config = require('config').module(module);
var object = require('util').object;
var logger = {};
var Logger = function (module) {
if(object.isString(module)) {
this.module = require('module');
this.moduleId = module;
} else if(module){
this.module = module;
this.moduleId = module.id;
}
this.update();
};
Logger.prototype.update = function () {
var result;
if (this.moduleId) {
var moduleConfig = require('config').module(this.moduleId);
if (moduleConfig.traceLevel && traceLevels.indexOf(moduleConfig.traceLevel.toUpperCase()) >= 0) {
result = traceLevels.indexOf(moduleConfig.traceLevel.toUpperCase());
}
}
if (!result) {
result = config.traceLevel || TRACE_INFO;
}
return this.traceLevel = result;
};
Logger.prototype.trace = function (msg,details, setStatus) {
this._log(msg, details, setStatus, TRACE_TRACE);
};
Logger.prototype.debug = function (msg, details, setStatus) {
this._log(msg, details, setStatus, TRACE_DEBUG);
};
Logger.prototype.info = function (msg, details, setStatus) {
this._log(msg, details, setStatus, TRACE_INFO);
};
Logger.prototype.success = function (msg, setStatus) {
setStatus = object.isDefined(setStatus) ? setStatus : true;
this._log(msg, undefined, setStatus, TRACE_SUCCESS);
};
Logger.prototype.warn = function (msg, error, setStatus) {
this._log(msg, error, setStatus, TRACE_WARN);
};
Logger.prototype.error = function (msg, error, setStatus) {
this._log(msg, error, setStatus, TRACE_ERROR);
};
Logger.prototype.fatal = function (msg, error, setStatus) {
this._log(msg, error, setStatus, TRACE_FATAL);
};
Logger.prototype._log = function (msg, details, setStatus, level) {
try {
if (object.isBoolean(details)) {
setStatus = details;
details = undefined;
}
if(msg instanceof Error && level >= TRACE_WARN) {
details = msg;
msg = this.getMessage(details.message, true);
} else if(object.isObject(msg) && msg.status && level >= TRACE_WARN) {
details = msg;
msg = this.getMessage(msg.status, true);
} else if(!object.isObject(msg)) {
msg = this.getMessage(msg, (!object.isDefined(msg) && level >= TRACE_WARN));
}
if (this.traceLevel > level) {
return;
}
this._consoleLog(msg, level, details);
if (setStatus) {
event.trigger('humhub:modules:log:setStatus', [msg, details, level]);
}
} catch(e) {
console.error('Error while generating log', e);
}
};
Logger.prototype.getMessage = function (key, returnDefault) {
if(!object.isString(key)) {
return key;
}
var result;
if(this.module) {
result = this.module.text(key);
}
if(!result) {
result = module.text(key);
}
if(!result && returnDefault) {
result = module.text('default.error');
} else if(!result){
result = key;
}
return result;
};
Logger.prototype._consoleLog = function (msg, level, details) {
if (window.console) {
var consoleMsg = this.moduleId || 'root';
consoleMsg += '(' + traceLevels[level] + '): ' + msg;
switch (level) {
case TRACE_ERROR:
case TRACE_FATAL:
console.error(consoleMsg, details);
break;
case TRACE_WARN:
if (details) {
console.warn(consoleMsg, details);
} else {
console.warn(consoleMsg);
}
break;
default:
if (details) {
console.log(consoleMsg, details);
} else {
console.log(consoleMsg);
}
break;
}
}
};
var init = function () {
module.rootLogger = new Logger();
};
var getRootLogger = function () {
if (!module.rootLogger) {
module.rootLogger = new Logger();
}
return module.rootLogger;
};
var getModuleLogger = function (module) {
var moduleId = (object.isString(module)) ? module : module.id;
if (!logger[moduleId]) {
logger[moduleId] = new Logger(module);
}
return logger[moduleId];
};
var trace = function (msg, details, setStatus) {
module.getRootLogger().trace(msg, details, setStatus);
};
var debug = function (msg, details, setStatus) {
module.getRootLogger().debug(msg, details, setStatus);
};
var success = function (msg, details, setStatus) {
module.getRootLogger().success(msg, details, setStatus);
};
var info = function (msg, details, setStatus) {
module.getRootLogger().info(msg, details, setStatus);
};
var warn = function (msg, error, setStatus) {
module.getRootLogger().warn(msg, error, setStatus);
};
var error = function (msg, error, setStatus) {
module.getRootLogger().error(msg, error, setStatus);
};
var fatal = function (msg, error, setStatus) {
module.getRootLogger().fatal(msg, error, setStatus);
};
module.export({
init: init,
Logger: Logger,
module: getModuleLogger,
getRootLogger: getRootLogger,
trace: trace,
debug: debug,
info: info,
success: success,
warn: warn,
error: error,
fata: fatal,
TRACE_TRACE: TRACE_TRACE,
TRACE_DEBUG: TRACE_DEBUG,
TRACE_INFO: TRACE_INFO,
TRACE_SUCCESS: TRACE_SUCCESS,
TRACE_WARN: TRACE_WARN,
TRACE_ERROR: TRACE_ERROR,
TRACE_OFF: TRACE_OFF
});
});

View File

@ -5,7 +5,10 @@
* An addition can be registered for a specific selector e.g: <input data-addition-richtext ... />
* It is possible to register multiple additions for the same selector.
*/
humhub.initModule('additions', function(module, require, $) {
humhub.initModule('ui.additions', function (module, require, $) {
var event = require('event');
var _additions = {};
/**
@ -17,7 +20,7 @@ humhub.initModule('additions', function(module, require, $) {
* @returns {undefined}
*/
module.registerAddition = function (selector, addition) {
if(!_additions[selector]) {
if (!_additions[selector]) {
_additions[selector] = [];
}
@ -29,23 +32,35 @@ humhub.initModule('additions', function(module, require, $) {
* @param {type} element
* @returns {undefined}
*/
module.applyTo = function(element) {
module.applyTo = function (element) {
var $element = $(element);
$.each(_additions, function(selector, additions) {
$.each(additions, function(i, addition) {
$.each($element.find(selector).addBack(selector), function() {
$.each(_additions, function (selector, additions) {
$.each(additions, function (i, addition) {
$.each($element.find(selector).addBack(selector), function () {
try {
var $match = $(this);
addition.apply($match, [$match, $element]);
} catch(e) {
console.error('Error while applying addition '+addition+' on selector '+selector);
} catch (e) {
console.error('Error while applying addition on selector ' + selector, e);
}
});
});
});
};
module.init = function() {
module.init = function () {
event.on('humhub:modules:client:pjax:afterPageLoad', function (evt, cfg) {
module.applyTo(cfg.options.container);
});
event.on('humhub:afterInit', function (evt) {
module.applyTo($('html'));
});
this.registerAddition('.autosize', function ($match) {
$match.autosize();
});
//TODO: apply to html on startup, the problem is this could crash legacy code.
};
});

View File

@ -1,8 +0,0 @@
humhub.initModule('ui', function(module, require, $) {
var additions = require('additions');
module.init = function() {
additions.registerAddition('.autosize', function($match) {
$match.autosize();
});
};
});

View File

@ -0,0 +1,166 @@
/**
* Module for adding loader animations to dom nodes.
*
* The default loader animation can be added or appended/prepended as follows
*
* var loader = require('ui.loader');
*
* // Overwrite current html content with loader animation
* loader.set(myNode);
*
* // Remove loader animation
* loader.reset(myNode);
*
* The loader module also adds an click handler to all buttons and links with a
* data-ui-loader attribute set.
*
* If a data-ui-loader button is used within a yii ActiveForm we automaticly reset all loader buttons
* in case of form validation errors.
*
*
* @param {type} param1
* @param {type} param2
*/
humhub.initModule('ui.loader', function (module, require, $) {
var DEFAULT_LOADER_SELECTOR = '#humhub-ui-loader-default';
module.initOnPjaxLoad = false;
var set = function (node, cfg) {
var $node = $(node);
if ($node.length) {
$node.each(function () {
var $this = $(this);
$this.data('htmlOld', $node.html());
$this.html(getInstance(cfg));
});
}
};
var append = function (node, cfg) {
var $node = $(node);
if ($node.length) {
$node.append(getInstance(cfg));
}
};
var prepend = function (node, cfg) {
var $node = $(node);
if ($node.length) {
$node.prepend(getInstance(cfg));
}
};
var reset = function (node) {
var $node = $(node);
var $loader = $node.find('.loader').length;
if (!$loader) {
return;
}
$node.removeClass('disabled');
if ($loader && $node.data('htmlOld')) {
$node.html($node.data('htmlOld'));
} else if ($loader) {
$node.find('.loader').remove();
}
};
var getInstance = function (cfg) {
cfg = cfg || {};
var $result = $(DEFAULT_LOADER_SELECTOR).clone().removeAttr('id').show();
if (cfg['cssClass']) {
$result.addClass(cfg['cssClass']);
}
if (cfg['id']) {
$result.attr('id', cfg['id']);
}
if (cfg['css']) {
$result.css(cfg['css']);
}
if (cfg['position']) {
if (cfg['position'] === 'left') {
$result.find('.sk-spinner').css('margin', '0');
}
$result.css(cfg['css']);
}
if (cfg['size']) {
var size = cfg['size'];
$result.find('.sk-bounce1').css({'width': size, 'height': size});
$result.find('.sk-bounce2').css({'width': size, 'height': size});
$result.find('.sk-bounce3').css({'width': size, 'height': size});
}
return $result;
};
var init = function (cfg) {
$(document).on('click.humhub:modules:ui:loader', 'a[data-ui-loader], button[data-ui-loader]', function (evt) {
module.initLoaderButton(this, evt);
});
$(document).on('afterValidate.humhub:modules:ui:loader', function (evt, messages, errors) {
if (errors.length) {
$(evt.target).find('[data-ui-loader]').each(function () {
reset(this);
});
}
});
};
var initLoaderButton = function (node, evt) {
var $node = $(node);
var loader = $node.find('.loader').length > 0;
/**
* Prevent multiple mouse clicks, if originalEvent is present its a real mouse event otherwise its script triggered
* This is a workaround since yii version 2.0.10 changed the activeForm submission from $form.submit() to data.submitObject.trigger("click");
* which triggers this handler twice. Here we get sure not to block the script triggered submission.
*/
if (loader && evt.originalEvent) {
return false;
} else if (loader) {
return;
}
// Adopt current color for the loader animation
var color = $node.css('color') || '#ffffff';
var $loader = $(module.template);
// Align bouncer animation color and size
$loader.find('.sk-bounce1, .sk-bounce2, .sk-bounce3')
.addClass('disabled')
.css({'background-color': color, 'width': '10px', 'height': '10px'});
// The loader does have some margin we have to hide
$node.css('overflow', 'hidden');
$node.addClass('disabled');
// Prevent the container from resizing
$node.css('min-width', node.getBoundingClientRect().width);
$node.data('htmlOld', $node.html());
$node.html($loader);
};
var template = '<span class="loader"><span class="sk-spinner sk-spinner-three-bounce"><span class="sk-bounce1"></span><span class="sk-bounce2"></span><span class="sk-bounce3"></span></span></span>';
module.export({
set: set,
append: append,
prepend: prepend,
reset: reset,
getInstance: getInstance,
template: template,
initLoaderButton: initLoaderButton,
init: init
});
});

View File

@ -17,14 +17,26 @@
*/
humhub.initModule('ui.modal', function (module, require, $) {
var object = require('util').object;
var additions = require('additions');
var config = humhub.config.getModuleConfig('ui.modal');
var additions = require('ui.additions');
var config = require('config').module(module);
var loader = require('ui.loader');
module.initOnPjaxLoad = false;
//Keeps track of all initialized modals
var modals = [];
var TMPL_MODAL_CONTAINER = '<div class="modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="display: none; background:rgba(0,0,0,0.1)"><div class="modal-dialog"><div class="modal-content"></div></div></div>';
var TMPL_MODAL_HEADER = '<div class="modal-header"><button type="button" class="close" data-modal-close="true" aria-hidden="true">×</button><h4 class="modal-title"></h4></div>';
var TMPL_MODAL_BODY = '<div class="modal-body"></div>';
/**
* Template for the modal splitted into different parts. Those can be overwritten my changing or overwriting module.template.
*/
var template = {
container : '<div class="modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="display: none; background:rgba(0,0,0,0.1)"><div class="modal-dialog"><div class="modal-content"></div></div></div>',
header : '<div class="modal-header"><button type="button" class="close" data-modal-close="true" aria-hidden="true">×</button><h4 class="modal-title"></h4></div>',
body: '<div class="modal-body"></div>',
}
var ERROR_DEFAULT_TITLE = 'Error';
var ERROR_DEFAULT_MESSAGE = 'An unknown error occured!';
@ -50,7 +62,7 @@ humhub.initModule('ui.modal', function (module, require, $) {
* @returns {undefined}
*/
Modal.prototype.createModal = function (id) {
this.$modal = $(TMPL_MODAL_CONTAINER).attr('id', id);
this.$modal = $(module.template.container).attr('id', id);
$('body').append(this.$modal);
};
@ -97,7 +109,7 @@ humhub.initModule('ui.modal', function (module, require, $) {
* @returns {undefined}
*/
Modal.prototype.reset = function () {
this.setBody('<div class="loader"><div class="sk-spinner sk-spinner-three-bounce"><div class="sk-bounce1"></div><div class="sk-bounce2"></div><div class="sk-bounce3"></div></div></div>');
loader.set(this.$body);
this.isFilled = false;
};
@ -241,7 +253,7 @@ humhub.initModule('ui.modal', function (module, require, $) {
Modal.prototype.setTitle = function (title) {
var $header = this.getHeader();
if (!$header.length) {
this.getContent().prepend($(TMPL_MODAL_HEADER));
this.getContent().prepend($(module.template.header));
$header = this.getHeader();
}
$header.find('.modal-title').html(title);
@ -255,7 +267,7 @@ humhub.initModule('ui.modal', function (module, require, $) {
Modal.prototype.setBody = function (content) {
var $body = this.getBody();
if (!$body.length) {
this.getContent().append($(TMPL_MODAL_BODY));
this.getContent().append($(module.template.body));
$body = this.getBody();
}
$body.html(content);
@ -277,13 +289,14 @@ humhub.initModule('ui.modal', function (module, require, $) {
return this.$modal.find('.modal-body');
};
var ConfirmModal = function(id, cfg) {
var ConfirmModal = function(id) {
Modal.call(this, id);
};
object.inherits(ConfirmModal, Modal);
ConfirmModal.prototype.open = function(cfg) {
cfg = cfg || {};
this.clear();
cfg['header'] = cfg['header'] || config['defaultConfirmHeader'];
cfg['body'] = cfg['body'] || config['defaultConfirmBody'];
@ -323,8 +336,6 @@ humhub.initModule('ui.modal', function (module, require, $) {
cfg['cancel'](evt);
});
}
};
module.export({
@ -335,6 +346,7 @@ humhub.initModule('ui.modal', function (module, require, $) {
module.globalConfirm.open(cfg);
};
},
Modal: Modal
Modal: Modal,
template: template
});
});

View File

@ -0,0 +1,189 @@
/**
*
* @param {type} param1
* @param {type} param2
*/
humhub.initModule('ui.status', function (module, require, $) {
var event = require('event');
var log = require('log');
var object = require('util').object;
var SELECTOR_ROOT = '#status-bar';
var SELECTOR_BODY = '.status-bar-body';
var SELECTOR_CONTENT = '.status-bar-content';
var AUTOCLOSE_DELAY = 6000;
var StatusBar = function () {
this.$ = $(SELECTOR_ROOT);
};
StatusBar.prototype.info = function (msg, closeAfter) {
closeAfter = closeAfter || AUTOCLOSE_DELAY;
this._trigger('<i class="fa fa-info-circle info"></i><span>' + msg + '</span>', undefined, closeAfter);
};
StatusBar.prototype.success = function (msg, closeAfter) {
closeAfter = closeAfter || AUTOCLOSE_DELAY;
this._trigger('<i class="fa fa-check-circle success"></i><span>' + msg + '</span>', undefined, closeAfter);
};
StatusBar.prototype.warning = function (msg, error, closeAfter) {
this._trigger('<i class="fa fa-exclamation-triangle warning"></i><span>' + msg + '</span>', error, closeAfter);
};
StatusBar.prototype.error = function (msg, error, closeAfter) {
this._trigger('<i class="fa fa-exclamation-circle error"></i><span>' + msg + '</span>', error, closeAfter);
};
StatusBar.prototype._trigger = function (content, error, closeAfter) {
if (this.closeTimer) {
clearTimeout(this.closeTimer);
}
var that = this;
this.hide(function () {
that.setContent(content, error).show(function () {
if (closeAfter > 0) {
that.closeTimer = setTimeout(function () {
that.hide();
}, closeAfter);
}
});
});
};
StatusBar.prototype.setContent = function (content, error) {
var that = this;
var $content = this.$.find(SELECTOR_CONTENT).html(content);
var $closeButton = $('<a class="status-bar-close pull-right" style="">×</a>');
if (error && module.config['showMore']) {
this._addShowMoreButton($content, error);
}
$closeButton.on('click', function () {
that.hide();
});
$content.prepend($closeButton);
return this;
};
StatusBar.prototype._addShowMoreButton = function ($content, error) {
var $showMore = $('<a class="showMore"><i class="fa fa-angle-up"></i></a>');
$showMore.on('click', function () {
var $details = $content.find('.status-bar-details');
if($details.length) {
$details.stop().slideToggle('fast', function() {
$details.remove();
});
$showMore.find('i').attr('class', 'fa fa-angle-up');
} else {
$details = $('<div class="status-bar-details" style="display:none;"><pre>' + getErrorMessage(error) + '</pre><div>');
$content.append($details);
$details.slideToggle('fast');
$showMore.find('i').attr('class', 'fa fa-angle-down');
}
});
$content.append($showMore);
};
var getErrorMessage = function (error) {
try {
if (!error) {
return;
} else if (object.isString(error)) {
return error;
} else if (error instanceof Error) {
var result = error.toString();
if(error.stack) {
result += error.stack;
}
return result;
} else {
return JSON.stringify(error, null, 4);
}
} catch (e) {
log.error(e);
}
}
StatusBar.prototype.show = function (callback) {
// Make the container transparent for beeing able to measure the body height
this.$.css('opacity', 0);
this.$.show();
// Prepare the body node for animation, we set auto height to get the real node height
var $body = this.$.find(SELECTOR_BODY).stop().css('height', 'auto');
var height = $body.innerHeight();
// Hide element before animation
$body.css({'opacity': '0', 'bottom': -height});
// Show root container
this.$.css('opacity', 1);
$body.animate({bottom: '0', opacity: 1.0}, 500, function () {
if (callback) {
callback();
}
});
};
StatusBar.prototype.hide = function (callback) {
var that = this;
var $body = this.$.find(SELECTOR_BODY);
var height = $body.innerHeight();
$body.stop().animate({bottom: -height, opacity:0}, 500, function () {
that.$.hide();
$body.css('bottom', '0');
if (callback) {
callback();
}
});
};
var init = function () {
module.statusBar = new StatusBar();
event.on('humhub:modules:log:setStatus', function (evt, msg, details, level) {
switch (level) {
case log.TRACE_ERROR:
case log.TRACE_FATAL:
module.statusBar.error(msg, details);
break;
case log.TRACE_WARN:
module.statusBar.warning(msg, details);
break;
case log.TRACE_SUCCESS:
module.statusBar.success(msg);
break;
default:
module.statusBar.info(msg);
break;
}
});
};
module.export({
init: init,
StatusBar: StatusBar,
success: function (msg, closeAfter) {
module.statusBar.success(msg, closeAfter);
},
info: function (msg, closeAfter) {
module.statusBar.info(msg, closeAfter);
},
warn: function (msg, error, closeAfter) {
module.statusBar.warn(msg, error, closeAfter);
},
error: function (msg, error, closeAfter) {
module.statusBar.error(msg, error, closeAfter);
}
});
});

View File

@ -0,0 +1,135 @@
/**
* Module for creating an manipulating modal dialoges.
* Normal layout of a dialog:
*
* <div class="modal">
* <div class="modal-dialog">
* <div class="modal-content">
* <div class="modal-header"></div>
* <div class="modal-body"></div>
* <div class="modal-footer"></div>
* </div>
* </div>
* </div>
*
* @param {type} param1
* @param {type} param2
*/
humhub.initModule('ui.tabbedForm', function (module, require, $) {
var additions = require('ui.additions');
/**
* Prepares all included fieldsets for $form indexed
* by its label (legend).
*
* @param {type} $form
* @returns {$lastFieldSet$fieldSet} Array of fieldsets indexed by its label
*/
var getPreparedFieldSets = function ($form) {
var result = {};
var $lastFieldSet;
// Assamble all fieldsets with label
$form.find('fieldset').each(function () {
var $fieldSet = $(this).hide();
var legend = $fieldSet.children('legend').text();
// If we have a label we add the fieldset as is else we append its inputs to the previous fieldset
if (legend && legend.length) {
result[legend] = $lastFieldSet = $fieldSet;
} else if($lastFieldSet) {
$lastFieldSet.append($fieldSet.children(".form-group"));
}
});
return result;
};
/**
* Check for errors in a specific category.
* @param _object
* @returns {boolean}
*/
var hasErrors = function($fieldSet) {
return $fieldSet.find('.error, .has-error').length > 0;
};
var init = function () {
additions.registerAddition('[data-ui-tabbed-form]', function ($form) {
var activeTab = 0;
var $tabContent = $('<div class="tab-content"></div>');
var $tabs = $('<ul id="profile-tabs" class="nav nav-tabs" data-tabs="tabs"></ul>');
$form.prepend($tabContent);
$form.prepend($tabs);
var index = 0;
$.each(getPreparedFieldSets($form), function (label, $fieldSet) {
// activate this tab if there are any errors
if (hasErrors($fieldSet)) {
activeTab = index;
}
// init tab structure
$tabs.append('<li><a href="#tab-' + index + '" data-toggle="tab">' + label + '</a></li>');
$tabContent.append('<div class="tab-pane" data-tab-index="' + index + '" id="tab-' + index + '"></div>');
// clone inputs from fieldSet into our tab structure
var $inputs = $fieldSet.children(".form-group");
$('#tab-' + index).html($inputs.clone());
// remove old fieldset from dom
$fieldSet.remove();
// change tab on tab key for the last input of each tab
var tabIndex = index;
$tabContent.find('.form-control').last().on('keydown', function (e) {
var keyCode = e.keyCode || e.which;
if (keyCode === 9) { //Tab
var $nextTabLink = $tabs.find('a[href="#tab-' + (tabIndex + 1) + '"]');
if ($nextTabLink.length) {
e.preventDefault();
$nextTabLink.tab('show');
}
}
});
index++;
});
// prepend error summary to form if exists
var $errorSummary = $('.errorSummary');
if ($errorSummary.length) {
$form.prepend($errorSummary.clone());
$errorSummary.remove();
}
// focus first input on tab change
$form.find('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var tabId = $(e.target).attr('href'); // newly activated tab
$(tabId).find('.form-control').first().focus();
});
// activate the first tab or the tab with errors
$tabs.find('a[href="#tab-' + activeTab + '"]').tab('show');
});
// Make sure frontend validation also activates the tab with errors.
$(document).on('afterValidate.humhub:ui:tabbedForm', function (evt, messages, errors) {
if (errors.length) {
var index = $(errors[0].container).closest('.tab-pane').data('tab-index');
$('a[href="#tab-' + index + '"]').tab('show');
}
});
};
module.export({
init: init
});
});

View File

@ -166,23 +166,6 @@ $(document).ready(function () {
setModalHandler();
initPlugins();
$(document).on('click', 'a[data-ui-loader], button[data-ui-loader]', function () {
});
$(document).on('afterValidate', function(evt, messages, errors) {
if(errors.length) {
$('[data-ui-loader]').each(function() {
var $this = $(this);
if($this.find('.loader').length) {
$this.html($this.data('text'));
$this.removeClass('disabled');
}
});
}
});
});
function setModalHandler() {

View File

@ -1,112 +0,0 @@
$(document).ready(function () {
/**
* Prepares all included fieldsets for $form indexed
* by its label (legend).
*
* @param {type} $form
* @returns {$lastFieldSet$fieldSet} Array of fieldsets indexed by its label
*/
var getPreparedFieldSets = function ($form) {
var result = {};
var $lastFieldSet;
// Assamble all fieldsets with label
$form.find('fieldset').each(function () {
var $fieldSet = $(this).hide();
var legend = $fieldSet.children('legend').text();
// If we have a label we add the fieldset as is else we append its inputs to the previous fieldset
if (legend && legend.length) {
result[legend] = $lastFieldSet = $fieldSet;
} else if($lastFieldSet) {
$lastFieldSet.append($fieldSet.children(".form-group"));
}
});
return result;
};
/**
* Check for errors in a specific category.
* @param _object
* @returns {boolean}
*/
var hasErrors = function($fieldSet) {
return $fieldSet.find('.error, .has-error').length > 0;
};
/**
* Initialize tabbed forms.
* Note: this currently does only work with on form per page because of the tab id's
*/
$('[data-ui-tabbed-form]').each(function () {
var activeTab = 0;
var $form = $(this);
var $tabContent = $('<div class="tab-content"></div>');
var $tabs = $('<ul id="profile-tabs" class="nav nav-tabs" data-tabs="tabs"></ul>');
$form.prepend($tabContent);
$form.prepend($tabs);
var index = 0;
$.each(getPreparedFieldSets($form), function(label, $fieldSet) {
// activate this tab if there are any errors
if (hasErrors($fieldSet)) {
activeTab = index;
}
// init tab structure
$tabs.append('<li><a href="#tab-' + index + '" data-toggle="tab">' + label + '</a></li>');
$tabContent.append('<div class="tab-pane" data-tab-index="'+index+'" id="tab-' + index + '"></div>');
// clone inputs from fieldSet into our tab structure
var $inputs = $fieldSet.children(".form-group");
$('#tab-' + index).html($inputs.clone());
// remove old fieldset from dom
$fieldSet.remove();
// change tab on tab key for the last input of each tab
var tabIndex = index;
$tabContent.find('.form-control').last().on('keydown', function(e) {
var keyCode = e.keyCode || e.which;
if(keyCode === 9) { //Tab
var $nextTabLink = $tabs.find('a[href="#tab-' + (tabIndex+1) + '"]');
if($nextTabLink.length) {
e.preventDefault();
$nextTabLink.tab('show');
}
}
});
index++;
});
// prepend error summary to form if exists
var $errorSummary = $('.errorSummary');
if ($errorSummary.length) {
$form.prepend($errorSummary.clone());
$errorSummary.remove();
}
// focus first input on tab change
$form.find('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var tabId = $(e.target).attr('href'); // newly activated tab
$(tabId).find('.form-control').first().focus();
});
// activate the first tab or the tab with errors
$tabs.find('a[href="#tab-' + activeTab + '"]').tab('show');
});
// Make sure frontend validation also activates the tab with errors.
$(document).on('afterValidate', function(evt, messages, errors) {
if(errors.length) {
var index = $(errors[0].container).closest('.tab-pane').data('tab-index');
$('a[href="#tab-' + index + '"]').tab('show');
}
});
});

View File

@ -45,13 +45,15 @@ class CoreApiAsset extends AssetBundle
'js/humhub/legacy/app.js',
'js/humhub/humhub.core.js',
'js/humhub/humhub.util.js',
'js/humhub/humhub.log.js',
//'js/humhub/humhub.scripts.js',
'js/humhub/humhub.additions.js',
'js/humhub/humhub.ui.status.js',
'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.ui.js',
'js/humhub/humhub.ui.modal.js',
'js/humhub/humhub.actions.js'
'js/humhub/humhub.action.js'
];
/**

View File

@ -0,0 +1,91 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\assets;
use yii\web\AssetBundle;
/**
* The AppAsset assets are included in the core layout.
* This Assetbundle includes some core dependencies and the humhub core api.
*/
class AppAsset extends AssetBundle
{
/**
* @inheritdoc
*/
public $basePath = '@webroot';
/**
* @inheritdoc
*/
public $baseUrl = '@web';
/**
* @inheritdoc
*/
public $css = [
'css/temp.css',
'css/bootstrap-wysihtml5.css',
'css/flatelements.css',
];
/**
* @inheritdoc
*/
public $jsOptions = ['position' => \yii\web\View::POS_BEGIN];
/**
* @inheritdoc
*/
public $js = [
'js/ekko-lightbox-modified.js',
//'js/modernizr.js', // In use???
'js/jquery.highlight.min.js',
//'js/wysihtml5-0.3.0.js',
//'js/bootstrap3-wysihtml5.js',
'js/desktop-notify-min.js',
'js/desktop-notify-config.js',
'js/jquery.nicescroll.min.js',
'resources/file/fileuploader.js',
'resources/user/userpicker.js',
];
/**
* @inheritdoc
*/
public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
'yii\bootstrap\BootstrapPluginAsset',
/**
* Temporary disabled
* https://github.com/inuyaksa/jquery.nicescroll/issues/574
*/
//'humhub\assets\JqueryNiceScrollAsset',
'humhub\assets\BluebirdAsset',
'humhub\assets\JqueryTimeAgoAsset',
'humhub\assets\JqueryWidgetAsset',
'humhub\assets\JqueryColorAsset', //TODO: only required for post/comment/stream
'humhub\assets\JqueryPlaceholderAsset',
'humhub\assets\FontAwesomeAsset',
'humhub\assets\BlueimpFileUploadAsset',
'humhub\assets\JqueryHighlightAsset',
'humhub\assets\JqueryCookieAsset',
'humhub\assets\JqueryAutosizeAsset',
'humhub\assets\AtJsAsset',
'humhub\assets\AnimateCssAsset',
'humhub\assets\CoreApiAsset',
'humhub\modules\content\assets\ContentAsset',
'humhub\assets\NProgressAsset',
'humhub\assets\IE9FixesAsset',
'humhub\assets\IEFixesAsset',
];
}

View File

@ -18,7 +18,7 @@ use yii\web\AssetBundle;
class TabbedFormAsset extends AssetBundle
{
public $jsOptions = ['position' => \yii\web\View::POS_BEGIN];
public $jsOptions = ['position' => \yii\web\View::POS_END];
public $basePath = '@webroot';
public $baseUrl = '@web';
@ -26,10 +26,10 @@ class TabbedFormAsset extends AssetBundle
/**
* @inheritdoc
*/
public $js = ['js/tabbedForm.js'];
public $js = ['js/humhub/humhub.ui.tabbedForm.js'];
public $depends = [
'humhub\assets\AppAsset'
'humhub\assets\CoreApiAsset'
];
}

View File

@ -18,6 +18,7 @@ class View extends \yii\web\View
{
private $_pageTitle;
private $jsConfig = [];
/**
* Sets current page title
@ -51,6 +52,21 @@ class View extends \yii\web\View
$this->registerJs($jsCode, View::POS_HEAD, $name);
}
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])) {
$this->jsConfig[$module] = yii\helpers\ArrayHelper::merge($this->jsConfig[$module], $params);
} else {
$this->jsConfig[$module] = $params;
}
}
/**
* Renders a string as Ajax including assets.
*
@ -124,12 +140,16 @@ class View extends \yii\web\View
*/
public function endBody()
{
$this->registerJs("humhub.config.set(".json_encode($this->jsConfig).");", View::POS_BEGIN, 'jsConfig');
if (Yii::$app->request->isAjax) {
return parent::endBody();
}
echo \humhub\widgets\LayoutAddons::widget();
// Add Layout Addons
return \humhub\widgets\LayoutAddons::widget() . parent::endBody();
return parent::endBody();
}
}

View File

@ -2,7 +2,7 @@
/**
* This file is generated by the "yii asset" command.
* DO NOT MODIFY THIS FILE DIRECTLY.
* @version 2016-09-12 17:54:43
* @version 2016-10-21 15:35:07
*/
return [
'all' => [
@ -10,11 +10,13 @@ return [
'basePath' => '@webroot',
'baseUrl' => '@web',
'js' => [
'js/all-cbb2ffbc142aa643edde05905376dd29.js',
'js/all-a366b3723e2189716ce68fbe333d59bd.js',
],
'css' => [
'css/all-5f84fd9cb4f93bb90cc038bed1ec4f2d.css',
'css/all-2d605362857c9db6fdb10a1df599e902.css',
],
'sourcePath' => null,
'depends' => [],
],
'yii\\web\\JqueryAsset' => [
'sourcePath' => null,

View File

@ -87,7 +87,6 @@ $config = [
'class' => '\humhub\components\AssetManager',
'appendTimestamp' => true,
'bundles' => require(__DIR__ . '/' . (YII_ENV_PROD ? 'assets-prod.php' : 'assets-dev.php')),
#'bundles' => require(__DIR__ . '/' . 'assets-prod.php'),
],
'view' => [
'class' => '\humhub\components\View',

View File

@ -12,10 +12,24 @@ Build
- npm update (in humhub root)
- npm install grunt --save-dev
## Assets
- Yii asset management http://www.yiiframework.com/doc-2.0/guide-structure-assets.html#combining-compressing-assets
- php yii asset humhub/config/assets.php humhub/config/assets-prod.php
HumHub uses the Yii's build in mechanism for compressing and combining assets as javascript or stylesheet files in combination with grunt.
HumHub will only use the compressed assets if operated in [production mode](admin-installation.md#disable-errors-debugging), otherwise
all assets are included seperatly.
The compressed production assets are build by calling:
```
yii asset humhub/config/assets.php humhub/config/assets-prod.php
```
This will create the following files:
- /humhub/js/all-*.js - compressed js file with all core libraries
- /humhub/css/all-*.css - compressed css files with all core stylesheets
More information is available on http://www.yiiframework.com/doc-2.0/guide-structure-assets.html#combining-compressing-assets
### Grunt Tasks
- watch

View File

@ -2,6 +2,8 @@
Here you will learn how you can adapt existing modules to working fine with actually versions.
## Migrate from 1.1 to 1.2
## Migrate from 1.0 to 1.1
- Dropped unused space attribute "website"

View File

@ -2,6 +2,14 @@
Here you will learn how you can adapt existing themes to working fine with actually versions.
## Migrate to 1.2
### Stream
Set data-stream attributes for stream
### Status Bar
## Migrate to 1.1
- Make sure to update your themed less file with the latest version.

View File

@ -0,0 +1,26 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\activity\assets;
use yii\web\AssetBundle;
class ActivityAsset extends AssetBundle
{
public $sourcePath = '@activity/assets';
public $css = [];
public $js = [
'js/humhub.activity.js'
];
public $depends = [
'humhub\assets\CoreApiAsset'
];
}

View File

@ -0,0 +1,134 @@
/**
* Core module for managing Streams and StreamItems
* @type Function
*/
humhub.initModule('activity', function (module, require, $) {
var util = require('util');
var object = util.object;
var string = util.string;
var stream = require('stream');
var loader = require('ui.loader');
var event = require('event');
/**
* Number of initial stream enteis loaded when stream is initialized.
* @type Number
*/
var STREAM_INIT_COUNT = 10;
/**
* Number of stream entries loaded with each request (except initial request)
* @type Number
*/
var STREAM_LOAD_COUNT = 10;
/**
* Number of stream entries loaded with each request (except initial request)
* @type Number
*/
var ACTIVITY_STREAM_SELECTOR = '#activityStream';
/**
* ActivityStream instance;
* @type ActivityStream
*/
var instance;
var ActivityStreamEntry = function (id) {
stream.StreamEntry.call(this, id);
};
object.inherits(ActivityStreamEntry, stream.StreamEntry);
ActivityStreamEntry.prototype.actions = function () {
return [];
};
ActivityStreamEntry.prototype.delete = function () {/* Not implemented */}
ActivityStreamEntry.prototype.edit = function () {/* Not implemented */}
/**
* ActivityStream implementation.
*
* @param {type} container id or jQuery object of the stream container
* @returns {undefined}
*/
var ActivityStream = function (container) {
stream.Stream.call(this, container, {
'loadInitialCount': STREAM_INIT_COUNT,
'loadCount': STREAM_LOAD_COUNT,
'streamEntryClass': ActivityStreamEntry
});
};
object.inherits(ActivityStream, stream.Stream);
ActivityStream.prototype.showLoader = function() {
var $loaderListItem = $('<li id="activityLoader" class="streamLoader">');
loader.append($loaderListItem);
this.$content.append($loaderListItem);
};
ActivityStream.prototype.hideLoader = function() {
this.$content.find('#activityLoader').remove();
};
var getStream = function () {
instance = instance || new ActivityStream($(ACTIVITY_STREAM_SELECTOR));
return instance;
};
var init = function () {
instance = undefined;
var stream = getStream();
if (!stream) {
console.log('No activity stream found!');
return;
}
stream.init();
var activityLastEntryReached = false;
// listen for scrolling event yes or no
var scrolling = true;
stream.$content.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
stream.loadEntries().finally(function() {
scrolling = true;
});
}
}
}
});
};
module.export({
ActivityStream: ActivityStream,
getStream: getStream,
init: init
});
});

View File

@ -1,5 +1,5 @@
<?php if ($clickable): ?><a href="#" onClick="activityShowItem(<?= $record->id; ?>); return false;"><?php endif; ?>
<li class="activity-entry">
<li class="activity-entry" data-stream-entry data-action-component="activity.ActivityStreamEntry" data-content-key="<?= $record->content->id ?>">
<div class="media">
<?php if ($originator !== null) : ?>
<!-- Show user image -->

View File

@ -54,11 +54,9 @@ class Stream extends \yii\base\Widget
protected function getStreamUrl()
{
$params = array(
'limit' => '10',
'from' => '-from-',
$params = [
'mode' => \humhub\modules\stream\actions\Stream::MODE_ACTIVITY
);
];
if ($this->contentContainer) {
return $this->contentContainer->createUrl($this->streamAction, $params);

View File

@ -1,9 +1,10 @@
<?php
/* @var $this humhub\components\View */
\humhub\modules\activity\assets\ActivityAsset::register($this);
$this->registerJsFile('@web/resources/activity/activies.js');
$this->registerJsVar('activityStreamUrl', $streamUrl);
//$this->registerJsFile('@web/resources/activity/activies.js');
//$this->registerJsVar('activityStreamUrl', $streamUrl);
$this->registerJsVar('activityInfoUrl', $infoUrl);
?>
@ -11,13 +12,13 @@ $this->registerJsVar('activityInfoUrl', $infoUrl);
<div
class="panel-heading"><?php echo Yii::t('ActivityModule.widgets_views_activityStream', '<strong>Latest</strong> activities'); ?></div>
<div id="activityStream">
<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">
<li id="activityLoader">
<ul id="activityContents" class="media-list activities" data-stream-content>
<li id="activityLoader" class="streamLoader">
<?php echo \humhub\widgets\LoaderWidget::widget(); ?>
</li>
</ul>

View File

@ -20,7 +20,6 @@ use humhub\models\Setting;
<div class="form-group">
<?php echo $form->labelEx($model, 'expireTime'); ?>
<?php echo $form->textField($model, 'expireTime', array('class' => 'form-control', 'readonly' => Setting::IsFixed('cache.expireTime'))); ?>
<br>
</div>
<hr>

View File

@ -0,0 +1,28 @@
<?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 ContentFormAsset extends AssetBundle
{
public $jsOptions = ['position' => \yii\web\View::POS_END];
public $sourcePath = '@content/assets';
public $css = [];
public $js = [
'js/humhub.content.form.js'
];
public $depends = [
'humhub\assets\CoreApiAsset'
];
}

View File

@ -0,0 +1,147 @@
/**
* Core module for managing Streams and StreamItems
* @type Function
*/
humhub.initModule('content.form', function (module, require, $) {
var CREATE_FORM_ROOT_SELECTOR = '#contentFormBody';
var util = require('util');
var client = require('client');
var config = require('config').module(module);
var event = require('event');
var instance;
var CreateForm = function () {
this.$ = $(CREATE_FORM_ROOT_SELECTOR);
};
CreateForm.prototype.init = function () {
this.$.hide();
// Hide options by default
$('.contentForm_options').hide();
$('#contentFormError').hide();
// Remove info text from the textinput
$('#contentFormBody').click(function () {
// Hide options by default
$('.contentForm_options').fadeIn();
});
this.setDefaultVisibility();
this.$.fadeIn('fast');
}
CreateForm.prototype.actions = function () {
return ['submit', 'notifyUser', 'changeVisibility'];
};
CreateForm.prototype.submit = function (evt) {
$("#contentFormError").hide();
$("#contentFormError li").remove();
$(".contentForm_options .btn").hide();
$("#postform-loader").removeClass("hidden");
var that = this;
client.submit(this.getForm(), {url: evt.url}).then(function (response) {
if (!response.errors) {
event.trigger('humhub:modules:content:newEntry', response.output);
that.resetForm();
} else {
that.handleError(response);
}
$('.contentForm_options .btn').show();
$('#postform-loader').addClass('hidden');
});
};
/**
* Todo: this is post form only, this needs to be added to post module perhaps by calling $form.trigger('humhub:form:clear');
* @returns {undefined}
*/
CreateForm.prototype.resetForm = function () {
// Reset Form (Empty State)
$('.contentForm_options').hide();
$('.contentForm').filter(':text').val('');
$('.contentForm').filter('textarea').val('').trigger('autosize.resize');
$('.contentForm').attr('checked', false);
$('.userInput').remove(); // used by UserPickerWidget
$('#notifyUserContainer').addClass('hidden');
$('#notifyUserInput').val('');
this.setDefaultVisibility();
$('#contentFrom_files').val('');
$('#public').attr('checked', false);
$('#contentForm_message_contenteditable').addClass('atwho-placeholder');
$('#contentFormBody').find('.atwho-input').trigger('clear');
// Notify FileUploadButtonWidget to clear (by providing uploaderId)
// TODO: use api
resetUploader('contentFormFiles');
};
CreateForm.prototype.handleError = function (response) {
$('#contentFormError').show();
$.each(response.errors, function (fieldName, errorMessage) {
// Mark Fields as Error
var fieldId = 'contentForm_' + fieldName;
$('#' + fieldId).addClass('error');
$.each(errorMessage, function (key, msg) {
$('#contentFormError').append('<li><i class=\"icon-warning-sign\"></i> ' + msg + '</li>');
});
});
};
CreateForm.prototype.getForm = function () {
return this.$.find('form:visible');
};
CreateForm.prototype.changeVisibility = function() {
if (!$('#contentForm_visibility').prop('checked')) {
this.setPublicVisibility();
} else {
this.setPrivateVisibility();
}
}
CreateForm.prototype.setDefaultVisibility = function() {
if (config['defaultVisibility']) {
this.setPublicVisibility();
} else {
this.setPrivateVisibility();
}
}
CreateForm.prototype.setPublicVisibility = function() {
$('#contentForm_visibility').prop("checked", true);
$('#contentForm_visibility_entry').html('<i class="fa fa-lock"></i>'+config['text']['makePrivate']);
$('.label-public').removeClass('hidden');
}
CreateForm.prototype.setPrivateVisibility = function() {
$('#contentForm_visibility').prop("checked", false);
$('#contentForm_visibility_entry').html('<i class="fa fa-unlock"></i>'+config['text']['makePublic']);
$('.label-public').addClass('hidden');
}
CreateForm.prototype.notifyUser = function() {
$('#notifyUserContainer').removeClass('hidden');
$('#notifyUserInput_tag_input_field').focus();
}
var init = function () {
instance = new CreateForm();
instance.init();
};
module.export({
CreateForm: CreateForm,
instance: instance,
init: init
});
});

View File

@ -7,8 +7,9 @@
humhub.initModule('content', function(module, require, $) {
var client = require('client');
var object = require('util').object;
var actions = require('actions');
var actions = require('action');
var Component = actions.Component;
var event = require('event');
var DATA_CONTENT_KEY = "content-key";
var DATA_CONTENT_EDIT_URL = "content-edit-url";
@ -20,6 +21,10 @@ humhub.initModule('content', function(module, require, $) {
Component.call(this, container);
};
Content.getNodeByKey = function(key) {
return $('[data-content-key="'+key+'"]');
};
object.inherits(Content, Component);
Content.prototype.actions = function() {
@ -30,6 +35,7 @@ humhub.initModule('content', function(module, require, $) {
return this.$.data(DATA_CONTENT_KEY);
};
//TODO: return promise
Content.prototype.create = function (addContentHandler) {
//Note that this Content won't have an id, so the backend will create an instance
if(this.hasAction('create')) {
@ -39,6 +45,7 @@ humhub.initModule('content', function(module, require, $) {
this.edit(addContentHandler);
};
//TODO: return promise
Content.prototype.edit = function (successHandler) {
if(!this.hasAction('edit')) {
return;
@ -91,12 +98,13 @@ humhub.initModule('content', function(module, require, $) {
},
error: function(errResponse) {
modal.error(errResponse);
console.log('Error occured while editing content: '+errResponse.getFirstError());
console.error('Error occured while editing content: '+errResponse.getFirstError());
//Todo: handle error
}
});
};
//TODO: return promise
Content.prototype.delete = function () {
if(!this.hasAction('delete')) {
return;
@ -127,10 +135,14 @@ humhub.initModule('content', function(module, require, $) {
Content.prototype.remove = function() {
var that = this;
this.$.animate({ height: 'toggle', opacity: 'toggle' }, 'fast', function() {
that.$.remove();
//TODO: fire global event
return new Promise(function(resolve, reject) {
that.$.animate({ height: 'toggle', opacity: 'toggle' }, 'fast', function() {
that.$.remove();
event.trigger('humhub:modules:content:afterRemove', that);
resolve(that);
});
});
};
module.export({

View File

@ -97,7 +97,6 @@ class ContentController extends Controller
$content->archive();
$json['success'] = true;
$json['wallEntryIds'] = $content->getWallEntryIds();
}
return $json;
@ -123,7 +122,6 @@ class ContentController extends Controller
$content->unarchive();
$json['success'] = true;
$json['wallEntryIds'] = $content->getWallEntryIds();
}
return $json;
@ -149,7 +147,7 @@ class ContentController extends Controller
$content->stick();
$json['success'] = true;
$json['wallEntryIds'] = $content->getWallEntryIds();
$json['contentId'] = $content->id;
} else {
$json['errorMessage'] = Yii::t('ContentModule.controllers_ContentController', "Maximum number of sticked items reached!\n\nYou can stick only two items at once.\nTo however stick this item, unstick another before!");
}
@ -178,7 +176,6 @@ class ContentController extends Controller
if ($content !== null && $content->canStick()) {
$content->unstick();
$json['success'] = true;
$json['wallEntryIds'] = $content->getWallEntryIds();
}
return $json;

View File

@ -45,36 +45,13 @@ class PermaController extends Controller
$id = (int) Yii::$app->request->get('id', "");
$content = Content::findOne(['id' => $id]);
if ($content !== null) {
return $this->redirect($content->getUrl());
return $this->redirect($content->container->createUrl(null, array('contentId' => $id)));
}
throw new HttpException(404, Yii::t('ContentModule.controllers_PermaController', 'Could not find requested content!'));
}
/**
* On given WallEntryId redirect the user to the corresponding content object.
*
* This is mainly used by ActivityStream or Permalinks.
*/
public function actionWallEntry()
{
// Id of wall entry
$id = Yii::$app->request->get('id', "");
$wallEntry = WallEntry::find()->joinWith('content')->where(['wall_entry.id' => $id])->one();
if ($wallEntry != null) {
$obj = $wallEntry->content; // Type of IContent
if ($obj) {
return $this->redirect($obj->container->createUrl(null, array('wallEntryId' => $id)));
}
}
throw new HttpException(404, Yii::t('ContentModule.controllers_PermaController', 'Could not find requested permalink!'));
}
}
?>

View File

@ -3,15 +3,18 @@
use yii\helpers\Url;
$this->registerJsVar('wallArchiveLinkUrl', Url::to(['/content/content/archive', 'id' => '-id-']));
$this->registerJsVar('wallUnarchiveLinkUrl', Url::to(['/content/content/unarchive', 'id' => '-id-']));
$archiveLink = Url::to(['/content/content/archive', 'id' => $id]);
$unarchiveLink = Url::to(['/content/content/unarchive', 'id' => $id]);
?>
<li>
<?php if ($object->content->isArchived()): ?>
<a href="#" onClick="wallUnarchive('<?php echo $id; ?>');
return false;"><i class="fa fa-archive"></i> <?php echo Yii::t('ContentModule.widgets_views_archiveLink', 'Unarchive'); ?></a>
<a href="#" data-action-click="unarchive" data-action-url="<?= $unarchiveLink ?>">
<i class="fa fa-archive"></i> <?php echo Yii::t('ContentModule.widgets_views_archiveLink', 'Unarchive'); ?>
</a>
<?php else: ?>
<a href="#" onClick="wallArchive('<?php echo $id; ?>');
return false;"><i class="fa fa-archive"></i> <?php echo Yii::t('ContentModule.widgets_views_archiveLink', 'Move to archive'); ?></a>
<a href="#" data-action-click="archive" data-action-url="<?= $archiveLink ?>">
<i class="fa fa-archive"></i> <?php echo Yii::t('ContentModule.widgets_views_archiveLink', 'Move to archive'); ?>
</a>
<?php endif; ?>
</li>

View File

@ -9,17 +9,4 @@ use yii\helpers\Url;
<a href="#" data-action-click="delete">
<i class="fa fa-trash-o"></i> <?= Yii::t('ContentModule.widgets_views_deleteLink', 'Delete') ?>
</a>
<?php
/*echo humhub\widgets\ModalConfirm::widget(array(
'uniqueID' => 'modal_postdelete_' . $id,
'linkOutput' => 'a',
'title' => Yii::t('ContentModule.widgets_views_deleteLink', '<strong>Confirm</strong> post deleting'),
'message' => Yii::t('ContentModule.widgets_views_deleteLink', 'Do you really want to delete this post? All likes and comments will be lost!'),
'buttonTrue' => Yii::t('ContentModule.widgets_views_deleteLink', 'Delete'),
'buttonFalse' => Yii::t('ContentModule.widgets_views_deleteLink', 'Cancel'),
'linkContent' => '<i class="fa fa-trash-o"></i> ' . Yii::t('ContentModule.widgets_views_deleteLink', 'Delete'),
'linkHref' => Url::to(['/content/content/delete', 'model' => $model, 'id' => $id]),
'confirmJS' => 'function(json) { $(".wall_"+json.uniqueId).remove(); }'
));*/
?>
</li>

View File

@ -1,23 +1,9 @@
<?php
use yii\web\JsExpression;
/* @var $this humhub\components\View */
?>
<li>
<?php
echo \humhub\widgets\AjaxButton::widget([
'label' => '<i class="fa fa-pencil"></i> ' . Yii::t('ContentModule.widgets_views_editLink', 'Edit'),
'tag' => 'a',
'ajaxOptions' => [
'type' => 'POST',
'success' => new JsExpression('function(html){ $(".preferences .dropdown").removeClass("open"); $("#wall_content_' . $content->getUniqueId() . '").replaceWith(html); }'),
'url' => $editUrl,
],
'htmlOptions' => [
'href' => '#'
]
]);
?>
<a href="#" data-action-click="edit"
data-action-url="<?= $editUrl ?>"><i class="fa fa-pencil"></i> <?= Yii::t('ContentModule.widgets_views_editLink', 'Edit') ?></a>
</li>

View File

@ -1,9 +1,11 @@
<li>
<?php if ($isSticked): ?>
<a href="#" onClick="wallUnstick('<?php echo $unstickUrl; ?>');
return false;"><i class="fa fa-arrow-up"></i> <?php echo Yii::t('ContentModule.widgets_views_stickLink', 'Unstick'); ?></a>
<a href="#" data-action-click="unstick" data-action-url="<?= $unstickUrl ?>">
<i class="fa fa-arrow-up"></i> <?php echo Yii::t('ContentModule.widgets_views_stickLink', 'Unstick'); ?>
</a>
<?php else: ?>
<a href="#" onClick="wallStick('<?php echo $stickUrl; ?>');
return false;"><i class="fa fa-arrow-up"></i> <?php echo Yii::t('ContentModule.widgets_views_stickLink', 'Stick'); ?></a>
<a href="#" data-action-click="stick" data-action-url="<?= $stickUrl ?>">
<i class="fa fa-arrow-up"></i> <?php echo Yii::t('ContentModule.widgets_views_stickLink', 'Stick'); ?>
</a>
<?php endif; ?>
</li>

View File

@ -3,11 +3,22 @@
use yii\helpers\Html;
use yii\helpers\Url;
use humhub\modules\space\models\Space;
\humhub\modules\content\assets\ContentFormAsset::register($this);
?>
<?php
$this->registerJsConfig('content.form', [
'defaultVisibility' => $defaultVisibility,
'text' => [
'makePrivate' => Yii::t('ContentModule.widgets_views_contentForm', 'Make private'),
'makePublic' => Yii::t('ContentModule.widgets_views_contentForm', 'Make public')
]]);
?>
<div class="panel panel-default">
<div class="panel-body" id="contentFormBody">
<div class="panel-body" id="contentFormBody" style="display:none;" data-action-component="content.form.CreateForm" data-action-param="{defaultVisibility: <?= $defaultVisibility ?>}">
<?php echo Html::beginForm('', 'POST'); ?>
<ul id="contentFormError">
@ -49,24 +60,10 @@ use humhub\modules\space\models\Space;
<?php echo \humhub\widgets\LoaderWidget::widget(['id' => 'postform-loader', 'cssClass' => 'loader-postform hidden']); ?>
<?php
echo \humhub\widgets\AjaxButton::widget([
'label' => $submitButtonText,
'ajaxOptions' => [
'url' => $submitUrl,
'type' => 'POST',
'dataType' => 'json',
'beforeSend' => "function() { $('.contentForm').removeClass('error'); $('#contentFormError').hide(); $('#contentFormError').empty(); }",
'beforeSend' => 'function(){ $("#contentFormError").hide(); $("#contentFormError li").remove(); $(".contentForm_options .btn").hide(); $("#postform-loader").removeClass("hidden"); }',
'success' => "function(response) { handleResponse(response);}"
],
'htmlOptions' => [
'id' => "post_submit_button",
'data-action' => 'post_create',
'class' => 'btn btn-info',
'type' => 'submit'
]]);
?>
<button id="post_submit_button" data-action-click="submit" data-action-url="<?= $submitUrl ?>" class="btn btn-info">
<?= $submitButtonText ?>
</button>
<?php
// Creates Uploading Button
echo humhub\modules\file\widgets\FileUploadButton::widget(array(
@ -74,20 +71,6 @@ use humhub\modules\space\models\Space;
'fileListFieldName' => 'fileList',
));
?>
<script>
$('#fileUploaderButton_contentFormFiles').bind('fileuploaddone', function (e, data) {
$('.btn_container').show();
});
$('#fileUploaderButton_contentFormFiles').bind('fileuploadprogressall', function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
if (progress != 100) {
// Fix: remove focus from upload button to hide tooltip
$('#post_submit_button').focus();
// hide form buttons
$('.btn_container').hide();
}
});</script>
<!-- public checkbox -->
<?php echo Html::checkbox("visibility", "", array('id' => 'contentForm_visibility', 'class' => 'contentForm hidden')); ?>
@ -103,14 +86,14 @@ use humhub\modules\space\models\Space;
class="fa fa-cogs"></i></a>
<ul class="dropdown-menu pull-right">
<li>
<a href="javascript:notifyUser();"><i
class="fa fa-bell"></i> <?php echo Yii::t('ContentModule.widgets_views_contentForm', 'Notify members'); ?>
<a data-action-click="notifyUser">
<i class="fa fa-bell"></i> <?php echo Yii::t('ContentModule.widgets_views_contentForm', 'Notify members'); ?>
</a>
</li>
<?php if ($canSwitchVisibility): ?>
<li>
<a id="contentForm_visibility_entry" href="javascript:changeVisibility();"><i
class="fa fa-unlock"></i> <?php echo Yii::t('ContentModule.widgets_views_contentForm', 'Make public'); ?>
<a id="contentForm_visibility_entry" data-action-click="changeVisibility">
<i class="fa fa-unlock"></i> <?php echo Yii::t('ContentModule.widgets_views_contentForm', 'Make public'); ?>
</a>
</li>
<?php endif; ?>
@ -129,102 +112,12 @@ use humhub\modules\space\models\Space;
'uploaderId' => 'contentFormFiles'
));
?>
</div>
<!-- /contentForm_Options -->
<!-- /contentForm_Options -->
<?php echo Html::endForm(); ?>
</div>
<!-- /panel body -->
</div> <!-- /panel -->
<div class="clearFloats"></div>
<script type="text/javascript">
// Hide options by default
jQuery('.contentForm_options').hide();
$('#contentFormError').hide();
// Remove info text from the textinput
jQuery('#contentFormBody').click(function () {
// Hide options by default
jQuery('.contentForm_options').fadeIn();
});
setDefaultVisibility();
function setDefaultVisibility() {
<?php if ($defaultVisibility == humhub\modules\content\models\Content::VISIBILITY_PRIVATE) : ?>
setPrivateVisibility();
<?php endif ;?>
<?php if ($defaultVisibility == humhub\modules\content\models\Content::VISIBILITY_PUBLIC) : ?>
setPublicVisibility();
<?php endif ;?>
}
function setPublicVisibility() {
$('#contentForm_visibility').prop( "checked", true );
$('#contentForm_visibility_entry').html('<i class="fa fa-lock"></i> <?php echo Yii::t('ContentModule.widgets_views_contentForm', 'Make private'); ?>');
$('.label-public').removeClass('hidden');
}
function setPrivateVisibility() {
$('#contentForm_visibility').prop( "checked", false );
$('#contentForm_visibility_entry').html('<i class="fa fa-unlock"></i> <?php echo Yii::t('ContentModule.widgets_views_contentForm', 'Make public'); ?>');
$('.label-public').addClass('hidden');
}
function changeVisibility() {
if (!$('#contentForm_visibility').prop('checked')) {
setPublicVisibility();
} else {
setPrivateVisibility();
}
}
function notifyUser() {
$('#notifyUserContainer').removeClass('hidden');
$('#notifyUserInput_tag_input_field').focus();
}
function handleResponse(response) {
if (!response.errors) {
// application.modules_core.wall function
humhub.modules.stream.getStream().appendEntry(response);
// Reset Form (Empty State)
jQuery('.contentForm_options').hide();
$('.contentForm').filter(':text').val('');
$('.contentForm').filter('textarea').val('').trigger('autosize.resize');
$('.contentForm').attr('checked', false);
$('.userInput').remove(); // used by UserPickerWidget
$('#notifyUserContainer').addClass('hidden');
$('#notifyUserInput').val('');
setDefaultVisibility();
$('#contentFrom_files').val('');
$('#public').attr('checked', false);
$('#contentForm_message_contenteditable').html('<?php echo Html::encode(Yii::t("ContentModule.widgets_views_contentForm", "What's on your mind?")); ?>');
$('#contentForm_message_contenteditable').addClass('atwho-placeholder');
$('#contentFormBody').find('.atwho-input').trigger('clear');
// Notify FileUploadButtonWidget to clear (by providing uploaderId)
resetUploader('contentFormFiles');
} else {
$('#contentFormError').show();
$.each(response.errors, function (fieldName, errorMessage) {
// Mark Fields as Error
fieldId = 'contentForm_' + fieldName;
$('#' + fieldId).addClass('error');
$.each(errorMessage, function (key, msg) {
$('#contentFormError').append('<li><i class=\"icon-warning-sign\"></i> ' + msg + '</li>');
});
});
}
$('.contentForm_options .btn').show();
$('#postform-loader').addClass('hidden');
}
</script>

View File

@ -89,8 +89,10 @@ $container = $object->content->container;
<?php endif; ?>
<?php echo $content; ?>
</div>
<?php echo \humhub\modules\content\widgets\WallEntryAddons::widget(['object' => $object]); ?>
<div class="clearfix">
<span class="entry-loader"></span>
<?php echo \humhub\modules\content\widgets\WallEntryAddons::widget(['object' => $object]); ?>
</div>
</div>

View File

@ -1,11 +0,0 @@
humhub.modules.post = (function(module, $) {
humhub.modules.registerAjaxHandler('humhub.modules.post.create', function(json) {
humhub.modules.stream.getStream();
}, function(error) {
if(error)
alert(error);
});
return module;
humhub.ui.richtext.register()
})(humhub.modules.post || {}, $);

View File

@ -24,24 +24,10 @@ use humhub\compat\CActiveForm;
'object' => $post
));
?>
<button type="submit" class="btn btn-default btn-sm btn-comment-submit" data-action-click="editSubmit" data-action-url="<?= $post->content->container->createUrl('/post/post/edit', ['id' => $post->id]) ?>">
<?= Yii::t('PostModule.views_edit', 'Save') ?>
</button>
<?php
echo \humhub\widgets\AjaxButton::widget([
'label' => Yii::t('PostModule.views_edit', 'Save'),
'ajaxOptions' => [
'type' => 'POST',
'beforeSend' => new yii\web\JsExpression('function(html){ $("#post_input_' . $post->id . '_contenteditable").hide(); showLoader("' . $post->id . '"); }'),
'success' => new yii\web\JsExpression('function(html){ $(".wall_' . $post->getUniqueId() . '").replaceWith(html); }'),
'statusCode' => ['400' => new yii\web\JsExpression('function(xhr) { $("#post_edit_'. $post->id.'").replaceWith(xhr.responseText); }')],
'url' => $post->content->container->createUrl('/post/post/edit', ['id' => $post->id]),
],
'htmlOptions' => [
'class' => 'btn btn-default btn-sm btn-comment-submit',
'id' => 'post_edit_post_' . $post->id,
'type' => 'submit'
]
]);
?>
</div>

View File

@ -0,0 +1,47 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2016 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\post\widgets;
/**
* This widget is used include the post form.
* It normally should be placed above a steam.
*
* @since 0.5
*/
class Form extends \humhub\modules\content\widgets\WallCreateContentForm
{
/**
* @inheritdoc
*/
public $submitUrl = '/post/post/post';
/**
* @inheritdoc
*/
public function renderForm()
{
return $this->render('form', array());
}
/**
* @inheritdoc
*/
public function run()
{
if (!$this->contentContainer->permissionManager->can(new \humhub\modules\post\permissions\CreatePost())) {
return;
}
return parent::run();
}
}
?>

View File

@ -0,0 +1,14 @@
<?php
use yii\helpers\Html;
?>
<?php echo Html::textarea("message", '', array('id' => 'contentForm_message', 'class' => 'form-control autosize contentForm', 'rows' => '1', 'placeholder' => Yii::t("PostModule.widgets_views_postForm", "What's on your mind?"))); ?>
<?php
/* Modify textarea for mention input */
echo \humhub\widgets\RichTextEditor::widget(array(
'id' => 'contentForm_message',
));
?>

View File

@ -58,6 +58,12 @@ abstract class Stream extends Action
*/
public $mode;
/**
* Used to load single content entries.
* @since 1.2
*/
public $contentId;
/**
* First wall entry id to deliver
*
@ -112,6 +118,8 @@ abstract class Stream extends Action
// Read parameters
if (!Yii::$app->request->isConsoleRequest) {
$this->contentId = Yii::$app->getRequest()->get('id');
$from = Yii::$app->getRequest()->get('from', 0);
if ($from != 0) {
$this->from = (int) $from;
@ -131,10 +139,12 @@ abstract class Stream extends Action
if ($limit != "" && $limit <= self::MAX_LIMIT) {
$this->limit = $limit;
}
$mode = Yii::$app->getRequest()->get('mode', '');
if ($mode != "" && ($mode == self::MODE_ACTIVITY || $mode == self::MODE_NORMAL)) {
$this->mode = $mode;
}
foreach (explode(',', Yii::$app->getRequest()->get('filters', "")) as $filter) {
$this->filters[] = trim($filter);
}
@ -167,6 +177,11 @@ abstract class Stream extends Action
$this->activeQuery->andWhere(['!=', 'content.object_model', \humhub\modules\activity\models\Activity::className()]);
}
if($this->contentId) {
$this->activeQuery->andWhere(['content.id' => $this->contentId]);
return;
}
/**
* Setup Sorting
*/

View File

@ -16,11 +16,11 @@ class StreamAsset extends AssetBundle
public $sourcePath = '@humhub/modules/stream/assets';
public $css = [];
public $js = [
'js/humhub.stream.js'
'js/humhub.stream.js',
];
public $depends = [
'humhub\assets\CoreApiAsset'
'humhub\modules\content\assets\ContentAsset'
];
}

View File

@ -9,6 +9,10 @@ humhub.initModule('stream', function (module, require, $) {
var string = util.string;
var client = require('client');
var Content = require('content').Content;
var Component = require('action').Component;
var loader = require('ui.loader');
var event = require('event');
var log = require('log').module(module);
/**
* Number of initial stream enteis loaded when stream is initialized.
@ -29,6 +33,12 @@ humhub.initModule('stream', function (module, require, $) {
*/
var DATA_STREAM_SELECTOR = '[data-stream]';
/**
* Number of stream entries loaded with each request (except initial request)
* @type Number
*/
var DATA_WALL_STREAM_SELECTOR = '#wallStream';
/**
* Set on a stream entry root node to identify stream-entries.
* @type String
@ -42,9 +52,15 @@ humhub.initModule('stream', function (module, require, $) {
*/
var DATA_STREAM_CONTENTID = 'stream-contentid';
/**
* If a data-stream-contentid is set on the stream root only one entry will
* be loaded. e.g. for permlinks
* @type String
*/
var DATA_STREAM_ENTRY_ID_SELECTOR = 'content-key';
//TODO: load streamUrl from config
//TODO: readonly
var streams = {};
/**
* Represents an stream entry within a stream.
@ -64,7 +80,6 @@ humhub.initModule('stream', function (module, require, $) {
StreamEntry.prototype.delete = function () {
var content = this.getContentComponent();
if (content && content.delete) {
//TODO: modalconfirm
content.delete();
} else {
StreamEntry._super.delete.call(this);
@ -77,44 +92,180 @@ humhub.initModule('stream', function (module, require, $) {
};
StreamEntry.prototype.reload = function () {
getStream().reload(this);
return getStream().reloadEntry(this);
};
StreamEntry.prototype.edit = function () {
//Search for data-content-edit-url on root.
//Call this url with data-content-key
//Trigger delete event
StreamEntry.prototype.edit = function (evt) {
var that = this;
this.loader();
client.get(evt.url, {
dataType: 'html',
success: function (response) {
var $content = that.$.find('.content:first');
var $oldContent = $content.clone();
$content.replaceWith(response.html);
that.$.data('oldContent', $oldContent);
that.$.find('input[type="text"], textarea, [contenteditable="true"]').first().focus();
that.unsetLoader();
},
error: function(e) {
//TODO: handle error
that.unsetLoader();
}
});
// Listen to click events outside of the stream entry and cancel edit.
$('body').off('click.humhub:modules:stream:edit').on('click.humhub:modules:stream:edit', function (e) {
if (!$(e.target).closest('[data-content-key="' + that.getKey() + '"]').length) {
var $editContent = that.$.find('.content_edit:first');
if ($editContent && that.$.data('oldContent')) {
$editContent.replaceWith(that.$.data('oldContent'));
that.$.data('oldContent', undefined);
}
$('body').off('click.humhub:modules:stream:edit');
}
});
};
StreamEntry.prototype.loader = function (selector) {
//selector = selector || '.content:first';
selector = selector || '.entry-loader';
loader.set(this.$.find(selector), {
'position': 'left',
'size': '8px',
'css': {
'padding': '0px'
}
});
};
StreamEntry.prototype.unsetLoader = function (selector) {
//selector = selector || '.content:first';
selector = selector || '.entry-loader';
loader.reset(this.$.find(selector));
};
StreamEntry.prototype.editSubmit = function (evt) {
var that = this;
client.submit(evt.$form, {
url: evt.url,
dataType: 'html',
beforeSend: function () {
//that.loader('.content_edit:first');
that.loader();
},
success: function (response) {
that.$.html(response.html);
}
});
};
StreamEntry.prototype.stick = function (evt) {
var that = this;
this.loader();
var stream = that.getStream();
client.post(evt.url).done(function (data) {
if (data.success) {
that.remove().then(function () {
stream.loadEntry(that.getKey(), {'prepend': true});
});
}
});
};
StreamEntry.prototype.unstick = function (evt) {
this.loader();
client.post(evt.url).done(function (data) {
module.init();
});
};
StreamEntry.prototype.archive = function (evt) {
var that = this;
this.loader();
client.post(evt.url).then(function (response) {
if (response.success) {
that.reload().then(function () {
log.info(module.text('info.archive.success'), true);
});
}
}).catch(function (e) {
log.error(e, true);
});
};
StreamEntry.prototype.unarchive = function (evt) {
var that = this;
this.loader();
client.post(evt.url).then(function (response) {
if (response.success) {
that.reload().then(function () {
log.info(module.text('info.unarchive.success'), true);
});
}
}).catch(function (e) {
log.error('Unexpected error', e, true);
});
};
StreamEntry.prototype.getStream = function () {
// Just return the parent stream component.
return this.parent();
};
/**
* Stream implementation.
* Generic Stream implementation.
*
* @param {type} container id or jQuery object of the stream container
* @returns {undefined}
*/
var Stream = function (container) {
Content.call(this, container);
var Stream = function (container, cfg) {
Component.call(this, container);
this.cfg = this.initConfig(cfg);
//If a contentId is set on the stream root we will only show the single content
//If a contentId is set on the stream, the root we will only show a single entry
if (this.$.data(DATA_STREAM_CONTENTID)) {
this.contentId = parseInt(this.$.data(DATA_STREAM_CONTENTID));
}
this.$stream = this.$.find(".s2_stream");
this.$stream = this.$;
//Cache some stream relevant data/nodes
this.url = this.$.data('stream'); //TODO: set this in config instead of data field
this.$loader = this.$stream.find(".streamLoader");
this.$content = this.$stream.find('.s2_streamContent');
this.$filter = $('.wallFilterPanel');
this.url = this.$.data('stream');
this.$loader = this.$stream.find(this.cfg['loaderSelector']);
this.$content = this.$stream.find(this.cfg['contentSelector']);
this.$filter = this.cfg['filterPanel'];
//TODO: make this configurable
this.filters = [];
this.sort = "c";
};
object.inherits(Stream, Content);
object.inherits(Stream, Component);
/**
* Initializes the stream configuration with default values.
*
* @param {type} cfg
* @returns {humhub_stream_L5.Stream.prototype.initConfig.cfg}
*/
Stream.prototype.initConfig = function (cfg) {
cfg = cfg || {};
cfg['filterPanel'] = cfg['filterPanel'] || $('<div></div>');
cfg['loaderSelector'] = cfg['loaderSelector'] || ".streamLoader";
cfg['filterSelector'] = cfg['filterSelector'] || ".wallFilterPanel";
cfg['contentSelector'] = cfg['contentSelector'] || "[data-stream-content]";
cfg['loadInitialCount'] = cfg['loadInitialCount'] || STREAM_INIT_COUNT;
cfg['loadCount'] = cfg['loadCount'] || STREAM_LOAD_COUNT;
cfg['streamEntryClass'] = cfg['streamEntryClass'] || StreamEntry;
return cfg;
};
/**
* The stream itself does not provide any content actions.
*
* @returns {Array}
*/
Stream.prototype.getContentActions = function () {
return [];
};
@ -129,128 +280,210 @@ humhub.initModule('stream', function (module, require, $) {
Stream.prototype.init = function () {
this.clear();
this.$stream.show();
if (this.isShowSingleEntry()) {
this.loadSingleEntry(this.contentId);
this.loadEntry(this.contentId);
} else {
this.loadEntries(STREAM_INIT_COUNT).then(function() {
this.loadEntries({'limit': this.cfg['loadInitialCount']}).then(function () {
/**
* TODO: REWRITE OLD INITPLUGINS!!!
*/
initPlugins();
});
}
return this;
};
/**
* Clears the stream content.
*
* @returns {undefined}
*/
Stream.prototype.clear = function () {
this.lastEntryLoaded = false;
this.readOnly = false;
this.loading = false;
this.$.find(".s2_streamContent").empty();
this.$.find(".s2_stream").hide();
this.$.find(".s2_single").hide();
this.$.find(".streamLoader").hide();
this.$.find(".emptyStreamMessage").hide();
this.$.find(".emptyFilterStreamMessage").hide();
this.$.find('.back_button_holder').hide();
this.$content.empty();
this.$stream.hide();
//this.$.find(".s2_single").hide();
this.hideLoader();
this.$filter.hide();
this.$.trigger('humhub:modules:stream:clear', this);
};
Stream.prototype.loadSingleEntry = function (contentId) {
this.$.find('.back_button_holder').show();
this.loadEntries(1, (contentId + 1), '');
/**
* Loads a single stream entry by a given content id.
*
* @param {type} contentId
* @returns {undefined}
*/
Stream.prototype.loadEntry = function (contentId, cfg) {
cfg = cfg || {};
cfg['contentId'] = contentId;
var that = this;
return new Promise(function (resolve, reject) {
that.loadEntries(cfg).then(function ($entryNode) {
resolve($entryNode);
}).catch(reject);
});
};
/**
* Reloads a given entry either by providing the contentId or a StreamEntry instance.
* This function returns a Promise instance.
*
* @param {string|StreamEntry} entry
* @returns {Promise}
*/
Stream.prototype.reloadEntry = function (entry) {
var that = this;
return new Promise(function (resolve, reject) {
entry = (entry instanceof StreamEntry) ? entry : that.getEntry(entry);
entry = (object.isString(entry)) ? that.getEntry(entry) : entry;
if (!entry) {
console.warn('Attempt to reload of non existent entry: ' + entry);
reject();
return;
log.warn('Attempt to reload non existing entry');
return reject();
}
var contentId = entry.getKey();
return that._load(1, (contentId + 1), '').then(function (response) {
if (response.content[contentId]) {
entry.replaceContent(response.content[contentId].output);
that.loadEntry(contentId, {'preventInsert': true}).then(function ($entryNode) {
if (!$entryNode || !$entryNode.length) {
entry.remove();
resolve(entry);
} else {
console.warn('Reload failed: ContentId not found in response: ' + contentId);
reject();
entry.$.fadeOut();
entry.$.replaceWith($entryNode);
$entryNode.fadeIn(function () {
resolve(entry);
});
}
}, reject);
});
};
Stream.prototype.loadEntries = function (limit, from, filter, sort) {
if (this.loading || this.lastEntryLoaded) {
return;
}
/**
* Loads new entries to a stream by the given stream settings.
*
* @param {type} limit
* @param {type} from
* @param {type} filter
* @param {type} sort
* @returns {Promise|undefined}
*/
Stream.prototype.loadEntries = function (cfg) {
// Overwrite the default stream settings if provided
cfg = this.initLoadConfig(cfg);
//Initialize loading process
this.$loader.show();
this.loading = true;
//Overwrite the stream settings if provided
limit = limit || STREAM_LOAD_COUNT;
from = from || this.getLastContentId();
filter = filter || this.getFilterString();
sort = sort || this.sort;
this.$.trigger('humhub:modules:stream:beforeLoadEntries', [this, cfg]);
var that = this;
return new Promise(function (resolve, reject) {
that._load(limit, from, filter, sort).then(function (response) {
that.$loader.hide();
if (object.isEmpty(response.content)) {
var $result;
// Don't proceed if stream is already loading
if (that.loading || that.lastEntryLoaded) {
resolve();
return;
}
that.showLoader();
that.loading = true;
that._load(cfg).then(function (response) {
that.hideLoader();
// If its not a single entry load and we get no content, we expect last entry is loaded
// This may have to be change if we require to reload multiple elements.
if (!cfg['contentId'] && object.isEmpty(response.content)) {
that.lastEntryLoaded = true;
$('#btn-load-more').hide();
that.$.trigger('humhub:modules:stream:lastEntryLoaded');
} else {
that.lastEntryLoaded = response.isLast;
that.appendEntries(response);
$result = that.addEntries(response, cfg['prepend']);
}
that.loading = false;
that.onChange();
resolve();
that.$.trigger('humhub:modules:stream:afterLoadEntries', this);
resolve($result);
}).catch(function (err) {
//TODO: handle error
that.loading = false;
that.$loader.hide();
reject();
that.hideLoader();
reject(err);
});
});
};
Stream.prototype._load = function (limit, from, filter, sort) {
Stream.prototype.initLoadConfig = function (cfg) {
cfg = cfg || {};
if (!object.isDefined(cfg['contentId'])) {
cfg['limit'] = object.isDefined(cfg['limit']) ? cfg['limit'] : this.loadCount;
cfg['from'] = object.isDefined(cfg['from']) ? cfg['from'] : this.getLastContentId();
cfg['sort'] = cfg['sort'] || this.sort;
} else {
cfg['limit'] = 1;
}
cfg['filter'] = cfg['filter'] || this.getFilterString();
cfg['prepend'] = object.isDefined(cfg['prepend']) ? cfg['prepend'] : false;
return cfg;
}
Stream.prototype.showLoader = function () {
loader.append(this.$content);
};
Stream.prototype.hideLoader = function () {
this.$content.find('.humhub-ui-loader').remove();
};
Stream.prototype._load = function (cfg) {
cfg = cfg || {}
return client.ajax(this.url, {
data: {
filters: filter,
sort: sort,
from: from,
limit: limit
filters: cfg.filter,
sort: cfg.sort,
from: cfg.from,
limit: cfg.limit,
id: cfg.contentId
}
});
};
/**
* Returns the content id of the last entry loaded.
* @returns {unresolved}
*/
Stream.prototype.getLastContentId = function () {
var $lastEntry = this.$stream.find(DATA_STREAM_ENTRY_SELECTOR).last();
if ($lastEntry.length) {
return $lastEntry.data(DATA_STREAM_CONTENTID);
return $lastEntry.data(DATA_STREAM_ENTRY_ID_SELECTOR);
}
};
Stream.prototype.prependEntry = function (response) {
return this.$content.prepend(response.output);
Stream.prototype.prependEntry = function (html) {
var $html = $(html).hide();
this.$content.prepend($html);
$html.fadeIn();
};
Stream.prototype.appendEntry = function (response) {
return this.$content.append(response.output);
Stream.prototype.appendEntry = function (html) {
var $html = $(html).hide();
this.$content.append($html);
$html.fadeIn();
};
Stream.prototype.appendEntries = function (response) {
/**
* Appends all entries of a given stream response to the stream content.
*
* @param {type} response
* @returns {unresolved}
*/
Stream.prototype.addEntries = function (response, cfg) {
var that = this;
var result = '';
$.each(response.contentOrder, function (i, key) {
@ -260,20 +493,31 @@ humhub.initModule('stream', function (module, require, $) {
}
result += response.content[key].output;
});
return this.$content.append(result);
var $result = $(result).hide();
if (cfg['preventInsert']) {
return $result;
}
this.$.trigger('humhub:modules:stream:beforeAddEntries', [response, result]);
if (cfg['prepend']) {
this.prependEntry($result);
} else {
this.appendEntry($result);
}
this.$.trigger('humhub:modules:stream:afterAddEntries', [response, result]);
$result.fadeIn('fast');
return $result;
};
/**
* Fired when new entries are shown
*/
Stream.prototype.onChange = function () {
if (this.readOnly) {
$('.wallReadOnlyHide').hide();
$('.wallReadOnlyShow').show();
} else {
$('.wallReadOnlyShow').hide();
}
var hasEntries = this.hasEntries();
if (!hasEntries && !this.hasFilter()) {
this.$.find('.emptyStreamMessage').show();
@ -287,30 +531,55 @@ humhub.initModule('stream', function (module, require, $) {
}
this.$entryCache = this.getEntryNodes();
//TODO: fire global event
};
/**
* Checks if the stream is single entry mode.
* @returns {boolean}
*/
Stream.prototype.isShowSingleEntry = function () {
return object.isDefined(this.contentId);
};
/**
* Checks if the stream has entries loaded.
*
* @returns {boolean}
*/
Stream.prototype.hasEntries = function () {
return this.getEntryCount() > 0;
};
/**
* Returns the count of loaded stream entries.
*
* @returns {humhub_stream_L5.Stream.$.find.length}
*/
Stream.prototype.getEntryCount = function () {
return this.$.find(DATA_STREAM_ENTRY_SELECTOR).length;
};
/**
* Returns all stream entry nodes.
*
* @returns {unresolved}
*/
Stream.prototype.getEntryNodes = function () {
return this.$.find(DATA_STREAM_ENTRY_SELECTOR);
};
/**
* Checks if a stream has filter settings.
* @returns {boolean}
*/
Stream.prototype.hasFilter = function () {
return this.filters.length > 0;
};
/**
* Creates a filter string out of the filter array.
* @returns {string}
*/
Stream.prototype.getFilterString = function () {
var result = '';
$.each(this.filters, function (i, filter) {
@ -320,12 +589,24 @@ humhub.initModule('stream', function (module, require, $) {
return string.cutsuffix(result, ',');
};
/**
* Adds a given filterId to the filter array.
*
* @param {type} filterId
* @returns {undefined}
*/
Stream.prototype.setFilter = function (filterId) {
if (this.filters.indexOf(filterId) < 0) {
this.filters.push(filterId);
}
};
/**
* Clears a given filter.
*
* @param {type} filterId
* @returns {undefined}
*/
Stream.prototype.unsetFilter = function (filterId) {
var index = this.filters.indexOf(filterId);
if (index > -1) {
@ -333,29 +614,78 @@ humhub.initModule('stream', function (module, require, $) {
}
};
/**
* Returns a StreamEntry instance for a iven content id.
* @param {type} key
* @returns {humhub_stream_L5.StreamEntry}
*/
Stream.prototype.getEntry = function (key) {
return new StreamEntry(this.$.find(DATA_STREAM_ENTRY_SELECTOR+'[data-content-key="' + key + '"]'));
return new this.cfg.streamEntryClass(this.$.find(DATA_STREAM_ENTRY_SELECTOR + '[data-content-key="' + key + '"]'));
};
/**
* Creates a new StreamEntry out of the given childNode.
* @param {type} $childNode
* @returns {humhub_stream_L5.StreamEntry}
*/
Stream.prototype.getEntryByNode = function ($childNode) {
return new StreamEntry($childNode.closest(DATA_STREAM_ENTRY_SELECTOR));
return new this.cfg.streamEntryClass($childNode.closest(DATA_STREAM_ENTRY_SELECTOR));
};
var getStream = function () {
if (!module.instance) {
var $stream = $(DATA_STREAM_SELECTOR).first();
module.instance = $stream.length ? new Stream($stream) : undefined;
/**
* Stream implementation for main wall streams.
*
* @param {type} container
* @param {type} cfg
* @returns {undefined}
*/
var WallStream = function (container, cfg) {
cfg = cfg || {};
cfg['filterPanel'] = $('.wallFilterPanel');
Stream.call(this, container, cfg);
var that = this;
this.$.on('humhub:modules:stream:clear', function () {
that.$.find(".emptyStreamMessage").hide();
that.$.find(".emptyFilterStreamMessage").hide();
that.$.find('.back_button_holder').hide();
});
this.$.on('humhub:modules:stream:afterAppendEntries', function (evt, stream) {
if (that.isShowSingleEntry()) {
that.$.find('.back_button_holder').show();
}
});
this.$.on('humhub:modules:stream:lastEntryLoaded', function () {
$('#btn-load-more').hide();
});
};
object.inherits(WallStream, Stream);
var getStream = function ($selector) {
$selector = $selector || DATA_WALL_STREAM_SELECTOR;
if (!streams[$selector]) {
var $stream = (!$selector) ? $(DATA_WALL_STREAM_SELECTOR) : $($selector).first();
return streams[$selector] = $stream.length ? new WallStream($stream) : undefined;
}
return module.instance;
return streams[$selector];
};
var getEntry = function (id) {
return module.getStream().getEntry(id);
};
/**
* Initializes wall stream
* @returns {undefined}
*/
var init = function () {
var stream = getStream();
streams = {};
var stream = getStream();
if (!stream) {
console.log('Non-Stream Page!');
return;
@ -363,8 +693,14 @@ humhub.initModule('stream', function (module, require, $) {
stream.init();
var lastKey;
event.on('humhub:modules:content:newEntry', function (evt, html) {
stream.prependEntry(html);
});
$(window).scroll(function () {
if (stream.isShowSingleEntry()) {
return;
}
var $window = $(window);
var scrollTop = $window.scrollTop();
var windowHeight = $window.height();
@ -374,27 +710,32 @@ humhub.initModule('stream', function (module, require, $) {
}
}
// Defines our base y position for changing the current entry
var yLimit = scrollTop + (windowHeight / 2);
/*
// Get id of current scroll item
//TODO: chache the entry nodes !
var matchingNodes = stream.$entryCache.map(function () {
var $this = $(this);
if ($this.offset().top < yLimit) {
return $this;
}
});
This can be used to trace the currently visible entries
// Get the id of the current element
var $current = matchingNodes[matchingNodes.length - 1];
var currentKey = $current && $current.length ? $current.data('content-key') : "";
var lastKey;
// Defines our base y position for changing the current entry
var yLimit = scrollTop + (windowHeight / 2);
if (lastKey !== currentKey) {
lastKey = currentKey;
// Set/remove active class
console.log(currentKey);
}
// Get id of current scroll item
//TODO: chache the entry nodes !
var matchingNodes = stream.$entryCache.map(function () {
var $this = $(this);
if ($this.offset().top < yLimit) {
return $this;
}
});
// Get the id of the current element
var $current = matchingNodes[matchingNodes.length - 1];
var currentKey = $current && $current.length ? $current.data('content-key') : "";
if (lastKey !== currentKey) {
lastKey = currentKey;
// Set/remove active class
}
*/
});
stream.$.on('click', '.singleBackLink', function () {
@ -444,108 +785,14 @@ humhub.initModule('stream', function (module, require, $) {
module.export({
StreamEntry: StreamEntry,
Stream: Stream,
WallStream: WallStream,
getStream: getStream,
getEntry: getEntry,
init: init
});
});
/* TODO:
Stream.prototype.wallStick = function (url) {
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
currentStream.deleteEntry(wallEntryId);
currentStream.prependEntry(wallEntryId);
});
$('html, body').animate({scrollTop: 0}, 'slow');
}
} else {
alert(data.errorMessage);
}
});
};
Stream.prototype.wallUnstick = function (url) {
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
//Reload the whole stream, since we have to reorder the entries
currentStream.showStream();
}
});
};
/**
* Click Handler for Archive Link of Wall Posts
* (archiveLink.php)
*
* @param {type} className
* @param {type} id
Stream.prototype.wallArchive = function (id) {
url = wallArchiveLinkUrl.replace('-id-', id);
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
//currentStream.reloadWallEntry(wallEntryId);
// fade out post
setInterval(fadeOut(), 1000);
function fadeOut() {
// fade out current archived post
$('#wallEntry_' + wallEntryId).fadeOut('slow');
}
});
}
}
});
};
/**
* Click Handler for Un Archive Link of Wall Posts
* (archiveLink.php)
*
* @param {type} className
* @param {type} id
Stream.prototype.wallUnarchive = function (id) {
url = wallUnarchiveLinkUrl.replace('-id-', id);
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
currentStream.reloadWallEntry(wallEntryId);
});
}
}
});
};
/*
/*
module.StreamItem.prototype.highlightContent = function () {
var $content = this.getContent();
$content.addClass('highlight');

View File

@ -5,7 +5,18 @@ use yii\web\View;
\humhub\modules\stream\assets\StreamAsset::register($this);
$contentId = (int) Yii::$app->request->getQueryParam('wallEntryId');
$this->registerJsConfig([
'stream' => [
'text' => [
'info.archive.success' => Yii::t('ContentModule.widgets_views_stream', 'The content has been successfully archived.'),
'info.unarchive.success' => Yii::t('ContentModule.widgets_views_stream', 'The content has been successfully unarchived.'),
'info.stick.success' => Yii::t('ContentModule.widgets_views_stream', 'The content has been successfully sticked.'),
'info.unstick.success' => Yii::t('ContentModule.widgets_views_stream', 'The content has been successfully unsticked.'),
]
]
]);
$contentId = (int) Yii::$app->request->getQueryParam('contentId');
$contentIdData = ($contentId != "") ? 'data-stream-contentid="' . $contentId . '"' : '';
if (Yii::$app->settings->get('horImageScrollOnMobile'))
@ -49,16 +60,15 @@ $this->registerJsVar('defaultStreamSort', ($defaultStreamSort != '') ? $defaultS
<!-- Stream content -->
<div id="wallStream" data-stream="<?= $streamUrl ?>" <?= $contentIdData ?>
data-action-component="humhub.modules.stream.Stream"
data-action-component="stream.WallStream"
data-content-delete-url="<?= Url::to(['/content/content/delete']) ?>">
<!-- DIV for a normal wall stream -->
<div class="s2_stream" style="display:none">
<div class="s2_stream">
<div class="back_button_holder" style="display:none">
<a href="#" class="singleBackLink btn btn-primary"><?php echo Yii::t('ContentModule.widgets_views_stream', 'Back to stream'); ?></a><br><br>
</div>
<div class="s2_streamContent"></div>
<?php echo \humhub\widgets\LoaderWidget::widget(['cssClass' => 'streamLoader']); ?>
<div class="s2_streamContent" data-stream-content></div>
<div class="emptyStreamMessage" style="display:none;">
<div class="<?php echo $this->context->messageStreamEmptyCss; ?>">

View File

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

View File

@ -3,6 +3,7 @@
namespace tests\codeception\_support;
use Codeception\Module;
use Yii;
/**
* This helper is used to populate the database with needed fixtures before any tests are run.
@ -21,5 +22,17 @@ class WebHelper extends Module
public function _beforeSuite($settings = [])
{
include __DIR__.'/../acceptance/_bootstrap.php';
$this->initModules();
}
/**
* Initializes modules defined in @tests/codeception/config/test.config.php
* Note the config key in test.config.php is modules and not humhubModules!
*/
protected function initModules() {
$cfg = \Codeception\Configuration::config();
if(!empty($cfg['humhub_modules'])) {
Yii::$app->moduleManager->enableModules($cfg['humhub_modules']);
}
}
}

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;
/**
* GlobalConfirmModal used as template for humhub.ui.modal.confirm actions.
*
* @see LayoutAddons
* @author buddha
* @since 1.2
*/
class GlobalConfirmModal extends \yii\base\Widget
{
/**
* @inheritdoc
*/
public function run()
{
return $this->render('globalConfirmModal');
}
}

View File

@ -9,6 +9,7 @@
namespace humhub\widgets;
use yii\base\Widget;
use Yii;
/**
* AjaxButton is an replacement for Yii1 CHtml::AjaxButton
@ -17,9 +18,39 @@ use yii\base\Widget;
*/
class JSConfig extends Widget
{
public function run()
{
return $this->render('jsConfig');
$this->getView()->registerJsConfig(
[
'action' => [
'text' => [
'actionHandlerNotFound' => Yii::t('base', 'An error occured while handling your last action. (Handler not found).'),
]
],
'ui.modal' => [
'defaultConfirmHeader' => Yii::t('base', '<strong>Confirm</strong> Action'),
'defaultConfirmBody' => Yii::t('base', 'Do you really want to perform this Action?'),
'defaultConfirmText' => Yii::t('base', 'Confirm'),
'defaultCancelText' => Yii::t('base', 'Cancel')
],
'log' => [
'traceLevel' => (YII_DEBUG) ? 'DEBUG' : 'INFO',
'text' => [
'default.error' => Yii::t('base', 'An unexpected error occured. If this keeps happening, please contact a site administrator.'),
'0' => Yii::t('base', 'An unexpected error occured. If this keeps happening, please contact a site administrator.'),
'403' => Yii::t('base', 'You are not allowed to run this action.'),
'500' => Yii::t('base', 'An unexpected server error occured. If this keeps happening, please contact a site administrator.')
]
],
'ui.status' => [
'showMore' => Yii::$app->user->isAdmin() || YII_DEBUG,
'text' => [
'showMore' => Yii::t('base', 'Show more'),
'showLess' => Yii::t('base', 'Show less')
]
]
]);
}
}

View File

@ -24,9 +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());
if (Yii::$app->params['enablePjax']) {
$this->addWidget(Pjax::className());

View File

@ -44,6 +44,11 @@ class LoaderWidget extends \yii\base\Widget
*/
public $cssClass = "";
/**
* defines if the loader is initially shown
*/
public $show = true;
/**
* Displays / Run the Widgets
*/
@ -52,6 +57,7 @@ class LoaderWidget extends \yii\base\Widget
return $this->render('loader', [
'id' => $this->id,
'cssClass' => $this->cssClass,
'show' => $this->show
]);
}

View File

@ -101,6 +101,7 @@ class Modal extends Widget
* @var type
*/
public $initialLoader;
public function run()
{
$dialogClass = 'modal-dialog';

View File

@ -29,6 +29,7 @@ namespace humhub\widgets;
* @package humhub.widgets
* @since 0.5
* @author Andreas Strobel
* @deprecated 1.2 Prefer using js api humhub.ui.modal.confirm.
*/
class ModalConfirm extends \yii\base\Widget
{

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;
/**
* StatusBar for user feedback (error/warning/info).
*
* @see LayoutAddons
* @author buddha
* @since 1.2
*/
class StatusBar extends \yii\base\Widget
{
/**
* @inheritdoc
*/
public function run()
{
return $this->render('statusBar');
}
}

View File

@ -1,19 +1,10 @@
<!-- check if flash message exists -->
<?php if(Yii::$app->getSession()->hasFlash('data-saved')): ?>
<!-- <span> element to display the message -->
<span class="data-saved"><i class="fa fa-check-circle"></i> <?php echo Yii::$app->getSession()->getFlash('data-saved'); ?></span>
<script type="text/javascript">
/* animate the flash message */
$('.data-saved').hide();
$('.data-saved').fadeIn('slow', function() {
$('.data-saved').delay(1000).fadeOut('slow', function() {
$(this).remove();
});
$(function() {
humhub.modules.log.success('<?php echo Yii::$app->getSession()->getFlash('data-saved'); ?>', true);
});
</script>
<?php endif; ?>

View File

@ -0,0 +1,11 @@
<?php
echo \humhub\widgets\Modal::widget([
'id' => 'globalModalConfirm',
'size' => 'extra-small',
'centerText' => true,
'animation' => 'pulse',
'footer' => '<button data-modal-cancel data-modal-close class="btn btn-primary"></button><button data-modal-confirm data-modal-close class="btn btn-primary"></button>'
]);
?>

View File

@ -1,3 +1,4 @@
<?php //Todo: use base Modal widget ?>
<div class="modal" id="globalModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">

View File

@ -1,16 +0,0 @@
<script type="text/javascript">
humhub.config.set({
'ui.modal': {
'defaultConfirmHeader': '<?= Yii::t('base', '<strong>Confirm</strong> Action') ?>',
'defaultConfirmBody': '<?=Yii::t('base', 'Do you really want to perform this Action?') ?>',
'defaultConfirmText': '<?=Yii::t('base', 'Confirm') ?>',
'defaultCancelText': '<?=Yii::t('base', 'Cancel') ?>'
}
});
</script>

View File

@ -1,4 +1,4 @@
<div id="<?php echo $id; ?>" class="loader <?php echo $cssClass; ?>">
<div id="<?php echo $id; ?>" class="loader humhub-ui-loader <?php echo $cssClass; ?>" <?php if(isset($show) && !$show) : ?> style="display:none;" <?php endif; ?>>
<div class="sk-spinner sk-spinner-three-bounce">
<div class="sk-bounce1"></div>
<div class="sk-bounce2"></div>

View File

@ -0,0 +1,5 @@
<div id="status-bar" style="display:none;">
<div class="status-bar-body">
<div class="status-bar-content"></div>
</div>
</div>

File diff suppressed because one or more lines are too long

View File

@ -3030,8 +3030,92 @@ img.bounceIn {
background: lighten(@info, 25%) !important;
}
#nprogress .bar {
height:2px;
background: @info;
}
//
// Statusbar
// --------------------------------------------------
#status-bar {
}
.status-bar-body {
color: white;
position: fixed;
width: 100%;
background-color: rgba(0, 0, 0, 0.7);
text-align: center;
padding: 20px;
z-index: 9999999;
bottom: 0px;
display: block;
line-height:20px;
}
.status-bar-close {
color:white;
fonfont-weight:bold;
font-size:21px;
cursor: pointer;
}
.status-bar-close:hover {
color:white;
}
.status-bar-close i {
vertical-align:top !important;
padding-top:3px;
}
.status-bar-content i {
margin-right:10px;
font-size:21px;
vertical-align:middle;
}
.status-bar-content .showMore {
color: @info;
margin-left:10px;
font-size:0.7em;
cursor:pointer;
vertical-align:middle;
white-space:nowrap;
}
.status-bar-content .status-bar-details {
text-align:left;
font-size:0.7em;
margin-top:20px;
max-height:200px;
overflow:auto;
}
.status-bar-content span {
vertical-align:middle;
}
.status-bar-content i.error, .status-bar-content i.fatal {
color:@danger;
}
.status-bar-content i.warning {
color:@warning;
}
.status-bar-content i.info, .status-bar-content i.debug {
color:@info;
}
.status-bar-content i.success {
color:#85CA2B;
}
.entry-loader {
float:right;
}