A lot small changes that results in a timeslider that shows the latest text

This commit is contained in:
Peter 'Pita' Martischka 2011-06-20 11:44:04 +01:00
parent 182477af93
commit 44aa476ec0
36 changed files with 6944 additions and 52 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;

83
node/SocketIORouter.js Normal file
View File

@ -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);
}
});
});
}

View File

@ -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);
});
}

View File

@ -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));
}
}

View File

@ -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",

251
static/css/broadcast.css Normal file
View File

@ -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;
}

1139
static/css/pad2_ejs.css Normal file

File diff suppressed because it is too large Load Diff

BIN
static/img/backgrad.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

BIN
static/img/padtop5.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
static/img/padtopback2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

BIN
static/img/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

714
static/js/broadcast.js Normal file
View File

@ -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);
}

View File

@ -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))

View File

@ -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");
})
}

View File

@ -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) {

View File

@ -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;
}
};
}

292
static/js/domline_client.js Normal file
View File

@ -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, '&quot;') + '">';
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 += '&nbsp;';
}
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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&#34;',
"'": '&#39;'
};
}
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, '&nbsp;');
}
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] = '&nbsp;';
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] = '&nbsp;';
break;
}
else if (p.charAt(0) != "<")
{
break;
}
}
}
else
{
for (var i = 0; i < parts.length; i++)
{
var p = parts[i];
if (p == " ")
{
parts[i] = '&nbsp;';
}
}
}
return parts.join('');
};

189
static/js/draggable.js Normal file
View File

@ -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();
}
});
}

File diff suppressed because it is too large Load Diff

View File

@ -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, '');
};

View File

@ -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);
});

376
static/timeslider.html Normal file

File diff suppressed because one or more lines are too long