Richtext JS Api, Disabled Archived Stream, Stream Console Fix

This commit is contained in:
buddha87 2016-12-18 23:05:34 +01:00
parent db66b3582c
commit 0cef6f0330
96 changed files with 7628 additions and 457 deletions

View File

@ -42,7 +42,7 @@
"bower-asset/jquery-color": "^2.1.2",
"bower-asset/autosize": "1.*",
"bower-asset/nprogress": "*",
"bower-asset/At.js": "0.5.0",
"bower-asset/At.js": "^1.5.1",
"bower-asset/animate.css": "*",
"bower-asset/html5shiv": "^3.7"
},
@ -60,12 +60,6 @@
"scripts": {
"post-create-project-cmd": [
"yii\\composer\\Installer::postCreateProject"
],
"post-install-cmd": [
"PhantomInstaller\\Installer::installPhantomJS"
],
"post-update-cmd": [
"PhantomInstaller\\Installer::installPhantomJS"
]
},
"extra": {

View File

@ -350,7 +350,7 @@ humhub.module('action', function(module, require, $) {
// As last resort we try to call the action by namespace for handlers like humhub.modules.myModule.myAction
var handlerAction = event.handler.split('.').pop();
var target = require(string.cutsuffix(event.handler, '.' + handlerAction));
var target = require(string.cutSuffix(event.handler, '.' + handlerAction));
if(!_executeAction(target, handlerAction, event)) {
module.log.error('actionHandlerNotFound', event.handler, true);
@ -576,6 +576,7 @@ humhub.module('action', function(module, require, $) {
var trigger = function($trigger, type, options) {
options.$trigger = $trigger;
// For manually triggered action events we do not need a block in most cases.
if(!options.block) {
options.block = BLOCK_NONE;
}

View File

@ -3,7 +3,7 @@ humhub.module('client.pjax', function(module, require, $) {
var init = function() {
if (module.config.active) {
$(document).pjax('a:not([data-pjax-prevent],[target="_blank"],[data-target])', "#layout-content", module.config.options);
$(document).pjax('a:not([data-pjax-prevent],[target],[data-target],[data-toggle])', "#layout-content", module.config.options);
pjaxRedirectFix();
module.installLoader();
}

158
js/humhub/humhub.live.js Normal file
View File

@ -0,0 +1,158 @@
humhub.module('live', function(module, require, $) {
var object = require('util').object;
var space = require('space', true);
var user = require('user', true);
var instances = [];
var _delay = 11;
var _idleFactor = 0;
var _defaultOptions = {
id: 'unknown',
min: 20,
max: 45,
idle: true,
idleFactor: 1,
active: true
};
var LiveUpdate = function(topic, options) {
this.topic = (arguments.length === 1) ? topic.topic : topic;
this.options = (arguments.length === 1) ? topic.options : options;
this.options = $.extend(_defaultOptions, this.options);
this.id = this.options.id;
};
LiveUpdate.prototype.on = function() {
this.options.active = true;
};
LiveUpdate.prototype.off = function() {
this.options.active = false;
};
LiveUpdate.prototype.isExpired = function() {
if(!this.options.active) {
return false;
} else if(!this.lastUpdate) {
return true;
}
var minDelay = this.options.min + (this.options.min * (_idleFactor * this.options.idleFactor));
if(minDelay > this.options.max) {
minDelay = this.options.max;
}
return (this.lastUpdate + minDelay) >= Date.now();
};
LiveUpdate.prototype.handleResult = function(result) {
}
LiveUpdate.prototype.validate = function() {
return !((this.topic.user && user.isGuest()) || (this.topic.space && !space.guid()));
};
var register = function(topic, options) {
var instance = new LiveUpdate(topic, options);
instances.push(instance);
return instance;
};
var init = function() {
setTimeout(_run, _delay);
};
var _run = function() {
var topics = [];
var lastUpdate = Date.now();
instances.forEach(function(update) {
if(update.isExpired() && update.validate()) {
var topic = _getServerTopic(update);
module.log.debug('Topic update:' + update.id, topic);
topics.push(topic);
update.lastUpdate = lastUpdate;
}
});
_send().then(function(result) {
instances.forEach(function(liveUpdate) {
liveUpdate.handleResult(result);
});
}).catch(function(err) {
// Silent error log
module.log.error(err);
});
}
var _getServerTopic = function(update) {
var result = {};
if(update.topic.user === true) {
result.uguid = user.guid();
} else if(update.topic.uguid) {
result.uguid = update.topic.uguid;
}
if(update.topic.space) {
result.sguid = space.guid();
} else if(update.topic.sguid) {
result.sguid = update.topic.sguid;
}
if(update.topic.suffix) {
result.suffix = update.topic.suffix;
}
if(update.topic.module) {
result.module = update.topic.module;
}
if(update.lastResult) {
result.last = update.lastResult;
}
return result;
};
var update = new LiveUpdate({
topic: {
'user': true,
'space': true,
'module': 'mail',
'suffix': 'whatever'
},
options: {
min: 10,
max: 30,
idle: true,
idleFactor: 1
}
});
/**
LiveUpdate
Settings
LastUpdate -> Date this specific update was requested the last time.
(Register To) --> function or static object
- User
- Space
- Module
(Live Settings)
- Min Duration
- Idle Factor
(Callback)
- Callback
-On/Off
*/
module.export({
init: init,
register: register
});
});

View File

@ -15,13 +15,13 @@
* @param {type} param1
* @param {type} param2
*/
humhub.module('ui.modal', function (module, require, $) {
humhub.module('ui.modal', function(module, require, $) {
var util = require('util');
var object = util.object;
var string = util.string;
var additions = require('ui.additions');
var loader = require('ui.loader');
var client = require('client', true);
var Widget = require('ui.widget').Widget;
//Keeps track of all initialized modals
var modals = [];
@ -36,22 +36,25 @@ humhub.module('ui.modal', function (module, require, $) {
*
* @param {string} id - id of the modal
*/
var Modal = function (id, options) {
options = options || {};
var selector = string.startsWith(id, '#') ? id : '#' + id;
this.$ = $(selector);
var Modal = function(node, options) {
Widget.call(this, node, options);
};
if (!this.$.length) {
object.inherits(Modal, Widget)
Modal.component = 'humhub-ui-modal';
;
Modal.prototype.init = function() {
if(!this.$.length) {
this.createModal(id);
this.reset();
}
this.initModal(options);
this.initModal(this.options);
modals.push(this);
return this;
};
/**
* Template for the modal splitted into different parts. Those can be overwritten my changing or overwriting module.template.
*/
@ -60,19 +63,19 @@ humhub.module('ui.modal', function (module, require, $) {
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>',
footer: '<div class="modal-footer"></div>',
};
};
/**
* Creates a new modal dom skeleton.
* @param {type} id the modal id
* @returns {undefined}
*/
Modal.prototype.createModal = function (id) {
Modal.prototype.createModal = function(id) {
this.$ = $(this.getTemplate('container')).attr('id', id);
$('body').append(this.$);
};
Modal.prototype.getTemplate = function (id) {
Modal.prototype.getTemplate = function(id) {
return Modal.template[id];
};
@ -80,13 +83,13 @@ humhub.module('ui.modal', function (module, require, $) {
* Initializes default modal events and sets initial data.
* @returns {undefined}
*/
Modal.prototype.initModal = function (options) {
Modal.prototype.initModal = function(options) {
var that = this;
//Set default modal manipulation event handlers
this.$.on('click', '[data-modal-close]', function () {
this.$.on('click', '[data-modal-close]', function() {
that.close();
}).on('click', '[data-modal-clear-error]', function () {
}).on('click', '[data-modal-clear-error]', function() {
that.clearErrorMessage();
});
@ -95,7 +98,7 @@ humhub.module('ui.modal', function (module, require, $) {
this.$.attr('aria-labelledby', this.getTitleId());
};
Modal.prototype.getTitleId = function () {
Modal.prototype.getTitleId = function() {
return this.$.attr('id') + '-title';
};
@ -103,11 +106,11 @@ humhub.module('ui.modal', function (module, require, $) {
* Closes the modal with fade animation and sets the loader content
* @returns {undefined}
*/
Modal.prototype.close = function (reset) {
Modal.prototype.close = function(reset) {
var that = this;
this.$.fadeOut('fast', function () {
this.$.fadeOut('fast', function() {
that.$.modal('hide');
if (reset) {
if(reset) {
that.reset();
}
});
@ -117,7 +120,7 @@ humhub.module('ui.modal', function (module, require, $) {
* Sets the loader content and shows the modal
* @returns {undefined}
*/
Modal.prototype.loader = function () {
Modal.prototype.loader = function() {
this.reset();
this.show();
};
@ -126,7 +129,7 @@ humhub.module('ui.modal', function (module, require, $) {
* Sets the default content (a loader animation)
* @returns {undefined}
*/
Modal.prototype.reset = function () {
Modal.prototype.reset = function() {
// Clear old script tags.
var $content = this.getContent().empty();
this.$.find('script').remove();
@ -141,18 +144,18 @@ humhub.module('ui.modal', function (module, require, $) {
* @param {function} callback - callback function is called after html was inserted
* @returns {undefined}
*/
Modal.prototype.setContent = function (content, callback) {
Modal.prototype.setContent = function(content, callback) {
var that = this;
return new Promise(function (resolve, reject) {
return new Promise(function(resolve, reject) {
// TODO: assure content order header/content/footer
try {
that.clearErrorMessage();
that.getContent().html(content).promise().always(function () {
that.getContent().html(content).promise().always(function() {
that.applyAdditions();
});
that.isFilled = true;
resolve(that);
} catch (err) {
} catch(err) {
that.setErrorMessage(err.message);
// We try to apply additions anyway
that.applyAdditions();
@ -161,15 +164,32 @@ humhub.module('ui.modal', function (module, require, $) {
});
};
Modal.prototype.applyAdditions = function () {
Modal.prototype.applyAdditions = function() {
additions.applyTo(this.getContent());
};
Modal.prototype.load = function (url, cfg, originalEvent) {
Modal.prototype.load = function(url, cfg, originalEvent) {
var that = this;
return new Promise(function (resolve, reject) {
that.loader();
client.html(url, cfg, originalEvent).then(function (response) {
return new Promise(function(resolve, reject) {
if(!that.isVisible()) {
that.loader();
}
client.html(url, cfg, originalEvent).then(function(response) {
that.setDialog(response.html);
resolve(response);
}).catch(reject);
});
};
Modal.prototype.post = function(url, cfg, originalEvent) {
var that = this;
var cfg = cfg || {};
cfg.dataType = cfg.dataType || 'html';
return new Promise(function(resolve, reject) {
if(!that.isVisible()) {
that.loader();
}
client.post(url, cfg, originalEvent).then(function(response) {
that.setDialog(response.html);
resolve(response);
}).catch(reject);
@ -184,9 +204,9 @@ humhub.module('ui.modal', function (module, require, $) {
* @param {type} message
* @returns {undefined}
*/
Modal.prototype.error = function (title, message) {
Modal.prototype.error = function(title, message) {
if (arguments.length === 1 && title) {
if(arguments.length === 1 && title) {
message = (title.getFirstError) ? title.getFirstError() : title;
title = (title.getErrorTitle) ? title.getErrorTitle() : ERROR_DEFAULT_TITLE;
}
@ -195,7 +215,7 @@ humhub.module('ui.modal', function (module, require, $) {
message = message || ERROR_DEFAULT_MESSAGE;
//If there is no content yet we create an error only content
if (!this.isFilled) {
if(!this.isFilled) {
this.clear();
this.setHeader(title);
this.setBody('');
@ -211,10 +231,10 @@ humhub.module('ui.modal', function (module, require, $) {
* Removes existing error messages
* @returns {undefined}
*/
Modal.prototype.clearErrorMessage = function () {
Modal.prototype.clearErrorMessage = function() {
var modalError = this.getErrorMessage();
if (modalError.length) {
modalError.fadeOut('fast', function () {
if(modalError.length) {
modalError.fadeOut('fast', function() {
modalError.remove();
});
}
@ -225,9 +245,9 @@ humhub.module('ui.modal', function (module, require, $) {
* @param {type} message
* @returns {undefined}
*/
Modal.prototype.setErrorMessage = function (message) {
Modal.prototype.setErrorMessage = function(message) {
var $errorMessage = this.getErrorMessage();
if ($errorMessage.length) {
if($errorMessage.length) {
$errorMessage.css('opacity', 0);
$errorMessage.text(message);
$errorMessage.animate({'opacity': 1}, 'fast');
@ -240,7 +260,7 @@ humhub.module('ui.modal', function (module, require, $) {
* Returns the current errormessagebox
* @returns {humhub.ui.modal_L18.Modal.prototype@call;getContent@call;find}
*/
Modal.prototype.getErrorMessage = function () {
Modal.prototype.getErrorMessage = function() {
return this.getContent().find('.modal-error');
};
@ -248,9 +268,9 @@ humhub.module('ui.modal', function (module, require, $) {
* Shows the modal
* @returns {undefined}
*/
Modal.prototype.show = function () {
if (!this.$.is(':visible')) {
if (!this.$.data('bs.modal')) {
Modal.prototype.show = function() {
if(!this.$.is(':visible')) {
if(!this.$.data('bs.modal')) {
this.$.modal(this.options);
} else {
this.$.modal('show');
@ -262,7 +282,7 @@ humhub.module('ui.modal', function (module, require, $) {
* Clears the modal content
* @returns {undefined}
*/
Modal.prototype.clear = function () {
Modal.prototype.clear = function() {
this.getContent().empty();
};
@ -270,7 +290,7 @@ humhub.module('ui.modal', function (module, require, $) {
* Retrieves the modal content jQuery representation
* @returns {humhub.ui.modal_L18.Modal.prototype@pro;$modal@call;find}
*/
Modal.prototype.getContent = function () {
Modal.prototype.getContent = function() {
//We use the :first selector since jQuery refused to execute javascript if we set content with inline js
return this.$.find('.modal-content:first');
};
@ -279,7 +299,7 @@ humhub.module('ui.modal', function (module, require, $) {
* Retrieves the modal dialog jQuery representation
* @returns {humhub.ui.modal_L18.Modal.prototype@pro;$modal@call;find}
*/
Modal.prototype.getDialog = function () {
Modal.prototype.getDialog = function() {
return this.$.find('.modal-dialog');
};
@ -287,7 +307,7 @@ humhub.module('ui.modal', function (module, require, $) {
* Returns the modal footer
* @returns {humhub.ui.modal_L18.Modal.prototype@pro;$modal@call;find}
*/
Modal.prototype.getFooter = function () {
Modal.prototype.getFooter = function() {
return this.$.find('.modal-footer');
};
@ -295,7 +315,7 @@ humhub.module('ui.modal', function (module, require, $) {
* Searches for forms within the modal
* @returns {humhub.ui.modal_L18.Modal.prototype@pro;$modal@call;find}
*/
Modal.prototype.getForm = function () {
Modal.prototype.getForm = function() {
return this.$.find('form');
};
@ -304,9 +324,9 @@ humhub.module('ui.modal', function (module, require, $) {
* @param {type} title
* @returns {undefined}
*/
Modal.prototype.setHeader = function (title) {
Modal.prototype.setHeader = function(title) {
var $header = this.getHeader();
if (!$header.length) {
if(!$header.length) {
$header = $(this.getTemplate('header'));
this.getContent().prepend($header);
}
@ -315,9 +335,9 @@ humhub.module('ui.modal', function (module, require, $) {
$header.find('.modal-title').attr('id', this.getTitleId()).html(title);
};
Modal.prototype.setFooter = function (footer) {
Modal.prototype.setFooter = function(footer) {
var $footer = this.getFooter();
if (!$footer.length) {
if(!$footer.length) {
$footer = $(this.getTemplate('footer'));
this.getContent().append($footer);
}
@ -325,29 +345,29 @@ humhub.module('ui.modal', function (module, require, $) {
$footer.html(footer);
};
Modal.prototype.set = function (options) {
Modal.prototype.set = function(options) {
this.options = options;
if (this.options.header) {
if(this.options.header) {
this.setHeader(this.options.header);
}
if (this.options.body) {
if(this.options.body) {
this.setBody(this.options.body);
}
if (this.options.content) {
if(this.options.content) {
this.setContent(this.options.content);
}
if (this.options.footer) {
if(this.options.footer) {
this.setFooter(this.options.footer);
}
this.options.backdrop = options.backdrop || true;
this.options.keyboard = options.keyboard || true;
if (this.$.data('bs.modal')) {
if(this.$.data('bs.modal')) {
this.$.data('bs.modal').options = this.options;
}
@ -358,7 +378,7 @@ humhub.module('ui.modal', function (module, require, $) {
* Retrieves the modal-header element
* @returns {humhub.ui.modal_L18.Modal.prototype@pro;$modal@call;find}
*/
Modal.prototype.getHeader = function () {
Modal.prototype.getHeader = function() {
return this.$.find('.modal-header');
};
@ -367,16 +387,16 @@ humhub.module('ui.modal', function (module, require, $) {
* @param {type} content
* @returns {undefined}
*/
Modal.prototype.setBody = function (content) {
Modal.prototype.setBody = function(content) {
var $body = this.getBody();
if (!$body.length) {
if(!$body.length) {
this.setContent($(this.getTemplate('body')));
$body = this.getBody();
}
$body.html(content);
};
Modal.prototype.setDialog = function (content) {
Modal.prototype.setDialog = function(content) {
this.$.empty().append(content);
this.$.find('input[type="text"]:visible, textarea:visible, [contenteditable="true"]:visible').first().focus();
this.applyAdditions();
@ -387,19 +407,19 @@ humhub.module('ui.modal', function (module, require, $) {
* Retrieves the modal-body element
* @returns {humhub.ui.modal_L18.Modal.prototype@pro;$modal@call;find}
*/
Modal.prototype.getBody = function () {
Modal.prototype.getBody = function() {
return this.$.find('.modal-body');
};
var ConfirmModal = function (id) {
Modal.call(this, id);
var ConfirmModal = function(node, options) {
Modal.call(this, node, options);
};
object.inherits(ConfirmModal, Modal);
ConfirmModal.prototype.open = function (cfg) {
ConfirmModal.prototype.open = function(cfg) {
var that = this;
return new Promise(function (resolve, reject) {
return new Promise(function(resolve, reject) {
cfg = cfg || {};
cfg.handler = resolve;
@ -417,12 +437,12 @@ humhub.module('ui.modal', function (module, require, $) {
});
};
ConfirmModal.prototype.clear = function (cfg) {
ConfirmModal.prototype.clear = function(cfg) {
this.$.find('[data-modal-confirm]').off('click');
this.$.find('[data-modal-cancel]').off('click');
};
ConfirmModal.prototype.initButtons = function (cfg) {
ConfirmModal.prototype.initButtons = function(cfg) {
//Set button text
var $cancelButton = this.$.find('[data-modal-cancel]');
$cancelButton.text(cfg['cancelText']);
@ -432,97 +452,97 @@ humhub.module('ui.modal', function (module, require, $) {
//Init handler
var that = this;
if (cfg['handler']) {
$confirmButton.one('click', function (evt) {
if(cfg['handler']) {
$confirmButton.one('click', function(evt) {
that.clear();
cfg['handler'](true);
});
}
if (cfg['handler']) {
$cancelButton.one('click', function (evt) {
if(cfg['handler']) {
$cancelButton.one('click', function(evt) {
that.clear();
cfg['handler'](false);
});
}
};
};
var init = function () {
module.global = new Modal('#globalModal');
module.global.$.on('hidden.bs.modal', function (e) {
var init = function() {
module.global = Modal.instance('#globalModal');
module.global.$.on('hidden.bs.modal', function(e) {
module.global.reset();
});
module.globalConfirm = new ConfirmModal('#globalModalConfirm');
module.confirm = function (cfg) {
module.globalConfirm = ConfirmModal.instance('#globalModalConfirm');
module.confirm = function(cfg) {
return module.globalConfirm.open(cfg);
};
_setModalEnforceFocus();
_setGlobalModalTargetHandler();
$(document).on('show.bs.modal', '.modal', function (event) {
$(document).on('show.bs.modal', '.modal', function(event) {
$(this).appendTo($('body'));
});
$(document).on('shown.bs.modal', '.modal.in', function (event) {
$(document).on('shown.bs.modal', '.modal.in', function(event) {
_setModalsAndBackdropsOrder();
});
$(document).on('hidden.bs.modal', '.modal', function (event) {
$(document).on('hidden.bs.modal', '.modal', function(event) {
_setModalsAndBackdropsOrder();
});
};
var _setModalsAndBackdropsOrder = function () {
var _setModalsAndBackdropsOrder = function() {
var modalZIndex = 1040;
$('.modal.in').each(function (index) {
$('.modal.in').each(function(index) {
var $modal = $(this);
modalZIndex++;
$modal.css('zIndex', modalZIndex);
$modal.next('.modal-backdrop.in').addClass('hidden').css('zIndex', modalZIndex - 1);
});
$('.modal.in:visible:last').focus().next('.modal-backdrop.in').removeClass('hidden');
}
};
/**
* To allow other frameworks to overlay focusable nodes over an active modal we have
* to explicitly allow ith within this overwritten function.
*
*/
var _setModalEnforceFocus = function () {
$.fn.modal.Constructor.prototype.enforceFocus = function () {
var _setModalEnforceFocus = function() {
$.fn.modal.Constructor.prototype.enforceFocus = function() {
var that = this;
$(document).on('focusin.modal', function (e) {
$(document).on('focusin.modal', function(e) {
var $target = $(e.target);
if ($target.hasClass('select2-input') || $target.hasClass('select2-search__field') || $target.hasClass('hexInput')) {
if($target.hasClass('select2-input') || $target.hasClass('select2-search__field') || $target.hasClass('hexInput')) {
return true;
}
var $parent = $(e.target.parentNode);
if ($parent.hasClass('cke_dialog_ui_input_select') || $parent.hasClass('cke_dialog_ui_input_text')) {
if($parent.hasClass('cke_dialog_ui_input_select') || $parent.hasClass('cke_dialog_ui_input_text')) {
return true;
}
// Allow stacking of modals
if ($target.closest('.modal.in').length) {
if($target.closest('.modal.in').length) {
return true;
}
if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
if(that.$element[0] !== e.target && !that.$element.has(e.target).length) {
that.$element.focus();
}
});
};
};
var _setGlobalModalTargetHandler = function () {
var _setGlobalModalTargetHandler = function() {
// unbind all previously-attached events
$("a[data-target='#globalModal']").off('.humhub:globalModal');
// deprecated use action handler instead @see get action
$(document).off('click.humhub:globalModal').on('click.humhub:globalModal', "a[data-target='#globalModal']", function (evt) {
$(document).off('click.humhub:globalModal').on('click.humhub:globalModal', "a[data-target='#globalModal']", function(evt) {
evt.preventDefault();
var options = {
@ -535,52 +555,60 @@ humhub.module('ui.modal', function (module, require, $) {
var target = $(this).attr("href");
client.html(target).then(function (response) {
client.html(target).then(function(response) {
module.global.setDialog(response.html);
if (!module.global.$.is(':visible')) {
if(!module.global.$.is(':visible')) {
module.global.show();
}
}).catch(function (error) {
}).catch(function(error) {
module.log.error(error, true);
});
});
};
var submit = function (evt) {
var submit = function(evt) {
evt.$form = evt.$form || evt.$trigger.closest('form');
if(!evt.$form.length) {
evt.$form = evt.$target;
}
client.submit(evt, {'dataType': 'html'}).then(function (response) {
client.submit(evt, {'dataType': 'html'}).then(function(response) {
module.global.setDialog(response.html);
if (!module.global.$.is(':visible')) {
if(!module.global.$.is(':visible')) {
module.global.show();
}
}).catch(function (error) {
}).catch(function(error) {
module.log.error(error, true);
});
};
var load = function (evt) {
module.global.load(evt).catch(function (err) {
var load = function(evt) {
module.global.load(evt).catch(function(err) {
module.log.error(err, true);
});
};
var get = function (id, options) {
var get = function(id, options) {
var modal = !(modals[id]) ? new Modal(id) : modals[id];
if (options) {
if(options) {
modal.set(options);
}
return modal;
};
var post = function(evt) {
module.global.post(evt).catch(function(err) {
module.log.error(err, true);
});
};
module.export({
init: init,
Modal: Modal,
ConfirmModal: ConfirmModal,
get: get,
post: post,
load: load,
submit: submit
});

View File

@ -218,7 +218,7 @@ humhub.module('ui.picker', function(module, require, $) {
selectionWithImage: '{imageNode}<span class="with-image">{text}</span> <i class="fa fa-times-circle picker-close"></i>',
selectionNoImage: '<span class="no-image">{text}</span> <i class="fa fa-times-circle picker-close"></i>',
result: '<a href="#" tabindex="-1" style="margin-right:5px;">{imageNode} {text}</a>',
resultDisabled: '<a href="#" title="{title}" data-placement="right" tabindex="-1" style="margin-right:5px;opacity: 0.4;cursor:not-allowed">{imageNode} {text}</a>',
resultDisabled: '<a href="#" title="{disabledText}" data-placement="right" tabindex="-1" style="margin-right:5px;opacity: 0.4;cursor:not-allowed">{imageNode} {text}</a>',
imageNode: '<img class="img-rounded" src="{image}" alt="24x24" style="width:24px;height:24px;" height="24" width="24">',
option: '<option value="{id}" data-image="{image}" selected>{text}</option>',
};
@ -236,7 +236,7 @@ humhub.module('ui.picker', function(module, require, $) {
}
item.imageNode = this.getImageNode(item);
item.disabledText = item.disabledText || '';
var template = (item.disabled) ? Picker.template.resultDisabled : Picker.template.result;
var $result = $(string.template(template, item))

View File

@ -45,7 +45,7 @@ humhub.module('ui.progress', function(module, require, $) {
Progress.prototype.value = function() {
var width = this.$progressBar[0].style.width;
return (width) ? parseInt(string.cutsuffix(width, '%')) : 0;
return (width) ? parseInt(string.cutSuffix(width, '%')) : 0;
};
Progress.prototype.reset = function() {

View File

@ -0,0 +1,445 @@
/**
* Manages the client/server communication. Handles humhub json api responses and
* pjax requests.
*/
humhub.module('ui.richtext', function(module, require, $) {
var Widget = require('ui.widget').Widget;
var util = require('util');
var object = util.object;
var string = util.string;
var Richtext = function(node, options) {
Widget.call(this, node, options);
};
object.inherits(Richtext, Widget);
Richtext.component = 'humhub-ui-richtext';
Richtext.prototype.init = function() {
this.$input = $('#' + this.$.attr('id') + '_input').hide();
this.features = [];
this.checkPlaceholder();
this.initEvents();
if(this.options.disabled) {
this.disable();
}
};
Richtext.prototype.initEvents = function() {
var that = this;
this.$.on('focus', function() {
that.checkPlaceholder(true);
// Initialize features on first focus.
if(!that.featuresInitialized) {
that.initFeatures();
}
}).on('focusout', function() {
that.update();
that.checkPlaceholder();
}).on('paste', function(event) {
event.preventDefault();
event.stopImmediatePropagation();
var text = "";
if(event.originalEvent.clipboardData) { //Forefox, Webkit
text = event.originalEvent.clipboardData.getData('text/plain');
} else if(window.clipboardData) { // IE
text = window.clipboardData.getData("Text");
}
that.insertTextAtCursor(text);
}).on('keydown', function(e) {
that.checkForEmptySpans();
}).on('keypress', function(e) {
switch(e.which) {
case 8: // Backspace
// Note chrome won't fire the backspace keypress event, but we don't need the workaround for chrome so its ok..
that.checkLinkRemoval();
break;
}
}).on('clear', function(e) {
that.clear();
}).on('disable', function(e) {
that.disable();
});
};
/**
* This is a workaround for deleting links as a whole in firefox https://bugzilla.mozilla.org/show_bug.cgi?id=685445
*/
Richtext.prototype.checkLinkRemoval = function() {
var position = this.$.caret('offset');
if(!position) {
return;
}
var that = this;
// Check if the caret position is right before a link, if yes remove link and perhaps also the parent if empty.
this.$.find('.richtext-link, .atwho-emoji').each(function() {
var $this = $(this);
var offset = $this.offset();
var right = offset.left + $this.outerWidth(true);
// The caret top position seems a bit out in some cases...
if(Math.abs(position.left - right) < 1 && Math.abs(position.top - offset.top) < 18) {
$this.remove();
// This is a workaround for a caret position issue in firefox
_checkCaretPositionAfterRemove(that.$);
return false; // leave loop
}
});
};
/**
* This will prevent an caret glitch in firefox https://bugzilla.mozilla.org/show_bug.cgi?id=904846
* @param {type} $node
* @returns {undefined}
*/
var _checkCaretPositionAfterRemove = function($node) {
if(!$node.text().length) {
var spaceText = document.createTextNode("\u00a0");
$node.prepend(spaceText);
var sel = window.getSelection();
var range = document.createRange();
range.setStart(spaceText, 0);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
};
/**
* Empty spans prevent text deletions in some browsers, so we have to get sure there are no empty spans present.
* @param {type} $node
* @returns {undefined}
*/
Richtext.prototype.checkForEmptySpans = function($node) {
$node = $node || this.$;
$node.find('span').each(function() {
_checkEmptySpan($(this));
});
};
var _checkEmptySpan = function($node) {
if($node.is('span') && !$node.contents().length) {
var $parent = $node.parent();
$node.remove();
_checkEmptySpan($parent);
}
};
/**
* Inserts the given text at the current cursor position.
* This function gets sure not to insert unwanted html by escaping html chars.
*
* @param {type} text
* @returns {undefined}
*/
Richtext.prototype.insertTextAtCursor = function(text) {
// Get rid of unwanted html
var text = $('<div></div>').append(string.escapeHtml(text)).text();
var lastNode;
var sel = window.getSelection();
// Clear current selection, we'll overwrite selected text
var range = sel.getRangeAt(0);
range.deleteContents();
// Remove leading line-breaks and spaces
text = text.replace(/^(?:\r\n|\r|\n)/g, '').trim();
// We insert the lines reversed since we don't have to align the range
var lines = text.split(/(?:\r\n|\r|\n)/g).reverse();
$.each(lines, function(i, line) {
// Prevent break after last line
if(i !== 0) {
var br = document.createElement("br");
range.insertNode(br);
}
// Insert actual text node
var newNode = document.createTextNode(line.trim());
range.insertNode(newNode);
//Insert leading spaces as textnodes
var leadingSpaces = line.match(/^\s+/);
if(leadingSpaces) {
var spaceCount = leadingSpaces[0].length;
while(spaceCount > 0) {
var spaceNode = document.createTextNode("\u00a0");
range.insertNode(spaceNode);
spaceCount--;
}
}
// The last node of the loop is the first node in dom since we insert reversed
if(i === 0) {
lastNode = newNode;
}
});
// Align range after insertion
range.setStartAfter(lastNode);
range.setEndAfter(lastNode);
sel.removeAllRanges();
sel.addRange(range);
};
Richtext.prototype.update = function() {
this.$input.val(this.getPlainText());
};
Richtext.prototype.checkPlaceholder = function(focus) {
if(!focus && !this.$.text().trim().length) {
this.$.addClass('atwho-placeholder');
this.$.html(this.options.placeholder);
this.$.attr('spellcheck', 'false');
} else if(this.$.hasClass('atwho-placeholder')) {
this.$.removeClass('atwho-placeholder');
this.$.attr('spellcheck', 'true');
this.$.html('');
}
};
Richtext.prototype.initFeatures = function() {
var that = this;
$.each(Richtext.features, function(id, feature) {
if(!that.isFeatureEnabled(id)) {
return;
}
if(feature.atwho) {
that.initAtwhoFeature(id, feature);
}
});
// It seems atwho detatches the original element so we have to do a requery
this.$ = $('#' + this.$.attr('id'));
this.featuresInitialized = true;
};
Richtext.prototype.isFeatureEnabled = function(id) {
if(this.options.excludes && this.options.excludes.indexOf(id) >= 0) {
return false;
}
if(this.options.includes && this.options.includes.length && this.options.includes.indexOf(id) < 0) {
return false;
}
return true;
};
Richtext.prototype.initAtwhoFeature = function(id, feature) {
var options = (object.isFunction(feature.atwho))
? feature.atwho.call(this, feature)
: $.extend({}, feature.atwho);
if(object.isFunction(feature.init)) {
feature.init.call(this, feature, options);
}
if(feature.atwho) {
this.$.atwho(options);
}
this.features.push(id);
};
Richtext.prototype.disable = function(tooltip) {
tooltip = tooltip || this.options.disabledText;
this.$.removeAttr('contenteditable').attr({
disabled: 'disabled',
title : tooltip,
}).tooltip({
placement : 'bottom'
});
};
Richtext.prototype.clear = function() {
this.$.html('');
this.checkPlaceholder();
};
Richtext.prototype.focus = function() {
this.$.trigger('focus');
};
Richtext.prototype.getFeatures = function() {
var result = [];
$.each(this.features, function(i, id) {
result.push(Richtext.features[id]);
});
return result;
};
Richtext.prototype.getPlainText = function() {
// GENERATE USER GUIDS
var that = this;
var $clone = this.$.clone();
$.each(this.getFeatures(), function(id, feature) {
if(object.isFunction(feature.parse)) {
feature.parse($clone, that, feature);
}
});
var html = $clone.html();
// replace html space
html = html.replace(/\&nbsp;/g, ' ');
// rebuild tag structure for webkit browsers
html = html.replace(/\<div>\s*<br\s*\\*>\<\/div>/g, '<div></div>');
// replace all div tags with br tags (webkit)
html = html.replace(/\<div>/g, '<br>');
// replace all p tags with br tags (IE)
html = html.replace(/\<p>\<br\s*\\*>\<\/p>/g, '<br>');
html = html.replace(/\<\/p>/g, '<br>');
// remove all line breaks
html = html.replace(/(?:\r\n|\r|\n)/g, "");
// At.js adds a zwj at the end of each mentioning
html = html.replace(/\u200d/g,'');
// replace all <br> with new line break
$clone.html(html.replace(/\<br\s*\>/g, '\n'));
// return plain text without html tags
return $clone.text().trim();
};
Richtext.features = {};
Richtext.features.emoji = {
'emojis': [
"Relaxed", "Yum", "Relieved", "Hearteyes", "Cool", "Smirk",
"KissingClosedEyes", "StuckOutTongue", "StuckOutTongueWinkingEye", "StuckOutTongueClosedEyes", "Disappointed", "Frown",
"ColdSweat", "TiredFace", "Grin", "Sob", "Gasp", "Gasp2",
"Laughing", "Joy", "Sweet", "Satisfied", "Innocent", "Wink",
"Ambivalent", "Expressionless", "Sad", "Slant", "Worried", "Kissing",
"KissingHeart", "Angry", "Naughty", "Furious", "Cry", "OpenMouth",
"Fearful", "Confused", "Weary", "Scream", "Astonished", "Flushed",
"Sleeping", "NoMouth", "Mask", "Worried", "Smile", "Muscle",
"Facepunch", "ThumbsUp", "ThumbsDown", "Beers", "Cocktail", "Burger",
"PoultryLeg", "Party", "Cake", "Sun", "Fire", "Heart"
],
'atwho': {
at: ":",
highlightFirst: true,
limit: 100
},
init: function(feature, options) {
options.data = feature.emojis;
options.insertTpl = "<img data-emoji-name=';${name};' class='atwho-emoji' with='18' height='18' src='" + module.config['emoji.url'] + "${name}.svg' />";
options.displayTpl = "<li class='atwho-emoji-entry' data-value=';${name};'><img with='18' height='18' src='" + module.config['emoji.url'] + "${name}.svg' /></li>";
},
parse: function($clone) {
$clone.find('.atwho-emoji').each(function() {
$(this).replaceWith($(this).data('emoji-name'));
});
}
};
/**
* Mentioning feature supports mentionings by typing @ the default mentioning calls an url after typing three digits.
* Other mentionings can be registered by adding Richtext.features with the at option @:<prefix>
*/
Richtext.features.mentioning = {};
Richtext.features.mentioning.atwho = function() {
// this is the widget instance.
var that = this;
return {
at: "@",
data: [{image: '', 'cssClass': 'hint', name: module.text('info.minInput')}],
insertTpl: "<a href='${link}' class='atwho-user richtext-mention richtext-link' contenteditable='false' target='_blank' data-guid='${atwho-at}-${type}${guid}'>${name}</a>",
displayTpl: "<li class='hint' data-value=''>${name}</li>",
limit: 10,
highlightFirst: false,
callbacks: {
matcher: function(flag, subtext, should_start_with_space) {
var match, regexp;
regexp = new RegExp(/(\s+|^)@([\u00C0-\u1FFF\u2C00-\uD7FF\w\s\-\']*$)/);
match = regexp.exec(subtext);
this.setting.tpl = "<li class='hint' data-value=''>${name}</li>";
if(match && typeof match[2] !== 'undefined') {
return match[2];
}
return null;
},
remoteFilter: function(query, callback) {
this.setting.highlight_first = false;
// check the char length and data-query attribute for changing plugin settings for showing results
if(query.length >= 3) {
// Render loading user feedback.
this.setting.displayTpl = "<li class='hint' data-value=''>${name}</li>";
this.view.render([{"type": "test", "cssClass": "hint", "name": module.text('info.loading'), "image": "", "link": ""}]);
// set plugin settings for showing results
this.setting.highlightFirst = true;
this.setting.displayTpl = '<li class="${cssClass}" data-value="@${name}">${image} ${name}</li>';
$.getJSON(that.options.mentioningUrl, {keyword: query}, function(data) {
callback(data);
});
// reset query count
query.length = 0;
}
}
}
};
};
Richtext.features.mentioning.init = function(feature, options) {
var widget = this;
// This is a workaround for mobile browsers especially for Android Chrome which is not able to remove contenteditable="false" nodes.
// This will enable mobile browsers to delete images and links by taphold/contextmenu event.
if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Kindle|Silk|Opera Mini/i.test(navigator.userAgent)) {
this.$.on('contextmenu', 'a, img', function() {
var $this = $(this);
if($this.parent().is('span')) {
$this.parent().remove();
} else {
$this.remove();
}
widget.checkForEmptySpans();
return false;
});
}
};
/**
* Used to parse the feature elements.
*
* @param {type} $clone
* @param {type} widget
* @returns {undefined}
*/
Richtext.features.mentioning.parse = function($clone, widget) {
$clone.find('.atwho-user, .atwho-space').each(function() {
$(this).text($(this).data('guid'));
});
};
module.export({
Richtext: Richtext
});
});

View File

@ -10,6 +10,7 @@ humhub.module('ui.widget', function(module, require, $) {
var additions = require('ui.additions');
var Component = require('action').Component;
var object = require('util').object;
var string = require('util').string;
var action = require('action');
var event = require('event');
@ -21,26 +22,61 @@ humhub.module('ui.widget', function(module, require, $) {
this.errors = [];
this.options = this.initOptions(options || {});
// Use internal events
event.sub(this);
this.initWidgetEvents();
if(!this.validate()) {
module.log.warn('Could not initialize widget.', this.errors);
} else {
var initData = this.$.data('ui-init');
this.fire('before-init', [this, initData]);
this.fire('beforeInit', [this, initData]);
this.init(initData);
this.fire('after-init', [this]);
this.fire('afterInit', [this]);
}
};
object.inherits(Widget, Component);
Widget.prototype.fire = function(event, args) {
if(this.$.data('action-' + event)) {
action.trigger(this.$, event, {params: args});
Widget.prototype.initWidgetEvents = function() {
// Use internal event object for handling widget events.
event.sub(this);
var that = this;
// Bind dom events to widget events actions.
$.each(this.options, function(key, value) {
if(string.startsWith(key, 'widgetAction')) {
var eventType = string.cutPrefix(key, 'widgetAction').toLowerCase();
that.$.on(eventType+'.humhub:widget:events', function() {
that.fire(eventType);
});
}
});
};
Widget.prototype.fire = function(event, args, triggerDom) {
// Trigger action if there is an action handler set
var widgetAction = 'widgetAction' + string.capitalize(event);
if(this.options[widgetAction]){
var handler = this.options[widgetAction];
if(string.startsWith(handler, 'this.')) {
handler = string.cutPrefix(handler, 'this.');
var handlerFunc = object.resolve(this, handler);
if(object.isFunction(handlerFunc)) {
handlerFunc.apply(this);
}
} else {
action.trigger(this.$, event, {handler: handler, params: args});
}
}
// Trigger internal widget event
this.trigger(event, args);
// If required, trigger dom event
if(triggerDom) {
this.$.trigger(event, args);
}
};
/**
@ -71,6 +107,10 @@ humhub.module('ui.widget', function(module, require, $) {
return true;
};
Widget.prototype.isVisible = function() {
return this.$.is(':visible');
};
/**
* Can be overwritten to initialize the widget.
* @returns {undefined}

View File

@ -59,7 +59,7 @@ humhub.module('util', function(module, require, $) {
var handlers = [];
Array.prototype.push.apply(handlers, arguments);
handlers.shift();
return function() {
var _arguments = arguments;
handlers.forEach(function(handler) {
@ -107,17 +107,33 @@ humhub.module('util', function(module, require, $) {
}
};
var entityMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;',
'`': '&#x60;',
'=': '&#x3D;'
};
var string = {
escapeHtml: function(string) {
return String(string).replace(/[&<>"'`=\/]/g, function(s) {
return entityMap[s];
});
},
capitalize: function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
},
cutprefix: function(val, prefix) {
cutPrefix: function(val, prefix) {
if(!this.startsWith(val, prefix)) {
return val;
}
return val.substring(prefix.length, val.length);
},
cutsuffix: function(val, suffix) {
cutSuffix: function(val, suffix) {
return val.slice(0, suffix.length * -1);
},
startsWith: function(val, prefix) {

View File

@ -1,5 +1,6 @@
/**
* Handles conflicting jquery ui and bootstrap namespaces.
*/
$.widget.bridge('uibutton', $.ui.button);
$.widget.bridge('uitooltip', $.ui.tooltip);

View File

@ -1,5 +1,12 @@
<?php
return [
'bootstrap' => ['debug'],
'modules' => [
'debug' => [
'class' => 'yii\debug\Module',
//'allowedIPs' => ['*'],
],
],
];

View File

@ -87,6 +87,7 @@ class AppAsset extends AssetBundle
'humhub\modules\content\assets\ContentAsset',
'humhub\modules\user\assets\UserAsset',
'humhub\modules\user\assets\UserPickerAsset',
'humhub\modules\post\assets\PostAsset',
'humhub\modules\comment\assets\CommentAsset',
'humhub\assets\NProgressAsset',
'humhub\assets\IE9FixesAsset',

View File

@ -59,6 +59,7 @@ class CoreApiAsset extends AssetBundle
'js/humhub/humhub.ui.navigation.js',
'js/humhub/humhub.ui.gallery.js',
'js/humhub/humhub.ui.picker.js',
'js/humhub/humhub.ui.richtext.js',
// Note this should stay at last for other click event listeners beeing able to prevent pjax handling (e.g gallery)
'js/humhub/humhub.client.pjax.js',
];

View File

@ -9,6 +9,7 @@
namespace humhub\assets;
use yii\web\AssetBundle;
use yii\web\View;
/**
* select2
@ -17,6 +18,8 @@ use yii\web\AssetBundle;
*/
class PjaxAsset extends AssetBundle
{
public $jsOptions = ['position' => View::POS_HEAD];
/**
* @inheritdoc
*/
@ -33,7 +36,7 @@ class PjaxAsset extends AssetBundle
public $js = ['js/jquery.pjax.modified.js'];
public $depends = [
'yii\web\YiiAsset',
//'yii\web\YiiAsset',
];
}

View File

@ -11,13 +11,21 @@ HumHub Change Log
- Enh: Refactored File module (luke-)
- Enh: Added yii2-imagine Extension (luke-)
- Enh: Use of blueimp image gallery (buddha)
- Enh: JS module system (buddha)
- Enh: JS core api (buddha)
- Enh: JS module system with build in logging/text/config features (buddha)
- Enh: JS core api under humhub namespace (buddha)
- Enh: Use of compressed assets (js,css) in production mode (buddha)
- Enh: Enhanced testability (buddha)
- Enh: Added backend group permissions (buddha)
- Enh: Enhanced AccessControll filter with permission rules. (buddha)
- Enh: Added administrative backend group permissions (buddha)
- Enh: Enhanced AccessControl filter with permission rules. (buddha)
- Enh: Splitted less files to facilitate theming. (buddha)
- Enh: Added user status bar for user feedback (buddha)
- Enh: Better UserFeedack (buttons/messages) / Replacement of old DataSaved widget (buddha)
- Enh: Overwrite default permission settings (buddha)
- Enh: SpaceChooser rewrite with following spaces and remote search (buddha)
- Enh: Modal widget rewrite.
- Enh: Enhanced Archived Space handling (buddha)
- Enh: Upload widget rewrite. (buddha)
- Enh: Picker widgets rewrite (UserPicker/SpacePicker/MultiselectDropdown). (buddha)
- Enh: Richtext widget rewrite. (buddha)
- Enh: Removed almost all inline JS blocks. (buddha)
- Enh: StreamAction now uses flexible StreamQuery Model.

View File

@ -47,6 +47,12 @@ Please see the following file for 1.2 changes (at the buttom):
- modules/space/views/membership/invite.php
- modules/comment/widget/views/showComment.php
- modules/files/widget/views/showFiles.php
- modules/post/widget/views/post.php
- Richtext rewrite:
- modules/comment/widget/views/form.php
- modules/comment/views/comment/edit.php
- modules/post/views/post/edit.php
- modules/post/widget/views/form.php
- UserPicker rewrite:
- modules/admin/views/group/edit.php
- modules/admin/views/group/members.php

View File

@ -17,13 +17,17 @@ humhub.module('comment', function(module, require, $) {
client.submit(evt, {dataType: 'html'}).then(function(response) {
that.addComment(response.html);
that.getInput().val('').trigger('autosize.resize');
that.getRichtext().addClass('atwho-placeholder').focus();
that.getRichtext().$.addClass('atwho-placeholder').focus();
that.getUpload().reset();
}).catch(function(err) {
module.log.error(err, true);
});
};
Form.prototype.getRichtext = function(html) {
return Widget.instance(this.$.find('div.humhub-ui-richtext'));
};
Form.prototype.addComment = function(html) {
var $html = $(html).hide();
this.getCommentsContainer().append($html);
@ -41,10 +45,6 @@ humhub.module('comment', function(module, require, $) {
Form.prototype.getInput = function() {
return this.$.find('textarea');
};
Form.prototype.getRichtext = function() {
return this.$.find('[contenteditable]');
};
var Comment = function(node) {
Content.call(this, node);
@ -58,13 +58,17 @@ humhub.module('comment', function(module, require, $) {
var that = this;
client.post(evt, {dataType: 'html'}).then(function(response) {
that.$.find('.comment_edit_content').replaceWith(response.html);
that.$.find('[contenteditable]').focus();
that.getRichtext().focus();
that.$.find('.comment-cancel-edit-link').show();
that.$.find('.comment-edit-link').hide();
}).finally(function() {
that.loader(false);
});
};
Comment.prototype.getRichtext = function() {
return Widget.instance(this.$.find('div.humhub-ui-richtext'));
};
Comment.prototype.delete = function() {
this.super('delete', {modal: module.config.modal.delteConfirm}).then(function($confirm) {

View File

@ -10,17 +10,13 @@ use yii\helpers\Url;
<?php $form = CActiveForm::begin(); ?>
<?= Html::hiddenInput('contentModel', $contentModel); ?>
<?= Html::hiddenInput('contentId', $contentId); ?>
<?= $form->textArea($comment, 'message', array('class' => 'form-control', 'id' => 'comment_input_' . $comment->id, 'placeholder' => Yii::t('CommentModule.views_edit', 'Edit your comment...'))); ?>
<!-- create contenteditable div for HEditorWidget to place the data -->
<div id="comment_input_<?= $comment->id; ?>_contenteditable" class="form-control atwho-input"
contenteditable="true"><?= \humhub\widgets\RichText::widget(['text' => $comment->message, 'edit' => true]); ?></div>
<?= \humhub\widgets\RichTextEditor::widget(array(
'id' => 'comment_input_' . $comment->id,
'inputContent' => $comment->message,
'record' => $comment,
));?>
<?= humhub\widgets\RichtextField::widget([
'id' => 'comment_input_'.$comment->id,
'placeholder' => Yii::t('CommentModule.views_edit', 'Edit your comment...'),
'model' => $comment,
'attribute' => 'message'
]); ?>
<div class="comment-buttons">

View File

@ -11,10 +11,12 @@ use yii\helpers\Url;
<?php echo Html::hiddenInput('contentModel', $modelName); ?>
<?php echo Html::hiddenInput('contentId', $modelId); ?>
<?php echo Html::textarea("message", "", array('id' => 'newCommentForm_' . $id, 'rows' => '1', 'class' => 'form-control autosize commentForm', 'placeholder' => Yii::t('CommentModule.widgets_views_form', 'Write a new comment...'))); ?>
<?php echo humhub\widgets\RichTextEditor::widget(['id' => 'newCommentForm_' . $id]); ?>
<?= humhub\widgets\RichtextField::widget([
'id' => 'newCommentForm_' . $id,
'placeholder' => Yii::t('CommentModule.widgets_views_form', 'Write a new comment...'),
'name' => 'message'
]); ?>
<div class="comment-buttons">
<?= humhub\modules\file\widgets\UploadButton::widget([
'id' => 'comment_create_upload_' . $id,

View File

@ -8,10 +8,7 @@ humhub.module('content.form', function(module, require, $) {
var object = require('util').object;
var client = require('client');
var config = require('config').module(module);
var event = require('event');
var Widget = require('ui.widget').Widget;
var instance;
@ -28,15 +25,20 @@ humhub.module('content.form', function(module, require, $) {
// 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');
if(!module.config['disabled']) {
// Remove info text from the textinput
$('#contentFormBody').on('click.humhub:content:form', function() {
// Hide options by default
$('.contentForm_options').fadeIn();
});
} else {
$('#contentFormBody').find('.humhub-ui-richtext').trigger('disable');
}
};
CreateForm.prototype.actions = function() {
@ -80,15 +82,14 @@ humhub.module('content.form', function(module, require, $) {
this.resetFilePreview();
$('#public').attr('checked', false);
$('#contentForm_message_contenteditable').addClass('atwho-placeholder').attr('spellcheck', 'false');
$('#contentFormBody').find('.atwho-input').trigger('clear');
$('#contentFormBody').find('.humhub-ui-richtext').trigger('clear');
};
CreateForm.prototype.resetNotifyUser = function() {
$('#notifyUserContainer').hide();
Widget.instance('#notifyUserInput').reset();
};
CreateForm.prototype.resetFilePreview = function() {
Widget.instance($('#contentFormFiles_preview')).reset();
};
@ -118,22 +119,22 @@ humhub.module('content.form', function(module, require, $) {
};
CreateForm.prototype.setDefaultVisibility = function() {
if(config['defaultVisibility']) {
if(module.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']);
$('#contentForm_visibility_entry').html('<i class="fa fa-lock"></i>' + module.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']);
$('#contentForm_visibility_entry').html('<i class="fa fa-unlock"></i>' + module.text(['makePublic']));
$('.label-public').addClass('hidden');
};
@ -143,12 +144,21 @@ humhub.module('content.form', function(module, require, $) {
};
var init = function() {
instance = Widget.instance($(CREATE_FORM_ROOT_SELECTOR));
var $root = $(CREATE_FORM_ROOT_SELECTOR);
if($root.length) {
instance = Widget.instance($root);
}
};
var unload = function() {
instance = undefined;
}
module.export({
CreateForm: CreateForm,
instance: instance,
init: init
init: init,
initOnPjaxLoad: true,
unload: unload
});
});

View File

@ -213,5 +213,15 @@ abstract class ContentContainerActiveRecord extends ActiveRecord
{
return [];
}
/**
* Returns weather or not the contentcontainer is archived. (Default false).
* @return boolean
* @since 1.2
*/
public function isArchived()
{
return false;
}
}

View File

@ -336,7 +336,7 @@ class Content extends \humhub\components\ActiveRecord
*/
public function isArchived()
{
return ($this->archived);
return $this->archived || $this->getContainer()->isArchived();
}
/**

View File

@ -19,7 +19,8 @@ class ContentFixture extends ActiveFixture
public $depends = [
'humhub\modules\content\tests\codeception\fixtures\ContentContainerFixture',
'humhub\modules\post\tests\codeception\fixtures\PostFixture',
'humhub\modules\comment\tests\codeception\fixtures\CommentFixture'
'humhub\modules\comment\tests\codeception\fixtures\CommentFixture',
'humhub\modules\like\tests\codeception\fixtures\LikeFixture'
];
}

View File

@ -32,7 +32,7 @@ class ArchiveLink extends \yii\base\Widget
*/
public function run()
{
if (!Yii::$app->controller instanceof ContentContainerController || !$this->content->content->canArchive()) {
if (!Yii::$app->controller instanceof ContentContainerController || !$this->content->content->canArchive() || $this->content->content->getContainer()->isArchived()) {
return;
}

View File

@ -30,6 +30,10 @@ class DeleteLink extends \yii\base\Widget
*/
public function run()
{
if($this->content->content->isArchived()) {
return;
}
if ($this->content->content->canEdit()) {
return $this->render('deleteLink', array(
'model' => $this->content->content->object_model,

View File

@ -18,14 +18,13 @@ $this->registerJsConfig('content.form', [
<div class="panel panel-default clearfix">
<div class="panel-body" id="contentFormBody" style="display:none;" data-action-component="content.form.CreateForm" >
<?= Html::beginForm($submitUrl, 'POST'); ?>
<?= $form; ?>
<div id="notifyUserContainer" class="form-group" style="margin-top: 15px;display:none;">
<?php $memberPickerUrl = ($contentContainer instanceof Space) ? $contentContainer->createUrl('/space/membership/search') : null ?>
<?= humhub\modules\user\widgets\UserPickerField::widget([
'id' => 'notifyUserInput',
'url' => $memberPickerUrl,
'url' => ($contentContainer instanceof Space) ? $contentContainer->createUrl('/space/membership/search') : null,
'formName' => 'notifyUserInput',
'maxSelection' => 10,
'disabledItems' => [Yii::$app->user->guid],
@ -35,7 +34,6 @@ $this->registerJsConfig('content.form', [
<?= Html::hiddenInput("containerGuid", $contentContainer->guid); ?>
<?= Html::hiddenInput("containerClass", get_class($contentContainer)); ?>
<ul id="contentFormError"></ul>

View File

@ -102,7 +102,7 @@ class DashboardStreamTest extends HumHubDbTestCase
$post2->save();
$w2 = $post2->content->id;
$this->assertEquals($this->getStreamActionIds(2), array($w2, $w1));
$this->assertEquals($this->getStreamActionIds(2), [$w2, $w1]);
$this->becomeUser('User3');
$ids = $this->getStreamActionIds(2);

View File

@ -0,0 +1,24 @@
actor: Tester
namespace: like
settings:
bootstrap: _bootstrap.php
suite_class: \PHPUnit_Framework_TestSuite
colors: true
shuffle: false
memory_limit: 1024M
log: true
# This value controls whether PHPUnit attempts to backup global variables
# See https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.backupGlobals
backup_globals: true
paths:
tests: codeception
log: codeception/_output
data: codeception/_data
helpers: codeception/_support
envs: config/env
config:
# the entry script URL (with host info) for functional and acceptance tests
# PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
test_entry_url: http://localhost:8080/index-test.php

View File

@ -0,0 +1,57 @@
<?php
/**
* This is the initial test bootstrap, which will load the default test bootstrap from the humhub core
*/
// Parse the environment arguments (Note: only simple --env ENV is supported no comma sepration merge...)
$argv = $_SERVER['argv'];
$env = [];
for ($i = 0; $i < count($argv); $i++) {
if ($argv[$i] === '--env') {
$env[] = explode(',', $argv[++$i]);
}
}
// If environment was set try loading special environment config else load default
if (count($env) > 0) {
\Codeception\Configuration::append(['environment' => $env]);
echo 'Run execution environment: ' . $env[0][0] . PHP_EOL;
$envCfgFile = dirname(__DIR__) . '/config/env/test.' . $env[0][0] . '.php';
if (file_exists($envCfgFile)) {
$cfg = array_merge(require_once(__DIR__ . '/../config/test.php'), require_once($envCfgFile));
}
}
// If no environment is set we have to load the default config
if (!isset($cfg)) {
$cfg = require_once(__DIR__ . '/../config/test.php');
}
// If no humhub_root is given we assume our module is in the a root to be in /protected/humhub/modules/<module>/tests/codeception directory
$cfg['humhub_root'] = isset($cfg['humhub_root']) ? $cfg['humhub_root'] : dirname(__DIR__) . '/../../../../..';
echo 'Using HumHub Root: ' . $cfg['humhub_root'] . PHP_EOL;
// Load default test bootstrap
require_once($cfg['humhub_root'] . '/protected/humhub/tests/codeception/_bootstrap.php');
// Overwrite the default test alias
Yii::setAlias('@tests', dirname(__DIR__));
Yii::setAlias('@env', '@tests/config/env');
Yii::setAlias('@root', $cfg['humhub_root']);
Yii::setAlias('@humhubTests', $cfg['humhub_root'] . '/protected/humhub/tests');
// Load all supporting test classes needed for test execution
\Codeception\Util\Autoload::addNamespace('', Yii::getAlias('@humhubTests/codeception/_support'));
\Codeception\Util\Autoload::addNamespace('tests\codeception\fixtures', Yii::getAlias('@humhubTests/codeception/fixtures'));
\Codeception\Util\Autoload::addNamespace('', Yii::getAlias('@humhubTests/codeception/_pages'));
if(isset($cfg['modules'])) {
\Codeception\Configuration::append(['humhub_modules' => $cfg['modules']]);
}
if(isset($cfg['fixtures'])) {
\Codeception\Configuration::append(['fixtures' => $cfg['fixtures']]);
}
?>

View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@ -0,0 +1,26 @@
<?php
namespace like;
/**
* Inherited Methods
* @method void wantToTest($text)
* @method void wantTo($text)
* @method void execute($callable)
* @method void expectTo($prediction)
* @method void expect($prediction)
* @method void amGoingTo($argumentation)
* @method void am($role)
* @method void lookForwardTo($achieveValue)
* @method void comment($description)
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = null)
*
* @SuppressWarnings(PHPMD)
*/
class AcceptanceTester extends \AcceptanceTester
{
use _generated\AcceptanceTesterActions;
/**
* Define custom actions here
*/
}

View File

@ -0,0 +1,26 @@
<?php
namespace like;
/**
* Inherited Methods
* @method void wantToTest($text)
* @method void wantTo($text)
* @method void execute($callable)
* @method void expectTo($prediction)
* @method void expect($prediction)
* @method void amGoingTo($argumentation)
* @method void am($role)
* @method void lookForwardTo($achieveValue)
* @method void comment($description)
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = null)
*
* @SuppressWarnings(PHPMD)
*/
class FunctionalTester extends \FunctionalTester
{
use _generated\FunctionalTesterActions;
/**
* Define custom actions here
*/
}

View File

@ -0,0 +1,26 @@
<?php
namespace like;
/**
* Inherited Methods
* @method void wantToTest($text)
* @method void wantTo($text)
* @method void execute($callable)
* @method void expectTo($prediction)
* @method void expect($prediction)
* @method void amGoingTo($argumentation)
* @method void am($role)
* @method void lookForwardTo($achieveValue)
* @method void comment($description)
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = null)
*
* @SuppressWarnings(PHPMD)
*/
class UnitTester extends \UnitTester
{
use _generated\UnitTesterActions;
/**
* Define custom actions here
*/
}

View File

@ -0,0 +1,38 @@
<?php //[STAMP] 0468b7e2480518455b63a22d3aa6f7c2
namespace like\_generated;
// This class was automatically generated by build task
// You should not change it manually as it will be overwritten on next build
// @codingStandardsIgnoreFile
use tests\codeception\_support\CodeHelper;
trait UnitTesterActions
{
/**
* @return \Codeception\Scenario
*/
abstract protected function getScenario();
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
*
* @see \tests\codeception\_support\CodeHelper::assertContainsError()
*/
public function assertContainsError($model, $message) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertContainsError', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
*
* @see \tests\codeception\_support\CodeHelper::assertNotContainsError()
*/
public function assertNotContainsError($model, $message) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotContainsError', func_get_args()));
}
}

View File

@ -0,0 +1,23 @@
# Codeception Test Suite Configuration
# suite for acceptance tests.
# perform tests in browser using the Selenium-like tools.
# powered by Mink (http://mink.behat.org).
# (tip: that's what your customer will see).
# (tip: test your ajax and javascript by one of Mink drivers).
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
class_name: AcceptanceTester
modules:
enabled:
- WebDriver
- tests\codeception\_support\WebHelper
- tests\codeception\_support\DynamicFixtureHelper
config:
WebDriver:
url: http://localhost:8080/
browser: phantomjs
window_size: 1024x768
restart: true
lang: en

View File

@ -0,0 +1,6 @@
<?php
/**
* Initialize the HumHub Application for functional testing. The default application configuration for this suite can be overwritten
* in @tests/config/functional.php
*/
require(Yii::getAlias('@humhubTests/codeception/acceptance/_bootstrap.php'));

View File

@ -0,0 +1,3 @@
<?php
return \tests\codeception\_support\HumHubTestConfiguration::getSuiteConfig('functional');

View File

@ -0,0 +1,3 @@
<?php
return \tests\codeception\_support\HumHubTestConfiguration::getSuiteConfig('unit');

View File

@ -0,0 +1,19 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\like\tests\codeception\fixtures;
use yii\test\ActiveFixture;
class LikeFixture extends ActiveFixture
{
public $modelClass = 'humhub\modules\like\models\Like';
public $dataFile = '@modules/like/tests/codeception/fixtures/data/like.php';
}

View File

@ -0,0 +1,9 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
return [];

View File

@ -0,0 +1,18 @@
# Codeception Test Suite Configuration
# suite for functional (integration) tests.
# emulate web requests and make application process them.
# (tip: better to use with frameworks).
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
class_name: FunctionalTester
modules:
enabled:
- Filesystem
- Yii2
- tests\codeception\_support\TestHelper
- tests\codeception\_support\DynamicFixtureHelper
- tests\codeception\_support\HumHubHelper
config:
Yii2:
configFile: 'codeception/config/functional.php'

View File

@ -0,0 +1,6 @@
<?php
/**
* Initialize the HumHub Application for functional testing. The default application configuration for this suite can be overwritten
* in @tests/config/functional.php
*/
require(Yii::getAlias('@humhubTests/codeception/functional/_bootstrap.php'));

View File

@ -0,0 +1,9 @@
# Codeception Test Suite Configuration
# suite for unit (internal) tests.
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
class_name: UnitTester
modules:
enabled:
- tests\codeception\_support\CodeHelper

View File

@ -0,0 +1,6 @@
<?php
/**
* Initialize the HumHub Application for functional testing. The default application configuration for this suite can be overwritten
* in @tests/config/functional.php
*/
require(Yii::getAlias('@humhubTests/codeception/unit/_bootstrap.php'));

View File

@ -0,0 +1,5 @@
<?php
/**
* This config is shared by all suites (unit/functional/acceptance) and can be overwritten by a suite config (e.g. functional.php)
*/
return [];

View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@ -0,0 +1,5 @@
<?php
/**
* Here you can overwrite the default config for the functional suite. The default config resides in @humhubTests/codeception/config/config.php
*/
return [];

View File

@ -0,0 +1,11 @@
<?php
return [
'fixtures' => [
'default',
'content' => 'humhub\modules\content\tests\codeception\fixtures\ContentFixture',
]
];

View File

@ -0,0 +1,5 @@
<?php
/**
* Here you can overwrite your functional humhub config. The default config resiedes in @humhubTests/codeception/config/config.php
*/
return [];

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\post\assets;
use yii\web\AssetBundle;
class PostAsset extends AssetBundle
{
public $sourcePath = '@post/resources';
public $css = [];
public $js = [
'js/humhub.post.js'
];
public $depends = [
'humhub\assets\CoreApiAsset'
];
}

View File

@ -0,0 +1,83 @@
humhub.module('post', function(module, require, $) {
var Widget = require('ui.widget').Widget;
var object = require('util').object;
var Post = function(node, options) {
Widget.call(this, node, options);
};
object.inherits(Post, Widget);
Post.prototype.getDefaultOptions = function() {
return {
collapse: 310,
readMore: module.text('default.readMore'),
readLess: module.text('default.readLess')
};
};
Post.prototype.init = function() {
this.initCollapse();
};
Post.prototype.initCollapse = function() {
var that = this;
var height = this.$.outerHeight();
this.$collapseButton = this.$.siblings('.showMore');
if(this.options.prevCollapse) {
return;
}
// If height expands the max height we init the collapse post logic
if(height > this.options.collapse) {
if(!this.$collapseButton.length) {
this.$collapseButton = $(Post.templates.showMore);
this.$.after(this.$collapseButton);
}
// Init collapse button
this.$collapseButton.on('click', function(evt) {
evt.preventDefault();
that.toggleCollapse();
}).show();
// Set init state
if(this.data('state') !== 'expanded') {
this.collapse();
} else {
this.expand();
}
}
};
Post.prototype.toggleCollapse = function() {
if(this.$.data('state') === 'collapsed') {
this.expand();
} else {
this.collapse();
}
};
Post.prototype.collapse = function() {
this.$.css({'display': 'block', 'max-height': this.options.collapse+'px'});
this.$collapseButton.html('<i class="fa fa-arrow-down"></i> ' + this.options.readMore);
this.$.data('state', 'collapsed');
};
Post.prototype.expand = function() {
this.$.css('max-height', '');
this.$collapseButton.html('<i class="fa fa-arrow-up"></i> ' + this.options.readLess);
this.$.data('state', 'expanded');
};
Post.templates = {
showMore : '<a href="#" style="display:block;margin: 5px 0;"></a>'
};
module.export({
Post: Post
});
});

View File

@ -1,46 +1,47 @@
<?php
use humhub\compat\CActiveForm;
use yii\helpers\Html;
?>
<div class="content content_edit" id="post_edit_<?php echo $post->id; ?>">
<?php $form = CActiveForm::begin(['id' => 'post-edit-form_' . $post->id]); ?>
<!-- create contenteditable div for HEditorWidget to place the data -->
<div id="post_input_<?php echo $post->id; ?>_contenteditable" class="form-control atwho-input" contenteditable="true">
<?php echo \humhub\widgets\RichText::widget(['text' => $post->message, 'edit' => true]); ?>
</div>
<?= Html::activeTextarea($post, 'message', ['class' => 'form-control', 'id' => 'post_input_' . $post->id, 'placeholder' => Yii::t('PostModule.views_edit', 'Edit your post...')]) ?>
<?= \humhub\widgets\RichTextEditor::widget(['id' => 'post_input_' . $post->id, 'inputContent' => $post->message, 'record' => $post]); ?>
<?= humhub\widgets\RichtextField::widget([
'id' => 'post_input_'. $post->id,
'placeholder' => Yii::t('PostModule.views_edit', 'Edit your post...'),
'model' => $post,
'attribute' => 'message'
]); ?>
<div class="comment-buttons">
<?= \humhub\modules\file\widgets\UploadButton::widget([
<?=
\humhub\modules\file\widgets\UploadButton::widget([
'id' => 'post_upload_' . $post->id,
'model' => $post,
'dropZone' => '#post_edit_'.$post->id.':parent',
'preview' => '#post_upload_preview_'.$post->id,
'progress' => '#post_upload_progress_'.$post->id
]) ?>
'dropZone' => '#post_edit_' . $post->id . ':parent',
'preview' => '#post_upload_preview_' . $post->id,
'progress' => '#post_upload_progress_' . $post->id
])
?>
<!-- editSubmit action of surrounding StreamEntry component -->
<button type="submit" class="btn btn-default btn-sm btn-comment-submit" data-ui-loader data-action-click="editSubmit" data-action-url="<?= $post->content->container->createUrl('/post/post/edit', ['id' => $post->id]) ?>">
<?= Yii::t('PostModule.views_edit', 'Save') ?>
<?= Yii::t('PostModule.views_edit', 'Save') ?>
</button>
</div>
<div id="post_upload_progress_<?= $post->id ?>" style="display:none;margin:10px 0px;"></div>
<?= \humhub\modules\file\widgets\FilePreview::widget([
'id' => 'post_upload_preview_'.$post->id,
<?=
\humhub\modules\file\widgets\FilePreview::widget([
'id' => 'post_upload_preview_' . $post->id,
'options' => ['style' => 'margin-top:10px'],
'model' => $post,
'edit' => true
]) ?>
<?php CActiveForm::end(); ?>
])
?>
<?php CActiveForm::end(); ?>
</div>

View File

@ -1,14 +0,0 @@
<?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

@ -1,14 +1,7 @@
<?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',
));
?>
<?= humhub\widgets\RichtextField::widget([
'id' => 'contentForm_message',
'placeholder' => Yii::t("PostModule.widgets_views_postForm", "What's on your mind?"),
'name' => 'message',
'disabled' => (property_exists(Yii::$app->controller, 'contentContainer') && Yii::$app->controller->contentContainer->isArchived()),
'disabledText' => Yii::t("PostModule.widgets_views_postForm", "This space is archived."),
]);?>

View File

@ -1,60 +1,3 @@
<?php
use yii\helpers\Html;
$richOutput = humhub\widgets\RichText::widget(['text' => $post->message, 'record' => $post]);
?>
<span id="post-content-<?php echo $post->id; ?>" style="overflow: hidden; margin-bottom: 5px;">
<?php print $richOutput; ?>
<span data-ui-widget="post.Post" data-state="collapsed" data-ui-init id="post-content-<?= $post->id; ?>" style="overflow: hidden; margin-bottom: 5px;">
<?= humhub\widgets\RichText::widget(['text' => $post->message, 'record' => $post]) ?>
</span>
<a class="more-link-post hidden" id="more-link-post-<?php echo $post->id; ?>" data-state="down"
style="margin: 20px 0 20px 0;" href="javascript:showMore(<?php echo $post->id; ?>);"><i
class="fa fa-arrow-down"></i> <?php echo Yii::t('PostModule.widgets_views_post', 'Read full post...'); ?>
</a>
<script type="text/javascript">
$(document).ready(function () {
// save the count of characters
var _words = '<?php echo strlen(strip_tags($richOutput)); ?>';
var _postHeight = $('#post-content-<?php echo $post->id; ?>').outerHeight();
if (_postHeight > 310) {
// show more-button
$('#more-link-post-<?php echo $post->id; ?>').removeClass('hidden');
// set limited height
$('#post-content-<?php echo $post->id; ?>').css({'display': 'block', 'max-height': '310px'});
}
});
function showMore(post_id) {
// set current state
var _state = $('#more-link-post-' + post_id).attr('data-state');
if (_state == "down") {
$('#post-content-' + post_id).css('max-height', '2000px');
// set new link content
$('#more-link-post-' + post_id).html('<i class="fa fa-arrow-up"></i> <?php echo Html::encode(Yii::t('PostModule.widgets_views_post', 'Collapse')); ?>');
// update link state
$('#more-link-post-' + post_id).attr('data-state', 'up');
} else {
// set back to limited length
$('#post-content-' + post_id).css('max-height', '310px');
// set new link content
$('#more-link-post-' + post_id).html('<i class="fa fa-arrow-down"></i> <?php echo Html::encode(Yii::t('PostModule.widgets_views_post', 'Read full post...')); ?>');
// update link state
$('#more-link-post-' + post_id).attr('data-state', 'down');
}
}
</script>

View File

@ -13,7 +13,7 @@ use yii\web\AssetBundle;
class SpaceAsset extends AssetBundle
{
public $jsOptions = ['position' => \yii\web\View::POS_END];
public $jsOptions = ['position' => \yii\web\View::POS_BEGIN];
public $sourcePath = '@space/resources';
public $css = [];
public $js = [

View File

@ -361,6 +361,17 @@ class Space extends ContentContainerActiveRecord implements \humhub\modules\sear
$this->status = self::STATUS_ENABLED;
$this->save();
}
/**
* Returns wether or not a Space is archived.
*
* @return boolean
* @since 1.2
*/
public function isArchived()
{
return $this->status === self::STATUS_ARCHIVED;
}
/**
* Creates an url in space scope.

View File

@ -64,6 +64,15 @@ class DefaultController extends Controller
$this->ownerOnly();
$space = $this->getSpace();
$space->archive();
if(Yii::$app->request->isAjax) {
Yii::$app->response->format = 'json';
return [
'success' => true,
'space' => \humhub\modules\space\controllers\BrowseController::getSpaceResult($space, true, ['isMember' => true])
];
}
return $this->redirect($space->createUrl('/space/manage'));
}
@ -75,6 +84,15 @@ class DefaultController extends Controller
$this->ownerOnly();
$space = $this->getSpace();
$space->unarchive();
if(Yii::$app->request->isAjax) {
Yii::$app->response->format = 'json';
return [
'success' => true,
'space' => \humhub\modules\space\controllers\BrowseController::getSpaceResult($space, true, ['isMember' => true])
];
}
return $this->redirect($space->createUrl('/space/manage'));
}

View File

@ -32,11 +32,16 @@ use humhub\modules\space\modules\manage\widgets\DefaultMenu;
<?php echo \humhub\widgets\DataSaved::widget(); ?>
<div class="pull-right">
<?php if ($model->status == Space::STATUS_ENABLED) { ?>
<?php echo Html::a(Yii::t('SpaceModule.views_admin_edit', 'Archive'), $model->createUrl('/space/manage/default/archive'), array('class' => 'btn btn-warning', 'data-post' => 'POST')); ?>
<?php } elseif ($model->status == Space::STATUS_ARCHIVED) { ?>
<?php echo Html::a(Yii::t('SpaceModule.views_admin_edit', 'Unarchive'), $model->createUrl('/space/manage/default/unarchive'), array('class' => 'btn btn-warning', 'data-post' => 'POST')); ?>
<?php } ?>
<?php if ($model->status == Space::STATUS_ENABLED || $model->status == Space::STATUS_ARCHIVED) : ?>
<a href="#" <?= $model->status == Space::STATUS_ARCHIVED ? 'style="display:none;"' : '' ?> class="btn btn-warning archive"
data-action-click="space.archive" data-action-url="<?= $model->createUrl('/space/manage/default/archive') ?>" data-ui-loader>
<?= Yii::t('SpaceModule.views_admin_edit', 'Archive') ?>
</a>
<a href="#" <?= $model->status == Space::STATUS_ENABLED ? 'style="display:none;"' : '' ?> class="btn btn-warning unarchive"
data-action-click="space.unarchive" data-action-url="<?= $model->createUrl('/space/manage/default/unarchive') ?>" data-ui-loader>
<?= Yii::t('SpaceModule.views_admin_edit', 'Unarchive') ?>
</a>
<?php endif; ?>
</div>
<?php ActiveForm::end(); ?>

View File

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

View File

@ -1,4 +1,4 @@
<?php //[STAMP] 0468b7e2480518455b63a22d3aa6f7c2
<?php //[STAMP] e93907d5924b39a3cd4134cc6a6ea3a3
namespace space\_generated;
// This class was automatically generated by build task

View File

@ -67,7 +67,7 @@ use yii\helpers\Url;
<div class="modal-footer">
<a href="#" class="btn btn-primary"
data-action-click="ui.modal.submit"
data-action-click="ui.modal.post"
data-ui-loader
data-action-url="<?= Url::to(['/space/create/invite', 'spaceId' => $space->id]) ?>">
<?= Yii::t('SpaceModule.views_create_create', 'Next'); ?>

View File

@ -44,7 +44,11 @@ class Chooser extends Widget
}
$query->joinWith('space');
$query->where(['space_membership.user_id' => Yii::$app->user->id, 'space_membership.status' => Membership::STATUS_MEMBER]);
$query->where([
'space_membership.user_id' => Yii::$app->user->id,
'space_membership.status' => Membership::STATUS_MEMBER,
'space.status' => \humhub\modules\space\models\Space::STATUS_ENABLED
]);
return $query;
}

View File

@ -56,6 +56,9 @@ class SpaceChooserItem extends Widget
} else if($this->isFollowing) {
$badge = '<i class="fa fa-star badge-space pull-right type tt" title="' . Yii::t('SpaceModule.widgets_spaceChooserItem', 'You are following this space') . '" aria-hidden="true"></i>';
$data = 'data-space-following';
} else if($this->space->isArchived()) {
$badge = '<i class="fa fa-history badge-space pull-right type tt" title="' . Yii::t('SpaceModule.widgets_spaceChooserItem', 'This space is archived') . '" aria-hidden="true"></i>';
$data = 'data-space-archived';
} else {
$data = 'data-space-none';
}

View File

@ -76,9 +76,9 @@ $this->registerJsConfig('space.chooser', [
<?php if ($canCreateSpace): ?>
<li>
<div class="dropdown-footer">
<?= Html::a(Yii::t('SpaceModule.widgets_views_spaceChooser', 'Create new space'),
Url::to(['/space/create/create']),
['class' => 'btn btn-info col-md-12', 'data-target' => '#globalModal']);?>
<a href="#" class="btn btn-info col-md-12" data-action-click="ui.modal.load" data-action-url="<?= Url::to(['/space/create/create']) ?>">
<?= Yii::t('SpaceModule.widgets_views_spaceChooser', 'Create new space') ?>
</a>
</div>
</li>
<?php endif; ?>

View File

@ -140,8 +140,6 @@ abstract class Stream extends Action
if (!Yii::$app->request->isConsoleRequest) {
$this->streamQuery->load(Yii::$app->request->get());
$this->setActionSettings();
if (Yii::$app->getRequest()->get('mode', $this->mode) === self::MODE_ACTIVITY) {
$this->streamQuery->includes(\humhub\modules\activity\models\Activity::className());
$this->streamQuery->query()->leftJoin('activity', 'content.object_id=activity.id AND content.object_model=:activityModel', ['activityModel' => \humhub\modules\activity\models\Activity::className()]);
@ -154,6 +152,8 @@ abstract class Stream extends Action
}
}
$this->setActionSettings();
// Build query and set activeQuery.
$this->activeQuery = $this->streamQuery->query(true);
$this->user = $this->streamQuery->user;

View File

@ -0,0 +1,355 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2016 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\stream\actions;
use Yii;
use yii\base\Action;
use humhub\modules\content\models\Content;
use humhub\modules\user\models\User;
use yii\base\ActionEvent;
use yii\base\Exception;
/**
* Stream is the basic action for content streams.
*
* @author luke
* @since 0.11
*/
abstract class Stream_old extends Action
{
/**
* @event ActionEvent Event triggered before this action is run.
* This can be used for example to customize [[activeQuery]] before it gets executed.
* @since 1.1.1
*/
const EVENT_BEFORE_RUN = 'beforeRun';
/**
* @event ActionEvent Event triggered after this action is run.
* @since 1.1.1
*/
const EVENT_AFTER_RUN = 'afterRun';
/**
* Constants used for sorting
*/
const SORT_CREATED_AT = 'c';
const SORT_UPDATED_AT = 'u';
/**
* Modes
*/
const MODE_NORMAL = "normal";
const MODE_ACTIVITY = "activity";
/**
* Maximum wall entries per request
*/
const MAX_LIMIT = 50;
/**
* @var string
*/
public $mode;
/**
* Used to load single content entries.
* @since 1.2
*/
public $contentId;
/**
* First wall entry id to deliver
*
* @var int
*/
public $from;
/**
* Sorting Mode
*
* @var int
*/
public $sort;
/**
* Maximum wall entries to return
* @var int
*/
public $limit = 4;
/**
* Filters
*
* @var array
*/
public $filters = [];
/**
* @var \yii\db\ActiveQuery
*/
public $activeQuery;
/**
* Optional stream user
* if no user is specified, the current logged in user will be used.
*
* @var User
*/
public $user;
/**
* @inheritdocs
*/
public function init()
{
$this->activeQuery = Content::find();
// If no user is set, take current if logged in
if ($this->user === null && !Yii::$app->user->isGuest) {
$this->user = Yii::$app->user->getIdentity();
}
// 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;
}
/**
* Sorting
*/
$sort = Yii::$app->getRequest()->get('sort', Yii::$app->getModule('content')->settings->get('stream.defaultSort'));
if ($sort === static::SORT_CREATED_AT || $sort === static::SORT_UPDATED_AT) {
$this->sort = $sort;
} else {
$this->sort = static::SORT_CREATED_AT;
}
$limit = Yii::$app->getRequest()->get('limit', '');
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);
}
}
$this->setupCriteria();
$this->setupFilters();
}
public function setupCriteria()
{
$this->activeQuery->joinWith('createdBy');
$this->activeQuery->joinWith('contentContainer');
$this->activeQuery->limit($this->limit);
$this->activeQuery->andWhere(['user.status' => User::STATUS_ENABLED]);
/**
* Handle Stream Mode (Normal Stream or Activity Stream)
*/
if ($this->mode == self::MODE_ACTIVITY) {
$this->activeQuery->andWhere(['content.object_model' => \humhub\modules\activity\models\Activity::className()]);
// Dont show own activities
if ($this->user !== null) {
$this->activeQuery->leftJoin('activity', 'content.object_id=activity.id AND content.object_model=:activityModel', ['activityModel' => \humhub\modules\activity\models\Activity::className()]);
$this->activeQuery->andWhere('content.created_by != :userId', array(':userId' => $this->user->id));
}
} else {
$this->activeQuery->andWhere(['!=', 'content.object_model', \humhub\modules\activity\models\Activity::className()]);
}
if($this->isSingleContentQuery()) {
$this->activeQuery->andWhere(['content.id' => $this->contentId]);
return;
}
/**
* Setup Sorting
*/
if ($this->sort == self::SORT_UPDATED_AT) {
$this->activeQuery->orderBy('content.stream_sort_date DESC');
if ($this->from != "") {
$this->activeQuery->andWhere("content.stream_sort_date < (SELECT updated_at FROM content wd WHERE wd.id=" . $this->from . ")");
}
} else {
$this->activeQuery->orderBy('content.id DESC');
if ($this->from != "")
$this->activeQuery->andWhere("content.id < " . $this->from);
}
}
/**
* Setup additional filters
*/
public function setupFilters()
{
if (in_array('entry_files', $this->filters)) {
$fileSelector = (new \yii\db\Query())
->select(["id"])
->from('file')
->where('file.object_model=content.object_model AND file.object_id=content.object_id')
->limit(1);
$fileSelectorSql = Yii::$app->db->getQueryBuilder()->build($fileSelector)[0];
$this->activeQuery->andWhere('(' . $fileSelectorSql . ') IS NOT NULL');
}
// Setup Post specific filters
if (in_array('posts_links', $this->filters)) {
$this->activeQuery->leftJoin('post', 'content.object_id=post.id AND content.object_model=:postModel', ['postModel' => \humhub\modules\post\models\Post::className()]);
$this->activeQuery->andWhere("post.url is not null");
}
// Only apply archived filter when we should load more than one entry
if ($this->limit != 1) {
if (!in_array('entry_archived', $this->filters)) {
$this->activeQuery->andWhere("(content.archived != 1 OR content.archived IS NULL)");
}
}
// Show only mine items
if (in_array('entry_mine', $this->filters) && $this->user !== null) {
$this->activeQuery->andWhere(['content.created_by' => $this->user->id]);
}
// Show only items where the current user is userinvolvedz
if (in_array('entry_userinvolved', $this->filters) && $this->user !== null) {
$this->activeQuery->leftJoin('user_follow', 'content.object_model=user_follow.object_model AND content.object_id=user_follow.object_id AND user_follow.user_id = :userId', ['userId' => $this->user->id]);
$this->activeQuery->andWhere("user_follow.id IS NOT NULL");
}
if (in_array('model_posts', $this->filters)) {
$this->activeQuery->andWhere(["content.object_model" => \humhub\modules\post\models\Post::className()]);
}
// Visibility filters
if (in_array('visibility_private', $this->filters)) {
$this->activeQuery->andWhere(['content.visibility' => Content::VISIBILITY_PRIVATE]);
}
if (in_array('visibility_public', $this->filters)) {
$this->activeQuery->andWhere(['content.visibility' => Content::VISIBILITY_PUBLIC]);
}
}
public function isSingleContentQuery() {
return $this->contentId != null;
}
/**
* @inheritdoc
*/
public function run()
{
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$output = [];
$this->init();
$output['content'] = [];
foreach ($this->activeQuery->all() as $content) {
try {
$output['content'][$content->id] = static::getContentResultEntry($content);
} catch(Exception $e) {
// We do not want to kill the stream action in prod environments in case the rendering of an entry fails.
if(YII_ENV_PROD) {
Yii::error($e);
} else {
throw $e;
}
}
}
$output['total'] = count($output['content']);
$output['isLast'] = ($output['total'] < $this->activeQuery->limit);
$output['contentOrder'] = array_keys($output['content']);
return $output;
}
/**
* Returns an array contains all informations required to display a content
* in stream.
*
* @param Content $content the content
* @return array
*/
public static function getContentResultEntry(Content $content)
{
$result = [];
// Get Underlying Object (e.g. Post, Poll, ...)
$underlyingObject = $content->getPolymorphicRelation();
if ($underlyingObject === null) {
throw new Exception('Could not get contents underlying object!');
}
$underlyingObject->populateRelation('content', $content);
$result['output'] = Yii::$app->controller->renderAjax('@humhub/modules/content/views/layouts/wallEntry', [
'entry' => $content,
'user' => $underlyingObject->content->createdBy,
'object' => $underlyingObject,
'content' => $underlyingObject->getWallOut()
], true);
$result['sticked'] = (boolean) $content->sticked;
$result['archived'] = (boolean) $content->archived;
$result['guid'] = $content->guid;
$result['id'] = $content->id;
return $result;
}
/**
* Is inital stream requests (show first stream content)
*
* @return boolean Is initial request
*/
protected function isInitialRequest()
{
return ($this->from == '' && $this->limit != 1);
}
/**
* This method is called right before `run()` is executed.
* You may override this method to do preparation work for the action run.
* If the method returns false, it will cancel the action.
*
* @return boolean whether to run the action.
*/
protected function beforeRun()
{
$event = new ActionEvent($this);
$this->trigger(self::EVENT_BEFORE_RUN, $event);
return $event->isValid;
}
/**
* This method is called right after `run()` is executed.
* You may override this method to do post-processing work for the action run.
*/
protected function afterRun()
{
$event = new ActionEvent($this);
$this->trigger(self::EVENT_AFTER_RUN, $event);
}
}

View File

@ -238,8 +238,10 @@ humhub.module('stream', function(module, require, $) {
// Sinc the response does not only include the node itself we have to search it.
that.$ = $newEntry.find(DATA_STREAM_ENTRY_SELECTOR)
.addBack(DATA_STREAM_ENTRY_SELECTOR);
$newEntry.fadeIn(resolve);
that.apply();
$newEntry.fadeIn('fast', function() {
that.apply();
resolve();
});
});
});
@ -294,6 +296,8 @@ humhub.module('stream', function(module, require, $) {
if(response.success) {
that.reload().then(function() {
module.log.success('success.unarchive', true);
}).catch(function(err) {
module.log.error('error.default', true);
});
}
}).catch(function(e) {
@ -327,7 +331,7 @@ humhub.module('stream', function(module, require, $) {
}
this.$stream = this.$;
//Cache some stream relevant data/nodes
this.url = this.$.data(DATA_STREAM_URL);
this.$content = this.$.find(this.cfg['contentSelector']);
@ -417,31 +421,11 @@ humhub.module('stream', function(module, require, $) {
this.loading = false;
this.$content.empty();
this.$.hide();
//this.$.find(".s2_single").hide();
this.hideLoader();
this.$filter.hide();
//this.$filter.hide();
this.$.trigger('humhub:modules:stream:clear', this);
};
/**
* 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.
@ -456,7 +440,8 @@ humhub.module('stream', function(module, require, $) {
if(!entry) {
module.log.warn('Attempt to reload non existing entry');
return reject();
reject();
return;
}
var contentId = entry.getKey();
@ -471,6 +456,18 @@ humhub.module('stream', function(module, require, $) {
}, reject);
});
};
/**
* 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;
return this.loadEntries(cfg);
};
/**
* Loads new entries to a stream by the given stream settings.
@ -491,7 +488,7 @@ humhub.module('stream', function(module, require, $) {
return new Promise(function(resolve, reject) {
var $result;
// Don't proceed if stream is already loading
if(that.loading || that.lastEntryLoaded) {
if(!cfg.contentId && (that.loading || that.lastEntryLoaded)) {
resolve();
return;
}
@ -583,17 +580,22 @@ humhub.module('stream', function(module, require, $) {
Stream.prototype.prependEntry = function(html) {
var $html = $(html).hide();
this.$content.prepend($html);
additions.applyTo($html);
$html.fadeIn();
this.onChange();
var that = this;
$html.fadeIn('fast', function() {
additions.applyTo($html);
that.onChange();
});
};
Stream.prototype.appendEntry = function(html) {
var $html = $(html).hide();
this.$content.append($html);
additions.applyTo($html);
$html.fadeIn();
this.onChange();
var that = this;
$html.fadeIn('fast', function() {
additions.applyTo($html);
that.onChange();
});
};
@ -614,7 +616,6 @@ humhub.module('stream', function(module, require, $) {
result += response.content[key].output;
});
var $result = $(result).hide();
if(cfg['preventInsert']) {
@ -778,7 +779,8 @@ humhub.module('stream', function(module, require, $) {
this.$.find('.emptyStreamMessage').show();
this.$filter.hide();
} else if(!hasEntries) {
this.$.find('.emptyFilterStreamMessage').hide();
this.$.find('.emptyFilterStreamMessage').show();
this.$filter.show();
} else if(!this.isShowSingleEntry()) {
this.$filter.show();
this.$.find('.emptyStreamMessage').hide();

View File

@ -28,7 +28,7 @@ class StreamQuery extends \yii\base\Model
const FILTER_FILES = "entry_files";
const FILTER_ARCHIVED = "entry_archived";
const FILTER_MINE = "entry_mine";
const FILTER_INVOLVED = "entry_userinvoled";
const FILTER_INVOLVED = "entry_userinvolved";
const FILTER_PRIVATE = "visibility_private";
const FILTER_PUBLIC = "visibility_public";
@ -50,7 +50,7 @@ class StreamQuery extends \yii\base\Model
* @var array Content type filter
*/
protected $_excludes;
/**
* Used to deactivate the default exclusion of activity entries
* @var type
@ -113,9 +113,8 @@ class StreamQuery extends \yii\base\Model
* @var \yii\db\ActiveQuery
*/
protected $_query;
private $_built = false;
public function rules()
{
return [
@ -148,7 +147,7 @@ class StreamQuery extends \yii\base\Model
$instance = new static();
return $instance->includes($includes)->excludes($excludes);
}
public function init()
{
$this->_query = Content::find();
@ -158,7 +157,7 @@ class StreamQuery extends \yii\base\Model
public function content($contentId)
{
if(!is_int($contentId)) {
if (!is_int($contentId)) {
$this->contentId = $contentId;
}
return $this;
@ -170,42 +169,42 @@ class StreamQuery extends \yii\base\Model
$this->checkUser();
return $this;
}
public function filters($filters = [])
{
$this->filters = (is_string($filters)) ? [$this->filters] : $this->filters;
return $this;
}
public function addFilter($filters)
{
if(!is_string($filters)) {
if (!is_string($filters)) {
$this->filters[] = $filters;
} else if(is_array($filters)) {
} else if (is_array($filters)) {
$this->filters = \yii\helpers\ArrayHelper::merge($this->filters, $filters);
}
return $this;
}
public function includes($includes = [])
{
if(is_string($includes)) {
if (is_string($includes)) {
$this->_includes = [$includes];
} else if(is_array ($includes)) {
} else if (is_array($includes)) {
$this->_includes = $includes;
}
return $this;
}
public function excludes($types = [])
{
if(is_string($types)) {
if (is_string($types)) {
$this->_excludes = [$types];
} else if(is_array ($types)) {
} else if (is_array($types)) {
$this->_excludes = $types;
}
return $this;
}
@ -234,18 +233,20 @@ class StreamQuery extends \yii\base\Model
}
public function query($build = false)
{
if($build) {
{
if ($build) {
$this->setupQuery();
}
return $this->_query;
}
public function all()
{
if(!$this->_built) {
if (!$this->_built) {
$this->setupQuery();
}
return $this->_query->all();
}
@ -274,7 +275,7 @@ class StreamQuery extends \yii\base\Model
$this->sort = static::SORT_CREATED_AT;
}
}
protected function checkFrom()
{
if (empty($this->from)) {
@ -335,13 +336,14 @@ class StreamQuery extends \yii\base\Model
$this->_query->andWhere(['content.created_by' => $this->originator->id]);
}
}
public function isFIlter($filter) {
public function isFIlter($filter)
{
return in_array($filter, $this->filters);
}
protected function setDefaultFilter()
{
{
if ($this->isFilter(self::FILTER_FILES)) {
$this->filterFile();
}
@ -349,14 +351,14 @@ class StreamQuery extends \yii\base\Model
// Only apply archived filter when we should load more than one entry
if (!$this->isSingleContentQuery() && !$this->isFilter(self::FILTER_ARCHIVED)) {
$this->unFilterArchived();
}
}
// Show only mine items
if ($this->isFilter(self::FILTER_MINE)) {
$this->filterMine();
}
// Show only items where the current user is involed
// Show only items where the current user is invovled
if ($this->isFilter(self::FILTER_INVOLVED)) {
$this->filterInvolved();
}

View File

@ -6,7 +6,6 @@ use stream\AcceptanceTester;
class StreamCest
{
public function testDeletePost(AcceptanceTester $I)
{
$I->amUser();
@ -14,8 +13,8 @@ class StreamCest
$I->wantToTest('the deletion of a stream entry');
$I->amGoingTo('create a new post and delete it afterwards');
$I->click('#contentForm_message_contenteditable');
$I->fillField('#contentForm_message_contenteditable', 'This is my stream test post!');
$I->click('#contentForm_message');
$I->fillField('#contentForm_message', 'This is my stream test post!');
$I->click('#post_submit_button');
$newEntrySelector = '[data-content-key="12"]';
@ -29,8 +28,8 @@ class StreamCest
$I->click('Delete');
$I->waitForElementVisible('#globalModalConfirm', 5);
$I->see('Do you really want to perform this action?');
$I->click('Confirm');
$I->see('Confirm post deletion');
$I->click('Delete', '#globalModalConfirm');
$I->seeSuccess('The content has been deleted');
}
@ -144,16 +143,29 @@ class StreamCest
$I->waitForElementVisible($newEntrySelector);
$I->see('This is my first stream test post', '.wall-entry');
$I->amGoingTo('edit load the edit form');
$I->click('.preferences', $newEntrySelector);
$I->waitForText('Edit', 10);
$I->click('Edit', $newEntrySelector);
$I->waitForElementVisible($newEntrySelector . ' .content_edit', 20);
$I->amGoingTo('cancel my edit');
$I->click('.preferences', $newEntrySelector);
$I->waitForText('Cancel Edit', 10);
$I->click('Cancel Edit', $newEntrySelector);
$I->waitForElementNotVisible($newEntrySelector . ' .content_edit', 20);
$I->waitForElementVisible($newEntrySelector . ' .content', 20);
$I->see('This is my first stream test post!', $newEntrySelector);
$I->amGoingTo('edit my new post');
$I->click('.preferences', $newEntrySelector);
$I->waitForText('Edit', 10);
$I->click('Edit', $newEntrySelector);
$I->waitForElementVisible($newEntrySelector . ' .content_edit', 20);
$I->fillField($newEntrySelector . ' [contenteditable]', 'This is my edited post!');
$I->click('Save', $newEntrySelector);
;
$I->seeSuccess('Saved');
$I->seeElement($newEntrySelector);
$I->see('This is my edited post!', $newEntrySelector);
@ -182,13 +194,52 @@ class StreamCest
$I->click('Delete');
$I->waitForElementVisible('#globalModalConfirm', 5);
$I->see('Do you really want to perform this action?');
$I->click('Confirm');
$I->see('Confirm post deletion');
$I->click('Delete', '#globalModalConfirm');
$I->seeSuccess('The content has been deleted');
$I->see('This space is still empty!');
$I->dontSeeElement('#filter');
}
public function testFilterInvolved(AcceptanceTester $I)
{
$I->amUser();
$I->amOnSpace2();
$I->waitForElementVisible('#filter');
$I->click('.stream-filter', '#filter');
$I->waitForElementVisible('#filter_entry_userinvolved');
$I->click('#filter_entry_userinvolved');
$I->waitForText('No matches with your selected filters!');
$I->createPost('Involved Post.');
$I->dontSee('No matches with your selected filters!');
$I->amGoingTo('Reset filter');
$I->click('.stream-filter', '#filter');
$I->waitForElementVisible('#filter_entry_userinvolved');
$I->click('#filter_entry_userinvolved');
$I->waitForElementVisible('[data-content-key="10"]');
$I->click('Comment', '[data-content-key="10"]');
$I->waitForElementVisible('#newCommentForm_humhubmodulespostmodelsPost_10');
$I->fillField('#newCommentForm_humhubmodulespostmodelsPost_10', 'My Comment');
$I->click('Send', '#comment_create_form_humhubmodulespostmodelsPost_10');
$I->waitForText('My Comment', 30, '#comment_humhubmodulespostmodelsPost_10 .comment');
$I->click('Like', '[data-content-key="11"]');
$I->click('.stream-filter', '#filter');
$I->waitForElementVisible('#filter_entry_userinvolved');
$I->click('#filter_entry_userinvolved');
$I->wait(1);
$I->waitForText('Involved Post.');
$I->seeElement('[data-content-key="10"]');
$I->seeElement('[data-content-key="11"]');
$I->seeElement('[data-content-key="12"]');
}
public function testSortStream(AcceptanceTester $I)
{

View File

@ -89,7 +89,7 @@ class StreamViewer extends \yii\base\Widget
// Add default Filters
if (count($this->filters) === 0) {
$this->filters['filter_entry_userinvoled'] = Yii::t('ContentModule.widgets_views_stream', 'Where I´m involved');
$this->filters['filter_entry_userinvolved'] = Yii::t('ContentModule.widgets_views_stream', 'Where I´m involved');
$this->filters['filter_entry_mine'] = Yii::t('ContentModule.widgets_views_stream', 'Created by me');
$this->filters['filter_entry_files'] = Yii::t('ContentModule.widgets_views_stream', 'Content with attached files');
$this->filters['filter_posts_links'] = Yii::t('ContentModule.widgets_views_stream', 'Posts with links');
@ -135,7 +135,7 @@ class StreamViewer extends \yii\base\Widget
*/
public function run()
{
return $this->render('stream', ['streamUrl' => $this->getStreamUrl(), 'showFilters' => $this->showFilters, 'filters' => $this->filters]);
return $this->render('stream', ['streamUrl' => $this->getStreamUrl(), 'showFilters' => $this->showFilters, 'filters' => $this->filters, '', 'contentContainer' => $this->contentContainer]);
}
}

View File

@ -1,7 +1,6 @@
<?php
use yii\helpers\Url;
use yii\web\View;
\humhub\modules\stream\assets\StreamAsset::register($this);
@ -27,6 +26,9 @@ $this->registerJsVar('defaultStreamSort', ($defaultStreamSort != '') ? $defaultS
?>
<!-- Stream filter section -->
<?php if($contentContainer && $contentContainer->isArchived()) : ?>
<span class="label label-warning pull-right" style="margin-top:10px;"><?php echo Yii::t('ContentModule.widgets_views_label', 'Archived'); ?></span>
<?php endif; ?>
<?php if ($this->context->showFilters) { ?>
<ul class="nav nav-tabs wallFilterPanel" id="filter" style="display: none;">
<li class=" dropdown">

View File

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

View File

@ -19,4 +19,5 @@ modules:
url: http://localhost:8080/
browser: phantomjs
window_size: 1024x768
restart: true
lang: en

View File

@ -2,7 +2,6 @@
namespace user\acceptance;
use user\AcceptanceTester;
use tests\codeception\_pages\DirectoryPage;
use tests\codeception\_pages\AccountSettingsPage;
class ProfileCest
@ -16,7 +15,9 @@ class ProfileCest
$I->clickAccountDropDown();
$I->click('Account settings');
$I->expectTo('see the profile edit form');
$I->seeElement('#profile-tabs');
$I->waitForElementVisible('#profile-tabs', 20);
//$I->seeElement();
$I->amGoingTo('fill only my firstname');
$I->fillField('#profile-firstname', 'MyFirstName');
@ -29,7 +30,7 @@ class ProfileCest
$I->amGoingTo('fill all required fields plus birthday and hide year field');
$I->fillField('#profile-lastname', 'MyLastName');
$I->fillField('#profile-birthday', '4/16/87');
$I->click('/html/body/div[3]/div/div[2]/div/div[3]/form/div/div[1]/div[11]/label/div'); // Hide year in profile
$I->click('.field-profile-birthday_hide_year div'); // Hide year in profile
$I->scrollToTop();
@ -61,10 +62,11 @@ class ProfileCest
$I->wantTo('ensure that my profile works as expected.');
$I->amUser2();
DirectoryPage::openBy($I);
$I->click('Members');
$directory = $I->amOnDirectory();
$directory->clickMembers();
$I->click('User1');
$I->waitForText('Profile menu');
$I->expectTo('see the profile of User2');
$I->see('Follow');
$I->see('Stream');
@ -75,15 +77,13 @@ class ProfileCest
$I->see('Peter');
$I->see('Tester');
AccountSettingsPage::openBy($I);
$I->click('Security');
#$I->wait(2);
$accountSettings = AccountSettingsPage::openBy($I);
$accountSettings->clickSecurity();
$I->selectOption('select[data-attribute0*=ViewAboutPage]', 'Deny');
$I->amUser1(true);
DirectoryPage::openBy($I);
$I->click('Members');
$directory = $I->amOnDirectory();
$directory->clickMembers();
$I->click('User2');
}
}

View File

@ -9,7 +9,9 @@ class UserFollowCest
{
$I->wantTo('test the user follow by directory link');
$I->amUser1();
//$I->wait(300);
$I->amOnProfile();
$I->createPost('New User1 profile post');

View File

@ -1,19 +0,0 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user\tests\codeception\fixtures;
use yii\test\ActiveFixture;
class ProfileFixture extends ActiveFixture
{
public $modelClass = 'humhub\modules\user\models\Profile';
public $dataFile = '@modules/user/tests/codeception/fixtures/data/profile.php';
}

View File

@ -16,8 +16,10 @@ class UserFixture extends ActiveFixture
public $modelClass = 'humhub\modules\user\models\User';
public $dataFile = '@modules/user/tests/codeception/fixtures/data/user.php';
public $depends = [
'humhub\modules\user\tests\codeception\fixtures\UserProfileFixture',
'humhub\modules\content\tests\codeception\fixtures\ContentContainerFixture',
'humhub\modules\user\tests\codeception\fixtures\UserPasswordFixture',
'humhub\modules\user\tests\codeception\fixtures\UserFollowFixture',
'humhub\modules\user\tests\codeception\fixtures\GroupFixture'
];

View File

@ -10,13 +10,10 @@ namespace humhub\modules\user\tests\codeception\fixtures;
use yii\test\ActiveFixture;
class UserFixture extends ActiveFixture
class UserProfileFixture extends ActiveFixture
{
public $modelClass = 'humhub\modules\user\models\Profile';
public $dataFile = '@modules/user/tests/codeception/fixtures/data/profile.php';
public $depends = [
'humhub\modules\user\tests\codeception\fixtures\UserProfileFixture'
];
}

View File

@ -12,5 +12,11 @@ class AccountSettingsPage extends BasePage
{
public $route = 'user/account/edit';
public function clickSecurity()
{
$this->actor->click('Security');
$this->actor->waitForText('Security settings', 30);
}
}

View File

@ -12,5 +12,11 @@ class DirectoryPage extends BasePage
{
public $route = 'directory/directory';
public function clickMembers()
{
$this->actor->click('Member');
$this->actor->waitForText('Member directory', 30);
}
}

View File

@ -16,6 +16,7 @@ class ProfilePage extends BasePage
public function clickAbout()
{
$this->actor->click('About');
$this->actor->waitForText('About this user', 30);
}
}

View File

@ -64,8 +64,8 @@ class AcceptanceTester extends \Codeception\Actor
public function createPost($text)
{
$this->click('#contentForm_message_contenteditable');
$this->fillField('#contentForm_message_contenteditable', $text);
$this->click('#contentForm_message');
$this->fillField('#contentForm_message', $text);
$this->click('#post_submit_button');
$this->waitForText($text, 30, '.wall-entry');
}
@ -158,6 +158,11 @@ class AcceptanceTester extends \Codeception\Actor
$this->jsClick('#account-dropdown-link');
$this->wait(2);
}
public function amOnDirectory()
{
return tests\codeception\_pages\DirectoryPage::openBy($this);
}
public function amOnProfile()
{

View File

@ -106,7 +106,6 @@ class DynamicFixtureHelper extends Module
{
return [
'user' => ['class' => \humhub\modules\user\tests\codeception\fixtures\UserFixture::className()],
'profile' => ['class' => \humhub\modules\user\tests\codeception\fixtures\ProfileFixture::className()],
'group' => ['class' => \humhub\modules\user\tests\codeception\fixtures\GroupFixture::className()],
'group_permission' => ['class' => \humhub\modules\user\tests\codeception\fixtures\GroupPermissionFixture::className()],
'settings' => ['class' => \humhub\tests\codeception\fixtures\SettingFixture::className()],

View File

@ -61,6 +61,13 @@ class CoreJsConfig extends Widget
'error.unknown' => Yii::t('base', 'No error information given.'),
]
],
'ui.richtext' => [
'emoji.url' => Yii::getAlias('@web/img/emoji/'),
'text' => [
'info.minInput' => Yii::t('base', 'Please type at least 3 characters'),
'info.loading' => Yii::t('base', 'Loading...'),
]
],
'log' => [
'traceLevel' => (YII_DEBUG) ? 'DEBUG' : 'INFO',
'text' => [
@ -86,6 +93,12 @@ class CoreJsConfig extends Widget
'showMore' => Yii::t('base', 'Show more'),
]
],
'post' => [
'text' => [
'default.readMore' => Yii::t('PostModule.widgets_views_post', 'Read full post...'),
'default.readLess' => Yii::t('PostModule.widgets_views_post', 'Collapse'),
]
],
'content' => [
'modal' => [
'permalink' => [

View File

@ -25,6 +25,7 @@ class GlobalConfirmModal extends \yii\base\Widget
{
return \humhub\widgets\Modal::widget([
'id' => 'globalModalConfirm',
'jsWidget' => 'ui.modal.ConfirmModal',
'size' => 'extra-small',
'centerText' => true,
'backdrop' => false,

View File

@ -61,16 +61,12 @@ class JsWidget extends Widget
{
$attributes = $this->getAttributes();
$attributes['data'] = $this->getData();
$attributes['id'] = $this->id;
$attributes['id'] = $this->getId();
$this->setDefaultOptions();
$result = \yii\helpers\ArrayHelper::merge($attributes, $this->options);
if(empty($result['id'])) {
$result['id'] = $this->getId(true);
}
if(!$this->visible) {
if(isset($result['style'])) {
$result['style'] .= ';display:none;';
@ -86,7 +82,7 @@ class JsWidget extends Widget
{
// Set event data
foreach($this->events as $event => $handler) {
$this->options['data']['action-'.$event] = $handler;
$this->options['data']['widget-action-'.$event] = $handler;
}
$this->options['data']['ui-widget'] = $this->jsWidget;
@ -96,6 +92,14 @@ class JsWidget extends Widget
}
}
public function getId($autoGenerate = true)
{
if($this->id) {
return $this->id;
}
return parent::getId($autoGenerate);
}
protected function getData()
{
return [];

View File

@ -15,13 +15,12 @@ use yii\base\Widget;
*
* @author luke
*/
class Modal extends Widget
{
/**
* Unique modal container id
* @var type
class Modal extends JsWidget
{
/*
* @inheritdoc
*/
public $id;
public $jsWidget = 'ui.modal.Modal';
/**
* Header text
@ -104,22 +103,45 @@ class Modal extends Widget
public function run()
{
$modalData = '';
$modalData .= !$this->backdrop ? 'data-backdrop="static"' : '';
$modalData .= !$this->keyboard ? 'data-keyboard="false"' : '';
$modalData .= $this->show ? 'data-show="true"' : '';
return $this->render('modal', [
'id' => $this->id,
'options' => $this->getOptions(),
'header' => $this->header,
'body' => $this->body,
'footer' => $this->footer,
'animation' => $this->animation,
'size' => $this->size,
'modalData' => $modalData,
'centerText' => $this->centerText,
'initialLoader' => $this->initialLoader
]);
}
public function getAttributes()
{
return [
'class' => "modal",
'tabindex' => "-1",
'role' => "dialog",
'aria-hidden' => "true"
];
}
public function getData()
{
$result = [];
if(!$this->backdrop) {
$result['backdrop'] = 'static';
}
if(!$this->keyboard) {
$result['keyboard'] = 'false';
}
if($this->show) {
$result['show'] = 'true';
}
return $result;
}
}

View File

@ -18,7 +18,7 @@ use humhub\libs\ParameterEvent;
*
* @author luke
*/
class RichText extends \humhub\components\Widget
class RichText extends JsWidget
{
/**
@ -166,7 +166,7 @@ REGEXP;
$user = \humhub\modules\user\models\User::findOne(['guid' => $hit[2]]);
if ($user !== null) {
if ($buildAnchors) {
return ' <span contenteditable="false"><a href="' . $user->getUrl() . '" target="_self" class="atwho-user" data-user-guid="@-u' . $user->guid . '">@' . Html::encode($user->getDisplayName()) . '&#x200b;</a></span>' . $hit[3];
return ' <span contenteditable="false"><a href="' . $user->getUrl() . '" target="_self" class="atwho-user" data-guid="@-u' . $user->guid . '">@' . Html::encode($user->getDisplayName()) . '&#x200b;</a></span>' . $hit[3];
}
return " @" . Html::encode($user->getDisplayName()) . $hit[3];
}
@ -175,7 +175,7 @@ REGEXP;
if ($space !== null) {
if ($buildAnchors) {
return ' <span contenteditable="false"><a href="' . $space->getUrl() . '" target="_self" class="atwho-user" data-user-guid="@-s' . $space->guid . '">@' . Html::encode($space->name) . '&#x200b;</a></span>' . $hit[3];
return ' <span contenteditable="false"><a href="' . $space->getUrl() . '" target="_self" class="atwho-user" data-guid="@-s' . $space->guid . '">@' . Html::encode($space->name) . '&#x200b;</a></span>' . $hit[3];
}
return " @" . Html::encode($space->name) . $hit[3];
}

View File

@ -0,0 +1,191 @@
<?php
namespace humhub\widgets;
use Yii;
use yii\helpers\Html;
use \yii\helpers\Url;
use humhub\widgets\RichText;
/**
* @package humhub.modules_core.user.widgets
* @since 1.2
* @author buddha
*/
class RichtextField extends JsWidget
{
/**
* Defines the javascript picker implementation.
*
* @var string
*/
public $jsWidget = 'ui.richtext.Richtext';
/**
* Minimum character input before triggering search query.
*
* @var integer
*/
public $minInput = 3;
/**
* If the ActiveForm is set, it will be used to create the picker field,
* otherwise it's created by Html::activeDropDownList
*
* @var \yii\widgets\ActiveForm
*/
public $form;
/**
* Model instance. Requires the setting of an model $attribute.
*
* @var \yii\db\ActiveRecord
*/
public $model;
/**
* Model attribute which holds the picker value. The referenced model attribute has to be an
* array.
*
* @var string
*/
public $attribute;
/**
* Input form name.
* This can be provided if no form and model is provided for custom input field setting.
*
* @var type
*/
public $name;
/**
* Can be used to overwrite the default placeholder.
*
* @var string
*/
public $placeholder;
/**
* The url used for the default @ metioning.
* If there is no $searchUrl is given, the $searchRoute will be used instead.
*
* @var string
*/
public $mentioningUrl;
/**
* Route used for the default @ mentioning. This will only be used if
* not $searchUrl is given.
*
* @var string
*/
protected $mentioningRoute = "/search/search/mentioning";
/**
* Richtext features supported for within this feature.
* By default all features will be included.
*
* @var array
*/
public $includes = [];
/**
* Richtext features not supported in this richtext feature.
*
* @var array
*/
public $excludes = [];
/**
* Can be used to set the value in case no $model and $attribute is provided.
* $model and $attribute is provided.
*
* @var string
*/
public $value;
/**
* If set to true the picker will be focused automatically.
*
* @var boolean
*/
public $focus = false;
/**
* Disables the input field.
* @var boolean
*/
public $disabled = false;
/**
* Will be used as userfeedback, why this richtext is disabled.
*
* @var string
*/
public $disabledText = false;
/**
* @inheritdoc
* @var boolean
*/
public $init = true;
public $visible = true;
/**
* @inhertidoc
*/
public function run()
{
$inputOptions = $this->getAttributes();
$inputOptions['id'] = $this->getId(true).'_input';
$inputOptions['style'] = 'display:none;color';
unset($inputOptions['contenteditable']);
$modelAttribute = $this->attribute;
if ($this->form != null) {
$input = $this->form->field($this->model, $this->attribute)->textarea($inputOptions);
$richText = Html::tag('div', RichText::widget(['text' => $this->model->$modelAttribute, 'edit' => true]), $this->getOptions());
} else if ($this->model != null) {
$input = Html::activeTextarea($this->model, $this->attribute, $inputOptions);
$richText = Html::tag('div', RichText::widget(['text' => $this->model->$modelAttribute, 'edit' => true]), $this->getOptions());
} else {
$input = Html::textarea(((!$this->name) ? 'richtext' : $this->name), $this->value, $inputOptions);
$richText = Html::tag('div', RichText::widget(['text' => $this->value, 'edit' => true]),$this->getOptions());
}
return $input.$richText;
}
public function getData()
{
$result = [
'excludes' => $this->excludes,
'includes' => $this->includes,
'mentioning-url' => $this->getMentioningUrl(),
'placeholder' => $this->placeholder,
];
if($this->disabled) {
$result['disabled'] = true;
$result['disabled-text'] = $this->disabledText;
}
return $result;
}
public function getAttributes()
{
return [
'class' => "atwho-input form-control humhub-ui-richtext",
'contenteditable' => "true",
];
}
public function getMentioningUrl()
{
return ($this->mentioningUrl) ? $this->mentioningUrl : Url::to([$this->mentioningRoute]);
}
}

View File

@ -1,4 +1,5 @@
<div id="<?= $id ?>" <?= $modalData ?> class="modal" tabindex="-1" role="dialog" aria-hidden="true">
<?= yii\helpers\Html::beginTag('div', $options) ?>
<?= \humhub\widgets\ModalDialog::widget([
'header' => $header,
'animation' => $animation,
@ -8,4 +9,5 @@
'footer' => $footer,
'initialLoader' => $initialLoader
]); ?>
</div>
<?= yii\helpers\Html::endTag('div') ?>

View File

@ -39,11 +39,11 @@ use yii\helpers\Url;
$editableContent.atwho({
at: "@",
data: [{image: '', 'cssClass': 'hint', name: "<?= Yii::t('base', 'Please type at least 3 characters') ?>"}],
insert_tpl: "<a href='${link}' target='_blank' class='atwho-user' data-user-guid='${atwho-at}-${type}${guid}'>${atwho-data-value}&#x200b;</a>",
tpl: "<li class='hint' data-value=''>${name}</li>",
insertTpl: "<a href='${link}' target='_blank' class='atwho-user' data-user-guid='${atwho-at}-${type}${guid}'>${name}&#x200b;</a>",
displayTpl: "<li class='hint' data-value=''>${name}</li>",
search_key: "name",
limit: 10,
highlight_first: false,
highlightFirst: false,
callbacks: {
matcher: function (flag, subtext, should_start_with_space) {
var match, regexp;
@ -58,18 +58,18 @@ use yii\helpers\Url;
return null;
},
remote_filter: function (query, callback) {
remoteFilter: function (query, callback) {
this.setting.highlight_first = false;
// Render loading user feedback.
this.setting.tpl = "<li class='hint' data-value=''>${name}</li>";
this.setting.displayTpl = "<li class='hint' data-value=''>${name}</li>";
this.view.render([{"type":"test","cssClass": "hint", "name":"<?= Yii::t('base', 'Loading...') ?>","image":"","link":""}]);
// check the char length and data-query attribute for changing plugin settings for showing results
if (query.length >= 3 && $('#<?= $id; ?>_contenteditable').attr('data-query') == '1') {
// set plugin settings for showing results
this.setting.highlight_first = true;
this.setting.tpl = '<li class="${cssClass}" data-value="@${name}">${image} ${name}</li>';
this.setting.highlightFirst = true;
this.setting.displayTpl = '<li class="${cssClass}" data-value="@${name}">${image} ${name}</li>';
$.getJSON("<?php echo Url::to([$userSearchUrl]); ?>", {keyword: query}, function (data) {
callback(data);
});
@ -81,10 +81,10 @@ use yii\helpers\Url;
}
}).atwho({
at: ":",
insert_tpl: "<img data-emoji-name=';${name};' class='atwho-emoji' with='18' height='18' src='<?php echo Yii::getAlias('@web/img/emoji/${name}.svg'); ?>' />",
tpl: "<li class='atwho-emoji-entry' data-value=';${name};'><img with='18' height='18' src='<?php echo Yii::getAlias('@web/img/emoji/${name}.svg'); ?>'/></li>",
insertTpl: "<img data-emoji-name=';${name};' class='atwho-emoji' with='18' height='18' src='<?php echo Yii::getAlias('@web/img/emoji/${name}.svg'); ?>' />",
displayTpl: "<li class='atwho-emoji-entry' data-value=';${name};'><img with='18' height='18' src='<?php echo Yii::getAlias('@web/img/emoji/${name}.svg'); ?>'/></li>",
data: emojis_list,
highlight_first: true,
highlightFirst: true,
limit: 100
});
@ -217,13 +217,15 @@ use yii\helpers\Url;
// remove all line breaks
html = html.replace(/(?:\r\n|\r|\n)/g, "");
// At.js adds a zwj at the end of each mentioning
html = html.replace(/\u200d/g,'');
// replace all <br> with new line break
element.html(html.replace(/\<br\s*\>/g, '\n'));
// return plain text without html tags
return element.text().trim();
}
/**

File diff suppressed because one or more lines are too long