Index: lams_tool_doku/conf/etherpad-lite/src/node/utils/Settings.js =================================================================== diff -u --- lams_tool_doku/conf/etherpad-lite/src/node/utils/Settings.js (revision 0) +++ lams_tool_doku/conf/etherpad-lite/src/node/utils/Settings.js (revision eeb7734f1d37604e1b9e14b4df3214d5cdd0338b) @@ -0,0 +1,444 @@ +/** + * The Settings Modul reads the settings out of settings.json and provides + * this information to the other modules + */ + +/* + * 2011 Peter 'Pita' Martischka (Primary Technology Ltd) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var fs = require("fs"); +var os = require("os"); +var path = require('path'); +var argv = require('./Cli').argv; +var npm = require("npm/lib/npm.js"); +var jsonminify = require("jsonminify"); +var log4js = require("log4js"); +var randomString = require("./randomstring"); +var suppressDisableMsg = " -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n"; +var _ = require("underscore"); + +/* Root path of the installation */ +exports.root = path.normalize(path.join(npm.dir, "..")); + +/** + * The app title, visible e.g. in the browser window + */ +exports.title = "Etherpad"; + +/** + * The app favicon fully specified url, visible e.g. in the browser window + */ +exports.favicon = "favicon.ico"; +exports.faviconPad = "../" + exports.favicon; +exports.faviconTimeslider = "../../" + exports.favicon; + +/** + * The IP ep-lite should listen to + */ +exports.ip = "0.0.0.0"; + +/** + * The Port ep-lite should listen to + */ +exports.port = process.env.PORT || 9001; + +/** + * Should we suppress Error messages from being in Pad Contents + */ +exports.suppressErrorsInPadText = false; + +/** + * The SSL signed server key and the Certificate Authority's own certificate + * default case: ep-lite does *not* use SSL. A signed server key is not required in this case. + */ +exports.ssl = false; + +/** + * socket.io transport methods + **/ +exports.socketTransportProtocols = ['xhr-polling', 'jsonp-polling', 'htmlfile']; + +/* + * The Type of the database + */ +exports.dbType = "dirty"; +/** + * This setting is passed with dbType to ueberDB to set up the database + */ +exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") }; + +/** + * The default Text of a new pad + */ +exports.defaultPadText = "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad on Github: https:\/\/github.com\/ether\/etherpad-lite\n"; + +/** + * The default Pad Settings for a user (Can be overridden by changing the setting + */ +exports.padOptions = { + "noColors": false, + "showControls": true, + "showChat": true, + "showLineNumbers": true, + "useMonospaceFont": false, + "userName": false, + "userColor": false, + "rtl": false, + "alwaysShowChat": false, + "chatAndUsers": false, + "lang": "en-gb" +} + +/** + * The toolbar buttons and order. + */ +exports.toolbar = { + left: [ + ["bold", "italic", "underline", "strikethrough"], + ["orderedlist", "unorderedlist", "indent", "outdent"], + ["undo", "redo"] + //*LAMS* commented out the following two lines + //, + //["clearauthorship"] + ], + right: [ + //*LAMS* commented out the following two lines + //["importexport", "timeslider", "savedrevision"], + //["settings", "embed"], + ["showusers"] + ], + timeslider: [ + ["timeslider_export", "timeslider_settings", "timeslider_returnToPad"] + ] +} + +/** + * A flag that requires any user to have a valid session (via the api) before accessing a pad + */ +exports.requireSession = false; + +/** + * A flag that prevents users from creating new pads + */ +exports.editOnly = false; + +/** + * A flag that bypasses password prompts for users with valid sessions + */ +exports.sessionNoPassword = false; + +/** + * Max age that responses will have (affects caching layer). + */ +exports.maxAge = 1000*60*60*6; // 6 hours + +/** + * A flag that shows if minification is enabled or not + */ +exports.minify = true; + +/** + * The path of the abiword executable + */ +exports.abiword = null; + +/** + * The path of the libreoffice executable + */ +exports.soffice = null; + +/** + * The path of the tidy executable + */ +exports.tidyHtml = null; + +/** + * Should we support none natively supported file types on import? + */ +exports.allowUnknownFileEnds = true; + +/** + * The log level of log4js + */ +exports.loglevel = "INFO"; + +/** + * Disable IP logging + */ +exports.disableIPlogging = false; + +/** + * Disable Load Testing + */ +exports.loadTest = false; + +/** + * Enable indentation on new lines + */ +exports.indentationOnNewLine = true; + +/* +* log4js appender configuration +*/ +exports.logconfig = { appenders: [{ type: "console" }]}; + +/* +* Session Key, do not sure this. +*/ +exports.sessionKey = false; + +/* +* Trust Proxy, whether or not trust the x-forwarded-for header. +*/ +exports.trustProxy = false; + +/* This setting is used if you need authentication and/or + * authorization. Note: /admin always requires authentication, and + * either authorization by a module, or a user with is_admin set */ +exports.requireAuthentication = false; +exports.requireAuthorization = false; +exports.users = {}; + +/* +* Show settings in admin page, by default it is true +*/ +exports.showSettingsInAdminPage = true; + +//checks if abiword is avaiable +exports.abiwordAvailable = function() +{ + if(exports.abiword != null) + { + return os.type().indexOf("Windows") != -1 ? "withoutPDF" : "yes"; + } + else + { + return "no"; + } +}; + +exports.sofficeAvailable = function () { + if(exports.soffice != null) { + return os.type().indexOf("Windows") != -1 ? "withoutPDF": "yes"; + } else { + return "no"; + } +}; + +exports.exportAvailable = function () { + var abiword = exports.abiwordAvailable(); + var soffice = exports.sofficeAvailable(); + + if(abiword == "no" && soffice == "no") { + return "no"; + } else if ((abiword == "withoutPDF" && soffice == "no") || (abiword == "no" && soffice == "withoutPDF")) { + return "withoutPDF"; + } else { + return "yes"; + } +}; + +// Provide git version if available +exports.getGitCommit = function() { + var version = ""; + try + { + var rootPath = path.resolve(npm.dir, '..'); + if (fs.lstatSync(rootPath + '/.git').isFile()) { + rootPath = fs.readFileSync(rootPath + '/.git', "utf8"); + rootPath = rootPath.split(' ').pop().trim(); + } else { + rootPath += '/.git'; + } + var ref = fs.readFileSync(rootPath + "/HEAD", "utf-8"); + var refPath = rootPath + "/" + ref.substring(5, ref.indexOf("\n")); + version = fs.readFileSync(refPath, "utf-8"); + version = version.substring(0, 7); + } + catch(e) + { + console.warn("Can't get git version for server header\n" + e.message) + } + return version; +} + +// Return etherpad version from package.json +exports.getEpVersion = function() { + return require('ep_etherpad-lite/package.json').version; +} + +exports.reloadSettings = function reloadSettings() { + // Discover where the settings file lives + var settingsFilename = argv.settings || "settings.json"; + + // Discover if a credential file exists + var credentialsFilename = argv.credentials || "credentials.json"; + + if (path.resolve(settingsFilename)===settingsFilename) { + settingsFilename = path.resolve(settingsFilename); + } else { + settingsFilename = path.resolve(path.join(exports.root, settingsFilename)); + } + + if (path.resolve(credentialsFilename)===credentialsFilename) { + credentialsFilename = path.resolve(credentialsFilename); + } + + var settingsStr, credentialsStr; + try{ + //read the settings sync + settingsStr = fs.readFileSync(settingsFilename).toString(); + } catch(e){ + console.warn('No settings file found. Continuing using defaults!'); + } + + try{ + //read the credentials sync + credentialsStr = fs.readFileSync(credentialsFilename).toString(); + } catch(e){ + // Doesn't matter if no credentials file found.. + } + + // try to parse the settings + var settings; + var credentials; + try { + if(settingsStr) { + settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}"); + settings = JSON.parse(settingsStr); + } + }catch(e){ + console.error('There was an error processing your settings.json file: '+e.message); + process.exit(1); + } + + if(credentialsStr) { + credentialsStr = jsonminify(credentialsStr).replace(",]","]").replace(",}","}"); + credentials = JSON.parse(credentialsStr); + } + + //loop trough the settings + for(var i in settings) + { + //test if the setting start with a low character + if(i.charAt(0).search("[a-z]") !== 0) + { + console.warn("Settings should start with a low character: '" + i + "'"); + } + + //we know this setting, so we overwrite it + //or it's a settings hash, specific to a plugin + if(exports[i] !== undefined || i.indexOf('ep_')==0) + { + if (_.isObject(settings[i]) && !_.isArray(settings[i])) { + exports[i] = _.defaults(settings[i], exports[i]); + } else { + exports[i] = settings[i]; + } + } + //this setting is unkown, output a warning and throw it away + else + { + console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed"); + } + } + + //loop trough the settings + for(var i in credentials) + { + //test if the setting start with a low character + if(i.charAt(0).search("[a-z]") !== 0) + { + console.warn("Settings should start with a low character: '" + i + "'"); + } + + //we know this setting, so we overwrite it + //or it's a settings hash, specific to a plugin + if(exports[i] !== undefined || i.indexOf('ep_')==0) + { + if (_.isObject(credentials[i]) && !_.isArray(credentials[i])) { + exports[i] = _.defaults(credentials[i], exports[i]); + } else { + exports[i] = credentials[i]; + } + } + //this setting is unkown, output a warning and throw it away + else + { + console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed"); + } + } + + log4js.configure(exports.logconfig);//Configure the logging appenders + log4js.setGlobalLogLevel(exports.loglevel);//set loglevel + process.env['DEBUG'] = 'socket.io:' + exports.loglevel; // Used by SocketIO for Debug + log4js.replaceConsole(); + + if(exports.abiword){ + // Check abiword actually exists + if(exports.abiword != null) + { + fs.exists(exports.abiword, function(exists) { + if (!exists) { + var abiwordError = "Abiword does not exist at this path, check your settings file"; + if(!exports.suppressErrorsInPadText){ + exports.defaultPadText = exports.defaultPadText + "\nError: " + abiwordError + suppressDisableMsg; + } + console.error(abiwordError); + exports.abiword = null; + } + }); + } + } + + if(exports.soffice) { + fs.exists(exports.soffice, function (exists) { + if(!exists) { + var sofficeError = "SOffice does not exist at this path, check your settings file"; + + if(!exports.suppressErrorsInPadText) { + exports.defaultPadText = exports.defaultPadText + "\nError: " + sofficeError + suppressDisableMsg; + } + console.error(sofficeError); + exports.soffice = null; + } + }); + } + + if (!exports.sessionKey) { + try { + exports.sessionKey = fs.readFileSync("./SESSIONKEY.txt","utf8"); + } catch(e) { + exports.sessionKey = randomString(32); + fs.writeFileSync("./SESSIONKEY.txt",exports.sessionKey,"utf8"); + } + } else { + console.warn("Declaring the sessionKey in the settings.json is deprecated. This value is auto-generated now. Please remove the setting from the file."); + } + + if(exports.dbType === "dirty"){ + var dirtyWarning = "DirtyDB is used. This is fine for testing but not recommended for production."; + if(!exports.suppressErrorsInPadText){ + exports.defaultPadText = exports.defaultPadText + "\nWarning: " + dirtyWarning + suppressDisableMsg; + } + console.warn(dirtyWarning); + } +}; + +// initially load settings +exports.reloadSettings(); + + Index: lams_tool_doku/conf/etherpad-lite/src/static/js/pad_userlist.js =================================================================== diff -u --- lams_tool_doku/conf/etherpad-lite/src/static/js/pad_userlist.js (revision 0) +++ lams_tool_doku/conf/etherpad-lite/src/static/js/pad_userlist.js (revision eeb7734f1d37604e1b9e14b4df3214d5cdd0338b) @@ -0,0 +1,854 @@ +/** + * This code is mostly from the old Etherpad. Please help us to comment this code. + * This helps other people to understand this code better and helps them to improve it. + * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED + */ + +/** + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var padutils = require('./pad_utils').padutils; +var hooks = require('./pluginfw/hooks'); + +var myUserInfo = {}; + +var colorPickerOpen = false; +var colorPickerSetup = false; +var previousColorId = 0; + + +var paduserlist = (function() +{ + + var rowManager = (function() + { + // The row manager handles rendering rows of the user list and animating + // their insertion, removal, and reordering. It manipulates TD height + // and TD opacity. + + function nextRowId() + { + return "usertr" + (nextRowId.counter++); + } + nextRowId.counter = 1; + // objects are shared; fields are "domId","data","animationStep" + var rowsFadingOut = []; // unordered set + var rowsFadingIn = []; // unordered set + var rowsPresent = []; // in order + var ANIMATION_START = -12; // just starting to fade in + var ANIMATION_END = 12; // just finishing fading out + + + function getAnimationHeight(step, power) + { + var a = Math.abs(step / 12); + if (power == 2) a = a * a; + else if (power == 3) a = a * a * a; + else if (power == 4) a = a * a * a * a; + else if (power >= 5) a = a * a * a * a * a; + return Math.round(26 * (1 - a)); + } + var OPACITY_STEPS = 6; + + var ANIMATION_STEP_TIME = 20; + var LOWER_FRAMERATE_FACTOR = 2; + var scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR).scheduleAnimation; + + var NUMCOLS = 4; + + // we do lots of manipulation of table rows and stuff that JQuery makes ok, despite + // IE's poor handling when manipulating the DOM directly. + + function getEmptyRowHtml(height) + { + return ''; + } + + function isNameEditable(data) + { + return (!data.name) && (data.status != 'Disconnected'); + } + + function replaceUserRowContents(tr, height, data) + { + var tds = getUserRowHtml(height, data).match(//gi); + if (isNameEditable(data) && tr.find("td.usertdname input:enabled").length > 0) + { + // preserve input field node + for (var i = 0; i < tds.length; i++) + { + var oldTd = $(tr.find("td").get(i)); + if (!oldTd.hasClass('usertdname')) + { + oldTd.replaceWith(tds[i]); + } + } + } + else + { + tr.html(tds.join('')); + } + return tr; + } + + function getUserRowHtml(height, data) + { + var nameHtml; + if (data.name) + { + nameHtml = padutils.escapeHtml(data.name); + } + else + { + nameHtml = ''; + } + + return ['
 
', '', nameHtml, '', '', padutils.escapeHtml(data.activity), ''].join(''); + } + + function getRowHtml(id, innerHtml, authorId) + { + return '' + innerHtml + ''; + } + + function rowNode(row) + { + return $("#" + row.domId); + } + + function handleRowData(row) + { + if (row.data && row.data.status == 'Disconnected') + { + row.opacity = 0.5; + } + else + { + delete row.opacity; + } + } + + function handleRowNode(tr, data) + { + if (data.titleText) + { + var titleText = data.titleText; + window.setTimeout(function() + { + /* tr.attr('title', titleText)*/ + }, 0); + } + else + { + tr.removeAttr('title'); + } + } + + function handleOtherUserInputs() + { + // handle 'INPUT' elements for naming other unnamed users + $("#otheruserstable input.newinput").each(function() + { + var input = $(this); + var tr = input.closest("tr"); + if (tr.length > 0) + { + var index = tr.parent().children().index(tr); + if (index >= 0) + { + var userId = rowsPresent[index].data.id; + rowManagerMakeNameEditor($(this), userId); + } + } + }).removeClass('newinput'); + } + + // animationPower is 0 to skip animation, 1 for linear, 2 for quadratic, etc. + + + function insertRow(position, data, animationPower) + { + position = Math.max(0, Math.min(rowsPresent.length, position)); + animationPower = (animationPower === undefined ? 4 : animationPower); + + var domId = nextRowId(); + var row = { + data: data, + animationStep: ANIMATION_START, + domId: domId, + animationPower: animationPower + }; + var authorId = data.id; + + handleRowData(row); + rowsPresent.splice(position, 0, row); + var tr; + if (animationPower == 0) + { + tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data), authorId)); + row.animationStep = 0; + } + else + { + rowsFadingIn.push(row); + tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START)), authorId)); + } + handleRowNode(tr, data); + if (position == 0) + { + $("table#otheruserstable").prepend(tr); + } + else + { + rowNode(rowsPresent[position - 1]).after(tr); + } + + if (animationPower != 0) + { + scheduleAnimation(); + } + + handleOtherUserInputs(); + + return row; + } + + function updateRow(position, data) + { + var row = rowsPresent[position]; + if (row) + { + row.data = data; + handleRowData(row); + if (row.animationStep == 0) + { + // not currently animating + var tr = rowNode(row); + replaceUserRowContents(tr, getAnimationHeight(0), row.data).find("td").css('opacity', (row.opacity === undefined ? 1 : row.opacity)); + handleRowNode(tr, data); + handleOtherUserInputs(); + } + } + } + + function removeRow(position, animationPower) + { + animationPower = (animationPower === undefined ? 4 : animationPower); + var row = rowsPresent[position]; + if (row) + { + rowsPresent.splice(position, 1); // remove + if (animationPower == 0) + { + rowNode(row).remove(); + } + else + { + row.animationStep = -row.animationStep; // use symmetry + row.animationPower = animationPower; + rowsFadingOut.push(row); + scheduleAnimation(); + } + } + } + + // newPosition is position after the row has been removed + + + function moveRow(oldPosition, newPosition, animationPower) + { + animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best + var row = rowsPresent[oldPosition]; + if (row && oldPosition != newPosition) + { + var rowData = row.data; + removeRow(oldPosition, animationPower); + insertRow(newPosition, rowData, animationPower); + } + } + + function animateStep() + { + // animation must be symmetrical + for (var i = rowsFadingIn.length - 1; i >= 0; i--) + { // backwards to allow removal + var row = rowsFadingIn[i]; + var step = ++row.animationStep; + var animHeight = getAnimationHeight(step, row.animationPower); + var node = rowNode(row); + var baseOpacity = (row.opacity === undefined ? 1 : row.opacity); + if (step <= -OPACITY_STEPS) + { + node.find("td").height(animHeight); + } + else if (step == -OPACITY_STEPS + 1) + { + node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1 / OPACITY_STEPS); + handleRowNode(node, row.data); + } + else if (step < 0) + { + node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - (-step)) / OPACITY_STEPS).height(animHeight); + } + else if (step == 0) + { + // set HTML in case modified during animation + node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1).height(animHeight); + handleRowNode(node, row.data); + rowsFadingIn.splice(i, 1); // remove from set + } + } + for (var i = rowsFadingOut.length - 1; i >= 0; i--) + { // backwards to allow removal + var row = rowsFadingOut[i]; + var step = ++row.animationStep; + var node = rowNode(row); + var animHeight = getAnimationHeight(step, row.animationPower); + var baseOpacity = (row.opacity === undefined ? 1 : row.opacity); + if (step < OPACITY_STEPS) + { + node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - step) / OPACITY_STEPS).height(animHeight); + } + else if (step == OPACITY_STEPS) + { + node.html(getEmptyRowHtml(animHeight)); + } + else if (step <= ANIMATION_END) + { + node.find("td").height(animHeight); + } + else + { + rowsFadingOut.splice(i, 1); // remove from set + node.remove(); + } + } + + handleOtherUserInputs(); + + return (rowsFadingIn.length > 0) || (rowsFadingOut.length > 0); // is more to do + } + + var self = { + insertRow: insertRow, + removeRow: removeRow, + moveRow: moveRow, + updateRow: updateRow + }; + return self; + }()); ////////// rowManager + var otherUsersInfo = []; + var otherUsersData = []; + + function rowManagerMakeNameEditor(jnode, userId) + { + setUpEditable(jnode, function() + { + var existingIndex = findExistingIndex(userId); + if (existingIndex >= 0) + { + return otherUsersInfo[existingIndex].name || ''; + } + else + { + return ''; + } + }, function(newName) + { + if (!newName) + { + jnode.addClass("editempty"); + jnode.val(_('pad.userlist.unnamed')); + } + else + { + jnode.attr('disabled', 'disabled'); + pad.suggestUserName(userId, newName); + } + }); + } + + function findExistingIndex(userId) + { + var existingIndex = -1; + for (var i = 0; i < otherUsersInfo.length; i++) + { + if (otherUsersInfo[i].userId == userId) + { + existingIndex = i; + break; + } + } + return existingIndex; + } + + function setUpEditable(jqueryNode, valueGetter, valueSetter) + { + jqueryNode.bind('focus', function(evt) + { + var oldValue = valueGetter(); + if (jqueryNode.val() !== oldValue) + { + jqueryNode.val(oldValue); + } + jqueryNode.addClass("editactive").removeClass("editempty"); + }); + jqueryNode.bind('blur', function(evt) + { + var newValue = jqueryNode.removeClass("editactive").val(); + valueSetter(newValue); + }); + padutils.bindEnterAndEscape(jqueryNode, function onEnter() + { + jqueryNode.blur(); + }, function onEscape() + { + jqueryNode.val(valueGetter()).blur(); + }); + jqueryNode.removeAttr('disabled').addClass('editable'); + } + + function updateInviteNotice() + { + if (otherUsersInfo.length == 0) + { + $("#otheruserstable").hide(); + $("#nootherusers").show(); + } + else + { + $("#nootherusers").hide(); + $("#otheruserstable").show(); + } + } + + var knocksToIgnore = {}; + var guestPromptFlashState = 0; + var guestPromptFlash = padutils.makeAnimationScheduler( + + function() + { + var prompts = $("#guestprompts .guestprompt"); + if (prompts.length == 0) + { + return false; // no more to do + } + + guestPromptFlashState = 1 - guestPromptFlashState; + if (guestPromptFlashState) + { + prompts.css('background', '#ffa'); + } + else + { + prompts.css('background', '#ffe'); + } + + return true; + }, 1000); + + var pad = undefined; + var self = { + init: function(myInitialUserInfo, _pad) + { + pad = _pad; + + self.setMyUserInfo(myInitialUserInfo); + + if($('#online_count').length === 0) $('#editbar [data-key=showusers] > a').append('1'); + + $("#otheruserstable tr").remove(); + + //*LAMS* commented out the following paragraph + /* + if (pad.getUserIsGuest()) + { + $("#myusernameedit").addClass('myusernameedithoverable'); + setUpEditable($("#myusernameedit"), function() + { + return myUserInfo.name || ''; + }, function(newValue) + { + myUserInfo.name = newValue; + pad.notifyChangeName(newValue); + // wrap with setTimeout to do later because we get + // a double "blur" fire in IE... + window.setTimeout(function() + { + self.renderMyUserInfo(); + }, 0); + }); + } + */ + + // color picker + $("#myswatchbox").click(showColorPicker); + $("#mycolorpicker .pickerswatchouter").click(function() + { + $("#mycolorpicker .pickerswatchouter").removeClass('picked'); + $(this).addClass('picked'); + }); + $("#mycolorpickersave").click(function() + { + closeColorPicker(true); + }); + $("#mycolorpickercancel").click(function() + { + closeColorPicker(false); + }); + // + }, + usersOnline: function() + { + // Returns an object of users who are currently online on this pad + var userList = [].concat(otherUsersInfo); // Make a copy of the otherUsersInfo, otherwise every call to users modifies the referenced array + // Now we need to add ourselves.. + userList.push(myUserInfo); + return userList; + }, + users: function(){ + // Returns an object of users who have been on this pad + var userList = self.usersOnline(); + + // Now we add historical authors + var historical = clientVars.collab_client_vars.historicalAuthorData; + for (var key in historical){ + var userId = historical[key].userId; + // Check we don't already have this author in our array + var exists = false; + + userList.forEach(function(user){ + if(user.userId === userId) exists = true; + }); + + if(exists === false){ + userList.push(historical[key]); + } + } + return userList; + }, + setMyUserInfo: function(info) + { + //translate the colorId + if(typeof info.colorId == "number") + { + info.colorId = clientVars.colorPalette[info.colorId]; + } + + myUserInfo = $.extend( + {}, info); + + self.renderMyUserInfo(); + }, + userJoinOrUpdate: function(info) + { + if ((!info.userId) || (info.userId == myUserInfo.userId)) + { + // not sure how this would happen + return; + } + + hooks.callAll('userJoinOrUpdate', { + userInfo: info + }); + + var userData = {}; + userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId; + userData.name = info.name; + userData.status = ''; + userData.activity = ''; + userData.id = info.userId; + // Firefox ignores \n in title text; Safari does a linebreak + userData.titleText = [info.userAgent || '', info.ip || ''].join(' \n'); + + var existingIndex = findExistingIndex(info.userId); + + var numUsersBesides = otherUsersInfo.length; + if (existingIndex >= 0) + { + numUsersBesides--; + } + var newIndex = padutils.binarySearch(numUsersBesides, function(n) + { + if (existingIndex >= 0 && n >= existingIndex) + { + // pretend existingIndex isn't there + n++; + } + var infoN = otherUsersInfo[n]; + var nameN = (infoN.name || '').toLowerCase(); + var nameThis = (info.name || '').toLowerCase(); + var idN = infoN.userId; + var idThis = info.userId; + return (nameN > nameThis) || (nameN == nameThis && idN > idThis); + }); + + if (existingIndex >= 0) + { + // update + if (existingIndex == newIndex) + { + otherUsersInfo[existingIndex] = info; + otherUsersData[existingIndex] = userData; + rowManager.updateRow(existingIndex, userData); + } + else + { + otherUsersInfo.splice(existingIndex, 1); + otherUsersData.splice(existingIndex, 1); + otherUsersInfo.splice(newIndex, 0, info); + otherUsersData.splice(newIndex, 0, userData); + rowManager.updateRow(existingIndex, userData); + rowManager.moveRow(existingIndex, newIndex); + } + } + else + { + otherUsersInfo.splice(newIndex, 0, info); + otherUsersData.splice(newIndex, 0, userData); + rowManager.insertRow(newIndex, userData); + } + + updateInviteNotice(); + + self.updateNumberOfOnlineUsers(); + }, + updateNumberOfOnlineUsers: function() + { + var online = 1; // you are always online! + for (var i = 0; i < otherUsersData.length; i++) + { + if (otherUsersData[i].status == "") + { + online++; + } + } + + $('#online_count').text(online); + + return online; + }, + userLeave: function(info) + { + var existingIndex = findExistingIndex(info.userId); + if (existingIndex >= 0) + { + var userData = otherUsersData[existingIndex]; + userData.status = 'Disconnected'; + rowManager.updateRow(existingIndex, userData); + if (userData.leaveTimer) + { + window.clearTimeout(userData.leaveTimer); + } + // set up a timer that will only fire if no leaves, + // joins, or updates happen for this user in the + // next N seconds, to remove the user from the list. + var thisUserId = info.userId; + var thisLeaveTimer = window.setTimeout(function() + { + var newExistingIndex = findExistingIndex(thisUserId); + if (newExistingIndex >= 0) + { + var newUserData = otherUsersData[newExistingIndex]; + if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer) + { + otherUsersInfo.splice(newExistingIndex, 1); + otherUsersData.splice(newExistingIndex, 1); + rowManager.removeRow(newExistingIndex); + hooks.callAll('userLeave', { + userInfo: info + }); + updateInviteNotice(); + } + } + }, 8000); // how long to wait + userData.leaveTimer = thisLeaveTimer; + } + updateInviteNotice(); + + self.updateNumberOfOnlineUsers(); + }, + showGuestPrompt: function(userId, displayName) + { + if (knocksToIgnore[userId]) + { + return; + } + + var encodedUserId = padutils.encodeUserId(userId); + + var actionName = 'hide-guest-prompt-' + encodedUserId; + padutils.cancelActions(actionName); + + var box = $("#guestprompt-" + encodedUserId); + if (box.length == 0) + { + // make guest prompt box + box = $('
'+_('pad.userlist.guest')+': ' + padutils.escapeHtml(displayName) + '
'); + $("#guestprompts").append(box); + } + else + { + // update display name + box.find(".guestname").html(''+_('pad.userlist.guest')+': ' + padutils.escapeHtml(displayName)); + } + var hideLater = padutils.getCancellableAction(actionName, function() + { + self.removeGuestPrompt(userId); + }); + window.setTimeout(hideLater, 15000); // time-out with no knock + guestPromptFlash.scheduleAnimation(); + }, + removeGuestPrompt: function(userId) + { + var box = $("#guestprompt-" + padutils.encodeUserId(userId)); + // remove ID now so a new knock by same user gets new, unfaded box + box.removeAttr('id').fadeOut("fast", function() + { + box.remove(); + }); + + knocksToIgnore[userId] = true; + window.setTimeout(function() + { + delete knocksToIgnore[userId]; + }, 5000); + }, + answerGuestPrompt: function(encodedUserId, approve) + { + var guestId = padutils.decodeUserId(encodedUserId); + + var msg = { + type: 'guestanswer', + authId: pad.getUserId(), + guestId: guestId, + answer: (approve ? "approved" : "denied") + }; + pad.sendClientMessage(msg); + + self.removeGuestPrompt(guestId); + }, + renderMyUserInfo: function() + { + if (myUserInfo.name) + { + $("#myusernameedit").removeClass("editempty").val(myUserInfo.name); + } + else + { + $("#myusernameedit").addClass("editempty").val(_("pad.userlist.entername")); + } + if (colorPickerOpen) + { + $("#myswatchbox").addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable'); + } + else + { + $("#myswatchbox").addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable'); + } + + $("#myswatch").css({'background-color': myUserInfo.colorId}); + + if (browser.msie && parseInt(browser.version) <= 8) { + $("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId,'background-color': myUserInfo.colorId}); + } + else + { + $("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId}); + } + } + }; + return self; +}()); + +function getColorPickerSwatchIndex(jnode) +{ + // return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1; + return $("#colorpickerswatches li").index(jnode); +} + +function closeColorPicker(accept) +{ + if (accept) + { + var newColor = $("#mycolorpickerpreview").css("background-color"); + var parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); + // parts now should be ["rgb(0, 70, 255", "0", "70", "255"] + if (parts) { + delete (parts[0]); + for (var i = 1; i <= 3; ++i) { + parts[i] = parseInt(parts[i]).toString(16); + if (parts[i].length == 1) parts[i] = '0' + parts[i]; + } + var newColor = "#" +parts.join(''); // "0070ff" + } + myUserInfo.colorId = newColor; + pad.notifyChangeColor(newColor); + paduserlist.renderMyUserInfo(); + } + else + { + //pad.notifyChangeColor(previousColorId); + //paduserlist.renderMyUserInfo(); + } + + colorPickerOpen = false; + $("#mycolorpicker").fadeOut("fast"); +} + +function showColorPicker() +{ + previousColorId = myUserInfo.colorId; + + if (!colorPickerOpen) + { + var palette = pad.getColorPalette(); + + if (!colorPickerSetup) + { + var colorsList = $("#colorpickerswatches") + for (var i = 0; i < palette.length; i++) + { + + var li = $('
  • ', { + style: 'background: ' + palette[i] + ';' + }); + + li.appendTo(colorsList); + + li.bind('click', function(event) + { + $("#colorpickerswatches li").removeClass('picked'); + $(event.target).addClass("picked"); + + var newColorId = getColorPickerSwatchIndex($("#colorpickerswatches .picked")); + pad.notifyChangeColor(newColorId); + }); + + } + + colorPickerSetup = true; + } + + $("#mycolorpicker").fadeIn(); + colorPickerOpen = true; + + $("#colorpickerswatches li").removeClass('picked'); + $($("#colorpickerswatches li")[myUserInfo.colorId]).addClass("picked"); //seems weird + } +} + +exports.paduserlist = paduserlist; Index: lams_tool_doku/conf/language/lams/ApplicationResources_en_AU.properties =================================================================== diff -u -r3135ffa870d30b00e33204b2ab83954665ce8d61 -reeb7734f1d37604e1b9e14b4df3214d5cdd0338b --- lams_tool_doku/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 3135ffa870d30b00e33204b2ab83954665ce8d61) +++ lams_tool_doku/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision eeb7734f1d37604e1b9e14b4df3214d5cdd0338b) @@ -57,8 +57,8 @@ pageTitle.admin =Dokumaran settings label.select.leader =Leader selection label.use.select.leader.tool.output =Use leaders from Select Leader tool -label.show.chat =Show the line numbers -label.show.line.numbers =Show the chat button +label.show.chat =Show the chat button +label.show.line.numbers =Show the line numbers admin.formError =Missing values, please make sure all required fields are entered. admin.etherpad.url =Etherpad server URL admin.apiKey =API Key @@ -73,5 +73,8 @@ label.refresh =Refresh label.shared.pad.id =Shared pad id that allows using the same pad between several doKumaran activities label.allow.multiple.leaders =All leaders participate together (applicable only for non-grouped doKumaran activities) +label.timeslider =Timeslider +label.export.pad.html =Export current pad as HTML +label.manage.faulty.pads =Pads with issues #======= End labels: Exported 164 labels for en AU ===== Index: lams_tool_doku/readme.txt =================================================================== diff -u --- lams_tool_doku/readme.txt (revision 0) +++ lams_tool_doku/readme.txt (revision eeb7734f1d37604e1b9e14b4df3214d5cdd0338b) @@ -0,0 +1,3 @@ +Modifications required to be done for Etherpad server. Copy two files: +1. /lams_tool_doku/conf/etherpad-lite/src/node/utils/Settings.js to /${etherpad-lite-server-folder}/src/node/utils/Settings.js +2. /lams_tool_doku/conf/etherpad-lite/src/static/js/pad_userlist.js to /${etherpad-lite-server-folder}/src/static/js/pad_userlist.js \ No newline at end of file Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/DokumaranService.java =================================================================== diff -u -r3135ffa870d30b00e33204b2ab83954665ce8d61 -reeb7734f1d37604e1b9e14b4df3214d5cdd0338b --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/DokumaranService.java (.../DokumaranService.java) (revision 3135ffa870d30b00e33204b2ab83954665ce8d61) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/service/DokumaranService.java (.../DokumaranService.java) (revision eeb7734f1d37604e1b9e14b4df3214d5cdd0338b) @@ -32,6 +32,8 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.servlet.http.Cookie; @@ -404,7 +406,32 @@ // DokumaranSession session = dokumaranSessionDao.getSessionBySessionId(toolSessionId); // session.setStatus(DokumaranConstants.COMPLETED); // dokumaranSessionDao.saveObject(session); + + //finish Etherpad session. Encapsulate it in try-catch block as we don't want it to affect regular LAMS workflow. + try { + EPLiteClient client = initializeEPLiteClient(); + DokumaranSession session = dokumaranSessionDao.getSessionBySessionId(toolSessionId); + String groupId = session.getEtherpadGroupId(); + + String userName = user.getFirstName() + " " + user.getLastName(); + Map map = client.createAuthorIfNotExistsFor(user.getUserId().toString(), userName); + String authorId = map.get("authorID"); + + // search for already existing user's session at Etherpad server + Map sessionsMap = client.listSessionsOfAuthor(authorId); + for (String sessionId : (Set) sessionsMap.keySet()) { + Map sessessionAttributes = (Map) sessionsMap.get(sessionId); + String groupIdIter = sessessionAttributes.get("groupID"); + if (groupIdIter.equals(groupId)) { + client.deleteSession(sessionId); + break; + } + } + } catch (DokumaranConfigurationException e1) { + log.debug(e1.getMessage()); + } + String nextUrl = null; try { nextUrl = this.leaveToolSession(toolSessionId, userId); @@ -840,19 +867,7 @@ userSessionId = (String) map2.get("sessionID"); } - DokumaranConfigItem etherpadServerUrlConfig = getConfigItem(DokumaranConfigItem.KEY_ETHERPAD_URL); - String etherpadServerUrl = etherpadServerUrlConfig.getConfigValue(); - URI uri = new URI(etherpadServerUrl); - String domain = uri.getHost(); - - Cookie etherpadSessionCookie = new Cookie("sessionID", userSessionId); - etherpadSessionCookie.setDomain(domain); - // A negative value means that the cookie is not stored persistently and will be deleted when the Web browser - // exits. A zero value causes the cookie to be deleted. - etherpadSessionCookie.setMaxAge(-1); - etherpadSessionCookie.setPath("/"); - - return etherpadSessionCookie; + return createEtherpadCookie(userSessionId); } @Override @@ -874,8 +889,8 @@ // in case sharedPadId is present - all sessions will share the same padId - and thus show only one pad Dokumaran dokumaran = getDokumaranByContentId(contentId); - if (StringUtils.isEmpty(dokumaran.getSharedPadId())) { - sessionList = sessionList.subList(0, 0); + if (dokumaran.isSharedPadEnabled()) { + sessionList = sessionList.subList(0, 1); } // find according session @@ -903,19 +918,34 @@ etherpadSessionIds += StringUtils.isEmpty(etherpadSessionIds) ? userSessionId : "," + userSessionId; } + return createEtherpadCookie(etherpadSessionIds); + } + + /** + * Constructs cookie to be stored at a clientside browser. + * + * @param etherpadSessionIds + * @return + * @throws URISyntaxException + */ + private Cookie createEtherpadCookie(String etherpadSessionIds) throws URISyntaxException { DokumaranConfigItem etherpadServerUrlConfig = getConfigItem(DokumaranConfigItem.KEY_ETHERPAD_URL); String etherpadServerUrl = etherpadServerUrlConfig.getConfigValue(); URI uri = new URI(etherpadServerUrl); - String domain = uri.getHost(); + //regex to get the top level part of a domain + Pattern p = Pattern.compile("^(?:\\w+://)?[^:?#/\\s]*?([^.\\s]+\\.(?:[a-z]{2,}|co\\.uk|org\\.uk|ac\\.uk|edu\\.au|org\\.au|com\\.au|edu\\.sg|com\\.sg|net\\.sg|org\\.sg|gov\\.sg|per\\.sg))(?:[:?#/]|$)"); + // eg: uri.getHost() will return "www.foo.com" + Matcher m = p.matcher(uri.getHost()); + String topLevelDomain = m.matches() ? m.group(1) : uri.getHost(); Cookie etherpadSessionCookie = new Cookie("sessionID", etherpadSessionIds); - etherpadSessionCookie.setDomain(domain); + etherpadSessionCookie.setDomain(topLevelDomain); // A negative value means that the cookie is not stored persistently and will be deleted when the Web browser // exits. A zero value causes the cookie to be deleted. etherpadSessionCookie.setMaxAge(-1); etherpadSessionCookie.setPath("/"); - return etherpadSessionCookie; + return etherpadSessionCookie; } @Override Index: lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/action/LearningAction.java =================================================================== diff -u -r3135ffa870d30b00e33204b2ab83954665ce8d61 -reeb7734f1d37604e1b9e14b4df3214d5cdd0338b --- lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/action/LearningAction.java (.../LearningAction.java) (revision 3135ffa870d30b00e33204b2ab83954665ce8d61) +++ lams_tool_doku/src/java/org/lamsfoundation/lams/tool/dokumaran/web/action/LearningAction.java (.../LearningAction.java) (revision eeb7734f1d37604e1b9e14b4df3214d5cdd0338b) @@ -149,7 +149,7 @@ // support for leader select feature List leaders = dokumaran.isUseSelectLeaderToolOuput() ? service.checkLeaderSelectToolForSessionLeader(user, new Long(toolSessionId).longValue(), isFirstTimeAccess) - : null; + : new ArrayList(); // forwards to the leaderSelection page if (dokumaran.isUseSelectLeaderToolOuput() && leaders.isEmpty() && !mode.isTeacher()) { Index: lams_tool_doku/web/pages/monitoring/monitoring.jsp =================================================================== diff -u -rb15a3b889da23ac048a689fc2d661df42571b229 -reeb7734f1d37604e1b9e14b4df3214d5cdd0338b --- lams_tool_doku/web/pages/monitoring/monitoring.jsp (.../monitoring.jsp) (revision b15a3b889da23ac048a689fc2d661df42571b229) +++ lams_tool_doku/web/pages/monitoring/monitoring.jsp (.../monitoring.jsp) (revision eeb7734f1d37604e1b9e14b4df3214d5cdd0338b) @@ -5,6 +5,12 @@ <%@ include file="/common/tabbedheader.jsp" %> + + @@ -64,6 +64,23 @@
    +
    + + + "> + + + + + + "> + + + +
    +
    @@ -83,3 +100,25 @@ <%@ include file="advanceoptions.jsp"%> + + + + + + + + + + + + + + + +
    + ${faultySession.sessionName} + + +
    +
    +