summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md12
-rw-r--r--bin/importSqlFile.js110
-rw-r--r--src/ep.json3
-rw-r--r--src/locales/fa.json8
-rw-r--r--src/locales/ia.json32
-rw-r--r--src/locales/te.json1
-rw-r--r--src/node/handler/APIHandler.js5
-rw-r--r--src/node/handler/PadMessageHandler.js18
-rw-r--r--src/node/hooks/express/admin.js1
-rw-r--r--src/node/hooks/express/swagger.js431
-rw-r--r--src/package.json5
-rw-r--r--src/static/js/ace2_inner.js4
-rw-r--r--src/static/js/chat.js2
-rw-r--r--src/static/js/pad.js12
-rw-r--r--src/static/js/pad_userlist.js12
-rw-r--r--tests/frontend/specs/alphabet.js (renamed from tests/frontend/specs/keystroke_alphabet.js)0
-rw-r--r--tests/frontend/specs/bold.js71
-rw-r--r--tests/frontend/specs/button_bold.js36
-rw-r--r--tests/frontend/specs/button_italic.js36
-rw-r--r--tests/frontend/specs/button_redo.js37
-rw-r--r--tests/frontend/specs/button_undo.js33
-rw-r--r--tests/frontend/specs/chat.js (renamed from tests/frontend/specs/keystroke_chat.js)35
-rw-r--r--tests/frontend/specs/chat_always_on_screen.js40
-rw-r--r--tests/frontend/specs/clear_authorship_colors.js (renamed from tests/frontend/specs/button_clear_authorship_colors.js)0
-rw-r--r--tests/frontend/specs/delete.js (renamed from tests/frontend/specs/keystroke_delete.js)0
-rw-r--r--tests/frontend/specs/enter.js (renamed from tests/frontend/specs/keystroke_enter.js)2
-rw-r--r--tests/frontend/specs/indentation.js (renamed from tests/frontend/specs/button_indentation.js)28
-rw-r--r--tests/frontend/specs/italic.js73
-rw-r--r--tests/frontend/specs/language.js3
-rw-r--r--tests/frontend/specs/ordered_list.js (renamed from tests/frontend/specs/button_ordered_list.js)0
-rw-r--r--tests/frontend/specs/redo.js76
-rw-r--r--tests/frontend/specs/strikethrough.js (renamed from tests/frontend/specs/button_strikethrough.js)0
-rw-r--r--tests/frontend/specs/timeslider.js (renamed from tests/frontend/specs/button_timeslider.js)0
-rw-r--r--tests/frontend/specs/undo.js69
-rw-r--r--tests/frontend/specs/urls_become_clickable.js (renamed from tests/frontend/specs/keystroke_urls_become_clickable.js)0
35 files changed, 980 insertions, 215 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0fac58f2..642846a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,15 @@
+# 1.2.9
+ * Fix: MAJOR Security issue, where a hacker could submit content as another user
+ * Fix: security issue due to unescaped user input
+ * Fix: Admin page at /admin redirects to /admin/ now to prevent breaking relative links
+ * Fix: indentation in chrome on linux
+ * Fix: PadUsers API endpoint
+ * NEW: A script to import data to all dbms
+ * NEW: Add authorId to chat and userlist as a data attribute
+ * NEW Refactor and fix our frontend tests
+ * NEW: Localisation updates
+
+
# 1.2.81
* Fix: CtrlZ-Y for Undo Redo
* Fix: RTL functionality on contents & fix RTL/LTR tests and RTL in Safari
diff --git a/bin/importSqlFile.js b/bin/importSqlFile.js
new file mode 100644
index 00000000..6491cbea
--- /dev/null
+++ b/bin/importSqlFile.js
@@ -0,0 +1,110 @@
+var startTime = new Date().getTime();
+
+require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) {
+
+ var fs = require("fs");
+
+ var ueberDB = require("ep_etherpad-lite/node_modules/ueberDB");
+ var settings = require("ep_etherpad-lite/node/utils/Settings");
+ var log4js = require('ep_etherpad-lite/node_modules/log4js');
+
+ var dbWrapperSettings = {
+ cache: 0,
+ writeInterval: 100,
+ json: false // data is already json encoded
+ };
+ var db = new ueberDB.database(settings.dbType, settings.dbSettings, dbWrapperSettings, log4js.getLogger("ueberDB"));
+
+ var sqlFile = process.argv[2];
+
+ //stop if the settings file is not set
+ if(!sqlFile)
+ {
+ console.error("Use: node importSqlFile.js $SQLFILE");
+ process.exit(1);
+ }
+
+ log("initializing db");
+ db.init(function(err)
+ {
+ //there was an error while initializing the database, output it and stop
+ if(err)
+ {
+ console.error("ERROR: Problem while initalizing the database");
+ console.error(err.stack ? err.stack : err);
+ process.exit(1);
+ }
+ else
+ {
+ log("done");
+
+ log("open output file...");
+ var lines = fs.readFileSync(sqlFile, 'utf8').split("\n");
+
+ var count = lines.length;
+ var keyNo = 0;
+
+ process.stdout.write("Start importing " + count + " keys...\n");
+ lines.forEach(function(l) {
+ if (l.substr(0, 27) == "REPLACE INTO store VALUES (") {
+ var pos = l.indexOf("', '");
+ var key = l.substr(28, pos - 28);
+ var value = l.substr(pos + 3);
+ value = value.substr(0, value.length - 2);
+ console.log("key: " + key + " val: " + value);
+ console.log("unval: " + unescape(value));
+ db.set(key, unescape(value), null);
+ keyNo++;
+ if (keyNo % 1000 == 0) {
+ process.stdout.write(" " + keyNo + "/" + count + "\n");
+ }
+ }
+ });
+ process.stdout.write("\n");
+ process.stdout.write("done. waiting for db to finish transaction. depended on dbms this may take some time...\n");
+
+ db.doShutdown(function() {
+ log("finished, imported " + keyNo + " keys.");
+ process.exit(0);
+ });
+ }
+ });
+});
+
+function log(str)
+{
+ console.log((new Date().getTime() - startTime)/1000 + "\t" + str);
+}
+
+unescape = function(val) {
+ // value is a string
+ if (val.substr(0, 1) == "'") {
+ val = val.substr(0, val.length - 1).substr(1);
+
+ return val.replace(/\\[0nrbtZ\\'"]/g, function(s) {
+ switch(s) {
+ case "\\0": return "\0";
+ case "\\n": return "\n";
+ case "\\r": return "\r";
+ case "\\b": return "\b";
+ case "\\t": return "\t";
+ case "\\Z": return "\x1a";
+ default: return s.substr(1);
+ }
+ });
+ }
+
+ // value is a boolean or NULL
+ if (val == 'NULL') {
+ return null;
+ }
+ if (val == 'true') {
+ return true;
+ }
+ if (val == 'false') {
+ return false;
+ }
+
+ // value is a number
+ return val;
+};
diff --git a/src/ep.json b/src/ep.json
index 89c8964a..eeb5c640 100644
--- a/src/ep.json
+++ b/src/ep.json
@@ -23,6 +23,7 @@
{ "name": "adminsettings", "hooks": {
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminsettings:expressCreateServer",
"socketio": "ep_etherpad-lite/node/hooks/express/adminsettings:socketio" }
- }
+ },
+ { "name": "swagger", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/swagger:expressCreateServer" } }
]
}
diff --git a/src/locales/fa.json b/src/locales/fa.json
index 437b8da0..bccc353c 100644
--- a/src/locales/fa.json
+++ b/src/locales/fa.json
@@ -2,7 +2,8 @@
"@metadata": {
"authors": {
"0": "BMRG14",
- "2": "ZxxZxxZ"
+ "1": "Dalba",
+ "3": "ZxxZxxZ"
}
},
"index.newPad": "\u062f\u0641\u062a\u0631\u0686\u0647 \u06cc\u0627\u062f\u062f\u0627\u0634\u062a \u062a\u0627\u0632\u0647",
@@ -20,7 +21,7 @@
"pad.toolbar.clearAuthorship.title": "\u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 \u0631\u0646\u06af\u200c\u0647\u0627\u06cc \u0646\u0648\u06cc\u0633\u0646\u062f\u06af\u06cc",
"pad.toolbar.import_export.title": "\u062f\u0631\u0648\u0646\u200c\u0631\u06cc\u0632\u06cc\/\u0628\u0631\u0648\u0646\u200c\u0631\u06cc\u0632\u06cc \u0627\u0632\/\u0628\u0647 \u0642\u0627\u0644\u0628\u200c\u0647\u0627\u06cc \u0645\u062e\u062a\u0644\u0641",
"pad.toolbar.timeslider.title": "\u0627\u0633\u0644\u0627\u06cc\u062f\u0631 \u0632\u0645\u0627\u0646",
- "pad.toolbar.savedRevision.title": "\u0628\u0627\u0632\u0646\u0648\u06cc\u0633\u06cc\u200c\u0647\u0627\u06cc \u0630\u062e\u06cc\u0631\u0647 \u0634\u062f\u0647",
+ "pad.toolbar.savedRevision.title": "\u0630\u062e\u06cc\u0631\u0647\u200c\u0633\u0627\u0632\u06cc \u0646\u0633\u062e\u0647",
"pad.toolbar.settings.title": "\u062a\u0646\u0638\u06cc\u0645\u0627\u062a",
"pad.toolbar.embed.title": "\u062c\u0627\u0633\u0627\u0632\u06cc \u0627\u06cc\u0646 \u062f\u0641\u062a\u0631\u0686\u0647 \u06cc\u0627\u062f\u062f\u0627\u0634\u062a",
"pad.toolbar.showusers.title": "\u0646\u0645\u0627\u06cc\u0634 \u06a9\u0627\u0631\u0628\u0631\u0627\u0646 \u062f\u0631 \u0627\u06cc\u0646 \u062f\u0641\u062a\u0631\u0686\u0647 \u06cc\u0627\u062f\u062f\u0627\u0634\u062a",
@@ -79,6 +80,7 @@
"pad.share.emebdcode": "\u062c\u0627\u0633\u0627\u0632\u06cc \u0646\u0634\u0627\u0646\u06cc",
"pad.chat": "\u06af\u0641\u062a\u06af\u0648",
"pad.chat.title": "\u0628\u0627\u0632\u06a9\u0631\u062f\u0646 \u06af\u0641\u062a\u06af\u0648 \u0628\u0631\u0627\u06cc \u0627\u06cc\u0646 \u062f\u0641\u062a\u0631\u0686\u0647 \u06cc\u0627\u062f\u062f\u0627\u0634\u062a",
+ "pad.chat.loadmessages": "\u0628\u0627\u0631\u06af\u06cc\u0631\u06cc \u067e\u06cc\u0627\u0645\u200c\u0647\u0627\u06cc \u0628\u06cc\u0634\u062a\u0631",
"timeslider.pageTitle": "\u0627\u0633\u0644\u0627\u06cc\u062f\u0631 \u0632\u0645\u0627\u0646 {{appTitle}}",
"timeslider.toolbar.returnbutton": "\u0628\u0627\u0632\u06af\u0634\u062a \u0628\u0647 \u062f\u0641\u062a\u0631\u0686\u0647 \u06cc\u0627\u062f\u062f\u0627\u0634\u062a",
"timeslider.toolbar.authors": "\u0646\u0648\u06cc\u0633\u0646\u062f\u06af\u0627\u0646:",
@@ -100,6 +102,8 @@
"timeslider.month.october": "\u0627\u06a9\u062a\u0628\u0631",
"timeslider.month.november": "\u0646\u0648\u0627\u0645\u0628\u0631",
"timeslider.month.december": "\u062f\u0633\u0627\u0645\u0628\u0631",
+ "timeslider.unnamedauthor": "{{num}} \u0646\u0648\u06cc\u0633\u0646\u062f\u0647\u0654 \u0628\u06cc\u200c\u0646\u0627\u0645",
+ "timeslider.unnamedauthors": "{{num}} \u0646\u0648\u06cc\u0633\u0646\u062f\u0647\u0654 \u0628\u06cc\u200c\u0646\u0627\u0645",
"pad.savedrevs.marked": "\u0627\u06cc\u0646 \u0628\u0627\u0632\u0646\u0648\u06cc\u0633\u06cc \u0647\u0645 \u0627\u06a9\u0646\u0648\u0646 \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u0630\u062e\u06cc\u0631\u0647 \u0634\u062f\u0647 \u0639\u0644\u0627\u0645\u062a\u200c\u06af\u0630\u0627\u0631\u06cc \u0634\u062f",
"pad.userlist.entername": "\u0646\u0627\u0645 \u062e\u0648\u062f \u0631\u0627 \u0628\u0646\u0648\u06cc\u0633\u06cc\u062f",
"pad.userlist.unnamed": "\u0628\u062f\u0648\u0646 \u0646\u0627\u0645",
diff --git a/src/locales/ia.json b/src/locales/ia.json
index 21b1d291..e6c5dde1 100644
--- a/src/locales/ia.json
+++ b/src/locales/ia.json
@@ -19,13 +19,16 @@
"pad.toolbar.clearAuthorship.title": "Rader colores de autor",
"pad.toolbar.import_export.title": "Importar\/exportar in differente formatos de file",
"pad.toolbar.timeslider.title": "Glissa-tempore",
- "pad.toolbar.savedRevision.title": "Versiones salveguardate",
+ "pad.toolbar.savedRevision.title": "Version salveguardate",
"pad.toolbar.settings.title": "Configuration",
"pad.toolbar.embed.title": "Incorporar iste pad",
"pad.toolbar.showusers.title": "Monstrar le usatores de iste pad",
"pad.colorpicker.save": "Salveguardar",
"pad.colorpicker.cancel": "Cancellar",
"pad.loading": "Cargamento\u2026",
+ "pad.passwordRequired": "Un contrasigno es necessari pro acceder a iste pad",
+ "pad.permissionDenied": "Tu non ha le permission de acceder a iste pad",
+ "pad.wrongPassword": "Le contrasigno es incorrecte",
"pad.settings.padSettings": "Configuration del pad",
"pad.settings.myView": "Mi vista",
"pad.settings.stickychat": "Chat sempre visibile",
@@ -38,6 +41,7 @@
"pad.settings.language": "Lingua:",
"pad.importExport.import_export": "Importar\/Exportar",
"pad.importExport.import": "Incargar qualcunque file de texto o documento",
+ "pad.importExport.importSuccessful": "Succedite!",
"pad.importExport.export": "Exportar le pad actual como:",
"pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "Texto simple",
@@ -45,9 +49,11 @@
"pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document Format)",
"pad.importExport.exportdokuwiki": "DokuWiki",
+ "pad.importExport.abiword.innerHTML": "Tu pote solmente importar files in formato de texto simple o HTML. Pro functionalitate de importation plus extense, <a href=\"https:\/\/github.com\/ether\/etherpad-lite\/wiki\/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">installa AbiWord<\/a>.",
"pad.modals.connected": "Connectite.",
"pad.modals.reconnecting": "Reconnecte a tu pad\u2026",
"pad.modals.forcereconnect": "Fortiar reconnexion",
+ "pad.modals.userdup": "Aperte in un altere fenestra",
"pad.modals.userdup.explanation": "Iste pad pare esser aperte in plus de un fenestra de navigator in iste computator.",
"pad.modals.userdup.advice": "Reconnecte pro usar iste fenestra.",
"pad.modals.unauth": "Non autorisate",
@@ -72,11 +78,16 @@
"pad.share.emebdcode": "Codice de incorporation",
"pad.chat": "Chat",
"pad.chat.title": "Aperir le chat pro iste pad.",
+ "pad.chat.loadmessages": "Cargar plus messages",
"timeslider.pageTitle": "Glissa-tempore pro {{appTitle}}",
"timeslider.toolbar.returnbutton": "Retornar al pad",
"timeslider.toolbar.authors": "Autores:",
"timeslider.toolbar.authorsList": "Nulle autor",
+ "timeslider.toolbar.exportlink.title": "Exportar",
"timeslider.exportCurrent": "Exportar le version actual como:",
+ "timeslider.version": "Version {{version}}",
+ "timeslider.saved": "Salveguardate le {{day}} de {{month}} {{year}}",
+ "timeslider.dateformat": "{{year}}-{{month}}-{{day}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "januario",
"timeslider.month.february": "februario",
"timeslider.month.march": "martio",
@@ -88,5 +99,22 @@
"timeslider.month.september": "septembre",
"timeslider.month.october": "octobre",
"timeslider.month.november": "novembre",
- "timeslider.month.december": "decembre"
+ "timeslider.month.december": "decembre",
+ "timeslider.unnamedauthor": "{{num}} autor sin nomine",
+ "timeslider.unnamedauthors": "{{num}} autores sin nomine",
+ "pad.savedrevs.marked": "Iste version es ora marcate como version salveguardate",
+ "pad.userlist.entername": "Entra tu nomine",
+ "pad.userlist.unnamed": "sin nomine",
+ "pad.userlist.guest": "Invitato",
+ "pad.userlist.deny": "Refusar",
+ "pad.userlist.approve": "Approbar",
+ "pad.editbar.clearcolors": "Rader le colores de autor in tote le documento?",
+ "pad.impexp.importbutton": "Importar ora",
+ "pad.impexp.importing": "Importation in curso\u2026",
+ "pad.impexp.confirmimport": "Le importation de un file superscribera le texto actual del pad. Es tu secur de voler continuar?",
+ "pad.impexp.convertFailed": "Nos non ha potite importar iste file. Per favor usa un altere formato de documento o copia e colla le texto manualmente.",
+ "pad.impexp.uploadFailed": "Le incargamento ha fallite. Per favor reproba.",
+ "pad.impexp.importfailed": "Importation fallite",
+ "pad.impexp.copypaste": "Per favor copia e colla",
+ "pad.impexp.exportdisabled": "Le exportation in formato {{type}} es disactivate. Per favor contacta le administrator del systema pro detalios."
} \ No newline at end of file
diff --git a/src/locales/te.json b/src/locales/te.json
index 666a40aa..955b263a 100644
--- a/src/locales/te.json
+++ b/src/locales/te.json
@@ -26,6 +26,7 @@
"pad.colorpicker.save": "\u0c2d\u0c26\u0c4d\u0c30\u0c2a\u0c30\u0c1a\u0c41",
"pad.colorpicker.cancel": "\u0c30\u0c26\u0c4d\u0c26\u0c41\u0c1a\u0c47\u0c2f\u0c3f",
"pad.loading": "\u0c32\u0c4b\u0c21\u0c35\u0c41\u0c24\u0c4b\u0c02\u0c26\u0c3f...",
+ "pad.wrongPassword": "\u0c2e\u0c40 \u0c30\u0c39\u0c38\u0c4d\u0c2f\u0c2a\u0c26\u0c02 \u0c24\u0c2a\u0c41",
"pad.settings.padSettings": "\u0c2a\u0c32\u0c15 \u0c05\u0c2e\u0c30\u0c3f\u0c15\u0c32\u0c41",
"pad.settings.myView": "\u0c28\u0c3e \u0c09\u0c26\u0c4d\u0c26\u0c47\u0c36\u0c4d\u0c2f\u0c2e\u0c41",
"pad.settings.stickychat": "\u0c24\u0c46\u0c30\u0c2a\u0c48\u0c28\u0c47 \u0c2e\u0c3e\u0c1f\u0c3e\u0c2e\u0c02\u0c24\u0c3f\u0c28\u0c3f \u0c0e\u0c32\u0c4d\u0c32\u0c2a\u0c41\u0c21\u0c41 \u0c1a\u0c47\u0c2f\u0c41\u0c2e\u0c41",
diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js
index 8be5b5fe..4b7dd951 100644
--- a/src/node/handler/APIHandler.js
+++ b/src/node/handler/APIHandler.js
@@ -219,6 +219,9 @@ var version =
// set the latest available API version here
exports.latestApiVersion = '1.2.7';
+// exports the versions so it can be used by the new Swagger endpoint
+exports.version = version;
+
/**
* Handles a HTTP API call
* @param functionName the name of the called function
@@ -266,6 +269,8 @@ exports.handle = function(apiVersion, functionName, fields, req, res)
}
//check the api key!
+ fields["apikey"] = fields["apikey"] || fields["api_key"];
+
if(fields["apikey"] != apikey.trim())
{
res.send({code: 4, message: "no or wrong API Key", data: null});
diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js
index 15a9b8ea..4b98292e 100644
--- a/src/node/handler/PadMessageHandler.js
+++ b/src/node/handler/PadMessageHandler.js
@@ -550,11 +550,25 @@ function handleUserChanges(client, message)
throw "Attribute pool is missing attribute "+n+" for changeset "+changeset;
}
});
+
+ // Validate all added 'author' attribs to be the same value as the current user
+ var iterator = Changeset.opIterator(Changeset.unpack(changeset).ops)
+ , op
+ while(iterator.hasNext()) {
+ op = iterator.next()
+ if(op.opcode != '+') continue;
+ op.attribs.split('*').forEach(function(attr) {
+ if(!attr) return
+ attr = wireApool.getAttrib(attr)
+ if(!attr) return
+ if('author' == attr[0] && attr[1] != thisSession.author) throw "Trying to submit changes as another author"
+ })
+ }
}
catch(e)
{
// There is an error in this changeset, so just refuse it
- console.warn("Can't apply USER_CHANGES "+changeset+", because it failed checkRep");
+ console.warn("Can't apply USER_CHANGES "+changeset+", because: "+e);
client.json.send({disconnect:"badChangeset"});
return;
}
@@ -1448,7 +1462,7 @@ exports.padUsersCount = function (padID, callback) {
exports.padUsers = function (padID, callback) {
var result = [];
- async.forEach(socketio.sockets.clients(padId), function(roomClient, callback) {
+ async.forEach(socketio.sockets.clients(padID), function(roomClient, callback) {
var s = sessioninfos[roomClient.id];
if(s) {
authorManager.getAuthor(s.author, function(err, author) {
diff --git a/src/node/hooks/express/admin.js b/src/node/hooks/express/admin.js
index 766370fc..70539f0c 100644
--- a/src/node/hooks/express/admin.js
+++ b/src/node/hooks/express/admin.js
@@ -2,6 +2,7 @@ var eejs = require('ep_etherpad-lite/node/eejs');
exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/admin', function(req, res) {
+ if('/' != req.path[req.path.length-1]) return res.redirect('/admin/');
res.send( eejs.require("ep_etherpad-lite/templates/admin/index.html", {}) );
});
}
diff --git a/src/node/hooks/express/swagger.js b/src/node/hooks/express/swagger.js
new file mode 100644
index 00000000..f4fc5cff
--- /dev/null
+++ b/src/node/hooks/express/swagger.js
@@ -0,0 +1,431 @@
+var log4js = require('log4js');
+var express = require('express');
+var apiHandler = require('../../handler/APIHandler');
+var apiCaller = require('./apicalls').apiCaller;
+var settings = require("../../utils/Settings");
+
+var swaggerModels = {
+ 'models': {
+ 'SessionInfo' : {
+ "id": 'SessionInfo',
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "authorID": {
+ "type": "string"
+ },
+ "groupID":{
+ "type":"string"
+ },
+ "validUntil":{
+ "type":"long"
+ }
+ }
+ },
+ 'UserInfo' : {
+ "id": 'UserInfo',
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "colorId": {
+ "type": "string"
+ },
+ "name":{
+ "type":"string"
+ },
+ "timestamp":{
+ "type":"long"
+ }
+ }
+ },
+ 'Message' : {
+ "id": 'Message',
+ "properties": {
+ "text": {
+ "type": "string"
+ },
+ "userId": {
+ "type": "string"
+ },
+ "userName":{
+ "type":"string"
+ },
+ "time":{
+ "type":"long"
+ }
+ }
+ }
+ }
+};
+
+function sessionListResponseProcessor(res) {
+ if (res.data) {
+ var sessions = [];
+ for (var sessionId in res.data) {
+ var sessionInfo = res.data[sessionId];
+ sessionId["id"] = sessionId;
+ sessions.push(sessionInfo);
+ }
+ res.data = sessions;
+ }
+
+ return res;
+}
+
+// We'll add some more info to the API methods
+var API = {
+
+ // Group
+ "group": {
+ "create" : {
+ "func" : "createGroup",
+ "description": "creates a new group",
+ "response": {"groupID":{"type":"string"}}
+ },
+ "createIfNotExistsFor" : {
+ "func": "createGroupIfNotExistsFor",
+ "description": "this functions helps you to map your application group ids to etherpad lite group ids",
+ "response": {"groupID":{"type":"string"}}
+ },
+ "delete" : {
+ "func": "deleteGroup",
+ "description": "deletes a group"
+ },
+ "listPads" : {
+ "func": "listPads",
+ "description": "returns all pads of this group",
+ "response": {"padIDs":{"type":"List", "items":{"type":"string"}}}
+ },
+ "createPad" : {
+ "func": "createGroupPad",
+ "description": "creates a new pad in this group"
+ },
+ "listSessions": {
+ "func": "listSessionsOfGroup",
+ "responseProcessor": sessionListResponseProcessor,
+ "description": "",
+ "response": {"sessions":{"type":"List", "items":{"type":"SessionInfo"}}}
+ },
+ "list": {
+ "func": "listAllGroups",
+ "description": "",
+ "response": {"groupIDs":{"type":"List", "items":{"type":"string"}}}
+ },
+ },
+
+ // Author
+ "author": {
+ "create" : {
+ "func" : "createAuthor",
+ "description": "creates a new author",
+ "response": {"authorID":{"type":"string"}}
+ },
+ "createIfNotExistsFor": {
+ "func": "createAuthorIfNotExistsFor",
+ "description": "this functions helps you to map your application author ids to etherpad lite author ids",
+ "response": {"authorID":{"type":"string"}}
+ },
+ "listPads": {
+ "func": "listPadsOfAuthor",
+ "description": "returns an array of all pads this author contributed to",
+ "response": {"padIDs":{"type":"List", "items":{"type":"string"}}}
+ },
+ "listSessions": {
+ "func": "listSessionsOfAuthor",
+ "responseProcessor": sessionListResponseProcessor,
+ "description": "returns all sessions of an author",
+ "response": {"sessions":{"type":"List", "items":{"type":"SessionInfo"}}}
+ },
+ // We need an operation that return a UserInfo so it can be picked up by the codegen :(
+ "getName" : {
+ "func": "getAuthorName",
+ "description": "Returns the Author Name of the author",
+ "responseProcessor": function(response) {
+ if (response.data) {
+ response["info"] = {"name": response.data.authorName};
+ delete response["data"];
+ }
+ },
+ "response": {"info":{"type":"UserInfo"}}
+ }
+ },
+ "session": {
+ "create" : {
+ "func": "createSession",
+ "description": "creates a new session. validUntil is an unix timestamp in seconds",
+ "response": {"sessionID":{"type":"string"}}
+ },
+ "delete" : {
+ "func": "deleteSession",
+ "description": "deletes a session"
+ },
+ // We need an operation that returns a SessionInfo so it can be picked up by the codegen :(
+ "info": {
+ "func": "getSessionInfo",
+ "description": "returns informations about a session",
+ "responseProcessor": function(response) {
+ // move this to info
+ if (response.data) {
+ response["info"] = response.data;
+ delete response["data"];
+ }
+ },
+ "response": {"info":{"type":"SessionInfo"}}
+ }
+ },
+ "pad": {
+ "listAll" : {
+ "func": "listAllPads",
+ "description": "list all the pads",
+ "response": {"padIDs":{"type":"List", "items": {"type" : "string"}}}
+ },
+ "createDiffHTML" : {
+ "func" : "createDiffHTML",
+ "description": "",
+ "response": {}
+ },
+ "create" : {
+ "func" : "createPad",
+ "description": "creates a new (non-group) pad. Note that if you need to create a group Pad, you should call createGroupPad"
+ },
+ "getText" : {
+ "func" : "getText",
+ "description": "returns the text of a pad",
+ "response": {"text":{"type":"string"}}
+ },
+ "setText" : {
+ "func" : "setText",
+ "description": "sets the text of a pad"
+ },
+ "getHTML": {
+ "func" : "getHTML",
+ "description": "returns the text of a pad formatted as HTML",
+ "response": {"html":{"type":"string"}}
+ },
+ "setHTML": {
+ "func" : "setHTML",
+ "description": "sets the text of a pad with HTML"
+ },
+ "getRevisionsCount": {
+ "func" : "getRevisionsCount",
+ "description": "returns the number of revisions of this pad",
+ "response": {"revisions":{"type":"long"}}
+ },
+ "getLastEdited": {
+ "func" : "getLastEdited",
+ "description": "returns the timestamp of the last revision of the pad",
+ "response": {"lastEdited":{"type":"long"}}
+ },
+ "delete": {
+ "func" : "deletePad",
+ "description": "deletes a pad"
+ },
+ "getReadOnlyID": {
+ "func" : "getReadOnlyID",
+ "description": "returns the read only link of a pad",
+ "response": {"readOnlyID":{"type":"string"}}
+ },
+ "setPublicStatus": {
+ "func": "setPublicStatus",
+ "description": "sets a boolean for the public status of a pad"
+ },
+ "getPublicStatus": {
+ "func": "getPublicStatus",
+ "description": "return true of false",
+ "response": {"publicStatus":{"type":"boolean"}}
+ },
+ "setPassword": {
+ "func": "setPassword",
+ "description": "returns ok or a error message"
+ },
+ "isPasswordProtected": {
+ "func": "isPasswordProtected",
+ "description": "returns true or false",
+ "response": {"passwordProtection":{"type":"boolean"}}
+ },
+ "authors": {
+ "func": "listAuthorsOfPad",
+ "description": "returns an array of authors who contributed to this pad",
+ "response": {"authorIDs":{"type":"List", "items":{"type" : "string"}}}
+ },
+ "usersCount": {
+ "func": "padUsersCount",
+ "description": "returns the number of user that are currently editing this pad",
+ "response": {"padUsersCount":{"type": "long"}}
+ },
+ "users": {
+ "func": "padUsers",
+ "description": "returns the list of users that are currently editing this pad",
+ "response": {"padUsers":{"type":"List", "items":{"type": "UserInfo"}}}
+ },
+ "sendClientsMessage": {
+ "func": "sendClientsMessage",
+ "description": "sends a custom message of type msg to the pad"
+ },
+ "checkToken" : {
+ "func": "checkToken",
+ "description": "returns ok when the current api token is valid"
+ },
+ "getChatHistory": {
+ "func": "getChatHistory",
+ "description": "returns the chat history",
+ "response": {"messages":{"type":"List", "items": {"type" : "Message"}}}
+ },
+ // We need an operation that returns a Message so it can be picked up by the codegen :(
+ "getChatHead": {
+ "func": "getChatHead",
+ "description": "returns the chatHead (chat-message) of the pad",
+ "responseProcessor": function(response) {
+ // move this to info
+ if (response.data) {
+ response["chatHead"] = {"time": response.data["chatHead"]};
+ delete response["data"];
+ }
+ },
+ "response": {"chatHead":{"type":"Message"}}
+ }
+ }
+};
+
+function capitalise(string){
+ return string.charAt(0).toUpperCase() + string.slice(1);
+}
+
+for (var resource in API) {
+ for (var func in API[resource]) {
+
+ // The base response model
+ var responseModel = {
+ "properties": {
+ "code":{
+ "type":"int"
+ },
+ "message":{
+ "type":"string"
+ }
+ }
+ };
+
+ var responseModelId = "Response";
+
+ // Add the data properties (if any) to the response
+ if (API[resource][func]["response"]) {
+ // This is a specific response so let's set a new id
+ responseModelId = capitalise(resource) + capitalise(func) + "Response";
+
+ for(var prop in API[resource][func]["response"]) {
+ var propType = API[resource][func]["response"][prop];
+ responseModel["properties"][prop] = propType;
+ }
+ }
+
+ // Add the id
+ responseModel["id"] = responseModelId;
+
+ // Add this to the swagger models
+ swaggerModels['models'][responseModelId] = responseModel;
+
+ // Store the response model id
+ API[resource][func]["responseClass"] = responseModelId;
+
+ }
+}
+
+function newSwagger() {
+ var swagger_module = require.resolve("swagger-node-express");
+ if (require.cache[swagger_module]) {
+ // delete the child modules from cache
+ require.cache[swagger_module].children.forEach(function(m) {delete require.cache[m.id];});
+ // delete the module from cache
+ delete require.cache[swagger_module];
+ }
+ return require("swagger-node-express");
+}
+
+exports.expressCreateServer = function (hook_name, args, cb) {
+
+ for (var version in apiHandler.version) {
+
+ var swagger = newSwagger();
+ var basePath = "/rest/" + version;
+
+ // Let's put this under /rest for now
+ var subpath = express();
+
+ args.app.use(express.bodyParser());
+ args.app.use(basePath, subpath);
+
+ swagger.setAppHandler(subpath);
+
+ swagger.addModels(swaggerModels);
+
+ for (var resource in API) {
+
+ for (var funcName in API[resource]) {
+ var func = API[resource][funcName];
+
+ // get the api function
+ var apiFunc = apiHandler.version[version][func["func"]];
+
+ // Skip this one if it does not exist in the version
+ if(!apiFunc) {
+ continue;
+ }
+
+ var swaggerFunc = {
+ 'spec': {
+ "description" : func["description"],
+ "path" : "/" + resource + "/" + funcName,
+ "summary" : funcName,
+ "nickname" : funcName,
+ "method": "GET",
+ "params" : apiFunc.map( function(param) {
+ return swagger.queryParam(param, param, "string");
+ }),
+ "responseClass" : func["responseClass"]
+ },
+ 'action': (function(func, responseProcessor) {
+ return function (req,res) {
+ req.params.version = version;
+ req.params.func = func; // call the api function
+
+ //wrap the send function so we can process the response
+ res.__swagger_send = res.send;
+ res.send = function (response) {
+ // ugly but we need to get this as json
+ response = JSON.parse(response);
+ // process the response if needed
+ if (responseProcessor) {
+ response = responseProcessor(response);
+ }
+ // Let's move everything out of "data"
+ if (response.data) {
+ for(var prop in response.data) {
+ response[prop] = response.data[prop];
+ delete response.data;
+ }
+ }
+ response = JSON.stringify(response);
+ res.__swagger_send(response);
+ };
+
+ apiCaller(req, res, req.query);
+ };
+ })(func["func"], func["responseProcessor"]) // must use a closure here
+ };
+
+ swagger.addGet(swaggerFunc);
+ }
+ }
+
+ swagger.setHeaders = function setHeaders(res) {
+ res.header('Access-Control-Allow-Origin', "*");
+ };
+
+ swagger.configureSwaggerPaths("", "/api" , "");
+
+ swagger.configure("http://" + settings.ip + ":" + settings.port + basePath, version);
+ }
+};
diff --git a/src/package.json b/src/package.json
index 13a4de4c..a7147cf2 100644
--- a/src/package.json
+++ b/src/package.json
@@ -36,7 +36,8 @@
"tinycon" : "0.0.1",
"underscore" : "1.3.1",
"unorm" : "1.0.0",
- "languages4translatewiki" : "0.1.3"
+ "languages4translatewiki" : "0.1.3",
+ "swagger-node-express" : "1.2.3"
},
"bin": { "etherpad-lite": "./node/server.js" },
"devDependencies": {
@@ -45,5 +46,5 @@
"engines" : { "node" : ">=0.6.3",
"npm" : ">=1.0"
},
- "version" : "1.2.81"
+ "version" : "1.2.9"
}
diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js
index b8b59b84..2dc6408b 100644
--- a/src/static/js/ace2_inner.js
+++ b/src/static/js/ace2_inner.js
@@ -3583,7 +3583,7 @@ function Ace2Inner(){
}
var specialHandled = false;
- var isTypeForSpecialKey = ((browser.msie || browser.safari) ? (type == "keydown") : (type == "keypress"));
+ var isTypeForSpecialKey = ((browser.msie || browser.safari || browser.chrome) ? (type == "keydown") : (type == "keypress"));
var isTypeForCmdKey = ((browser.msie || browser.safari || browser.chrome) ? (type == "keydown") : (type == "keypress"));
var stopped = false;
@@ -5125,7 +5125,7 @@ function Ace2Inner(){
function initLineNumbers()
{
lineNumbersShown = 1;
- sideDiv.innerHTML = '<table border="0" cellpadding="0" cellspacing="0" align="right">' + '<tr><td id="sidedivinner"><div>1</div></td></tr></table>';
+ sideDiv.innerHTML = '<table border="0" cellpadding="0" cellspacing="0" align="right"><tr><td id="sidedivinner"><div>1</div></td></tr></table>';
sideDivInner = outerWin.document.getElementById("sidedivinner");
}
diff --git a/src/static/js/chat.js b/src/static/js/chat.js
index 2dff2edf..83a487de 100644
--- a/src/static/js/chat.js
+++ b/src/static/js/chat.js
@@ -111,7 +111,7 @@ var chat = (function()
var authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName);
- var html = "<p class='" + authorClass + "'><b>" + authorName + ":</b><span class='time " + authorClass + "'>" + timeStr + "</span> " + text + "</p>";
+ var html = "<p data-authorId='" + msg.userId + "' class='" + authorClass + "'><b>" + authorName + ":</b><span class='time " + authorClass + "'>" + timeStr + "</span> " + text + "</p>";
if(isHistoryAdd)
$(html).insertAfter('#chatloadmessagesbutton');
else
diff --git a/src/static/js/pad.js b/src/static/js/pad.js
index 4b052620..60a43557 100644
--- a/src/static/js/pad.js
+++ b/src/static/js/pad.js
@@ -470,14 +470,6 @@ var pad = {
userAgent: pad.getDisplayUserAgent()
};
- if (clientVars.specialKey)
- {
- pad.myUserInfo.specialKey = clientVars.specialKey;
- if (clientVars.specialKeyTranslation)
- {
- $("#specialkeyarea").html("mode: " + String(clientVars.specialKeyTranslation).toUpperCase());
- }
- }
padimpexp.init(this);
padsavedrevs.init(this);
@@ -663,8 +655,8 @@ var pad = {
{
alertBar.displayMessage(function(abar)
{
- abar.find("#servermsgdate").html(" (" + padutils.simpleDateTime(new Date) + ")");
- abar.find("#servermsgtext").html(m.text);
+ abar.find("#servermsgdate").text(" (" + padutils.simpleDateTime(new Date) + ")");
+ abar.find("#servermsgtext").text(m.text);
});
}
if (m.js)
diff --git a/src/static/js/pad_userlist.js b/src/static/js/pad_userlist.js
index 962595d2..77ebb190 100644
--- a/src/static/js/pad_userlist.js
+++ b/src/static/js/pad_userlist.js
@@ -116,12 +116,12 @@ var paduserlist = (function()
nameHtml = '<input data-l10n-id="pad.userlist.unnamed" type="text" class="editempty newinput" value="'+_('pad.userlist.unnamed')+'" ' + (isNameEditable(data) ? '' : 'disabled="disabled" ') + '/>';
}
- return ['<td style="height:', height, 'px" class="usertdswatch"><div class="swatch" style="background:' + data.color + '">&nbsp;</div></td>', '<td style="height:', height, 'px" class="usertdname">', nameHtml, '</td>', '<td style="height:', height, 'px" class="activity">', padutils.escapeHtml(data.activity), '</td>'].join('');
+ return ['<td style="height:', height, 'px" class="usertdswatch"><div class="swatch" style="background:' + padutils.escapeHtml(data.color) + '">&nbsp;</div></td>', '<td style="height:', height, 'px" class="usertdname">', nameHtml, '</td>', '<td style="height:', height, 'px" class="activity">', padutils.escapeHtml(data.activity), '</td>'].join('');
}
- function getRowHtml(id, innerHtml)
+ function getRowHtml(id, innerHtml, authorId)
{
- return '<tr id="' + id + '">' + innerHtml + '</tr>';
+ return '<tr data-authorId="'+authorId+'" id="' + id + '">' + innerHtml + '</tr>';
}
function rowNode(row)
@@ -191,18 +191,20 @@ var paduserlist = (function()
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)));
+ tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data), authorId));
row.animationStep = 0;
}
else
{
rowsFadingIn.push(row);
- tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START))));
+ tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START)), authorId));
}
handleRowNode(tr, data);
if (position == 0)
diff --git a/tests/frontend/specs/keystroke_alphabet.js b/tests/frontend/specs/alphabet.js
index 131a81c0..131a81c0 100644
--- a/tests/frontend/specs/keystroke_alphabet.js
+++ b/tests/frontend/specs/alphabet.js
diff --git a/tests/frontend/specs/bold.js b/tests/frontend/specs/bold.js
new file mode 100644
index 00000000..95da7331
--- /dev/null
+++ b/tests/frontend/specs/bold.js
@@ -0,0 +1,71 @@
+describe("bold button", function(){
+ //create a new pad before each test run
+ beforeEach(function(cb){
+ helper.newPad(cb);
+ this.timeout(60000);
+ });
+
+ it("makes text bold on click", function(done) {
+ var inner$ = helper.padInner$;
+ var chrome$ = helper.padChrome$;
+
+ //get the first text element out of the inner iframe
+ var $firstTextElement = inner$("div").first();
+
+ //select this text element
+ $firstTextElement.sendkeys('{selectall}');
+
+ //get the bold button and click it
+ var $boldButton = chrome$(".buttonicon-bold");
+ $boldButton.click();
+
+ //ace creates a new dom element when you press a button, so just get the first text element again
+ var $newFirstTextElement = inner$("div").first();
+
+ // is there a <b> element now?
+ var isBold = $newFirstTextElement.find("b").length === 1;
+
+ //expect it to be bold
+ expect(isBold).to.be(true);
+
+ //make sure the text hasn't changed
+ expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
+
+ done();
+ });
+
+ it("makes text bold on keypress", function(done) {
+ var inner$ = helper.padInner$;
+ var chrome$ = helper.padChrome$;
+
+ //get the first text element out of the inner iframe
+ var $firstTextElement = inner$("div").first();
+
+ //select this text element
+ $firstTextElement.sendkeys('{selectall}');
+ if(inner$.browser.mozilla){ // if it's a mozilla browser
+ var evtType = "keypress";
+ }else{
+ var evtType = "keydown";
+ }
+
+ var e = inner$.Event(evtType);
+ e.ctrlKey = true; // Control key
+ e.which = 66; // b
+ inner$("#innerdocbody").trigger(e);
+
+ //ace creates a new dom element when you press a button, so just get the first text element again
+ var $newFirstTextElement = inner$("div").first();
+
+ // is there a <b> element now?
+ var isBold = $newFirstTextElement.find("b").length === 1;
+
+ //expect it to be bold
+ expect(isBold).to.be(true);
+
+ //make sure the text hasn't changed
+ expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
+
+ done();
+ });
+});
diff --git a/tests/frontend/specs/button_bold.js b/tests/frontend/specs/button_bold.js
deleted file mode 100644
index 1feafe61..00000000
--- a/tests/frontend/specs/button_bold.js
+++ /dev/null
@@ -1,36 +0,0 @@
-describe("bold button", function(){
- //create a new pad before each test run
- beforeEach(function(cb){
- helper.newPad(cb);
- this.timeout(60000);
- });
-
- it("makes text bold", function(done) {
- var inner$ = helper.padInner$;
- var chrome$ = helper.padChrome$;
-
- //get the first text element out of the inner iframe
- var $firstTextElement = inner$("div").first();
-
- //select this text element
- $firstTextElement.sendkeys('{selectall}');
-
- //get the bold button and click it
- var $boldButton = chrome$(".buttonicon-bold");
- $boldButton.click();
-
- //ace creates a new dom element when you press a button, so just get the first text element again
- var $newFirstTextElement = inner$("div").first();
-
- // is there a <b> element now?
- var isBold = $newFirstTextElement.find("b").length === 1;
-
- //expect it to be bold
- expect(isBold).to.be(true);
-
- //make sure the text hasn't changed
- expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
-
- done();
- });
-}); \ No newline at end of file
diff --git a/tests/frontend/specs/button_italic.js b/tests/frontend/specs/button_italic.js
deleted file mode 100644
index fc2e15a7..00000000
--- a/tests/frontend/specs/button_italic.js
+++ /dev/null
@@ -1,36 +0,0 @@
-describe("italic button", function(){
- //create a new pad before each test run
- beforeEach(function(cb){
- helper.newPad(cb);
- this.timeout(60000);
- });
-
- it("makes text italic", function(done) {
- var inner$ = helper.padInner$;
- var chrome$ = helper.padChrome$;
-
- //get the first text element out of the inner iframe
- var $firstTextElement = inner$("div").first();
-
- //select this text element
- $firstTextElement.sendkeys('{selectall}');
-
- //get the bold button and click it
- var $boldButton = chrome$(".buttonicon-italic");
- $boldButton.click();
-
- //ace creates a new dom element when you press a button, so just get the first text element again
- var $newFirstTextElement = inner$("div").first();
-
- // is there a <i> element now?
- var isItalic = $newFirstTextElement.find("i").length === 1;
-
- //expect it to be bold
- expect(isItalic).to.be(true);
-
- //make sure the text hasn't changed
- expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
-
- done();
- });
-});
diff --git a/tests/frontend/specs/button_redo.js b/tests/frontend/specs/button_redo.js
deleted file mode 100644
index 3ce69142..00000000
--- a/tests/frontend/specs/button_redo.js
+++ /dev/null
@@ -1,37 +0,0 @@
-describe("undo button then redo button", function(){
- beforeEach(function(cb){
- helper.newPad(cb); // creates a new pad
- this.timeout(60000);
- });
-
- it("undo some typing", function(done){
- var inner$ = helper.padInner$;
- var chrome$ = helper.padChrome$;
-
- // get the first text element inside the editable space
- var $firstTextElement = inner$("div span").first();
- var originalValue = $firstTextElement.text(); // get the original value
- var newString = "Foo";
-
- $firstTextElement.sendkeys(newString); // send line 1 to the pad
- var modifiedValue = $firstTextElement.text(); // get the modified value
- expect(modifiedValue).not.to.be(originalValue); // expect the value to change
-
- // get undo and redo buttons
- var $undoButton = chrome$(".buttonicon-undo");
- var $redoButton = chrome$(".buttonicon-redo");
- // click the buttons
- $undoButton.click(); // removes foo
- $redoButton.click(); // resends foo
-
- helper.waitFor(function(){
- console.log(inner$("div span").first().text());
- return inner$("div span").first().text() === newString;
- }).done(function(){
- var finalValue = inner$("div").first().text();
- expect(finalValue).to.be(modifiedValue); // expect the value to change
- done();
- });
- });
-});
-
diff --git a/tests/frontend/specs/button_undo.js b/tests/frontend/specs/button_undo.js
deleted file mode 100644
index 412b786b..00000000
--- a/tests/frontend/specs/button_undo.js
+++ /dev/null
@@ -1,33 +0,0 @@
-describe("undo button", function(){
- beforeEach(function(cb){
- helper.newPad(cb); // creates a new pad
- this.timeout(60000);
- });
-
- it("undo some typing", function(done){
- var inner$ = helper.padInner$;
- var chrome$ = helper.padChrome$;
-
- // get the first text element inside the editable space
- var $firstTextElement = inner$("div span").first();
- var originalValue = $firstTextElement.text(); // get the original value
-
- $firstTextElement.sendkeys("foo"); // send line 1 to the pad
- var modifiedValue = $firstTextElement.text(); // get the modified value
- expect(modifiedValue).not.to.be(originalValue); // expect the value to change
-
- // get clear authorship button as a variable
- var $undoButton = chrome$(".buttonicon-undo");
- // click the button
- $undoButton.click();
-
- helper.waitFor(function(){
- return inner$("div span").first().text() === originalValue;
- }).done(function(){
- var finalValue = inner$("div span").first().text();
- expect(finalValue).to.be(originalValue); // expect the value to change
- done();
- });
- });
-});
-
diff --git a/tests/frontend/specs/keystroke_chat.js b/tests/frontend/specs/chat.js
index e4908728..a488193f 100644
--- a/tests/frontend/specs/keystroke_chat.js
+++ b/tests/frontend/specs/chat.js
@@ -1,4 +1,4 @@
-describe("send chat message", function(){
+describe("Chat messages and UI", function(){
//create a new pad before each test run
beforeEach(function(cb){
helper.newPad(cb);
@@ -64,5 +64,36 @@ describe("send chat message", function(){
});
});
-});
+ it("makes chat stick to right side of the screen", function(done) {
+ var inner$ = helper.padInner$;
+ var chrome$ = helper.padChrome$;
+
+ //click on the settings button to make settings visible
+ var $settingsButton = chrome$(".buttonicon-settings");
+ $settingsButton.click();
+
+ //get the chat selector
+ var $stickychatCheckbox = chrome$("#options-stickychat");
+
+ //select chat always on screen and fire change event
+ $stickychatCheckbox.attr('selected','selected');
+ $stickychatCheckbox.change();
+ $stickychatCheckbox.click();
+ //check if chat changed to get the stickychat Class
+ var $chatbox = chrome$("#chatbox");
+ var hasStickyChatClass = $chatbox.hasClass("stickyChat");
+ expect(hasStickyChatClass).to.be(true);
+
+ //select chat always on screen and fire change event
+ $stickychatCheckbox.attr('selected','selected');
+ $stickychatCheckbox.change();
+ $stickychatCheckbox.click();
+
+ //check if chat changed to remove the stickychat Class
+ var hasStickyChatClass = $chatbox.hasClass("stickyChat");
+ expect(hasStickyChatClass).to.be(false);
+
+ done();
+ });
+});
diff --git a/tests/frontend/specs/chat_always_on_screen.js b/tests/frontend/specs/chat_always_on_screen.js
deleted file mode 100644
index 4873763f..00000000
--- a/tests/frontend/specs/chat_always_on_screen.js
+++ /dev/null
@@ -1,40 +0,0 @@
-describe("chat always ons creen select", function(){
- //create a new pad before each test run
- beforeEach(function(cb){
- helper.newPad(cb);
- this.timeout(60000);
- });
-
- it("makes chat stick to right side of the screen", function(done) {
- var inner$ = helper.padInner$;
- var chrome$ = helper.padChrome$;
-
- //click on the settings button to make settings visible
- var $settingsButton = chrome$(".buttonicon-settings");
- $settingsButton.click();
-
- //get the chat selector
- var $stickychatCheckbox = chrome$("#options-stickychat");
-
- //select chat always on screen and fire change event
- $stickychatCheckbox.attr('selected','selected');
- $stickychatCheckbox.change();
- $stickychatCheckbox.click();
-
- //check if chat changed to get the stickychat Class
- var $chatbox = chrome$("#chatbox");
- var hasStickyChatClass = $chatbox.hasClass("stickyChat");
- expect(hasStickyChatClass).to.be(true);
-
- //select chat always on screen and fire change event
- $stickychatCheckbox.attr('selected','selected');
- $stickychatCheckbox.change();
- $stickychatCheckbox.click();
-
- //check if chat changed to remove the stickychat Class
- var hasStickyChatClass = $chatbox.hasClass("stickyChat");
- expect(hasStickyChatClass).to.be(false);
-
- done();
- });
-});
diff --git a/tests/frontend/specs/button_clear_authorship_colors.js b/tests/frontend/specs/clear_authorship_colors.js
index 5db35612..5db35612 100644
--- a/tests/frontend/specs/button_clear_authorship_colors.js
+++ b/tests/frontend/specs/clear_authorship_colors.js
diff --git a/tests/frontend/specs/keystroke_delete.js b/tests/frontend/specs/delete.js
index 86e76f56..86e76f56 100644
--- a/tests/frontend/specs/keystroke_delete.js
+++ b/tests/frontend/specs/delete.js
diff --git a/tests/frontend/specs/keystroke_enter.js b/tests/frontend/specs/enter.js
index e46b1d2f..baafeded 100644
--- a/tests/frontend/specs/keystroke_enter.js
+++ b/tests/frontend/specs/enter.js
@@ -5,7 +5,7 @@ describe("enter keystroke", function(){
this.timeout(60000);
});
- it("creates a enw line & puts cursor onto a new line", function(done) {
+ it("creates a new line & puts cursor onto a new line", function(done) {
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
diff --git a/tests/frontend/specs/button_indentation.js b/tests/frontend/specs/indentation.js
index 9c8e317e..9692120a 100644
--- a/tests/frontend/specs/button_indentation.js
+++ b/tests/frontend/specs/indentation.js
@@ -5,7 +5,32 @@ describe("indentation button", function(){
this.timeout(60000);
});
- it("indent text", function(done){
+ it("indent text with keypress", function(done){
+ var inner$ = helper.padInner$;
+ var chrome$ = helper.padChrome$;
+
+ //get the first text element out of the inner iframe
+ var $firstTextElement = inner$("div").first();
+
+ //select this text element
+ $firstTextElement.sendkeys('{selectall}');
+
+ if(inner$.browser.mozilla){ // if it's a mozilla browser
+ var evtType = "keypress";
+ }else{
+ var evtType = "keydown";
+ }
+
+ var e = inner$.Event(evtType);
+ e.keyCode = 9; // tab :|
+ inner$("#innerdocbody").trigger(e);
+
+ helper.waitFor(function(){
+ return inner$("div").first().find("ul li").length === 1;
+ }).done(done);
+ });
+
+ it("indent text with button", function(done){
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
@@ -176,4 +201,5 @@ describe("indentation button", function(){
expect(isLI).to.be(true);
},1000);
});*/
+
});
diff --git a/tests/frontend/specs/italic.js b/tests/frontend/specs/italic.js
new file mode 100644
index 00000000..29dbae59
--- /dev/null
+++ b/tests/frontend/specs/italic.js
@@ -0,0 +1,73 @@
+describe("italic some text", function(){
+ //create a new pad before each test run
+ beforeEach(function(cb){
+ helper.newPad(cb);
+ this.timeout(60000);
+ });
+
+ it("makes text italic using button", function(done) {
+ var inner$ = helper.padInner$;
+ var chrome$ = helper.padChrome$;
+
+ //get the first text element out of the inner iframe
+ var $firstTextElement = inner$("div").first();
+
+ //select this text element
+ $firstTextElement.sendkeys('{selectall}');
+
+ //get the bold button and click it
+ var $boldButton = chrome$(".buttonicon-italic");
+ $boldButton.click();
+
+ //ace creates a new dom element when you press a button, so just get the first text element again
+ var $newFirstTextElement = inner$("div").first();
+
+ // is there a <i> element now?
+ var isItalic = $newFirstTextElement.find("i").length === 1;
+
+ //expect it to be bold
+ expect(isItalic).to.be(true);
+
+ //make sure the text hasn't changed
+ expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
+
+ done();
+ });
+
+ it("makes text italic using keypress", function(done) {
+ var inner$ = helper.padInner$;
+ var chrome$ = helper.padChrome$;
+
+ //get the first text element out of the inner iframe
+ var $firstTextElement = inner$("div").first();
+
+ //select this text element
+ $firstTextElement.sendkeys('{selectall}');
+
+ if(inner$.browser.mozilla){ // if it's a mozilla browser
+ var evtType = "keypress";
+ }else{
+ var evtType = "keydown";
+ }
+
+ var e = inner$.Event(evtType);
+ e.ctrlKey = true; // Control key
+ e.which = 105; // i
+ inner$("#innerdocbody").trigger(e);
+
+ //ace creates a new dom element when you press a button, so just get the first text element again
+ var $newFirstTextElement = inner$("div").first();
+
+ // is there a <i> element now?
+ var isItalic = $newFirstTextElement.find("i").length === 1;
+
+ //expect it to be bold
+ expect(isItalic).to.be(true);
+
+ //make sure the text hasn't changed
+ expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
+
+ done();
+ });
+
+});
diff --git a/tests/frontend/specs/language.js b/tests/frontend/specs/language.js
index ab7f2b3d..d607ff98 100644
--- a/tests/frontend/specs/language.js
+++ b/tests/frontend/specs/language.js
@@ -13,7 +13,6 @@ describe("Language select and change", function(){
});
// Destroy language cookies
-
it("makes text german", function(done) {
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
@@ -92,7 +91,7 @@ describe("Language select and change", function(){
var $languageoption = $language.find("[value=ar]");
//select arabic
- $languageoption.attr('selected','selected');
+ // $languageoption.attr('selected','selected'); // Breaks the test..
$language.val("ar");
$languageoption.change();
diff --git a/tests/frontend/specs/button_ordered_list.js b/tests/frontend/specs/ordered_list.js
index ca7d755e..ca7d755e 100644
--- a/tests/frontend/specs/button_ordered_list.js
+++ b/tests/frontend/specs/ordered_list.js
diff --git a/tests/frontend/specs/redo.js b/tests/frontend/specs/redo.js
new file mode 100644
index 00000000..c2f8a95a
--- /dev/null
+++ b/tests/frontend/specs/redo.js
@@ -0,0 +1,76 @@
+describe("undo button then redo button", function(){
+ beforeEach(function(cb){
+ helper.newPad(cb); // creates a new pad
+ this.timeout(60000);
+ });
+
+ it("redo some typing with button", function(done){
+ var inner$ = helper.padInner$;
+ var chrome$ = helper.padChrome$;
+
+ // get the first text element inside the editable space
+ var $firstTextElement = inner$("div span").first();
+ var originalValue = $firstTextElement.text(); // get the original value
+ var newString = "Foo";
+
+ $firstTextElement.sendkeys(newString); // send line 1 to the pad
+ var modifiedValue = $firstTextElement.text(); // get the modified value
+ expect(modifiedValue).not.to.be(originalValue); // expect the value to change
+
+ // get undo and redo buttons
+ var $undoButton = chrome$(".buttonicon-undo");
+ var $redoButton = chrome$(".buttonicon-redo");
+ // click the buttons
+ $undoButton.click(); // removes foo
+ $redoButton.click(); // resends foo
+
+ helper.waitFor(function(){
+ console.log(inner$("div span").first().text());
+ return inner$("div span").first().text() === newString;
+ }).done(function(){
+ var finalValue = inner$("div").first().text();
+ expect(finalValue).to.be(modifiedValue); // expect the value to change
+ done();
+ });
+ });
+
+ it("redo some typing with keypress", function(done){
+ var inner$ = helper.padInner$;
+ var chrome$ = helper.padChrome$;
+
+ // get the first text element inside the editable space
+ var $firstTextElement = inner$("div span").first();
+ var originalValue = $firstTextElement.text(); // get the original value
+ var newString = "Foo";
+
+ $firstTextElement.sendkeys(newString); // send line 1 to the pad
+ var modifiedValue = $firstTextElement.text(); // get the modified value
+ expect(modifiedValue).not.to.be(originalValue); // expect the value to change
+
+ if(inner$.browser.mozilla){ // if it's a mozilla browser
+ var evtType = "keypress";
+ }else{
+ var evtType = "keydown";
+ }
+
+ var e = inner$.Event(evtType);
+ e.ctrlKey = true; // Control key
+ e.which = 90; // z
+ inner$("#innerdocbody").trigger(e);
+
+ var e = inner$.Event(evtType);
+ e.ctrlKey = true; // Control key
+ e.which = 121; // y
+ inner$("#innerdocbody").trigger(e);
+
+ helper.waitFor(function(){
+ console.log(inner$("div span").first().text());
+ return inner$("div span").first().text() === newString;
+ }).done(function(){
+ var finalValue = inner$("div").first().text();
+ expect(finalValue).to.be(modifiedValue); // expect the value to change
+ done();
+ });
+ });
+});
+
diff --git a/tests/frontend/specs/button_strikethrough.js b/tests/frontend/specs/strikethrough.js
index 9afcea0f..9afcea0f 100644
--- a/tests/frontend/specs/button_strikethrough.js
+++ b/tests/frontend/specs/strikethrough.js
diff --git a/tests/frontend/specs/button_timeslider.js b/tests/frontend/specs/timeslider.js
index cb37bacb..cb37bacb 100644
--- a/tests/frontend/specs/button_timeslider.js
+++ b/tests/frontend/specs/timeslider.js
diff --git a/tests/frontend/specs/undo.js b/tests/frontend/specs/undo.js
new file mode 100644
index 00000000..0c58c9b8
--- /dev/null
+++ b/tests/frontend/specs/undo.js
@@ -0,0 +1,69 @@
+describe("undo button", function(){
+ beforeEach(function(cb){
+ helper.newPad(cb); // creates a new pad
+ this.timeout(60000);
+ });
+
+/*
+ it("undo some typing by clicking undo button", function(done){
+ var inner$ = helper.padInner$;
+ var chrome$ = helper.padChrome$;
+
+ // get the first text element inside the editable space
+ var $firstTextElement = inner$("div span").first();
+ var originalValue = $firstTextElement.text(); // get the original value
+
+ $firstTextElement.sendkeys("foo"); // send line 1 to the pad
+ var modifiedValue = $firstTextElement.text(); // get the modified value
+ expect(modifiedValue).not.to.be(originalValue); // expect the value to change
+
+ // get clear authorship button as a variable
+ var $undoButton = chrome$(".buttonicon-undo");
+ // click the button
+ $undoButton.click();
+
+ helper.waitFor(function(){
+ return inner$("div span").first().text() === originalValue;
+ }).done(function(){
+ var finalValue = inner$("div span").first().text();
+ expect(finalValue).to.be(originalValue); // expect the value to change
+ done();
+ });
+ });
+*/
+
+ it("undo some typing using a keypress", function(done){
+ var inner$ = helper.padInner$;
+ var chrome$ = helper.padChrome$;
+
+ // get the first text element inside the editable space
+ var $firstTextElement = inner$("div span").first();
+ var originalValue = $firstTextElement.text(); // get the original value
+
+ $firstTextElement.sendkeys("foo"); // send line 1 to the pad
+ var modifiedValue = $firstTextElement.text(); // get the modified value
+ expect(modifiedValue).not.to.be(originalValue); // expect the value to change
+
+ if(inner$.browser.mozilla){ // if it's a mozilla browser
+ var evtType = "keypress";
+ }else{
+ var evtType = "keydown";
+ }
+
+ var e = inner$.Event(evtType);
+ e.ctrlKey = true; // Control key
+ e.which = 90; // z
+ inner$("#innerdocbody").trigger(e);
+
+ helper.waitFor(function(){
+ return inner$("div span").first().text() === originalValue;
+ }).done(function(){
+ var finalValue = inner$("div span").first().text();
+ expect(finalValue).to.be(originalValue); // expect the value to change
+ done();
+ });
+ });
+
+
+});
+
diff --git a/tests/frontend/specs/keystroke_urls_become_clickable.js b/tests/frontend/specs/urls_become_clickable.js
index 8c72d748..8c72d748 100644
--- a/tests/frontend/specs/keystroke_urls_become_clickable.js
+++ b/tests/frontend/specs/urls_become_clickable.js