Index: lams_build/conf/etherpad/etherpad-lite/node_modules/ep_stats/static/css/stats.css =================================================================== diff -u --- lams_build/conf/etherpad/etherpad-lite/node_modules/ep_stats/static/css/stats.css (revision 0) +++ lams_build/conf/etherpad/etherpad-lite/node_modules/ep_stats/static/css/stats.css (revision 3cd4201e8c6d28d1539c24fe5d2efba5f680251e) @@ -0,0 +1,46 @@ +#stats { + display: none; + z-index: 99999; + position: fixed; + bottom: 0; + right: 30px; + width: 300px; + background-color: #fff; + color: #000; + padding: 20px; + overflow: auto; +} + +#stats .stats { + font-size: 14px; + display: inline; +} + +#stats h1 { + font-size: 16px; +} + +#stats h2 { + font-size: 14px; +} + +#stats>div>div { + line-height: 20px; + padding: 5px; +} + +#stats>div>div>h2 { + display: inline; +} + +#stats>div>div:nth-child(even) { + background: #CCC +} + +#stats>div>div:nth-child(odd) { + background: #FFF +} + +.statsAuthorColor { + width: 4px; +} \ No newline at end of file Index: lams_build/conf/etherpad/etherpad-lite/node_modules/ep_stats/static/js/stats.js =================================================================== diff -u --- lams_build/conf/etherpad/etherpad-lite/node_modules/ep_stats/static/js/stats.js (revision 0) +++ lams_build/conf/etherpad/etherpad-lite/node_modules/ep_stats/static/js/stats.js (revision 3cd4201e8c6d28d1539c24fe5d2efba5f680251e) @@ -0,0 +1,303 @@ +if(typeof exports == 'undefined'){ + var exports = this['mymodule'] = {}; +} + +exports.stats = { + init: function(){ + stats.update(); + }, + show: function(){ + $('#stats').show(); + $('#stats').css("top", $('#settings').offset().top + $('#settings').height() + 10 + 'px'); +// $('#options-stickychat').attr("checked","checked"); + exports.stats.update(); + }, + hide: function(){ + $('#stats').hide(); + }, + update: function(){ + var html = $('iframe[name="ace_outer"]').contents().find('iframe').contents().find("#innerdocbody").html(); + var text = $('iframe[name="ace_outer"]').contents().find('iframe').contents().find("#innerdocbody").text(); + $('#length > .stats').html( text.replace(/\s/g,"").length ); + $('#lengthWhitespace > .stats').html( text.length ); + $('#wordCount > .stats').html( exports.wordCount()); + $('#revCount > .stats').html( pad.getCollabRevisionNumber() ); + $('#savedRevCount > .stats').html( clientVars.savedRevisions.length ); // TODO cake doesnt update in real time + $('#authorCount > .stats').html( Object.keys(clientVars.collab_client_vars.historicalAuthorData).length ); + $('#wordsContributed > .stats').html( tb(exports.stats.authors.numberOfWords()) ); + $('#linesContributed > .stats').html( tb(exports.stats.authors.numberOfLines()) ); + $('#linesAsOnlyContributor > .stats').html( tb(exports.stats.authors.numberOfLinesExclusive()) ); + $('#numberOfCharsIncWS > .stats').html( tb(exports.stats.authors.numberOfChars()) ); + $('#numberOfCharsExcWS > .stats').html( tb(exports.stats.authors.numberOfCharsExcWS()) ); + } +} + + +exports.wordCount = function(){ + var totalCount = 0; + $('iframe[name="ace_outer"]').contents().find('iframe').contents().find("#innerdocbody").contents().each(function(){ + var lineCount = 0; + $(this).contents().each(function(){ + var numberOf = $(this).text().split(" "); + numberOf = arrayDelete(numberOf, ""); // dont include spaces or line breaks or other nastyness + lineCount += numberOf.length; + }); + totalCount += lineCount; + }); + return totalCount; +} + +exports.stats.authors = { + numberOfWords: function(){ + var results = {}; + // go through each word, does it have the class of this author? + // output format. John -- 6, Dave -- 9 + $('iframe[name="ace_outer"]').contents().find('iframe').contents().find("#innerdocbody").contents().each(function(){ + $(this).contents().each(function(){ + var line = this; + var classes = $(this).attr("class"); + if(classes){ + classes = classes.split(" "); + $.each(classes, function(k, spanClass){ + if( spanClass.indexOf("author") !== -1){ // if an author class exists on this span + // how many words are in this string? + var number = $(line).text().split(" ").length; + if( !results[ spanClass ] ){ + results[ spanClass ] = number; + }else{ + results[ spanClass ] = results[ spanClass ] + number; + } + } + }); + } + }); + }); + return results; + }, + numberOfLines: function(){ + var results = {}; + // output format. John -- 2, Dave -- 3 + $('iframe[name="ace_outer"]').contents().find('iframe').contents().find("#innerdocbody").contents().each(function(){ // each line + $(this).contents().each(function(){ + var line = this; + var classes = $(this).attr("class"); + if(classes){ + classes = classes.split(" "); + $.each(classes, function(k, spanClass){ + if( spanClass.indexOf("author") !== -1){ // if an author class exists on this span + // how many words are in this string? + var number = $(line).text().split(" ").length; + if( !results[ spanClass ] ){ + results[ spanClass ] = 1; + }else{ + results[ spanClass ] = results[ spanClass ] + 1; + } + } + }); + } + }); + + }); + return results; + + }, + numberOfLinesExclusive: function(){ + var results = {}; + var lineCount = 1; + // output format. John -- 1, Dave -- 1 + $('iframe[name="ace_outer"]').contents().find('iframe').contents().find("#innerdocbody").contents().each(function(){ // For Each LINE + var line = {}; + $(this).contents().each(function(){ // For SPAN! + var classes = $(this).attr("class"); + if(classes){ + classes = classes.split(" "); + $.each(classes, function(k, spanClass){ + if( spanClass.indexOf("author") !== -1){ // if an author class exists on this span + if( !line[ lineCount ] ){ + line[ lineCount ] = {}; + line[ lineCount ].author = spanClass; // first author! + }else{ + delete line[ lineCount ];// already has an author so nuke! + } + } + }); + + + } + // End Span + }); + var lineHasOneAuthor = (line[lineCount]) + if(lineHasOneAuthor){ + // add author to results obj + var author = line[lineCount].author; + results[author] = (results[author] +1) || 1 + } + lineCount++; + // End Line + }); + return results; + }, + numberOfChars: function(){ + var results = {}; + // output format. John -- 6, Dave -- 9 + $('iframe[name="ace_outer"]').contents().find('iframe').contents().find("#innerdocbody").contents().each(function(){ + $(this).contents().each(function(){ + var classes = $(this).attr("class"); + if(classes){ + classes = classes.split(" "); + var number = $(this).text().length; + $.each(classes, function(k, spanClass){ + if( spanClass.indexOf("author") !== -1){ // if an author class exists on this span + results[ spanClass ] = number; + }else{ + results[ spanClass ] = results [ spanClass] + 1; + } + }); + }; + }); + }); + return results; + }, + numberOfCharsExcWS: function(){ + var results = {}; + // output format. John -- 6, Dave -- 9 + $('iframe[name="ace_outer"]').contents().find('iframe').contents().find("#innerdocbody").contents().each(function(){ + $(this).contents().each(function(){ + var classes = $(this).attr("class"); + if(classes){ + + classes = classes.split(" "); + var number = $(this).text().replace(/\s/g,"").length; // get length without whitespace + $.each(classes, function(k, spanClass){ + if( classes.indexOf("author") !== -1){ // if an author class exists on this span + results[ spanClass ] = number; + }else{ + results[ spanClass ] = results[ spanClass] + number; + } + }); + } + }); + }); + return results; + } +} + +exports.postAceInit = function(hook, context){ + stats = exports.stats; + + /* on click */ + $('#options-stats').on('click', function() { + if($('#options-stats').is(':checked')) { + stats.show(); + } else { + stats.hide(); + } + }); +} + +exports.aceEditEvent = function(hook_name, event, cb){ + if($('#options-stats').is(':checked')) { // if stats are enabled + if(event.callstack.docTextChanged && event.callstack.domClean){ + exports.stats.update(); + } + } +} + +exports.className2Author = function(className) +{ + return className.substring(7).replace(/[a-y0-9]+|-|z.+?z/g, function(cc) + { + if (cc == '-') return '.'; + else if (cc.charAt(0) == 'z') + { + return String.fromCharCode(Number(cc.slice(1, -1))); + } + else + { + return cc; + } + }); +} + +exports.getAuthorClassName = function(author) +{ + return "ep_cursortrace-" + author.replace(/[^a-y0-9]/g, function(c) + { + if (c == ".") return "-"; + return 'z' + c.charCodeAt(0) + 'z'; + }); +} + +function tb(data){ // turns data object into a table + var table = ""; + for (var value in data){ + table += ""; + }; + table += "
"+authorNameFromClass(value)+":"+data[value]+"
"; + return table; +} + +function authorNameFromClass(authorClass){ // turn authorClass into authorID then authorname.. + // get the authorID from the class.. + var authorId = exports.className2Author(authorClass); + + // It could always be me.. + var myAuthorId = pad.myUserInfo.userId; + if(myAuthorId == authorId){ + return "Me"; + } + + // Not me, let's look up in the DOM + var name = null; + $('#otheruserstable > tbody > tr').each(function(){ + if (authorId == $(this).data("authorid")){ + $(this).find('.usertdname').each( function() { + name = $(this).text(); + }); + } + }); + if (name) + return name; + + // Else go historical + var historical = clientVars.collab_client_vars.historicalAuthorData[authorId]; + return (historical && historical.name) || "Unknown Author"; +} + +function authorColorFromClass(authorClass){ // turn authorClass into authorID then authorname.. + // get the authorID from the class.. + var authorId = exports.className2Author(authorClass); + + // It could always be me.. + var myAuthorId = pad.myUserInfo.userId; + if(myAuthorId == authorId){ + return "#fff"; + } + + // Not me, let's look up in the DOM + var color = null; + $('#otheruserstable > tbody > tr').each(function(){ + if (authorId == $(this).data("authorid")){ + $(this).find('.usertdswatch > div').each( function() { + color = $(this).css("background-color"); + }); + } + }); + if (color) + return color; + + // Else go historical + var historical = clientVars.collab_client_vars.historicalAuthorData[authorId]; + return (historical && historical.color) || "#fff"; +} + + +function arrayDelete(array, deleteValue) { + for (var i = 0; i < this.length; i++) { + if (this[i] == deleteValue) { + this.splice(i, 1); + i--; + } + } + return this; +}