Commit 17341efd authored by Carsten  Rose's avatar Carsten Rose
Browse files

Merge branch 'thesisCodeCorrection' into 'master'

Thesis code correction

See merge request !71
parents 989ff133 1a4bae6c
Pipeline #889 passed with stage
in 1 minute and 49 seconds
/**
* @author Benjamin Baer <benjamin.baer@math.uzh.ch>
*/
/* global $ */
/* global EventEmitter */
/* @depend QfqEvents.js */
/* @depend Alert.js */
/* @depend Comment.js */
/* @depend CommentController */
/* @depend SyntaxHighlighter */
/**
* Qfq Namespace
*
* @namespace QfqNS
*/
var QfqNS = QfqNS || {};
(function (n) {
'use strict';
/**
* Displays Code in a stylized fashion and allows
* to write and display comments for each line of code.
*
* @param form Reference to the parent qfq Element
* @param data Object containing the to be displayed data
* @param $container Reference to the HTML Element that displays
* the code correction
* @param $target Reference to the HTML Element where the output (comment JSON)
* should be stored.
*/
n.CodeCorrection = function (form, data, $container, $target, currentUser, language) {
this.form = form;
this.data = data;
this.eventEmitter = new EventEmitter();
this.$parent = $container;
this.$target = $target;
this.$rows = [];
this.annotations = [];
this.users = [];
this.currentUser = currentUser;
this.language = language;
this.syntaxHighlight = {};
};
/**
* Initializes the Code Correction object, fetches data from URL or API if needed
*/
n.CodeCorrection.prototype.initialize = function() {
var that = this;
if (this.$target.val()) {
var jImport = $.parseJSON(this.$target.val());
if (jImport.annotations) {
this.annotations = jImport.annotations;
console.log("[CodeCorrection] Imported Annotations: " + this.annotations.length);
}
if (jImport.users) {
this.users = jImport.users;
console.log("[CodeCorrection] Imported Users: " + this.users.length);
}
}
if (this.data.url) {
// Get data of a file and write it to data.text
$.get(this.data.url, function(response) {
that.data.text = response;
that._prepareBuild();
});
} else if (this.data.text) {
this._prepareBuild();
} else {
console.error("[CodeCorrection] No Code to correct passed to the object.");
}
};
n.CodeCorrection.prototype._prepareBuild = function() {
var that = this;
this.syntaxHighlight = new n.SyntaxHighlighter();
this.syntaxHighlight.importInstructions(this.language, function() {
that._buildEditor();
});
};
/**
* Breaks up the String by line and returns it as an Array
* @param text Unix formatted text of the Code File
* @returns {Array} Array with the Code broken up by Line
*/
n.CodeCorrection.prototype.createLineByLineArray = function(text) {
var textArray = [];
if (typeof text === 'string' || text instanceof String) {
textArray = text.split("\n");
}
return textArray;
};
/**
* Builds the Code Correction HTML Element that should be displayed to the user.
* @private
*/
n.CodeCorrection.prototype._buildEditor = function() {
var that = this;
var $title = $('<div/>', {
class: 'qfqCodeCorrectionTitle'
});
$title.appendTo(this.$parent);
var container = $('<div/>', {
class: 'codeCorrectionWrap'
});
var lineCount = 1;
var textArray = this.createLineByLineArray(this.data.text);
textArray.forEach(function(line) {
that.$rows[lineCount] = that._buildLine(lineCount, line);
that.$rows[lineCount].appendTo(container);
lineCount++;
});
container.appendTo(this.$parent);
this._buildAnnotations();
};
/**
* Checks which codelines have annotations and initializes a CommentController
* for each of them.
* @private
*/
n.CodeCorrection.prototype._buildAnnotations = function() {
for (var i = 0; i < this.annotations.length; i++) {
var annotation = this.annotations[i];
var $hook = this.$rows[annotation.lineNumber];
var commentController = this._buildCommentContainer($hook);
commentController.importComments(annotation.comments, this.users);
this._setListeners(commentController);
this._setCommentController(annotation.lineNumber, commentController);
}
};
/**
* Builds a Line as a combination of HTML Elements. Binds the necessary Events.
*
* @param lineCount
* @param line
* @returns {jQuery|HTMLElement}
* @private
*/
n.CodeCorrection.prototype._buildLine = function(lineCount, line) {
var that = this;
var htmlRow = $('<div/>', {
class: 'clearfix qfqCodeLine',
id: 'qfqC' + lineCount
});
htmlRow.on("click", function() { that._handleClick(htmlRow, lineCount);});
var htmlLineNumber = $('<div/>', {
class: 'pull-left qfqLineCount',
text: lineCount
});
var cLine = line.replace(/\s/g, '&nbsp;')
.replace('<', '&lt;')
.replace('>', '&gt;');
cLine = this.syntaxHighlight.highlightLine(cLine);
var htmlCodeLine = $('<div/>', {
class: 'pull-right qfqCode'
});
htmlCodeLine.html(cLine);
htmlLineNumber.appendTo(htmlRow);
htmlCodeLine.appendTo(htmlRow);
return htmlRow;
};
/**
* Initializes a CommentContainer at a given jQuery Hook and returns
* the CommentContainer object
* @param $hook
* @returns {QfqNS.CommentController}
* @private
*/
n.CodeCorrection.prototype._buildCommentContainer = function($hook) {
var commentController = new n.CommentController();
commentController.buildContainer($hook);
commentController.setCurrentUser(this.currentUser);
return commentController;
};
/**
* References the CommentController in this.annotations Array
* for easy access later.
* @param lineCount
* @param commentController
* @returns {boolean}
* @private
*/
n.CodeCorrection.prototype._setCommentController = function(lineCount, commentController) {
for (var i=0; i < this.annotations.length; i++) {
if (this.annotations[i].lineNumber === lineCount) {
this.annotations[i].commentController = commentController;
return true;
}
}
return false;
};
/**
* Sets listeners for events generated by the CommentController object
* @param commentController
* @private
*/
n.CodeCorrection.prototype._setListeners = function(commentController) {
var that = this;
commentController.on('comment.added', function(argument) {
console.log("Catch event: " + that.annotations.length);
console.log("With data: " + argument.data);
});
};
/**
* Places a comment editor under the hook.
* @param $hook
* @param lineCount
* @private
*/
n.CodeCorrection.prototype._handleClick = function($hook, lineCount) {
var comments = {};
if (this._hasComments(lineCount)) {
comments = this._getComments(lineCount);
comments.commentController.toggle();
comments.commentController.emitEvent("new");
} else {
comments.lineNumber = lineCount;
comments.commentController = new n.CommentController();
comments.commentController.buildContainer($hook);
comments.commentController.displayEditor();
this._setListeners(comments.commentController);
this.annotations.push(comments);
}
};
/**
* Checks if a line already has comments
* @param lineCount
* @returns {boolean}
* @private
*/
n.CodeCorrection.prototype._hasComments = function(lineCount) {
for (var i=0; i < this.annotations.length; i++) {
if (this.annotations[i].lineNumber === lineCount) {
return true;
}
}
return false;
};
/**
* Gets comments for a specific line or returns false if said
* comments can't be found.
* @param lineCount
* @returns {*}
* @private
*/
n.CodeCorrection.prototype._getComments = function(lineCount) {
for (var i=0; i < this.annotations.length; i++) {
if (this.annotations[i].lineNumber === lineCount) {
return this.annotations[i];
}
}
return false;
};
})(QfqNS);
\ No newline at end of file
/**
* @author Benjamin Baer <benjamin.baer@math.uzh.ch>
*/
/* global $ */
/* global EventEmitter */
/* @depend QfqEvents.js */
/* @depend Alert.js */
/**
* Qfq Namespace
*
* @namespace QfqNS
*/
var QfqNS = QfqNS || {};
(function (n) {
'use strict';
/**
* Displays Comment or an Editor to create a comment
*
* https://www.quora.com/What-is-the-best-way-to-check-if-a-property-or-variable-is-undefined
*
*/
n.Comment = function (comment, user, $container, options) {
this.comment = comment;
this.user = user;
this.$parent = $container;
this.$comment = {};
if (arguments.length == 3) {
this.options = { readonly: false };
} else {
this.options = options;
}
this.childrenController = {};
};
n.Comment.prototype.display = function() {
var displayElement;
displayElement = this._buildComment();
displayElement.appendTo(this.$parent);
this.$comment = displayElement;
};
n.Comment.prototype.getParent = function() {
return this.$parent;
};
n.Comment.prototype.height = function() {
return this.$comment.height();
};
n.Comment.prototype._buildComment = function(allowEdit) {
var $commentWrap = $('<div/>', {
class: "qfqComment"
});
var $avatar = $('<img>', {
src: this.user.avatar,
class: "qfqCommentAvatar"
});
$avatar.appendTo($commentWrap);
var $topLine = $('<div />', {
class: "qfqCommentTopLine"
});
$('<span />', {
class: "qfqCommentAuthor",
text: this.user.name + ":"
}).appendTo($topLine);
$('<span />', {
class: "qfqCommentDateTime",
text: this.comment.dateTime
}).appendTo($topLine);
$topLine.appendTo($commentWrap);
var $comment = $('<div />', {
class: "qfqCommentText",
});
$comment.html(this.comment.comment);
var $commentCommands = $("<div />", {
class: "qfqCommentCommands"
});
var $reply = $('<span />', {
class: "glyphicon glyphicon-comment qfqCommentReply"
});
$reply.appendTo($commentCommands);
$commentCommands.appendTo($comment);
$comment.appendTo($commentWrap);
return $commentWrap;
};
})(QfqNS);
\ No newline at end of file
/**
* @author Benjamin Baer <benjamin.baer@math.uzh.ch>
*/
/* global $ */
/* global EventEmitter */
/* @depend QfqEvents.js */
/* @depend Alert.js */
/* @depend Comment.js */
/**
* Qfq Namespace
*
* @namespace QfqNS
*/
var QfqNS = QfqNS || {};
(function (n) {
'use strict';
/**
* Manages a group of comments
*
*/
n.CommentController = function () {
this.comments = [];
this.currentUser = {};
this.$container = {};
this.$parent = {};
this.height = "auto";
// Event Emitter is a Library qfq uses to emit custom Events.
this.eventEmitter = new EventEmitter();
};
n.CommentController.prototype.on = n.EventEmitter.onMixin;
n.CommentController.prototype.setCurrentUser = function(user) {
this.currentUser = user;
};
/**
* changeHandler emits custom events for actions.
* Additionally writes log entries to console for easier
* testing.
* @private
* @param event String containing possible change states
* @return {boolean} true on success
*/
n.CommentController.prototype._changeHandler = function(event) {
if (event === "edit") {
this.eventEmitter.emitEvent('comment.edited',
n.EventEmitter.makePayload(this, "edit"));
console.log("[CommentController] Event comment.edit emitted");
return true;
} else if (event === "new") {
this.eventEmitter.emitEvent('comment.added',
n.EventEmitter.makePayload(this, "add"));
console.log("[CommentController] Event comment.add emitted");
return true;
} else if (event === "remove") {
this.eventEmitter.emitEvent('comment.removed',
n.EventEmitter.makePayload(this, "remove"));
}
console.error("[CommentController] Changehandler called without valid event");
return false;
};
n.CommentController.prototype.emitEvent = function(event) {
this._changeHandler(event);
};
n.CommentController.prototype.buildContainer = function($hook) {
var $container = $("<div />", {
class: "qfqCommentContainer"
});
$hook.after($container);
this.$container = $container;
};
n.CommentController.prototype.hasComments = function() {
if (this.comments.length > 0) {
return true;
} else {
return false;
}
};
n.CommentController.prototype.toggle = function() {
this.$container.slideToggle("swing");
};
n.CommentController.prototype.getComment = function(reference) {
if (reference < this.comments.length && reference >= 0) {
return this.comments[reference];
} else {
console.error("[CommentController] Requested Comment doesn't exist");
return false;
}
};
n.CommentController.prototype.addComment = function(comment, user) {
var commentObject = new n.Comment(comment, user, this.$container);
commentObject.display();
this.comments.push(commentObject);
this.updateHeight();
return this.comments.length - 1;
};
n.CommentController.prototype.displayComments = function() {
for (var i = 0; this.comments; i++) {
this.comments[i].display();
}
this.updateHeight();
};
n.CommentController.prototype.displayEditor = function() {
var editor = new n.Editor();
var that = this;
var $editor = editor.buildEditor();
editor.on("editor.submit", function(editor) {
that._handleEditorSubmit(editor);
});
$editor.appendTo(this.$container);
};
n.CommentController.prototype._handleEditorSubmit = function(editor) {
var comment = this.buildCommentObject(editor.data.text);
this.addComment(comment, this.currentUser);
editor.data.destroy();
};
n.CommentController.prototype.buildCommentObject = function(text) {
var comment = {};
comment.comment = text;
comment.dateTime = new Date().toISOString();
comment.uid = this.currentUser.uid;
return comment;
};
n.CommentController.prototype.getContainer = function() {
return this.$container;
};
n.CommentController.prototype.removeComment = function(reference) {
};
n.CommentController.prototype.updateComment = function(reference, data) {
};
n.CommentController.prototype.updateHeight = function() {
//this.height = this.$container.height();
//this.$container.css("max-height", this.height);
};
n.CommentController.prototype.importComments = function(comments, users) {
for (var i=0; i < comments.length; i++) {
var user = this._searchUsersByUid(users, comments[i].uid);
this.addComment(comments[i], user);
}
this.displayEditor();
};
n.CommentController.prototype._searchUsersByUid = function (users, uid) {
for (var i=0; i < users.length; i++) {
if (users[i].uid === uid) {
return users[i];
}
}
};
})(QfqNS);
\ No newline at end of file
/**
* @author Benjamin Baer <benjamin.baer@math.uzh.ch>
*/
/* global $ */
/* global EventEmitter */
/* @depend QfqEvents.js */
/* @depend Alert.js */
/* @depend Comment.js */
/**
* Qfq Namespace
*
* @namespace QfqNS
*/
var QfqNS = QfqNS || {};
(function (n) {
'use strict';
/**
* Manages Text Editor for Comments
*
*/
n.Editor = function () {
this.$container = {};
this.$textArea = {};
this.$submitButton = {};
this.text = "";
// Event Emitter is a Library qfq uses to emit custom Events.
this.eventEmitter = new EventEmitter();
};
n.Editor.prototype.on = n.EventEmitter.onMixin;
n.Editor.prototype.buildEditor = function() {
var that = this;
this.$container = $("<div />", {
class: "qfqEditorContainer"
});
this.$textArea = $("<div />", {
class: "qfqEditor",
contenteditable: true
});
this.$textArea.keydown(function() { that.activateSubmit(); });
this._addEditorControls();
var controls = $("<div />", {
class: "qfqEditorControls"
});
var submitButton = $("<button />", {
class: "btn btn-primary",
disabled: true,
text: "Send"
});
submitButton.on("click", function() { that._handleClick();});
submitButton.appendTo(controls);
this.$submitButton = submitButton;
this.$textArea.appendTo(this.$container);
controls.appendTo(this.$container);
return this.$container;
};
n.Editor.prototype.activateSubmit = function() {
this.$submitButton.attr("disabled", false);
};