A lot small changes that results in a timeslider that shows the latest text
|
@ -72,6 +72,16 @@ exports.getAuthor4Token = function (token, callback)
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Author Obj of the author
|
||||
* @param {String} author The id of the author
|
||||
* @param {Function} callback callback(err, authorObj)
|
||||
*/
|
||||
exports.getAuthor = function (author, callback)
|
||||
{
|
||||
db.get("globalAuthor:" + author, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color Id of the author
|
||||
* @param {String} author The id of the author
|
||||
|
|
|
@ -90,6 +90,11 @@ Class('Pad', {
|
|||
db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "author"], callback);
|
||||
}, // getRevisionAuthor
|
||||
|
||||
getRevisionDate : function(revNum, callback)
|
||||
{
|
||||
db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback);
|
||||
}, // getRevisionAuthor
|
||||
|
||||
getAllAuthors : function()
|
||||
{
|
||||
var authors = [];
|
||||
|
@ -105,6 +110,77 @@ Class('Pad', {
|
|||
return authors;
|
||||
},
|
||||
|
||||
getInternalRevisionAText : function(targetRev, callback)
|
||||
{
|
||||
var _this = this;
|
||||
|
||||
var keyRev = this.getKeyRevisionNumber(targetRev);
|
||||
var atext;
|
||||
var changesets = [];
|
||||
|
||||
//find out which changesets are needed
|
||||
var neededChangesets = [];
|
||||
var curRev = keyRev;
|
||||
while (curRev < targetRev)
|
||||
{
|
||||
curRev++;
|
||||
neededChangesets.push(curRev);
|
||||
}
|
||||
|
||||
async.series([
|
||||
//get all needed data out of the database
|
||||
function(callback)
|
||||
{
|
||||
async.parallel([
|
||||
//get the atext of the key revision
|
||||
function (callback)
|
||||
{
|
||||
db.getSub("pad:"+_this.id+":revs:"+keyRev, ["meta", "atext"], function(err, _atext)
|
||||
{
|
||||
atext = Changeset.cloneAText(_atext);
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
//get all needed changesets
|
||||
function (callback)
|
||||
{
|
||||
async.forEach(neededChangesets, function(item, callback)
|
||||
{
|
||||
_this.getRevisionChangeset(item, function(err, changeset)
|
||||
{
|
||||
changesets[item] = changeset;
|
||||
callback(err);
|
||||
});
|
||||
}, callback);
|
||||
}
|
||||
], callback);
|
||||
},
|
||||
//apply all changesets to the key changeset
|
||||
function(callback)
|
||||
{
|
||||
var apool = _this.apool();
|
||||
var curRev = keyRev;
|
||||
|
||||
while (curRev < targetRev)
|
||||
{
|
||||
curRev++;
|
||||
var cs = changesets[curRev];
|
||||
atext = Changeset.applyToAText(cs, atext, apool);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
callback(err, atext);
|
||||
});
|
||||
},
|
||||
|
||||
getKeyRevisionNumber : function(revNum)
|
||||
{
|
||||
return Math.floor(revNum / 100) * 100;
|
||||
},
|
||||
|
||||
text : function()
|
||||
{
|
||||
return this.atext.text;
|
||||
|
|
|
@ -143,11 +143,6 @@ exports.handleMessage = function(client, message)
|
|||
{
|
||||
throw "Message is null!";
|
||||
}
|
||||
//Etherpad sometimes send JSON and sometimes a JSONstring...
|
||||
if(typeof message == "string")
|
||||
{
|
||||
message = JSON.parse(message);
|
||||
}
|
||||
if(!message.type)
|
||||
{
|
||||
throw "Message have no type attribute!";
|
||||
|
@ -161,19 +156,16 @@ exports.handleMessage = function(client, message)
|
|||
else if(message.type == "COLLABROOM" &&
|
||||
message.data.type == "USER_CHANGES")
|
||||
{
|
||||
console.error(JSON.stringify(message));
|
||||
handleUserChanges(client, message);
|
||||
}
|
||||
else if(message.type == "COLLABROOM" &&
|
||||
message.data.type == "USERINFO_UPDATE")
|
||||
{
|
||||
console.error(JSON.stringify(message));
|
||||
handleUserInfoUpdate(client, message);
|
||||
}
|
||||
//if the message type is unkown, throw an exception
|
||||
else
|
||||
{
|
||||
console.error(message);
|
||||
throw "unkown Message Type: '" + message.type + "'";
|
||||
}
|
||||
}
|
||||
|
@ -469,9 +461,9 @@ function handleClientReady(client, message)
|
|||
{
|
||||
throw "CLIENT_READY Message have no protocolVersion!";
|
||||
}
|
||||
if(message.protocolVersion != 1)
|
||||
if(message.protocolVersion != 2)
|
||||
{
|
||||
throw "CLIENT_READY Message have a unkown protocolVersion '" + protocolVersion + "'!";
|
||||
throw "CLIENT_READY Message have a unkown protocolVersion '" + message.protocolVersion + "'!";
|
||||
}
|
||||
|
||||
var author;
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* This is the Socket.IO Router. It routes the Messages between the
|
||||
* components of the Server. The components are at the moment: pad and timeslider
|
||||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Saves all components
|
||||
* key is the component name
|
||||
* value is the component module
|
||||
*/
|
||||
var components = {};
|
||||
|
||||
var socket;
|
||||
|
||||
/**
|
||||
* adds a component
|
||||
*/
|
||||
exports.addComponent = function(moduleName, module)
|
||||
{
|
||||
//save the component
|
||||
components[moduleName] = module;
|
||||
|
||||
//give the module the socket
|
||||
module.setSocketIO(socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the socket.io and adds event functions for routing
|
||||
*/
|
||||
exports.setSocketIO = function(_socket)
|
||||
{
|
||||
//save this socket internaly
|
||||
socket = _socket;
|
||||
|
||||
socket.on('connection', function(client)
|
||||
{
|
||||
//tell all components about this connect
|
||||
for(var i in components)
|
||||
{
|
||||
components[i].handleConnect(client);
|
||||
}
|
||||
|
||||
client.on('message', function(message)
|
||||
{
|
||||
//route this message to the correct component, if possible
|
||||
if(message.component && components[message.component])
|
||||
{
|
||||
console.error(message);
|
||||
|
||||
components[message.component].handleMessage(client, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Can't route the message:" + JSON.stringify(message);
|
||||
}
|
||||
});
|
||||
|
||||
client.on('disconnect', function()
|
||||
{
|
||||
//tell all components about this disconnect
|
||||
for(var i in components)
|
||||
{
|
||||
components[i].handleDisconnect(client);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,446 @@
|
|||
/**
|
||||
* The MessageHandler handles all Messages that comes from Socket.IO and controls the sessions
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var async = require("async");
|
||||
var padManager = require("./PadManager");
|
||||
var Changeset = require("./Changeset");
|
||||
var AttributePoolFactory = require("./AttributePoolFactory");
|
||||
var authorManager = require("./AuthorManager");
|
||||
|
||||
/**
|
||||
* Saves the Socket class we need to send and recieve data from the client
|
||||
*/
|
||||
var socketio;
|
||||
|
||||
/**
|
||||
* This Method is called by server.js to tell the message handler on which socket it should send
|
||||
* @param socket_io The Socket
|
||||
*/
|
||||
exports.setSocketIO = function(socket_io)
|
||||
{
|
||||
socketio=socket_io;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the connection of a new user
|
||||
* @param client the new client
|
||||
*/
|
||||
exports.handleConnect = function(client)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the disconnection of a user
|
||||
* @param client the client that leaves
|
||||
*/
|
||||
exports.handleDisconnect = function(client)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a message from a user
|
||||
* @param client the client that send this message
|
||||
* @param message the message from the client
|
||||
*/
|
||||
exports.handleMessage = function(client, message)
|
||||
{
|
||||
//Check what type of message we get and delegate to the other methodes
|
||||
if(message.type == "CLIENT_READY")
|
||||
{
|
||||
handleClientReady(client, message);
|
||||
}
|
||||
//if the message type is unkown, throw an exception
|
||||
else
|
||||
{
|
||||
throw "unkown Message Type: '" + message.type + "'";
|
||||
}
|
||||
}
|
||||
|
||||
function handleClientReady(client, message)
|
||||
{
|
||||
createTimesliderClientVars (message.padId, function(err, clientVars)
|
||||
{
|
||||
if(err) throw err;
|
||||
|
||||
client.send({type: "CLIENT_VARS", data: clientVars});
|
||||
})
|
||||
}
|
||||
|
||||
function createTimesliderClientVars (padId, callback)
|
||||
{
|
||||
var clientVars = {
|
||||
viewId: padId,
|
||||
colorPalette: ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd"],
|
||||
sliderEnabled : true,
|
||||
supportsSlider: true,
|
||||
savedRevisions: [],
|
||||
padIdForUrl: padId,
|
||||
fullWidth: false,
|
||||
disableRightBar: false,
|
||||
initialChangesets: [],
|
||||
hooks: [],
|
||||
initialStyledContents: {}
|
||||
};
|
||||
var pad;
|
||||
var initialChangesets = [];
|
||||
|
||||
async.series([
|
||||
//get the pad from the database
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(padId, function(err, _pad)
|
||||
{
|
||||
pad = _pad;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
//get all authors and add them to
|
||||
function(callback)
|
||||
{
|
||||
var historicalAuthorData = {};
|
||||
//get all authors out of the attribut pool
|
||||
var authors = pad.getAllAuthors();
|
||||
|
||||
//get all author data out of the database
|
||||
async.forEach(authors, function(authorId, callback)
|
||||
{
|
||||
authorManager.getAuthor(authorId, function(err, author)
|
||||
{
|
||||
historicalAuthorData[authorId] = author;
|
||||
callback(err);
|
||||
});
|
||||
}, function(err)
|
||||
{
|
||||
//add historicalAuthorData to the clientVars and continue
|
||||
clientVars.historicalAuthorData = historicalAuthorData;
|
||||
clientVars.initialStyledContents.historicalAuthorData = historicalAuthorData;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
//currentTime: rev.timestamp,
|
||||
|
||||
//get the head revision Number
|
||||
var lastRev = pad.getHeadRevisionNumber();
|
||||
|
||||
//add the revNum to the client Vars
|
||||
clientVars.revNum = lastRev;
|
||||
clientVars.totalRevs = lastRev;
|
||||
|
||||
var atext = Changeset.cloneAText(pad.atext);
|
||||
var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
|
||||
var apool = attribsForWire.pool.toJsonable();
|
||||
atext.attribs = attribsForWire.translated;
|
||||
|
||||
clientVars.initialStyledContents.apool = apool;
|
||||
clientVars.initialStyledContents.atext = atext;
|
||||
|
||||
var granularities = [100, 10, 1];
|
||||
|
||||
//get the latest rough changesets
|
||||
async.forEach(granularities, function(granularity, callback)
|
||||
{
|
||||
var topGranularity = granularity*10;
|
||||
|
||||
getChangesetInfo(padId, Math.floor(lastRev / topGranularity)*topGranularity,
|
||||
Math.floor(lastRev / topGranularity)*topGranularity+topGranularity, granularity,
|
||||
function(err, changeset)
|
||||
{
|
||||
clientVars.initialChangesets.push(changeset);
|
||||
callback(err);
|
||||
});
|
||||
}, callback);
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
callback(err, clientVars);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to rebuild the getChangestInfo function of the original Etherpad
|
||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L144
|
||||
*/
|
||||
function getChangesetInfo(padId, startNum, endNum, granularity, callback)
|
||||
{
|
||||
var forwardsChangesets = [];
|
||||
var backwardsChangesets = [];
|
||||
var timeDeltas = [];
|
||||
var apool = AttributePoolFactory.createAttributePool();
|
||||
var pad;
|
||||
var composedChangesets = {};
|
||||
var revisionDate = [];
|
||||
var lines;
|
||||
|
||||
async.series([
|
||||
//get the pad from the database
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(padId, function(err, _pad)
|
||||
{
|
||||
pad = _pad;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
//calculate the last full endnum
|
||||
var lastRev = pad.getHeadRevisionNumber();
|
||||
if (endNum > lastRev+1) {
|
||||
endNum = lastRev+1;
|
||||
}
|
||||
endNum = Math.floor(endNum / granularity)*granularity;
|
||||
|
||||
var compositesChangesetNeeded = [];
|
||||
var revTimesNeeded = [];
|
||||
|
||||
//figure out which composite Changeset and revTimes we need, to load them in bulk
|
||||
var compositeStart = startNum;
|
||||
while (compositeStart < endNum)
|
||||
{
|
||||
var compositeEnd = compositeStart + granularity;
|
||||
|
||||
//add the composite Changeset we needed
|
||||
compositesChangesetNeeded.push({start: compositeStart, end: compositeEnd});
|
||||
|
||||
//add the t1 time we need
|
||||
revTimesNeeded.push(compositeStart == 0 ? 0 : compositeStart - 1);
|
||||
//add the t2 time we need
|
||||
revTimesNeeded.push(compositeEnd - 1);
|
||||
|
||||
compositeStart += granularity;
|
||||
}
|
||||
|
||||
//get all needed db values parallel
|
||||
async.parallel([
|
||||
function(callback)
|
||||
{
|
||||
//get all needed composite Changesets
|
||||
async.forEach(compositesChangesetNeeded, function(item, callback)
|
||||
{
|
||||
composePadChangesets(padId, item.start, item.end, function(err, changeset)
|
||||
{
|
||||
composedChangesets[item.start + "/" + item.end] = changeset;
|
||||
callback(err);
|
||||
});
|
||||
}, callback);
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
//get all needed revision Dates
|
||||
async.forEach(revTimesNeeded, function(revNum, callback)
|
||||
{
|
||||
pad.getRevisionDate(revNum, function(err, revDate)
|
||||
{
|
||||
revisionDate[revNum] = Math.floor(revDate/1000);
|
||||
callback(err);
|
||||
});
|
||||
}, callback);
|
||||
},
|
||||
//get the lines
|
||||
function(callback)
|
||||
{
|
||||
getPadLines(padId, startNum-1, function(err, _lines)
|
||||
{
|
||||
lines = _lines;
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
},
|
||||
//doesn't know what happens here excatly :/
|
||||
function(callback)
|
||||
{
|
||||
var compositeStart = startNum;
|
||||
|
||||
while (compositeStart < endNum)
|
||||
{
|
||||
if (compositeStart + granularity > endNum)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var compositeEnd = compositeStart + granularity;
|
||||
|
||||
var forwards = composedChangesets[compositeStart + "/" + compositeEnd];
|
||||
var backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool());
|
||||
|
||||
Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool());
|
||||
Changeset.mutateTextLines(forwards, lines.textlines);
|
||||
|
||||
var forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool);
|
||||
var backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool);
|
||||
|
||||
var t1, t2;
|
||||
if (compositeStart == 0)
|
||||
{
|
||||
t1 = revisionDate[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
t1 = revisionDate[compositeStart - 1];
|
||||
}
|
||||
|
||||
t2 = revisionDate[compositeEnd - 1];
|
||||
|
||||
timeDeltas.push(t2 - t1);
|
||||
forwardsChangesets.push(forwards2);
|
||||
backwardsChangesets.push(backwards2);
|
||||
|
||||
compositeStart += granularity;
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(null, {forwardsChangesets: forwardsChangesets,
|
||||
backwardsChangesets: backwardsChangesets,
|
||||
apool: apool.toJsonable(),
|
||||
actualEndNum: endNum,
|
||||
timeDeltas: timeDeltas,
|
||||
start: startNum,
|
||||
granularity: granularity });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to rebuild the getPadLines function of the original Etherpad
|
||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L263
|
||||
*/
|
||||
function getPadLines(padId, revNum, callback)
|
||||
{
|
||||
var atext;
|
||||
var result = {};
|
||||
var pad;
|
||||
|
||||
async.series([
|
||||
//get the pad from the database
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(padId, function(err, _pad)
|
||||
{
|
||||
pad = _pad;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
//get the atext
|
||||
function(callback)
|
||||
{
|
||||
if(revNum >= 0)
|
||||
{
|
||||
pad.getInternalRevisionAText(revNum, function(err, _atext)
|
||||
{
|
||||
atext = _atext;
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
atext = Changeset.makeAText("\n");
|
||||
callback(null);
|
||||
}
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
result.textlines = Changeset.splitTextLines(atext.text);
|
||||
result.alines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
callback(null);
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
callback(err, result);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to rebuild the composePadChangeset function of the original Etherpad
|
||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241
|
||||
*/
|
||||
function composePadChangesets(padId, startNum, endNum, callback)
|
||||
{
|
||||
var pad;
|
||||
var changesets = [];
|
||||
var changeset;
|
||||
|
||||
async.series([
|
||||
//get the pad from the database
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(padId, function(err, _pad)
|
||||
{
|
||||
pad = _pad;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
//fetch all changesets we need
|
||||
function(callback)
|
||||
{
|
||||
var changesetsNeeded=[];
|
||||
|
||||
//create a array for all changesets, we will
|
||||
//replace the values with the changeset later
|
||||
for(var r=startNum;r<endNum;r++)
|
||||
{
|
||||
changesetsNeeded.push(r);
|
||||
}
|
||||
|
||||
//get all changesets
|
||||
async.forEach(changesetsNeeded, function(revNum,callback)
|
||||
{
|
||||
pad.getRevisionChangeset(revNum, function(err, value)
|
||||
{
|
||||
changesets[revNum] = value;
|
||||
callback(err);
|
||||
});
|
||||
},callback);
|
||||
},
|
||||
//compose Changesets
|
||||
function(callback)
|
||||
{
|
||||
changeset = changesets[startNum];
|
||||
var pool = pad.apool();
|
||||
|
||||
for(var r=startNum+1;r<endNum;r++)
|
||||
{
|
||||
var cs = changesets[r];
|
||||
changeset = Changeset.compose(changeset, cs, pool);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}
|
||||
],
|
||||
//return err and changeset
|
||||
function(err)
|
||||
{
|
||||
if(err) throw err;
|
||||
callback(err, changeset);
|
||||
});
|
||||
}
|
|
@ -24,6 +24,7 @@ require('joose');
|
|||
|
||||
var socketio = require('socket.io');
|
||||
var settings = require('./settings');
|
||||
var socketIORouter = require("./SocketIORouter");
|
||||
var db = require('./db');
|
||||
var async = require('async');
|
||||
var express = require('express');
|
||||
|
@ -83,6 +84,14 @@ async.waterfall([
|
|||
res.sendfile(filePath, { maxAge: exports.maxAge });
|
||||
});
|
||||
|
||||
//serve timeslider.html under /p/$padname/timeslider
|
||||
app.get('/p/:pad/timeslider', function(req, res)
|
||||
{
|
||||
res.header("Server", serverName);
|
||||
var filePath = path.normalize(__dirname + "/../static/timeslider.html");
|
||||
res.sendfile(filePath, { maxAge: exports.maxAge });
|
||||
});
|
||||
|
||||
//serve index.html under /
|
||||
app.get('/', function(req, res)
|
||||
{
|
||||
|
@ -113,44 +122,15 @@ async.waterfall([
|
|||
|
||||
//init socket.io and redirect all requests to the MessageHandler
|
||||
var io = socketio.listen(app);
|
||||
var messageHandler = require("./MessageHandler");
|
||||
messageHandler.setSocketIO(io);
|
||||
io.on('connection', function(client){
|
||||
try{
|
||||
messageHandler.handleConnect(client);
|
||||
}catch(e){errorlog(e);}
|
||||
|
||||
client.on('message', function(message){
|
||||
try{
|
||||
messageHandler.handleMessage(client, message);
|
||||
}catch(e){errorlog(e);}
|
||||
});
|
||||
|
||||
client.on('disconnect', function(){
|
||||
try{
|
||||
messageHandler.handleDisconnect(client);
|
||||
}catch(e){errorlog(e);}
|
||||
});
|
||||
});
|
||||
|
||||
var padMessageHandler = require("./PadMessageHandler");
|
||||
var timesliderMessageHandler = require("./TimesliderMessageHandler");
|
||||
|
||||
//Initalize the Socket.IO Router
|
||||
socketIORouter.setSocketIO(io);
|
||||
socketIORouter.addComponent("pad", padMessageHandler);
|
||||
socketIORouter.addComponent("timeslider", timesliderMessageHandler);
|
||||
|
||||
callback(null);
|
||||
}
|
||||
]);
|
||||
|
||||
function errorlog(e)
|
||||
{
|
||||
var timeStr = new Date().toUTCString() + ": ";
|
||||
|
||||
if(typeof e == "string")
|
||||
{
|
||||
console.error(timeStr + e);
|
||||
}
|
||||
else if(e.stack != null)
|
||||
{
|
||||
console.error(timeStr + e.stack);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error(timeStr + JSON.stringify(e));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
],
|
||||
"dependencies" : {
|
||||
"socket.io" : "0.6.18",
|
||||
"ueberDB" : "0.0.6",
|
||||
"ueberDB" : "0.0.7",
|
||||
"async" : "0.1.9",
|
||||
"joose" : "3.18.0",
|
||||
"express" : "2.3.10",
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
#editorcontainerbox {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#padcontent {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#timeslider-wrapper {
|
||||
position: relative;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
#timeslider-left {
|
||||
position: absolute;
|
||||
left:-2px;
|
||||
background-image: url(/static/img/timeslider_left.png);
|
||||
width: 134px;
|
||||
height: 63px;
|
||||
}
|
||||
|
||||
#timeslider-right {
|
||||
position: absolute;
|
||||
top:0px;
|
||||
right:-2px;
|
||||
background-image: url(/static/img/timeslider_right.png);
|
||||
width: 155px;
|
||||
height: 63px;
|
||||
}
|
||||
|
||||
|
||||
#timeslider {
|
||||
margin:7px;
|
||||
margin-bottom: 0px;
|
||||
height: 63px;
|
||||
margin-left: 9px;
|
||||
margin-right: 9px;
|
||||
background-image: url(/static/img/timeslider_background.png);
|
||||
}
|
||||
|
||||
div#timeslider #timeslider-slider {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 1px;
|
||||
height: 61px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div#ui-slider-handle {
|
||||
width: 13px;
|
||||
height: 61px;
|
||||
background-image: url(/static/img/crushed_current_location.png);
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
* html div#ui-slider-handle { /* IE 6/7 */
|
||||
background-image: url(/static/img/current_location.gif);
|
||||
}
|
||||
|
||||
div#ui-slider-bar {
|
||||
position: relative;
|
||||
margin-right: 148px;
|
||||
height: 35px;
|
||||
margin-left: 5px;
|
||||
top: 20px;
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
|
||||
div#timeslider div#playpause_button {
|
||||
background-image: url(/static/img/crushed_button_undepressed.png);
|
||||
width: 47px;
|
||||
height: 47px;
|
||||
position: absolute;
|
||||
right: 77px;
|
||||
top: 9px;
|
||||
}
|
||||
|
||||
div#timeslider div#playpause_button div#playpause_button_icon {
|
||||
background-image: url(/static/img/play.png);
|
||||
width: 47px;
|
||||
height: 47px;
|
||||
position: absolute;
|
||||
top :0px;
|
||||
left:0px;
|
||||
}
|
||||
* html div#timeslider div#playpause_button div#playpause_button_icon {
|
||||
background-image: url(/static/img/play.gif); /* IE 6/7 */
|
||||
}
|
||||
|
||||
div#timeslider div#playpause_button div.pause#playpause_button_icon {
|
||||
background-image: url(/static/img/pause.png);
|
||||
}
|
||||
* html div#timeslider div#playpause_button div.pause#playpause_button_icon {
|
||||
background-image: url(/static/img/pause.gif); /* IE 6/7 */
|
||||
}
|
||||
|
||||
div #timeslider div#steppers div#leftstar {
|
||||
position: absolute;
|
||||
right: 34px;
|
||||
top: 8px;
|
||||
width:30px;
|
||||
height:21px;
|
||||
background: url(/static/img/stepper_buttons.png) 0px 44px;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
div #timeslider div#steppers div#rightstar {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 8px;
|
||||
width:29px;
|
||||
height:21px;
|
||||
background: url(/static/img/stepper_buttons.png) 29px 44px;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
div #timeslider div#steppers div#leftstep {
|
||||
position: absolute;
|
||||
right: 34px;
|
||||
top: 33px;
|
||||
width:30px;
|
||||
height:21px;
|
||||
background: url(/static/img/stepper_buttons.png) 0px 22px;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
div #timeslider div#steppers div#rightstep {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 33px;
|
||||
width:29px;
|
||||
height:21px;
|
||||
background: url(/static/img/stepper_buttons.png) 29px 22px;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
#timeslider div.star {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
background-image: url(/static/img/star.png);
|
||||
width: 15px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
* html #timeslider div.star {
|
||||
background-image: url(/static/img/star.gif); /* IE 6/7 */
|
||||
}
|
||||
|
||||
#timeslider div#timer {
|
||||
position: absolute;
|
||||
font-family: Arial, sans-serif;
|
||||
left: 7px;
|
||||
top: 9px;
|
||||
width: 122px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#rightbars {
|
||||
margin-left: 730px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 7px;
|
||||
position: absolute;
|
||||
top:27px;
|
||||
}
|
||||
|
||||
#rightbar {
|
||||
width: 143px;
|
||||
background-color: white;
|
||||
border: 1px solid rgb(194, 194, 194);
|
||||
padding: 16px;
|
||||
padding-top: 13px;
|
||||
font-size: 1.20em;
|
||||
line-height: 1.8em;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
|
||||
#rightbar h2 {
|
||||
font-weight: 700;
|
||||
font-size: 1.2em;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
#rightbar a {
|
||||
color: rgb(50, 132, 213);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#rightbars h2 {
|
||||
font-weight: 700;
|
||||
font-size: 1.2em;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
#rightbar img {
|
||||
padding-left: 4px;
|
||||
padding-right: 8px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
#rightbar a {
|
||||
color: rgb(50, 132, 213);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#legend {
|
||||
width: 143px;
|
||||
background-color: white;
|
||||
border: 1px solid rgb(194, 194, 194);
|
||||
padding: 16px;
|
||||
padding-top: 0px;
|
||||
font-size: 1.20em;
|
||||
line-height: 1.8em;
|
||||
vertical-align: top;
|
||||
margin-top: 10px;
|
||||
}
|
||||
#legend h2 {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#authorstable {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#authorstable div.swatch {
|
||||
width:15px;
|
||||
height:15px;
|
||||
margin: 5px;
|
||||
margin-top:3px;
|
||||
margin-right: 14px;
|
||||
border: rgb(149, 149, 149) 1px solid;
|
||||
}
|
After Width: | Height: | Size: 697 B |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 1009 B |
After Width: | Height: | Size: 181 B |
After Width: | Height: | Size: 204 B |
After Width: | Height: | Size: 867 B |
After Width: | Height: | Size: 604 B |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 372 B |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 123 B |
After Width: | Height: | Size: 131 B |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 915 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,714 @@
|
|||
/**
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var global = this;
|
||||
|
||||
function loadBroadcastJS()
|
||||
{
|
||||
// just in case... (todo: this must be somewhere else in the client code.)
|
||||
// Below Array#map code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_map.htm
|
||||
if (!Array.prototype.map)
|
||||
{
|
||||
Array.prototype.map = function (fun /*, thisp*/ )
|
||||
{
|
||||
var len = this.length >>> 0;
|
||||
if (typeof fun != "function") throw new TypeError();
|
||||
|
||||
var res = new Array(len);
|
||||
var thisp = arguments[1];
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
if (i in this) res[i] = fun.call(thisp, this[i], i, this);
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
// Below Array#forEach code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_foreach.htm
|
||||
if (!Array.prototype.forEach)
|
||||
{
|
||||
Array.prototype.forEach = function (fun /*, thisp*/ )
|
||||
{
|
||||
var len = this.length >>> 0;
|
||||
if (typeof fun != "function") throw new TypeError();
|
||||
|
||||
var thisp = arguments[1];
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
if (i in this) fun.call(thisp, this[i], i, this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Below Array#indexOf code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_indexof.htm
|
||||
if (!Array.prototype.indexOf)
|
||||
{
|
||||
Array.prototype.indexOf = function (elt /*, from*/ )
|
||||
{
|
||||
var len = this.length >>> 0;
|
||||
|
||||
var from = Number(arguments[1]) || 0;
|
||||
from = (from < 0) ? Math.ceil(from) : Math.floor(from);
|
||||
if (from < 0) from += len;
|
||||
|
||||
for (; from < len; from++)
|
||||
{
|
||||
if (from in this && this[from] === elt) return from;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
}
|
||||
|
||||
function debugLog()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(window.console) console.log.apply(console, arguments);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
if(window.console) console.log("error printing: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
function randomString()
|
||||
{
|
||||
return "_" + Math.floor(Math.random() * 1000000);
|
||||
}
|
||||
|
||||
// for IE
|
||||
if ($.browser.msie)
|
||||
{
|
||||
try
|
||||
{
|
||||
document.execCommand("BackgroundImageCache", false, true);
|
||||
}
|
||||
catch (e)
|
||||
{}
|
||||
}
|
||||
|
||||
var userId = "hiddenUser" + randomString();
|
||||
var socketId;
|
||||
var socket;
|
||||
|
||||
var channelState = "DISCONNECTED";
|
||||
|
||||
var appLevelDisconnectReason = null;
|
||||
|
||||
var padContents = {
|
||||
currentRevision: clientVars.revNum,
|
||||
currentTime: clientVars.currentTime,
|
||||
currentLines: Changeset.splitTextLines(clientVars.initialStyledContents.atext.text),
|
||||
currentDivs: null,
|
||||
// to be filled in once the dom loads
|
||||
apool: (new AttribPool()).fromJsonable(clientVars.initialStyledContents.apool),
|
||||
alines: Changeset.splitAttributionLines(
|
||||
clientVars.initialStyledContents.atext.attribs, clientVars.initialStyledContents.atext.text),
|
||||
|
||||
// generates a jquery element containing HTML for a line
|
||||
lineToElement: function (line, aline)
|
||||
{
|
||||
var element = document.createElement("div");
|
||||
var emptyLine = (line == '\n');
|
||||
var domInfo = domline.createDomLine(!emptyLine, true);
|
||||
linestylefilter.populateDomLine(line, aline, this.apool, domInfo);
|
||||
domInfo.prepareForAdd();
|
||||
element.className = domInfo.node.className;
|
||||
element.innerHTML = domInfo.node.innerHTML;
|
||||
element.id = Math.random();
|
||||
return $(element);
|
||||
},
|
||||
|
||||
applySpliceToDivs: function (start, numRemoved, newLines)
|
||||
{
|
||||
// remove spliced-out lines from DOM
|
||||
for (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++)
|
||||
{
|
||||
debugLog("removing", this.currentDivs[i].attr('id'));
|
||||
this.currentDivs[i].remove();
|
||||
}
|
||||
|
||||
// remove spliced-out line divs from currentDivs array
|
||||
this.currentDivs.splice(start, numRemoved);
|
||||
|
||||
var newDivs = [];
|
||||
for (var i = 0; i < newLines.length; i++)
|
||||
{
|
||||
newDivs.push(this.lineToElement(newLines[i], this.alines[start + i]));
|
||||
}
|
||||
|
||||
// grab the div just before the first one
|
||||
var startDiv = this.currentDivs[start - 1] || null;
|
||||
|
||||
// insert the div elements into the correct place, in the correct order
|
||||
for (var i = 0; i < newDivs.length; i++)
|
||||
{
|
||||
if (startDiv)
|
||||
{
|
||||
startDiv.after(newDivs[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#padcontent").prepend(newDivs[i]);
|
||||
}
|
||||
startDiv = newDivs[i];
|
||||
}
|
||||
|
||||
// insert new divs into currentDivs array
|
||||
newDivs.unshift(0); // remove 0 elements
|
||||
newDivs.unshift(start);
|
||||
this.currentDivs.splice.apply(this.currentDivs, newDivs);
|
||||
return this;
|
||||
},
|
||||
|
||||
// splice the lines
|
||||
splice: function (start, numRemoved, newLinesVA)
|
||||
{
|
||||
var newLines = Array.prototype.slice.call(arguments, 2).map(
|
||||
|
||||
function (s)
|
||||
{
|
||||
return s;
|
||||
});
|
||||
|
||||
// apply this splice to the divs
|
||||
this.applySpliceToDivs(start, numRemoved, newLines);
|
||||
|
||||
// call currentLines.splice, to keep the currentLines array up to date
|
||||
newLines.unshift(numRemoved);
|
||||
newLines.unshift(start);
|
||||
this.currentLines.splice.apply(this.currentLines, arguments);
|
||||
},
|
||||
// returns the contents of the specified line I
|
||||
get: function (i)
|
||||
{
|
||||
return this.currentLines[i];
|
||||
},
|
||||
// returns the number of lines in the document
|
||||
length: function ()
|
||||
{
|
||||
return this.currentLines.length;
|
||||
},
|
||||
|
||||
getActiveAuthors: function ()
|
||||
{
|
||||
var self = this;
|
||||
var authors = [];
|
||||
var seenNums = {};
|
||||
var alines = self.alines;
|
||||
for (var i = 0; i < alines.length; i++)
|
||||
{
|
||||
Changeset.eachAttribNumber(alines[i], function (n)
|
||||
{
|
||||
if (!seenNums[n])
|
||||
{
|
||||
seenNums[n] = true;
|
||||
if (self.apool.getAttribKey(n) == 'author')
|
||||
{
|
||||
var a = self.apool.getAttribValue(n);
|
||||
if (a)
|
||||
{
|
||||
authors.push(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
authors.sort();
|
||||
return authors;
|
||||
}
|
||||
};
|
||||
|
||||
function callCatchingErrors(catcher, func)
|
||||
{
|
||||
try
|
||||
{
|
||||
wrapRecordingErrors(catcher, func)();
|
||||
}
|
||||
catch (e)
|
||||
{ /*absorb*/
|
||||
}
|
||||
}
|
||||
|
||||
function wrapRecordingErrors(catcher, func)
|
||||
{
|
||||
return function ()
|
||||
{
|
||||
try
|
||||
{
|
||||
return func.apply(this, Array.prototype.slice.call(arguments));
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// caughtErrors.push(e);
|
||||
// caughtErrorCatchers.push(catcher);
|
||||
// caughtErrorTimes.push(+new Date());
|
||||
// console.dir({catcher: catcher, e: e});
|
||||
debugLog(e); // TODO(kroo): added temporary, to catch errors
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function loadedNewChangeset(changesetForward, changesetBackward, revision, timeDelta)
|
||||
{
|
||||
var broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest);
|
||||
debugLog("broadcasting:", broadcasting, BroadcastSlider.getSliderPosition(), revisionInfo.latest, revision);
|
||||
revisionInfo.addChangeset(revision, revision + 1, changesetForward, changesetBackward, timeDelta);
|
||||
BroadcastSlider.setSliderLength(revisionInfo.latest);
|
||||
if (broadcasting) applyChangeset(changesetForward, revision + 1, false, timeDelta);
|
||||
}
|
||||
|
||||
/*
|
||||
At this point, we must be certain that the changeset really does map from
|
||||
the current revision to the specified revision. Any mistakes here will
|
||||
cause the whole slider to get out of sync.
|
||||
*/
|
||||
|
||||
function applyChangeset(changeset, revision, preventSliderMovement, timeDelta)
|
||||
{
|
||||
// disable the next 'gotorevision' call handled by a timeslider update
|
||||
if (!preventSliderMovement)
|
||||
{
|
||||
goToRevisionIfEnabledCount++;
|
||||
BroadcastSlider.setSliderPosition(revision);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// must mutate attribution lines before text lines
|
||||
Changeset.mutateAttributionLines(changeset, padContents.alines, padContents.apool);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
debugLog(e);
|
||||
}
|
||||
|
||||
Changeset.mutateTextLines(changeset, padContents);
|
||||
padContents.currentRevision = revision;
|
||||
padContents.currentTime += timeDelta * 1000;
|
||||
debugLog('Time Delta: ', timeDelta)
|
||||
updateTimer();
|
||||
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function (name)
|
||||
{
|
||||
return authorData[name];
|
||||
}));
|
||||
}
|
||||
|
||||
function updateTimer()
|
||||
{
|
||||
var zpad = function (str, length)
|
||||
{
|
||||
str = str + "";
|
||||
while (str.length < length)
|
||||
str = '0' + str;
|
||||
return str;
|
||||
}
|
||||
|
||||
var date = new Date(padContents.currentTime);
|
||||
var dateFormat = function ()
|
||||
{
|
||||
var month = zpad(date.getMonth() + 1, 2);
|
||||
var day = zpad(date.getDate(), 2);
|
||||
var year = (date.getFullYear());
|
||||
var hours = zpad(date.getHours(), 2);
|
||||
var minutes = zpad(date.getMinutes(), 2);
|
||||
var seconds = zpad(date.getSeconds(), 2);
|
||||
return ([month, '/', day, '/', year, ' ', hours, ':', minutes, ':', seconds].join(""));
|
||||
}
|
||||
|
||||
|
||||
|
||||
$('#timer').html(dateFormat());
|
||||
|
||||
var revisionDate = ["Saved", ["Jan", "Feb", "March", "April", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec"][date.getMonth()], date.getDate() + ",", date.getFullYear()].join(" ")
|
||||
$('#revision_date').html(revisionDate)
|
||||
|
||||
}
|
||||
|
||||
function goToRevision(newRevision)
|
||||
{
|
||||
padContents.targetRevision = newRevision;
|
||||
var self = this;
|
||||
var path = revisionInfo.getPath(padContents.currentRevision, newRevision);
|
||||
debugLog('newRev: ', padContents.currentRevision, path);
|
||||
if (path.status == 'complete')
|
||||
{
|
||||
var cs = path.changesets;
|
||||
debugLog("status: complete, changesets: ", cs, "path:", path);
|
||||
var changeset = cs[0];
|
||||
var timeDelta = path.times[0];
|
||||
for (var i = 1; i < cs.length; i++)
|
||||
{
|
||||
changeset = Changeset.compose(changeset, cs[i], padContents.apool);
|
||||
timeDelta += path.times[i];
|
||||
}
|
||||
if (changeset) applyChangeset(changeset, path.rev, true, timeDelta);
|
||||
}
|
||||
else if (path.status == "partial")
|
||||
{
|
||||
debugLog('partial');
|
||||
var sliderLocation = padContents.currentRevision;
|
||||
// callback is called after changeset information is pulled from server
|
||||
// this may never get called, if the changeset has already been loaded
|
||||
var update = function (start, end)
|
||||
{
|
||||
// if we've called goToRevision in the time since, don't goToRevision
|
||||
goToRevision(padContents.targetRevision);
|
||||
};
|
||||
|
||||
// do our best with what we have...
|
||||
var cs = path.changesets;
|
||||
|
||||
var changeset = cs[0];
|
||||
var timeDelta = path.times[0];
|
||||
for (var i = 1; i < cs.length; i++)
|
||||
{
|
||||
changeset = Changeset.compose(changeset, cs[i], padContents.apool);
|
||||
timeDelta += path.times[i];
|
||||
}
|
||||
if (changeset) applyChangeset(changeset, path.rev, true, timeDelta);
|
||||
|
||||
|
||||
if (BroadcastSlider.getSliderLength() > 10000)
|
||||
{
|
||||
var start = (Math.floor((newRevision) / 10000) * 10000); // revision 0 to 10
|
||||
changesetLoader.queueUp(start, 100);
|
||||
}
|
||||
|
||||
if (BroadcastSlider.getSliderLength() > 1000)
|
||||
{
|
||||
var start = (Math.floor((newRevision) / 1000) * 1000); // (start from -1, go to 19) + 1
|
||||
changesetLoader.queueUp(start, 10);
|
||||
}
|
||||
|
||||
start = (Math.floor((newRevision) / 100) * 100);
|
||||
|
||||
changesetLoader.queueUp(start, 1, update);
|
||||
}
|
||||
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function (name)
|
||||
{
|
||||
return authorData[name];
|
||||
}));
|
||||
}
|
||||
|
||||
var changesetLoader = {
|
||||
running: false,
|
||||
resolved: [],
|
||||
requestQueue1: [],
|
||||
requestQueue2: [],
|
||||
requestQueue3: [],
|
||||
queueUp: function (revision, width, callback)
|
||||
{
|
||||
if (revision < 0) revision = 0;
|
||||
// if(changesetLoader.requestQueue.indexOf(revision) != -1)
|
||||
// return; // already in the queue.
|
||||
if (changesetLoader.resolved.indexOf(revision + "_" + width) != -1) return; // already loaded from the server
|
||||
changesetLoader.resolved.push(revision + "_" + width);
|
||||
|
||||
var requestQueue = width == 1 ? changesetLoader.requestQueue3 : width == 10 ? changesetLoader.requestQueue2 : changesetLoader.requestQueue1;
|
||||
requestQueue.push(
|
||||
{
|
||||
'rev': revision,
|
||||
'res': width,
|
||||
'callback': callback
|
||||
});
|
||||
if (!changesetLoader.running)
|
||||
{
|
||||
changesetLoader.running = true;
|
||||
setTimeout(changesetLoader.loadFromQueue, 10);
|
||||
}
|
||||
},
|
||||
loadFromQueue: function ()
|
||||
{
|
||||
var self = changesetLoader;
|
||||
var requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null;
|
||||
|
||||
if (!requestQueue)
|
||||
{
|
||||
self.running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var request = requestQueue.pop();
|
||||
var granularity = request.res;
|
||||
var callback = request.callback;
|
||||
var start = request.rev;
|
||||
debugLog("loadinging revision", start, "through ajax");
|
||||
$.getJSON("/ep/pad/changes/" + clientVars.padIdForUrl + "?s=" + start + "&g=" + granularity, function (data, textStatus)
|
||||
{
|
||||
if (textStatus !== "success")
|
||||
{
|
||||
console.log(textStatus);
|
||||
BroadcastSlider.showReconnectUI();
|
||||
}
|
||||
self.handleResponse(data, start, granularity, callback);
|
||||
|
||||
setTimeout(self.loadFromQueue, 10); // load the next ajax function
|
||||
});
|
||||
},
|
||||
handleResponse: function (data, start, granularity, callback)
|
||||
{
|
||||
debugLog("response: ", data);
|
||||
var pool = (new AttribPool()).fromJsonable(data.apool);
|
||||
for (var i = 0; i < data.forwardsChangesets.length; i++)
|
||||
{
|
||||
var astart = start + i * granularity - 1; // rev -1 is a blank single line
|
||||
var aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision
|
||||
if (aend > data.actualEndNum - 1) aend = data.actualEndNum - 1;
|
||||
debugLog("adding changeset:", astart, aend);
|
||||
var forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
|
||||
var backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
|
||||
revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]);
|
||||
}
|
||||
if (callback) callback(start - 1, start + data.forwardsChangesets.length * granularity - 1);
|
||||
}
|
||||
};
|
||||
|
||||
function handleMessageFromServer()
|
||||
{
|
||||
debugLog("handleMessage:", arguments);
|
||||
var obj = arguments[0]['data'];
|
||||
var expectedType = "COLLABROOM";
|
||||
|
||||
obj = JSON.parse(obj);
|
||||
if (obj['type'] == expectedType)
|
||||
{
|
||||
obj = obj['data'];
|
||||
|
||||
if (obj['type'] == "NEW_CHANGES")
|
||||
{
|
||||
debugLog(obj);
|
||||
var changeset = Changeset.moveOpsToNewPool(
|
||||
obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
|
||||
|
||||
var changesetBack = Changeset.moveOpsToNewPool(
|
||||
obj.changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
|
||||
|
||||
loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta);
|
||||
}
|
||||
else if (obj['type'] == "NEW_AUTHORDATA")
|
||||
{
|
||||
var authorMap = {};
|
||||
authorMap[obj.author] = obj.data;
|
||||
receiveAuthorData(authorMap);
|
||||
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function (name)
|
||||
{
|
||||
return authorData[name];
|
||||
}));
|
||||
}
|
||||
else if (obj['type'] == "NEW_SAVEDREV")
|
||||
{
|
||||
var savedRev = obj.savedRev;
|
||||
BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
debugLog("incorrect message type: " + obj['type'] + ", expected " + expectedType);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSocketClosed(params)
|
||||
{
|
||||
debugLog("socket closed!", params);
|
||||
socket = null;
|
||||
|
||||
BroadcastSlider.showReconnectUI();
|
||||
// var reason = appLevelDisconnectReason || params.reason;
|
||||
// var shouldReconnect = params.reconnect;
|
||||
// if (shouldReconnect) {
|
||||
// // determine if this is a tight reconnect loop due to weird connectivity problems
|
||||
// // reconnectTimes.push(+new Date());
|
||||
// var TOO_MANY_RECONNECTS = 8;
|
||||
// var TOO_SHORT_A_TIME_MS = 10000;
|
||||
// if (reconnectTimes.length >= TOO_MANY_RECONNECTS &&
|
||||
// ((+new Date()) - reconnectTimes[reconnectTimes.length-TOO_MANY_RECONNECTS]) <
|
||||
// TOO_SHORT_A_TIME_MS) {
|
||||
// setChannelState("DISCONNECTED", "looping");
|
||||
// }
|
||||
// else {
|
||||
// setChannelState("RECONNECTING", reason);
|
||||
// setUpSocket();
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// BroadcastSlider.showReconnectUI();
|
||||
// setChannelState("DISCONNECTED", reason);
|
||||
// }
|
||||
}
|
||||
|
||||
function sendMessage(msg)
|
||||
{
|
||||
socket.postMessage(JSON.stringify(
|
||||
{
|
||||
type: "COLLABROOM",
|
||||
data: msg
|
||||
}));
|
||||
}
|
||||
|
||||
function setUpSocket()
|
||||
{
|
||||
// required for Comet
|
||||
if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0)))
|
||||
{
|
||||
document.domain = document.domain; // for comet
|
||||
}
|
||||
|
||||
var success = false;
|
||||
callCatchingErrors("setUpSocket", function ()
|
||||
{
|
||||
appLevelDisconnectReason = null;
|
||||
|
||||
socketId = String(Math.floor(Math.random() * 1e12));
|
||||
socket = new WebSocket(socketId);
|
||||
socket.onmessage = wrapRecordingErrors("socket.onmessage", handleMessageFromServer);
|
||||
socket.onclosed = wrapRecordingErrors("socket.onclosed", handleSocketClosed);
|
||||
socket.onopen = wrapRecordingErrors("socket.onopen", function ()
|
||||
{
|
||||
setChannelState("CONNECTED");
|
||||
var msg = {
|
||||
type: "CLIENT_READY",
|
||||
roomType: 'padview',
|
||||
roomName: 'padview/' + clientVars.viewId,
|
||||
data: {
|
||||
lastRev: clientVars.revNum,
|
||||
userInfo: {
|
||||
userId: userId
|
||||
}
|
||||
}
|
||||
};
|
||||
sendMessage(msg);
|
||||
});
|
||||
// socket.onhiccup = wrapRecordingErrors("socket.onhiccup", handleCometHiccup);
|
||||
// socket.onlogmessage = function(x) {debugLog(x); };
|
||||
socket.connect();
|
||||
success = true;
|
||||
});
|
||||
if (success)
|
||||
{
|
||||
//initialStartConnectTime = +new Date();
|
||||
}
|
||||
else
|
||||
{
|
||||
abandonConnection("initsocketfail");
|
||||
}
|
||||
}
|
||||
|
||||
function setChannelState(newChannelState, moreInfo)
|
||||
{
|
||||
if (newChannelState != channelState)
|
||||
{
|
||||
channelState = newChannelState;
|
||||
// callbacks.onChannelStateChange(channelState, moreInfo);
|
||||
}
|
||||
}
|
||||
|
||||
function abandonConnection(reason)
|
||||
{
|
||||
if (socket)
|
||||
{
|
||||
socket.onclosed = function ()
|
||||
{};
|
||||
socket.onhiccup = function ()
|
||||
{};
|
||||
socket.disconnect();
|
||||
}
|
||||
socket = null;
|
||||
setChannelState("DISCONNECTED", reason);
|
||||
}
|
||||
|
||||
/*window['onloadFuncts'] = [];
|
||||
window.onload = function ()
|
||||
{
|
||||
window['isloaded'] = true;
|
||||
window['onloadFuncts'].forEach(function (funct)
|
||||
{
|
||||
funct();
|
||||
});
|
||||
};*/
|
||||
|
||||
// to start upon window load, just push a function onto this array
|
||||
//window['onloadFuncts'].push(setUpSocket);
|
||||
//window['onloadFuncts'].push(function ()
|
||||
fireWhenAllScriptsAreLoaded.push(function ()
|
||||
{
|
||||
// set up the currentDivs and DOM
|
||||
padContents.currentDivs = [];
|
||||
$("#padcontent").html("");
|
||||
for (var i = 0; i < padContents.currentLines.length; i++)
|
||||
{
|
||||
var div = padContents.lineToElement(padContents.currentLines[i], padContents.alines[i]);
|
||||
padContents.currentDivs.push(div);
|
||||
$("#padcontent").append(div);
|
||||
}
|
||||
debugLog(padContents.currentDivs);
|
||||
});
|
||||
|
||||
// this is necessary to keep infinite loops of events firing,
|
||||
// since goToRevision changes the slider position
|
||||
var goToRevisionIfEnabledCount = 0;
|
||||
var goToRevisionIfEnabled = function ()
|
||||
{
|
||||
if (goToRevisionIfEnabledCount > 0)
|
||||
{
|
||||
goToRevisionIfEnabledCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
goToRevision.apply(goToRevision, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
BroadcastSlider.onSlider(goToRevisionIfEnabled);
|
||||
|
||||
(function ()
|
||||
{
|
||||
for (var i = 0; i < clientVars.initialChangesets.length; i++)
|
||||
{
|
||||
var csgroup = clientVars.initialChangesets[i];
|
||||
var start = clientVars.initialChangesets[i].start;
|
||||
var granularity = clientVars.initialChangesets[i].granularity;
|
||||
debugLog("loading changest on startup: ", start, granularity, csgroup);
|
||||
changesetLoader.handleResponse(csgroup, start, granularity, null);
|
||||
}
|
||||
})();
|
||||
|
||||
var dynamicCSS = makeCSSManager('dynamicsyntax');
|
||||
var authorData = {};
|
||||
|
||||
function receiveAuthorData(newAuthorData)
|
||||
{
|
||||
for (var author in newAuthorData)
|
||||
{
|
||||
var data = newAuthorData[author];
|
||||
if ((typeof data.colorId) == 'number')
|
||||
{
|
||||
var bgcolor = clientVars.colorPalette[data.colorId];
|
||||
if (bgcolor && dynamicCSS)
|
||||
{
|
||||
dynamicCSS.selectorStyle('.' + linestylefilter.getAuthorClassName(author)).backgroundColor = bgcolor;
|
||||
}
|
||||
}
|
||||
authorData[author] = data;
|
||||
}
|
||||
}
|
||||
|
||||
receiveAuthorData(clientVars.historicalAuthorData);
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// revision info is a skip list whos entries represent a particular revision
|
||||
// of the document. These revisions are connected together by various
|
||||
// changesets, or deltas, between any two revisions.
|
||||
|
||||
var global = this;
|
||||
|
||||
function loadBroadcastRevisionsJS()
|
||||
{
|
||||
function Revision(revNum)
|
||||
{
|
||||
this.rev = revNum;
|
||||
this.changesets = [];
|
||||
}
|
||||
|
||||
Revision.prototype.addChangeset = function (destIndex, changeset, timeDelta)
|
||||
{
|
||||
var changesetWrapper = {
|
||||
deltaRev: destIndex - this.rev,
|
||||
deltaTime: timeDelta,
|
||||
getValue: function ()
|
||||
{
|
||||
return changeset;
|
||||
}
|
||||
};
|
||||
this.changesets.push(changesetWrapper);
|
||||
this.changesets.sort(function (a, b)
|
||||
{
|
||||
return (b.deltaRev - a.deltaRev)
|
||||
});
|
||||
}
|
||||
|
||||
revisionInfo = {};
|
||||
revisionInfo.addChangeset = function (fromIndex, toIndex, changeset, backChangeset, timeDelta)
|
||||
{
|
||||
var startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
|
||||
var endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex);
|
||||
startRevision.addChangeset(toIndex, changeset, timeDelta);
|
||||
endRevision.addChangeset(fromIndex, backChangeset, -1 * timeDelta);
|
||||
}
|
||||
|
||||
revisionInfo.latest = clientVars.totalRevs || -1;
|
||||
|
||||
revisionInfo.createNew = function (index)
|
||||
{
|
||||
revisionInfo[index] = new Revision(index);
|
||||
if (index > revisionInfo.latest)
|
||||
{
|
||||
revisionInfo.latest = index;
|
||||
}
|
||||
|
||||
return revisionInfo[index];
|
||||
}
|
||||
|
||||
// assuming that there is a path from fromIndex to toIndex, and that the links
|
||||
// are laid out in a skip-list format
|
||||
revisionInfo.getPath = function (fromIndex, toIndex)
|
||||
{
|
||||
var changesets = [];
|
||||
var spans = [];
|
||||
var times = [];
|
||||
var elem = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
|
||||
if (elem.changesets.length != 0 && fromIndex != toIndex)
|
||||
{
|
||||
var reverse = !(fromIndex < toIndex)
|
||||
while (((elem.rev < toIndex) && !reverse) || ((elem.rev > toIndex) && reverse))
|
||||
{
|
||||
var couldNotContinue = false;
|
||||
var oldRev = elem.rev;
|
||||
|
||||
for (var i = reverse ? elem.changesets.length - 1 : 0;
|
||||
reverse ? i >= 0 : i < elem.changesets.length;
|
||||
i += reverse ? -1 : 1)
|
||||
{
|
||||
if (((elem.changesets[i].deltaRev < 0) && !reverse) || ((elem.changesets[i].deltaRev > 0) && reverse))
|
||||
{
|
||||
couldNotContinue = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (((elem.rev + elem.changesets[i].deltaRev <= toIndex) && !reverse) || ((elem.rev + elem.changesets[i].deltaRev >= toIndex) && reverse))
|
||||
{
|
||||
var topush = elem.changesets[i];
|
||||
changesets.push(topush.getValue());
|
||||
spans.push(elem.changesets[i].deltaRev);
|
||||
times.push(topush.deltaTime);
|
||||
elem = revisionInfo[elem.rev + elem.changesets[i].deltaRev];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (couldNotContinue || oldRev == elem.rev) break;
|
||||
}
|
||||
}
|
||||
|
||||
var status = 'partial';
|
||||
if (elem.rev == toIndex) status = 'complete';
|
||||
|
||||
return {
|
||||
'fromRev': fromIndex,
|
||||
'rev': elem.rev,
|
||||
'status': status,
|
||||
'changesets': changesets,
|
||||
'spans': spans,
|
||||
'times': times
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// revisionInfo.addChangeset(0, 5, "abcde")
|
||||
// revisionInfo.addChangeset(5, 10, "fghij")
|
||||
// revisionInfo.addChangeset(10, 11, "k")
|
||||
// revisionInfo.addChangeset(11, 12, "l")
|
||||
// revisionInfo.addChangeset(12, 13, "m")
|
||||
// revisionInfo.addChangeset(13, 14, "n")
|
||||
// revisionInfo.addChangeset(14, 15, "o")
|
||||
// revisionInfo.addChangeset(15, 20, "pqrst")
|
||||
//
|
||||
// print (revisionInfo.getPath(15, 0))
|
|
@ -0,0 +1,489 @@
|
|||
/**
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
var global = this;
|
||||
|
||||
function loadBroadcastSliderJS()
|
||||
{
|
||||
|
||||
(function ()
|
||||
{ // wrap this code in its own namespace
|
||||
var sliderLength = 1000;
|
||||
var sliderPos = 0;
|
||||
var sliderActive = false;
|
||||
var slidercallbacks = [];
|
||||
var savedRevisions = [];
|
||||
var sliderPlaying = false;
|
||||
|
||||
function disableSelection(element)
|
||||
{
|
||||
element.onselectstart = function ()
|
||||
{
|
||||
return false;
|
||||
};
|
||||
element.unselectable = "on";
|
||||
element.style.MozUserSelect = "none";
|
||||
element.style.cursor = "default";
|
||||
}
|
||||
var _callSliderCallbacks = function (newval)
|
||||
{
|
||||
sliderPos = newval;
|
||||
for (var i = 0; i < slidercallbacks.length; i++)
|
||||
{
|
||||
slidercallbacks[i](newval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var updateSliderElements = function ()
|
||||
{
|
||||
for (var i = 0; i < savedRevisions.length; i++)
|
||||
{
|
||||
var position = parseInt(savedRevisions[i].attr('pos'));
|
||||
savedRevisions[i].css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1);
|
||||
}
|
||||
$("#ui-slider-handle").css('left', sliderPos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
var addSavedRevision = function (position, info)
|
||||
{
|
||||
var newSavedRevision = $('<div></div>');
|
||||
newSavedRevision.addClass("star");
|
||||
|
||||
newSavedRevision.attr('pos', position);
|
||||
newSavedRevision.css('position', 'absolute');
|
||||
newSavedRevision.css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1);
|
||||
$("#timeslider-slider").append(newSavedRevision);
|
||||
newSavedRevision.mouseup(function (evt)
|
||||
{
|
||||
BroadcastSlider.setSliderPosition(position);
|
||||
});
|
||||
savedRevisions.push(newSavedRevision);
|
||||
};
|
||||
|
||||
var removeSavedRevision = function (position)
|
||||
{
|
||||
var element = $("div.star [pos=" + position + "]");
|
||||
savedRevisions.remove(element);
|
||||
element.remove();
|
||||
return element;
|
||||
};
|
||||
|
||||
/* Begin small 'API' */
|
||||
|
||||
function onSlider(callback)
|
||||
{
|
||||
slidercallbacks.push(callback);
|
||||
}
|
||||
|
||||
function getSliderPosition()
|
||||
{
|
||||
return sliderPos;
|
||||
}
|
||||
|
||||
function setSliderPosition(newpos)
|
||||
{
|
||||
newpos = Number(newpos);
|
||||
if (newpos < 0 || newpos > sliderLength) return;
|
||||
$("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0));
|
||||
$("a.tlink").map(function ()
|
||||
{
|
||||
$(this).attr('href', $(this).attr('thref').replace("%revision%", newpos));
|
||||
});
|
||||
$("#revision_label").html("Version " + newpos);
|
||||
|
||||
if (newpos == 0)
|
||||
{
|
||||
$("#leftstar").css('opacity', .5);
|
||||
$("#leftstep").css('opacity', .5);
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#leftstar").css('opacity', 1);
|
||||
$("#leftstep").css('opacity', 1);
|
||||
}
|
||||
|
||||
if (newpos == sliderLength)
|
||||
{
|
||||
$("#rightstar").css('opacity', .5);
|
||||
$("#rightstep").css('opacity', .5);
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#rightstar").css('opacity', 1);
|
||||
$("#rightstep").css('opacity', 1);
|
||||
}
|
||||
|
||||
sliderPos = newpos;
|
||||
_callSliderCallbacks(newpos);
|
||||
}
|
||||
|
||||
function getSliderLength()
|
||||
{
|
||||
return sliderLength;
|
||||
}
|
||||
|
||||
function setSliderLength(newlength)
|
||||
{
|
||||
sliderLength = newlength;
|
||||
updateSliderElements();
|
||||
}
|
||||
|
||||
// just take over the whole slider screen with a reconnect message
|
||||
|
||||
|
||||
function showReconnectUI()
|
||||
{
|
||||
if (!clientVars.sliderEnabled || !clientVars.supportsSlider)
|
||||
{
|
||||
$("#padmain, #rightbars").css('top', "130px");
|
||||
$("#timeslider").show();
|
||||
}
|
||||
$('#error').show();
|
||||
}
|
||||
|
||||
function setAuthors(authors)
|
||||
{
|
||||
$("#authorstable").empty();
|
||||
var numAnonymous = 0;
|
||||
var numNamed = 0;
|
||||
authors.forEach(function (author)
|
||||
{
|
||||
if (author.name)
|
||||
{
|
||||
numNamed++;
|
||||
var tr = $('<tr></tr>');
|
||||
var swatchtd = $('<td></td>');
|
||||
var swatch = $('<div class="swatch"></div>');
|
||||
swatch.css('background-color', clientVars.colorPalette[author.colorId]);
|
||||
swatchtd.append(swatch);
|
||||
tr.append(swatchtd);
|
||||
var nametd = $('<td></td>');
|
||||
nametd.text(author.name || "unnamed");
|
||||
tr.append(nametd);
|
||||
$("#authorstable").append(tr);
|
||||
}
|
||||
else
|
||||
{
|
||||
numAnonymous++;
|
||||
}
|
||||
});
|
||||
if (numAnonymous > 0)
|
||||
{
|
||||
var html = "<tr><td colspan=\"2\" style=\"color:#999; padding-left: 10px\">" + (numNamed > 0 ? "...and " : "") + numAnonymous + " unnamed author" + (numAnonymous > 1 ? "s" : "") + "</td></tr>";
|
||||
$("#authorstable").append($(html));
|
||||
}
|
||||
if (authors.length == 0)
|
||||
{
|
||||
$("#authorstable").append($("<tr><td colspan=\"2\" style=\"color:#999; padding-left: 10px\">No Authors</td></tr>"))
|
||||
}
|
||||
}
|
||||
|
||||
global.BroadcastSlider = {
|
||||
onSlider: onSlider,
|
||||
getSliderPosition: getSliderPosition,
|
||||
setSliderPosition: setSliderPosition,
|
||||
getSliderLength: getSliderLength,
|
||||
setSliderLength: setSliderLength,
|
||||
isSliderActive: function ()
|
||||
{
|
||||
return sliderActive;
|
||||
},
|
||||
playpause: playpause,
|
||||
addSavedRevision: addSavedRevision,
|
||||
showReconnectUI: showReconnectUI,
|
||||
setAuthors: setAuthors
|
||||
}
|
||||
|
||||
function playButtonUpdater()
|
||||
{
|
||||
if (sliderPlaying)
|
||||
{
|
||||
if (getSliderPosition() + 1 > sliderLength)
|
||||
{
|
||||
$("#playpause_button_icon").toggleClass('pause');
|
||||
sliderPlaying = false;
|
||||
return;
|
||||
}
|
||||
setSliderPosition(getSliderPosition() + 1);
|
||||
|
||||
setTimeout(playButtonUpdater, 100);
|
||||
}
|
||||
}
|
||||
|
||||
function playpause()
|
||||
{
|
||||
$("#playpause_button_icon").toggleClass('pause');
|
||||
|
||||
if (!sliderPlaying)
|
||||
{
|
||||
if (getSliderPosition() == sliderLength) setSliderPosition(0);
|
||||
sliderPlaying = true;
|
||||
playButtonUpdater();
|
||||
}
|
||||
else
|
||||
{
|
||||
sliderPlaying = false;
|
||||
}
|
||||
}
|
||||
|
||||
// assign event handlers to html UI elements after page load
|
||||
//$(window).load(function ()
|
||||
fireWhenAllScriptsAreLoaded.push(function ()
|
||||
{
|
||||
disableSelection($("#playpause_button")[0]);
|
||||
disableSelection($("#timeslider")[0]);
|
||||
|
||||
if (clientVars.sliderEnabled && clientVars.supportsSlider)
|
||||
{
|
||||
$(document).keyup(function (e)
|
||||
{
|
||||
var code = -1;
|
||||
if (!e) var e = window.event;
|
||||
if (e.keyCode) code = e.keyCode;
|
||||
else if (e.which) code = e.which;
|
||||
|
||||
if (code == 37)
|
||||
{ // left
|
||||
if (!e.shiftKey)
|
||||
{
|
||||
setSliderPosition(getSliderPosition() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
var nextStar = 0; // default to first revision in document
|
||||
for (var i = 0; i < savedRevisions.length; i++)
|
||||
{
|
||||
var pos = parseInt(savedRevisions[i].attr('pos'));
|
||||
if (pos < getSliderPosition() && nextStar < pos) nextStar = pos;
|
||||
}
|
||||
setSliderPosition(nextStar);
|
||||
}
|
||||
}
|
||||
else if (code == 39)
|
||||
{
|
||||
if (!e.shiftKey)
|
||||
{
|
||||
setSliderPosition(getSliderPosition() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
var nextStar = sliderLength; // default to last revision in document
|
||||
for (var i = 0; i < savedRevisions.length; i++)
|
||||
{
|
||||
var pos = parseInt(savedRevisions[i].attr('pos'));
|
||||
if (pos > getSliderPosition() && nextStar > pos) nextStar = pos;
|
||||
}
|
||||
setSliderPosition(nextStar);
|
||||
}
|
||||
}
|
||||
else if (code == 32) playpause();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
$(window).resize(function ()
|
||||
{
|
||||
updateSliderElements();
|
||||
});
|
||||
|
||||
$("#ui-slider-bar").mousedown(function (evt)
|
||||
{
|
||||
setSliderPosition(Math.floor((evt.clientX - $("#ui-slider-bar").offset().left) * sliderLength / 742));
|
||||
$("#ui-slider-handle").css('left', (evt.clientX - $("#ui-slider-bar").offset().left));
|
||||
$("#ui-slider-handle").trigger(evt);
|
||||
});
|
||||
|
||||
// Slider dragging
|
||||
$("#ui-slider-handle").mousedown(function (evt)
|
||||
{
|
||||
this.startLoc = evt.clientX;
|
||||
this.currentLoc = parseInt($(this).css('left'));
|
||||
var self = this;
|
||||
sliderActive = true;
|
||||
$(document).mousemove(function (evt2)
|
||||
{
|
||||
$(self).css('pointer', 'move')
|
||||
var newloc = self.currentLoc + (evt2.clientX - self.startLoc);
|
||||
if (newloc < 0) newloc = 0;
|
||||
if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2);
|
||||
$("#revision_label").html("Version " + Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2)));
|
||||
$(self).css('left', newloc);
|
||||
if (getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))) _callSliderCallbacks(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2)))
|
||||
});
|
||||
$(document).mouseup(function (evt2)
|
||||
{
|
||||
$(document).unbind('mousemove');
|
||||
$(document).unbind('mouseup');
|
||||
sliderActive = false;
|
||||
var newloc = self.currentLoc + (evt2.clientX - self.startLoc);
|
||||
if (newloc < 0) newloc = 0;
|
||||
if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2);
|
||||
$(self).css('left', newloc);
|
||||
// if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2)))
|
||||
setSliderPosition(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2)))
|
||||
self.currentLoc = parseInt($(self).css('left'));
|
||||
});
|
||||
})
|
||||
|
||||
// play/pause toggling
|
||||
$("#playpause_button").mousedown(function (evt)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
$(self).css('background-image', 'url(/static/img/pad/timeslider/crushed_button_depressed.png)');
|
||||
$(self).mouseup(function (evt2)
|
||||
{
|
||||
$(self).css('background-image', 'url(/static/img/pad/timeslider/crushed_button_undepressed.png)');
|
||||
$(self).unbind('mouseup');
|
||||
BroadcastSlider.playpause();
|
||||
});
|
||||
$(document).mouseup(function (evt2)
|
||||
{
|
||||
$(self).css('background-image', 'url(/static/img/pad/timeslider/crushed_button_undepressed.png)');
|
||||
$(document).unbind('mouseup');
|
||||
});
|
||||
});
|
||||
|
||||
// next/prev saved revision and changeset
|
||||
$('.stepper').mousedown(function (evt)
|
||||
{
|
||||
var self = this;
|
||||
var origcss = $(self).css('background-position');
|
||||
if (!origcss)
|
||||
{
|
||||
origcss = $(self).css('background-position-x') + " " + $(self).css('background-position-y');
|
||||
}
|
||||
var origpos = parseInt(origcss.split(" ")[1]);
|
||||
var newpos = (origpos - 43);
|
||||
if (newpos < 0) newpos += 87;
|
||||
|
||||
var newcss = (origcss.split(" ")[0] + " " + newpos + "px");
|
||||
if ($(self).css('opacity') != 1.0) newcss = origcss;
|
||||
|
||||
$(self).css('background-position', newcss)
|
||||
|
||||
$(self).mouseup(function (evt2)
|
||||
{
|
||||
$(self).css('background-position', origcss);
|
||||
$(self).unbind('mouseup');
|
||||
$(document).unbind('mouseup');
|
||||
if ($(self).attr("id") == ("leftstep"))
|
||||
{
|
||||
setSliderPosition(getSliderPosition() - 1);
|
||||
}
|
||||
else if ($(self).attr("id") == ("rightstep"))
|
||||
{
|
||||
setSliderPosition(getSliderPosition() + 1);
|
||||
}
|
||||
else if ($(self).attr("id") == ("leftstar"))
|
||||
{
|
||||
var nextStar = 0; // default to first revision in document
|
||||
for (var i = 0; i < savedRevisions.length; i++)
|
||||
{
|
||||
var pos = parseInt(savedRevisions[i].attr('pos'));
|
||||
if (pos < getSliderPosition() && nextStar < pos) nextStar = pos;
|
||||
}
|
||||
setSliderPosition(nextStar);
|
||||
}
|
||||
else if ($(self).attr("id") == ("rightstar"))
|
||||
{
|
||||
var nextStar = sliderLength; // default to last revision in document
|
||||
for (var i = 0; i < savedRevisions.length; i++)
|
||||
{
|
||||
var pos = parseInt(savedRevisions[i].attr('pos'));
|
||||
if (pos > getSliderPosition() && nextStar > pos) nextStar = pos;
|
||||
}
|
||||
setSliderPosition(nextStar);
|
||||
}
|
||||
});
|
||||
$(document).mouseup(function (evt2)
|
||||
{
|
||||
$(self).css('background-position', origcss);
|
||||
$(self).unbind('mouseup');
|
||||
$(document).unbind('mouseup');
|
||||
});
|
||||
})
|
||||
|
||||
if (clientVars)
|
||||
{
|
||||
if (clientVars.fullWidth)
|
||||
{
|
||||
$("#padpage").css('width', '100%');
|
||||
$("#revision").css('position', "absolute")
|
||||
$("#revision").css('right', "20px")
|
||||
$("#revision").css('top', "20px")
|
||||
$("#padmain").css('left', '0px');
|
||||
$("#padmain").css('right', '197px');
|
||||
$("#padmain").css('width', 'auto');
|
||||
$("#rightbars").css('right', '7px');
|
||||
$("#rightbars").css('margin-right', '0px');
|
||||
$("#timeslider").css('width', 'auto');
|
||||
}
|
||||
|
||||
if (clientVars.disableRightBar)
|
||||
{
|
||||
$("#rightbars").css('display', 'none');
|
||||
$('#padmain').css('width', 'auto');
|
||||
if (clientVars.fullWidth) $("#padmain").css('right', '7px');
|
||||
else $("#padmain").css('width', '860px');
|
||||
$("#revision").css('position', "absolute");
|
||||
$("#revision").css('right', "20px");
|
||||
$("#revision").css('top', "20px");
|
||||
}
|
||||
|
||||
|
||||
if (clientVars.sliderEnabled)
|
||||
{
|
||||
if (clientVars.supportsSlider)
|
||||
{
|
||||
$("#padmain, #rightbars").css('top', "130px");
|
||||
$("#timeslider").show();
|
||||
setSliderLength(clientVars.totalRevs);
|
||||
setSliderPosition(clientVars.revNum);
|
||||
clientVars.savedRevisions.forEach(function (revision)
|
||||
{
|
||||
addSavedRevision(revision.revNum, revision);
|
||||
})
|
||||
}
|
||||
else
|
||||
{
|
||||
// slider is not supported
|
||||
$("#padmain, #rightbars").css('top', "130px");
|
||||
$("#timeslider").show();
|
||||
$("#error").html("The timeslider feature is not supported on this pad. <a href=\"/ep/about/faq#disabledslider\">Why not?</a>");
|
||||
$("#error").show();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (clientVars.supportsSlider)
|
||||
{
|
||||
setSliderLength(clientVars.totalRevs);
|
||||
setSliderPosition(clientVars.revNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
BroadcastSlider.onSlider(function (loc)
|
||||
{
|
||||
$("#viewlatest").html(loc == BroadcastSlider.getSliderLength() ? "Viewing latest content" : "View latest content");
|
||||
})
|
||||
}
|
|
@ -270,8 +270,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
|
|||
}
|
||||
|
||||
function sendMessage(msg) {
|
||||
//socket.postMessage(JSON.stringify({type: "COLLABROOM", data: msg}));
|
||||
socket.send(JSON.stringify({type: "COLLABROOM", data: msg}));
|
||||
socket.send({type: "COLLABROOM", component: "pad", data: msg});
|
||||
}
|
||||
|
||||
function wrapRecordingErrors(catcher, func) {
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/cssmanager.js
|
||||
/**
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
function makeCSSManager(emptyStylesheetTitle)
|
||||
{
|
||||
|
||||
function getSheetByTitle(title)
|
||||
{
|
||||
var allSheets = document.styleSheets;
|
||||
for (var i = 0; i < allSheets.length; i++)
|
||||
{
|
||||
var s = allSheets[i];
|
||||
if (s.title == title)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*function getSheetTagByTitle(title) {
|
||||
var allStyleTags = document.getElementsByTagName("style");
|
||||
for(var i=0;i<allStyleTags.length;i++) {
|
||||
var t = allStyleTags[i];
|
||||
if (t.title == title) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}*/
|
||||
|
||||
var browserSheet = getSheetByTitle(emptyStylesheetTitle);
|
||||
//var browserTag = getSheetTagByTitle(emptyStylesheetTitle);
|
||||
|
||||
|
||||
function browserRules()
|
||||
{
|
||||
return (browserSheet.cssRules || browserSheet.rules);
|
||||
}
|
||||
|
||||
function browserDeleteRule(i)
|
||||
{
|
||||
if (browserSheet.deleteRule) browserSheet.deleteRule(i);
|
||||
else browserSheet.removeRule(i);
|
||||
}
|
||||
|
||||
function browserInsertRule(i, selector)
|
||||
{
|
||||
if (browserSheet.insertRule) browserSheet.insertRule(selector + ' {}', i);
|
||||
else browserSheet.addRule(selector, null, i);
|
||||
}
|
||||
var selectorList = [];
|
||||
|
||||
function indexOfSelector(selector)
|
||||
{
|
||||
for (var i = 0; i < selectorList.length; i++)
|
||||
{
|
||||
if (selectorList[i] == selector)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function selectorStyle(selector)
|
||||
{
|
||||
var i = indexOfSelector(selector);
|
||||
if (i < 0)
|
||||
{
|
||||
// add selector
|
||||
browserInsertRule(0, selector);
|
||||
selectorList.splice(0, 0, selector);
|
||||
i = 0;
|
||||
}
|
||||
return browserRules().item(i).style;
|
||||
}
|
||||
|
||||
function removeSelectorStyle(selector)
|
||||
{
|
||||
var i = indexOfSelector(selector);
|
||||
if (i >= 0)
|
||||
{
|
||||
browserDeleteRule(i);
|
||||
selectorList.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
selectorStyle: selectorStyle,
|
||||
removeSelectorStyle: removeSelectorStyle,
|
||||
info: function ()
|
||||
{
|
||||
return selectorList.length + ":" + browserRules().length;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/domline.js
|
||||
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.domline
|
||||
/**
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// requires: top
|
||||
// requires: plugins
|
||||
// requires: undefined
|
||||
var domline = {};
|
||||
domline.noop = function ()
|
||||
{};
|
||||
domline.identity = function (x)
|
||||
{
|
||||
return x;
|
||||
};
|
||||
|
||||
domline.addToLineClass = function (lineClass, cls)
|
||||
{
|
||||
// an "empty span" at any point can be used to add classes to
|
||||
// the line, using line:className. otherwise, we ignore
|
||||
// the span.
|
||||
cls.replace(/\S+/g, function (c)
|
||||
{
|
||||
if (c.indexOf("line:") == 0)
|
||||
{
|
||||
// add class to line
|
||||
lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5);
|
||||
}
|
||||
});
|
||||
return lineClass;
|
||||
}
|
||||
|
||||
// if "document" is falsy we don't create a DOM node, just
|
||||
// an object with innerHTML and className
|
||||
domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument)
|
||||
{
|
||||
var result = {
|
||||
node: null,
|
||||
appendSpan: domline.noop,
|
||||
prepareForAdd: domline.noop,
|
||||
notifyAdded: domline.noop,
|
||||
clearSpans: domline.noop,
|
||||
finishUpdate: domline.noop,
|
||||
lineMarker: 0
|
||||
};
|
||||
|
||||
var browser = (optBrowser || {});
|
||||
var document = optDocument;
|
||||
|
||||
if (document)
|
||||
{
|
||||
result.node = document.createElement("div");
|
||||
}
|
||||
else
|
||||
{
|
||||
result.node = {
|
||||
innerHTML: '',
|
||||
className: ''
|
||||
};
|
||||
}
|
||||
|
||||
var html = [];
|
||||
var preHtml, postHtml;
|
||||
var curHTML = null;
|
||||
|
||||
function processSpaces(s)
|
||||
{
|
||||
return domline.processSpaces(s, doesWrap);
|
||||
}
|
||||
var identity = domline.identity;
|
||||
var perTextNodeProcess = (doesWrap ? identity : processSpaces);
|
||||
var perHtmlLineProcess = (doesWrap ? processSpaces : identity);
|
||||
var lineClass = 'ace-line';
|
||||
result.appendSpan = function (txt, cls)
|
||||
{
|
||||
if (cls.indexOf('list') >= 0)
|
||||
{
|
||||
var listType = /(?:^| )list:(\S+)/.exec(cls);
|
||||
if (listType)
|
||||
{
|
||||
listType = listType[1];
|
||||
if (listType)
|
||||
{
|
||||
preHtml = '<ul class="list-' + listType + '"><li>';
|
||||
postHtml = '</li></ul>';
|
||||
}
|
||||
result.lineMarker += txt.length;
|
||||
return; // don't append any text
|
||||
}
|
||||
}
|
||||
var href = null;
|
||||
var simpleTags = null;
|
||||
if (cls.indexOf('url') >= 0)
|
||||
{
|
||||
cls = cls.replace(/(^| )url:(\S+)/g, function (x0, space, url)
|
||||
{
|
||||
href = url;
|
||||
return space + "url";
|
||||
});
|
||||
}
|
||||
if (cls.indexOf('tag') >= 0)
|
||||
{
|
||||
cls = cls.replace(/(^| )tag:(\S+)/g, function (x0, space, tag)
|
||||
{
|
||||
if (!simpleTags) simpleTags = [];
|
||||
simpleTags.push(tag.toLowerCase());
|
||||
return space + tag;
|
||||
});
|
||||
}
|
||||
|
||||
var extraOpenTags = "";
|
||||
var extraCloseTags = "";
|
||||
|
||||
var plugins_;
|
||||
if (typeof (plugins) != 'undefined')
|
||||
{
|
||||
plugins_ = plugins;
|
||||
}
|
||||
else
|
||||
{
|
||||
plugins_ = parent.parent.plugins;
|
||||
}
|
||||
|
||||
plugins_.callHook("aceCreateDomLine", {
|
||||
domline: domline,
|
||||
cls: cls,
|
||||
document: document
|
||||
}).map(function (modifier)
|
||||
{
|
||||
cls = modifier.cls;
|
||||
extraOpenTags = extraOpenTags + modifier.extraOpenTags;
|
||||
extraCloseTags = modifier.extraCloseTags + extraCloseTags;
|
||||
});
|
||||
|
||||
if ((!txt) && cls)
|
||||
{
|
||||
lineClass = domline.addToLineClass(lineClass, cls);
|
||||
}
|
||||
else if (txt)
|
||||
{
|
||||
if (href)
|
||||
{
|
||||
extraOpenTags = extraOpenTags + '<a href="' + href.replace(/\"/g, '"') + '">';
|
||||
extraCloseTags = '</a>' + extraCloseTags;
|
||||
}
|
||||
if (simpleTags)
|
||||
{
|
||||
simpleTags.sort();
|
||||
extraOpenTags = extraOpenTags + '<' + simpleTags.join('><') + '>';
|
||||
simpleTags.reverse();
|
||||
extraCloseTags = '</' + simpleTags.join('></') + '>' + extraCloseTags;
|
||||
}
|
||||
html.push('<span class="', cls || '', '">', extraOpenTags, perTextNodeProcess(domline.escapeHTML(txt)), extraCloseTags, '</span>');
|
||||
}
|
||||
};
|
||||
result.clearSpans = function ()
|
||||
{
|
||||
html = [];
|
||||
lineClass = ''; // non-null to cause update
|
||||
result.lineMarker = 0;
|
||||
};
|
||||
|
||||
function writeHTML()
|
||||
{
|
||||
var newHTML = perHtmlLineProcess(html.join(''));
|
||||
if (!newHTML)
|
||||
{
|
||||
if ((!document) || (!optBrowser))
|
||||
{
|
||||
newHTML += ' ';
|
||||
}
|
||||
else if (!browser.msie)
|
||||
{
|
||||
newHTML += '<br/>';
|
||||
}
|
||||
}
|
||||
if (nonEmpty)
|
||||
{
|
||||
newHTML = (preHtml || '') + newHTML + (postHtml || '');
|
||||
}
|
||||
html = preHtml = postHtml = null; // free memory
|
||||
if (newHTML !== curHTML)
|
||||
{
|
||||
curHTML = newHTML;
|
||||
result.node.innerHTML = curHTML;
|
||||
}
|
||||
if (lineClass !== null) result.node.className = lineClass;
|
||||
}
|
||||
result.prepareForAdd = writeHTML;
|
||||
result.finishUpdate = writeHTML;
|
||||
result.getInnerHTML = function ()
|
||||
{
|
||||
return curHTML || '';
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
domline.escapeHTML = function (s)
|
||||
{
|
||||
var re = /[&<>'"]/g;
|
||||
/']/; // stupid indentation thing
|
||||
if (!re.MAP)
|
||||
{
|
||||
// persisted across function calls!
|
||||
re.MAP = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
}
|
||||
return s.replace(re, function (c)
|
||||
{
|
||||
return re.MAP[c];
|
||||
});
|
||||
};
|
||||
|
||||
domline.processSpaces = function (s, doesWrap)
|
||||
{
|
||||
if (s.indexOf("<") < 0 && !doesWrap)
|
||||
{
|
||||
// short-cut
|
||||
return s.replace(/ /g, ' ');
|
||||
}
|
||||
var parts = [];
|
||||
s.replace(/<[^>]*>?| |[^ <]+/g, function (m)
|
||||
{
|
||||
parts.push(m);
|
||||
});
|
||||
if (doesWrap)
|
||||
{
|
||||
var endOfLine = true;
|
||||
var beforeSpace = false;
|
||||
// last space in a run is normal, others are nbsp,
|
||||
// end of line is nbsp
|
||||
for (var i = parts.length - 1; i >= 0; i--)
|
||||
{
|
||||
var p = parts[i];
|
||||
if (p == " ")
|
||||
{
|
||||
if (endOfLine || beforeSpace) parts[i] = ' ';
|
||||
endOfLine = false;
|
||||
beforeSpace = true;
|
||||
}
|
||||
else if (p.charAt(0) != "<")
|
||||
{
|
||||
endOfLine = false;
|
||||
beforeSpace = false;
|
||||
}
|
||||
}
|
||||
// beginning of line is nbsp
|
||||
for (var i = 0; i < parts.length; i++)
|
||||
{
|
||||
var p = parts[i];
|
||||
if (p == " ")
|
||||
{
|
||||
parts[i] = ' ';
|
||||
break;
|
||||
}
|
||||
else if (p.charAt(0) != "<")
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < parts.length; i++)
|
||||
{
|
||||
var p = parts[i];
|
||||
if (p == " ")
|
||||
{
|
||||
parts[i] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts.join('');
|
||||
};
|
|
@ -0,0 +1,189 @@
|
|||
/**
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
function makeDraggable(jqueryNodes, eventHandler)
|
||||
{
|
||||
jqueryNodes.each(function ()
|
||||
{
|
||||
var node = $(this);
|
||||
var state = {};
|
||||
var inDrag = false;
|
||||
|
||||
function dragStart(evt)
|
||||
{
|
||||
if (inDrag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
inDrag = true;
|
||||
if (eventHandler('dragstart', evt, state) !== false)
|
||||
{
|
||||
$(document).bind('mousemove', dragUpdate);
|
||||
$(document).bind('mouseup', dragEnd);
|
||||
}
|
||||
evt.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
function dragUpdate(evt)
|
||||
{
|
||||
if (!inDrag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
eventHandler('dragupdate', evt, state);
|
||||
evt.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
function dragEnd(evt)
|
||||
{
|
||||
if (!inDrag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
inDrag = false;
|
||||
try
|
||||
{
|
||||
eventHandler('dragend', evt, state);
|
||||
}
|
||||
finally
|
||||
{
|
||||
$(document).unbind('mousemove', dragUpdate);
|
||||
$(document).unbind('mouseup', dragEnd);
|
||||
evt.preventDefault();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
node.bind('mousedown', dragStart);
|
||||
});
|
||||
}
|
||||
|
||||
function makeResizableVPane(top, sep, bottom, minTop, minBottom, callback)
|
||||
{
|
||||
if (minTop === undefined) minTop = 0;
|
||||
if (minBottom === undefined) minBottom = 0;
|
||||
|
||||
makeDraggable($(sep), function (eType, evt, state)
|
||||
{
|
||||
if (eType == 'dragstart')
|
||||
{
|
||||
state.startY = evt.pageY;
|
||||
state.topHeight = $(top).height();
|
||||
state.bottomHeight = $(bottom).height();
|
||||
state.minTop = minTop;
|
||||
state.maxTop = (state.topHeight + state.bottomHeight) - minBottom;
|
||||
}
|
||||
else if (eType == 'dragupdate')
|
||||
{
|
||||
var change = evt.pageY - state.startY;
|
||||
|
||||
var topHeight = state.topHeight + change;
|
||||
if (topHeight < state.minTop)
|
||||
{
|
||||
topHeight = state.minTop;
|
||||
}
|
||||
if (topHeight > state.maxTop)
|
||||
{
|
||||
topHeight = state.maxTop;
|
||||
}
|
||||
change = topHeight - state.topHeight;
|
||||
|
||||
var bottomHeight = state.bottomHeight - change;
|
||||
var sepHeight = $(sep).height();
|
||||
|
||||
var totalHeight = topHeight + sepHeight + bottomHeight;
|
||||
topHeight = 100.0 * topHeight / totalHeight;
|
||||
sepHeight = 100.0 * sepHeight / totalHeight;
|
||||
bottomHeight = 100.0 * bottomHeight / totalHeight;
|
||||
|
||||
$(top).css('bottom', 'auto');
|
||||
$(top).css('height', topHeight + "%");
|
||||
$(sep).css('top', topHeight + "%");
|
||||
$(bottom).css('top', (topHeight + sepHeight) + '%');
|
||||
$(bottom).css('height', 'auto');
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function makeResizableHPane(left, sep, right, minLeft, minRight, sepWidth, sepOffset, callback)
|
||||
{
|
||||
if (minLeft === undefined) minLeft = 0;
|
||||
if (minRight === undefined) minRight = 0;
|
||||
|
||||
makeDraggable($(sep), function (eType, evt, state)
|
||||
{
|
||||
if (eType == 'dragstart')
|
||||
{
|
||||
state.startX = evt.pageX;
|
||||
state.leftWidth = $(left).width();
|
||||
state.rightWidth = $(right).width();
|
||||
state.minLeft = minLeft;
|
||||
state.maxLeft = (state.leftWidth + state.rightWidth) - minRight;
|
||||
}
|
||||
else if (eType == 'dragend' || eType == 'dragupdate')
|
||||
{
|
||||
var change = evt.pageX - state.startX;
|
||||
|
||||
var leftWidth = state.leftWidth + change;
|
||||
if (leftWidth < state.minLeft)
|
||||
{
|
||||
leftWidth = state.minLeft;
|
||||
}
|
||||
if (leftWidth > state.maxLeft)
|
||||
{
|
||||
leftWidth = state.maxLeft;
|
||||
}
|
||||
change = leftWidth - state.leftWidth;
|
||||
|
||||
var rightWidth = state.rightWidth - change;
|
||||
newSepWidth = sepWidth;
|
||||
if (newSepWidth == undefined) newSepWidth = $(sep).width();
|
||||
newSepOffset = sepOffset;
|
||||
if (newSepOffset == undefined) newSepOffset = 0;
|
||||
|
||||
if (change == 0)
|
||||
{
|
||||
if (rightWidth != minRight || state.lastRightWidth == undefined)
|
||||
{
|
||||
state.lastRightWidth = rightWidth;
|
||||
rightWidth = minRight;
|
||||
}
|
||||
else
|
||||
{
|
||||
rightWidth = state.lastRightWidth;
|
||||
state.lastRightWidth = minRight;
|
||||
}
|
||||
change = state.rightWidth - rightWidth;
|
||||
leftWidth = change + state.leftWidth;
|
||||
}
|
||||
|
||||
var totalWidth = leftWidth + newSepWidth + rightWidth;
|
||||
leftWidth = 100.0 * leftWidth / totalWidth;
|
||||
newSepWidth = 100.0 * newSepWidth / totalWidth;
|
||||
newSepOffset = 100.0 * newSepOffset / totalWidth;
|
||||
rightWidth = 100.0 * rightWidth / totalWidth;
|
||||
|
||||
$(left).css('right', 'auto');
|
||||
$(left).css('width', leftWidth + "%");
|
||||
$(sep).css('left', (leftWidth + newSepOffset) + "%");
|
||||
$(right).css('left', (leftWidth + newSepWidth) + '%');
|
||||
$(right).css('width', 'auto');
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,342 @@
|
|||
// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/linestylefilter.js
|
||||
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.linestylefilter
|
||||
/**
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// requires: easysync2.Changeset
|
||||
// requires: top
|
||||
// requires: plugins
|
||||
// requires: undefined
|
||||
var linestylefilter = {};
|
||||
|
||||
linestylefilter.ATTRIB_CLASSES = {
|
||||
'bold': 'tag:b',
|
||||
'italic': 'tag:i',
|
||||
'underline': 'tag:u',
|
||||
'strikethrough': 'tag:s'
|
||||
};
|
||||
|
||||
linestylefilter.getAuthorClassName = function (author)
|
||||
{
|
||||
return "author-" + author.replace(/[^a-y0-9]/g, function (c)
|
||||
{
|
||||
if (c == ".") return "-";
|
||||
return 'z' + c.charCodeAt(0) + 'z';
|
||||
});
|
||||
};
|
||||
|
||||
// lineLength is without newline; aline includes newline,
|
||||
// but may be falsy if lineLength == 0
|
||||
linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFunc, apool)
|
||||
{
|
||||
|
||||
var plugins_;
|
||||
if (typeof (plugins) != 'undefined')
|
||||
{
|
||||
plugins_ = plugins;
|
||||
}
|
||||
else
|
||||
{
|
||||
plugins_ = parent.parent.plugins;
|
||||
}
|
||||
|
||||
if (lineLength == 0) return textAndClassFunc;
|
||||
|
||||
var nextAfterAuthorColors = textAndClassFunc;
|
||||
|
||||
var authorColorFunc = (function ()
|
||||
{
|
||||
var lineEnd = lineLength;
|
||||
var curIndex = 0;
|
||||
var extraClasses;
|
||||
var leftInAuthor;
|
||||
|
||||
function attribsToClasses(attribs)
|
||||
{
|
||||
var classes = '';
|
||||
Changeset.eachAttribNumber(attribs, function (n)
|
||||
{
|
||||
var key = apool.getAttribKey(n);
|
||||
if (key)
|
||||
{
|
||||
var value = apool.getAttribValue(n);
|
||||
if (value)
|
||||
{
|
||||
if (key == 'author')
|
||||
{
|
||||
classes += ' ' + linestylefilter.getAuthorClassName(value);
|
||||
}
|
||||
else if (key == 'list')
|
||||
{
|
||||
classes += ' list:' + value;
|
||||
}
|
||||
else if (linestylefilter.ATTRIB_CLASSES[key])
|
||||
{
|
||||
classes += ' ' + linestylefilter.ATTRIB_CLASSES[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
classes += plugins_.callHookStr("aceAttribsToClasses", {
|
||||
linestylefilter: linestylefilter,
|
||||
key: key,
|
||||
value: value
|
||||
}, " ", " ", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return classes.substring(1);
|
||||
}
|
||||
|
||||
var attributionIter = Changeset.opIterator(aline);
|
||||
var nextOp, nextOpClasses;
|
||||
|
||||
function goNextOp()
|
||||
{
|
||||
nextOp = attributionIter.next();
|
||||
nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs));
|
||||
}
|
||||
goNextOp();
|
||||
|
||||
function nextClasses()
|
||||
{
|
||||
if (curIndex < lineEnd)
|
||||
{
|
||||
extraClasses = nextOpClasses;
|
||||
leftInAuthor = nextOp.chars;
|
||||
goNextOp();
|
||||
while (nextOp.opcode && nextOpClasses == extraClasses)
|
||||
{
|
||||
leftInAuthor += nextOp.chars;
|
||||
goNextOp();
|
||||
}
|
||||
}
|
||||
}
|
||||
nextClasses();
|
||||
|
||||
return function (txt, cls)
|
||||
{
|
||||
while (txt.length > 0)
|
||||
{
|
||||
if (leftInAuthor <= 0)
|
||||
{
|
||||
// prevent infinite loop if something funny's going on
|
||||
return nextAfterAuthorColors(txt, cls);
|
||||
}
|
||||
var spanSize = txt.length;
|
||||
if (spanSize > leftInAuthor)
|
||||
{
|
||||
spanSize = leftInAuthor;
|
||||
}
|
||||
var curTxt = txt.substring(0, spanSize);
|
||||
txt = txt.substring(spanSize);
|
||||
nextAfterAuthorColors(curTxt, (cls && cls + " ") + extraClasses);
|
||||
curIndex += spanSize;
|
||||
leftInAuthor -= spanSize;
|
||||
if (leftInAuthor == 0)
|
||||
{
|
||||
nextClasses();
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
return authorColorFunc;
|
||||
};
|
||||
|
||||
linestylefilter.getAtSignSplitterFilter = function (lineText, textAndClassFunc)
|
||||
{
|
||||
var at = /@/g;
|
||||
at.lastIndex = 0;
|
||||
var splitPoints = null;
|
||||
var execResult;
|
||||
while ((execResult = at.exec(lineText)))
|
||||
{
|
||||
if (!splitPoints)
|
||||
{
|
||||
splitPoints = [];
|
||||
}
|
||||
splitPoints.push(execResult.index);
|
||||
}
|
||||
|
||||
if (!splitPoints) return textAndClassFunc;
|
||||
|
||||
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
|
||||
};
|
||||
|
||||
linestylefilter.getRegexpFilter = function (regExp, tag)
|
||||
{
|
||||
return function (lineText, textAndClassFunc)
|
||||
{
|
||||
regExp.lastIndex = 0;
|
||||
var regExpMatchs = null;
|
||||
var splitPoints = null;
|
||||
var execResult;
|
||||
while ((execResult = regExp.exec(lineText)))
|
||||
{
|
||||
if (!regExpMatchs)
|
||||
{
|
||||
regExpMatchs = [];
|
||||
splitPoints = [];
|
||||
}
|
||||
var startIndex = execResult.index;
|
||||
var regExpMatch = execResult[0];
|
||||
regExpMatchs.push([startIndex, regExpMatch]);
|
||||
splitPoints.push(startIndex, startIndex + regExpMatch.length);
|
||||
}
|
||||
|
||||
if (!regExpMatchs) return textAndClassFunc;
|
||||
|
||||
function regExpMatchForIndex(idx)
|
||||
{
|
||||
for (var k = 0; k < regExpMatchs.length; k++)
|
||||
{
|
||||
var u = regExpMatchs[k];
|
||||
if (idx >= u[0] && idx < u[0] + u[1].length)
|
||||
{
|
||||
return u[1];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var handleRegExpMatchsAfterSplit = (function ()
|
||||
{
|
||||
var curIndex = 0;
|
||||
return function (txt, cls)
|
||||
{
|
||||
var txtlen = txt.length;
|
||||
var newCls = cls;
|
||||
var regExpMatch = regExpMatchForIndex(curIndex);
|
||||
if (regExpMatch)
|
||||
{
|
||||
newCls += " " + tag + ":" + regExpMatch;
|
||||
}
|
||||
textAndClassFunc(txt, newCls);
|
||||
curIndex += txtlen;
|
||||
};
|
||||
})();
|
||||
|
||||
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, splitPoints);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
linestylefilter.REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
|
||||
linestylefilter.REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + linestylefilter.REGEX_WORDCHAR.source + ')');
|
||||
linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + linestylefilter.REGEX_URLCHAR.source + '*(?![:.,;])' + linestylefilter.REGEX_URLCHAR.source, 'g');
|
||||
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(
|
||||
linestylefilter.REGEX_URL, 'url');
|
||||
|
||||
linestylefilter.textAndClassFuncSplitter = function (func, splitPointsOpt)
|
||||
{
|
||||
var nextPointIndex = 0;
|
||||
var idx = 0;
|
||||
|
||||
// don't split at 0
|
||||
while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0)
|
||||
{
|
||||
nextPointIndex++;
|
||||
}
|
||||
|
||||
function spanHandler(txt, cls)
|
||||
{
|
||||
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length)
|
||||
{
|
||||
func(txt, cls);
|
||||
idx += txt.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
var splitPoints = splitPointsOpt;
|
||||
var pointLocInSpan = splitPoints[nextPointIndex] - idx;
|
||||
var txtlen = txt.length;
|
||||
if (pointLocInSpan >= txtlen)
|
||||
{
|
||||
func(txt, cls);
|
||||
idx += txt.length;
|
||||
if (pointLocInSpan == txtlen)
|
||||
{
|
||||
nextPointIndex++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pointLocInSpan > 0)
|
||||
{
|
||||
func(txt.substring(0, pointLocInSpan), cls);
|
||||
idx += pointLocInSpan;
|
||||
}
|
||||
nextPointIndex++;
|
||||
// recurse
|
||||
spanHandler(txt.substring(pointLocInSpan), cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
return spanHandler;
|
||||
};
|
||||
|
||||
linestylefilter.getFilterStack = function (lineText, textAndClassFunc, browser)
|
||||
{
|
||||
var func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
|
||||
|
||||
var plugins_;
|
||||
if (typeof (plugins) != 'undefined')
|
||||
{
|
||||
plugins_ = plugins;
|
||||
}
|
||||
else
|
||||
{
|
||||
plugins_ = parent.parent.plugins;
|
||||
}
|
||||
|
||||
var hookFilters = plugins_.callHook("aceGetFilterStack", {
|
||||
linestylefilter: linestylefilter,
|
||||
browser: browser
|
||||
});
|
||||
hookFilters.map(function (hookFilter)
|
||||
{
|
||||
func = hookFilter(lineText, func);
|
||||
});
|
||||
|
||||
if (browser !== undefined && browser.msie)
|
||||
{
|
||||
// IE7+ will take an e-mail address like <foo@bar.com> and linkify it to foo@bar.com.
|
||||
// We then normalize it back to text with no angle brackets. It's weird. So always
|
||||
// break spans at an "at" sign.
|
||||
func = linestylefilter.getAtSignSplitterFilter(
|
||||
lineText, func);
|
||||
}
|
||||
return func;
|
||||
};
|
||||
|
||||
// domLineObj is like that returned by domline.createDomLine
|
||||
linestylefilter.populateDomLine = function (textLine, aline, apool, domLineObj)
|
||||
{
|
||||
// remove final newline from text if any
|
||||
var text = textLine;
|
||||
if (text.slice(-1) == '\n')
|
||||
{
|
||||
text = text.substring(0, text.length - 1);
|
||||
}
|
||||
|
||||
function textAndClassFunc(tokenText, tokenClass)
|
||||
{
|
||||
domLineObj.appendSpan(tokenText, tokenClass);
|
||||
}
|
||||
|
||||
var func = linestylefilter.getFilterStack(text, textAndClassFunc);
|
||||
func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
|
||||
func(text, '');
|
||||
};
|
|
@ -75,10 +75,11 @@ function handshake()
|
|||
createCookie("token", token, 60);
|
||||
}
|
||||
|
||||
var msg = { "type":"CLIENT_READY",
|
||||
var msg = { "component" : "pad",
|
||||
"type":"CLIENT_READY",
|
||||
"padId": padId,
|
||||
"token": token,
|
||||
"protocolVersion": 1};
|
||||
"protocolVersion": 2};
|
||||
|
||||
socket.send(msg);
|
||||
});
|
||||
|
|