JS rewrite . initial stream implementation.

This commit is contained in:
buddha87 2016-04-28 14:35:30 +02:00
parent 57c736fbff
commit 6eb0b942ff
21 changed files with 1732 additions and 952 deletions

3
.gitignore vendored
View File

@ -4,8 +4,7 @@ assets/*
protected/runtime/*
!protected/runtime/.gitignore
node_modules/*
!node_modules/.gitignore
node_modules
protected/config/local/*
!protected/config/local/.gitignore

View File

@ -7,8 +7,8 @@ module.exports = function (grunt) {
},
dist: {
src: ['js/humhub.core.js', 'js/humhub.util.js' ,'js/humhub.additions.js',
'js/humhub.client.js', 'js/humhub.ui.js', 'js/humhub.actions.js', 'js/humhub.content.js',
'js/humhub.stream.js', 'js/humhub.ui.modal.js'],
'js/humhub.client.js', 'js/humhub.ui.js', 'js/humhub.ui.modal.js', 'js/humhub.actions.js',
'js/humhub.content.js', 'js/humhub.stream.js'],
dest: 'js/dist/humhub.all.js'
}
},

View File

@ -34,6 +34,7 @@
"bower-asset/fontawesome": "^4.3.0",
"bower-asset/bootstrap-markdown": "2.10.*",
"bower-asset/select2" : "^4.0.2",
"bower-asset/bluebird" : "^3.3.5",
"bower-asset/select2-bootstrap-theme" : "0.1.0-beta.4"
},
"require-dev": {

1559
js/dist/humhub.all.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -33,6 +33,7 @@ humhub.initModule('client', function (module, require, $) {
*/
var Response = function (data) {
this.data = data;
$.extend(this, data);
};
/**
@ -52,14 +53,10 @@ humhub.initModule('client', function (module, require, $) {
Response.prototype.isError = function () {
return this.getStatus() > 0 || this.getErrors().length;
};
Response.prototype.getStatus = function () {
return (this.data && object.isDefined(this.data.status)) ? this.data.status : -1;
};
Response.prototype.getErrorTitle = function() {
return (this.data) ? this.data.errorTitle : undefined;
};
Response.prototype.getStatus = function () {
return (this.status) ? this.status : -1;
};
Response.prototype.getFirstError = function() {
var errors = this.getErrors();
@ -71,9 +68,8 @@ humhub.initModule('client', function (module, require, $) {
Response.prototype.setAjaxError = function(xhr, errorThrown, textStatus,data , status) {
this.xhr = xhr;
this.textStatus = textStatus;
this.data = data || {};
this.data.status = status || xhr.status;
this.data.errors = [errorThrown];
this.status = status || xhr.status;
this.errors = [errorThrown];
};
/**
@ -82,40 +78,8 @@ humhub.initModule('client', function (module, require, $) {
* @returns {array} error array or empty array
*/
Response.prototype.getErrors = function () {
if (this.data) {
var errors = this.data.errors || [];
return (object.isString(errors)) ? [errors] : errors;
}
return [];
};
/**
* Returns the raw content object. The content object can either be an
* object with multiple partials {partialId: content string} or a single content string.
* @param {type} id
* @returns {undefined|humhub.client_L5.Response.data.content}1
*/
Response.prototype.getContent = function () {
return this.data.content;
};
/**
* Returns the response partial. If no id is given we return the first partial
* we find.
* @returns {humhub.client_L5.Response.data.content}
*/
Response.prototype.getPartial = function (id) {
if (!this.data) {
return;
}
//TODO: handleResponse filter...
if (object.isObject(this.data.content)) {
return (id) ? this.data.content[id] : this.data.content;
} else if (!id) {
return this.data.content;
}
return;
var errors = this.errors || [];
return (object.isString(errors)) ? [errors] : errors;
};
Response.prototype.toString = function () {
@ -126,53 +90,62 @@ humhub.initModule('client', function (module, require, $) {
var cfg = cfg || {};
$form = object.isString($form) ? $($form) : $form;
cfg.type = $form.attr('method') || 'post';
cfg.data = $form.serialize()
cfg.data = $form.serialize();
ajax($form.attr('action'), cfg);
};
var ajax = function (path, cfg) {
var post = function(path, cfg) {
var cfg = cfg || {};
var async = cfg.async || true;
var dataType = cfg.dataType || "json";
cfg.type = 'POST';
return ajax(path, cfg);
};
var error = function (xhr, textStatus, errorThrown, data, status) {
//Textstatus = "timeout", "error", "abort", "parsererror", "application"
if (cfg.error && object.isFunction(cfg.error)) {
var response = new Response();
response.setAjaxError(xhr, errorThrown, textStatus, data, status);
cfg.error(response);
} else {
console.warn('Unhandled ajax error: ' + path + " type" + type + " error: " + errorThrown);
}
};
var ajax = function (path, cfg) {
return new Promise(function(resolve, reject) {
var cfg = cfg || {};
var async = cfg.async || true;
var dataType = cfg.dataType || "json";
var success = function (json, textStatus, xhr) {
var response = new Response(json);
if (response.isError()) { //Application errors
return error(xhr, "application", response.getErrors(), json, response.getStatus() );
} else if (cfg.success) {
response.textStatus = textStatus;
response.xhr = xhr;
cfg.success(response);
}
};
var error = function (xhr, textStatus, errorThrown, data, status) {
//Textstatus = "timeout", "error", "abort", "parsererror", "application"
if (cfg.error && object.isFunction(cfg.error)) {
var response = new Response();
response.setAjaxError(xhr, errorThrown, textStatus, data, status);
cfg.error(response);
}
reject(xhr, textStatus, errorThrown, data, status);
};
$.ajax({
url: path,
data: cfg.data,
type: cfg.type,
beforeSend: cfg.beforeSend,
processData: cfg.processData,
contentType: cfg.contentType,
async: async,
dataType: dataType,
success: success,
error: error
var success = function (json, textStatus, xhr) {
var response = new Response(json);
if (response.isError()) { //Application errors
return error(xhr, "application", response.getErrors(), json, response.getStatus() );
} else if (cfg.success) {
response.textStatus = textStatus;
response.xhr = xhr;
cfg.success(response);
}
resolve(response);
};
$.ajax({
url: path,
data: cfg.data,
type: cfg.type,
beforeSend: cfg.beforeSend,
processData: cfg.processData,
contentType: cfg.contentType,
async: async,
dataType: dataType,
success: success,
error: error
});
});
};
module.export({
ajax: ajax,
post: post,
submit: submit,
init: init
});

View File

@ -9,67 +9,28 @@ humhub.initModule('content', function(module, require, $) {
var object = require('util').object;
var actions = require('actions');
module.init = function() {
actions.registerHandler('humhub.modules.content.actiontHandler', function(event) {
return module.handleAction(event);
});
};
/**
* Handles the given contentAction event. The event should provide the following properties:
*
* $trigger (required) : the trigger node of the event
* handler (required) : the handler functionn name to be executed on the content
* type (optoinal) : the event type 'click', 'change',...
*
* @param {object} event - event object
* @returns {Boolean} true if the contentAction could be executed else false
*/
module.handleAction = function(event) {
var $contentBase = this.getContentBase(event.$trigger);
if($contentBase.length) {
//Initialize a content instance by means of the content-base type and execute the handler
var ContentType = require($contentBase.data('content-base'));
if(ContentType) {
var content = new ContentType($contentBase);
if(event.handler && content[event.handler]) {
content[event.handler](event);
return true;
}
} else {
console.error('No ContentType found for '+$contentBase.data('content-base'));
}
}
return false;
};
module.getContentBase = function($element) {
return $element.closest('[data-content-base]');
};
var Content = function(id) {
if(!id) { //Create content
var Content = function(container) {
if(!container) { //Create content
return;
}
if (typeof id === 'string') {
this.id = id;
this.$ = $('#' + id);
} else if (id.jquery) {
this.$ = id;
this.id = this.$.attr('id');
}
this.$ = (object.isString(container)) ? $('#' + container) : container;
this.contentBase = this.$.data('content-base');
};
Content.prototype.getContentActions = function() {
return ['create','edit','delete'];
};
Content.prototype.getKey = function () {
return this.$.data('content-key');
return this.$.data('content-pk');
};
Content.prototype.getEditUrl = function () {
var result = this.$.data('content-edit-url');
Content.prototype.data = function(dataSuffix) {
var result = this.$.data(dataSuffix);
if(!result) {
var parentContent = this.getParentContentBase('[data-content-base]');
var parentContent = this.getParentContentBase();
if(parentContent) {
return parentContent.getEditUrl();
return parentContent.data(dataSuffix);
}
}
return result;
@ -89,11 +50,19 @@ humhub.initModule('content', function(module, require, $) {
Content.prototype.create = function (addContentHandler) {
//Note that this Content won't have an id, so the backend will create an instance
if(indexOf(this.getContentActions(), 'create') < 0) {
return;
}
this.edit(addContentHandler);
};
Content.prototype.edit = function (successHandler) {
var editUrl = this.getEditUrl();
if(indexOf(this.getContentActions(), 'edit') < 0) {
return;
}
var editUrl = this.data('content-edit-url');
var contentId = this.getKey();
var modal = require('ui.modal').global;
@ -146,20 +115,103 @@ humhub.initModule('content', function(module, require, $) {
});
};
Content.prototype.replaceContent = function(content) {
try {
this.$.html($(content).children());
} catch(e) {
console.error('Error occured while replacing content: '+this.id , e);
Content.prototype.delete = function () {
if(this.getContentActions().indexOf('delete') < 0) {
return;
}
var that = this;
var url = this.data('content-delete-url');
if(url) {
client.post(url, {
data: {
id: that.getKey()
},
success: function(json) {
json.success;
that.remove();
},
error: function(json) {
console.error(json);
}
})
} else {
console.error('Content delete was called, but no url could be determined for '+this.contentBase);
}
};
Content.prototype.delete = function () {
//Search for data-content-delte-url on root.
//if(this.deleteModal) {open modal bla}
//Call this url with data-content-pk
//Trigger delete event
Content.prototype.replaceContent = function(content) {
try {
var that = this;
this.$.animate({ opacity: 0 }, 'fast', function() {
that.$.html($(content).children());
that.$.stop().animate({ opacity: 1 }, 'fast');
if(that.highlight) {
that.highlight();
}
});
} catch(e) {
console.error('Error occured while replacing content: '+this.$.attr('id') , e);
}
};
module.Content = Content;
Content.prototype.remove = function() {
var that = this;
this.$.animate({ height: 'toggle', opacity: 'toggle' }, 'fast', function() {
that.$.remove();
//TODO: fire global event
});
};
Content.getContentBase = function($element) {
return $element.closest('[data-content-base]');
};
Content.getInstance = function($contentBase) {
$contentBase = (object.isString($contentBase)) ? $('#'+$contentBase) : $contentBase;
var ContentType = require($contentBase.data('content-base'));
if(ContentType) {
return new ContentType($contentBase);
}
};
var init = function() {
actions.registerHandler('humhub.modules.content.actiontHandler', function(event) {
return module.handleAction(event);
});
};
/**
* Handles the given contentAction event. The event should provide the following properties:
*
* $trigger (required) : the trigger node of the event
* handler (required) : the handler functionn name to be executed on the content
* type (optoinal) : the event type 'click', 'change',...
*
* @param {object} event - event object
* @returns {Boolean} true if the contentAction could be executed else false
*/
handleAction = function(event) {
var $contentBase = Content.getContentBase(event.$trigger);
if($contentBase.length) {
//Initialize a content instance by means of the content-base type and execute the handler
var content = Content.getInstance($contentBase);
if(content) {
//Check if the content instance provides this actionhandler
if(event.handler && content[event.handler]) {
content[event.handler](event);
return true;
}
} else {
console.error('No ContentType found for '+$contentBase.data('content-base'));
}
}
return false;
};
module.export({
Content : Content,
init : init,
handleAction: handleAction
});
});

View File

@ -149,6 +149,38 @@ var humhub = humhub || (function($) {
}
};
/**
* Config implementation
*/
var config = {
get : function(module, key, defaultVal) {
if(_isDefined(key)) {
var result = this.getModuleConfig(module)[key];
return (_isDefined(result)) ? result : defaultVal;
}
},
getModuleConfig: function(module) {
if(!this.module) {
this.module = {};
}
return this.module;
},
is : function(module, key, defaultVal) {
return this.get(module, key,defaultVal) === true;
},
set : function(module, key, value) {
//Moduleid with multiple values
if(arguments.length === 2) {
$.extend(this.getModuleConfig(module), key);
} else if(arguments.length === 3) {
this.getModuleConfig(module)[key] = value;
}
}
};
/**
* Cuts the prefix humub.modules or modules. from the given value.
* @param {type} value
@ -186,6 +218,10 @@ var humhub = humhub || (function($) {
return val.indexOf(prefix) === 0;
};
var _isDefined = function(obj) {
return typeof obj !== 'undefined';
};
//Initialize all initial modules
$(document).ready(function() {
$.each(initialModules, function(i, module) {
@ -197,7 +233,11 @@ var humhub = humhub || (function($) {
});
});
return {
initModule: initModule
initModule: initModule,
modules: modules,
config: config
};
})($);

View File

@ -2,183 +2,480 @@
* Core module for managing Streams and StreamItems
* @type Function
*/
humhub.initModule('stream', function(module, require, $) {
humhub.initModule('stream', function (module, require, $) {
var ENTRY_ID_SELECTOR_PREFIX = '#wallEntry_';
var WALLSTREAM_ID = 'wallStream';
var util = require('util');
var object = util.object;
var string = util.string;
var client = require('client');
var modal = require('modal');
var Content = require('content').Content;
var STREAM_INIT_COUNT = 8;
var STREAM_LOAD_COUNT = 4;
//TODO: load streamUrl from config
//TODO: readonly
/**
* Base class for all StreamItems
* Base class for all StreamContent
* @param {type} id
* @returns {undefined}
*/
var StreamItem = function (id) {
if (typeof id === 'string') {
this.id = id;
this.$ = $('#' + id);
} else if (id.jquery) {
this.$ = id;
this.id = this.$.attr('id');
var StreamEntry = function (id) {
this.$ = object.isString(id) ? this.$ = $('#' + id) : id;
Content.call(this);
};
object.inherits(StreamEntry, Content);
StreamEntry.prototype.getContentActions = function() {
return ['delete', 'edit'];
};
StreamEntry.prototype.delete = function () {
var content = this.getContentInstance();
if(content && content.delete) {
//TODO: modalconfirm
content.delete();
} else {
StreamEntry._super.delete.call(this);
}
};
/**
* Removes the stream item from stream
*/
StreamItem.prototype.remove = function () {
this.$.remove();
StreamEntry.prototype.reload = function () {
getStream().reload(this);
};
StreamItem.prototype.getContentKey = function () {}
StreamItem.prototype.edit = function () {
StreamEntry.prototype.edit = function () {
//Search for data-content-edit-url on root.
//Call this url with data-content-pk
//Trigger delete event
};
StreamEntry.prototype.getContentInstance = function () {
return Content.getInstance(this.$.find('[data-content-base]'));
};
StreamItem.prototype.delete = function () {
//Search for data-content-delte-url on root.
//Call this url with data-content-pk
//Trigger delete event
};
StreamItem.prototype.getContent = function () {
return this.$.find('.content');
};
/*
module.StreamItem.prototype.highlightContent = function () {
var $content = this.getContent();
$content.addClass('highlight');
$content.delay(200).animate({backgroundColor: 'transparent'}, 1000, function () {
$content.removeClass('highlight');
$content.css('backgroundColor', '');
});
};
*/
/**
* Stream implementation
* @param {type} id
* Stream implementation.
*
* @param {type} container id or jQuery object of the stream container
* @returns {undefined}
*/
var Stream = function (id) {
this.id = id;
this.$ = $('#' + id);
};
Stream.prototype.getEntry = function (id) {
//Search for data-content-base and try to initiate the Item class
var Stream = function (container) {
this.$ = (object.isString(container)) ? $('#' + container) : container;
return new module.Entry(this.$.find(ENTRY_ID_SELECTOR_PREFIX + id));
};
if (!this.$.length) {
console.error('Could not initialize stream, invalid container given'+ container);
return;
}
//If a contentId is set on the stream root we will only show the single content
if(this.$.data('stream-contentid')) {
this.contentId = parseInt(this.$.data('stream-contentid'));
}
this.$stream = this.$.find(".s2_stream");
//Cache some stream relevant data/nodes
this.url = this.$.data('stream'); //TODO: set this in config instead of data field
this.$loader = this.$stream.find(".streamLoader");
this.$content = this.$stream.find('.s2_streamContent');
this.$filter = $('.wallFilterPanel');
Stream.prototype.wallStick = function (url) {
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
currentStream.deleteEntry(wallEntryId);
currentStream.prependEntry(wallEntryId);
});
$('html, body').animate({scrollTop: 0}, 'slow');
}
} else {
alert(data.errorMessage);
}
});
//TODO: make this configurable
this.filters = [];
this.sort = "c";
Content.call(this);
};
Stream.prototype.wallUnstick = function (url) {
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
//Reload the whole stream, since we have to reorder the entries
currentStream.showStream();
}
});
object.inherits(Stream, Content);
Stream.prototype.getContentActions = function() {
return [];
};
/**
* Click Handler for Archive Link of Wall Posts
* (archiveLink.php)
* Initializes the stream, by clearing the stream and reloading initial stream entries,
* this should be called if any filter/sort settings are changed or the stream
* needs an reload.
*
* @param {type} className
* @param {type} id
* @returns {humhub.stream_L5.Stream.prototype}
*/
Stream.prototype.wallArchive = function (id) {
url = wallArchiveLinkUrl.replace('-id-', id);
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
//currentStream.reloadWallEntry(wallEntryId);
// fade out post
setInterval(fadeOut(), 1000);
function fadeOut() {
// fade out current archived post
$('#wallEntry_' + wallEntryId).fadeOut('slow');
}
});
}
}
});
Stream.prototype.init = function () {
this.clear();
this.$stream.show();
if (this.isShowSingleEntry()) {
this.loadSingleEntry(this.contentId);
} else {
this.loadEntries(STREAM_INIT_COUNT);
}
return this;
};
Stream.prototype.clear = function() {
this.lastEntryLoaded = false;
this.readOnly = false;
this.loading = false;
this.$.find(".s2_streamContent").empty();
this.$.find(".s2_stream").hide();
this.$.find(".s2_single").hide();
this.$.find(".streamLoader").hide();
this.$.find(".emptyStreamMessage").hide();
this.$.find(".emptyFilterStreamMessage").hide();
this.$.find('.back_button_holder').hide();
this.$filter.hide();
};
/**
* Click Handler for Un Archive Link of Wall Posts
* (archiveLink.php)
*
* @param {type} className
* @param {type} id
*/
Stream.prototype.wallUnarchive = function (id) {
url = wallUnarchiveLinkUrl.replace('-id-', id);
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
currentStream.reloadWallEntry(wallEntryId);
});
}
Stream.prototype.loadSingleEntry = function(contentId) {
this.$.find('.back_button_holder').show();
this.loadEntries(1, (contentId + 1), '');
};
Stream.prototype.reloadEntry = function(entry) {
var that = this;
return new Promise(function(resolve, reject) {
entry = (entry instanceof StreamEntry) ? entry : that.getEntry(entry);
if(!entry) {
console.warn('Attempt to reload of non existent entry: '+entry);
reject();
return;
}
var contentId = entry.getKey();
return that._load(1, (contentId + 1), '').then(function(response) {
if(response.content[contentId]) {
entry.replaceContent(response.content[contentId].output);
resolve(entry);
} else {
console.warn('Reload failed: ContentId not found in response: '+contentId);
reject();
}
}, reject);
});
};
Stream.prototype.loadEntries = function (limit, from, filter, sort) {
if (this.loading || this.lastEntryLoaded) {
return;
}
//Initialize loading process
this.$loader.show();
this.loading = true;
//Overwrite the stream settings if provided
limit = limit || STREAM_LOAD_COUNT;
from = from || this.getLastContentId();
filter = filter || this.getFilterString();
sort = sort || this.sort;
var that = this;
return new Promise(function(resolve, reject) {
that._load(limit, from, filter,sort).then(function(response) {
that.$loader.hide();
if (object.isEmpty(response.content)) {
that.lastEntryLoaded = true;
$('#btn-load-more').hide();
} else {
that.lastEntryLoaded = response.is_last;
that.appendEntries(response);
}
that.loading = false;
that.onChange();
resolve();
}).catch(function(err) {
//TODO: handle error
that.loading = false;
that.$loader.hide();
reject();
});
});
};
var getStream = function () {
if (!module.mainStream) {
module.mainStream = new module.Stream(WALLSTREAM_ID);
Stream.prototype._load = function (limit, from, filter, sort) {
return client.ajax(this.url, {
data: {
filters: filter,
sort: sort,
from: from,
limit: limit
}
});
};
Stream.prototype.getLastContentId = function () {
var $lastEntry = this.$stream.find('[data-content-pk]').last();
if ($lastEntry.length) {
return $lastEntry.data('stream-contentid');
}
return module.mainStream;
};
Stream.prototype.appendEntries = function (response) {
var that = this;
var result = '';
$.each(response.contentIds, function (i, key) {
var $entry = that.$.find('[data-content-pk="' + key + '"]');
if ($entry.length) {
$entry.remove();
}
result += response.content[key].output;
});
return this.$content.append(result);
};
/**
* Fired when new entries are shown
*/
Stream.prototype.onChange = function () {
if (this.readOnly) {
$('.wallReadOnlyHide').hide();
$('.wallReadOnlyShow').show();
} else {
$('.wallReadOnlyShow').hide();
}
var hasEntries = this.hasEntries();
if (!hasEntries && !this.hasFilter()) {
this.$.find('.emptyStreamMessage').show();
this.$filter.hide();
} else if (!hasEntries) {
this.$.find('.emptyFilterStreamMessage').hide();
} else if(!this.isShowSingleEntry()) {
this.$filter.show();
this.$.find('.emptyStreamMessage').hide();
this.$.find('.emptyFilterStreamMessage').hide();
}
//TODO: fire global event
};
Stream.prototype.isShowSingleEntry = function () {
return object.isDefined(this.contentId);
};
Stream.prototype.hasEntries = function () {
return this.getEntryCount() > 0;
};
Stream.prototype.getEntryCount = function () {
return this.$.find('[data-content-pk]').length;
};
Stream.prototype.hasFilter = function () {
return this.filters.length > 0;
};
Stream.prototype.getFilterString = function () {
var result = '';
$.each(this.filters, function(i, filter) {
result += filter+',';
});
return string.cutsuffix(result, ',');
};
Stream.prototype.setFilter = function (filterId) {
if(this.filters.indexOf(filterId) < 0) {
this.filters.push(filterId);
}
};
Stream.prototype.unsetFilter = function (filterId) {
var index = this.filters.indexOf(filterId);
if(index > -1) {
this.filters.splice(index, 1);
}
};
Stream.prototype.getEntry = function(key) {
return new StreamEntry(this.$.find('[data-content-pk="' + key + '"]'));
};
Stream.prototype.getEntryByNode = function($childNode) {
return new StreamEntry($childNode.closest('[data-content-pk]'));
};
var getStream = function () {
if (!module.instance) {
module.instance = new Stream($('[data-stream]'));
}
return module.instance;
};
var getEntry = function (id) {
return module.getStream().getEntry(id);
};
var init = function () {
var stream = getStream().init();
$(window).scroll(function () {
if ($(window).scrollTop() == $(document).height() - $(window).height()) {
if (stream && !stream.loading && !stream.isShowSingleEntry() && !stream.lastEntryLoaded) {
stream.loadEntries();
}
}
});
stream.$.on('click', '.singleBackLink', function() {
stream.contentId = undefined;
stream.init();
$(this).hide();
});
initFilterNav();
};
var initFilterNav = function() {
$(".wallFilter").click(function () {
var $filter = $(this);
var checkboxi = $filter.children("i");
checkboxi.toggleClass('fa-square-o').toggleClass('fa-check-square-o');
if(checkboxi.hasClass('fa-check-square-o')) {
getStream().setFilter($filter.attr('id').replace('filter_', ''));
} else {
getStream().unsetFilter($filter.attr('id').replace('filter_', ''));
}
getStream().init();
});
$(".wallSorting").click(function () {
var newSortingMode = $(this).attr('id');
// uncheck all sorting
$(".wallSorting").find('i')
.removeClass('fa-check-square-o')
.addClass('fa-square-o');
// check current sorting mode
$("#" + newSortingMode).children("i")
.removeClass('fa-square-o')
.addClass('fa-check-square-o');
// remove sorting id append
newSortingMode = newSortingMode.replace('sorting_', '');
// Switch sorting mode and reload stream
getStream().sort = newSortingMode;
getStream().init();
});
};
module.export({
getStream : getStream,
getEntry : getEntry
StreamEntry: StreamEntry,
Stream: Stream,
getStream: getStream,
getEntry: getEntry,
init: init
});
});
});
/* TODO:
Stream.prototype.wallStick = function (url) {
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
currentStream.deleteEntry(wallEntryId);
currentStream.prependEntry(wallEntryId);
});
$('html, body').animate({scrollTop: 0}, 'slow');
}
} else {
alert(data.errorMessage);
}
});
};
Stream.prototype.wallUnstick = function (url) {
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
//Reload the whole stream, since we have to reorder the entries
currentStream.showStream();
}
});
};
/**
* Click Handler for Archive Link of Wall Posts
* (archiveLink.php)
*
* @param {type} className
* @param {type} id
Stream.prototype.wallArchive = function (id) {
url = wallArchiveLinkUrl.replace('-id-', id);
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
//currentStream.reloadWallEntry(wallEntryId);
// fade out post
setInterval(fadeOut(), 1000);
function fadeOut() {
// fade out current archived post
$('#wallEntry_' + wallEntryId).fadeOut('slow');
}
});
}
}
});
};
/**
* Click Handler for Un Archive Link of Wall Posts
* (archiveLink.php)
*
* @param {type} className
* @param {type} id
Stream.prototype.wallUnarchive = function (id) {
url = wallUnarchiveLinkUrl.replace('-id-', id);
$.ajax({
dataType: "json",
type: 'post',
url: url
}).done(function (data) {
if (data.success) {
if (currentStream) {
$.each(data.wallEntryIds, function (k, wallEntryId) {
currentStream.reloadWallEntry(wallEntryId);
});
}
}
});
};
/*
module.StreamItem.prototype.highlightContent = function () {
var $content = this.getContent();
$content.addClass('highlight');
$content.delay(200).animate({backgroundColor: 'transparent'}, 1000, function () {
$content.removeClass('highlight');
$content.css('backgroundColor', '');
});
};
*/

View File

@ -4,5 +4,5 @@ humhub.initModule('ui', function(module, require, $) {
additions.registerAddition('.autosize', function($match) {
$match.autosize();
});
}
};
});

View File

@ -16,8 +16,8 @@
* @param {type} param2
*/
humhub.initModule('ui.modal', function (module, require, $) {
var object = require('util').object;
var additions = require('additions');
var actions = require('actions');
//Keeps track of all initialized modals
var modals = [];
@ -285,6 +285,18 @@ humhub.initModule('ui.modal', function (module, require, $) {
return this.$modal.find('.modal-body');
};
var ConfirmModal = function(id, confirmHandler) {
Modal.call(this, id);
this.initButtons();
};
ConfirmModal.prototype.initButtons = function(confirmHandler) {
this.$confirm = this.$modal.find('[data-modal-submit]');
this.$confirm.on('click', confirmHandler);
};
object.inherits(ConfirmModal, Modal);
module.export({
init: function () {
module.global = new Modal('global-modal');

View File

@ -15,6 +15,9 @@ humhub.initModule('util', function(module, require, $) {
isArray: function(obj) {
return $.isArray(obj);
},
isEmpty: function(obj) {
return $.isEmptyObject(obj);
},
isString: function (obj) {
return typeof obj === 'string';
},
@ -46,7 +49,7 @@ humhub.initModule('util', function(module, require, $) {
var string = {
cutprefix : function(val, prefix) {
if(!this.startsWith(prefix)) {
if(!this.startsWith(val, prefix)) {
return val;
}
return val.substring(prefix.length, val.length);

4
node_modules/.gitignore generated vendored
View File

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

View File

@ -58,6 +58,7 @@ class AppAsset extends AssetBundle
* https://github.com/inuyaksa/jquery.nicescroll/issues/574
*/
//'humhub\assets\JqueryNiceScrollAsset',
'humhub\assets\BluebirdAsset',
'humhub\assets\JqueryTimeAgoAsset',
'humhub\assets\JqueryKnobAsset',
'humhub\assets\JqueryWidgetAsset',

View File

@ -0,0 +1,38 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\assets;
use yii\web\AssetBundle;
/**
* jquery-knob
*
* @author luke
*/
class BluebirdAsset extends AssetBundle
{
public $jsOptions = ['position' => \yii\web\View::POS_BEGIN];
/**
* @inheritdoc
*/
public $sourcePath = '@bower/bluebird';
/**
* @inheritdoc
*/
public $js = ['js/browser/bluebird.min.js'];
/**
* @inheritdoc
*/
public $css = [];
}

View File

@ -34,6 +34,13 @@ class ContentController extends Controller
];
}
public function actionDeleteById()
{
Yii::$app->response->format = 'json';
$id = (int) Yii::$app->request->get('id');
Content::findOne($id);
}
/**
* Deletes a content object
*
@ -44,22 +51,27 @@ class ContentController extends Controller
Yii::$app->response->format = 'json';
$this->forcePostRequest();
$json = [
'success' => 'false'
];
$model = Yii::$app->request->get('model');
$id = (int) Yii::$app->request->get('id');
//Due to backward compatibility we use the old delte mechanism in case a model parameter is provided
$id = (int) ($model != null) ? Yii::$app->request->get('id') : Yii::$app->request->post('id');
$contentObj = Content::get($model, $id);
$contentObj = ($model != null) ? Content::get($model, $id) : Content::findOne($id);
if ($contentObj !== null && $contentObj->content->canDelete() && $contentObj->delete()) {
if(!$contentObj->canDelete()) {
throw new HttpException(400, Yii::t('ContentModule.controllers_ContentController', 'Could not delete content: Access denied!'));
}
if ($contentObj !== null && $contentObj->delete()) {
$json = [
'success' => true,
'uniqueId' => $contentObj->getUniqueId(),
'model' => $model,
'pk' => $id
];
} else {
throw new HttpException(500, Yii::t('ContentModule.controllers_ContentController', 'Could not delete content!'));
}
return $json;

View File

@ -14,7 +14,8 @@
$cssClass = ($entry->sticked) ? 'wall-entry sticked-entry' : 'wall-entry';
if ($mode != "activity") : ?>
<div class="<?php echo $cssClass ?>" id="wallEntry_<?php echo $entry->id; ?>">
<div class="<?php echo $cssClass ?>" data-content-base="humhub.modules.stream.StreamEntry"
data-content-pk="<?php echo $entry->id; ?>" data-stream-sticked="<?= $entry->sticked ?>">
<?php endif; ?>
<?php echo $content; ?>

View File

@ -6,8 +6,11 @@ use yii\helpers\Url;
?>
<li>
<!-- load modal confirm widget -->
<a href="#" data-action-click="delete">
<i class="fa fa-trash-o"></i> <?= Yii::t('ContentModule.widgets_views_deleteLink', 'Delete') ?>
</a>
<?php
echo humhub\widgets\ModalConfirm::widget(array(
/*echo humhub\widgets\ModalConfirm::widget(array(
'uniqueID' => 'modal_postdelete_' . $id,
'linkOutput' => 'a',
'title' => Yii::t('ContentModule.widgets_views_deleteLink', '<strong>Confirm</strong> post deleting'),
@ -17,6 +20,6 @@ use yii\helpers\Url;
'linkContent' => '<i class="fa fa-trash-o"></i> ' . Yii::t('ContentModule.widgets_views_deleteLink', 'Delete'),
'linkHref' => Url::to(['/content/content/delete', 'model' => $model, 'id' => $id]),
'confirmJS' => 'function(json) { $(".wall_"+json.uniqueId).remove(); }'
));
));*/
?>
</li>

View File

@ -20,7 +20,7 @@ use humhub\modules\user\models\User;
* @author luke
* @since 0.11
*/
abstract class Stream extends \yii\base\Action
abstract class Stream extends Action
{
/**
@ -97,7 +97,6 @@ abstract class Stream extends \yii\base\Action
$this->user = Yii::$app->user->getIdentity();
}
// Read parameters
if (!Yii::$app->request->isConsoleRequest) {
$from = Yii::$app->getRequest()->get('from', 0);
@ -191,16 +190,19 @@ abstract class Stream extends \yii\base\Action
$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 involed
if (in_array('entry_userinvoled', $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()]);
}
@ -223,22 +225,21 @@ abstract class Stream extends \yii\base\Action
$this->init();
$output['contents'] = [];
$output['content'] = [];
foreach ($this->activeQuery->all() as $content) {
$output['contents'][$content->id] = $this->getContentResultEntry($content);
$output['content'][$content->id] = $this->getContentResultEntry($content);
}
$output['total'] = count($output['contents']);
$output['total'] = count($output['content']);
$output['is_last'] = ($output['total'] < $this->activeQuery->limit);
// BEGIN: TEMPORARY until JS Rewrite
$output['output'] = '';
foreach ($output['contents'] as $i => $c) {
foreach ($output['content'] as $i => $c) {
$output['output'] .= $c['output'];
$output['lastEntryId'] = $i;
$output['entryIds'][] = $i;
$output['lastContentId'] = $i;
$output['contentIds'][] = $i;
}
$output['counter'] = $output['total'];
$output['counter'] = $output['total'];
// END: Temporary
return $output;

View File

@ -112,10 +112,6 @@ class StreamViewer extends \yii\base\Widget
{
$params = [
$this->streamAction,
'limit' => '-limit-',
'filters' => '-filter-',
'sort' => '-sort-',
'from' => '-from-',
'mode' => \humhub\modules\stream\actions\Stream::MODE_NORMAL
];

View File

@ -1,24 +1,11 @@
<?php
use yii\helpers\Url;
use \yii\web\View;
\humhub\modules\stream\assets\Stream::register($this);
$this->registerJs('var streamUrl="' . $streamUrl . '"', View::POS_BEGIN);
$jsLoadWall = "s = new Stream('#wallStream');\n";
$wallEntryId = (int) Yii::$app->request->getQueryParam('wallEntryId');
if ($wallEntryId != "") {
$jsLoadWall .= "s.showItem(" . $wallEntryId . ");\n";
} else {
$jsLoadWall .= "s.showStream();\n";
}
$jsLoadWall .= "currentStream = s;\n";
$jsLoadWall .= "mainStream = s;\n";
$jsLoadWall .= "$('#btn-load-more').click(function() { currentStream.loadMore(); })\n";
$this->registerJs($jsLoadWall, View::POS_READY);
$contentId = (int) Yii::$app->request->getQueryParam('wallEntryId');
$contentIdData = ($contentId != "") ? 'data-stream-contentid="'.$contentId.'"' : '' ;
?>
<!-- Stream filter section -->
<?php if ($this->context->showFilters) { ?>
<ul class="nav nav-tabs wallFilterPanel" id="filter" style="display: none;">
<li class=" dropdown">
@ -26,11 +13,12 @@ $this->registerJs($jsLoadWall, View::POS_READY);
class="caret"></b></a>
<ul class="dropdown-menu">
<?php foreach ($filters as $filterId => $filterTitle): ?>
<li><a href="#" class="wallFilter" id="<?php echo $filterId; ?>"><i
class="fa fa-square-o"></i> <?php echo $filterTitle; ?></a>
<li>
<a href="#" class="wallFilter" id="<?php echo $filterId; ?>">
<i class="fa fa-square-o"></i> <?php echo $filterTitle; ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</li>
<li class="dropdown">
@ -46,16 +34,20 @@ $this->registerJs($jsLoadWall, View::POS_READY);
</ul>
<?php } ?>
<div id="wallStream">
<!-- Stream content -->
<div id="wallStream" data-stream="<?= $streamUrl ?>" <?= $contentIdData ?>
data-content-base="humhub.modules.stream.Stream"
data-content-delete-url="<?= Url::to(['/content/content/delete']) ?>">
<!-- DIV for a normal wall stream -->
<div class="s2_stream" style="display:none">
<div class="back_button_holder" style="display:none">
<a href="#" class="singleBackLink btn btn-primary"><?php echo Yii::t('ContentModule.widgets_views_stream', 'Back to stream'); ?></a><br><br>
</div>
<div class="s2_streamContent"></div>
<?php echo \humhub\widgets\LoaderWidget::widget(['cssClass' => 'streamLoader']); ?>
<div class="emptyStreamMessage">
<div class="emptyStreamMessage" style="display:none;">
<div class="<?php echo $this->context->messageStreamEmptyCss; ?>">
<div class="panel">
<div class="panel-body">
@ -63,9 +55,8 @@ $this->registerJs($jsLoadWall, View::POS_READY);
</div>
</div>
</div>
</div>
<div class="emptyFilterStreamMessage">
<div class="emptyFilterStreamMessage" style="display:none;">
<div class="placeholder <?php echo $this->context->messageStreamEmptyWithFiltersCss; ?>">
<div class="panel">
<div class="panel-body">
@ -77,23 +68,10 @@ $this->registerJs($jsLoadWall, View::POS_READY);
</div>
</div>
<!-- DIV for an single wall entry -->
<div class="s2_single" style="display: none;">
<div class="back_button_holder">
<a href="#"
class="singleBackLink btn btn-primary"><?php echo Yii::t('ContentModule.widgets_views_stream', 'Back to stream'); ?></a><br><br>
</div>
<div class="p_border"></div>
<div class="s2_singleContent"></div>
<div class="loader streamLoaderSingle"></div>
<div class="test"></div>
</div>
</div>
<!-- show "Load More" button on mobile devices -->
<div class="col-md-12 text-center visible-xs visible-sm">
<button id="btn-load-more" class="btn btn-primary btn-lg ">Load more</button>
<br/><br/>
</div>
</div>