From 1cfc8eda19176f9359a1bce6ff62d3c6680d3395 Mon Sep 17 00:00:00 2001 From: Nelson Silva Date: Wed, 13 Feb 2013 16:29:01 +0000 Subject: [PATCH 01/32] Initial work on swagger --- src/ep.json | 3 +- src/node/handler/APIHandler.js | 5 + src/node/hooks/express/swagger.js | 378 ++++++++++++++++++++++++++++++ src/package.json | 3 +- 4 files changed, 387 insertions(+), 2 deletions(-) create mode 100644 src/node/hooks/express/swagger.js diff --git a/src/ep.json b/src/ep.json index 89c8964aa..eeb5c6409 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/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index 8be5b5fe3..4b7dd9517 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/hooks/express/swagger.js b/src/node/hooks/express/swagger.js new file mode 100644 index 000000000..cd0bf8ad5 --- /dev/null +++ b/src/node/hooks/express/swagger.js @@ -0,0 +1,378 @@ +var log4js = require('log4js'); +var express = require('express'); +var swagger = require("swagger-node-express"); +var apiHandler = require('../../handler/APIHandler'); +var apiCaller = require('./apicalls').apiCaller; +var settings = require("../../utils/Settings"); + +var versions = Object.keys(apiHandler.version) +var version = versions[versions.length - 1]; + +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"}}} + }, + "getName" : { + "func": "getAuthorName", + "description": "Returns the Author Name of the author", + "response": {"authorName":{"type":"string"}} + }, + }, + "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" + }, + "info": { + "func": "getSessionInfo", + "description": "returns informations about a session", + "response": {"authorID":{"type":"string"}, "groupID":{"type":"string"}, "validUntil":{"type":"long"}} + }, + }, + "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" + }, + "setText" : { + "func" : "setText", + "description": "sets the text of a pad", + "response": {"groupID":{"type":"string"}} + }, + "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":"bool"}} + }, + "setPassword": { + "func": "setPassword", + "description": "returns ok or a error message" + }, + "isPasswordProtected": { + "func": "isPasswordProtected", + "description": "returns true or false", + "response": {"passwordProtection":{"type":"bool"}} + }, + "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":"Lists", "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"}}} + }, + "getChatHead": { + "func": "getChatHead", + "description": "returns the chatHead (last number of the last chat-message) of the pad", + "response": {"chatHead":{"type":"long"}} + } + } +}; + +function capitalise(string){ + return string.charAt(0).toUpperCase() + string.slice(1); +} + +for (var resource in API) { + for (var func in API[resource]) { + + // Add the response model + var responseModelId = capitalise(resource) + capitalise(func) + "Response"; + + swaggerModels['models'][responseModelId] = { + "id": responseModelId, + "properties": { + "code":{ + "type":"int" + }, + "message":{ + "type":"string" + } + } + }; + + // This returns some data + if (API[resource][func]["response"]) { + // Add the data model + var dataModelId = capitalise(resource) + capitalise(func) + "Data"; + swaggerModels['models'][dataModelId] = { + "id": dataModelId, + "properties": API[resource][func]["response"] + }; + + swaggerModels['models'][responseModelId]["properties"]["data"] = { + "type": dataModelId + }; + } + + // Store the response model id + API[resource][func]["responseClass"] = responseModelId; + + // get the api function + var apiFunc = apiHandler.version[version][API[resource][func]["func"]]; + + // Add the api function parameters + API[resource][func]["params"] = apiFunc.map( function(param) { + return swagger.queryParam(param, param, "string"); + }); + } +} + +exports.expressCreateServer = function (hook_name, args, cb) { + + // Let's put this under /rest for now + var subpath = express(); + + args.app.use(express.bodyParser()); + args.app.use("/rest", subpath); + + swagger.setAppHandler(subpath); + + swagger.addModels(swaggerModels); + + for (var resource in API) { + + for (var funcName in API[resource]) { + var func = API[resource][funcName]; + + var swaggerFunc = { + 'spec': { + "description" : func["description"], + "path" : "/" + resource + "/" + funcName, + "summary" : funcName, + "nickname" : funcName, + "method": "GET", + "params" : func["params"], + "responseClass" : func["responseClass"] + }, + 'action': (function(func, responseProcessor) { + return function (req,res) { + req.params.version = version; + req.params.func = func; // call the api function + + if (responseProcessor) { + //wrap the send function so we can process the response + res.__swagger_send = res.send; + res.send = function (response) { + response = responseProcessor(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 + "/rest", version); +} diff --git a/src/package.json b/src/package.json index 44d03d809..f9d7a7a47 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": { From 8f279a67100f5225d05ee58fe90cb08fb6e9f119 Mon Sep 17 00:00:00 2001 From: Nelson Silva Date: Fri, 15 Feb 2013 14:10:03 +0000 Subject: [PATCH 02/32] Added some fixes to make it work with the codegen --- src/node/hooks/express/swagger.js | 105 ++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 34 deletions(-) diff --git a/src/node/hooks/express/swagger.js b/src/node/hooks/express/swagger.js index cd0bf8ad5..3e06ed290 100644 --- a/src/node/hooks/express/swagger.js +++ b/src/node/hooks/express/swagger.js @@ -142,11 +142,18 @@ var API = { "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", - "response": {"authorName":{"type":"string"}} - }, + "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" : { @@ -158,10 +165,18 @@ var API = { "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", - "response": {"authorID":{"type":"string"}, "groupID":{"type":"string"}, "validUntil":{"type":"long"}} + "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": { @@ -181,12 +196,12 @@ var API = { }, "getText" : { "func" : "getText", - "description": "returns the text of a pad" + "description": "returns the text of a pad", + "response": {"text":{"type":"string"}} }, "setText" : { "func" : "setText", - "description": "sets the text of a pad", - "response": {"groupID":{"type":"string"}} + "description": "sets the text of a pad" }, "getHTML": { "func" : "getHTML", @@ -223,7 +238,7 @@ var API = { "getPublicStatus": { "func": "getPublicStatus", "description": "return true of false", - "response": {"publicStatus":{"type":"bool"}} + "response": {"publicStatus":{"type":"boolean"}} }, "setPassword": { "func": "setPassword", @@ -232,7 +247,7 @@ var API = { "isPasswordProtected": { "func": "isPasswordProtected", "description": "returns true or false", - "response": {"passwordProtection":{"type":"bool"}} + "response": {"passwordProtection":{"type":"boolean"}} }, "authors": { "func": "listAuthorsOfPad", @@ -247,7 +262,7 @@ var API = { "users": { "func": "padUsers", "description": "returns the list of users that are currently editing this pad", - "response": {"padUsers":{"type":"Lists", "items":{"type": "UserInfo"}}} + "response": {"padUsers":{"type":"List", "items":{"type": "UserInfo"}}} }, "sendClientsMessage": { "func": "sendClientsMessage", @@ -262,10 +277,18 @@ var API = { "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 (last number of the last chat-message) of the pad", - "response": {"chatHead":{"type":"long"}} + "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"}} } } }; @@ -277,11 +300,8 @@ function capitalise(string){ for (var resource in API) { for (var func in API[resource]) { - // Add the response model - var responseModelId = capitalise(resource) + capitalise(func) + "Response"; - - swaggerModels['models'][responseModelId] = { - "id": responseModelId, + // The base response model + var responseModel = { "properties": { "code":{ "type":"int" @@ -292,20 +312,25 @@ for (var resource in API) { } }; - // This returns some data - if (API[resource][func]["response"]) { - // Add the data model - var dataModelId = capitalise(resource) + capitalise(func) + "Data"; - swaggerModels['models'][dataModelId] = { - "id": dataModelId, - "properties": API[resource][func]["response"] - }; + var responseModelId = "Response"; - swaggerModels['models'][responseModelId]["properties"]["data"] = { - "type": dataModelId - }; + // 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; @@ -351,14 +376,26 @@ exports.expressCreateServer = function (hook_name, args, cb) { req.params.version = version; req.params.func = func; // call the api function - if (responseProcessor) { - //wrap the send function so we can process the response - res.__swagger_send = res.send; - res.send = function (response) { + //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); - res.__swagger_send(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 From a5987285e01413ab9f0921011ab57d2615025b63 Mon Sep 17 00:00:00 2001 From: "nelson.silva" Date: Sat, 16 Feb 2013 14:57:09 +0000 Subject: [PATCH 03/32] Multiple REST endpoints (one per version) --- src/node/hooks/express/swagger.js | 230 ++++++++++++++++-------------- 1 file changed, 123 insertions(+), 107 deletions(-) diff --git a/src/node/hooks/express/swagger.js b/src/node/hooks/express/swagger.js index 3e06ed290..f4fc5cffa 100644 --- a/src/node/hooks/express/swagger.js +++ b/src/node/hooks/express/swagger.js @@ -1,13 +1,9 @@ var log4js = require('log4js'); var express = require('express'); -var swagger = require("swagger-node-express"); var apiHandler = require('../../handler/APIHandler'); var apiCaller = require('./apicalls').apiCaller; var settings = require("../../utils/Settings"); -var versions = Object.keys(apiHandler.version) -var version = versions[versions.length - 1]; - var swaggerModels = { 'models': { 'SessionInfo' : { @@ -83,14 +79,14 @@ var API = { // Group "group": { - "create" : { + "create" : { "func" : "createGroup", - "description": "creates a new group", + "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", + "description": "this functions helps you to map your application group ids to etherpad lite group ids", "response": {"groupID":{"type":"string"}} }, "delete" : { @@ -98,23 +94,23 @@ var API = { "description": "deletes a group" }, "listPads" : { - "func": "listPads", - "description": "returns all pads of this group", + "func": "listPads", + "description": "returns all pads of this group", "response": {"padIDs":{"type":"List", "items":{"type":"string"}}} }, "createPad" : { - "func": "createGroupPad", + "func": "createGroupPad", "description": "creates a new pad in this group" }, "listSessions": { "func": "listSessionsOfGroup", "responseProcessor": sessionListResponseProcessor, - "description": "", + "description": "", "response": {"sessions":{"type":"List", "items":{"type":"SessionInfo"}}} }, "list": { "func": "listAllGroups", - "description": "", + "description": "", "response": {"groupIDs":{"type":"List", "items":{"type":"string"}}} }, }, @@ -122,24 +118,24 @@ var API = { // Author "author": { "create" : { - "func" : "createAuthor", - "description": "creates a new author", + "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", + "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", + "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", + "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 :( @@ -158,7 +154,7 @@ var API = { "session": { "create" : { "func": "createSession", - "description": "creates a new session. validUntil is an unix timestamp in seconds", + "description": "creates a new session. validUntil is an unix timestamp in seconds", "response": {"sessionID":{"type":"string"}} }, "delete" : { @@ -167,7 +163,7 @@ var API = { }, // We need an operation that returns a SessionInfo so it can be picked up by the codegen :( "info": { - "func": "getSessionInfo", + "func": "getSessionInfo", "description": "returns informations about a session", "responseProcessor": function(response) { // move this to info @@ -177,22 +173,22 @@ var API = { } }, "response": {"info":{"type":"SessionInfo"}} - }, + } }, "pad": { - "listAll" : { - "func": "listAllPads", - "description": "list all the pads", + "listAll" : { + "func": "listAllPads", + "description": "list all the pads", "response": {"padIDs":{"type":"List", "items": {"type" : "string"}}} }, "createDiffHTML" : { - "func" : "createDiffHTML", - "description": "", + "func" : "createDiffHTML", + "description": "", "response": {} }, - "create" : { + "create" : { "func" : "createPad", - "description": "creates a new (non-group) pad. Note that if you need to create a group Pad, you should call createGroupPad", + "description": "creates a new (non-group) pad. Note that if you need to create a group Pad, you should call createGroupPad" }, "getText" : { "func" : "getText", @@ -205,7 +201,7 @@ var API = { }, "getHTML": { "func" : "getHTML", - "description": "returns the text of a pad formatted as HTML", + "description": "returns the text of a pad formatted as HTML", "response": {"html":{"type":"string"}} }, "setHTML": { @@ -214,12 +210,12 @@ var API = { }, "getRevisionsCount": { "func" : "getRevisionsCount", - "description": "returns the number of revisions of this pad", + "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", + "description": "returns the timestamp of the last revision of the pad", "response": {"lastEdited":{"type":"long"}} }, "delete": { @@ -228,16 +224,16 @@ var API = { }, "getReadOnlyID": { "func" : "getReadOnlyID", - "description": "returns the read only link of a pad", + "description": "returns the read only link of a pad", "response": {"readOnlyID":{"type":"string"}} }, "setPublicStatus": { - "func": "setPublicStatus", + "func": "setPublicStatus", "description": "sets a boolean for the public status of a pad" }, "getPublicStatus": { "func": "getPublicStatus", - "description": "return true of false", + "description": "return true of false", "response": {"publicStatus":{"type":"boolean"}} }, "setPassword": { @@ -245,27 +241,27 @@ var API = { "description": "returns ok or a error message" }, "isPasswordProtected": { - "func": "isPasswordProtected", - "description": "returns true or false", + "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", + "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", + "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", + "func": "padUsers", + "description": "returns the list of users that are currently editing this pad", "response": {"padUsers":{"type":"List", "items":{"type": "UserInfo"}}} }, "sendClientsMessage": { - "func": "sendClientsMessage", + "func": "sendClientsMessage", "description": "sends a custom message of type msg to the pad" }, "checkToken" : { @@ -273,13 +269,13 @@ var API = { "description": "returns ok when the current api token is valid" }, "getChatHistory": { - "func": "getChatHistory", - "description": "returns the chat history", + "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", + "func": "getChatHead", "description": "returns the chatHead (chat-message) of the pad", "responseProcessor": function(response) { // move this to info @@ -334,82 +330,102 @@ for (var resource in API) { // Store the response model id API[resource][func]["responseClass"] = responseModelId; - // get the api function - var apiFunc = apiHandler.version[version][API[resource][func]["func"]]; - - // Add the api function parameters - API[resource][func]["params"] = apiFunc.map( function(param) { - return swagger.queryParam(param, param, "string"); - }); } } +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) { - // Let's put this under /rest for now - var subpath = express(); + for (var version in apiHandler.version) { + + var swagger = newSwagger(); + var basePath = "/rest/" + version; - args.app.use(express.bodyParser()); - args.app.use("/rest", subpath); + // Let's put this under /rest for now + var subpath = express(); - swagger.setAppHandler(subpath); + args.app.use(express.bodyParser()); + args.app.use(basePath, subpath); - swagger.addModels(swaggerModels); + swagger.setAppHandler(subpath); - for (var resource in API) { + swagger.addModels(swaggerModels); - for (var funcName in API[resource]) { - var func = API[resource][funcName]; + for (var resource in API) { - var swaggerFunc = { - 'spec': { - "description" : func["description"], - "path" : "/" + resource + "/" + funcName, - "summary" : funcName, - "nickname" : funcName, - "method": "GET", - "params" : func["params"], - "responseClass" : func["responseClass"] - }, - 'action': (function(func, responseProcessor) { - return function (req,res) { - req.params.version = version; - req.params.func = func; // call the api function + for (var funcName in API[resource]) { + var func = API[resource][funcName]; - //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; + // 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); } - } - response = JSON.stringify(response); - res.__swagger_send(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 - }; + apiCaller(req, res, req.query); + }; + })(func["func"], func["responseProcessor"]) // must use a closure here + }; - swagger.addGet(swaggerFunc); + 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); } - - swagger.setHeaders = function setHeaders(res) { - res.header('Access-Control-Allow-Origin', "*"); - }; - - swagger.configureSwaggerPaths("", "/api" , ""); - - swagger.configure("http://" + settings.ip + ":" + settings.port + "/rest", version); -} +}; From 6dfc5f2c8868ebf7d4420e09b730f1e0dd09818f Mon Sep 17 00:00:00 2001 From: CeBe Date: Wed, 6 Mar 2013 18:16:30 +0100 Subject: [PATCH 04/32] a script that allows importing old etherpad db this script allows you to import the sql file generated with convert.js into all supported dbms, not only MySQL --- bin/importSqlFile.js | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 bin/importSqlFile.js diff --git a/bin/importSqlFile.js b/bin/importSqlFile.js new file mode 100644 index 000000000..4e9b1f3ed --- /dev/null +++ b/bin/importSqlFile.js @@ -0,0 +1,45 @@ +var startTime = new Date().getTime(); +var fs = require("fs"); +var db = require("../src/node/db/DB"); +//var async = require("../src/node_modules/async"); + +var sqlFile = process.argv[2]; + +//stop if the settings file is not set +if(!sqlFile) +{ + console.error("Use: node importSqlIntoRedis.js $SQLFILE"); + process.exit(1); +} + +log("initializing db"); +db.init(function(){ + log("done"); + + log("open output file..."); + var file = fs.readFileSync(sqlFile, 'utf8'); + + var keyNo = 0; + + file.split("\n").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 + 4); + value = value.substr(0, value.length - 3); + db.db.set(key, value, null); + keyNo++; + } + }); + + db.db.doShutdown(function() { + log("finished, imported " + keyNo + " keys."); + process.exit(0); + }); +}); + + +function log(str) +{ + console.log((new Date().getTime() - startTime)/1000 + "\t" + str); +} \ No newline at end of file From db0d0d1f72abb0c98093a1a2b309e73c41a00dff Mon Sep 17 00:00:00 2001 From: CeBe Date: Wed, 6 Mar 2013 22:08:14 +0100 Subject: [PATCH 05/32] fixed problem with npm --- bin/importSqlFile.js | 62 +++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/bin/importSqlFile.js b/bin/importSqlFile.js index 4e9b1f3ed..b1642288b 100644 --- a/bin/importSqlFile.js +++ b/bin/importSqlFile.js @@ -1,44 +1,46 @@ var startTime = new Date().getTime(); -var fs = require("fs"); -var db = require("../src/node/db/DB"); -//var async = require("../src/node_modules/async"); -var sqlFile = process.argv[2]; +require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) { -//stop if the settings file is not set -if(!sqlFile) -{ - console.error("Use: node importSqlIntoRedis.js $SQLFILE"); - process.exit(1); -} + var fs = require("fs"); + var db = require("ep_etherpad-lite/node/db/DB");; -log("initializing db"); -db.init(function(){ - log("done"); + var sqlFile = process.argv[2]; - log("open output file..."); - var file = fs.readFileSync(sqlFile, 'utf8'); + //stop if the settings file is not set + if(!sqlFile) + { + console.error("Use: node importSqlFile.js $SQLFILE"); + process.exit(1); + } - var keyNo = 0; + log("initializing db"); + db.init(function(){ + log("done"); - file.split("\n").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 + 4); - value = value.substr(0, value.length - 3); - db.db.set(key, value, null); - keyNo++; - } - }); + log("open output file..."); + var file = fs.readFileSync(sqlFile, 'utf8'); - db.db.doShutdown(function() { - log("finished, imported " + keyNo + " keys."); - process.exit(0); + var keyNo = 0; + + file.split("\n").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 + 4); + value = value.substr(0, value.length - 3); + db.db.set(key, value, null); + keyNo++; + } + }); + + db.db.doShutdown(function() { + log("finished, imported " + keyNo + " keys."); + process.exit(0); + }); }); }); - function log(str) { console.log((new Date().getTime() - startTime)/1000 + "\t" + str); From 4b7238c2cde98da86d07a4ea4529c8cb1786b9bc Mon Sep 17 00:00:00 2001 From: CeBe Date: Wed, 6 Mar 2013 22:28:00 +0100 Subject: [PATCH 06/32] improved output for importSqlFile --- bin/importSqlFile.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bin/importSqlFile.js b/bin/importSqlFile.js index b1642288b..b43a0622e 100644 --- a/bin/importSqlFile.js +++ b/bin/importSqlFile.js @@ -19,11 +19,12 @@ require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) { log("done"); log("open output file..."); - var file = fs.readFileSync(sqlFile, 'utf8'); + var lines = fs.readFileSync(sqlFile, 'utf8').split("\n");; + var count = lines.length; var keyNo = 0; - file.split("\n").forEach(function(l) { + lines.forEach(function(l) { if (l.substr(0, 27) == "REPLACE INTO store VALUES (") { var pos = l.indexOf("', '"); var key = l.substr(28, pos - 28); @@ -31,8 +32,13 @@ require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) { value = value.substr(0, value.length - 3); db.db.set(key, value, null); keyNo++; + process.stdout.write("."); + if (keyNo % 100 == 0) { + console.log(" " + keyNo + "/" + count); + } } }); + process.stdout.write("\n"); db.db.doShutdown(function() { log("finished, imported " + keyNo + " keys."); From 76fbc2960728ee9f6b7058e03ace0064b9302644 Mon Sep 17 00:00:00 2001 From: CeBe Date: Wed, 6 Mar 2013 22:36:00 +0100 Subject: [PATCH 07/32] improved output for importSqlFile --- bin/importSqlFile.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/importSqlFile.js b/bin/importSqlFile.js index b43a0622e..bdc2c4345 100644 --- a/bin/importSqlFile.js +++ b/bin/importSqlFile.js @@ -24,6 +24,7 @@ require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) { 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("', '"); @@ -32,9 +33,8 @@ require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) { value = value.substr(0, value.length - 3); db.db.set(key, value, null); keyNo++; - process.stdout.write("."); - if (keyNo % 100 == 0) { - console.log(" " + keyNo + "/" + count); + if (keyNo % 1000 == 0) { + process.stdout.write(" " + keyNo + "/" + count); } } }); From f2b173f566e6c53caa1f045a714c52753caec710 Mon Sep 17 00:00:00 2001 From: CeBe Date: Wed, 6 Mar 2013 22:38:18 +0100 Subject: [PATCH 08/32] improved output for importSqlFile --- bin/importSqlFile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/importSqlFile.js b/bin/importSqlFile.js index bdc2c4345..faf6b1b9a 100644 --- a/bin/importSqlFile.js +++ b/bin/importSqlFile.js @@ -34,7 +34,7 @@ require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) { db.db.set(key, value, null); keyNo++; if (keyNo % 1000 == 0) { - process.stdout.write(" " + keyNo + "/" + count); + process.stdout.write(" " + keyNo + "/" + count + "\n"); } } }); From 4026ba18156b0551191e7ac62a077ab16d694e25 Mon Sep 17 00:00:00 2001 From: CeBe Date: Thu, 7 Mar 2013 13:15:29 +0100 Subject: [PATCH 09/32] fixed saved data to be escaped properly --- bin/importSqlFile.js | 108 ++++++++++++++++++++++++++++++++----------- 1 file changed, 82 insertions(+), 26 deletions(-) diff --git a/bin/importSqlFile.js b/bin/importSqlFile.js index faf6b1b9a..6463088a9 100644 --- a/bin/importSqlFile.js +++ b/bin/importSqlFile.js @@ -3,7 +3,17 @@ var startTime = new Date().getTime(); require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) { var fs = require("fs"); - var db = require("ep_etherpad-lite/node/db/DB");; + + 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]; @@ -15,39 +25,85 @@ require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) { } log("initializing db"); - db.init(function(){ - log("done"); + 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");; + log("open output file..."); + var lines = fs.readFileSync(sqlFile, 'utf8').split("\n"); - var count = lines.length; - var keyNo = 0; + 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 + 4); - value = value.substr(0, value.length - 3); - db.db.set(key, value, null); - keyNo++; - if (keyNo % 1000 == 0) { - process.stdout.write(" " + keyNo + "/" + count + "\n"); + 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("\n"); - db.db.doShutdown(function() { - log("finished, imported " + keyNo + " keys."); - process.exit(0); - }); + db.doShutdown(function() { + log("finished, imported " + keyNo + " keys."); + process.exit(0); + }); + } }); }); function log(str) { console.log((new Date().getTime() - startTime)/1000 + "\t" + str); -} \ No newline at end of file +} + +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; +}; From 70c329957d0a488f30df138f56ab5d975b0bd54b Mon Sep 17 00:00:00 2001 From: CeBe Date: Thu, 7 Mar 2013 14:05:55 +0100 Subject: [PATCH 10/32] additional ouput for importSqlFile --- bin/importSqlFile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/importSqlFile.js b/bin/importSqlFile.js index 6463088a9..cd1e7df3b 100644 --- a/bin/importSqlFile.js +++ b/bin/importSqlFile.js @@ -61,6 +61,7 @@ require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) { } }); process.stdout.write("\n"); + process.stdout.wirte("done. waiting for db to finish transaction. depended on dbms this may take some time...\n"); db.doShutdown(function() { log("finished, imported " + keyNo + " keys."); From 62c13b4c3fee68de0935648b889f509490e95caa Mon Sep 17 00:00:00 2001 From: CeBe Date: Thu, 7 Mar 2013 14:10:54 +0100 Subject: [PATCH 11/32] typo --- bin/importSqlFile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/importSqlFile.js b/bin/importSqlFile.js index cd1e7df3b..6491cbea1 100644 --- a/bin/importSqlFile.js +++ b/bin/importSqlFile.js @@ -61,7 +61,7 @@ require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) { } }); process.stdout.write("\n"); - process.stdout.wirte("done. waiting for db to finish transaction. depended on dbms this may take some time...\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."); From 3cafa249829685651073004b8c255aa07a8d5bcd Mon Sep 17 00:00:00 2001 From: Jordan Hollinger Date: Thu, 7 Mar 2013 09:37:03 -0500 Subject: [PATCH 12/32] Fix variable name typo in PadMessageHandler.padUsers --- src/node/handler/PadMessageHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 15a9b8eab..c046f130a 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1448,7 +1448,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) { From 26a6765b50ffdb4c2739d7c5ec3e24bdd9e7dc9f Mon Sep 17 00:00:00 2001 From: John McLear Date: Fri, 8 Mar 2013 08:40:40 -0800 Subject: [PATCH 13/32] fix indent on chrome in linux --- src/static/js/ace2_inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index b8b59b84d..e57bb36e5 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; From acb4b4ebafd366c85662cf5de2a9b15df7eb170b Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Mon, 11 Mar 2013 04:52:12 +0000 Subject: [PATCH 14/32] Localisation updates from http://translatewiki.net. --- src/locales/fa.json | 8 ++++++-- src/locales/ia.json | 32 ++++++++++++++++++++++++++++++-- src/locales/te.json | 1 + 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/locales/fa.json b/src/locales/fa.json index 437b8da09..bccc353c2 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 21b1d2919..e6c5dde11 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, 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 666a40aa0..955b263ab 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", From b4ec07312b8926f29623f23548dfc0b2b6554902 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 13 Mar 2013 15:00:04 -0300 Subject: [PATCH 15/32] add keystroke tests for relevant buttonpresses and change naming schema to something more sane --- .../{keystroke_alphabet.js => alphabet.js} | 0 .../specs/{button_bold.js => bold.js} | 37 +++++++++- tests/frontend/specs/button_italic.js | 36 ---------- .../specs/{keystroke_chat.js => chat.js} | 35 +++++++++- tests/frontend/specs/chat_always_on_screen.js | 40 ----------- ...p_colors.js => clear_authorship_colors.js} | 0 .../specs/{keystroke_delete.js => delete.js} | 0 .../specs/{keystroke_enter.js => enter.js} | 0 .../{button_indentation.js => indentation.js} | 22 +++++- tests/frontend/specs/italic.js | 67 +++++++++++++++++++ tests/frontend/specs/language.js | 3 +- ...button_ordered_list.js => ordered_list.js} | 0 .../specs/{button_redo.js => redo.js} | 35 +++++++++- ...tton_strikethrough.js => strikethrough.js} | 0 .../{button_timeslider.js => timeslider.js} | 0 .../specs/{button_undo.js => undo.js} | 32 ++++++++- ..._clickable.js => urls_become_clickable.js} | 0 17 files changed, 222 insertions(+), 85 deletions(-) rename tests/frontend/specs/{keystroke_alphabet.js => alphabet.js} (100%) rename tests/frontend/specs/{button_bold.js => bold.js} (51%) delete mode 100644 tests/frontend/specs/button_italic.js rename tests/frontend/specs/{keystroke_chat.js => chat.js} (69%) delete mode 100644 tests/frontend/specs/chat_always_on_screen.js rename tests/frontend/specs/{button_clear_authorship_colors.js => clear_authorship_colors.js} (100%) rename tests/frontend/specs/{keystroke_delete.js => delete.js} (100%) rename tests/frontend/specs/{keystroke_enter.js => enter.js} (100%) rename tests/frontend/specs/{button_indentation.js => indentation.js} (90%) create mode 100644 tests/frontend/specs/italic.js rename tests/frontend/specs/{button_ordered_list.js => ordered_list.js} (100%) rename tests/frontend/specs/{button_redo.js => redo.js} (50%) rename tests/frontend/specs/{button_strikethrough.js => strikethrough.js} (100%) rename tests/frontend/specs/{button_timeslider.js => timeslider.js} (100%) rename tests/frontend/specs/{button_undo.js => undo.js} (50%) rename tests/frontend/specs/{keystroke_urls_become_clickable.js => urls_become_clickable.js} (100%) diff --git a/tests/frontend/specs/keystroke_alphabet.js b/tests/frontend/specs/alphabet.js similarity index 100% rename from tests/frontend/specs/keystroke_alphabet.js rename to tests/frontend/specs/alphabet.js diff --git a/tests/frontend/specs/button_bold.js b/tests/frontend/specs/bold.js similarity index 51% rename from tests/frontend/specs/button_bold.js rename to tests/frontend/specs/bold.js index 1feafe614..b5d2a212b 100644 --- a/tests/frontend/specs/button_bold.js +++ b/tests/frontend/specs/bold.js @@ -5,7 +5,7 @@ describe("bold button", function(){ this.timeout(60000); }); - it("makes text bold", function(done) { + it("makes text bold on click", function(done) { var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; @@ -33,4 +33,37 @@ describe("bold button", function(){ done(); }); -}); \ No newline at end of file + + 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}'); + + var e = inner$.Event("keydown"); + e.ctrlKey = true; // Control key + e.which = 66; // z + 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 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_italic.js b/tests/frontend/specs/button_italic.js deleted file mode 100644 index fc2e15a79..000000000 --- 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 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/keystroke_chat.js b/tests/frontend/specs/chat.js similarity index 69% rename from tests/frontend/specs/keystroke_chat.js rename to tests/frontend/specs/chat.js index e49087286..a488193f7 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 4873763fe..000000000 --- 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 similarity index 100% rename from tests/frontend/specs/button_clear_authorship_colors.js rename to tests/frontend/specs/clear_authorship_colors.js diff --git a/tests/frontend/specs/keystroke_delete.js b/tests/frontend/specs/delete.js similarity index 100% rename from tests/frontend/specs/keystroke_delete.js rename to tests/frontend/specs/delete.js diff --git a/tests/frontend/specs/keystroke_enter.js b/tests/frontend/specs/enter.js similarity index 100% rename from tests/frontend/specs/keystroke_enter.js rename to tests/frontend/specs/enter.js diff --git a/tests/frontend/specs/button_indentation.js b/tests/frontend/specs/indentation.js similarity index 90% rename from tests/frontend/specs/button_indentation.js rename to tests/frontend/specs/indentation.js index 9c8e317e6..06d90aa86 100644 --- a/tests/frontend/specs/button_indentation.js +++ b/tests/frontend/specs/indentation.js @@ -5,7 +5,26 @@ 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}'); + + var e = inner$.Event("keydown"); + 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 +195,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 000000000..052d2df4f --- /dev/null +++ b/tests/frontend/specs/italic.js @@ -0,0 +1,67 @@ +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 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}'); + + var e = inner$.Event("keydown"); + 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 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 ab7f2b3d7..d607ff982 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 similarity index 100% rename from tests/frontend/specs/button_ordered_list.js rename to tests/frontend/specs/ordered_list.js diff --git a/tests/frontend/specs/button_redo.js b/tests/frontend/specs/redo.js similarity index 50% rename from tests/frontend/specs/button_redo.js rename to tests/frontend/specs/redo.js index 3ce69142f..c14972219 100644 --- a/tests/frontend/specs/button_redo.js +++ b/tests/frontend/specs/redo.js @@ -4,7 +4,7 @@ describe("undo button then redo button", function(){ this.timeout(60000); }); - it("undo some typing", function(done){ + it("redo some typing with button", function(done){ var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; @@ -33,5 +33,38 @@ describe("undo button then redo button", function(){ 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 + + var e = inner$.Event("keydown"); + e.ctrlKey = true; // Control key + e.which = 90; // z + inner$("#innerdocbody").trigger(e); + + var e = inner$.Event("keydown"); + 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 similarity index 100% rename from tests/frontend/specs/button_strikethrough.js rename to tests/frontend/specs/strikethrough.js diff --git a/tests/frontend/specs/button_timeslider.js b/tests/frontend/specs/timeslider.js similarity index 100% rename from tests/frontend/specs/button_timeslider.js rename to tests/frontend/specs/timeslider.js diff --git a/tests/frontend/specs/button_undo.js b/tests/frontend/specs/undo.js similarity index 50% rename from tests/frontend/specs/button_undo.js rename to tests/frontend/specs/undo.js index 412b786be..8ba752ac5 100644 --- a/tests/frontend/specs/button_undo.js +++ b/tests/frontend/specs/undo.js @@ -4,7 +4,8 @@ describe("undo button", function(){ this.timeout(60000); }); - it("undo some typing", function(done){ +/* + it("undo some typing by clicking undo button", function(done){ var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; @@ -29,5 +30,34 @@ describe("undo button", function(){ 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 + + var e = inner$.Event("keydown"); + 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 similarity index 100% rename from tests/frontend/specs/keystroke_urls_become_clickable.js rename to tests/frontend/specs/urls_become_clickable.js From 5690f2d01ea04806ffada6eb395750a8f2007de3 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 13 Mar 2013 15:06:08 -0300 Subject: [PATCH 16/32] not z, is b! --- tests/frontend/specs/bold.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/frontend/specs/bold.js b/tests/frontend/specs/bold.js index b5d2a212b..2fb6bbfeb 100644 --- a/tests/frontend/specs/bold.js +++ b/tests/frontend/specs/bold.js @@ -46,7 +46,7 @@ describe("bold button", function(){ var e = inner$.Event("keydown"); e.ctrlKey = true; // Control key - e.which = 66; // z + 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 @@ -63,7 +63,4 @@ describe("bold button", function(){ done(); }); - - - }); From b81be97f94f1b973a60021e9db7116f8d2b8b039 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 13 Mar 2013 15:08:19 -0300 Subject: [PATCH 17/32] typo --- tests/frontend/specs/enter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/frontend/specs/enter.js b/tests/frontend/specs/enter.js index e46b1d2fe..baafeded2 100644 --- a/tests/frontend/specs/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$; From babb33d8251e93db2eb9472a4e46b3b723b0992f Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 12 Mar 2013 17:34:15 +0000 Subject: [PATCH 18/32] add authorId to chat and userlist, possibly privacy/security issue? --- src/static/js/chat.js | 2 +- src/static/js/pad_userlist.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 2dff2edf3..83a487dee 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 = "

" + authorName + ":" + timeStr + " " + text + "

"; + var html = "

" + authorName + ":" + timeStr + " " + text + "

"; if(isHistoryAdd) $(html).insertAfter('#chatloadmessagesbutton'); else diff --git a/src/static/js/pad_userlist.js b/src/static/js/pad_userlist.js index 962595d2b..c8f8e2c9e 100644 --- a/src/static/js/pad_userlist.js +++ b/src/static/js/pad_userlist.js @@ -119,9 +119,9 @@ var paduserlist = (function() return ['
 
', '', nameHtml, '', '', padutils.escapeHtml(data.activity), ''].join(''); } - function getRowHtml(id, innerHtml) + function getRowHtml(id, innerHtml, authorId) { - return '' + innerHtml + ''; + return '' + innerHtml + ''; } 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) From c30b0b72b85b3adc5fb00c5d082ab05c3d2c1efc Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 13 Mar 2013 22:23:35 +0100 Subject: [PATCH 19/32] Validate all 'author' attribs of incoming changesets to be the same value as the current user's authorId --- src/node/handler/PadMessageHandler.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index c046f130a..35f1ab4cc 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -550,11 +550,16 @@ function handleUserChanges(client, message) throw "Attribute pool is missing attribute "+n+" for changeset "+changeset; } }); + + // Validate all 'author' attribs to be the same value as the current user + wireApool.eachAttrib(function(type, value) { + if('author' == type && value != 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; } From 5fe60e72218d6371158a29ed5cc345b37a16b27a Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 14 Mar 2013 15:59:39 +0100 Subject: [PATCH 20/32] redirect /admin to /admin/ so that the relative links work --- src/node/hooks/express/admin.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node/hooks/express/admin.js b/src/node/hooks/express/admin.js index 766370fc3..70539f0c4 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", {}) ); }); } From 12107859bb2325c5ba7b7bf0efeaa76682b5edb7 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 Mar 2013 13:41:49 -0300 Subject: [PATCH 21/32] fix tests in firefox as firefox fires on keypress not down --- tests/frontend/specs/bold.js | 2 +- tests/frontend/specs/indentation.js | 2 +- tests/frontend/specs/italic.js | 2 +- tests/frontend/specs/redo.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/frontend/specs/bold.js b/tests/frontend/specs/bold.js index 2fb6bbfeb..010d69010 100644 --- a/tests/frontend/specs/bold.js +++ b/tests/frontend/specs/bold.js @@ -44,7 +44,7 @@ describe("bold button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - var e = inner$.Event("keydown"); + var e = inner$.Event("keypress"); e.ctrlKey = true; // Control key e.which = 66; // b inner$("#innerdocbody").trigger(e); diff --git a/tests/frontend/specs/indentation.js b/tests/frontend/specs/indentation.js index 06d90aa86..6b9b432da 100644 --- a/tests/frontend/specs/indentation.js +++ b/tests/frontend/specs/indentation.js @@ -15,7 +15,7 @@ describe("indentation button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - var e = inner$.Event("keydown"); + var e = inner$.Event("keypress"); e.keyCode = 9; // tab :| inner$("#innerdocbody").trigger(e); diff --git a/tests/frontend/specs/italic.js b/tests/frontend/specs/italic.js index 052d2df4f..15976f295 100644 --- a/tests/frontend/specs/italic.js +++ b/tests/frontend/specs/italic.js @@ -44,7 +44,7 @@ describe("italic some text", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - var e = inner$.Event("keydown"); + var e = inner$.Event("keypress"); e.ctrlKey = true; // Control key e.which = 105; // i inner$("#innerdocbody").trigger(e); diff --git a/tests/frontend/specs/redo.js b/tests/frontend/specs/redo.js index c14972219..d72b5a096 100644 --- a/tests/frontend/specs/redo.js +++ b/tests/frontend/specs/redo.js @@ -47,7 +47,7 @@ describe("undo button then redo button", function(){ var modifiedValue = $firstTextElement.text(); // get the modified value expect(modifiedValue).not.to.be(originalValue); // expect the value to change - var e = inner$.Event("keydown"); + var e = inner$.Event("keypress"); e.ctrlKey = true; // Control key e.which = 90; // z inner$("#innerdocbody").trigger(e); From 34c2cf40faae53ef33980378ca7f5e704b91be07 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 Mar 2013 13:51:23 -0300 Subject: [PATCH 22/32] This isn't ideal, basically some browsers interact with keypress/keydown in different ways so this is a workaround but it's not perma --- tests/frontend/specs/bold.js | 8 +++++++- tests/frontend/specs/indentation.js | 8 +++++++- tests/frontend/specs/italic.js | 8 +++++++- tests/frontend/specs/redo.js | 8 +++++++- tests/frontend/specs/undo.js | 8 +++++++- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/tests/frontend/specs/bold.js b/tests/frontend/specs/bold.js index 010d69010..7c04835e2 100644 --- a/tests/frontend/specs/bold.js +++ b/tests/frontend/specs/bold.js @@ -44,7 +44,13 @@ describe("bold button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - var e = inner$.Event("keypress"); + if(!inner$.browser.chrome){ + 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); diff --git a/tests/frontend/specs/indentation.js b/tests/frontend/specs/indentation.js index 6b9b432da..6e5b3c1a0 100644 --- a/tests/frontend/specs/indentation.js +++ b/tests/frontend/specs/indentation.js @@ -15,7 +15,13 @@ describe("indentation button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - var e = inner$.Event("keypress"); + if(!inner$.browser.chrome){ + var evtType = "keypress"; + }else{ + var evtType = "keydown"; + } + + var e = inner$.Event(evtType); e.keyCode = 9; // tab :| inner$("#innerdocbody").trigger(e); diff --git a/tests/frontend/specs/italic.js b/tests/frontend/specs/italic.js index 15976f295..9bee72dcb 100644 --- a/tests/frontend/specs/italic.js +++ b/tests/frontend/specs/italic.js @@ -44,7 +44,13 @@ describe("italic some text", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - var e = inner$.Event("keypress"); + if(!inner$.browser.chrome){ + 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); diff --git a/tests/frontend/specs/redo.js b/tests/frontend/specs/redo.js index d72b5a096..059e97111 100644 --- a/tests/frontend/specs/redo.js +++ b/tests/frontend/specs/redo.js @@ -47,7 +47,13 @@ describe("undo button then redo button", function(){ var modifiedValue = $firstTextElement.text(); // get the modified value expect(modifiedValue).not.to.be(originalValue); // expect the value to change - var e = inner$.Event("keypress"); + if(!inner$.browser.chrome){ + 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); diff --git a/tests/frontend/specs/undo.js b/tests/frontend/specs/undo.js index 8ba752ac5..4e9dd0d57 100644 --- a/tests/frontend/specs/undo.js +++ b/tests/frontend/specs/undo.js @@ -44,7 +44,13 @@ describe("undo button", function(){ var modifiedValue = $firstTextElement.text(); // get the modified value expect(modifiedValue).not.to.be(originalValue); // expect the value to change - var e = inner$.Event("keydown"); + if(!inner$.browser.chrome){ + 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); From 6bac01009b6706c63a63a8b6f63af99fb2f8e60e Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 Mar 2013 13:52:20 -0300 Subject: [PATCH 23/32] missed an evt --- tests/frontend/specs/redo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/frontend/specs/redo.js b/tests/frontend/specs/redo.js index 059e97111..14ec31f62 100644 --- a/tests/frontend/specs/redo.js +++ b/tests/frontend/specs/redo.js @@ -58,7 +58,7 @@ describe("undo button then redo button", function(){ e.which = 90; // z inner$("#innerdocbody").trigger(e); - var e = inner$.Event("keydown"); + var e = inner$.Event(evtType); e.ctrlKey = true; // Control key e.which = 121; // y inner$("#innerdocbody").trigger(e); From 1462d8e80c3a20486cee95cf10c1bc3d7724c6d1 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 Mar 2013 14:22:58 -0300 Subject: [PATCH 24/32] now IE friendly --- tests/frontend/specs/bold.js | 6 +++--- tests/frontend/specs/indentation.js | 6 +++--- tests/frontend/specs/italic.js | 6 +++--- tests/frontend/specs/redo.js | 6 +++--- tests/frontend/specs/undo.js | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/frontend/specs/bold.js b/tests/frontend/specs/bold.js index 7c04835e2..0bdbbc843 100644 --- a/tests/frontend/specs/bold.js +++ b/tests/frontend/specs/bold.js @@ -44,10 +44,10 @@ describe("bold button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(!inner$.browser.chrome){ - var evtType = "keypress"; - }else{ + if(!inner$.browser.firefox){ var evtType = "keydown"; + }else{ + var evtType = "keypress"; } var e = inner$.Event(evtType); diff --git a/tests/frontend/specs/indentation.js b/tests/frontend/specs/indentation.js index 6e5b3c1a0..81ffc78e7 100644 --- a/tests/frontend/specs/indentation.js +++ b/tests/frontend/specs/indentation.js @@ -15,10 +15,10 @@ describe("indentation button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(!inner$.browser.chrome){ - var evtType = "keypress"; - }else{ + if(!inner$.browser.firefox){ var evtType = "keydown"; + }else{ + var evtType = "keypress"; } var e = inner$.Event(evtType); diff --git a/tests/frontend/specs/italic.js b/tests/frontend/specs/italic.js index 9bee72dcb..be226d489 100644 --- a/tests/frontend/specs/italic.js +++ b/tests/frontend/specs/italic.js @@ -44,10 +44,10 @@ describe("italic some text", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(!inner$.browser.chrome){ - var evtType = "keypress"; - }else{ + if(!inner$.browser.firefox){ var evtType = "keydown"; + }else{ + var evtType = "keypress"; } var e = inner$.Event(evtType); diff --git a/tests/frontend/specs/redo.js b/tests/frontend/specs/redo.js index 14ec31f62..7595da599 100644 --- a/tests/frontend/specs/redo.js +++ b/tests/frontend/specs/redo.js @@ -47,10 +47,10 @@ describe("undo button then redo button", function(){ var modifiedValue = $firstTextElement.text(); // get the modified value expect(modifiedValue).not.to.be(originalValue); // expect the value to change - if(!inner$.browser.chrome){ - var evtType = "keypress"; - }else{ + if(!inner$.browser.firefox){ var evtType = "keydown"; + }else{ + var evtType = "keypress"; } var e = inner$.Event(evtType); diff --git a/tests/frontend/specs/undo.js b/tests/frontend/specs/undo.js index 4e9dd0d57..9152c884d 100644 --- a/tests/frontend/specs/undo.js +++ b/tests/frontend/specs/undo.js @@ -44,10 +44,10 @@ describe("undo button", function(){ var modifiedValue = $firstTextElement.text(); // get the modified value expect(modifiedValue).not.to.be(originalValue); // expect the value to change - if(!inner$.browser.chrome){ - var evtType = "keypress"; - }else{ + if(!inner$.browser.firefox){ var evtType = "keydown"; + }else{ + var evtType = "keypress"; } var e = inner$.Event(evtType); From 24188d70076eb6368b31a41466b567e7e59186f8 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 Mar 2013 14:36:54 -0300 Subject: [PATCH 25/32] this should pass more tests.. --- tests/frontend/specs/bold.js | 7 +++---- tests/frontend/specs/indentation.js | 6 +++--- tests/frontend/specs/italic.js | 6 +++--- tests/frontend/specs/redo.js | 6 +++--- tests/frontend/specs/undo.js | 4 ++-- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/frontend/specs/bold.js b/tests/frontend/specs/bold.js index 0bdbbc843..95da7331c 100644 --- a/tests/frontend/specs/bold.js +++ b/tests/frontend/specs/bold.js @@ -43,11 +43,10 @@ describe("bold button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - - if(!inner$.browser.firefox){ - var evtType = "keydown"; - }else{ + if(inner$.browser.mozilla){ // if it's a mozilla browser var evtType = "keypress"; + }else{ + var evtType = "keydown"; } var e = inner$.Event(evtType); diff --git a/tests/frontend/specs/indentation.js b/tests/frontend/specs/indentation.js index 81ffc78e7..9692120ae 100644 --- a/tests/frontend/specs/indentation.js +++ b/tests/frontend/specs/indentation.js @@ -15,10 +15,10 @@ describe("indentation button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(!inner$.browser.firefox){ - var evtType = "keydown"; - }else{ + if(inner$.browser.mozilla){ // if it's a mozilla browser var evtType = "keypress"; + }else{ + var evtType = "keydown"; } var e = inner$.Event(evtType); diff --git a/tests/frontend/specs/italic.js b/tests/frontend/specs/italic.js index be226d489..29dbae59c 100644 --- a/tests/frontend/specs/italic.js +++ b/tests/frontend/specs/italic.js @@ -44,10 +44,10 @@ describe("italic some text", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(!inner$.browser.firefox){ - var evtType = "keydown"; - }else{ + if(inner$.browser.mozilla){ // if it's a mozilla browser var evtType = "keypress"; + }else{ + var evtType = "keydown"; } var e = inner$.Event(evtType); diff --git a/tests/frontend/specs/redo.js b/tests/frontend/specs/redo.js index 7595da599..c2f8a95a6 100644 --- a/tests/frontend/specs/redo.js +++ b/tests/frontend/specs/redo.js @@ -47,10 +47,10 @@ describe("undo button then redo button", function(){ var modifiedValue = $firstTextElement.text(); // get the modified value expect(modifiedValue).not.to.be(originalValue); // expect the value to change - if(!inner$.browser.firefox){ - var evtType = "keydown"; - }else{ + if(inner$.browser.mozilla){ // if it's a mozilla browser var evtType = "keypress"; + }else{ + var evtType = "keydown"; } var e = inner$.Event(evtType); diff --git a/tests/frontend/specs/undo.js b/tests/frontend/specs/undo.js index 9152c884d..6fed22e38 100644 --- a/tests/frontend/specs/undo.js +++ b/tests/frontend/specs/undo.js @@ -45,9 +45,9 @@ describe("undo button", function(){ expect(modifiedValue).not.to.be(originalValue); // expect the value to change if(!inner$.browser.firefox){ - var evtType = "keydown"; - }else{ var evtType = "keypress"; + }else{ + var evtType = "keydown"; } var e = inner$.Event(evtType); From 29c0d790b51f187d6ce74fe8603013544a9217df Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 Mar 2013 14:48:23 -0300 Subject: [PATCH 26/32] fix undo test --- tests/frontend/specs/undo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/frontend/specs/undo.js b/tests/frontend/specs/undo.js index 6fed22e38..0c58c9b84 100644 --- a/tests/frontend/specs/undo.js +++ b/tests/frontend/specs/undo.js @@ -44,7 +44,7 @@ describe("undo button", function(){ var modifiedValue = $firstTextElement.text(); // get the modified value expect(modifiedValue).not.to.be(originalValue); // expect the value to change - if(!inner$.browser.firefox){ + if(inner$.browser.mozilla){ // if it's a mozilla browser var evtType = "keypress"; }else{ var evtType = "keydown"; From d72abceee7556c1337e0fde1d04b24b40a554cfb Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 Mar 2013 18:18:14 -0300 Subject: [PATCH 27/32] escape .color --- src/static/js/pad_userlist.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/pad_userlist.js b/src/static/js/pad_userlist.js index c8f8e2c9e..77ebb190e 100644 --- a/src/static/js/pad_userlist.js +++ b/src/static/js/pad_userlist.js @@ -116,7 +116,7 @@ var paduserlist = (function() nameHtml = ''; } - return ['
 
', '', nameHtml, '', '', padutils.escapeHtml(data.activity), ''].join(''); + return ['
 
', '', nameHtml, '', '', padutils.escapeHtml(data.activity), ''].join(''); } function getRowHtml(id, innerHtml, authorId) From 1bb9d1d6259983b1bcbdfa72f17872516f55a5c4 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 Mar 2013 18:23:27 -0300 Subject: [PATCH 28/32] remove pointless + --- src/static/js/ace2_inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index e57bb36e5..2dc6408b1 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -5125,7 +5125,7 @@ function Ace2Inner(){ function initLineNumbers() { lineNumbersShown = 1; - sideDiv.innerHTML = '' + '
1
'; + sideDiv.innerHTML = '
1
'; sideDivInner = outerWin.document.getElementById("sidedivinner"); } From 383439629a46c29578fded5db4bf9a2c07d6aa2f Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 Mar 2013 18:27:32 -0300 Subject: [PATCH 29/32] specialkey doesnt even exist afaik --- src/static/js/pad.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 4b0526208..7cefc41e9 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); From 5d12be940c6ed3062637be0f940a25f92702fb71 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 14 Mar 2013 18:28:35 -0300 Subject: [PATCH 30/32] return text instead of html --- src/static/js/pad.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 7cefc41e9..60a435572 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -655,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) From de552df6db435717912bad3aad558d343c23d8f8 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 15 Mar 2013 18:08:51 +0100 Subject: [PATCH 31/32] Fix clearing authorship colors which was broken by #1609 Fixes #1620 --- src/node/handler/PadMessageHandler.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 35f1ab4cc..4b98292ec 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -551,10 +551,19 @@ function handleUserChanges(client, message) } }); - // Validate all 'author' attribs to be the same value as the current user - wireApool.eachAttrib(function(type, value) { - if('author' == type && value != thisSession.author) throw "Trying to submit changes as another author" - }) + // 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) { From 54433db47f5083faa921da6050b4e4e4b55dfb20 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 15 Mar 2013 21:43:29 +0100 Subject: [PATCH 32/32] release v1.2.9 --- CHANGELOG.md | 12 ++++++++++++ src/package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fac58f2a..642846a6e 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/src/package.json b/src/package.json index 0e418edea..a7147cf2a 100644 --- a/src/package.json +++ b/src/package.json @@ -46,5 +46,5 @@ "engines" : { "node" : ">=0.6.3", "npm" : ">=1.0" }, - "version" : "1.2.81" + "version" : "1.2.9" }