mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-05-14 19:16:54 -04:00
Merge e4032f4d74
into 7a0ad3235a
This commit is contained in:
commit
a8bc88f8d4
15 changed files with 452 additions and 768 deletions
|
@ -325,17 +325,17 @@ exports.getChatHistory = function(padID, start, end, callback)
|
||||||
if(!start || !end)
|
if(!start || !end)
|
||||||
{
|
{
|
||||||
start = 0;
|
start = 0;
|
||||||
end = pad.chatHead - 1;
|
end = pad.chatHead;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(start >= chatHead)
|
if(start >= chatHead && chatHead > 0)
|
||||||
{
|
{
|
||||||
callback(new customError("start is higher or equal to the current chatHead","apierror"));
|
callback(new customError("start is higher or equal to the current chatHead","apierror"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(end >= chatHead)
|
if(end > chatHead)
|
||||||
{
|
{
|
||||||
callback(new customError("end is higher or equal to the current chatHead","apierror"));
|
callback(new customError("end is higher than the current chatHead","apierror"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,11 +35,6 @@ var messageLogger = log4js.getLogger("message");
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
||||||
|
|
||||||
/**
|
|
||||||
* A associative array that saves which sessions belong to a pad
|
|
||||||
*/
|
|
||||||
var pad2sessions = {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A associative array that saves informations about a session
|
* A associative array that saves informations about a session
|
||||||
* key = sessionId
|
* key = sessionId
|
||||||
|
@ -83,14 +78,11 @@ exports.handleConnect = function(client)
|
||||||
exports.kickSessionsFromPad = function(padID)
|
exports.kickSessionsFromPad = function(padID)
|
||||||
{
|
{
|
||||||
//skip if there is nobody on this pad
|
//skip if there is nobody on this pad
|
||||||
if(!pad2sessions[padID])
|
if(socketio.sockets.clients(padID).length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//disconnect everyone from this pad
|
//disconnect everyone from this pad
|
||||||
for(var i in pad2sessions[padID])
|
socketio.sockets.in(padID).json.send({disconnect:"deleted"});
|
||||||
{
|
|
||||||
socketio.sockets.sockets[pad2sessions[padID][i]].json.send({disconnect:"deleted"});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,15 +92,13 @@ exports.kickSessionsFromPad = function(padID)
|
||||||
exports.handleDisconnect = function(client)
|
exports.handleDisconnect = function(client)
|
||||||
{
|
{
|
||||||
//save the padname of this session
|
//save the padname of this session
|
||||||
var sessionPad=sessioninfos[client.id].padId;
|
var session = sessioninfos[client.id];
|
||||||
|
|
||||||
//if this connection was already etablished with a handshake, send a disconnect message to the others
|
//if this connection was already etablished with a handshake, send a disconnect message to the others
|
||||||
if(sessioninfos[client.id] && sessioninfos[client.id].author)
|
if(session && session.author)
|
||||||
{
|
{
|
||||||
var author = sessioninfos[client.id].author;
|
|
||||||
|
|
||||||
//get the author color out of the db
|
//get the author color out of the db
|
||||||
authorManager.getAuthorColorId(author, function(err, color)
|
authorManager.getAuthorColorId(session.author, function(err, color)
|
||||||
{
|
{
|
||||||
ERR(err);
|
ERR(err);
|
||||||
|
|
||||||
|
@ -121,33 +111,16 @@ exports.handleDisconnect = function(client)
|
||||||
"ip": "127.0.0.1",
|
"ip": "127.0.0.1",
|
||||||
"colorId": color,
|
"colorId": color,
|
||||||
"userAgent": "Anonymous",
|
"userAgent": "Anonymous",
|
||||||
"userId": author
|
"userId": session.author
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//Go trough all user that are still on the pad, and send them the USER_LEAVE message
|
//Go trough all user that are still on the pad, and send them the USER_LEAVE message
|
||||||
for(i in pad2sessions[sessionPad])
|
client.broadcast.to(session.padId).json.send(messageToTheOtherUsers);
|
||||||
{
|
|
||||||
var socket = socketio.sockets.sockets[pad2sessions[sessionPad][i]];
|
|
||||||
if(socket !== undefined){
|
|
||||||
socket.json.send(messageToTheOtherUsers);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Go trough all sessions of this pad, search and destroy the entry of this client
|
|
||||||
for(i in pad2sessions[sessionPad])
|
|
||||||
{
|
|
||||||
if(pad2sessions[sessionPad][i] == client.id)
|
|
||||||
{
|
|
||||||
pad2sessions[sessionPad].splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Delete the sessioninfos entrys of this session
|
//Delete the sessioninfos entrys of this session
|
||||||
delete sessioninfos[client.id];
|
delete sessioninfos[client.id];
|
||||||
}
|
}
|
||||||
|
@ -228,11 +201,10 @@ exports.handleMessage = function(client, message)
|
||||||
function(callback)
|
function(callback)
|
||||||
{
|
{
|
||||||
|
|
||||||
if(!message.padId){
|
// If the message has a padId we assume the client is already known to the server and needs no re-authorization
|
||||||
// If the message has a padId we assume the client is already known to the server and needs no re-authorization
|
if(!message.padId)
|
||||||
callback();
|
return callback();
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Note: message.sessionID is an entirely different kind of
|
// Note: message.sessionID is an entirely different kind of
|
||||||
// session from the sessions we use here! Beware! FIXME: Call
|
// session from the sessions we use here! Beware! FIXME: Call
|
||||||
// our "sessions" "connections".
|
// our "sessions" "connections".
|
||||||
|
@ -292,9 +264,7 @@ exports.handleCustomMessage = function (padID, msg, cb) {
|
||||||
time: time
|
time: time
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (var i in pad2sessions[padID]) {
|
socketio.sockets.in(padID).json.send(msg);
|
||||||
socketio.sockets.sockets[pad2sessions[padID][i]].json.send(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
cb(null, {});
|
cb(null, {});
|
||||||
}
|
}
|
||||||
|
@ -352,10 +322,7 @@ function handleChatMessage(client, message)
|
||||||
};
|
};
|
||||||
|
|
||||||
//broadcast the chat message to everyone on the pad
|
//broadcast the chat message to everyone on the pad
|
||||||
for(var i in pad2sessions[padId])
|
socketio.sockets.in(padId).json.send(msg);
|
||||||
{
|
|
||||||
socketio.sockets.sockets[pad2sessions[padId][i]].json.send(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
@ -413,23 +380,16 @@ function handleGetChatMessages(client, message)
|
||||||
{
|
{
|
||||||
if(ERR(err, callback)) return;
|
if(ERR(err, callback)) return;
|
||||||
|
|
||||||
var infoMsg = {
|
var infoMsg = {
|
||||||
type: "COLLABROOM",
|
type: "COLLABROOM",
|
||||||
data: {
|
data: {
|
||||||
type: "CHAT_MESSAGES",
|
type: "CHAT_MESSAGES",
|
||||||
messages: chatMessages
|
messages: chatMessages
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// send the messages back to the client
|
|
||||||
for(var i in pad2sessions[padId])
|
|
||||||
{
|
|
||||||
if(pad2sessions[padId][i] == client.id)
|
|
||||||
{
|
|
||||||
socketio.sockets.sockets[pad2sessions[padId][i]].json.send(infoMsg);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// send the messages back to the client
|
||||||
|
client.json.send(infoMsg);
|
||||||
});
|
});
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
@ -453,14 +413,14 @@ function handleSuggestUserName(client, message)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var padId = sessioninfos[client.id].padId;
|
var padId = sessioninfos[client.id].padId,
|
||||||
|
clients = socketio.sockets.clients(padId);
|
||||||
|
|
||||||
//search the author and send him this message
|
//search the author and send him this message
|
||||||
for(var i in pad2sessions[padId])
|
for(var i = 0; i < clients.length; i++) {
|
||||||
{
|
var session = sessioninfos[clients[i].id];
|
||||||
if(sessioninfos[pad2sessions[padId][i]].author == message.data.payload.unnamedId)
|
if(session && session.author == message.data.payload.unnamedId) {
|
||||||
{
|
clients[i].json.send(message);
|
||||||
socketio.sockets.sockets[pad2sessions[padId][i]].send(message);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -501,7 +461,8 @@ function handleUserInfoUpdate(client, message)
|
||||||
type: "USER_NEWINFO",
|
type: "USER_NEWINFO",
|
||||||
userInfo: {
|
userInfo: {
|
||||||
userId: author,
|
userId: author,
|
||||||
name: message.data.userInfo.name,
|
//set a null name, when there is no name set. cause the client wants it null
|
||||||
|
name: message.data.userInfo.name || null,
|
||||||
colorId: message.data.userInfo.colorId,
|
colorId: message.data.userInfo.colorId,
|
||||||
userAgent: "Anonymous",
|
userAgent: "Anonymous",
|
||||||
ip: "127.0.0.1",
|
ip: "127.0.0.1",
|
||||||
|
@ -509,20 +470,8 @@ function handleUserInfoUpdate(client, message)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//set a null name, when there is no name set. cause the client wants it null
|
|
||||||
if(infoMsg.data.userInfo.name == null)
|
|
||||||
{
|
|
||||||
infoMsg.data.userInfo.name = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Send the other clients on the pad the update message
|
//Send the other clients on the pad the update message
|
||||||
for(var i in pad2sessions[padId])
|
client.broadcast.to(padId).json.send(infoMsg);
|
||||||
{
|
|
||||||
if(pad2sessions[padId][i] != client.id)
|
|
||||||
{
|
|
||||||
socketio.sockets.sockets[pad2sessions[padId][i]].json.send(infoMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -682,90 +631,76 @@ function handleUserChanges(client, message)
|
||||||
exports.updatePadClients = function(pad, callback)
|
exports.updatePadClients = function(pad, callback)
|
||||||
{
|
{
|
||||||
//skip this step if noone is on this pad
|
//skip this step if noone is on this pad
|
||||||
if(!pad2sessions[pad.id])
|
var roomClients = socketio.sockets.clients(pad.id);
|
||||||
{
|
if(roomClients.length==0)
|
||||||
callback();
|
return callback();
|
||||||
return;
|
|
||||||
}
|
// since all clients usually get the same set of changesets, store them in local cache
|
||||||
|
// to remove unnecessary roundtrip to the datalayer
|
||||||
|
// TODO: in REAL world, if we're working without datalayer cache, all requests to revisions will be fired
|
||||||
|
// BEFORE first result will be landed to our cache object. The solution is to replace parallel processing
|
||||||
|
// via async.forEach with sequential for() loop. There is no real benefits of running this in parallel,
|
||||||
|
// but benefit of reusing cached revision object is HUGE
|
||||||
|
var revCache = {};
|
||||||
|
|
||||||
//go trough all sessions on this pad
|
//go trough all sessions on this pad
|
||||||
async.forEach(pad2sessions[pad.id], function(session, callback)
|
async.forEach(roomClients, function(client, callback)
|
||||||
{
|
{
|
||||||
|
var sid = client.id;
|
||||||
|
|
||||||
//https://github.com/caolan/async#whilst
|
//https://github.com/caolan/async#whilst
|
||||||
//send them all new changesets
|
//send them all new changesets
|
||||||
async.whilst(
|
async.whilst(
|
||||||
function (){ return sessioninfos[session] && sessioninfos[session].rev < pad.getHeadRevisionNumber()},
|
function (){ return sessioninfos[sid] && sessioninfos[sid].rev < pad.getHeadRevisionNumber()},
|
||||||
function(callback)
|
function(callback)
|
||||||
{
|
{
|
||||||
var author, revChangeset, currentTime;
|
var r = sessioninfos[sid].rev + 1;
|
||||||
var r = sessioninfos[session].rev + 1;
|
|
||||||
|
|
||||||
async.parallel([
|
async.waterfall([
|
||||||
function (callback)
|
function(callback) {
|
||||||
{
|
if(revCache[r])
|
||||||
pad.getRevisionAuthor(r, function(err, value)
|
callback(null, revCache[r]);
|
||||||
{
|
else
|
||||||
if(ERR(err, callback)) return;
|
pad.getRevision(r, callback);
|
||||||
author = value;
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
function (callback)
|
function(revision, callback)
|
||||||
{
|
{
|
||||||
pad.getRevisionChangeset(r, function(err, value)
|
revCache[r] = revision;
|
||||||
|
|
||||||
|
var author = revision.meta.author,
|
||||||
|
revChangeset = revision.changeset,
|
||||||
|
currentTime = revision.meta.timestamp;
|
||||||
|
|
||||||
|
// next if session has not been deleted
|
||||||
|
if(sessioninfos[sid] == null)
|
||||||
|
return callback(null);
|
||||||
|
|
||||||
|
if(author == sessioninfos[sid].author)
|
||||||
{
|
{
|
||||||
if(ERR(err, callback)) return;
|
client.json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}});
|
||||||
revChangeset = value;
|
}
|
||||||
callback();
|
else
|
||||||
});
|
|
||||||
},
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
pad.getRevisionDate(r, function(err, date)
|
|
||||||
{
|
{
|
||||||
if(ERR(err, callback)) return;
|
var forWire = Changeset.prepareForWire(revChangeset, pad.pool);
|
||||||
currentTime = date;
|
var wireMsg = {"type":"COLLABROOM",
|
||||||
callback();
|
"data":{type:"NEW_CHANGES",
|
||||||
});
|
newRev:r,
|
||||||
}
|
changeset: forWire.translated,
|
||||||
], function(err)
|
apool: forWire.pool,
|
||||||
{
|
author: author,
|
||||||
if(ERR(err, callback)) return;
|
currentTime: currentTime,
|
||||||
// next if session has not been deleted
|
timeDelta: currentTime - sessioninfos[sid].time
|
||||||
if(sessioninfos[session] == null)
|
}};
|
||||||
{
|
|
||||||
|
client.json.send(wireMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
sessioninfos[sid].time = currentTime;
|
||||||
|
sessioninfos[sid].rev = r;
|
||||||
|
|
||||||
callback(null);
|
callback(null);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if(author == sessioninfos[session].author)
|
], callback);
|
||||||
{
|
|
||||||
socketio.sockets.sockets[session].json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var forWire = Changeset.prepareForWire(revChangeset, pad.pool);
|
|
||||||
var wireMsg = {"type":"COLLABROOM",
|
|
||||||
"data":{type:"NEW_CHANGES",
|
|
||||||
newRev:r,
|
|
||||||
changeset: forWire.translated,
|
|
||||||
apool: forWire.pool,
|
|
||||||
author: author,
|
|
||||||
currentTime: currentTime,
|
|
||||||
timeDelta: currentTime - sessioninfos[session].time
|
|
||||||
}};
|
|
||||||
|
|
||||||
socketio.sockets.sockets[session].json.send(wireMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(sessioninfos[session] != null)
|
|
||||||
{
|
|
||||||
sessioninfos[session].time = currentTime;
|
|
||||||
sessioninfos[session].rev = r;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
|
@ -895,23 +830,14 @@ function handleClientReady(client, message)
|
||||||
function(callback)
|
function(callback)
|
||||||
{
|
{
|
||||||
async.parallel([
|
async.parallel([
|
||||||
//get colorId
|
//get colorId and name
|
||||||
function(callback)
|
function(callback)
|
||||||
{
|
{
|
||||||
authorManager.getAuthorColorId(author, function(err, value)
|
authorManager.getAuthor(author, function(err, value)
|
||||||
{
|
{
|
||||||
if(ERR(err, callback)) return;
|
if(ERR(err, callback)) return;
|
||||||
authorColorId = value;
|
authorColorId = value.colorId;
|
||||||
callback();
|
authorName = value.name;
|
||||||
});
|
|
||||||
},
|
|
||||||
//get author name
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
authorManager.getAuthorName(author, function(err, value)
|
|
||||||
{
|
|
||||||
if(ERR(err, callback)) return;
|
|
||||||
authorName = value;
|
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -965,21 +891,17 @@ function handleClientReady(client, message)
|
||||||
{
|
{
|
||||||
//Check that the client is still here. It might have disconnected between callbacks.
|
//Check that the client is still here. It might have disconnected between callbacks.
|
||||||
if(sessioninfos[client.id] === undefined)
|
if(sessioninfos[client.id] === undefined)
|
||||||
{
|
return callback();
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check if this author is already on the pad, if yes, kick the other sessions!
|
//Check if this author is already on the pad, if yes, kick the other sessions!
|
||||||
if(pad2sessions[padIds.padId])
|
var roomClients = socketio.sockets.clients(padIds.padId);
|
||||||
{
|
for(var i = 0; i < roomClients.length; i++) {
|
||||||
for(var i in pad2sessions[padIds.padId])
|
var sinfo = sessioninfos[roomClients[i].id];
|
||||||
{
|
if(sinfo && sinfo.author == author) {
|
||||||
if(sessioninfos[pad2sessions[padIds.padId][i]] && sessioninfos[pad2sessions[padIds.padId][i]].author == author)
|
// fix user's counter, works on page refresh or if user closes browser window and then rejoins
|
||||||
{
|
sessioninfos[roomClients[i].id] = {};
|
||||||
var socket = socketio.sockets.sockets[pad2sessions[padIds.padId][i]];
|
roomClients[i].leave(padIds.padId);
|
||||||
if(socket) socket.json.send({disconnect:"userdup"});
|
roomClients[i].json.send({disconnect:"userdup"});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -988,15 +910,6 @@ function handleClientReady(client, message)
|
||||||
sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId;
|
sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId;
|
||||||
sessioninfos[client.id].readonly = padIds.readonly;
|
sessioninfos[client.id].readonly = padIds.readonly;
|
||||||
|
|
||||||
//check if there is already a pad2sessions entry, if not, create one
|
|
||||||
if(!pad2sessions[padIds.padId])
|
|
||||||
{
|
|
||||||
pad2sessions[padIds.padId] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
//Saves in pad2sessions that this session belongs to this pad
|
|
||||||
pad2sessions[padIds.padId].push(client.id);
|
|
||||||
|
|
||||||
//If this is a reconnect, we don't have to send the client the ClientVars again
|
//If this is a reconnect, we don't have to send the client the ClientVars again
|
||||||
if(message.reconnect == true)
|
if(message.reconnect == true)
|
||||||
{
|
{
|
||||||
|
@ -1044,7 +957,7 @@ function handleClientReady(client, message)
|
||||||
// tell the client the number of the latest chat-message, which will be
|
// tell the client the number of the latest chat-message, which will be
|
||||||
// used to request the latest 100 chat-messages later (GET_CHAT_MESSAGES)
|
// used to request the latest 100 chat-messages later (GET_CHAT_MESSAGES)
|
||||||
"chatHead": pad.chatHead,
|
"chatHead": pad.chatHead,
|
||||||
"numConnectedUsers": pad2sessions[padIds.padId].length,
|
"numConnectedUsers": roomClients.length,
|
||||||
"isProPad": false,
|
"isProPad": false,
|
||||||
"readOnlyId": padIds.readOnlyPadId,
|
"readOnlyId": padIds.readOnlyPadId,
|
||||||
"readonly": padIds.readonly,
|
"readonly": padIds.readonly,
|
||||||
|
@ -1080,6 +993,8 @@ function handleClientReady(client, message)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Join the pad and start receiving updates
|
||||||
|
client.join(padIds.padId);
|
||||||
//Send the clientVars to the Client
|
//Send the clientVars to the Client
|
||||||
client.json.send({type: "CLIENT_VARS", data: clientVars});
|
client.json.send({type: "CLIENT_VARS", data: clientVars});
|
||||||
//Save the current revision in sessioninfos, should be the same as in clientVars
|
//Save the current revision in sessioninfos, should be the same as in clientVars
|
||||||
|
@ -1109,71 +1024,53 @@ function handleClientReady(client, message)
|
||||||
messageToTheOtherUsers.data.userInfo.name = authorName;
|
messageToTheOtherUsers.data.userInfo.name = authorName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// notify all existing users about new user
|
||||||
|
client.broadcast.to(padIds.padIds).json.send(messageToTheOtherUsers);
|
||||||
|
|
||||||
//Run trough all sessions of this pad
|
//Run trough all sessions of this pad
|
||||||
async.forEach(pad2sessions[padIds.padId], function(sessionID, callback)
|
async.forEach(socketio.sockets.clients(padIds.padId), function(roomClient, callback)
|
||||||
{
|
{
|
||||||
var author, socket, sessionAuthorName, sessionAuthorColorId;
|
var author;
|
||||||
|
|
||||||
|
//Jump over, if this session is the connection session
|
||||||
|
if(roomClient.id == client.id)
|
||||||
|
return callback();
|
||||||
|
|
||||||
|
|
||||||
//Since sessioninfos might change while being enumerated, check if the
|
//Since sessioninfos might change while being enumerated, check if the
|
||||||
//sessionID is still assigned to a valid session
|
//sessionID is still assigned to a valid session
|
||||||
if(sessioninfos[sessionID] !== undefined &&
|
if(sessioninfos[roomClient.id] !== undefined)
|
||||||
socketio.sockets.sockets[sessionID] !== undefined){
|
author = sessioninfos[roomClient.id].author;
|
||||||
author = sessioninfos[sessionID].author;
|
else // If the client id is not valid, callback();
|
||||||
socket = socketio.sockets.sockets[sessionID];
|
return callback();
|
||||||
}else {
|
|
||||||
// If the sessionID is not valid, callback();
|
async.waterfall([
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
async.series([
|
|
||||||
//get the authorname & colorId
|
//get the authorname & colorId
|
||||||
function(callback)
|
function(callback)
|
||||||
{
|
{
|
||||||
async.parallel([
|
// reuse previously created cache of author's data
|
||||||
function(callback)
|
if(historicalAuthorData[author])
|
||||||
{
|
callback(null, historicalAuthorData[author]);
|
||||||
authorManager.getAuthorColorId(author, function(err, value)
|
else
|
||||||
{
|
authorManager.getAuthor(author, callback);
|
||||||
if(ERR(err, callback)) return;
|
|
||||||
sessionAuthorColorId = value;
|
|
||||||
callback();
|
|
||||||
})
|
|
||||||
},
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
authorManager.getAuthorName(author, function(err, value)
|
|
||||||
{
|
|
||||||
if(ERR(err, callback)) return;
|
|
||||||
sessionAuthorName = value;
|
|
||||||
callback();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
],callback);
|
|
||||||
},
|
},
|
||||||
function (callback)
|
function (authorInfo, callback)
|
||||||
{
|
{
|
||||||
//Jump over, if this session is the connection session
|
//Send the new User a Notification about this other user
|
||||||
if(sessionID != client.id)
|
var msg = {
|
||||||
{
|
"type": "COLLABROOM",
|
||||||
//Send this Session the Notification about the new user
|
"data": {
|
||||||
socket.json.send(messageToTheOtherUsers);
|
type: "USER_NEWINFO",
|
||||||
|
userInfo: {
|
||||||
//Send the new User a Notification about this other user
|
"ip": "127.0.0.1",
|
||||||
var messageToNotifyTheClientAboutTheOthers = {
|
"colorId": authorInfo.colorId,
|
||||||
"type": "COLLABROOM",
|
"name": authorInfo.name,
|
||||||
"data": {
|
"userAgent": "Anonymous",
|
||||||
type: "USER_NEWINFO",
|
"userId": author
|
||||||
userInfo: {
|
|
||||||
"ip": "127.0.0.1",
|
|
||||||
"colorId": sessionAuthorColorId,
|
|
||||||
"name": sessionAuthorName,
|
|
||||||
"userAgent": "Anonymous",
|
|
||||||
"userId": author
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
client.json.send(messageToNotifyTheClientAboutTheOthers);
|
};
|
||||||
}
|
client.json.send(msg);
|
||||||
}
|
}
|
||||||
], callback);
|
], callback);
|
||||||
}, callback);
|
}, callback);
|
||||||
|
@ -1521,33 +1418,30 @@ function composePadChangesets(padId, startNum, endNum, callback)
|
||||||
* Get the number of users in a pad
|
* Get the number of users in a pad
|
||||||
*/
|
*/
|
||||||
exports.padUsersCount = function (padID, callback) {
|
exports.padUsersCount = function (padID, callback) {
|
||||||
if (!pad2sessions[padID] || typeof pad2sessions[padID] != typeof []) {
|
callback(null, {
|
||||||
callback(null, {padUsersCount: 0});
|
padUsersCount: socketio.sockets.clients(padId).length
|
||||||
} else {
|
});
|
||||||
callback(null, {padUsersCount: pad2sessions[padID].length});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of users in a pad
|
* Get the list of users in a pad
|
||||||
*/
|
*/
|
||||||
exports.padUsers = function (padID, callback) {
|
exports.padUsers = function (padID, callback) {
|
||||||
if (!pad2sessions[padID] || typeof pad2sessions[padID] != typeof []) {
|
var result = [];
|
||||||
callback(null, {padUsers: []});
|
|
||||||
} else {
|
async.forEach(socketio.sockets.clients(padId), function(roomClient, callback) {
|
||||||
var authors = [];
|
var s = sessioninfos[roomClient.id];
|
||||||
for ( var ix in sessioninfos ) {
|
if(s) {
|
||||||
if ( sessioninfos[ix].padId !== padID ) {
|
authorManager.getAuthor(s.author, function(err, author) {
|
||||||
continue;
|
if(ERR(err, callback)) return;
|
||||||
}
|
|
||||||
var aid = sessioninfos[ix].author;
|
author.id = s.author;
|
||||||
authorManager.getAuthor( aid, function ( err, author ) {
|
result.push(author);
|
||||||
author.id = aid;
|
});
|
||||||
authors.push( author );
|
|
||||||
if ( authors.length === pad2sessions[padID].length ) {
|
|
||||||
callback(null, {padUsers: authors});
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
}
|
}, function(err) {
|
||||||
|
if(ERR(err, callback)) return;
|
||||||
|
|
||||||
|
callback(null, {padUsers: result});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ div.innerwrapper {
|
||||||
border-radius: 0 0 7px 7px;
|
border-radius: 0 0 7px 7px;
|
||||||
margin-left:250px;
|
margin-left:250px;
|
||||||
min-width:400px;
|
min-width:400px;
|
||||||
|
width:100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper {
|
#wrapper {
|
||||||
|
|
|
@ -33,19 +33,6 @@ function object(o)
|
||||||
f.prototype = o;
|
f.prototype = o;
|
||||||
return new f();
|
return new f();
|
||||||
}
|
}
|
||||||
var userAgent = (((function () {return this;})().navigator || {}).userAgent || 'node-js').toLowerCase();
|
|
||||||
|
|
||||||
// Figure out what browser is being used (stolen from jquery 1.2.1)
|
|
||||||
var browser = {
|
|
||||||
version: (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
|
|
||||||
safari: /webkit/.test(userAgent),
|
|
||||||
opera: /opera/.test(userAgent),
|
|
||||||
msie: /msie/.test(userAgent) && !/opera/.test(userAgent),
|
|
||||||
mozilla: /mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent),
|
|
||||||
windows: /windows/.test(userAgent),
|
|
||||||
mobile: /mobile/.test(userAgent) || /android/.test(userAgent)
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function getAssoc(obj, name)
|
function getAssoc(obj, name)
|
||||||
{
|
{
|
||||||
|
@ -97,7 +84,6 @@ var noop = function(){};
|
||||||
|
|
||||||
exports.isNodeText = isNodeText;
|
exports.isNodeText = isNodeText;
|
||||||
exports.object = object;
|
exports.object = object;
|
||||||
exports.browser = browser;
|
|
||||||
exports.getAssoc = getAssoc;
|
exports.getAssoc = getAssoc;
|
||||||
exports.setAssoc = setAssoc;
|
exports.setAssoc = setAssoc;
|
||||||
exports.binarySearch = binarySearch;
|
exports.binarySearch = binarySearch;
|
||||||
|
|
|
@ -28,7 +28,7 @@ $ = jQuery = require('./rjquery').$;
|
||||||
_ = require("./underscore");
|
_ = require("./underscore");
|
||||||
|
|
||||||
var isNodeText = Ace2Common.isNodeText,
|
var isNodeText = Ace2Common.isNodeText,
|
||||||
browser = Ace2Common.browser,
|
browser = $.browser,
|
||||||
getAssoc = Ace2Common.getAssoc,
|
getAssoc = Ace2Common.getAssoc,
|
||||||
setAssoc = Ace2Common.setAssoc,
|
setAssoc = Ace2Common.setAssoc,
|
||||||
isTextNode = Ace2Common.isTextNode,
|
isTextNode = Ace2Common.isTextNode,
|
||||||
|
@ -154,7 +154,17 @@ function Ace2Inner(){
|
||||||
var dmesg = noop;
|
var dmesg = noop;
|
||||||
window.dmesg = noop;
|
window.dmesg = noop;
|
||||||
|
|
||||||
var scheduler = parent;
|
// Ugly hack for Firefox 18
|
||||||
|
// get the timeout and interval methods from the parent iframe
|
||||||
|
// This hack breaks IE8
|
||||||
|
try{
|
||||||
|
setTimeout = parent.setTimeout;
|
||||||
|
clearTimeout = parent.clearTimeout;
|
||||||
|
setInterval = parent.setInterval;
|
||||||
|
clearInterval = parent.clearInterval;
|
||||||
|
}catch(err){
|
||||||
|
// IE8 can panic here.
|
||||||
|
}
|
||||||
|
|
||||||
var textFace = 'monospace';
|
var textFace = 'monospace';
|
||||||
var textSize = 12;
|
var textSize = 12;
|
||||||
|
@ -174,7 +184,7 @@ function Ace2Inner(){
|
||||||
parentDynamicCSS = makeCSSManager("dynamicsyntax", true);
|
parentDynamicCSS = makeCSSManager("dynamicsyntax", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var changesetTracker = makeChangesetTracker(scheduler, rep.apool, {
|
var changesetTracker = makeChangesetTracker(rep.apool, {
|
||||||
withCallbacks: function(operationName, f)
|
withCallbacks: function(operationName, f)
|
||||||
{
|
{
|
||||||
inCallStackIfNecessary(operationName, function()
|
inCallStackIfNecessary(operationName, function()
|
||||||
|
@ -594,7 +604,7 @@ function Ace2Inner(){
|
||||||
doesWrap = newVal;
|
doesWrap = newVal;
|
||||||
var dwClass = "doesWrap";
|
var dwClass = "doesWrap";
|
||||||
setClassPresence(root, "doesWrap", doesWrap);
|
setClassPresence(root, "doesWrap", doesWrap);
|
||||||
scheduler.setTimeout(function()
|
setTimeout(function()
|
||||||
{
|
{
|
||||||
inCallStackIfNecessary("setWraps", function()
|
inCallStackIfNecessary("setWraps", function()
|
||||||
{
|
{
|
||||||
|
@ -634,7 +644,7 @@ function Ace2Inner(){
|
||||||
textFace = face;
|
textFace = face;
|
||||||
root.style.fontFamily = textFace;
|
root.style.fontFamily = textFace;
|
||||||
lineMetricsDiv.style.fontFamily = textFace;
|
lineMetricsDiv.style.fontFamily = textFace;
|
||||||
scheduler.setTimeout(function()
|
setTimeout(function()
|
||||||
{
|
{
|
||||||
setUpTrackingCSS();
|
setUpTrackingCSS();
|
||||||
}, 0);
|
}, 0);
|
||||||
|
@ -647,7 +657,7 @@ function Ace2Inner(){
|
||||||
root.style.lineHeight = textLineHeight() + "px";
|
root.style.lineHeight = textLineHeight() + "px";
|
||||||
sideDiv.style.lineHeight = textLineHeight() + "px";
|
sideDiv.style.lineHeight = textLineHeight() + "px";
|
||||||
lineMetricsDiv.style.fontSize = textSize + "px";
|
lineMetricsDiv.style.fontSize = textSize + "px";
|
||||||
scheduler.setTimeout(function()
|
setTimeout(function()
|
||||||
{
|
{
|
||||||
setUpTrackingCSS();
|
setUpTrackingCSS();
|
||||||
}, 0);
|
}, 0);
|
||||||
|
@ -1085,7 +1095,7 @@ function Ace2Inner(){
|
||||||
{
|
{
|
||||||
if (scheduledTimeout)
|
if (scheduledTimeout)
|
||||||
{
|
{
|
||||||
scheduler.clearTimeout(scheduledTimeout);
|
clearTimeout(scheduledTimeout);
|
||||||
scheduledTimeout = null;
|
scheduledTimeout = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1096,7 +1106,7 @@ function Ace2Inner(){
|
||||||
scheduledTime = time;
|
scheduledTime = time;
|
||||||
var delay = time - now();
|
var delay = time - now();
|
||||||
if (delay < 0) delay = 0;
|
if (delay < 0) delay = 0;
|
||||||
scheduledTimeout = scheduler.setTimeout(callback, delay);
|
scheduledTimeout = setTimeout(callback, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
function callback()
|
function callback()
|
||||||
|
@ -2817,7 +2827,6 @@ function Ace2Inner(){
|
||||||
rep.selStart = selectStart;
|
rep.selStart = selectStart;
|
||||||
rep.selEnd = selectEnd;
|
rep.selEnd = selectEnd;
|
||||||
rep.selFocusAtStart = newSelFocusAtStart;
|
rep.selFocusAtStart = newSelFocusAtStart;
|
||||||
if (mozillaFakeArrows) mozillaFakeArrows.notifySelectionChanged();
|
|
||||||
currentCallStack.repChanged = true;
|
currentCallStack.repChanged = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -3614,7 +3623,7 @@ function Ace2Inner(){
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
doReturnKey();
|
doReturnKey();
|
||||||
//scrollSelectionIntoView();
|
//scrollSelectionIntoView();
|
||||||
scheduler.setTimeout(function()
|
setTimeout(function()
|
||||||
{
|
{
|
||||||
outerWin.scrollBy(-100, 0);
|
outerWin.scrollBy(-100, 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
@ -3690,11 +3699,41 @@ function Ace2Inner(){
|
||||||
doDeleteKey();
|
doDeleteKey();
|
||||||
specialHandled = true;
|
specialHandled = true;
|
||||||
}
|
}
|
||||||
|
if((evt.which == 33 || evt.which == 34) && type == 'keydown'){
|
||||||
|
var oldVisibleLineRange = getVisibleLineRange();
|
||||||
|
var topOffset = rep.selStart[0] - oldVisibleLineRange[0];
|
||||||
|
if(topOffset < 0 ){
|
||||||
|
topOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (mozillaFakeArrows && mozillaFakeArrows.handleKeyEvent(evt))
|
var isPageDown = evt.which === 34;
|
||||||
{
|
var isPageUp = evt.which === 33;
|
||||||
evt.preventDefault();
|
|
||||||
specialHandled = true;
|
setTimeout(function(){
|
||||||
|
var newVisibleLineRange = getVisibleLineRange();
|
||||||
|
var linesCount = rep.lines.length();
|
||||||
|
|
||||||
|
var newCaretRow = rep.selStart[0];
|
||||||
|
if(isPageUp){
|
||||||
|
newCaretRow = oldVisibleLineRange[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isPageDown){
|
||||||
|
newCaretRow = newVisibleLineRange[0] + topOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ensure min and max
|
||||||
|
if(newCaretRow < 0){
|
||||||
|
newCaretRow = 0;
|
||||||
|
}
|
||||||
|
if(newCaretRow >= linesCount){
|
||||||
|
newCaretRow = linesCount-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rep.selStart[0] = newCaretRow;
|
||||||
|
rep.selEnd[0] = newCaretRow;
|
||||||
|
updateBrowserSelectionFromRep();
|
||||||
|
}, 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4119,6 +4158,11 @@ function Ace2Inner(){
|
||||||
selection.startPoint = pointFromRangeBound(range.startContainer, range.startOffset);
|
selection.startPoint = pointFromRangeBound(range.startContainer, range.startOffset);
|
||||||
selection.endPoint = pointFromRangeBound(range.endContainer, range.endOffset);
|
selection.endPoint = pointFromRangeBound(range.endContainer, range.endOffset);
|
||||||
selection.focusAtStart = (((range.startContainer != range.endContainer) || (range.startOffset != range.endOffset)) && browserSelection.anchorNode && (browserSelection.anchorNode == range.endContainer) && (browserSelection.anchorOffset == range.endOffset));
|
selection.focusAtStart = (((range.startContainer != range.endContainer) || (range.startOffset != range.endOffset)) && browserSelection.anchorNode && (browserSelection.anchorNode == range.endContainer) && (browserSelection.anchorOffset == range.endOffset));
|
||||||
|
|
||||||
|
if(selection.startPoint.node.ownerDocument !== window.document){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
else return null;
|
else return null;
|
||||||
|
@ -4722,7 +4766,7 @@ function Ace2Inner(){
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
scheduler.setTimeout(function()
|
setTimeout(function()
|
||||||
{
|
{
|
||||||
parent.readyFunc(); // defined in code that sets up the inner iframe
|
parent.readyFunc(); // defined in code that sets up the inner iframe
|
||||||
}, 0);
|
}, 0);
|
||||||
|
@ -5033,331 +5077,6 @@ function Ace2Inner(){
|
||||||
editorInfo.ace_doInsertUnorderedList = doInsertUnorderedList;
|
editorInfo.ace_doInsertUnorderedList = doInsertUnorderedList;
|
||||||
editorInfo.ace_doInsertOrderedList = doInsertOrderedList;
|
editorInfo.ace_doInsertOrderedList = doInsertOrderedList;
|
||||||
|
|
||||||
var mozillaFakeArrows = (browser.mozilla && (function()
|
|
||||||
{
|
|
||||||
// In Firefox 2, arrow keys are unstable while DOM-manipulating
|
|
||||||
// operations are going on. Specifically, if an operation
|
|
||||||
// (computation that ties up the event queue) is going on (in the
|
|
||||||
// call-stack of some event, like a timeout) that at some point
|
|
||||||
// mutates nodes involved in the selection, then the arrow
|
|
||||||
// keypress may (randomly) move the caret to the beginning or end
|
|
||||||
// of the document. If the operation also mutates the selection
|
|
||||||
// range, the old selection or the new selection may be used, or
|
|
||||||
// neither.
|
|
||||||
// As long as the arrow is pressed during the busy operation, it
|
|
||||||
// doesn't seem to matter that the keydown and keypress events
|
|
||||||
// aren't generated until afterwards, or that the arrow movement
|
|
||||||
// can still be stopped (meaning it hasn't been performed yet);
|
|
||||||
// Firefox must be preserving some old information about the
|
|
||||||
// selection or the DOM from when the key was initially pressed.
|
|
||||||
// However, it also doesn't seem to matter when the key was
|
|
||||||
// actually pressed relative to the time of the mutation within
|
|
||||||
// the prolonged operation. Also, even in very controlled tests
|
|
||||||
// (like a mutation followed by a long period of busyWaiting), the
|
|
||||||
// problem shows up often but not every time, with no discernable
|
|
||||||
// pattern. Who knows, it could have something to do with the
|
|
||||||
// caret-blinking timer, or DOM changes not being applied
|
|
||||||
// immediately.
|
|
||||||
// This problem, mercifully, does not show up at all in IE or
|
|
||||||
// Safari. My solution is to have my own, full-featured arrow-key
|
|
||||||
// implementation for Firefox.
|
|
||||||
// Note that the problem addressed here is potentially very subtle,
|
|
||||||
// especially if the operation is quick and is timed to usually happen
|
|
||||||
// when the user is idle.
|
|
||||||
// features:
|
|
||||||
// - 'up' and 'down' arrows preserve column when passing through shorter lines
|
|
||||||
// - shift-arrows extend the "focus" point, which may be start or end of range
|
|
||||||
// - the focus point is kept horizontally and vertically scrolled into view
|
|
||||||
// - arrows without shift cause caret to move to beginning or end of selection (left,right)
|
|
||||||
// or move focus point up or down a line (up,down)
|
|
||||||
// - command-(left,right,up,down) on Mac acts like (line-start, line-end, doc-start, doc-end)
|
|
||||||
// - takes wrapping into account when doesWrap is true, i.e. up-arrow and down-arrow move
|
|
||||||
// between the virtual lines within a wrapped line; this was difficult, and unfortunately
|
|
||||||
// requires mutating the DOM to get the necessary information
|
|
||||||
var savedFocusColumn = 0; // a value of 0 has no effect
|
|
||||||
var updatingSelectionNow = false;
|
|
||||||
|
|
||||||
function getVirtualLineView(lineNum)
|
|
||||||
{
|
|
||||||
var lineNode = rep.lines.atIndex(lineNum).lineNode;
|
|
||||||
while (lineNode.firstChild && isBlockElement(lineNode.firstChild))
|
|
||||||
{
|
|
||||||
lineNode = lineNode.firstChild;
|
|
||||||
}
|
|
||||||
return makeVirtualLineView(lineNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
function markerlessLineAndChar(line, chr)
|
|
||||||
{
|
|
||||||
return [line, chr - rep.lines.atIndex(line).lineMarker];
|
|
||||||
}
|
|
||||||
|
|
||||||
function markerfulLineAndChar(line, chr)
|
|
||||||
{
|
|
||||||
return [line, chr + rep.lines.atIndex(line).lineMarker];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
notifySelectionChanged: function()
|
|
||||||
{
|
|
||||||
if (!updatingSelectionNow)
|
|
||||||
{
|
|
||||||
savedFocusColumn = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleKeyEvent: function(evt)
|
|
||||||
{
|
|
||||||
// returns "true" if handled
|
|
||||||
if (evt.type != "keypress") return false;
|
|
||||||
var keyCode = evt.keyCode;
|
|
||||||
if (keyCode < 37 || keyCode > 40) return false;
|
|
||||||
incorporateUserChanges();
|
|
||||||
|
|
||||||
if (!(rep.selStart && rep.selEnd)) return true;
|
|
||||||
|
|
||||||
// {byWord,toEnd,normal}
|
|
||||||
var moveMode = (evt.altKey ? "byWord" : (evt.ctrlKey ? "byWord" : (evt.metaKey ? "toEnd" : "normal")));
|
|
||||||
|
|
||||||
var anchorCaret = markerlessLineAndChar(rep.selStart[0], rep.selStart[1]);
|
|
||||||
var focusCaret = markerlessLineAndChar(rep.selEnd[0], rep.selEnd[1]);
|
|
||||||
var wasCaret = isCaret();
|
|
||||||
if (rep.selFocusAtStart)
|
|
||||||
{
|
|
||||||
var tmp = anchorCaret;
|
|
||||||
anchorCaret = focusCaret;
|
|
||||||
focusCaret = tmp;
|
|
||||||
}
|
|
||||||
var K_UP = 38,
|
|
||||||
K_DOWN = 40,
|
|
||||||
K_LEFT = 37,
|
|
||||||
K_RIGHT = 39;
|
|
||||||
var dontMove = false;
|
|
||||||
if (wasCaret && !evt.shiftKey)
|
|
||||||
{
|
|
||||||
// collapse, will mutate both together
|
|
||||||
anchorCaret = focusCaret;
|
|
||||||
}
|
|
||||||
else if ((!wasCaret) && (!evt.shiftKey))
|
|
||||||
{
|
|
||||||
if (keyCode == K_LEFT)
|
|
||||||
{
|
|
||||||
// place caret at beginning
|
|
||||||
if (rep.selFocusAtStart) anchorCaret = focusCaret;
|
|
||||||
else focusCaret = anchorCaret;
|
|
||||||
if (moveMode == "normal") dontMove = true;
|
|
||||||
}
|
|
||||||
else if (keyCode == K_RIGHT)
|
|
||||||
{
|
|
||||||
// place caret at end
|
|
||||||
if (rep.selFocusAtStart) focusCaret = anchorCaret;
|
|
||||||
else anchorCaret = focusCaret;
|
|
||||||
if (moveMode == "normal") dontMove = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// collapse, will mutate both together
|
|
||||||
anchorCaret = focusCaret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!dontMove)
|
|
||||||
{
|
|
||||||
function lineLength(i)
|
|
||||||
{
|
|
||||||
var entry = rep.lines.atIndex(i);
|
|
||||||
return entry.text.length - entry.lineMarker;
|
|
||||||
}
|
|
||||||
|
|
||||||
function lineText(i)
|
|
||||||
{
|
|
||||||
var entry = rep.lines.atIndex(i);
|
|
||||||
return entry.text.substring(entry.lineMarker);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyCode == K_UP || keyCode == K_DOWN)
|
|
||||||
{
|
|
||||||
var up = (keyCode == K_UP);
|
|
||||||
var canChangeLines = ((up && focusCaret[0]) || ((!up) && focusCaret[0] < rep.lines.length() - 1));
|
|
||||||
var virtualLineView, virtualLineSpot, canChangeVirtualLines = false;
|
|
||||||
if (doesWrap)
|
|
||||||
{
|
|
||||||
virtualLineView = getVirtualLineView(focusCaret[0]);
|
|
||||||
virtualLineSpot = virtualLineView.getVLineAndOffsetForChar(focusCaret[1]);
|
|
||||||
canChangeVirtualLines = ((up && virtualLineSpot.vline > 0) || ((!up) && virtualLineSpot.vline < (
|
|
||||||
virtualLineView.getNumVirtualLines() - 1)));
|
|
||||||
}
|
|
||||||
var newColByVirtualLineChange;
|
|
||||||
if (moveMode == "toEnd")
|
|
||||||
{
|
|
||||||
if (up)
|
|
||||||
{
|
|
||||||
focusCaret[0] = 0;
|
|
||||||
focusCaret[1] = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
focusCaret[0] = rep.lines.length() - 1;
|
|
||||||
focusCaret[1] = lineLength(focusCaret[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (moveMode == "byWord")
|
|
||||||
{
|
|
||||||
// move by "paragraph", a feature that Firefox lacks but IE and Safari both have
|
|
||||||
if (up)
|
|
||||||
{
|
|
||||||
if (focusCaret[1] === 0 && canChangeLines)
|
|
||||||
{
|
|
||||||
focusCaret[0]--;
|
|
||||||
focusCaret[1] = 0;
|
|
||||||
}
|
|
||||||
else focusCaret[1] = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var lineLen = lineLength(focusCaret[0]);
|
|
||||||
if (browser.windows)
|
|
||||||
{
|
|
||||||
if (canChangeLines)
|
|
||||||
{
|
|
||||||
focusCaret[0]++;
|
|
||||||
focusCaret[1] = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
focusCaret[1] = lineLen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (focusCaret[1] == lineLen && canChangeLines)
|
|
||||||
{
|
|
||||||
focusCaret[0]++;
|
|
||||||
focusCaret[1] = lineLength(focusCaret[0]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
focusCaret[1] = lineLen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
savedFocusColumn = 0;
|
|
||||||
}
|
|
||||||
else if (canChangeVirtualLines)
|
|
||||||
{
|
|
||||||
var vline = virtualLineSpot.vline;
|
|
||||||
var offset = virtualLineSpot.offset;
|
|
||||||
if (up) vline--;
|
|
||||||
else vline++;
|
|
||||||
if (savedFocusColumn > offset) offset = savedFocusColumn;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
savedFocusColumn = offset;
|
|
||||||
}
|
|
||||||
var newSpot = virtualLineView.getCharForVLineAndOffset(vline, offset);
|
|
||||||
focusCaret[1] = newSpot.lineChar;
|
|
||||||
}
|
|
||||||
else if (canChangeLines)
|
|
||||||
{
|
|
||||||
if (up) focusCaret[0]--;
|
|
||||||
else focusCaret[0]++;
|
|
||||||
var offset = focusCaret[1];
|
|
||||||
if (doesWrap)
|
|
||||||
{
|
|
||||||
offset = virtualLineSpot.offset;
|
|
||||||
}
|
|
||||||
if (savedFocusColumn > offset) offset = savedFocusColumn;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
savedFocusColumn = offset;
|
|
||||||
}
|
|
||||||
if (doesWrap)
|
|
||||||
{
|
|
||||||
var newLineView = getVirtualLineView(focusCaret[0]);
|
|
||||||
var vline = (up ? newLineView.getNumVirtualLines() - 1 : 0);
|
|
||||||
var newSpot = newLineView.getCharForVLineAndOffset(vline, offset);
|
|
||||||
focusCaret[1] = newSpot.lineChar;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var lineLen = lineLength(focusCaret[0]);
|
|
||||||
if (offset > lineLen) offset = lineLen;
|
|
||||||
focusCaret[1] = offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (up) focusCaret[1] = 0;
|
|
||||||
else focusCaret[1] = lineLength(focusCaret[0]);
|
|
||||||
savedFocusColumn = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (keyCode == K_LEFT || keyCode == K_RIGHT)
|
|
||||||
{
|
|
||||||
var left = (keyCode == K_LEFT);
|
|
||||||
if (left)
|
|
||||||
{
|
|
||||||
if (moveMode == "toEnd") focusCaret[1] = 0;
|
|
||||||
else if (focusCaret[1] > 0)
|
|
||||||
{
|
|
||||||
if (moveMode == "byWord")
|
|
||||||
{
|
|
||||||
focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
focusCaret[1]--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (focusCaret[0] > 0)
|
|
||||||
{
|
|
||||||
focusCaret[0]--;
|
|
||||||
focusCaret[1] = lineLength(focusCaret[0]);
|
|
||||||
if (moveMode == "byWord")
|
|
||||||
{
|
|
||||||
focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var lineLen = lineLength(focusCaret[0]);
|
|
||||||
if (moveMode == "toEnd") focusCaret[1] = lineLen;
|
|
||||||
else if (focusCaret[1] < lineLen)
|
|
||||||
{
|
|
||||||
if (moveMode == "byWord")
|
|
||||||
{
|
|
||||||
focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
focusCaret[1]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (focusCaret[0] < rep.lines.length() - 1)
|
|
||||||
{
|
|
||||||
focusCaret[0]++;
|
|
||||||
focusCaret[1] = 0;
|
|
||||||
if (moveMode == "byWord")
|
|
||||||
{
|
|
||||||
focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
savedFocusColumn = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var newSelFocusAtStart = ((focusCaret[0] < anchorCaret[0]) || (focusCaret[0] == anchorCaret[0] && focusCaret[1] < anchorCaret[1]));
|
|
||||||
var newSelStart = (newSelFocusAtStart ? focusCaret : anchorCaret);
|
|
||||||
var newSelEnd = (newSelFocusAtStart ? anchorCaret : focusCaret);
|
|
||||||
updatingSelectionNow = true;
|
|
||||||
performSelectionChange(markerfulLineAndChar(newSelStart[0], newSelStart[1]), markerfulLineAndChar(newSelEnd[0], newSelEnd[1]), newSelFocusAtStart);
|
|
||||||
updatingSelectionNow = false;
|
|
||||||
currentCallStack.userChangedSelection = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})());
|
|
||||||
|
|
||||||
var lineNumbersShown;
|
var lineNumbersShown;
|
||||||
var sideDivInner;
|
var sideDivInner;
|
||||||
|
|
||||||
|
@ -5495,7 +5214,7 @@ function Ace2Inner(){
|
||||||
documentAttributeManager: documentAttributeManager
|
documentAttributeManager: documentAttributeManager
|
||||||
});
|
});
|
||||||
|
|
||||||
scheduler.setTimeout(function()
|
setTimeout(function()
|
||||||
{
|
{
|
||||||
parent.readyFunc(); // defined in code that sets up the inner iframe
|
parent.readyFunc(); // defined in code that sets up the inner iframe
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
var AttributePool = require('./AttributePool');
|
var AttributePool = require('./AttributePool');
|
||||||
var Changeset = require('./Changeset');
|
var Changeset = require('./Changeset');
|
||||||
|
|
||||||
function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
|
function makeChangesetTracker(apool, aceCallbacksProvider)
|
||||||
{
|
{
|
||||||
|
|
||||||
// latest official text from server
|
// latest official text from server
|
||||||
|
@ -51,7 +51,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
|
||||||
// and if there isn't a timeout already scheduled.
|
// and if there isn't a timeout already scheduled.
|
||||||
if (changeCallback && changeCallbackTimeout === null)
|
if (changeCallback && changeCallbackTimeout === null)
|
||||||
{
|
{
|
||||||
changeCallbackTimeout = scheduler.setTimeout(function()
|
changeCallbackTimeout = setTimeout(function()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -30,8 +30,7 @@ var Security = require('./security');
|
||||||
var hooks = require('./pluginfw/hooks');
|
var hooks = require('./pluginfw/hooks');
|
||||||
var _ = require('./underscore');
|
var _ = require('./underscore');
|
||||||
var lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
|
var lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
|
||||||
var Ace2Common = require('./ace2_common');
|
var noop = function(){};
|
||||||
var noop = Ace2Common.noop;
|
|
||||||
|
|
||||||
|
|
||||||
var domline = {};
|
var domline = {};
|
||||||
|
|
|
@ -23,27 +23,27 @@
|
||||||
window.html10n = (function(window, document, undefined) {
|
window.html10n = (function(window, document, undefined) {
|
||||||
|
|
||||||
// fix console
|
// fix console
|
||||||
var console = window.console
|
var console = window.console;
|
||||||
function interceptConsole(method){
|
function interceptConsole(method){
|
||||||
if (!console) return function() {}
|
if (!console) return function() {};
|
||||||
|
|
||||||
var original = console[method]
|
var original = console[method];
|
||||||
|
|
||||||
// do sneaky stuff
|
// do sneaky stuff
|
||||||
if (original.bind){
|
if (original.bind){
|
||||||
// Do this for normal browsers
|
// Do this for normal browsers
|
||||||
return original.bind(console)
|
return original.bind(console);
|
||||||
}else{
|
}else{
|
||||||
return function() {
|
return function() {
|
||||||
// Do this for IE
|
// Do this for IE
|
||||||
var message = Array.prototype.slice.apply(arguments).join(' ')
|
var message = Array.prototype.slice.apply(arguments).join(' ');
|
||||||
original(message)
|
original(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var consoleLog = interceptConsole('log')
|
var consoleLog = interceptConsole('log')
|
||||||
, consoleWarn = interceptConsole('warn')
|
, consoleWarn = interceptConsole('warn')
|
||||||
, consoleError = interceptConsole('warn')
|
, consoleError = interceptConsole('warn');
|
||||||
|
|
||||||
|
|
||||||
// fix Array.prototype.instanceOf in, guess what, IE! <3
|
// fix Array.prototype.instanceOf in, guess what, IE! <3
|
||||||
|
@ -84,14 +84,14 @@ window.html10n = (function(window, document, undefined) {
|
||||||
* MicroEvent - to make any js object an event emitter (server or browser)
|
* MicroEvent - to make any js object an event emitter (server or browser)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var MicroEvent = function(){}
|
var MicroEvent = function(){}
|
||||||
MicroEvent.prototype = {
|
MicroEvent.prototype = {
|
||||||
bind : function(event, fct){
|
bind : function(event, fct){
|
||||||
this._events = this._events || {};
|
this._events = this._events || {};
|
||||||
this._events[event] = this._events[event] || [];
|
this._events[event] = this._events[event] || [];
|
||||||
this._events[event].push(fct);
|
this._events[event].push(fct);
|
||||||
},
|
},
|
||||||
unbind : function(event, fct){
|
unbind : function(event, fct){
|
||||||
this._events = this._events || {};
|
this._events = this._events || {};
|
||||||
if( event in this._events === false ) return;
|
if( event in this._events === false ) return;
|
||||||
this._events[event].splice(this._events[event].indexOf(fct), 1);
|
this._events[event].splice(this._events[event].indexOf(fct), 1);
|
||||||
|
@ -100,7 +100,7 @@ window.html10n = (function(window, document, undefined) {
|
||||||
this._events = this._events || {};
|
this._events = this._events || {};
|
||||||
if( event in this._events === false ) return;
|
if( event in this._events === false ) return;
|
||||||
for(var i = 0; i < this._events[event].length; i++){
|
for(var i = 0; i < this._events[event].length; i++){
|
||||||
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1))
|
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -122,50 +122,50 @@ window.html10n = (function(window, document, undefined) {
|
||||||
* and caching all necessary resources
|
* and caching all necessary resources
|
||||||
*/
|
*/
|
||||||
function Loader(resources) {
|
function Loader(resources) {
|
||||||
this.resources = resources
|
this.resources = resources;
|
||||||
this.cache = {} // file => contents
|
this.cache = {}; // file => contents
|
||||||
this.langs = {} // lang => strings
|
this.langs = {}; // lang => strings
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader.prototype.load = function(lang, cb) {
|
Loader.prototype.load = function(lang, cb) {
|
||||||
if(this.langs[lang]) return cb()
|
if(this.langs[lang]) return cb();
|
||||||
|
|
||||||
if (this.resources.length > 0) {
|
if (this.resources.length > 0) {
|
||||||
var reqs = 0;
|
var reqs = 0;
|
||||||
for (var i=0, n=this.resources.length; i < n; i++) {
|
for (var i=0, n=this.resources.length; i < n; i++) {
|
||||||
this.fetch(this.resources[i], lang, function(e) {
|
this.fetch(this.resources[i], lang, function(e) {
|
||||||
reqs++;
|
reqs++;
|
||||||
if(e) return setTimeout(function(){ throw e }, 0)
|
if(e) return setTimeout(function(){ throw e }, 0);
|
||||||
|
|
||||||
if (reqs < n) return;// Call back once all reqs are completed
|
if (reqs < n) return;// Call back once all reqs are completed
|
||||||
cb && cb()
|
cb && cb();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader.prototype.fetch = function(href, lang, cb) {
|
Loader.prototype.fetch = function(href, lang, cb) {
|
||||||
var that = this
|
var that = this;
|
||||||
|
|
||||||
if (this.cache[href]) {
|
if (this.cache[href]) {
|
||||||
this.parse(lang, href, this.cache[href], cb)
|
this.parse(lang, href, this.cache[href], cb)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest()
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', href, /*async: */true)
|
xhr.open('GET', href, /*async: */true);
|
||||||
if (xhr.overrideMimeType) {
|
if (xhr.overrideMimeType) {
|
||||||
xhr.overrideMimeType('application/json; charset=utf-8');
|
xhr.overrideMimeType('application/json; charset=utf-8');
|
||||||
}
|
}
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
if (xhr.readyState == 4) {
|
if (xhr.readyState == 4) {
|
||||||
if (xhr.status == 200 || xhr.status === 0) {
|
if (xhr.status == 200 || xhr.status === 0) {
|
||||||
var data = JSON.parse(xhr.responseText)
|
var data = JSON.parse(xhr.responseText);
|
||||||
that.cache[href] = data
|
that.cache[href] = data;
|
||||||
// Pass on the contents for parsing
|
// Pass on the contents for parsing
|
||||||
that.parse(lang, href, data, cb)
|
that.parse(lang, href, data, cb);
|
||||||
} else {
|
} else {
|
||||||
cb(new Error('Failed to load '+href))
|
cb(new Error('Failed to load '+href));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -174,39 +174,39 @@ window.html10n = (function(window, document, undefined) {
|
||||||
|
|
||||||
Loader.prototype.parse = function(lang, currHref, data, cb) {
|
Loader.prototype.parse = function(lang, currHref, data, cb) {
|
||||||
if ('object' != typeof data) {
|
if ('object' != typeof data) {
|
||||||
cb(new Error('A file couldn\'t be parsed as json.'))
|
cb(new Error('A file couldn\'t be parsed as json.'));
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data[lang]) lang = lang.substr(0, lang.indexOf('-') == -1? lang.length : lang.indexOf('-'))
|
if (!data[lang]) lang = lang.substr(0, lang.indexOf('-') == -1? lang.length : lang.indexOf('-'));
|
||||||
if (!data[lang]) {
|
if (!data[lang]) {
|
||||||
cb(new Error('Couldn\'t find translations for '+lang))
|
cb(new Error('Couldn\'t find translations for '+lang));
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('string' == typeof data[lang]) {
|
if ('string' == typeof data[lang]) {
|
||||||
// Import rule
|
// Import rule
|
||||||
|
|
||||||
// absolute path
|
// absolute path
|
||||||
var importUrl = data[lang]
|
var importUrl = data[lang];
|
||||||
|
|
||||||
// relative path
|
// relative path
|
||||||
if(data[lang].indexOf("http") != 0 && data[lang].indexOf("/") != 0) {
|
if(data[lang].indexOf("http") != 0 && data[lang].indexOf("/") != 0) {
|
||||||
importUrl = currHref+"/../"+data[lang]
|
importUrl = currHref+"/../"+data[lang];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetch(importUrl, lang, cb)
|
this.fetch(importUrl, lang, cb);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('object' != typeof data[lang]) {
|
if ('object' != typeof data[lang]) {
|
||||||
cb(new Error('Translations should be specified as JSON objects!'))
|
cb(new Error('Translations should be specified as JSON objects!'));
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.langs[lang] = data[lang]
|
this.langs[lang] = data[lang];
|
||||||
// TODO: Also store accompanying langs
|
// TODO: Also store accompanying langs
|
||||||
cb()
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -216,11 +216,11 @@ window.html10n = (function(window, document, undefined) {
|
||||||
var html10n =
|
var html10n =
|
||||||
{ language : null
|
{ language : null
|
||||||
}
|
}
|
||||||
MicroEvent.mixin(html10n)
|
MicroEvent.mixin(html10n);
|
||||||
|
|
||||||
html10n.macros = {}
|
html10n.macros = {};
|
||||||
|
|
||||||
html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"]
|
html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get rules for plural forms (shared with JetPack), see:
|
* Get rules for plural forms (shared with JetPack), see:
|
||||||
|
@ -664,14 +664,14 @@ window.html10n = (function(window, document, undefined) {
|
||||||
* @param langs An array of lang codes defining fallbacks
|
* @param langs An array of lang codes defining fallbacks
|
||||||
*/
|
*/
|
||||||
html10n.localize = function(langs) {
|
html10n.localize = function(langs) {
|
||||||
var that = this
|
var that = this;
|
||||||
// if only one string => create an array
|
// if only one string => create an array
|
||||||
if ('string' == typeof langs) langs = [langs]
|
if ('string' == typeof langs) langs = [langs];
|
||||||
|
|
||||||
this.build(langs, function(er, translations) {
|
this.build(langs, function(er, translations) {
|
||||||
html10n.translations = translations
|
html10n.translations = translations;
|
||||||
html10n.translateElement(translations)
|
html10n.translateElement(translations);
|
||||||
that.trigger('localized')
|
that.trigger('localized');
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -682,78 +682,78 @@ window.html10n = (function(window, document, undefined) {
|
||||||
* @param element A DOM element, if omitted, the document element will be used
|
* @param element A DOM element, if omitted, the document element will be used
|
||||||
*/
|
*/
|
||||||
html10n.translateElement = function(translations, element) {
|
html10n.translateElement = function(translations, element) {
|
||||||
element = element || document.documentElement
|
element = element || document.documentElement;
|
||||||
|
|
||||||
var children = element? getTranslatableChildren(element) : document.childNodes;
|
var children = element? getTranslatableChildren(element) : document.childNodes;
|
||||||
for (var i=0, n=children.length; i < n; i++) {
|
for (var i=0, n=children.length; i < n; i++) {
|
||||||
this.translateNode(translations, children[i])
|
this.translateNode(translations, children[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// translate element itself if necessary
|
// translate element itself if necessary
|
||||||
this.translateNode(translations, element)
|
this.translateNode(translations, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
function asyncForEach(list, iterator, cb) {
|
function asyncForEach(list, iterator, cb) {
|
||||||
var i = 0
|
var i = 0
|
||||||
, n = list.length
|
, n = list.length;
|
||||||
iterator(list[i], i, function each(err) {
|
iterator(list[i], i, function each(err) {
|
||||||
if(err) consoleLog(err)
|
if(err) consoleLog(err);
|
||||||
i++
|
i++;
|
||||||
if (i < n) return iterator(list[i],i, each);
|
if (i < n) return iterator(list[i],i, each);
|
||||||
cb()
|
cb();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTranslatableChildren(element) {
|
function getTranslatableChildren(element) {
|
||||||
if(!document.querySelectorAll) {
|
if(!document.querySelectorAll) {
|
||||||
if (!element) return []
|
if (!element) return [];
|
||||||
var nodes = element.getElementsByTagName('*')
|
var nodes = element.getElementsByTagName('*')
|
||||||
, l10nElements = []
|
, l10nElements = [];
|
||||||
for (var i=0, n=nodes.length; i < n; i++) {
|
for (var i=0, n=nodes.length; i < n; i++) {
|
||||||
if (nodes[i].getAttribute('data-l10n-id'))
|
if (nodes[i].getAttribute('data-l10n-id'))
|
||||||
l10nElements.push(nodes[i]);
|
l10nElements.push(nodes[i]);
|
||||||
}
|
}
|
||||||
return l10nElements
|
return l10nElements;
|
||||||
}
|
}
|
||||||
return element.querySelectorAll('*[data-l10n-id]')
|
return element.querySelectorAll('*[data-l10n-id]');
|
||||||
}
|
}
|
||||||
|
|
||||||
html10n.get = function(id, args) {
|
html10n.get = function(id, args) {
|
||||||
var translations = html10n.translations
|
var translations = html10n.translations;
|
||||||
if(!translations) return consoleWarn('No translations available (yet)')
|
if(!translations) return consoleWarn('No translations available (yet)');
|
||||||
if(!translations[id]) return consoleWarn('Could not find string '+id)
|
if(!translations[id]) return consoleWarn('Could not find string '+id);
|
||||||
|
|
||||||
// apply args
|
// apply args
|
||||||
var str = substArguments(translations[id], args)
|
var str = substArguments(translations[id], args);
|
||||||
|
|
||||||
// apply macros
|
// apply macros
|
||||||
return substMacros(id, str, args)
|
return substMacros(id, str, args);
|
||||||
|
|
||||||
// replace {{arguments}} with their values or the
|
// replace {{arguments}} with their values or the
|
||||||
// associated translation string (based on its key)
|
// associated translation string (based on its key)
|
||||||
function substArguments(str, args) {
|
function substArguments(str, args) {
|
||||||
var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/
|
var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/
|
||||||
, match
|
, match;
|
||||||
|
|
||||||
while (match = reArgs.exec(str)) {
|
while (match = reArgs.exec(str)) {
|
||||||
if (!match || match.length < 2)
|
if (!match || match.length < 2)
|
||||||
return str // argument key not found
|
return str; // argument key not found
|
||||||
|
|
||||||
var arg = match[1]
|
var arg = match[1]
|
||||||
, sub = ''
|
, sub = '';
|
||||||
if (arg in args) {
|
if (arg in args) {
|
||||||
sub = args[arg]
|
sub = args[arg];
|
||||||
} else if (arg in translations) {
|
} else if (arg in translations) {
|
||||||
sub = translations[arg]
|
sub = translations[arg];
|
||||||
} else {
|
} else {
|
||||||
consoleWarn('Could not find argument {{' + arg + '}}')
|
consoleWarn('Could not find argument {{' + arg + '}}');
|
||||||
return str
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length)
|
str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length);
|
||||||
}
|
}
|
||||||
|
|
||||||
return str
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace {[macros]} with their values
|
// replace {[macros]} with their values
|
||||||
|
@ -766,21 +766,21 @@ window.html10n = (function(window, document, undefined) {
|
||||||
// a macro has been found
|
// a macro has been found
|
||||||
// Note: at the moment, only one parameter is supported
|
// Note: at the moment, only one parameter is supported
|
||||||
var macroName = reMatch[1]
|
var macroName = reMatch[1]
|
||||||
, paramName = reMatch[2]
|
, paramName = reMatch[2];
|
||||||
|
|
||||||
if (!(macroName in gMacros)) return str
|
if (!(macroName in gMacros)) return str;
|
||||||
|
|
||||||
var param
|
var param;
|
||||||
if (args && paramName in args) {
|
if (args && paramName in args) {
|
||||||
param = args[paramName]
|
param = args[paramName];
|
||||||
} else if (paramName in translations) {
|
} else if (paramName in translations) {
|
||||||
param = translations[paramName]
|
param = translations[paramName];
|
||||||
}
|
}
|
||||||
|
|
||||||
// there's no macro parser yet: it has to be defined in gMacros
|
// there's no macro parser yet: it has to be defined in gMacros
|
||||||
var macro = html10n.macros[macroName]
|
var macro = html10n.macros[macroName];
|
||||||
str = macro(translations, key, str, param)
|
str = macro(translations, key, str, param);
|
||||||
return str
|
return str;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -788,26 +788,26 @@ window.html10n = (function(window, document, undefined) {
|
||||||
* Applies translations to a DOM node (recursive)
|
* Applies translations to a DOM node (recursive)
|
||||||
*/
|
*/
|
||||||
html10n.translateNode = function(translations, node) {
|
html10n.translateNode = function(translations, node) {
|
||||||
var str = {}
|
var str = {};
|
||||||
|
|
||||||
// get id
|
// get id
|
||||||
str.id = node.getAttribute('data-l10n-id')
|
str.id = node.getAttribute('data-l10n-id');
|
||||||
if (!str.id) return
|
if (!str.id) return;
|
||||||
|
|
||||||
if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id)
|
if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id);
|
||||||
|
|
||||||
// get args
|
// get args
|
||||||
if(window.JSON) {
|
if(window.JSON) {
|
||||||
str.args = JSON.parse(node.getAttribute('data-l10n-args'))
|
str.args = JSON.parse(node.getAttribute('data-l10n-args'));
|
||||||
}else{
|
}else{
|
||||||
try{
|
try{
|
||||||
str.args = eval(node.getAttribute('data-l10n-args'))
|
str.args = eval(node.getAttribute('data-l10n-args'));
|
||||||
}catch(e) {
|
}catch(e) {
|
||||||
consoleWarn('Couldn\'t parse args for '+str.id)
|
consoleWarn('Couldn\'t parse args for '+str.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
str.str = html10n.get(str.id, str.args)
|
str.str = html10n.get(str.id, str.args);
|
||||||
|
|
||||||
// get attribute name to apply str to
|
// get attribute name to apply str to
|
||||||
var prop
|
var prop
|
||||||
|
@ -817,31 +817,31 @@ window.html10n = (function(window, document, undefined) {
|
||||||
, "innerHTML": 1
|
, "innerHTML": 1
|
||||||
, "alt": 1
|
, "alt": 1
|
||||||
, "textContent": 1
|
, "textContent": 1
|
||||||
}
|
};
|
||||||
if (index > 0 && str.id.substr(index + 1) in attrList) { // an attribute has been specified
|
if (index > 0 && str.id.substr(index + 1) in attrList) { // an attribute has been specified
|
||||||
prop = str.id.substr(index + 1)
|
prop = str.id.substr(index + 1);
|
||||||
} else { // no attribute: assuming text content by default
|
} else { // no attribute: assuming text content by default
|
||||||
prop = document.body.textContent ? 'textContent' : 'innerText'
|
prop = document.body.textContent ? 'textContent' : 'innerText';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply translation
|
// Apply translation
|
||||||
if (node.children.length === 0 || prop != 'textContent') {
|
if (node.children.length === 0 || prop != 'textContent') {
|
||||||
node[prop] = str.str
|
node[prop] = str.str;
|
||||||
} else {
|
} else {
|
||||||
var children = node.childNodes,
|
var children = node.childNodes,
|
||||||
found = false
|
found = false;
|
||||||
for (var i=0, n=children.length; i < n; i++) {
|
for (var i=0, n=children.length; i < n; i++) {
|
||||||
if (children[i].nodeType === 3 && /\S/.test(children[i].textContent)) {
|
if (children[i].nodeType === 3 && /\S/.test(children[i].textContent)) {
|
||||||
if (!found) {
|
if (!found) {
|
||||||
children[i].nodeValue = str.str
|
children[i].nodeValue = str.str;
|
||||||
found = true
|
found = true;
|
||||||
} else {
|
} else {
|
||||||
children[i].nodeValue = ''
|
children[i].nodeValue = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
consoleWarn('Unexpected error: could not translate element content for key '+str.id, node)
|
consoleWarn('Unexpected error: could not translate element content for key '+str.id, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -852,32 +852,32 @@ window.html10n = (function(window, document, undefined) {
|
||||||
*/
|
*/
|
||||||
html10n.build = function(langs, cb) {
|
html10n.build = function(langs, cb) {
|
||||||
var that = this
|
var that = this
|
||||||
, build = {}
|
, build = {};
|
||||||
|
|
||||||
asyncForEach(langs, function (lang, i, next) {
|
asyncForEach(langs, function (lang, i, next) {
|
||||||
if(!lang) return next();
|
if(!lang) return next();
|
||||||
that.loader.load(lang, next)
|
that.loader.load(lang, next);
|
||||||
}, function() {
|
}, function() {
|
||||||
var lang
|
var lang;
|
||||||
langs.reverse()
|
langs.reverse();
|
||||||
|
|
||||||
// loop through priority array...
|
// loop through priority array...
|
||||||
for (var i=0, n=langs.length; i < n; i++) {
|
for (var i=0, n=langs.length; i < n; i++) {
|
||||||
lang = langs[i]
|
lang = langs[i];
|
||||||
|
|
||||||
if(!lang || !(lang in that.loader.langs)) continue;
|
if(!lang || !(lang in that.loader.langs)) continue;
|
||||||
|
|
||||||
// ... and apply all strings of the current lang in the list
|
// ... and apply all strings of the current lang in the list
|
||||||
// to our build object
|
// to our build object
|
||||||
for (var string in that.loader.langs[lang]) {
|
for (var string in that.loader.langs[lang]) {
|
||||||
build[string] = that.loader.langs[lang][string]
|
build[string] = that.loader.langs[lang][string];
|
||||||
}
|
}
|
||||||
|
|
||||||
// the last applied lang will be exposed as the
|
// the last applied lang will be exposed as the
|
||||||
// lang the page was translated to
|
// lang the page was translated to
|
||||||
that.language = lang
|
that.language = lang;
|
||||||
}
|
}
|
||||||
cb(null, build)
|
cb(null, build);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -893,8 +893,8 @@ window.html10n = (function(window, document, undefined) {
|
||||||
* Returns the direction of the language returned be html10n#getLanguage
|
* Returns the direction of the language returned be html10n#getLanguage
|
||||||
*/
|
*/
|
||||||
html10n.getDirection = function() {
|
html10n.getDirection = function() {
|
||||||
var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-'))
|
var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-'));
|
||||||
return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl'
|
return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -903,28 +903,28 @@ window.html10n = (function(window, document, undefined) {
|
||||||
html10n.index = function () {
|
html10n.index = function () {
|
||||||
// Find all <link>s
|
// Find all <link>s
|
||||||
var links = document.getElementsByTagName('link')
|
var links = document.getElementsByTagName('link')
|
||||||
, resources = []
|
, resources = [];
|
||||||
for (var i=0, n=links.length; i < n; i++) {
|
for (var i=0, n=links.length; i < n; i++) {
|
||||||
if (links[i].type != 'application/l10n+json')
|
if (links[i].type != 'application/l10n+json')
|
||||||
continue;
|
continue;
|
||||||
resources.push(links[i].href)
|
resources.push(links[i].href);
|
||||||
}
|
}
|
||||||
this.loader = new Loader(resources)
|
this.loader = new Loader(resources);
|
||||||
this.trigger('indexed')
|
this.trigger('indexed');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.addEventListener) // modern browsers and IE9+
|
if (document.addEventListener) // modern browsers and IE9+
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
html10n.index()
|
html10n.index();
|
||||||
}, false)
|
}, false);
|
||||||
else if (window.attachEvent)
|
else if (window.attachEvent)
|
||||||
window.attachEvent('onload', function() {
|
window.attachEvent('onload', function() {
|
||||||
html10n.index()
|
html10n.index();
|
||||||
}, false)
|
}, false);
|
||||||
|
|
||||||
// gettext-like shortcut
|
// gettext-like shortcut
|
||||||
if (window._ === undefined)
|
if (window._ === undefined)
|
||||||
window._ = html10n.get;
|
window._ = html10n.get;
|
||||||
|
|
||||||
return html10n
|
return html10n;
|
||||||
})(window, document)
|
})(window, document);
|
||||||
|
|
|
@ -94,11 +94,12 @@ exports.search = function(query, cache, cb) {
|
||||||
if (er) return cb(er);
|
if (er) return cb(er);
|
||||||
var res = {};
|
var res = {};
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
var pattern = query.pattern.toLowerCase();
|
||||||
for (key in data) { // for every plugin in the data from npm
|
for (key in data) { // for every plugin in the data from npm
|
||||||
if ( key.indexOf(plugins.prefix) == 0
|
if ( key.indexOf(plugins.prefix) == 0
|
||||||
&& key.indexOf(query.pattern) != -1
|
&& key.indexOf(pattern) != -1
|
||||||
|| key.indexOf(plugins.prefix) == 0
|
|| key.indexOf(plugins.prefix) == 0
|
||||||
&& data[key].description.indexOf(query.pattern) != -1
|
&& data[key].description.indexOf(pattern) != -1
|
||||||
) { // If the name contains ep_ and the search string is in the name or description
|
) { // If the name contains ep_ and the search string is in the name or description
|
||||||
i++;
|
i++;
|
||||||
if (i > query.offset
|
if (i > query.offset
|
||||||
|
|
|
@ -10,11 +10,14 @@
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<h1>Etherpad lite</h1>
|
<h1>Etherpad lite</h1>
|
||||||
<li><a href="admin/plugins">Plugin manager</a> </li>
|
<ul>
|
||||||
<li><a href="admin/settings">Settings</a> </li>
|
<% e.begin_block("adminMenu"); %>
|
||||||
<li><a href="admin/plugins/info">Troubleshooting information</a> </li>
|
<li><a href="admin/plugins">Plugin manager</a> </li>
|
||||||
|
<li><a href="admin/settings">Settings</a> </li>
|
||||||
|
<li><a href="admin/plugins/info">Troubleshooting information</a> </li>
|
||||||
|
<% e.end_block(); %>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div id="topborder"></div>
|
<div id="topborder"></div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -12,9 +12,13 @@
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<h1>Etherpad lite</h1>
|
<h1>Etherpad lite</h1>
|
||||||
<li><a href="../plugins">Plugin manager</a> </li>
|
<ul>
|
||||||
<li><a href="../settings">Settings</a> </li>
|
<% e.begin_block("adminMenu"); %>
|
||||||
<li><a href="../plugins/info">Troubleshooting information</a> </li>
|
<li><a href="../plugins">Plugin manager</a> </li>
|
||||||
|
<li><a href="../settings">Settings</a> </li>
|
||||||
|
<li><a href="../plugins/info">Troubleshooting information</a> </li>
|
||||||
|
<% e.end_block(); %>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="innerwrapper">
|
<div class="innerwrapper">
|
||||||
|
|
|
@ -20,10 +20,13 @@
|
||||||
|
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<h1>Etherpad lite</h1>
|
<h1>Etherpad lite</h1>
|
||||||
<li><a href="plugins">Plugin manager</a> </li>
|
<ul>
|
||||||
<li><a href="settings">Settings</a> </li>
|
<% e.begin_block("adminMenu"); %>
|
||||||
<li><a href="plugins/info">Troubleshooting information</a> </li>
|
<li><a href="plugins">Plugin manager</a> </li>
|
||||||
|
<li><a href="settings">Settings</a> </li>
|
||||||
|
<li><a href="plugins/info">Troubleshooting information</a> </li>
|
||||||
|
<% e.end_block(); %>
|
||||||
|
</ul>
|
||||||
<div id="progress"><img src="../static/img/loading.gif" alt=""/> <span class="message"></span></div>
|
<div id="progress"><img src="../static/img/loading.gif" alt=""/> <span class="message"></span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,13 @@
|
||||||
|
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<h1>Etherpad lite</h1>
|
<h1>Etherpad lite</h1>
|
||||||
<li><a href="plugins">Plugin manager</a> </li>
|
<ul>
|
||||||
<li><a href="settings">Settings</a> </li>
|
<% e.begin_block("adminMenu"); %>
|
||||||
<li><a href="plugins/info">Troubleshooting information</a> </li>
|
<li><a href="plugins">Plugin manager</a> </li>
|
||||||
|
<li><a href="settings">Settings</a> </li>
|
||||||
|
<li><a href="plugins/info">Troubleshooting information</a> </li>
|
||||||
|
<% e.end_block(); %>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="innerwrapper">
|
<div class="innerwrapper">
|
||||||
|
|
|
@ -64,7 +64,8 @@
|
||||||
box-shadow: 0px 1px 8px rgba(0,0,0,0.3);
|
box-shadow: 0px 1px 8px rgba(0,0,0,0.3);
|
||||||
}
|
}
|
||||||
#inner {
|
#inner {
|
||||||
width: 300px;
|
position:relative;
|
||||||
|
max-width: 300px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
#button {
|
#button {
|
||||||
|
@ -100,6 +101,10 @@
|
||||||
text-shadow: 0 1px 1px #fff;
|
text-shadow: 0 1px 1px #fff;
|
||||||
margin: 16px auto 0;
|
margin: 16px auto 0;
|
||||||
}
|
}
|
||||||
|
#padname{
|
||||||
|
height:38px;
|
||||||
|
max-width:280px;
|
||||||
|
}
|
||||||
form {
|
form {
|
||||||
height: 38px;
|
height: 38px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
@ -115,7 +120,8 @@
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
-moz-box-sizing: border-box;
|
-moz-box-sizing: border-box;
|
||||||
padding: 0 45px 0 10px;
|
line-height:36px; /* IE8 hack */
|
||||||
|
padding: 0px 45px 0 10px;
|
||||||
*padding: 0; /* IE7 hack */
|
*padding: 0; /* IE7 hack */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -125,7 +131,7 @@
|
||||||
}
|
}
|
||||||
button[type="submit"] {
|
button[type="submit"] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
left:253px;
|
||||||
width: 45px;
|
width: 45px;
|
||||||
height: 38px;
|
height: 38px;
|
||||||
}
|
}
|
||||||
|
|
64
tests/frontend/specs/timeslider_labels.js
Normal file
64
tests/frontend/specs/timeslider_labels.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
describe("timeslider", function(){
|
||||||
|
//create a new pad before each test run
|
||||||
|
beforeEach(function(cb){
|
||||||
|
helper.newPad(cb);
|
||||||
|
this.timeout(60000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Shows a date and time in the timeslider and make sure it doesn't include NaN", function(done) {
|
||||||
|
var inner$ = helper.padInner$;
|
||||||
|
var chrome$ = helper.padChrome$;
|
||||||
|
|
||||||
|
// make some changes to produce 100 revisions
|
||||||
|
var revs = 10;
|
||||||
|
this.timeout(60000);
|
||||||
|
for(var i=0; i < revs; i++) {
|
||||||
|
setTimeout(function() {
|
||||||
|
// enter 'a' in the first text element
|
||||||
|
inner$("div").first().sendkeys('a');
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
// go to timeslider
|
||||||
|
$('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider');
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
|
||||||
|
var $sliderBar = timeslider$('#ui-slider-bar');
|
||||||
|
|
||||||
|
var latestContents = timeslider$('#padcontent').text();
|
||||||
|
|
||||||
|
// Expect the date and time to be shown
|
||||||
|
|
||||||
|
// Click somewhere on the timeslider
|
||||||
|
var e = new jQuery.Event('mousedown');
|
||||||
|
e.clientX = e.pageX = 150;
|
||||||
|
e.clientY = e.pageY = 45;
|
||||||
|
$sliderBar.trigger(e);
|
||||||
|
|
||||||
|
e = new jQuery.Event('mousedown');
|
||||||
|
e.clientX = e.pageX = 150;
|
||||||
|
e.clientY = e.pageY = 40;
|
||||||
|
$sliderBar.trigger(e);
|
||||||
|
|
||||||
|
e = new jQuery.Event('mousedown');
|
||||||
|
e.clientX = e.pageX = 150;
|
||||||
|
e.clientY = e.pageY = 50;
|
||||||
|
$sliderBar.trigger(e);
|
||||||
|
|
||||||
|
$sliderBar.trigger('mouseup')
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
//make sure the text has changed
|
||||||
|
expect( timeslider$('#timer').text() ).not.to.eql( "" );
|
||||||
|
expect( timeslider$('#revision_date').text() ).not.to.eql( "" );
|
||||||
|
expect( timeslider$('#revision_label').text() ).not.to.eql( "" );
|
||||||
|
var includesNaN = timeslider$('#revision_label').text().indexOf("NaN"); // NaN is bad. Naan ist gut
|
||||||
|
expect( includesNaN ).to.eql( -1 ); // not quite so tasty, I like curry.
|
||||||
|
done();
|
||||||
|
}, 400);
|
||||||
|
}, 2000);
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue