beautified all static js files

This commit is contained in:
Peter 'Pita' Martischka 2011-07-07 18:59:34 +01:00
parent 2fa1d8768b
commit 271ee1776b
36 changed files with 9456 additions and 6035 deletions

View file

@ -18,38 +18,54 @@
// requires: plugins // requires: plugins
// requires: undefined // requires: undefined
Ace2Editor.registry = {
nextId: 1
};
Ace2Editor.registry = { nextId: 1 }; function Ace2Editor()
{
function Ace2Editor() {
var thisFunctionsName = "Ace2Editor"; var thisFunctionsName = "Ace2Editor";
var ace2 = Ace2Editor; var ace2 = Ace2Editor;
var editor = {}; var editor = {};
var info = { editor: editor, id: (ace2.registry.nextId++) }; var info = {
editor: editor,
id: (ace2.registry.nextId++)
};
var loaded = false; var loaded = false;
var actionsPendingInit = []; var actionsPendingInit = [];
function pendingInit(func, optDoNow) {
return function() { function pendingInit(func, optDoNow)
{
return function()
{
var that = this; var that = this;
var args = arguments; var args = arguments;
function action() {
function action()
{
func.apply(that, args); func.apply(that, args);
} }
if (optDoNow) { if (optDoNow)
{
optDoNow.apply(that, args); optDoNow.apply(that, args);
} }
if (loaded) { if (loaded)
{
action(); action();
} }
else { else
{
actionsPendingInit.push(action); actionsPendingInit.push(action);
} }
}; };
} }
function doActionsPendingInit() {
for(var i=0;i<actionsPendingInit.length;i++) { function doActionsPendingInit()
{
for (var i = 0; i < actionsPendingInit.length; i++)
{
actionsPendingInit[i](); actionsPendingInit[i]();
} }
actionsPendingInit = []; actionsPendingInit = [];
@ -57,30 +73,69 @@ function Ace2Editor() {
ace2.registry[info.id] = info; ace2.registry[info.id] = info;
editor.importText = pendingInit(function(newCode, undoable) { editor.importText = pendingInit(function(newCode, undoable)
info.ace_importText(newCode, undoable); }); {
editor.importAText = pendingInit(function(newCode, apoolJsonObj, undoable) { info.ace_importText(newCode, undoable);
info.ace_importAText(newCode, apoolJsonObj, undoable); }); });
editor.exportText = function() { editor.importAText = pendingInit(function(newCode, apoolJsonObj, undoable)
if (! loaded) return "(awaiting init)\n"; {
info.ace_importAText(newCode, apoolJsonObj, undoable);
});
editor.exportText = function()
{
if (!loaded) return "(awaiting init)\n";
return info.ace_exportText(); return info.ace_exportText();
}; };
editor.getFrame = function() { return info.frame || null; }; editor.getFrame = function()
editor.focus = pendingInit(function() { info.ace_focus(); }); {
editor.setEditable = pendingInit(function(newVal) { info.ace_setEditable(newVal); }); return info.frame || null;
editor.getFormattedCode = function() { return info.ace_getFormattedCode(); }; };
editor.setOnKeyPress = pendingInit(function (handler) { info.ace_setOnKeyPress(handler); }); editor.focus = pendingInit(function()
editor.setOnKeyDown = pendingInit(function (handler) { info.ace_setOnKeyDown(handler); }); {
editor.setNotifyDirty = pendingInit(function (handler) { info.ace_setNotifyDirty(handler); }); info.ace_focus();
});
editor.setEditable = pendingInit(function(newVal)
{
info.ace_setEditable(newVal);
});
editor.getFormattedCode = function()
{
return info.ace_getFormattedCode();
};
editor.setOnKeyPress = pendingInit(function(handler)
{
info.ace_setOnKeyPress(handler);
});
editor.setOnKeyDown = pendingInit(function(handler)
{
info.ace_setOnKeyDown(handler);
});
editor.setNotifyDirty = pendingInit(function(handler)
{
info.ace_setNotifyDirty(handler);
});
editor.setProperty = pendingInit(function(key, value) { info.ace_setProperty(key, value); }); editor.setProperty = pendingInit(function(key, value)
editor.getDebugProperty = function(prop) { return info.ace_getDebugProperty(prop); }; {
info.ace_setProperty(key, value);
});
editor.getDebugProperty = function(prop)
{
return info.ace_getDebugProperty(prop);
};
editor.setBaseText = pendingInit(function(txt) { info.ace_setBaseText(txt); }); editor.setBaseText = pendingInit(function(txt)
editor.setBaseAttributedText = pendingInit(function(atxt, apoolJsonObj) { {
info.ace_setBaseAttributedText(atxt, apoolJsonObj); }); info.ace_setBaseText(txt);
editor.applyChangesToBase = pendingInit(function (changes, optAuthor,apoolJsonObj) { });
info.ace_applyChangesToBase(changes, optAuthor, apoolJsonObj); }); editor.setBaseAttributedText = pendingInit(function(atxt, apoolJsonObj)
{
info.ace_setBaseAttributedText(atxt, apoolJsonObj);
});
editor.applyChangesToBase = pendingInit(function(changes, optAuthor, apoolJsonObj)
{
info.ace_applyChangesToBase(changes, optAuthor, apoolJsonObj);
});
// prepareUserChangeset: // prepareUserChangeset:
// Returns null if no new changes or ACE not ready. Otherwise, bundles up all user changes // Returns null if no new changes or ACE not ready. Otherwise, bundles up all user changes
// to the latest base text into a Changeset, which is returned (as a string if encodeAsString). // to the latest base text into a Changeset, which is returned (as a string if encodeAsString).
@ -90,36 +145,48 @@ function Ace2Editor() {
// to prepareUserChangeset will return an updated changeset that takes into account the // to prepareUserChangeset will return an updated changeset that takes into account the
// latest user changes, and modify the changeset to be applied by applyPreparedChangesetToBase // latest user changes, and modify the changeset to be applied by applyPreparedChangesetToBase
// accordingly. // accordingly.
editor.prepareUserChangeset = function() { editor.prepareUserChangeset = function()
if (! loaded) return null; {
if (!loaded) return null;
return info.ace_prepareUserChangeset(); return info.ace_prepareUserChangeset();
}; };
editor.applyPreparedChangesetToBase = pendingInit( editor.applyPreparedChangesetToBase = pendingInit(
function() { info.ace_applyPreparedChangesetToBase(); });
editor.setUserChangeNotificationCallback = pendingInit(function(callback) { function()
{
info.ace_applyPreparedChangesetToBase();
});
editor.setUserChangeNotificationCallback = pendingInit(function(callback)
{
info.ace_setUserChangeNotificationCallback(callback); info.ace_setUserChangeNotificationCallback(callback);
}); });
editor.setAuthorInfo = pendingInit(function(author, authorInfo) { editor.setAuthorInfo = pendingInit(function(author, authorInfo)
{
info.ace_setAuthorInfo(author, authorInfo); info.ace_setAuthorInfo(author, authorInfo);
}); });
editor.setAuthorSelectionRange = pendingInit(function(author, start, end) { editor.setAuthorSelectionRange = pendingInit(function(author, start, end)
{
info.ace_setAuthorSelectionRange(author, start, end); info.ace_setAuthorSelectionRange(author, start, end);
}); });
editor.getUnhandledErrors = function() { editor.getUnhandledErrors = function()
if (! loaded) return []; {
if (!loaded) return [];
// returns array of {error: <browser Error object>, time: +new Date()} // returns array of {error: <browser Error object>, time: +new Date()}
return info.ace_getUnhandledErrors(); return info.ace_getUnhandledErrors();
}; };
editor.callWithAce = pendingInit(function(fn, callStack, normalize) { editor.callWithAce = pendingInit(function(fn, callStack, normalize)
{
return info.ace_callWithAce(fn, callStack, normalize); return info.ace_callWithAce(fn, callStack, normalize);
}); });
editor.execCommand = pendingInit(function(cmd, arg1) { editor.execCommand = pendingInit(function(cmd, arg1)
{
info.ace_execCommand(cmd, arg1); info.ace_execCommand(cmd, arg1);
}); });
editor.replaceRange = pendingInit(function(start, end, text) { editor.replaceRange = pendingInit(function(start, end, text)
{
info.ace_replaceRange(start, end, text); info.ace_replaceRange(start, end, text);
}); });
@ -127,49 +194,57 @@ function Ace2Editor() {
// calls to these functions ($$INCLUDE_...) are replaced when this file is processed // calls to these functions ($$INCLUDE_...) are replaced when this file is processed
// and compressed, putting the compressed code from the named file directly into the // and compressed, putting the compressed code from the named file directly into the
// source here. // source here.
var $$INCLUDE_CSS = function(fileName)
var $$INCLUDE_CSS = function(fileName) { {
return '<link rel="stylesheet" type="text/css" href="'+fileName+'"/>'; return '<link rel="stylesheet" type="text/css" href="' + fileName + '"/>';
}; };
var $$INCLUDE_JS = function(fileName) { var $$INCLUDE_JS = function(fileName)
return '\x3cscript type="text/javascript" src="'+fileName+'">\x3c/script>'; {
return '\x3cscript type="text/javascript" src="' + fileName + '">\x3c/script>';
}; };
var $$INCLUDE_JS_DEV = $$INCLUDE_JS; var $$INCLUDE_JS_DEV = $$INCLUDE_JS;
var $$INCLUDE_CSS_DEV = $$INCLUDE_CSS; var $$INCLUDE_CSS_DEV = $$INCLUDE_CSS;
var $$INCLUDE_CSS_Q = function(fileName) { var $$INCLUDE_CSS_Q = function(fileName)
return '\'<link rel="stylesheet" type="text/css" href="'+fileName+'"/>\''; {
return '\'<link rel="stylesheet" type="text/css" href="' + fileName + '"/>\'';
}; };
var $$INCLUDE_JS_Q = function(fileName) { var $$INCLUDE_JS_Q = function(fileName)
return '\'\\x3cscript type="text/javascript" src="'+fileName+'">\\x3c/script>\''; {
return '\'\\x3cscript type="text/javascript" src="' + fileName + '">\\x3c/script>\'';
}; };
var $$INCLUDE_JS_Q_DEV = $$INCLUDE_JS_Q; var $$INCLUDE_JS_Q_DEV = $$INCLUDE_JS_Q;
var $$INCLUDE_CSS_Q_DEV = $$INCLUDE_CSS_Q; var $$INCLUDE_CSS_Q_DEV = $$INCLUDE_CSS_Q;
editor.destroy = pendingInit(function() { editor.destroy = pendingInit(function()
{
info.ace_dispose(); info.ace_dispose();
info.frame.parentNode.removeChild(info.frame); info.frame.parentNode.removeChild(info.frame);
delete ace2.registry[info.id]; delete ace2.registry[info.id];
info = null; // prevent IE 6 closure memory leaks info = null; // prevent IE 6 closure memory leaks
}); });
editor.init = function(containerId, initialCode, doneFunc) { editor.init = function(containerId, initialCode, doneFunc)
{
editor.importText(initialCode); editor.importText(initialCode);
info.onEditorReady = function() { info.onEditorReady = function()
{
loaded = true; loaded = true;
doActionsPendingInit(); doActionsPendingInit();
doneFunc(); doneFunc();
}; };
(function() { (function()
{
var doctype = "<!doctype html>"; var doctype = "<!doctype html>";
var iframeHTML = ["'"+doctype+"<html><head>'"]; var iframeHTML = ["'" + doctype + "<html><head>'"];
plugins.callHook( plugins.callHook("aceInitInnerdocbodyHead", {
"aceInitInnerdocbodyHead", {iframeHTML:iframeHTML}); iframeHTML: iframeHTML
});
// these lines must conform to a specific format because they are passed by the build script: // these lines must conform to a specific format because they are passed by the build script:
iframeHTML.push($$INCLUDE_CSS_Q("static/css/editor.css")); iframeHTML.push($$INCLUDE_CSS_Q("static/css/editor.css"));
@ -191,35 +266,22 @@ function Ace2Editor() {
iframeHTML.push('\'\\n<style type="text/css" title="dynamicsyntax"></style>\\n\''); iframeHTML.push('\'\\n<style type="text/css" title="dynamicsyntax"></style>\\n\'');
iframeHTML.push('\'</head><body id="innerdocbody" class="syntax" spellcheck="false">&nbsp;</body></html>\''); iframeHTML.push('\'</head><body id="innerdocbody" class="syntax" spellcheck="false">&nbsp;</body></html>\'');
var outerScript = 'editorId = "'+info.id+'"; editorInfo = parent.'+ var outerScript = 'editorId = "' + info.id + '"; editorInfo = parent.' + thisFunctionsName + '.registry[editorId]; ' + 'window.onload = function() ' + '{ window.onload = null; setTimeout' + '(function() ' + '{ var iframe = document.createElement("IFRAME"); ' + 'iframe.scrolling = "no"; var outerdocbody = document.getElementById("outerdocbody"); ' + 'iframe.frameBorder = 0; iframe.allowTransparency = true; ' + // for IE
thisFunctionsName+'.registry[editorId]; '+ 'outerdocbody.insertBefore(iframe, outerdocbody.firstChild); ' + 'iframe.ace_outerWin = window; ' + 'readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; ' + 'var doc = iframe.contentWindow.document; doc.open(); var text = (' + iframeHTML.join('+') + ').replace(/\\\\x3c/g, \'<\');doc.write(text); doc.close(); ' + '}, 0); }';
'window.onload = function() '+
'{ window.onload = null; setTimeout'+
'(function() '+
'{ var iframe = document.createElement("IFRAME"); '+
'iframe.scrolling = "no"; var outerdocbody = document.getElementById("outerdocbody"); '+
'iframe.frameBorder = 0; iframe.allowTransparency = true; '+ // for IE
'outerdocbody.insertBefore(iframe, outerdocbody.firstChild); '+
'iframe.ace_outerWin = window; '+
'readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; '+
'var doc = iframe.contentWindow.document; doc.open(); var text = ('+
iframeHTML.join('+')+').replace(/\\\\x3c/g, \'<\');doc.write(text); doc.close(); '+
'}, 0); }';
var outerHTML = [doctype, '<html><head>', var outerHTML = [doctype, '<html><head>', $$INCLUDE_CSS("static/css/editor.css"),
$$INCLUDE_CSS("static/css/editor.css"),
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly // bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
// (throbs busy while typing) // (throbs busy while typing)
'<link rel="stylesheet" type="text/css" href="data:text/css,"/>', '<link rel="stylesheet" type="text/css" href="data:text/css,"/>', '\x3cscript>\n', outerScript, '\n\x3c/script>', '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>'];
'\x3cscript>\n', outerScript, '\n\x3c/script>',
'</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>'];
if (!Array.prototype.map) Array.prototype.map = function(fun) { //needed for IE if (!Array.prototype.map) Array.prototype.map = function(fun)
{ //needed for IE
if (typeof fun != "function") throw new TypeError(); if (typeof fun != "function") throw new TypeError();
var len = this.length; var len = this.length;
var res = new Array(len); var res = new Array(len);
var thisp = arguments[1]; var thisp = arguments[1];
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++)
{
if (i in this) res[i] = fun.call(thisp, this[i], i, this); if (i in this) res[i] = fun.call(thisp, this[i], i, this);
} }
return res; return res;

View file

@ -15,53 +15,63 @@
*/ */
function isNodeText(node) { function isNodeText(node)
{
return (node.nodeType == 3); return (node.nodeType == 3);
} }
function object(o) { function object(o)
var f = function() {}; {
var f = function()
{};
f.prototype = o; f.prototype = o;
return new f(); return new f();
} }
function extend(obj, props) { function extend(obj, props)
for(var p in props) { {
for (var p in props)
{
obj[p] = props[p]; obj[p] = props[p];
} }
return obj; return obj;
} }
function forEach(array, func) { function forEach(array, func)
for(var i=0;i<array.length;i++) { {
for (var i = 0; i < array.length; i++)
{
var result = func(array[i], i); var result = func(array[i], i);
if (result) break; if (result) break;
} }
} }
function map(array, func) { function map(array, func)
{
var result = []; var result = [];
// must remain compatible with "arguments" pseudo-array // must remain compatible with "arguments" pseudo-array
for(var i=0;i<array.length;i++) { for (var i = 0; i < array.length; i++)
{
if (func) result.push(func(array[i], i)); if (func) result.push(func(array[i], i));
else result.push(array[i]); else result.push(array[i]);
} }
return result; return result;
} }
function filter(array, func) { function filter(array, func)
{
var result = []; var result = [];
// must remain compatible with "arguments" pseudo-array // must remain compatible with "arguments" pseudo-array
for(var i=0;i<array.length;i++) { for (var i = 0; i < array.length; i++)
{
if (func(array[i], i)) result.push(array[i]); if (func(array[i], i)) result.push(array[i]);
} }
return result; return result;
} }
function isArray(testObject) { function isArray(testObject)
return testObject && typeof testObject === 'object' && {
!(testObject.propertyIsEnumerable('length')) && return testObject && typeof testObject === 'object' && !(testObject.propertyIsEnumerable('length')) && typeof testObject.length === 'number';
typeof testObject.length === 'number';
} }
// Figure out what browser is being used (stolen from jquery 1.2.1) // Figure out what browser is being used (stolen from jquery 1.2.1)
@ -75,41 +85,48 @@ var browser = {
windows: /windows/.test(userAgent) // dgreensp windows: /windows/.test(userAgent) // dgreensp
}; };
function getAssoc(obj, name) { function getAssoc(obj, name)
return obj["_magicdom_"+name]; {
return obj["_magicdom_" + name];
} }
function setAssoc(obj, name, value) { function setAssoc(obj, name, value)
{
// note that in IE designMode, properties of a node can get // note that in IE designMode, properties of a node can get
// copied to new nodes that are spawned during editing; also, // copied to new nodes that are spawned during editing; also,
// properties representable in HTML text can survive copy-and-paste // properties representable in HTML text can survive copy-and-paste
obj["_magicdom_"+name] = value; obj["_magicdom_" + name] = value;
} }
// "func" is a function over 0..(numItems-1) that is monotonically // "func" is a function over 0..(numItems-1) that is monotonically
// "increasing" with index (false, then true). Finds the boundary // "increasing" with index (false, then true). Finds the boundary
// between false and true, a number between 0 and numItems inclusive. // between false and true, a number between 0 and numItems inclusive.
function binarySearch(numItems, func) {
function binarySearch(numItems, func)
{
if (numItems < 1) return 0; if (numItems < 1) return 0;
if (func(0)) return 0; if (func(0)) return 0;
if (! func(numItems-1)) return numItems; if (!func(numItems - 1)) return numItems;
var low = 0; // func(low) is always false var low = 0; // func(low) is always false
var high = numItems-1; // func(high) is always true var high = numItems - 1; // func(high) is always true
while ((high - low) > 1) { while ((high - low) > 1)
var x = Math.floor((low+high)/2); // x != low, x != high {
var x = Math.floor((low + high) / 2); // x != low, x != high
if (func(x)) high = x; if (func(x)) high = x;
else low = x; else low = x;
} }
return high; return high;
} }
function binarySearchInfinite(expectedLength, func) { function binarySearchInfinite(expectedLength, func)
{
var i = 0; var i = 0;
while (!func(i)) i += expectedLength; while (!func(i)) i += expectedLength;
return binarySearch(i, func); return binarySearch(i, func);
} }
function htmlPrettyEscape(str) { function htmlPrettyEscape(str)
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') {
.replace(/\r?\n/g, '\\n'); return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\r?\n/g, '\\n');
} }

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,7 @@ function loadBroadcastJS()
// Below Array#map code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_map.htm // 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) if (!Array.prototype.map)
{ {
Array.prototype.map = function (fun /*, thisp*/ ) Array.prototype.map = function(fun /*, thisp*/ )
{ {
var len = this.length >>> 0; var len = this.length >>> 0;
if (typeof fun != "function") throw new TypeError(); if (typeof fun != "function") throw new TypeError();
@ -41,7 +41,7 @@ function loadBroadcastJS()
// Below Array#forEach code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_foreach.htm // 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) if (!Array.prototype.forEach)
{ {
Array.prototype.forEach = function (fun /*, thisp*/ ) Array.prototype.forEach = function(fun /*, thisp*/ )
{ {
var len = this.length >>> 0; var len = this.length >>> 0;
if (typeof fun != "function") throw new TypeError(); if (typeof fun != "function") throw new TypeError();
@ -57,7 +57,7 @@ function loadBroadcastJS()
// Below Array#indexOf code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_indexof.htm // 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) if (!Array.prototype.indexOf)
{ {
Array.prototype.indexOf = function (elt /*, from*/ ) Array.prototype.indexOf = function(elt /*, from*/ )
{ {
var len = this.length >>> 0; var len = this.length >>> 0;
@ -77,11 +77,11 @@ function loadBroadcastJS()
{ {
try try
{ {
if(window.console) console.log.apply(console, arguments); if (window.console) console.log.apply(console, arguments);
} }
catch (e) catch (e)
{ {
if(window.console) console.log("error printing: ", e); if (window.console) console.log("error printing: ", e);
} }
} }
@ -104,7 +104,6 @@ function loadBroadcastJS()
var userId = "hiddenUser" + randomString(); var userId = "hiddenUser" + randomString();
var socketId; var socketId;
//var socket; //var socket;
var channelState = "DISCONNECTED"; var channelState = "DISCONNECTED";
var appLevelDisconnectReason = null; var appLevelDisconnectReason = null;
@ -120,7 +119,7 @@ function loadBroadcastJS()
clientVars.initialStyledContents.atext.attribs, clientVars.initialStyledContents.atext.text), clientVars.initialStyledContents.atext.attribs, clientVars.initialStyledContents.atext.text),
// generates a jquery element containing HTML for a line // generates a jquery element containing HTML for a line
lineToElement: function (line, aline) lineToElement: function(line, aline)
{ {
var element = document.createElement("div"); var element = document.createElement("div");
var emptyLine = (line == '\n'); var emptyLine = (line == '\n');
@ -133,7 +132,7 @@ function loadBroadcastJS()
return $(element); return $(element);
}, },
applySpliceToDivs: function (start, numRemoved, newLines) applySpliceToDivs: function(start, numRemoved, newLines)
{ {
// remove spliced-out lines from DOM // remove spliced-out lines from DOM
for (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++) for (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++)
@ -176,11 +175,11 @@ function loadBroadcastJS()
}, },
// splice the lines // splice the lines
splice: function (start, numRemoved, newLinesVA) splice: function(start, numRemoved, newLinesVA)
{ {
var newLines = Array.prototype.slice.call(arguments, 2).map( var newLines = Array.prototype.slice.call(arguments, 2).map(
function (s) function(s)
{ {
return s; return s;
}); });
@ -194,17 +193,17 @@ function loadBroadcastJS()
this.currentLines.splice.apply(this.currentLines, arguments); this.currentLines.splice.apply(this.currentLines, arguments);
}, },
// returns the contents of the specified line I // returns the contents of the specified line I
get: function (i) get: function(i)
{ {
return this.currentLines[i]; return this.currentLines[i];
}, },
// returns the number of lines in the document // returns the number of lines in the document
length: function () length: function()
{ {
return this.currentLines.length; return this.currentLines.length;
}, },
getActiveAuthors: function () getActiveAuthors: function()
{ {
var self = this; var self = this;
var authors = []; var authors = [];
@ -212,7 +211,7 @@ function loadBroadcastJS()
var alines = self.alines; var alines = self.alines;
for (var i = 0; i < alines.length; i++) for (var i = 0; i < alines.length; i++)
{ {
Changeset.eachAttribNumber(alines[i], function (n) Changeset.eachAttribNumber(alines[i], function(n)
{ {
if (!seenNums[n]) if (!seenNums[n])
{ {
@ -246,7 +245,7 @@ function loadBroadcastJS()
function wrapRecordingErrors(catcher, func) function wrapRecordingErrors(catcher, func)
{ {
return function () return function()
{ {
try try
{ {
@ -273,7 +272,7 @@ function loadBroadcastJS()
if (broadcasting) applyChangeset(changesetForward, revision + 1, false, timeDelta); if (broadcasting) applyChangeset(changesetForward, revision + 1, false, timeDelta);
} }
/* /*
At this point, we must be certain that the changeset really does map from 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 the current revision to the specified revision. Any mistakes here will
cause the whole slider to get out of sync. cause the whole slider to get out of sync.
@ -303,7 +302,7 @@ function loadBroadcastJS()
padContents.currentTime += timeDelta * 1000; padContents.currentTime += timeDelta * 1000;
debugLog('Time Delta: ', timeDelta) debugLog('Time Delta: ', timeDelta)
updateTimer(); updateTimer();
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function (name) BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name)
{ {
return authorData[name]; return authorData[name];
})); }));
@ -311,7 +310,7 @@ function loadBroadcastJS()
function updateTimer() function updateTimer()
{ {
var zpad = function (str, length) var zpad = function(str, length)
{ {
str = str + ""; str = str + "";
while (str.length < length) while (str.length < length)
@ -319,8 +318,10 @@ function loadBroadcastJS()
return str; return str;
} }
var date = new Date(padContents.currentTime); var date = new Date(padContents.currentTime);
var dateFormat = function () var dateFormat = function()
{ {
var month = zpad(date.getMonth() + 1, 2); var month = zpad(date.getMonth() + 1, 2);
var day = zpad(date.getDate(), 2); var day = zpad(date.getDate(), 2);
@ -333,6 +334,8 @@ function loadBroadcastJS()
$('#timer').html(dateFormat()); $('#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(" ") var revisionDate = ["Saved", ["Jan", "Feb", "March", "April", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec"][date.getMonth()], date.getDate() + ",", date.getFullYear()].join(" ")
@ -365,7 +368,7 @@ function loadBroadcastJS()
var sliderLocation = padContents.currentRevision; var sliderLocation = padContents.currentRevision;
// callback is called after changeset information is pulled from server // callback is called after changeset information is pulled from server
// this may never get called, if the changeset has already been loaded // this may never get called, if the changeset has already been loaded
var update = function (start, end) var update = function(start, end)
{ {
// if we've called goToRevision in the time since, don't goToRevision // if we've called goToRevision in the time since, don't goToRevision
goToRevision(padContents.targetRevision); goToRevision(padContents.targetRevision);
@ -400,7 +403,7 @@ function loadBroadcastJS()
changesetLoader.queueUp(start, 1, update); changesetLoader.queueUp(start, 1, update);
} }
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function (name) BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name)
{ {
return authorData[name]; return authorData[name];
})); }));
@ -413,7 +416,7 @@ function loadBroadcastJS()
requestQueue2: [], requestQueue2: [],
requestQueue3: [], requestQueue3: [],
reqCallbacks: [], reqCallbacks: [],
queueUp: function (revision, width, callback) queueUp: function(revision, width, callback)
{ {
if (revision < 0) revision = 0; if (revision < 0) revision = 0;
// if(changesetLoader.requestQueue.indexOf(revision) != -1) // if(changesetLoader.requestQueue.indexOf(revision) != -1)
@ -434,7 +437,7 @@ function loadBroadcastJS()
setTimeout(changesetLoader.loadFromQueue, 10); setTimeout(changesetLoader.loadFromQueue, 10);
} }
}, },
loadFromQueue: function () loadFromQueue: function()
{ {
var self = changesetLoader; var self = changesetLoader;
var requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null; var requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null;
@ -449,9 +452,9 @@ function loadBroadcastJS()
var granularity = request.res; var granularity = request.res;
var callback = request.callback; var callback = request.callback;
var start = request.rev; var start = request.rev;
var requestID = Math.floor(Math.random()*100000); var requestID = Math.floor(Math.random() * 100000);
/*var msg = { "component" : "timeslider", /*var msg = { "component" : "timeslider",
"type":"CHANGESET_REQ", "type":"CHANGESET_REQ",
"padId": padId, "padId": padId,
"token": token, "token": token,
@ -464,11 +467,15 @@ function loadBroadcastJS()
socket.send(msg);*/ socket.send(msg);*/
sendSocketMsg("CHANGESET_REQ",{ "start": start, "granularity": granularity, "requestID": requestID}); sendSocketMsg("CHANGESET_REQ", {
"start": start,
"granularity": granularity,
"requestID": requestID
});
self.reqCallbacks[requestID] = callback; self.reqCallbacks[requestID] = callback;
/*debugLog("loadinging revision", start, "through ajax"); /*debugLog("loadinging revision", start, "through ajax");
$.getJSON("/ep/pad/changes/" + clientVars.padIdForUrl + "?s=" + start + "&g=" + granularity, function (data, textStatus) $.getJSON("/ep/pad/changes/" + clientVars.padIdForUrl + "?s=" + start + "&g=" + granularity, function (data, textStatus)
{ {
if (textStatus !== "success") if (textStatus !== "success")
@ -481,7 +488,7 @@ function loadBroadcastJS()
setTimeout(self.loadFromQueue, 10); // load the next ajax function setTimeout(self.loadFromQueue, 10); // load the next ajax function
});*/ });*/
}, },
handleSocketResponse: function (message) handleSocketResponse: function(message)
{ {
var self = changesetLoader; var self = changesetLoader;
@ -493,7 +500,7 @@ function loadBroadcastJS()
self.handleResponse(message.data, start, granularity, callback); self.handleResponse(message.data, start, granularity, callback);
setTimeout(self.loadFromQueue, 10); setTimeout(self.loadFromQueue, 10);
}, },
handleResponse: function (data, start, granularity, callback) handleResponse: function(data, start, granularity, callback)
{ {
debugLog("response: ", data); debugLog("response: ", data);
var pool = (new AttribPool()).fromJsonable(data.apool); var pool = (new AttribPool()).fromJsonable(data.apool);
@ -538,7 +545,7 @@ function loadBroadcastJS()
var authorMap = {}; var authorMap = {};
authorMap[obj.author] = obj.data; authorMap[obj.author] = obj.data;
receiveAuthorData(authorMap); receiveAuthorData(authorMap);
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function (name) BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name)
{ {
return authorData[name]; return authorData[name];
})); }));
@ -593,7 +600,7 @@ function loadBroadcastJS()
})); }));
} }
/*function setUpSocket() /*function setUpSocket()
{ {
// required for Comet // required for Comet
if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0)))
@ -654,9 +661,9 @@ function loadBroadcastJS()
{ {
if (socket) if (socket)
{ {
socket.onclosed = function () socket.onclosed = function()
{}; {};
socket.onhiccup = function () socket.onhiccup = function()
{}; {};
socket.disconnect(); socket.disconnect();
} }
@ -664,7 +671,7 @@ function loadBroadcastJS()
setChannelState("DISCONNECTED", reason); setChannelState("DISCONNECTED", reason);
} }
/*window['onloadFuncts'] = []; /*window['onloadFuncts'] = [];
window.onload = function () window.onload = function ()
{ {
window['isloaded'] = true; window['isloaded'] = true;
@ -677,7 +684,7 @@ function loadBroadcastJS()
// to start upon window load, just push a function onto this array // to start upon window load, just push a function onto this array
//window['onloadFuncts'].push(setUpSocket); //window['onloadFuncts'].push(setUpSocket);
//window['onloadFuncts'].push(function () //window['onloadFuncts'].push(function ()
fireWhenAllScriptsAreLoaded.push(function () fireWhenAllScriptsAreLoaded.push(function()
{ {
// set up the currentDivs and DOM // set up the currentDivs and DOM
padContents.currentDivs = []; padContents.currentDivs = [];
@ -694,7 +701,7 @@ function loadBroadcastJS()
// this is necessary to keep infinite loops of events firing, // this is necessary to keep infinite loops of events firing,
// since goToRevision changes the slider position // since goToRevision changes the slider position
var goToRevisionIfEnabledCount = 0; var goToRevisionIfEnabledCount = 0;
var goToRevisionIfEnabled = function () var goToRevisionIfEnabled = function()
{ {
if (goToRevisionIfEnabledCount > 0) if (goToRevisionIfEnabledCount > 0)
{ {
@ -708,9 +715,11 @@ function loadBroadcastJS()
BroadcastSlider.onSlider(goToRevisionIfEnabled); BroadcastSlider.onSlider(goToRevisionIfEnabled);
(function () (function()
{ {
for (var i = 0; i < clientVars.initialChangesets.length; i++) for (var i = 0; i < clientVars.initialChangesets.length; i++)
{ {

View file

@ -16,7 +16,6 @@
// revision info is a skip list whos entries represent a particular revision // revision info is a skip list whos entries represent a particular revision
// of the document. These revisions are connected together by various // of the document. These revisions are connected together by various
// changesets, or deltas, between any two revisions. // changesets, or deltas, between any two revisions.
var global = this; var global = this;
function loadBroadcastRevisionsJS() function loadBroadcastRevisionsJS()
@ -27,25 +26,25 @@ function loadBroadcastRevisionsJS()
this.changesets = []; this.changesets = [];
} }
Revision.prototype.addChangeset = function (destIndex, changeset, timeDelta) Revision.prototype.addChangeset = function(destIndex, changeset, timeDelta)
{ {
var changesetWrapper = { var changesetWrapper = {
deltaRev: destIndex - this.rev, deltaRev: destIndex - this.rev,
deltaTime: timeDelta, deltaTime: timeDelta,
getValue: function () getValue: function()
{ {
return changeset; return changeset;
} }
}; };
this.changesets.push(changesetWrapper); this.changesets.push(changesetWrapper);
this.changesets.sort(function (a, b) this.changesets.sort(function(a, b)
{ {
return (b.deltaRev - a.deltaRev) return (b.deltaRev - a.deltaRev)
}); });
} }
revisionInfo = {}; revisionInfo = {};
revisionInfo.addChangeset = function (fromIndex, toIndex, changeset, backChangeset, timeDelta) revisionInfo.addChangeset = function(fromIndex, toIndex, changeset, backChangeset, timeDelta)
{ {
var startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex); var startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
var endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex); var endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex);
@ -55,7 +54,7 @@ function loadBroadcastRevisionsJS()
revisionInfo.latest = clientVars.totalRevs || -1; revisionInfo.latest = clientVars.totalRevs || -1;
revisionInfo.createNew = function (index) revisionInfo.createNew = function(index)
{ {
revisionInfo[index] = new Revision(index); revisionInfo[index] = new Revision(index);
if (index > revisionInfo.latest) if (index > revisionInfo.latest)
@ -68,7 +67,7 @@ function loadBroadcastRevisionsJS()
// assuming that there is a path from fromIndex to toIndex, and that the links // assuming that there is a path from fromIndex to toIndex, and that the links
// are laid out in a skip-list format // are laid out in a skip-list format
revisionInfo.getPath = function (fromIndex, toIndex) revisionInfo.getPath = function(fromIndex, toIndex)
{ {
var changesets = []; var changesets = [];
var spans = []; var spans = [];

View file

@ -18,7 +18,7 @@ var global = this;
function loadBroadcastSliderJS() function loadBroadcastSliderJS()
{ {
(function () (function()
{ // wrap this code in its own namespace { // wrap this code in its own namespace
var sliderLength = 1000; var sliderLength = 1000;
var sliderPos = 0; var sliderPos = 0;
@ -29,7 +29,7 @@ function loadBroadcastSliderJS()
function disableSelection(element) function disableSelection(element)
{ {
element.onselectstart = function () element.onselectstart = function()
{ {
return false; return false;
}; };
@ -37,7 +37,7 @@ function loadBroadcastSliderJS()
element.style.MozUserSelect = "none"; element.style.MozUserSelect = "none";
element.style.cursor = "default"; element.style.cursor = "default";
} }
var _callSliderCallbacks = function (newval) var _callSliderCallbacks = function(newval)
{ {
sliderPos = newval; sliderPos = newval;
for (var i = 0; i < slidercallbacks.length; i++) for (var i = 0; i < slidercallbacks.length; i++)
@ -48,7 +48,9 @@ function loadBroadcastSliderJS()
var updateSliderElements = function ()
var updateSliderElements = function()
{ {
for (var i = 0; i < savedRevisions.length; i++) for (var i = 0; i < savedRevisions.length; i++)
{ {
@ -60,7 +62,9 @@ function loadBroadcastSliderJS()
var addSavedRevision = function (position, info)
var addSavedRevision = function(position, info)
{ {
var newSavedRevision = $('<div></div>'); var newSavedRevision = $('<div></div>');
newSavedRevision.addClass("star"); newSavedRevision.addClass("star");
@ -69,14 +73,14 @@ function loadBroadcastSliderJS()
newSavedRevision.css('position', 'absolute'); newSavedRevision.css('position', 'absolute');
newSavedRevision.css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1); newSavedRevision.css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1);
$("#timeslider-slider").append(newSavedRevision); $("#timeslider-slider").append(newSavedRevision);
newSavedRevision.mouseup(function (evt) newSavedRevision.mouseup(function(evt)
{ {
BroadcastSlider.setSliderPosition(position); BroadcastSlider.setSliderPosition(position);
}); });
savedRevisions.push(newSavedRevision); savedRevisions.push(newSavedRevision);
}; };
var removeSavedRevision = function (position) var removeSavedRevision = function(position)
{ {
var element = $("div.star [pos=" + position + "]"); var element = $("div.star [pos=" + position + "]");
savedRevisions.remove(element); savedRevisions.remove(element);
@ -101,7 +105,7 @@ function loadBroadcastSliderJS()
newpos = Number(newpos); newpos = Number(newpos);
if (newpos < 0 || newpos > sliderLength) return; if (newpos < 0 || newpos > sliderLength) return;
$("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)); $("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0));
$("a.tlink").map(function () $("a.tlink").map(function()
{ {
$(this).attr('href', $(this).attr('thref').replace("%revision%", newpos)); $(this).attr('href', $(this).attr('thref').replace("%revision%", newpos));
}); });
@ -146,7 +150,6 @@ function loadBroadcastSliderJS()
// just take over the whole slider screen with a reconnect message // just take over the whole slider screen with a reconnect message
function showReconnectUI() function showReconnectUI()
{ {
if (!clientVars.sliderEnabled || !clientVars.supportsSlider) if (!clientVars.sliderEnabled || !clientVars.supportsSlider)
@ -162,7 +165,7 @@ function loadBroadcastSliderJS()
$("#authorstable").empty(); $("#authorstable").empty();
var numAnonymous = 0; var numAnonymous = 0;
var numNamed = 0; var numNamed = 0;
authors.forEach(function (author) authors.forEach(function(author)
{ {
if (author.name) if (author.name)
{ {
@ -200,7 +203,7 @@ function loadBroadcastSliderJS()
setSliderPosition: setSliderPosition, setSliderPosition: setSliderPosition,
getSliderLength: getSliderLength, getSliderLength: getSliderLength,
setSliderLength: setSliderLength, setSliderLength: setSliderLength,
isSliderActive: function () isSliderActive: function()
{ {
return sliderActive; return sliderActive;
}, },
@ -244,14 +247,14 @@ function loadBroadcastSliderJS()
// assign event handlers to html UI elements after page load // assign event handlers to html UI elements after page load
//$(window).load(function () //$(window).load(function ()
fireWhenAllScriptsAreLoaded.push(function () fireWhenAllScriptsAreLoaded.push(function()
{ {
disableSelection($("#playpause_button")[0]); disableSelection($("#playpause_button")[0]);
disableSelection($("#timeslider")[0]); disableSelection($("#timeslider")[0]);
if (clientVars.sliderEnabled && clientVars.supportsSlider) if (clientVars.sliderEnabled && clientVars.supportsSlider)
{ {
$(document).keyup(function (e) $(document).keyup(function(e)
{ {
var code = -1; var code = -1;
if (!e) var e = window.event; if (!e) var e = window.event;
@ -297,12 +300,12 @@ function loadBroadcastSliderJS()
}); });
} }
$(window).resize(function () $(window).resize(function()
{ {
updateSliderElements(); updateSliderElements();
}); });
$("#ui-slider-bar").mousedown(function (evt) $("#ui-slider-bar").mousedown(function(evt)
{ {
setSliderPosition(Math.floor((evt.clientX - $("#ui-slider-bar").offset().left) * sliderLength / 742)); 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").css('left', (evt.clientX - $("#ui-slider-bar").offset().left));
@ -310,13 +313,13 @@ function loadBroadcastSliderJS()
}); });
// Slider dragging // Slider dragging
$("#ui-slider-handle").mousedown(function (evt) $("#ui-slider-handle").mousedown(function(evt)
{ {
this.startLoc = evt.clientX; this.startLoc = evt.clientX;
this.currentLoc = parseInt($(this).css('left')); this.currentLoc = parseInt($(this).css('left'));
var self = this; var self = this;
sliderActive = true; sliderActive = true;
$(document).mousemove(function (evt2) $(document).mousemove(function(evt2)
{ {
$(self).css('pointer', 'move') $(self).css('pointer', 'move')
var newloc = self.currentLoc + (evt2.clientX - self.startLoc); var newloc = self.currentLoc + (evt2.clientX - self.startLoc);
@ -326,7 +329,7 @@ function loadBroadcastSliderJS()
$(self).css('left', newloc); $(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))) 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).mouseup(function(evt2)
{ {
$(document).unbind('mousemove'); $(document).unbind('mousemove');
$(document).unbind('mouseup'); $(document).unbind('mouseup');
@ -342,18 +345,18 @@ function loadBroadcastSliderJS()
}) })
// play/pause toggling // play/pause toggling
$("#playpause_button").mousedown(function (evt) $("#playpause_button").mousedown(function(evt)
{ {
var self = this; var self = this;
$(self).css('background-image', 'url(/static/img/crushed_button_depressed.png)'); $(self).css('background-image', 'url(/static/img/crushed_button_depressed.png)');
$(self).mouseup(function (evt2) $(self).mouseup(function(evt2)
{ {
$(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)'); $(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)');
$(self).unbind('mouseup'); $(self).unbind('mouseup');
BroadcastSlider.playpause(); BroadcastSlider.playpause();
}); });
$(document).mouseup(function (evt2) $(document).mouseup(function(evt2)
{ {
$(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)'); $(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)');
$(document).unbind('mouseup'); $(document).unbind('mouseup');
@ -361,7 +364,7 @@ function loadBroadcastSliderJS()
}); });
// next/prev saved revision and changeset // next/prev saved revision and changeset
$('.stepper').mousedown(function (evt) $('.stepper').mousedown(function(evt)
{ {
var self = this; var self = this;
var origcss = $(self).css('background-position'); var origcss = $(self).css('background-position');
@ -378,7 +381,7 @@ function loadBroadcastSliderJS()
$(self).css('background-position', newcss) $(self).css('background-position', newcss)
$(self).mouseup(function (evt2) $(self).mouseup(function(evt2)
{ {
$(self).css('background-position', origcss); $(self).css('background-position', origcss);
$(self).unbind('mouseup'); $(self).unbind('mouseup');
@ -412,7 +415,7 @@ function loadBroadcastSliderJS()
setSliderPosition(nextStar); setSliderPosition(nextStar);
} }
}); });
$(document).mouseup(function (evt2) $(document).mouseup(function(evt2)
{ {
$(self).css('background-position', origcss); $(self).css('background-position', origcss);
$(self).unbind('mouseup'); $(self).unbind('mouseup');
@ -456,7 +459,7 @@ function loadBroadcastSliderJS()
$("#timeslider").show(); $("#timeslider").show();
setSliderLength(clientVars.totalRevs); setSliderLength(clientVars.totalRevs);
setSliderPosition(clientVars.revNum); setSliderPosition(clientVars.revNum);
clientVars.savedRevisions.forEach(function (revision) clientVars.savedRevisions.forEach(function(revision)
{ {
addSavedRevision(revision.revNum, revision); addSavedRevision(revision.revNum, revision);
}) })
@ -482,7 +485,7 @@ function loadBroadcastSliderJS()
}); });
})(); })();
BroadcastSlider.onSlider(function (loc) BroadcastSlider.onSlider(function(loc)
{ {
$("#viewlatest").html(loc == BroadcastSlider.getSliderLength() ? "Viewing latest content" : "View latest content"); $("#viewlatest").html(loc == BroadcastSlider.getSliderLength() ? "Viewing latest content" : "View latest content");
}) })

View file

@ -15,7 +15,8 @@
*/ */
function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) { function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
{
// latest official text from server // latest official text from server
var baseAText = Changeset.makeAText("\n"); var baseAText = Changeset.makeAText("\n");
@ -34,16 +35,22 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) {
var changeCallback = null; var changeCallback = null;
var changeCallbackTimeout = null; var changeCallbackTimeout = null;
function setChangeCallbackTimeout() {
function setChangeCallbackTimeout()
{
// can call this multiple times per call-stack, because // can call this multiple times per call-stack, because
// we only schedule a call to changeCallback if it exists // we only schedule a call to changeCallback if it exists
// and if there isn't a timeout already scheduled. // and if there isn't a timeout already scheduled.
if (changeCallback && changeCallbackTimeout === null) { if (changeCallback && changeCallbackTimeout === null)
changeCallbackTimeout = scheduler.setTimeout(function() { {
try { changeCallbackTimeout = scheduler.setTimeout(function()
{
try
{
changeCallback(); changeCallback();
} }
finally { finally
{
changeCallbackTimeout = null; changeCallbackTimeout = null;
} }
}, 0); }, 0);
@ -52,43 +59,56 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) {
var self; var self;
return self = { return self = {
isTracking: function() { return tracking; }, isTracking: function()
setBaseText: function(text) { {
return tracking;
},
setBaseText: function(text)
{
self.setBaseAttributedText(Changeset.makeAText(text), null); self.setBaseAttributedText(Changeset.makeAText(text), null);
}, },
setBaseAttributedText: function(atext, apoolJsonObj) { setBaseAttributedText: function(atext, apoolJsonObj)
aceCallbacksProvider.withCallbacks("setBaseText", function(callbacks) { {
aceCallbacksProvider.withCallbacks("setBaseText", function(callbacks)
{
tracking = true; tracking = true;
baseAText = Changeset.cloneAText(atext); baseAText = Changeset.cloneAText(atext);
if (apoolJsonObj) { if (apoolJsonObj)
{
var wireApool = (new AttribPool()).fromJsonable(apoolJsonObj); var wireApool = (new AttribPool()).fromJsonable(apoolJsonObj);
baseAText.attribs = Changeset.moveOpsToNewPool(baseAText.attribs, wireApool, apool); baseAText.attribs = Changeset.moveOpsToNewPool(baseAText.attribs, wireApool, apool);
} }
submittedChangeset = null; submittedChangeset = null;
userChangeset = Changeset.identity(atext.text.length); userChangeset = Changeset.identity(atext.text.length);
applyingNonUserChanges = true; applyingNonUserChanges = true;
try { try
{
callbacks.setDocumentAttributedText(atext); callbacks.setDocumentAttributedText(atext);
} }
finally { finally
{
applyingNonUserChanges = false; applyingNonUserChanges = false;
} }
}); });
}, },
composeUserChangeset: function(c) { composeUserChangeset: function(c)
if (! tracking) return; {
if (!tracking) return;
if (applyingNonUserChanges) return; if (applyingNonUserChanges) return;
if (Changeset.isIdentity(c)) return; if (Changeset.isIdentity(c)) return;
userChangeset = Changeset.compose(userChangeset, c, apool); userChangeset = Changeset.compose(userChangeset, c, apool);
setChangeCallbackTimeout(); setChangeCallbackTimeout();
}, },
applyChangesToBase: function (c, optAuthor, apoolJsonObj) { applyChangesToBase: function(c, optAuthor, apoolJsonObj)
if (! tracking) return; {
if (!tracking) return;
aceCallbacksProvider.withCallbacks("applyChangesToBase", function(callbacks) { aceCallbacksProvider.withCallbacks("applyChangesToBase", function(callbacks)
{
if (apoolJsonObj) { if (apoolJsonObj)
{
var wireApool = (new AttribPool()).fromJsonable(apoolJsonObj); var wireApool = (new AttribPool()).fromJsonable(apoolJsonObj);
c = Changeset.moveOpsToNewPool(c, wireApool, apool); c = Changeset.moveOpsToNewPool(c, wireApool, apool);
} }
@ -96,7 +116,8 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) {
baseAText = Changeset.applyToAText(c, baseAText, apool); baseAText = Changeset.applyToAText(c, baseAText, apool);
var c2 = c; var c2 = c;
if (submittedChangeset) { if (submittedChangeset)
{
var oldSubmittedChangeset = submittedChangeset; var oldSubmittedChangeset = submittedChangeset;
submittedChangeset = Changeset.follow(c, oldSubmittedChangeset, false, apool); submittedChangeset = Changeset.follow(c, oldSubmittedChangeset, false, apool);
c2 = Changeset.follow(oldSubmittedChangeset, c, true, apool); c2 = Changeset.follow(oldSubmittedChangeset, c, true, apool);
@ -105,53 +126,63 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) {
var preferInsertingAfterUserChanges = true; var preferInsertingAfterUserChanges = true;
var oldUserChangeset = userChangeset; var oldUserChangeset = userChangeset;
userChangeset = Changeset.follow(c2, oldUserChangeset, preferInsertingAfterUserChanges, apool); userChangeset = Changeset.follow(c2, oldUserChangeset, preferInsertingAfterUserChanges, apool);
var postChange = var postChange = Changeset.follow(oldUserChangeset, c2, !preferInsertingAfterUserChanges, apool);
Changeset.follow(oldUserChangeset, c2, ! preferInsertingAfterUserChanges, apool);
var preferInsertionAfterCaret = true; //(optAuthor && optAuthor > thisAuthor); var preferInsertionAfterCaret = true; //(optAuthor && optAuthor > thisAuthor);
applyingNonUserChanges = true; applyingNonUserChanges = true;
try { try
{
callbacks.applyChangesetToDocument(postChange, preferInsertionAfterCaret); callbacks.applyChangesetToDocument(postChange, preferInsertionAfterCaret);
} }
finally { finally
{
applyingNonUserChanges = false; applyingNonUserChanges = false;
} }
}); });
}, },
prepareUserChangeset: function() { prepareUserChangeset: function()
{
// If there are user changes to submit, 'changeset' will be the // If there are user changes to submit, 'changeset' will be the
// changeset, else it will be null. // changeset, else it will be null.
var toSubmit; var toSubmit;
if (submittedChangeset) { if (submittedChangeset)
{
// submission must have been canceled, prepare new changeset // submission must have been canceled, prepare new changeset
// that includes old submittedChangeset // that includes old submittedChangeset
toSubmit = Changeset.compose(submittedChangeset, userChangeset, apool); toSubmit = Changeset.compose(submittedChangeset, userChangeset, apool);
} }
else { else
{
if (Changeset.isIdentity(userChangeset)) toSubmit = null; if (Changeset.isIdentity(userChangeset)) toSubmit = null;
else toSubmit = userChangeset; else toSubmit = userChangeset;
} }
var cs = null; var cs = null;
if (toSubmit) { if (toSubmit)
{
submittedChangeset = toSubmit; submittedChangeset = toSubmit;
userChangeset = Changeset.identity(Changeset.newLen(toSubmit)); userChangeset = Changeset.identity(Changeset.newLen(toSubmit));
cs = toSubmit; cs = toSubmit;
} }
var wireApool = null; var wireApool = null;
if (cs) { if (cs)
{
var forWire = Changeset.prepareForWire(cs, apool); var forWire = Changeset.prepareForWire(cs, apool);
wireApool = forWire.pool.toJsonable(); wireApool = forWire.pool.toJsonable();
cs = forWire.translated; cs = forWire.translated;
} }
var data = { changeset: cs, apool: wireApool }; var data = {
changeset: cs,
apool: wireApool
};
return data; return data;
}, },
applyPreparedChangesetToBase: function() { applyPreparedChangesetToBase: function()
if (! submittedChangeset) { {
if (!submittedChangeset)
{
// violation of protocol; use prepareUserChangeset first // violation of protocol; use prepareUserChangeset first
throw new Error("applySubmittedChangesToBase: no submitted changes to apply"); throw new Error("applySubmittedChangesToBase: no submitted changes to apply");
} }
@ -159,11 +190,13 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) {
baseAText = Changeset.applyToAText(submittedChangeset, baseAText, apool); baseAText = Changeset.applyToAText(submittedChangeset, baseAText, apool);
submittedChangeset = null; submittedChangeset = null;
}, },
setUserChangeNotificationCallback: function (callback) { setUserChangeNotificationCallback: function(callback)
{
changeCallback = callback; changeCallback = callback;
}, },
hasUncommittedChanges: function() { hasUncommittedChanges: function()
return !!(submittedChangeset || (! Changeset.isIdentity(userChangeset))); {
return !!(submittedChangeset || (!Changeset.isIdentity(userChangeset)));
} }
}; };

View file

@ -14,14 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
$(window).bind("load", function() { $(window).bind("load", function()
{
getCollabClient.windowLoaded = true; getCollabClient.windowLoaded = true;
}); });
/** Call this when the document is ready, and a new Ace2Editor() has been created and inited. /** Call this when the document is ready, and a new Ace2Editor() has been created and inited.
ACE's ready callback does not need to have fired yet. ACE's ready callback does not need to have fired yet.
"serverVars" are from calling doc.getCollabClientVars() on the server. */ "serverVars" are from calling doc.getCollabClientVars() on the server. */
function getCollabClient(ace2editor, serverVars, initialUserInfo, options) { function getCollabClient(ace2editor, serverVars, initialUserInfo, options)
{
var editor = ace2editor; var editor = ace2editor;
var rev = serverVars.rev; var rev = serverVars.rev;
@ -53,37 +55,56 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
tellAceActiveAuthorInfo(initialUserInfo); tellAceActiveAuthorInfo(initialUserInfo);
var callbacks = { var callbacks = {
onUserJoin: function() {}, onUserJoin: function()
onUserLeave: function() {}, {},
onUpdateUserInfo: function() {}, onUserLeave: function()
onChannelStateChange: function() {}, {},
onClientMessage: function() {}, onUpdateUserInfo: function()
onInternalAction: function() {}, {},
onConnectionTrouble: function() {}, onChannelStateChange: function()
onServerMessage: function() {} {},
onClientMessage: function()
{},
onInternalAction: function()
{},
onConnectionTrouble: function()
{},
onServerMessage: function()
{}
}; };
$(window).bind("unload", function() { $(window).bind("unload", function()
if (socket) { {
/*socket.onclosed = function() {}; if (socket)
{
/*socket.onclosed = function() {};
socket.onhiccup = function() {}; socket.onhiccup = function() {};
socket.disconnect(true);*/ socket.disconnect(true);*/
socket.disconnect(); socket.disconnect();
} }
}); });
if ($.browser.mozilla) { if ($.browser.mozilla)
{
// Prevent "escape" from taking effect and canceling a comet connection; // Prevent "escape" from taking effect and canceling a comet connection;
// doesn't work if focus is on an iframe. // doesn't work if focus is on an iframe.
$(window).bind("keydown", function(evt) { if (evt.which == 27) { evt.preventDefault() } }); $(window).bind("keydown", function(evt)
{
if (evt.which == 27)
{
evt.preventDefault()
}
});
} }
editor.setProperty("userAuthor", userId); editor.setProperty("userAuthor", userId);
editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool); editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool);
editor.setUserChangeNotificationCallback(wrapRecordingErrors("handleUserChanges", handleUserChanges)); editor.setUserChangeNotificationCallback(wrapRecordingErrors("handleUserChanges", handleUserChanges));
function abandonConnection(reason) { function abandonConnection(reason)
if (socket) { {
/*socket.onclosed = function() {}; if (socket)
{
/*socket.onclosed = function() {};
socket.onhiccup = function() {};*/ socket.onhiccup = function() {};*/
socket.disconnect(); socket.disconnect();
} }
@ -91,77 +112,87 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
setChannelState("DISCONNECTED", reason); setChannelState("DISCONNECTED", reason);
} }
function dmesg(str) { function dmesg(str)
if (typeof window.ajlog == "string") window.ajlog += str+'\n'; {
if (typeof window.ajlog == "string") window.ajlog += str + '\n';
debugMessages.push(str); debugMessages.push(str);
} }
function handleUserChanges() { function handleUserChanges()
if ((! socket) || channelState == "CONNECTING") { {
if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000)) { if ((!socket) || channelState == "CONNECTING")
{
if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000))
{
abandonConnection("initsocketfail"); // give up abandonConnection("initsocketfail"); // give up
} }
else { else
{
// check again in a bit // check again in a bit
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 1000);
1000);
} }
return; return;
} }
var t = (+new Date()); var t = (+new Date());
if (state != "IDLE") { if (state != "IDLE")
if (state == "COMMITTING" && (t - lastCommitTime) > 20000) { {
if (state == "COMMITTING" && (t - lastCommitTime) > 20000)
{
// a commit is taking too long // a commit is taking too long
appLevelDisconnectReason = "slowcommit"; appLevelDisconnectReason = "slowcommit";
socket.disconnect(); socket.disconnect();
} }
else if (state == "COMMITTING" && (t - lastCommitTime) > 5000) { else if (state == "COMMITTING" && (t - lastCommitTime) > 5000)
{
callbacks.onConnectionTrouble("SLOW"); callbacks.onConnectionTrouble("SLOW");
} }
else { else
{
// run again in a few seconds, to detect a disconnect // run again in a few seconds, to detect a disconnect
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000);
3000);
} }
return; return;
} }
var earliestCommit = lastCommitTime + 500; var earliestCommit = lastCommitTime + 500;
if (t < earliestCommit) { if (t < earliestCommit)
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), {
earliestCommit - t); setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), earliestCommit - t);
return; return;
} }
var sentMessage = false; var sentMessage = false;
var userChangesData = editor.prepareUserChangeset(); var userChangesData = editor.prepareUserChangeset();
if (userChangesData.changeset) { if (userChangesData.changeset)
{
lastCommitTime = t; lastCommitTime = t;
state = "COMMITTING"; state = "COMMITTING";
stateMessage = {type:"USER_CHANGES", baseRev:rev, stateMessage = {
changeset:userChangesData.changeset, type: "USER_CHANGES",
apool: userChangesData.apool }; baseRev: rev,
changeset: userChangesData.changeset,
apool: userChangesData.apool
};
stateMessageSocketId = socketId; stateMessageSocketId = socketId;
sendMessage(stateMessage); sendMessage(stateMessage);
sentMessage = true; sentMessage = true;
callbacks.onInternalAction("commitPerformed"); callbacks.onInternalAction("commitPerformed");
} }
if (sentMessage) { if (sentMessage)
{
// run again in a few seconds, to detect a disconnect // run again in a few seconds, to detect a disconnect
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000);
3000);
} }
} }
function getStats() { function getStats()
{
var stats = {}; var stats = {};
stats.screen = [$(window).width(), $(window).height(), stats.screen = [$(window).width(), $(window).height(), window.screen.availWidth, window.screen.availHeight, window.screen.width, window.screen.height].join(',');
window.screen.availWidth, window.screen.availHeight,
window.screen.width, window.screen.height].join(',');
stats.ip = serverVars.clientIp; stats.ip = serverVars.clientIp;
stats.useragent = serverVars.clientAgent; stats.useragent = serverVars.clientAgent;
@ -172,14 +203,13 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
{ {
//oldSocketId = String(Math.floor(Math.random()*1e12)); //oldSocketId = String(Math.floor(Math.random()*1e12));
//socketId = String(Math.floor(Math.random()*1e12)); //socketId = String(Math.floor(Math.random()*1e12));
/*socket = new io.Socket();
/*socket = new io.Socket();
socket.connect();*/ socket.connect();*/
//socket.on('connect', function(){ //socket.on('connect', function(){
hiccupCount = 0; hiccupCount = 0;
setChannelState("CONNECTED"); setChannelState("CONNECTED");
/*var msg = { type:"CLIENT_READY", roomType:'padpage', /*var msg = { type:"CLIENT_READY", roomType:'padpage',
roomName:'padpage/'+globalPadId, roomName:'padpage/'+globalPadId,
data: { data: {
lastRev:rev, lastRev:rev,
@ -194,18 +224,18 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
initialStartConnectTime = +new Date(); initialStartConnectTime = +new Date();
// }); // });
/*socket.on('message', function(obj){
/*socket.on('message', function(obj){
if(window.console) if(window.console)
console.log(obj); console.log(obj);
handleMessageFromServer(obj); handleMessageFromServer(obj);
});*/ });*/
socket.on('disconnect', function(obj){ socket.on('disconnect', function(obj)
{
handleSocketClosed(true); handleSocketClosed(true);
}); });
/*var success = false; /*var success = false;
callCatchingErrors("setUpSocket", function() { callCatchingErrors("setUpSocket", function() {
appLevelDisconnectReason = null; appLevelDisconnectReason = null;
@ -242,43 +272,62 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
abandonConnection("initsocketfail"); abandonConnection("initsocketfail");
}*/ }*/
} }
function setUpSocketWhenWindowLoaded() {
if (getCollabClient.windowLoaded) { function setUpSocketWhenWindowLoaded()
{
if (getCollabClient.windowLoaded)
{
setUpSocket(); setUpSocket();
} }
else { else
{
setTimeout(setUpSocketWhenWindowLoaded, 200); setTimeout(setUpSocketWhenWindowLoaded, 200);
} }
} }
setTimeout(setUpSocketWhenWindowLoaded, 0); setTimeout(setUpSocketWhenWindowLoaded, 0);
var hiccupCount = 0; var hiccupCount = 0;
function handleCometHiccup(params) {
dmesg("HICCUP (connected:"+(!!params.connected)+")"); function handleCometHiccup(params)
{
dmesg("HICCUP (connected:" + ( !! params.connected) + ")");
var connectedNow = params.connected; var connectedNow = params.connected;
if (! connectedNow) { if (!connectedNow)
{
hiccupCount++; hiccupCount++;
// skip first "cut off from server" notification // skip first "cut off from server" notification
if (hiccupCount > 1) { if (hiccupCount > 1)
{
setChannelState("RECONNECTING"); setChannelState("RECONNECTING");
} }
} }
else { else
{
hiccupCount = 0; hiccupCount = 0;
setChannelState("CONNECTED"); setChannelState("CONNECTED");
} }
} }
function sendMessage(msg) { function sendMessage(msg)
socket.json.send({type: "COLLABROOM", component: "pad", data: msg}); {
socket.json.send(
{
type: "COLLABROOM",
component: "pad",
data: msg
});
} }
function wrapRecordingErrors(catcher, func) { function wrapRecordingErrors(catcher, func)
return function() { {
try { return function()
{
try
{
return func.apply(this, Array.prototype.slice.call(arguments)); return func.apply(this, Array.prototype.slice.call(arguments));
} }
catch (e) { catch (e)
{
caughtErrors.push(e); caughtErrors.push(e);
caughtErrorCatchers.push(catcher); caughtErrorCatchers.push(catcher);
caughtErrorTimes.push(+new Date()); caughtErrorTimes.push(+new Date());
@ -288,147 +337,195 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
}; };
} }
function callCatchingErrors(catcher, func) { function callCatchingErrors(catcher, func)
try { {
try
{
wrapRecordingErrors(catcher, func)(); wrapRecordingErrors(catcher, func)();
} }
catch (e) { /*absorb*/ } catch (e)
{ /*absorb*/
}
} }
function handleMessageFromServer(evt) { function handleMessageFromServer(evt)
if(window.console) {
console.log(evt); if (window.console) console.log(evt);
if (! socket) return; if (!socket) return;
if (! evt.data) return; if (!evt.data) return;
var wrapper = evt; var wrapper = evt;
if(wrapper.type != "COLLABROOM") return; if (wrapper.type != "COLLABROOM") return;
var msg = wrapper.data; var msg = wrapper.data;
if (msg.type == "NEW_CHANGES") { if (msg.type == "NEW_CHANGES")
{
var newRev = msg.newRev; var newRev = msg.newRev;
var changeset = msg.changeset; var changeset = msg.changeset;
var author = (msg.author || ''); var author = (msg.author || '');
var apool = msg.apool; var apool = msg.apool;
if (newRev != (rev+1)) { if (newRev != (rev + 1))
dmesg("bad message revision on NEW_CHANGES: "+newRev+" not "+(rev+1)); {
dmesg("bad message revision on NEW_CHANGES: " + newRev + " not " + (rev + 1));
socket.disconnect(); socket.disconnect();
return; return;
} }
rev = newRev; rev = newRev;
editor.applyChangesToBase(changeset, author, apool); editor.applyChangesToBase(changeset, author, apool);
} }
else if (msg.type == "ACCEPT_COMMIT") { else if (msg.type == "ACCEPT_COMMIT")
{
var newRev = msg.newRev; var newRev = msg.newRev;
if (newRev != (rev+1)) { if (newRev != (rev + 1))
dmesg("bad message revision on ACCEPT_COMMIT: "+newRev+" not "+(rev+1)); {
dmesg("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (rev + 1));
socket.disconnect(); socket.disconnect();
return; return;
} }
rev = newRev; rev = newRev;
editor.applyPreparedChangesetToBase(); editor.applyPreparedChangesetToBase();
setStateIdle(); setStateIdle();
callCatchingErrors("onInternalAction", function() { callCatchingErrors("onInternalAction", function()
{
callbacks.onInternalAction("commitAcceptedByServer"); callbacks.onInternalAction("commitAcceptedByServer");
}); });
callCatchingErrors("onConnectionTrouble", function() { callCatchingErrors("onConnectionTrouble", function()
{
callbacks.onConnectionTrouble("OK"); callbacks.onConnectionTrouble("OK");
}); });
handleUserChanges(); handleUserChanges();
} }
else if (msg.type == "NO_COMMIT_PENDING") { else if (msg.type == "NO_COMMIT_PENDING")
if (state == "COMMITTING") { {
if (state == "COMMITTING")
{
// server missed our commit message; abort that commit // server missed our commit message; abort that commit
setStateIdle(); setStateIdle();
handleUserChanges(); handleUserChanges();
} }
} }
else if (msg.type == "USER_NEWINFO") { else if (msg.type == "USER_NEWINFO")
{
var userInfo = msg.userInfo; var userInfo = msg.userInfo;
var id = userInfo.userId; var id = userInfo.userId;
if (userSet[id]) { if (userSet[id])
{
userSet[id] = userInfo; userSet[id] = userInfo;
callbacks.onUpdateUserInfo(userInfo); callbacks.onUpdateUserInfo(userInfo);
dmesgUsers(); dmesgUsers();
} }
else { else
{
userSet[id] = userInfo; userSet[id] = userInfo;
callbacks.onUserJoin(userInfo); callbacks.onUserJoin(userInfo);
dmesgUsers(); dmesgUsers();
} }
tellAceActiveAuthorInfo(userInfo); tellAceActiveAuthorInfo(userInfo);
} }
else if (msg.type == "USER_LEAVE") { else if (msg.type == "USER_LEAVE")
{
var userInfo = msg.userInfo; var userInfo = msg.userInfo;
var id = userInfo.userId; var id = userInfo.userId;
if (userSet[id]) { if (userSet[id])
{
delete userSet[userInfo.userId]; delete userSet[userInfo.userId];
fadeAceAuthorInfo(userInfo); fadeAceAuthorInfo(userInfo);
callbacks.onUserLeave(userInfo); callbacks.onUserLeave(userInfo);
dmesgUsers(); dmesgUsers();
} }
} }
else if (msg.type == "DISCONNECT_REASON") { else if (msg.type == "DISCONNECT_REASON")
{
appLevelDisconnectReason = msg.reason; appLevelDisconnectReason = msg.reason;
} }
else if (msg.type == "CLIENT_MESSAGE") { else if (msg.type == "CLIENT_MESSAGE")
{
callbacks.onClientMessage(msg.payload); callbacks.onClientMessage(msg.payload);
} }
else if (msg.type == "SERVER_MESSAGE") { else if (msg.type == "SERVER_MESSAGE")
{
callbacks.onServerMessage(msg.payload); callbacks.onServerMessage(msg.payload);
} }
} }
function updateUserInfo(userInfo) {
function updateUserInfo(userInfo)
{
userInfo.userId = userId; userInfo.userId = userId;
userSet[userId] = userInfo; userSet[userId] = userInfo;
tellAceActiveAuthorInfo(userInfo); tellAceActiveAuthorInfo(userInfo);
if (! socket) return; if (!socket) return;
sendMessage({type: "USERINFO_UPDATE", userInfo:userInfo}); sendMessage(
{
type: "USERINFO_UPDATE",
userInfo: userInfo
});
} }
function tellAceActiveAuthorInfo(userInfo) { function tellAceActiveAuthorInfo(userInfo)
{
tellAceAuthorInfo(userInfo.userId, userInfo.colorId); tellAceAuthorInfo(userInfo.userId, userInfo.colorId);
} }
function tellAceAuthorInfo(userId, colorId, inactive) {
if (colorId || (typeof colorId) == "number") { function tellAceAuthorInfo(userId, colorId, inactive)
{
if (colorId || (typeof colorId) == "number")
{
colorId = Number(colorId); colorId = Number(colorId);
if (options && options.colorPalette && options.colorPalette[colorId]) { if (options && options.colorPalette && options.colorPalette[colorId])
{
var cssColor = options.colorPalette[colorId]; var cssColor = options.colorPalette[colorId];
if (inactive) { if (inactive)
editor.setAuthorInfo(userId, {bgcolor: cssColor, fade: 0.5}); {
editor.setAuthorInfo(userId, {
bgcolor: cssColor,
fade: 0.5
});
} }
else { else
editor.setAuthorInfo(userId, {bgcolor: cssColor}); {
editor.setAuthorInfo(userId, {
bgcolor: cssColor
});
} }
} }
} }
} }
function fadeAceAuthorInfo(userInfo) {
function fadeAceAuthorInfo(userInfo)
{
tellAceAuthorInfo(userInfo.userId, userInfo.colorId, true); tellAceAuthorInfo(userInfo.userId, userInfo.colorId, true);
} }
function getConnectedUsers() { function getConnectedUsers()
{
return valuesArray(userSet); return valuesArray(userSet);
} }
function tellAceAboutHistoricalAuthors(hadata) { function tellAceAboutHistoricalAuthors(hadata)
for(var author in hadata) { {
for (var author in hadata)
{
var data = hadata[author]; var data = hadata[author];
if (! userSet[author]) { if (!userSet[author])
{
tellAceAuthorInfo(author, data.colorId, true); tellAceAuthorInfo(author, data.colorId, true);
} }
} }
} }
function dmesgUsers() { function dmesgUsers()
{
//pad.dmesg($.map(getConnectedUsers(), function(u) { return u.userId.slice(-2); }).join(',')); //pad.dmesg($.map(getConnectedUsers(), function(u) { return u.userId.slice(-2); }).join(','));
} }
function handleSocketClosed(params) { function handleSocketClosed(params)
{
socket = null; socket = null;
$.each(keys(userSet), function() { $.each(keys(userSet), function()
{
var uid = String(this); var uid = String(this);
if (uid != userId) { if (uid != userId)
{
var userInfo = userSet[uid]; var userInfo = userSet[uid];
delete userSet[uid]; delete userSet[uid];
callbacks.onUserLeave(userInfo); callbacks.onUserLeave(userInfo);
@ -438,120 +535,201 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
var reason = appLevelDisconnectReason || params.reason; var reason = appLevelDisconnectReason || params.reason;
var shouldReconnect = params.reconnect; var shouldReconnect = params.reconnect;
if (shouldReconnect) { if (shouldReconnect)
{
// determine if this is a tight reconnect loop due to weird connectivity problems // determine if this is a tight reconnect loop due to weird connectivity problems
reconnectTimes.push(+new Date()); reconnectTimes.push(+new Date());
var TOO_MANY_RECONNECTS = 8; var TOO_MANY_RECONNECTS = 8;
var TOO_SHORT_A_TIME_MS = 10000; var TOO_SHORT_A_TIME_MS = 10000;
if (reconnectTimes.length >= TOO_MANY_RECONNECTS && if (reconnectTimes.length >= TOO_MANY_RECONNECTS && ((+new Date()) - reconnectTimes[reconnectTimes.length - TOO_MANY_RECONNECTS]) < TOO_SHORT_A_TIME_MS)
((+new Date()) - reconnectTimes[reconnectTimes.length-TOO_MANY_RECONNECTS]) < {
TOO_SHORT_A_TIME_MS) {
setChannelState("DISCONNECTED", "looping"); setChannelState("DISCONNECTED", "looping");
} }
else { else
{
setChannelState("RECONNECTING", reason); setChannelState("RECONNECTING", reason);
setUpSocket(); setUpSocket();
} }
} }
else { else
{
setChannelState("DISCONNECTED", reason); setChannelState("DISCONNECTED", reason);
} }
} }
function setChannelState(newChannelState, moreInfo) { function setChannelState(newChannelState, moreInfo)
if (newChannelState != channelState) { {
if (newChannelState != channelState)
{
channelState = newChannelState; channelState = newChannelState;
callbacks.onChannelStateChange(channelState, moreInfo); callbacks.onChannelStateChange(channelState, moreInfo);
} }
} }
function keys(obj) { function keys(obj)
{
var array = []; var array = [];
$.each(obj, function (k, v) { array.push(k); }); $.each(obj, function(k, v)
{
array.push(k);
});
return array; return array;
} }
function valuesArray(obj) {
function valuesArray(obj)
{
var array = []; var array = [];
$.each(obj, function (k, v) { array.push(v); }); $.each(obj, function(k, v)
{
array.push(v);
});
return array; return array;
} }
// We need to present a working interface even before the socket // We need to present a working interface even before the socket
// is connected for the first time. // is connected for the first time.
var deferredActions = []; var deferredActions = [];
function defer(func, tag) {
return function() { function defer(func, tag)
{
return function()
{
var that = this; var that = this;
var args = arguments; var args = arguments;
function action() {
function action()
{
func.apply(that, args); func.apply(that, args);
} }
action.tag = tag; action.tag = tag;
if (channelState == "CONNECTING") { if (channelState == "CONNECTING")
{
deferredActions.push(action); deferredActions.push(action);
} }
else { else
{
action(); action();
} }
} }
} }
function doDeferredActions(tag) {
function doDeferredActions(tag)
{
var newArray = []; var newArray = [];
for(var i=0;i<deferredActions.length;i++) { for (var i = 0; i < deferredActions.length; i++)
{
var a = deferredActions[i]; var a = deferredActions[i];
if ((!tag) || (tag == a.tag)) { if ((!tag) || (tag == a.tag))
{
a(); a();
} }
else { else
{
newArray.push(a); newArray.push(a);
} }
} }
deferredActions = newArray; deferredActions = newArray;
} }
function sendClientMessage(msg) { function sendClientMessage(msg)
sendMessage({ type: "CLIENT_MESSAGE", payload: msg }); {
sendMessage(
{
type: "CLIENT_MESSAGE",
payload: msg
});
} }
function getCurrentRevisionNumber() { function getCurrentRevisionNumber()
{
return rev; return rev;
} }
function getDiagnosticInfo() { function getDiagnosticInfo()
{
var maxCaughtErrors = 3; var maxCaughtErrors = 3;
var maxAceErrors = 3; var maxAceErrors = 3;
var maxDebugMessages = 50; var maxDebugMessages = 50;
var longStringCutoff = 500; var longStringCutoff = 500;
function trunc(str) { function trunc(str)
{
return String(str).substring(0, longStringCutoff); return String(str).substring(0, longStringCutoff);
} }
var info = { errors: {length: 0} }; var info = {
function addError(e, catcher, time) { errors: {
var error = {catcher:catcher}; length: 0
}
};
function addError(e, catcher, time)
{
var error = {
catcher: catcher
};
if (time) error.time = time; if (time) error.time = time;
// a little over-cautious? // a little over-cautious?
try { if (e.description) error.description = e.description; } catch (x) {} try
try { if (e.fileName) error.fileName = e.fileName; } catch (x) {} {
try { if (e.lineNumber) error.lineNumber = e.lineNumber; } catch (x) {} if (e.description) error.description = e.description;
try { if (e.message) error.message = e.message; } catch (x) {} }
try { if (e.name) error.name = e.name; } catch (x) {} catch (x)
try { if (e.number) error.number = e.number; } catch (x) {} {}
try { if (e.stack) error.stack = trunc(e.stack); } catch (x) {} try
{
if (e.fileName) error.fileName = e.fileName;
}
catch (x)
{}
try
{
if (e.lineNumber) error.lineNumber = e.lineNumber;
}
catch (x)
{}
try
{
if (e.message) error.message = e.message;
}
catch (x)
{}
try
{
if (e.name) error.name = e.name;
}
catch (x)
{}
try
{
if (e.number) error.number = e.number;
}
catch (x)
{}
try
{
if (e.stack) error.stack = trunc(e.stack);
}
catch (x)
{}
info.errors[info.errors.length] = error; info.errors[info.errors.length] = error;
info.errors.length++; info.errors.length++;
} }
for(var i=0; ((i<caughtErrors.length) && (i<maxCaughtErrors)); i++) { for (var i = 0;
((i < caughtErrors.length) && (i < maxCaughtErrors)); i++)
{
addError(caughtErrors[i], caughtErrorCatchers[i], caughtErrorTimes[i]); addError(caughtErrors[i], caughtErrorCatchers[i], caughtErrorTimes[i]);
} }
if (editor) { if (editor)
{
var aceErrors = editor.getUnhandledErrors(); var aceErrors = editor.getUnhandledErrors();
for(var i=0; ((i<aceErrors.length) && (i<maxAceErrors)) ;i++) { for (var i = 0;
((i < aceErrors.length) && (i < maxAceErrors)); i++)
{
var errorRecord = aceErrors[i]; var errorRecord = aceErrors[i];
addError(errorRecord.error, "ACE", errorRecord.time); addError(errorRecord.error, "ACE", errorRecord.time);
} }
@ -564,21 +742,26 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
info.numSocketReconnects = reconnectTimes.length; info.numSocketReconnects = reconnectTimes.length;
info.userId = userId; info.userId = userId;
info.currentRev = rev; info.currentRev = rev;
info.participants = (function() { info.participants = (function()
{
var pp = []; var pp = [];
for(var u in userSet) { for (var u in userSet)
{
pp.push(u); pp.push(u);
} }
return pp.join(','); return pp.join(',');
})(); })();
if (debugMessages.length > maxDebugMessages) { if (debugMessages.length > maxDebugMessages)
debugMessages = debugMessages.slice(debugMessages.length-maxDebugMessages, {
debugMessages.length); debugMessages = debugMessages.slice(debugMessages.length - maxDebugMessages, debugMessages.length);
} }
info.debugMessages = {length: 0}; info.debugMessages = {
for(var i=0;i<debugMessages.length;i++) { length: 0
};
for (var i = 0; i < debugMessages.length; i++)
{
info.debugMessages[i] = trunc(debugMessages[i]); info.debugMessages[i] = trunc(debugMessages[i]);
info.debugMessages.length++; info.debugMessages.length++;
} }
@ -586,40 +769,50 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
return info; return info;
} }
function getMissedChanges() { function getMissedChanges()
{
var obj = {}; var obj = {};
obj.userInfo = userSet[userId]; obj.userInfo = userSet[userId];
obj.baseRev = rev; obj.baseRev = rev;
if (state == "COMMITTING" && stateMessage) { if (state == "COMMITTING" && stateMessage)
{
obj.committedChangeset = stateMessage.changeset; obj.committedChangeset = stateMessage.changeset;
obj.committedChangesetAPool = stateMessage.apool; obj.committedChangesetAPool = stateMessage.apool;
obj.committedChangesetSocketId = stateMessageSocketId; obj.committedChangesetSocketId = stateMessageSocketId;
editor.applyPreparedChangesetToBase(); editor.applyPreparedChangesetToBase();
} }
var userChangesData = editor.prepareUserChangeset(); var userChangesData = editor.prepareUserChangeset();
if (userChangesData.changeset) { if (userChangesData.changeset)
{
obj.furtherChangeset = userChangesData.changeset; obj.furtherChangeset = userChangesData.changeset;
obj.furtherChangesetAPool = userChangesData.apool; obj.furtherChangesetAPool = userChangesData.apool;
} }
return obj; return obj;
} }
function setStateIdle() { function setStateIdle()
{
state = "IDLE"; state = "IDLE";
callbacks.onInternalAction("newlyIdle"); callbacks.onInternalAction("newlyIdle");
schedulePerhapsCallIdleFuncs(); schedulePerhapsCallIdleFuncs();
} }
function callWhenNotCommitting(func) { function callWhenNotCommitting(func)
{
idleFuncs.push(func); idleFuncs.push(func);
schedulePerhapsCallIdleFuncs(); schedulePerhapsCallIdleFuncs();
} }
var idleFuncs = []; var idleFuncs = [];
function schedulePerhapsCallIdleFuncs() {
setTimeout(function() { function schedulePerhapsCallIdleFuncs()
if (state == "IDLE") { {
while (idleFuncs.length > 0) { setTimeout(function()
{
if (state == "IDLE")
{
while (idleFuncs.length > 0)
{
var f = idleFuncs.shift(); var f = idleFuncs.shift();
f(); f();
} }
@ -629,14 +822,38 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
var self; var self;
return (self = { return (self = {
setOnUserJoin: function(cb) { callbacks.onUserJoin = cb; }, setOnUserJoin: function(cb)
setOnUserLeave: function(cb) { callbacks.onUserLeave = cb; }, {
setOnUpdateUserInfo: function(cb) { callbacks.onUpdateUserInfo = cb; }, callbacks.onUserJoin = cb;
setOnChannelStateChange: function(cb) { callbacks.onChannelStateChange = cb; }, },
setOnClientMessage: function(cb) { callbacks.onClientMessage = cb; }, setOnUserLeave: function(cb)
setOnInternalAction: function(cb) { callbacks.onInternalAction = cb; }, {
setOnConnectionTrouble: function(cb) { callbacks.onConnectionTrouble = cb; }, callbacks.onUserLeave = cb;
setOnServerMessage: function(cb) { callbacks.onServerMessage = cb; }, },
setOnUpdateUserInfo: function(cb)
{
callbacks.onUpdateUserInfo = cb;
},
setOnChannelStateChange: function(cb)
{
callbacks.onChannelStateChange = cb;
},
setOnClientMessage: function(cb)
{
callbacks.onClientMessage = cb;
},
setOnInternalAction: function(cb)
{
callbacks.onInternalAction = cb;
},
setOnConnectionTrouble: function(cb)
{
callbacks.onConnectionTrouble = cb;
},
setOnServerMessage: function(cb)
{
callbacks.onServerMessage = cb;
},
updateUserInfo: defer(updateUserInfo), updateUserInfo: defer(updateUserInfo),
handleMessageFromServer: handleMessageFromServer, handleMessageFromServer: handleMessageFromServer,
getConnectedUsers: getConnectedUsers, getConnectedUsers: getConnectedUsers,
@ -649,16 +866,21 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
}); });
} }
function selectElementContents(elem) { function selectElementContents(elem)
if ($.browser.msie) { {
if ($.browser.msie)
{
var range = document.body.createTextRange(); var range = document.body.createTextRange();
range.moveToElementText(elem); range.moveToElementText(elem);
range.select(); range.select();
} }
else { else
if (window.getSelection) { {
if (window.getSelection)
{
var browserSelection = window.getSelection(); var browserSelection = window.getSelection();
if (browserSelection) { if (browserSelection)
{
var range = document.createRange(); var range = document.createRange();
range.selectNodeContents(elem); range.selectNodeContents(elem);
browserSelection.removeAllRanges(); browserSelection.removeAllRanges();

View file

@ -1,6 +1,5 @@
// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/colorutils.js // DO NOT EDIT THIS FILE, edit infrastructure/ace/www/colorutils.js
// THIS FILE IS ALSO SERVED AS CLIENT-SIDE JS // THIS FILE IS ALSO SERVED AS CLIENT-SIDE JS
/** /**
* Copyright 2009 Google Inc. * Copyright 2009 Google Inc.
* *
@ -20,73 +19,97 @@
var colorutils = {}; var colorutils = {};
// "#ffffff" or "#fff" or "ffffff" or "fff" to [1.0, 1.0, 1.0] // "#ffffff" or "#fff" or "ffffff" or "fff" to [1.0, 1.0, 1.0]
colorutils.css2triple = function(cssColor) { colorutils.css2triple = function(cssColor)
{
var sixHex = colorutils.css2sixhex(cssColor); var sixHex = colorutils.css2sixhex(cssColor);
function hexToFloat(hh) {
return Number("0x"+hh)/255; function hexToFloat(hh)
{
return Number("0x" + hh) / 255;
} }
return [hexToFloat(sixHex.substr(0,2)), return [hexToFloat(sixHex.substr(0, 2)), hexToFloat(sixHex.substr(2, 2)), hexToFloat(sixHex.substr(4, 2))];
hexToFloat(sixHex.substr(2,2)),
hexToFloat(sixHex.substr(4,2))];
} }
// "#ffffff" or "#fff" or "ffffff" or "fff" to "ffffff" // "#ffffff" or "#fff" or "ffffff" or "fff" to "ffffff"
colorutils.css2sixhex = function(cssColor) { colorutils.css2sixhex = function(cssColor)
{
var h = /[0-9a-fA-F]+/.exec(cssColor)[0]; var h = /[0-9a-fA-F]+/.exec(cssColor)[0];
if (h.length != 6) { if (h.length != 6)
{
var a = h.charAt(0); var a = h.charAt(0);
var b = h.charAt(1); var b = h.charAt(1);
var c = h.charAt(2); var c = h.charAt(2);
h = a+a+b+b+c+c; h = a + a + b + b + c + c;
} }
return h; return h;
} }
// [1.0, 1.0, 1.0] -> "#ffffff" // [1.0, 1.0, 1.0] -> "#ffffff"
colorutils.triple2css = function(triple) { colorutils.triple2css = function(triple)
function floatToHex(n) { {
var n2 = colorutils.clamp(Math.round(n*255), 0, 255); function floatToHex(n)
return ("0"+n2.toString(16)).slice(-2); {
var n2 = colorutils.clamp(Math.round(n * 255), 0, 255);
return ("0" + n2.toString(16)).slice(-2);
} }
return "#" + floatToHex(triple[0]) + return "#" + floatToHex(triple[0]) + floatToHex(triple[1]) + floatToHex(triple[2]);
floatToHex(triple[1]) + floatToHex(triple[2]);
} }
colorutils.clamp = function(v,bot,top) { return v < bot ? bot : (v > top ? top : v); }; colorutils.clamp = function(v, bot, top)
colorutils.min3 = function(a,b,c) { return (a < b) ? (a < c ? a : c) : (b < c ? b : c); }; {
colorutils.max3 = function(a,b,c) { return (a > b) ? (a > c ? a : c) : (b > c ? b : c); }; return v < bot ? bot : (v > top ? top : v);
colorutils.colorMin = function(c) { return colorutils.min3(c[0], c[1], c[2]); }; };
colorutils.colorMax = function(c) { return colorutils.max3(c[0], c[1], c[2]); }; colorutils.min3 = function(a, b, c)
colorutils.scale = function(v, bot, top) { return colorutils.clamp(bot + v*(top-bot), 0, 1); }; {
colorutils.unscale = function(v, bot, top) { return colorutils.clamp((v-bot)/(top-bot), 0, 1); }; return (a < b) ? (a < c ? a : c) : (b < c ? b : c);
};
colorutils.max3 = function(a, b, c)
{
return (a > b) ? (a > c ? a : c) : (b > c ? b : c);
};
colorutils.colorMin = function(c)
{
return colorutils.min3(c[0], c[1], c[2]);
};
colorutils.colorMax = function(c)
{
return colorutils.max3(c[0], c[1], c[2]);
};
colorutils.scale = function(v, bot, top)
{
return colorutils.clamp(bot + v * (top - bot), 0, 1);
};
colorutils.unscale = function(v, bot, top)
{
return colorutils.clamp((v - bot) / (top - bot), 0, 1);
};
colorutils.scaleColor = function(c, bot, top) { colorutils.scaleColor = function(c, bot, top)
return [colorutils.scale(c[0], bot, top), {
colorutils.scale(c[1], bot, top), return [colorutils.scale(c[0], bot, top), colorutils.scale(c[1], bot, top), colorutils.scale(c[2], bot, top)];
colorutils.scale(c[2], bot, top)];
} }
colorutils.unscaleColor = function(c, bot, top) { colorutils.unscaleColor = function(c, bot, top)
return [colorutils.unscale(c[0], bot, top), {
colorutils.unscale(c[1], bot, top), return [colorutils.unscale(c[0], bot, top), colorutils.unscale(c[1], bot, top), colorutils.unscale(c[2], bot, top)];
colorutils.unscale(c[2], bot, top)];
} }
colorutils.luminosity = function(c) { colorutils.luminosity = function(c)
{
// rule of thumb for RGB brightness; 1.0 is white // rule of thumb for RGB brightness; 1.0 is white
return c[0]*0.30 + c[1]*0.59 + c[2]*0.11; return c[0] * 0.30 + c[1] * 0.59 + c[2] * 0.11;
} }
colorutils.saturate = function(c) { colorutils.saturate = function(c)
{
var min = colorutils.colorMin(c); var min = colorutils.colorMin(c);
var max = colorutils.colorMax(c); var max = colorutils.colorMax(c);
if (max - min <= 0) return [1.0, 1.0, 1.0]; if (max - min <= 0) return [1.0, 1.0, 1.0];
return colorutils.unscaleColor(c, min, max); return colorutils.unscaleColor(c, min, max);
} }
colorutils.blend = function(c1, c2, t) { colorutils.blend = function(c1, c2, t)
return [colorutils.scale(t, c1[0], c2[0]), {
colorutils.scale(t, c1[1], c2[1]), return [colorutils.scale(t, c1[0], c2[0]), colorutils.scale(t, c1[1], c2[1]), colorutils.scale(t, c1[2], c2[2])];
colorutils.scale(t, c1[2], c2[2])];
} }

View file

@ -1,7 +1,6 @@
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.contentcollector // THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.contentcollector
// %APPJET%: import("etherpad.collab.ace.easysync2.Changeset"); // %APPJET%: import("etherpad.collab.ace.easysync2.Changeset");
// %APPJET%: import("etherpad.admin.plugins"); // %APPJET%: import("etherpad.admin.plugins");
/** /**
* Copyright 2009 Google Inc. * Copyright 2009 Google Inc.
* *
@ -20,88 +19,129 @@
var _MAX_LIST_LEVEL = 8; var _MAX_LIST_LEVEL = 8;
function sanitizeUnicode(s) { function sanitizeUnicode(s)
{
return s.replace(/[\uffff\ufffe\ufeff\ufdd0-\ufdef\ud800-\udfff]/g, '?'); return s.replace(/[\uffff\ufffe\ufeff\ufdd0-\ufdef\ud800-\udfff]/g, '?');
} }
function makeContentCollector(collectStyles, browser, apool, domInterface, function makeContentCollector(collectStyles, browser, apool, domInterface, className2Author)
className2Author) { {
browser = browser || {}; browser = browser || {};
var plugins_; var plugins_;
if (typeof(plugins)!='undefined') { if (typeof(plugins) != 'undefined')
{
plugins_ = plugins; plugins_ = plugins;
} else { }
else
{
plugins_ = parent.parent.plugins; plugins_ = parent.parent.plugins;
} }
var dom = domInterface || { var dom = domInterface || {
isNodeText: function(n) { isNodeText: function(n)
{
return (n.nodeType == 3); return (n.nodeType == 3);
}, },
nodeTagName: function(n) { nodeTagName: function(n)
{
return n.tagName; return n.tagName;
}, },
nodeValue: function(n) { nodeValue: function(n)
{
return n.nodeValue; return n.nodeValue;
}, },
nodeNumChildren: function(n) { nodeNumChildren: function(n)
{
return n.childNodes.length; return n.childNodes.length;
}, },
nodeChild: function(n, i) { nodeChild: function(n, i)
{
return n.childNodes.item(i); return n.childNodes.item(i);
}, },
nodeProp: function(n, p) { nodeProp: function(n, p)
{
return n[p]; return n[p];
}, },
nodeAttr: function(n, a) { nodeAttr: function(n, a)
{
return n.getAttribute(a); return n.getAttribute(a);
}, },
optNodeInnerHTML: function(n) { optNodeInnerHTML: function(n)
{
return n.innerHTML; return n.innerHTML;
} }
}; };
var _blockElems = { "div":1, "p":1, "pre":1, "li":1 }; var _blockElems = {
function isBlockElement(n) { "div": 1,
"p": 1,
"pre": 1,
"li": 1
};
function isBlockElement(n)
{
return !!_blockElems[(dom.nodeTagName(n) || "").toLowerCase()]; return !!_blockElems[(dom.nodeTagName(n) || "").toLowerCase()];
} }
function textify(str) {
function textify(str)
{
return sanitizeUnicode( return sanitizeUnicode(
str.replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' ')); str.replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' '));
} }
function getAssoc(node, name) {
return dom.nodeProp(node, "_magicdom_"+name); function getAssoc(node, name)
{
return dom.nodeProp(node, "_magicdom_" + name);
} }
var lines = (function() { var lines = (function()
{
var textArray = []; var textArray = [];
var attribsArray = []; var attribsArray = [];
var attribsBuilder = null; var attribsBuilder = null;
var op = Changeset.newOp('+'); var op = Changeset.newOp('+');
var self = { var self = {
length: function() { return textArray.length; }, length: function()
atColumnZero: function() { {
return textArray[textArray.length-1] === ""; return textArray.length;
}, },
startNew: function() { atColumnZero: function()
{
return textArray[textArray.length - 1] === "";
},
startNew: function()
{
textArray.push(""); textArray.push("");
self.flush(true); self.flush(true);
attribsBuilder = Changeset.smartOpAssembler(); attribsBuilder = Changeset.smartOpAssembler();
}, },
textOfLine: function(i) { return textArray[i]; }, textOfLine: function(i)
appendText: function(txt, attrString) { {
textArray[textArray.length-1] += txt; return textArray[i];
},
appendText: function(txt, attrString)
{
textArray[textArray.length - 1] += txt;
//dmesg(txt+" / "+attrString); //dmesg(txt+" / "+attrString);
op.attribs = attrString; op.attribs = attrString;
op.chars = txt.length; op.chars = txt.length;
attribsBuilder.append(op); attribsBuilder.append(op);
}, },
textLines: function() { return textArray.slice(); }, textLines: function()
attribLines: function() { return attribsArray; }, {
return textArray.slice();
},
attribLines: function()
{
return attribsArray;
},
// call flush only when you're done // call flush only when you're done
flush: function(withNewline) { flush: function(withNewline)
if (attribsBuilder) { {
if (attribsBuilder)
{
attribsArray.push(attribsBuilder.toString()); attribsArray.push(attribsBuilder.toString());
attribsBuilder = null; attribsBuilder = null;
} }
@ -111,21 +151,31 @@ function makeContentCollector(collectStyles, browser, apool, domInterface,
return self; return self;
}()); }());
var cc = {}; var cc = {};
function _ensureColumnZero(state) {
if (! lines.atColumnZero()) { function _ensureColumnZero(state)
{
if (!lines.atColumnZero())
{
cc.startNewLine(state); cc.startNewLine(state);
} }
} }
var selection, startPoint, endPoint; var selection, startPoint, endPoint;
var selStart = [-1,-1], selEnd = [-1,-1]; var selStart = [-1, -1],
var blockElems = { "div":1, "p":1, "pre":1 }; selEnd = [-1, -1];
function _isEmpty(node, state) { var blockElems = {
"div": 1,
"p": 1,
"pre": 1
};
function _isEmpty(node, state)
{
// consider clean blank lines pasted in IE to be empty // consider clean blank lines pasted in IE to be empty
if (dom.nodeNumChildren(node) == 0) return true; if (dom.nodeNumChildren(node) == 0) return true;
if (dom.nodeNumChildren(node) == 1 && if (dom.nodeNumChildren(node) == 1 && getAssoc(node, "shouldBeEmpty") && dom.optNodeInnerHTML(node) == "&nbsp;" && !getAssoc(node, "unpasted"))
getAssoc(node, "shouldBeEmpty") && dom.optNodeInnerHTML(node) == "&nbsp;" {
&& ! getAssoc(node, "unpasted")) { if (state)
if (state) { {
var child = dom.nodeChild(node, 0); var child = dom.nodeChild(node, 0);
_reachPoint(child, 0, state); _reachPoint(child, 0, state);
_reachPoint(child, 1, state); _reachPoint(child, 1, state);
@ -134,85 +184,116 @@ function makeContentCollector(collectStyles, browser, apool, domInterface,
} }
return false; return false;
} }
function _pointHere(charsAfter, state) {
var ln = lines.length()-1; function _pointHere(charsAfter, state)
{
var ln = lines.length() - 1;
var chr = lines.textOfLine(ln).length; var chr = lines.textOfLine(ln).length;
if (chr == 0 && state.listType && state.listType != 'none') { if (chr == 0 && state.listType && state.listType != 'none')
{
chr += 1; // listMarker chr += 1; // listMarker
} }
chr += charsAfter; chr += charsAfter;
return [ln, chr]; return [ln, chr];
} }
function _reachBlockPoint(nd, idx, state) {
if (! dom.isNodeText(nd)) _reachPoint(nd, idx, state); function _reachBlockPoint(nd, idx, state)
{
if (!dom.isNodeText(nd)) _reachPoint(nd, idx, state);
} }
function _reachPoint(nd, idx, state) {
if (startPoint && nd == startPoint.node && startPoint.index == idx) { function _reachPoint(nd, idx, state)
{
if (startPoint && nd == startPoint.node && startPoint.index == idx)
{
selStart = _pointHere(0, state); selStart = _pointHere(0, state);
} }
if (endPoint && nd == endPoint.node && endPoint.index == idx) { if (endPoint && nd == endPoint.node && endPoint.index == idx)
{
selEnd = _pointHere(0, state); selEnd = _pointHere(0, state);
} }
} }
cc.incrementFlag = function(state, flagName) { cc.incrementFlag = function(state, flagName)
state.flags[flagName] = (state.flags[flagName] || 0)+1; {
state.flags[flagName] = (state.flags[flagName] || 0) + 1;
} }
cc.decrementFlag = function(state, flagName) { cc.decrementFlag = function(state, flagName)
{
state.flags[flagName]--; state.flags[flagName]--;
} }
cc.incrementAttrib = function(state, attribName) { cc.incrementAttrib = function(state, attribName)
if (! state.attribs[attribName]) { {
if (!state.attribs[attribName])
{
state.attribs[attribName] = 1; state.attribs[attribName] = 1;
} }
else { else
{
state.attribs[attribName]++; state.attribs[attribName]++;
} }
_recalcAttribString(state); _recalcAttribString(state);
} }
cc.decrementAttrib = function(state, attribName) { cc.decrementAttrib = function(state, attribName)
{
state.attribs[attribName]--; state.attribs[attribName]--;
_recalcAttribString(state); _recalcAttribString(state);
} }
function _enterList(state, listType) {
function _enterList(state, listType)
{
var oldListType = state.listType; var oldListType = state.listType;
state.listLevel = (state.listLevel || 0)+1; state.listLevel = (state.listLevel || 0) + 1;
if (listType != 'none') { if (listType != 'none')
state.listNesting = (state.listNesting || 0)+1; {
state.listNesting = (state.listNesting || 0) + 1;
} }
state.listType = listType; state.listType = listType;
_recalcAttribString(state); _recalcAttribString(state);
return oldListType; return oldListType;
} }
function _exitList(state, oldListType) {
function _exitList(state, oldListType)
{
state.listLevel--; state.listLevel--;
if (state.listType != 'none') { if (state.listType != 'none')
{
state.listNesting--; state.listNesting--;
} }
state.listType = oldListType; state.listType = oldListType;
_recalcAttribString(state); _recalcAttribString(state);
} }
function _enterAuthor(state, author) {
function _enterAuthor(state, author)
{
var oldAuthor = state.author; var oldAuthor = state.author;
state.authorLevel = (state.authorLevel || 0)+1; state.authorLevel = (state.authorLevel || 0) + 1;
state.author = author; state.author = author;
_recalcAttribString(state); _recalcAttribString(state);
return oldAuthor; return oldAuthor;
} }
function _exitAuthor(state, oldAuthor) {
function _exitAuthor(state, oldAuthor)
{
state.authorLevel--; state.authorLevel--;
state.author = oldAuthor; state.author = oldAuthor;
_recalcAttribString(state); _recalcAttribString(state);
} }
function _recalcAttribString(state) {
function _recalcAttribString(state)
{
var lst = []; var lst = [];
for(var a in state.attribs) { for (var a in state.attribs)
if (state.attribs[a]) { {
lst.push([a,'true']); if (state.attribs[a])
{
lst.push([a, 'true']);
} }
} }
if (state.authorLevel > 0) { if (state.authorLevel > 0)
{
var authorAttrib = ['author', state.author]; var authorAttrib = ['author', state.author];
if (apool.putAttrib(authorAttrib, true) >= 0) { if (apool.putAttrib(authorAttrib, true) >= 0)
{
// require that author already be in pool // require that author already be in pool
// (don't add authors from other documents, etc.) // (don't add authors from other documents, etc.)
lst.push(authorAttrib); lst.push(authorAttrib);
@ -220,155 +301,197 @@ function makeContentCollector(collectStyles, browser, apool, domInterface,
} }
state.attribString = Changeset.makeAttribsString('+', lst, apool); state.attribString = Changeset.makeAttribsString('+', lst, apool);
} }
function _produceListMarker(state) {
lines.appendText('*', Changeset.makeAttribsString( function _produceListMarker(state)
'+', [['list', state.listType], {
['insertorder', 'first']], lines.appendText('*', Changeset.makeAttribsString('+', [
apool)); ['list', state.listType],
['insertorder', 'first']
], apool));
} }
cc.startNewLine = function(state) { cc.startNewLine = function(state)
if (state) { {
var atBeginningOfLine = lines.textOfLine(lines.length()-1).length == 0; if (state)
if (atBeginningOfLine && state.listType && state.listType != 'none') { {
var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
if (atBeginningOfLine && state.listType && state.listType != 'none')
{
_produceListMarker(state); _produceListMarker(state);
} }
} }
lines.startNew(); lines.startNew();
} }
cc.notifySelection = function (sel) { cc.notifySelection = function(sel)
if (sel) { {
if (sel)
{
selection = sel; selection = sel;
startPoint = selection.startPoint; startPoint = selection.startPoint;
endPoint = selection.endPoint; endPoint = selection.endPoint;
} }
}; };
cc.doAttrib = function(state, na) { cc.doAttrib = function(state, na)
{
state.localAttribs = (state.localAttribs || []); state.localAttribs = (state.localAttribs || []);
state.localAttribs.push(na); state.localAttribs.push(na);
cc.incrementAttrib(state, na); cc.incrementAttrib(state, na);
}; };
cc.collectContent = function (node, state) { cc.collectContent = function(node, state)
if (! state) { {
state = {flags: {/*name -> nesting counter*/}, if (!state)
{
state = {
flags: { /*name -> nesting counter*/
},
localAttribs: null, localAttribs: null,
attribs: {/*name -> nesting counter*/}, attribs: { /*name -> nesting counter*/
attribString: ''}; },
attribString: ''
};
} }
var localAttribs = state.localAttribs; var localAttribs = state.localAttribs;
state.localAttribs = null; state.localAttribs = null;
var isBlock = isBlockElement(node); var isBlock = isBlockElement(node);
var isEmpty = _isEmpty(node, state); var isEmpty = _isEmpty(node, state);
if (isBlock) _ensureColumnZero(state); if (isBlock) _ensureColumnZero(state);
var startLine = lines.length()-1; var startLine = lines.length() - 1;
_reachBlockPoint(node, 0, state); _reachBlockPoint(node, 0, state);
if (dom.isNodeText(node)) { if (dom.isNodeText(node))
{
var txt = dom.nodeValue(node); var txt = dom.nodeValue(node);
var rest = ''; var rest = '';
var x = 0; // offset into original text var x = 0; // offset into original text
if (txt.length == 0) { if (txt.length == 0)
if (startPoint && node == startPoint.node) { {
if (startPoint && node == startPoint.node)
{
selStart = _pointHere(0, state); selStart = _pointHere(0, state);
} }
if (endPoint && node == endPoint.node) { if (endPoint && node == endPoint.node)
{
selEnd = _pointHere(0, state); selEnd = _pointHere(0, state);
} }
} }
while (txt.length > 0) { while (txt.length > 0)
{
var consumed = 0; var consumed = 0;
if (state.flags.preMode) { if (state.flags.preMode)
var firstLine = txt.split('\n',1)[0]; {
consumed = firstLine.length+1; var firstLine = txt.split('\n', 1)[0];
consumed = firstLine.length + 1;
rest = txt.substring(consumed); rest = txt.substring(consumed);
txt = firstLine; txt = firstLine;
} }
else { /* will only run this loop body once */ } else
if (startPoint && node == startPoint.node && { /* will only run this loop body once */
startPoint.index-x <= txt.length) {
selStart = _pointHere(startPoint.index-x, state);
} }
if (endPoint && node == endPoint.node && if (startPoint && node == startPoint.node && startPoint.index - x <= txt.length)
endPoint.index-x <= txt.length) { {
selEnd = _pointHere(endPoint.index-x, state); selStart = _pointHere(startPoint.index - x, state);
}
if (endPoint && node == endPoint.node && endPoint.index - x <= txt.length)
{
selEnd = _pointHere(endPoint.index - x, state);
} }
var txt2 = txt; var txt2 = txt;
if ((! state.flags.preMode) && /^[\r\n]*$/.exec(txt)) { if ((!state.flags.preMode) && /^[\r\n]*$/.exec(txt))
{
// prevents textnodes containing just "\n" from being significant // prevents textnodes containing just "\n" from being significant
// in safari when pasting text, now that we convert them to // in safari when pasting text, now that we convert them to
// spaces instead of removing them, because in other cases // spaces instead of removing them, because in other cases
// removing "\n" from pasted HTML will collapse words together. // removing "\n" from pasted HTML will collapse words together.
txt2 = ""; txt2 = "";
} }
var atBeginningOfLine = lines.textOfLine(lines.length()-1).length == 0; var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
if (atBeginningOfLine) { if (atBeginningOfLine)
{
// newlines in the source mustn't become spaces at beginning of line box // newlines in the source mustn't become spaces at beginning of line box
txt2 = txt2.replace(/^\n*/, ''); txt2 = txt2.replace(/^\n*/, '');
} }
if (atBeginningOfLine && state.listType && state.listType != 'none') { if (atBeginningOfLine && state.listType && state.listType != 'none')
{
_produceListMarker(state); _produceListMarker(state);
} }
lines.appendText(textify(txt2), state.attribString); lines.appendText(textify(txt2), state.attribString);
x += consumed; x += consumed;
txt = rest; txt = rest;
if (txt.length > 0) { if (txt.length > 0)
{
cc.startNewLine(state); cc.startNewLine(state);
} }
} }
} }
else { else
{
var tname = (dom.nodeTagName(node) || "").toLowerCase(); var tname = (dom.nodeTagName(node) || "").toLowerCase();
if (tname == "br") { if (tname == "br")
{
cc.startNewLine(state); cc.startNewLine(state);
} }
else if (tname == "script" || tname == "style") { else if (tname == "script" || tname == "style")
{
// ignore // ignore
} }
else if (! isEmpty) { else if (!isEmpty)
{
var styl = dom.nodeAttr(node, "style"); var styl = dom.nodeAttr(node, "style");
var cls = dom.nodeProp(node, "className"); var cls = dom.nodeProp(node, "className");
var isPre = (tname == "pre"); var isPre = (tname == "pre");
if ((! isPre) && browser.safari) { if ((!isPre) && browser.safari)
{
isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl)); isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl));
} }
if (isPre) cc.incrementFlag(state, 'preMode'); if (isPre) cc.incrementFlag(state, 'preMode');
var oldListTypeOrNull = null; var oldListTypeOrNull = null;
var oldAuthorOrNull = null; var oldAuthorOrNull = null;
if (collectStyles) { if (collectStyles)
plugins_.callHook('collectContentPre', {cc: cc, state:state, tname:tname, styl:styl, cls:cls}); {
if (tname == "b" || (styl && /\bfont-weight:\s*bold\b/i.exec(styl)) || plugins_.callHook('collectContentPre', {
tname == "strong") { cc: cc,
state: state,
tname: tname,
styl: styl,
cls: cls
});
if (tname == "b" || (styl && /\bfont-weight:\s*bold\b/i.exec(styl)) || tname == "strong")
{
cc.doAttrib(state, "bold"); cc.doAttrib(state, "bold");
} }
if (tname == "i" || (styl && /\bfont-style:\s*italic\b/i.exec(styl)) || if (tname == "i" || (styl && /\bfont-style:\s*italic\b/i.exec(styl)) || tname == "em")
tname == "em") { {
cc.doAttrib(state, "italic"); cc.doAttrib(state, "italic");
} }
if (tname == "u" || (styl && /\btext-decoration:\s*underline\b/i.exec(styl)) || if (tname == "u" || (styl && /\btext-decoration:\s*underline\b/i.exec(styl)) || tname == "ins")
tname == "ins") { {
cc.doAttrib(state, "underline"); cc.doAttrib(state, "underline");
} }
if (tname == "s" || (styl && /\btext-decoration:\s*line-through\b/i.exec(styl)) || if (tname == "s" || (styl && /\btext-decoration:\s*line-through\b/i.exec(styl)) || tname == "del")
tname == "del") { {
cc.doAttrib(state, "strikethrough"); cc.doAttrib(state, "strikethrough");
} }
if (tname == "ul") { if (tname == "ul")
{
var type; var type;
var rr = cls && /(?:^| )list-(bullet[12345678])\b/.exec(cls); var rr = cls && /(?:^| )list-(bullet[12345678])\b/.exec(cls);
type = rr && rr[1] || "bullet"+ type = rr && rr[1] || "bullet" + String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1));
String(Math.min(_MAX_LIST_LEVEL, (state.listNesting||0)+1));
oldListTypeOrNull = (_enterList(state, type) || 'none'); oldListTypeOrNull = (_enterList(state, type) || 'none');
} }
else if ((tname == "div" || tname == "p") && cls && else if ((tname == "div" || tname == "p") && cls && cls.match(/(?:^| )ace-line\b/))
cls.match(/(?:^| )ace-line\b/)) { {
oldListTypeOrNull = (_enterList(state, type) || 'none'); oldListTypeOrNull = (_enterList(state, type) || 'none');
} }
if (className2Author && cls) { if (className2Author && cls)
{
var classes = cls.match(/\S+/g); var classes = cls.match(/\S+/g);
if (classes && classes.length > 0) { if (classes && classes.length > 0)
for(var i=0;i<classes.length;i++) { {
for (var i = 0; i < classes.length; i++)
{
var c = classes[i]; var c = classes[i];
var a = className2Author(c); var a = className2Author(c);
if (a) { if (a)
{
oldAuthorOrNull = (_enterAuthor(state, a) || 'none'); oldAuthorOrNull = (_enterAuthor(state, a) || 'none');
break; break;
} }
@ -378,42 +501,59 @@ function makeContentCollector(collectStyles, browser, apool, domInterface,
} }
var nc = dom.nodeNumChildren(node); var nc = dom.nodeNumChildren(node);
for(var i=0;i<nc;i++) { for (var i = 0; i < nc; i++)
{
var c = dom.nodeChild(node, i); var c = dom.nodeChild(node, i);
cc.collectContent(c, state); cc.collectContent(c, state);
} }
if (collectStyles) { if (collectStyles)
plugins_.callHook('collectContentPost', {cc: cc, state:state, tname:tname, styl:styl, cls:cls}); {
plugins_.callHook('collectContentPost', {
cc: cc,
state: state,
tname: tname,
styl: styl,
cls: cls
});
} }
if (isPre) cc.decrementFlag(state, 'preMode'); if (isPre) cc.decrementFlag(state, 'preMode');
if (state.localAttribs) { if (state.localAttribs)
for(var i=0;i<state.localAttribs.length;i++) { {
for (var i = 0; i < state.localAttribs.length; i++)
{
cc.decrementAttrib(state, state.localAttribs[i]); cc.decrementAttrib(state, state.localAttribs[i]);
} }
} }
if (oldListTypeOrNull) { if (oldListTypeOrNull)
{
_exitList(state, oldListTypeOrNull); _exitList(state, oldListTypeOrNull);
} }
if (oldAuthorOrNull) { if (oldAuthorOrNull)
{
_exitAuthor(state, oldAuthorOrNull); _exitAuthor(state, oldAuthorOrNull);
} }
} }
} }
if (! browser.msie) { if (!browser.msie)
{
_reachBlockPoint(node, 1, state); _reachBlockPoint(node, 1, state);
} }
if (isBlock) { if (isBlock)
if (lines.length()-1 == startLine) { {
if (lines.length() - 1 == startLine)
{
cc.startNewLine(state); cc.startNewLine(state);
} }
else { else
{
_ensureColumnZero(state); _ensureColumnZero(state);
} }
} }
if (browser.msie) { if (browser.msie)
{
// in IE, a point immediately after a DIV appears on the next line // in IE, a point immediately after a DIV appears on the next line
_reachBlockPoint(node, 1, state); _reachBlockPoint(node, 1, state);
} }
@ -421,26 +561,38 @@ function makeContentCollector(collectStyles, browser, apool, domInterface,
state.localAttribs = localAttribs; state.localAttribs = localAttribs;
}; };
// can pass a falsy value for end of doc // can pass a falsy value for end of doc
cc.notifyNextNode = function (node) { cc.notifyNextNode = function(node)
{
// an "empty block" won't end a line; this addresses an issue in IE with // an "empty block" won't end a line; this addresses an issue in IE with
// typing into a blank line at the end of the document. typed text // typing into a blank line at the end of the document. typed text
// goes into the body, and the empty line div still looks clean. // goes into the body, and the empty line div still looks clean.
// it is incorporated as dirty by the rule that a dirty region has // it is incorporated as dirty by the rule that a dirty region has
// to end a line. // to end a line.
if ((!node) || (isBlockElement(node) && !_isEmpty(node))) { if ((!node) || (isBlockElement(node) && !_isEmpty(node)))
{
_ensureColumnZero(null); _ensureColumnZero(null);
} }
}; };
// each returns [line, char] or [-1,-1] // each returns [line, char] or [-1,-1]
var getSelectionStart = function() { return selStart; }; var getSelectionStart = function()
var getSelectionEnd = function() { return selEnd; }; {
return selStart;
};
var getSelectionEnd = function()
{
return selEnd;
};
// returns array of strings for lines found, last entry will be "" if // returns array of strings for lines found, last entry will be "" if
// last line is complete (i.e. if a following span should be on a new line). // last line is complete (i.e. if a following span should be on a new line).
// can be called at any point // can be called at any point
cc.getLines = function() { return lines.textLines(); }; cc.getLines = function()
{
return lines.textLines();
};
cc.finish = function() { cc.finish = function()
{
lines.flush(); lines.flush();
var lineAttribs = lines.attribLines(); var lineAttribs = lines.attribLines();
var lineStrings = cc.getLines(); var lineStrings = cc.getLines();
@ -451,43 +603,51 @@ function makeContentCollector(collectStyles, browser, apool, domInterface,
var ss = getSelectionStart(); var ss = getSelectionStart();
var se = getSelectionEnd(); var se = getSelectionEnd();
function fixLongLines() { function fixLongLines()
{
// design mode does not deal with with really long lines! // design mode does not deal with with really long lines!
var lineLimit = 2000; // chars var lineLimit = 2000; // chars
var buffer = 10; // chars allowed over before wrapping var buffer = 10; // chars allowed over before wrapping
var linesWrapped = 0; var linesWrapped = 0;
var numLinesAfter = 0; var numLinesAfter = 0;
for(var i=lineStrings.length-1; i>=0; i--) { for (var i = lineStrings.length - 1; i >= 0; i--)
{
var oldString = lineStrings[i]; var oldString = lineStrings[i];
var oldAttribString = lineAttribs[i]; var oldAttribString = lineAttribs[i];
if (oldString.length > lineLimit+buffer) { if (oldString.length > lineLimit + buffer)
{
var newStrings = []; var newStrings = [];
var newAttribStrings = []; var newAttribStrings = [];
while (oldString.length > lineLimit) { while (oldString.length > lineLimit)
{
//var semiloc = oldString.lastIndexOf(';', lineLimit-1); //var semiloc = oldString.lastIndexOf(';', lineLimit-1);
//var lengthToTake = (semiloc >= 0 ? (semiloc+1) : lineLimit); //var lengthToTake = (semiloc >= 0 ? (semiloc+1) : lineLimit);
lengthToTake = lineLimit; lengthToTake = lineLimit;
newStrings.push(oldString.substring(0, lengthToTake)); newStrings.push(oldString.substring(0, lengthToTake));
oldString = oldString.substring(lengthToTake); oldString = oldString.substring(lengthToTake);
newAttribStrings.push(Changeset.subattribution(oldAttribString, newAttribStrings.push(Changeset.subattribution(oldAttribString, 0, lengthToTake));
0, lengthToTake)); oldAttribString = Changeset.subattribution(oldAttribString, lengthToTake);
oldAttribString = Changeset.subattribution(oldAttribString,
lengthToTake);
} }
if (oldString.length > 0) { if (oldString.length > 0)
{
newStrings.push(oldString); newStrings.push(oldString);
newAttribStrings.push(oldAttribString); newAttribStrings.push(oldAttribString);
} }
function fixLineNumber(lineChar) {
function fixLineNumber(lineChar)
{
if (lineChar[0] < 0) return; if (lineChar[0] < 0) return;
var n = lineChar[0]; var n = lineChar[0];
var c = lineChar[1]; var c = lineChar[1];
if (n > i) { if (n > i)
n += (newStrings.length-1); {
n += (newStrings.length - 1);
} }
else if (n == i) { else if (n == i)
{
var a = 0; var a = 0;
while (c > newStrings[a].length) { while (c > newStrings[a].length)
{
c -= newStrings[a].length; c -= newStrings[a].length;
a++; a++;
} }
@ -507,13 +667,21 @@ function makeContentCollector(collectStyles, browser, apool, domInterface,
lineAttribs.splice.apply(lineAttribs, newAttribStrings); lineAttribs.splice.apply(lineAttribs, newAttribStrings);
} }
} }
return {linesWrapped:linesWrapped, numLinesAfter:numLinesAfter}; return {
linesWrapped: linesWrapped,
numLinesAfter: numLinesAfter
};
} }
var wrapData = fixLongLines(); var wrapData = fixLongLines();
return { selStart: ss, selEnd: se, linesWrapped: wrapData.linesWrapped, return {
selStart: ss,
selEnd: se,
linesWrapped: wrapData.linesWrapped,
numLinesAfter: wrapData.numLinesAfter, numLinesAfter: wrapData.numLinesAfter,
lines: lineStrings, lineAttribs: lineAttribs }; lines: lineStrings,
lineAttribs: lineAttribs
};
} }
return cc; return cc;

View file

@ -1,5 +1,3 @@
/** /**
* Copyright 2009 Google Inc. * Copyright 2009 Google Inc.
* *
@ -16,20 +14,24 @@
* limitations under the License. * limitations under the License.
*/ */
function makeCSSManager(emptyStylesheetTitle) { function makeCSSManager(emptyStylesheetTitle)
{
function getSheetByTitle(title) { function getSheetByTitle(title)
{
var allSheets = document.styleSheets; var allSheets = document.styleSheets;
for(var i=0;i<allSheets.length;i++) { for (var i = 0; i < allSheets.length; i++)
{
var s = allSheets[i]; var s = allSheets[i];
if (s.title == title) { if (s.title == title)
{
return s; return s;
} }
} }
return null; return null;
} }
/*function getSheetTagByTitle(title) { /*function getSheetTagByTitle(title) {
var allStyleTags = document.getElementsByTagName("style"); var allStyleTags = document.getElementsByTagName("style");
for(var i=0;i<allStyleTags.length;i++) { for(var i=0;i<allStyleTags.length;i++) {
var t = allStyleTags[i]; var t = allStyleTags[i];
@ -42,29 +44,43 @@ function makeCSSManager(emptyStylesheetTitle) {
var browserSheet = getSheetByTitle(emptyStylesheetTitle); var browserSheet = getSheetByTitle(emptyStylesheetTitle);
//var browserTag = getSheetTagByTitle(emptyStylesheetTitle); //var browserTag = getSheetTagByTitle(emptyStylesheetTitle);
function browserRules() { return (browserSheet.cssRules || browserSheet.rules); }
function browserDeleteRule(i) {
function browserRules()
{
return (browserSheet.cssRules || browserSheet.rules);
}
function browserDeleteRule(i)
{
if (browserSheet.deleteRule) browserSheet.deleteRule(i); if (browserSheet.deleteRule) browserSheet.deleteRule(i);
else browserSheet.removeRule(i); else browserSheet.removeRule(i);
} }
function browserInsertRule(i, selector) {
if (browserSheet.insertRule) browserSheet.insertRule(selector+' {}', i); function browserInsertRule(i, selector)
{
if (browserSheet.insertRule) browserSheet.insertRule(selector + ' {}', i);
else browserSheet.addRule(selector, null, i); else browserSheet.addRule(selector, null, i);
} }
var selectorList = []; var selectorList = [];
function indexOfSelector(selector) { function indexOfSelector(selector)
for(var i=0;i<selectorList.length;i++) { {
if (selectorList[i] == selector) { for (var i = 0; i < selectorList.length; i++)
{
if (selectorList[i] == selector)
{
return i; return i;
} }
} }
return -1; return -1;
} }
function selectorStyle(selector) { function selectorStyle(selector)
{
var i = indexOfSelector(selector); var i = indexOfSelector(selector);
if (i < 0) { if (i < 0)
{
// add selector // add selector
browserInsertRule(0, selector); browserInsertRule(0, selector);
selectorList.splice(0, 0, selector); selectorList.splice(0, 0, selector);
@ -73,16 +89,22 @@ function makeCSSManager(emptyStylesheetTitle) {
return browserRules().item(i).style; return browserRules().item(i).style;
} }
function removeSelectorStyle(selector) { function removeSelectorStyle(selector)
{
var i = indexOfSelector(selector); var i = indexOfSelector(selector);
if (i >= 0) { if (i >= 0)
{
browserDeleteRule(i); browserDeleteRule(i);
selectorList.splice(i, 1); selectorList.splice(i, 1);
} }
} }
return {selectorStyle:selectorStyle, removeSelectorStyle:removeSelectorStyle, return {
info: function() { selectorStyle: selectorStyle,
return selectorList.length+":"+browserRules().length; removeSelectorStyle: removeSelectorStyle,
}}; info: function()
{
return selectorList.length + ":" + browserRules().length;
}
};
} }

View file

@ -46,7 +46,6 @@ function makeCSSManager(emptyStylesheetTitle)
var browserSheet = getSheetByTitle(emptyStylesheetTitle); var browserSheet = getSheetByTitle(emptyStylesheetTitle);
//var browserTag = getSheetTagByTitle(emptyStylesheetTitle); //var browserTag = getSheetTagByTitle(emptyStylesheetTitle);
function browserRules() function browserRules()
{ {
return (browserSheet.cssRules || browserSheet.rules); return (browserSheet.cssRules || browserSheet.rules);
@ -103,7 +102,7 @@ function makeCSSManager(emptyStylesheetTitle)
return { return {
selectorStyle: selectorStyle, selectorStyle: selectorStyle,
removeSelectorStyle: removeSelectorStyle, removeSelectorStyle: removeSelectorStyle,
info: function () info: function()
{ {
return selectorList.length + ":" + browserRules().length; return selectorList.length + ":" + browserRules().length;
} }

View file

@ -1,6 +1,5 @@
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.domline // THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.domline
// %APPJET%: import("etherpad.admin.plugins"); // %APPJET%: import("etherpad.admin.plugins");
/** /**
* Copyright 2009 Google Inc. * Copyright 2009 Google Inc.
* *
@ -20,19 +19,25 @@
// requires: top // requires: top
// requires: plugins // requires: plugins
// requires: undefined // requires: undefined
var domline = {}; var domline = {};
domline.noop = function() {}; domline.noop = function()
domline.identity = function(x) { return x; }; {};
domline.identity = function(x)
{
return x;
};
domline.addToLineClass = function(lineClass, cls) { domline.addToLineClass = function(lineClass, cls)
{
// an "empty span" at any point can be used to add classes to // an "empty span" at any point can be used to add classes to
// the line, using line:className. otherwise, we ignore // the line, using line:className. otherwise, we ignore
// the span. // the span.
cls.replace(/\S+/g, function (c) { cls.replace(/\S+/g, function(c)
if (c.indexOf("line:") == 0) { {
if (c.indexOf("line:") == 0)
{
// add class to line // add class to line
lineClass = (lineClass ? lineClass+' ' : '')+c.substring(5); lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5);
} }
}); });
return lineClass; return lineClass;
@ -40,42 +45,56 @@ domline.addToLineClass = function(lineClass, cls) {
// if "document" is falsy we don't create a DOM node, just // if "document" is falsy we don't create a DOM node, just
// an object with innerHTML and className // an object with innerHTML and className
domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) { domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
var result = { node: null, {
var result = {
node: null,
appendSpan: domline.noop, appendSpan: domline.noop,
prepareForAdd: domline.noop, prepareForAdd: domline.noop,
notifyAdded: domline.noop, notifyAdded: domline.noop,
clearSpans: domline.noop, clearSpans: domline.noop,
finishUpdate: domline.noop, finishUpdate: domline.noop,
lineMarker: 0 }; lineMarker: 0
};
var browser = (optBrowser || {}); var browser = (optBrowser || {});
var document = optDocument; var document = optDocument;
if (document) { if (document)
{
result.node = document.createElement("div"); result.node = document.createElement("div");
} }
else { else
result.node = {innerHTML: '', className: ''}; {
result.node = {
innerHTML: '',
className: ''
};
} }
var html = []; var html = [];
var preHtml, postHtml; var preHtml, postHtml;
var curHTML = null; var curHTML = null;
function processSpaces(s) {
function processSpaces(s)
{
return domline.processSpaces(s, doesWrap); return domline.processSpaces(s, doesWrap);
} }
var identity = domline.identity; var identity = domline.identity;
var perTextNodeProcess = (doesWrap ? identity : processSpaces); var perTextNodeProcess = (doesWrap ? identity : processSpaces);
var perHtmlLineProcess = (doesWrap ? processSpaces : identity); var perHtmlLineProcess = (doesWrap ? processSpaces : identity);
var lineClass = 'ace-line'; var lineClass = 'ace-line';
result.appendSpan = function(txt, cls) { result.appendSpan = function(txt, cls)
if (cls.indexOf('list') >= 0) { {
if (cls.indexOf('list') >= 0)
{
var listType = /(?:^| )list:(\S+)/.exec(cls); var listType = /(?:^| )list:(\S+)/.exec(cls);
if (listType) { if (listType)
{
listType = listType[1]; listType = listType[1];
if (listType) { if (listType)
preHtml = '<ul class="list-'+listType+'"><li>'; {
preHtml = '<ul class="list-' + listType + '"><li>';
postHtml = '</li></ul>'; postHtml = '</li></ul>';
} }
result.lineMarker += txt.length; result.lineMarker += txt.length;
@ -84,17 +103,21 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) {
} }
var href = null; var href = null;
var simpleTags = null; var simpleTags = null;
if (cls.indexOf('url') >= 0) { if (cls.indexOf('url') >= 0)
cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url) { {
cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url)
{
href = url; href = url;
return space+"url"; return space + "url";
}); });
} }
if (cls.indexOf('tag') >= 0) { if (cls.indexOf('tag') >= 0)
cls = cls.replace(/(^| )tag:(\S+)/g, function(x0, space, tag) { {
if (! simpleTags) simpleTags = []; cls = cls.replace(/(^| )tag:(\S+)/g, function(x0, space, tag)
{
if (!simpleTags) simpleTags = [];
simpleTags.push(tag.toLowerCase()); simpleTags.push(tag.toLowerCase());
return space+tag; return space + tag;
}); });
} }
@ -102,60 +125,74 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) {
var extraCloseTags = ""; var extraCloseTags = "";
var plugins_; var plugins_;
if (typeof(plugins)!='undefined') { if (typeof(plugins) != 'undefined')
{
plugins_ = plugins; plugins_ = plugins;
} else { }
else
{
plugins_ = parent.parent.plugins; plugins_ = parent.parent.plugins;
} }
plugins_.callHook( plugins_.callHook("aceCreateDomLine", {
"aceCreateDomLine", {domline:domline, cls:cls} domline: domline,
).map(function (modifier) { cls: cls
}).map(function(modifier)
{
cls = modifier.cls; cls = modifier.cls;
extraOpenTags = extraOpenTags+modifier.extraOpenTags; extraOpenTags = extraOpenTags + modifier.extraOpenTags;
extraCloseTags = modifier.extraCloseTags+extraCloseTags; extraCloseTags = modifier.extraCloseTags + extraCloseTags;
}); });
if ((! txt) && cls) { if ((!txt) && cls)
{
lineClass = domline.addToLineClass(lineClass, cls); lineClass = domline.addToLineClass(lineClass, cls);
} }
else if (txt) { else if (txt)
if (href) { {
extraOpenTags = extraOpenTags+'<a href="'+ if (href)
href.replace(/\"/g, '&quot;')+'">'; {
extraCloseTags = '</a>'+extraCloseTags; extraOpenTags = extraOpenTags + '<a href="' + href.replace(/\"/g, '&quot;') + '">';
extraCloseTags = '</a>' + extraCloseTags;
} }
if (simpleTags) { if (simpleTags)
{
simpleTags.sort(); simpleTags.sort();
extraOpenTags = extraOpenTags+'<'+simpleTags.join('><')+'>'; extraOpenTags = extraOpenTags + '<' + simpleTags.join('><') + '>';
simpleTags.reverse(); simpleTags.reverse();
extraCloseTags = '</'+simpleTags.join('></')+'>'+extraCloseTags; extraCloseTags = '</' + simpleTags.join('></') + '>' + extraCloseTags;
} }
html.push('<span class="',cls||'','">',extraOpenTags, html.push('<span class="', cls || '', '">', extraOpenTags, perTextNodeProcess(domline.escapeHTML(txt)), extraCloseTags, '</span>');
perTextNodeProcess(domline.escapeHTML(txt)),
extraCloseTags,'</span>');
} }
}; };
result.clearSpans = function() { result.clearSpans = function()
{
html = []; html = [];
lineClass = ''; // non-null to cause update lineClass = ''; // non-null to cause update
result.lineMarker = 0; result.lineMarker = 0;
}; };
function writeHTML() {
function writeHTML()
{
var newHTML = perHtmlLineProcess(html.join('')); var newHTML = perHtmlLineProcess(html.join(''));
if (! newHTML) { if (!newHTML)
if ((! document) || (! optBrowser)) { {
if ((!document) || (!optBrowser))
{
newHTML += '&nbsp;'; newHTML += '&nbsp;';
} }
else if (! browser.msie) { else if (!browser.msie)
{
newHTML += '<br/>'; newHTML += '<br/>';
} }
} }
if (nonEmpty) { if (nonEmpty)
newHTML = (preHtml||'')+newHTML+(postHtml||''); {
newHTML = (preHtml || '') + newHTML + (postHtml || '');
} }
html = preHtml = postHtml = null; // free memory html = preHtml = postHtml = null; // free memory
if (newHTML !== curHTML) { if (newHTML !== curHTML)
{
curHTML = newHTML; curHTML = newHTML;
result.node.innerHTML = curHTML; result.node.innerHTML = curHTML;
} }
@ -163,14 +200,20 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) {
} }
result.prepareForAdd = writeHTML; result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML; result.finishUpdate = writeHTML;
result.getInnerHTML = function() { return curHTML || ''; }; result.getInnerHTML = function()
{
return curHTML || '';
};
return result; return result;
}; };
domline.escapeHTML = function(s) { domline.escapeHTML = function(s)
var re = /[&<>'"]/g; /']/; // stupid indentation thing {
if (! re.MAP) { var re = /[&<>'"]/g;
/']/; // stupid indentation thing
if (!re.MAP)
{
// persisted across function calls! // persisted across function calls!
re.MAP = { re.MAP = {
'&': '&amp;', '&': '&amp;',
@ -180,50 +223,67 @@ domline.escapeHTML = function(s) {
"'": '&#39;' "'": '&#39;'
}; };
} }
return s.replace(re, function(c) { return re.MAP[c]; }); return s.replace(re, function(c)
{
return re.MAP[c];
});
}; };
domline.processSpaces = function(s, doesWrap) { domline.processSpaces = function(s, doesWrap)
if (s.indexOf("<") < 0 && ! doesWrap) { {
if (s.indexOf("<") < 0 && !doesWrap)
{
// short-cut // short-cut
return s.replace(/ /g, '&nbsp;'); return s.replace(/ /g, '&nbsp;');
} }
var parts = []; var parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, function(m) { parts.push(m); }); s.replace(/<[^>]*>?| |[^ <]+/g, function(m)
if (doesWrap) { {
parts.push(m);
});
if (doesWrap)
{
var endOfLine = true; var endOfLine = true;
var beforeSpace = false; var beforeSpace = false;
// last space in a run is normal, others are nbsp, // last space in a run is normal, others are nbsp,
// end of line is nbsp // end of line is nbsp
for(var i=parts.length-1;i>=0;i--) { for (var i = parts.length - 1; i >= 0; i--)
{
var p = parts[i]; var p = parts[i];
if (p == " ") { if (p == " ")
if (endOfLine || beforeSpace) {
parts[i] = '&nbsp;'; if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false; endOfLine = false;
beforeSpace = true; beforeSpace = true;
} }
else if (p.charAt(0) != "<") { else if (p.charAt(0) != "<")
{
endOfLine = false; endOfLine = false;
beforeSpace = false; beforeSpace = false;
} }
} }
// beginning of line is nbsp // beginning of line is nbsp
for(var i=0;i<parts.length;i++) { for (var i = 0; i < parts.length; i++)
{
var p = parts[i]; var p = parts[i];
if (p == " ") { if (p == " ")
{
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
break; break;
} }
else if (p.charAt(0) != "<") { else if (p.charAt(0) != "<")
{
break; break;
} }
} }
} }
else { else
for(var i=0;i<parts.length;i++) { {
for (var i = 0; i < parts.length; i++)
{
var p = parts[i]; var p = parts[i];
if (p == " ") { if (p == " ")
{
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
} }
} }

View file

@ -19,19 +19,19 @@
// requires: plugins // requires: plugins
// requires: undefined // requires: undefined
var domline = {}; var domline = {};
domline.noop = function () domline.noop = function()
{}; {};
domline.identity = function (x) domline.identity = function(x)
{ {
return x; return x;
}; };
domline.addToLineClass = function (lineClass, cls) domline.addToLineClass = function(lineClass, cls)
{ {
// an "empty span" at any point can be used to add classes to // an "empty span" at any point can be used to add classes to
// the line, using line:className. otherwise, we ignore // the line, using line:className. otherwise, we ignore
// the span. // the span.
cls.replace(/\S+/g, function (c) cls.replace(/\S+/g, function(c)
{ {
if (c.indexOf("line:") == 0) if (c.indexOf("line:") == 0)
{ {
@ -44,7 +44,7 @@ domline.addToLineClass = function (lineClass, cls)
// if "document" is falsy we don't create a DOM node, just // if "document" is falsy we don't create a DOM node, just
// an object with innerHTML and className // an object with innerHTML and className
domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
{ {
var result = { var result = {
node: null, node: null,
@ -83,7 +83,7 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument)
var perTextNodeProcess = (doesWrap ? identity : processSpaces); var perTextNodeProcess = (doesWrap ? identity : processSpaces);
var perHtmlLineProcess = (doesWrap ? processSpaces : identity); var perHtmlLineProcess = (doesWrap ? processSpaces : identity);
var lineClass = 'ace-line'; var lineClass = 'ace-line';
result.appendSpan = function (txt, cls) result.appendSpan = function(txt, cls)
{ {
if (cls.indexOf('list') >= 0) if (cls.indexOf('list') >= 0)
{ {
@ -104,7 +104,7 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument)
var simpleTags = null; var simpleTags = null;
if (cls.indexOf('url') >= 0) if (cls.indexOf('url') >= 0)
{ {
cls = cls.replace(/(^| )url:(\S+)/g, function (x0, space, url) cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url)
{ {
href = url; href = url;
return space + "url"; return space + "url";
@ -112,7 +112,7 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument)
} }
if (cls.indexOf('tag') >= 0) if (cls.indexOf('tag') >= 0)
{ {
cls = cls.replace(/(^| )tag:(\S+)/g, function (x0, space, tag) cls = cls.replace(/(^| )tag:(\S+)/g, function(x0, space, tag)
{ {
if (!simpleTags) simpleTags = []; if (!simpleTags) simpleTags = [];
simpleTags.push(tag.toLowerCase()); simpleTags.push(tag.toLowerCase());
@ -124,7 +124,7 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument)
var extraCloseTags = ""; var extraCloseTags = "";
var plugins_; var plugins_;
if (typeof (plugins) != 'undefined') if (typeof(plugins) != 'undefined')
{ {
plugins_ = plugins; plugins_ = plugins;
} }
@ -137,7 +137,7 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument)
domline: domline, domline: domline,
cls: cls, cls: cls,
document: document document: document
}).map(function (modifier) }).map(function(modifier)
{ {
cls = modifier.cls; cls = modifier.cls;
extraOpenTags = extraOpenTags + modifier.extraOpenTags; extraOpenTags = extraOpenTags + modifier.extraOpenTags;
@ -165,7 +165,7 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument)
html.push('<span class="', cls || '', '">', extraOpenTags, perTextNodeProcess(domline.escapeHTML(txt)), extraCloseTags, '</span>'); html.push('<span class="', cls || '', '">', extraOpenTags, perTextNodeProcess(domline.escapeHTML(txt)), extraCloseTags, '</span>');
} }
}; };
result.clearSpans = function () result.clearSpans = function()
{ {
html = []; html = [];
lineClass = ''; // non-null to cause update lineClass = ''; // non-null to cause update
@ -200,7 +200,7 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument)
} }
result.prepareForAdd = writeHTML; result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML; result.finishUpdate = writeHTML;
result.getInnerHTML = function () result.getInnerHTML = function()
{ {
return curHTML || ''; return curHTML || '';
}; };
@ -208,7 +208,7 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument)
return result; return result;
}; };
domline.escapeHTML = function (s) domline.escapeHTML = function(s)
{ {
var re = /[&<>'"]/g; var re = /[&<>'"]/g;
/']/; // stupid indentation thing /']/; // stupid indentation thing
@ -223,13 +223,13 @@ domline.escapeHTML = function (s)
"'": '&#39;' "'": '&#39;'
}; };
} }
return s.replace(re, function (c) return s.replace(re, function(c)
{ {
return re.MAP[c]; return re.MAP[c];
}); });
}; };
domline.processSpaces = function (s, doesWrap) domline.processSpaces = function(s, doesWrap)
{ {
if (s.indexOf("<") < 0 && !doesWrap) if (s.indexOf("<") < 0 && !doesWrap)
{ {
@ -237,7 +237,7 @@ domline.processSpaces = function (s, doesWrap)
return s.replace(/ /g, '&nbsp;'); return s.replace(/ /g, '&nbsp;');
} }
var parts = []; var parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, function (m) s.replace(/<[^>]*>?| |[^ <]+/g, function(m)
{ {
parts.push(m); parts.push(m);
}); });

View file

@ -16,7 +16,7 @@
function makeDraggable(jqueryNodes, eventHandler) function makeDraggable(jqueryNodes, eventHandler)
{ {
jqueryNodes.each(function () jqueryNodes.each(function()
{ {
var node = $(this); var node = $(this);
var state = {}; var state = {};
@ -77,7 +77,7 @@ function makeResizableVPane(top, sep, bottom, minTop, minBottom, callback)
if (minTop === undefined) minTop = 0; if (minTop === undefined) minTop = 0;
if (minBottom === undefined) minBottom = 0; if (minBottom === undefined) minBottom = 0;
makeDraggable($(sep), function (eType, evt, state) makeDraggable($(sep), function(eType, evt, state)
{ {
if (eType == 'dragstart') if (eType == 'dragstart')
{ {
@ -125,7 +125,7 @@ function makeResizableHPane(left, sep, right, minLeft, minRight, sepWidth, sepOf
if (minLeft === undefined) minLeft = 0; if (minLeft === undefined) minLeft = 0;
if (minRight === undefined) minRight = 0; if (minRight === undefined) minRight = 0;
makeDraggable($(sep), function (eType, evt, state) makeDraggable($(sep), function(eType, evt, state)
{ {
if (eType == 'dragstart') if (eType == 'dragstart')
{ {

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,7 @@ function AttribPool()
p.attribToNum = {}; // e.g. {'foo,bar': 0} p.attribToNum = {}; // e.g. {'foo,bar': 0}
p.nextNum = 0; p.nextNum = 0;
p.putAttrib = function (attrib, dontAddIfAbsent) p.putAttrib = function(attrib, dontAddIfAbsent)
{ {
var str = String(attrib); var str = String(attrib);
if (str in p.attribToNum) if (str in p.attribToNum)
@ -42,28 +42,28 @@ function AttribPool()
return num; return num;
}; };
p.getAttrib = function (num) p.getAttrib = function(num)
{ {
var pair = p.numToAttrib[num]; var pair = p.numToAttrib[num];
if (!pair) return pair; if (!pair) return pair;
return [pair[0], pair[1]]; // return a mutable copy return [pair[0], pair[1]]; // return a mutable copy
}; };
p.getAttribKey = function (num) p.getAttribKey = function(num)
{ {
var pair = p.numToAttrib[num]; var pair = p.numToAttrib[num];
if (!pair) return ''; if (!pair) return '';
return pair[0]; return pair[0];
}; };
p.getAttribValue = function (num) p.getAttribValue = function(num)
{ {
var pair = p.numToAttrib[num]; var pair = p.numToAttrib[num];
if (!pair) return ''; if (!pair) return '';
return pair[1]; return pair[1];
}; };
p.eachAttrib = function (func) p.eachAttrib = function(func)
{ {
for (var n in p.numToAttrib) for (var n in p.numToAttrib)
{ {
@ -72,7 +72,7 @@ function AttribPool()
} }
}; };
p.toJsonable = function () p.toJsonable = function()
{ {
return { return {
numToAttrib: p.numToAttrib, numToAttrib: p.numToAttrib,
@ -80,7 +80,7 @@ function AttribPool()
}; };
}; };
p.fromJsonable = function (obj) p.fromJsonable = function(obj)
{ {
p.numToAttrib = obj.numToAttrib; p.numToAttrib = obj.numToAttrib;
p.nextNum = obj.nextNum; p.nextNum = obj.nextNum;
@ -112,35 +112,35 @@ Changeset.assert = function assert(b, msgParts)
} }
}; };
Changeset.parseNum = function (str) Changeset.parseNum = function(str)
{ {
return parseInt(str, 36); return parseInt(str, 36);
}; };
Changeset.numToString = function (num) Changeset.numToString = function(num)
{ {
return num.toString(36).toLowerCase(); return num.toString(36).toLowerCase();
}; };
Changeset.toBaseTen = function (cs) Changeset.toBaseTen = function(cs)
{ {
var dollarIndex = cs.indexOf('$'); var dollarIndex = cs.indexOf('$');
var beforeDollar = cs.substring(0, dollarIndex); var beforeDollar = cs.substring(0, dollarIndex);
var fromDollar = cs.substring(dollarIndex); var fromDollar = cs.substring(dollarIndex);
return beforeDollar.replace(/[0-9a-z]+/g, function (s) return beforeDollar.replace(/[0-9a-z]+/g, function(s)
{ {
return String(Changeset.parseNum(s)); return String(Changeset.parseNum(s));
}) + fromDollar; }) + fromDollar;
}; };
Changeset.oldLen = function (cs) Changeset.oldLen = function(cs)
{ {
return Changeset.unpack(cs).oldLen; return Changeset.unpack(cs).oldLen;
}; };
Changeset.newLen = function (cs) Changeset.newLen = function(cs)
{ {
return Changeset.unpack(cs).newLen; return Changeset.unpack(cs).newLen;
}; };
Changeset.opIterator = function (opsStr, optStartIndex) Changeset.opIterator = function(opsStr, optStartIndex)
{ {
//print(opsStr); //print(opsStr);
var regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g; var regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g;
@ -221,14 +221,14 @@ Changeset.opIterator = function (opsStr, optStartIndex)
}; };
}; };
Changeset.clearOp = function (op) Changeset.clearOp = function(op)
{ {
op.opcode = ''; op.opcode = '';
op.chars = 0; op.chars = 0;
op.lines = 0; op.lines = 0;
op.attribs = ''; op.attribs = '';
}; };
Changeset.newOp = function (optOpcode) Changeset.newOp = function(optOpcode)
{ {
return { return {
opcode: (optOpcode || ''), opcode: (optOpcode || ''),
@ -237,7 +237,7 @@ Changeset.newOp = function (optOpcode)
attribs: '' attribs: ''
}; };
}; };
Changeset.cloneOp = function (op) Changeset.cloneOp = function(op)
{ {
return { return {
opcode: op.opcode, opcode: op.opcode,
@ -246,14 +246,14 @@ Changeset.cloneOp = function (op)
attribs: op.attribs attribs: op.attribs
}; };
}; };
Changeset.copyOp = function (op1, op2) Changeset.copyOp = function(op1, op2)
{ {
op2.opcode = op1.opcode; op2.opcode = op1.opcode;
op2.chars = op1.chars; op2.chars = op1.chars;
op2.lines = op1.lines; op2.lines = op1.lines;
op2.attribs = op1.attribs; op2.attribs = op1.attribs;
}; };
Changeset.opString = function (op) Changeset.opString = function(op)
{ {
// just for debugging // just for debugging
if (!op.opcode) return 'null'; if (!op.opcode) return 'null';
@ -261,13 +261,13 @@ Changeset.opString = function (op)
assem.append(op); assem.append(op);
return assem.toString(); return assem.toString();
}; };
Changeset.stringOp = function (str) Changeset.stringOp = function(str)
{ {
// just for debugging // just for debugging
return Changeset.opIterator(str).next(); return Changeset.opIterator(str).next();
}; };
Changeset.checkRep = function (cs) Changeset.checkRep = function(cs)
{ {
// doesn't check things that require access to attrib pool (e.g. attribute order) // doesn't check things that require access to attrib pool (e.g. attribute order)
// or original string (e.g. newline positions) // or original string (e.g. newline positions)
@ -320,7 +320,7 @@ Changeset.checkRep = function (cs)
return cs; return cs;
} }
Changeset.smartOpAssembler = function () Changeset.smartOpAssembler = function()
{ {
// Like opAssembler but able to produce conforming changesets // Like opAssembler but able to produce conforming changesets
// from slightly looser input, at the cost of speed. // from slightly looser input, at the cost of speed.
@ -444,7 +444,7 @@ Changeset.smartOpAssembler = function ()
if (_opt) if (_opt)
{ {
Changeset.mergingOpAssembler = function () Changeset.mergingOpAssembler = function()
{ {
var assem = _opt.mergingOpAssembler(); var assem = _opt.mergingOpAssembler();
@ -478,7 +478,7 @@ if (_opt)
} }
else else
{ {
Changeset.mergingOpAssembler = function () Changeset.mergingOpAssembler = function()
{ {
// This assembler can be used in production; it efficiently // This assembler can be used in production; it efficiently
// merges consecutive operations that are mergeable, ignores // merges consecutive operations that are mergeable, ignores
@ -575,12 +575,11 @@ else
if (_opt) if (_opt)
{ {
Changeset.opAssembler = function () Changeset.opAssembler = function()
{ {
var assem = _opt.opAssembler(); var assem = _opt.opAssembler();
// this function allows op to be mutated later (doesn't keep a ref) // this function allows op to be mutated later (doesn't keep a ref)
function append(op) function append(op)
{ {
assem.append(op.opcode, op.chars, op.lines, op.attribs); assem.append(op.opcode, op.chars, op.lines, op.attribs);
@ -604,12 +603,11 @@ if (_opt)
} }
else else
{ {
Changeset.opAssembler = function () Changeset.opAssembler = function()
{ {
var pieces = []; var pieces = [];
// this function allows op to be mutated later (doesn't keep a ref) // this function allows op to be mutated later (doesn't keep a ref)
function append(op) function append(op)
{ {
pieces.push(op.attribs); pieces.push(op.attribs);
@ -638,7 +636,7 @@ else
}; };
} }
Changeset.stringIterator = function (str) Changeset.stringIterator = function(str)
{ {
var curIndex = 0; var curIndex = 0;
@ -680,7 +678,7 @@ Changeset.stringIterator = function (str)
}; };
}; };
Changeset.stringAssembler = function () Changeset.stringAssembler = function()
{ {
var pieces = []; var pieces = [];
@ -700,7 +698,7 @@ Changeset.stringAssembler = function ()
}; };
// "lines" need not be an array as long as it supports certain calls (lines_foo inside). // "lines" need not be an array as long as it supports certain calls (lines_foo inside).
Changeset.textLinesMutator = function (lines) Changeset.textLinesMutator = function(lines)
{ {
// Mutates lines, an array of strings, in place. // Mutates lines, an array of strings, in place.
// Mutation operations have the same constraints as changeset operations // Mutation operations have the same constraints as changeset operations
@ -743,7 +741,6 @@ Changeset.textLinesMutator = function (lines)
} }
// can be unimplemented if removeLines's return value not needed // can be unimplemented if removeLines's return value not needed
function lines_slice(start, end) function lines_slice(start, end)
{ {
if (lines.slice) if (lines.slice)
@ -1024,7 +1021,7 @@ Changeset.textLinesMutator = function (lines)
return self; return self;
}; };
Changeset.applyZip = function (in1, idx1, in2, idx2, func) Changeset.applyZip = function(in1, idx1, in2, idx2, func)
{ {
var iter1 = Changeset.opIterator(in1, idx1); var iter1 = Changeset.opIterator(in1, idx1);
var iter2 = Changeset.opIterator(in2, idx2); var iter2 = Changeset.opIterator(in2, idx2);
@ -1048,7 +1045,7 @@ Changeset.applyZip = function (in1, idx1, in2, idx2, func)
return assem.toString(); return assem.toString();
}; };
Changeset.unpack = function (cs) Changeset.unpack = function(cs)
{ {
var headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/; var headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/;
var headerMatch = headerRegex.exec(cs); var headerMatch = headerRegex.exec(cs);
@ -1071,7 +1068,7 @@ Changeset.unpack = function (cs)
}; };
}; };
Changeset.pack = function (oldLen, newLen, opsStr, bank) Changeset.pack = function(oldLen, newLen, opsStr, bank)
{ {
var lenDiff = newLen - oldLen; var lenDiff = newLen - oldLen;
var lenDiffStr = (lenDiff >= 0 ? '>' + Changeset.numToString(lenDiff) : '<' + Changeset.numToString(-lenDiff)); var lenDiffStr = (lenDiff >= 0 ? '>' + Changeset.numToString(lenDiff) : '<' + Changeset.numToString(-lenDiff));
@ -1080,7 +1077,7 @@ Changeset.pack = function (oldLen, newLen, opsStr, bank)
return a.join(''); return a.join('');
}; };
Changeset.applyToText = function (cs, str) Changeset.applyToText = function(cs, str)
{ {
var unpacked = Changeset.unpack(cs); var unpacked = Changeset.unpack(cs);
Changeset.assert(str.length == unpacked.oldLen, "mismatched apply: ", str.length, " / ", unpacked.oldLen); Changeset.assert(str.length == unpacked.oldLen, "mismatched apply: ", str.length, " / ", unpacked.oldLen);
@ -1108,7 +1105,7 @@ Changeset.applyToText = function (cs, str)
return assem.toString(); return assem.toString();
}; };
Changeset.mutateTextLines = function (cs, lines) Changeset.mutateTextLines = function(cs, lines)
{ {
var unpacked = Changeset.unpack(cs); var unpacked = Changeset.unpack(cs);
var csIter = Changeset.opIterator(unpacked.ops); var csIter = Changeset.opIterator(unpacked.ops);
@ -1133,7 +1130,7 @@ Changeset.mutateTextLines = function (cs, lines)
mut.close(); mut.close();
}; };
Changeset.composeAttributes = function (att1, att2, resultIsMutation, pool) Changeset.composeAttributes = function(att1, att2, resultIsMutation, pool)
{ {
// att1 and att2 are strings like "*3*f*1c", asMutation is a boolean. // att1 and att2 are strings like "*3*f*1c", asMutation is a boolean.
// Sometimes attribute (key,value) pairs are treated as attribute presence // Sometimes attribute (key,value) pairs are treated as attribute presence
@ -1158,12 +1155,12 @@ Changeset.composeAttributes = function (att1, att2, resultIsMutation, pool)
} }
if (!att2) return att1; if (!att2) return att1;
var atts = []; var atts = [];
att1.replace(/\*([0-9a-z]+)/g, function (_, a) att1.replace(/\*([0-9a-z]+)/g, function(_, a)
{ {
atts.push(pool.getAttrib(Changeset.parseNum(a))); atts.push(pool.getAttrib(Changeset.parseNum(a)));
return ''; return '';
}); });
att2.replace(/\*([0-9a-z]+)/g, function (_, a) att2.replace(/\*([0-9a-z]+)/g, function(_, a)
{ {
var pair = pool.getAttrib(Changeset.parseNum(a)); var pair = pool.getAttrib(Changeset.parseNum(a));
var found = false; var found = false;
@ -1201,7 +1198,7 @@ Changeset.composeAttributes = function (att1, att2, resultIsMutation, pool)
return buf.toString(); return buf.toString();
}; };
Changeset._slicerZipperFunc = function (attOp, csOp, opOut, pool) Changeset._slicerZipperFunc = function(attOp, csOp, opOut, pool)
{ {
// attOp is the op from the sequence that is being operated on, either an // attOp is the op from the sequence that is being operated on, either an
// attribution string or the earlier of two changesets being composed. // attribution string or the earlier of two changesets being composed.
@ -1304,11 +1301,11 @@ Changeset._slicerZipperFunc = function (attOp, csOp, opOut, pool)
} }
}; };
Changeset.applyToAttribution = function (cs, astr, pool) Changeset.applyToAttribution = function(cs, astr, pool)
{ {
var unpacked = Changeset.unpack(cs); var unpacked = Changeset.unpack(cs);
return Changeset.applyZip(astr, 0, unpacked.ops, 0, function (op1, op2, opOut) return Changeset.applyZip(astr, 0, unpacked.ops, 0, function(op1, op2, opOut)
{ {
return Changeset._slicerZipperFunc(op1, op2, opOut, pool); return Changeset._slicerZipperFunc(op1, op2, opOut, pool);
}); });
@ -1320,7 +1317,7 @@ Changeset.applyToAttribution = function (cs, astr, pool)
};*/ };*/
Changeset.mutateAttributionLines = function (cs, lines, pool) Changeset.mutateAttributionLines = function(cs, lines, pool)
{ {
//dmesg(cs); //dmesg(cs);
//dmesg(lines.toSource()+" ->"); //dmesg(lines.toSource()+" ->");
@ -1438,7 +1435,7 @@ Changeset.mutateAttributionLines = function (cs, lines, pool)
//dmesg("-> "+lines.toSource()); //dmesg("-> "+lines.toSource());
}; };
Changeset.joinAttributionLines = function (theAlines) Changeset.joinAttributionLines = function(theAlines)
{ {
var assem = Changeset.mergingOpAssembler(); var assem = Changeset.mergingOpAssembler();
for (var i = 0; i < theAlines.length; i++) for (var i = 0; i < theAlines.length; i++)
@ -1453,7 +1450,7 @@ Changeset.joinAttributionLines = function (theAlines)
return assem.toString(); return assem.toString();
}; };
Changeset.splitAttributionLines = function (attrOps, text) Changeset.splitAttributionLines = function(attrOps, text)
{ {
var iter = Changeset.opIterator(attrOps); var iter = Changeset.opIterator(attrOps);
var assem = Changeset.mergingOpAssembler(); var assem = Changeset.mergingOpAssembler();
@ -1497,12 +1494,12 @@ Changeset.splitAttributionLines = function (attrOps, text)
return lines; return lines;
}; };
Changeset.splitTextLines = function (text) Changeset.splitTextLines = function(text)
{ {
return text.match(/[^\n]*(?:\n|[^\n]$)/g); return text.match(/[^\n]*(?:\n|[^\n]$)/g);
}; };
Changeset.compose = function (cs1, cs2, pool) Changeset.compose = function(cs1, cs2, pool)
{ {
var unpacked1 = Changeset.unpack(cs1); var unpacked1 = Changeset.unpack(cs1);
var unpacked2 = Changeset.unpack(cs2); var unpacked2 = Changeset.unpack(cs2);
@ -1514,7 +1511,7 @@ Changeset.compose = function (cs1, cs2, pool)
var bankIter2 = Changeset.stringIterator(unpacked2.charBank); var bankIter2 = Changeset.stringIterator(unpacked2.charBank);
var bankAssem = Changeset.stringAssembler(); var bankAssem = Changeset.stringAssembler();
var newOps = Changeset.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function (op1, op2, opOut) var newOps = Changeset.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function(op1, op2, opOut)
{ {
//var debugBuilder = Changeset.stringAssembler(); //var debugBuilder = Changeset.stringAssembler();
//debugBuilder.append(Changeset.opString(op1)); //debugBuilder.append(Changeset.opString(op1));
@ -1551,7 +1548,7 @@ Changeset.compose = function (cs1, cs2, pool)
return Changeset.pack(len1, len3, newOps, bankAssem.toString()); return Changeset.pack(len1, len3, newOps, bankAssem.toString());
}; };
Changeset.attributeTester = function (attribPair, pool) Changeset.attributeTester = function(attribPair, pool)
{ {
// returns a function that tests if a string of attributes // returns a function that tests if a string of attributes
// (e.g. *3*4) contains a given attribute key,value that // (e.g. *3*4) contains a given attribute key,value that
@ -1568,7 +1565,7 @@ Changeset.attributeTester = function (attribPair, pool)
else else
{ {
var re = new RegExp('\\*' + Changeset.numToString(attribNum) + '(?!\\w)'); var re = new RegExp('\\*' + Changeset.numToString(attribNum) + '(?!\\w)');
return function (attribs) return function(attribs)
{ {
return re.test(attribs); return re.test(attribs);
}; };
@ -1580,12 +1577,12 @@ Changeset.attributeTester = function (attribPair, pool)
} }
}; };
Changeset.identity = function (N) Changeset.identity = function(N)
{ {
return Changeset.pack(N, N, "", ""); return Changeset.pack(N, N, "", "");
}; };
Changeset.makeSplice = function (oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool) Changeset.makeSplice = function(oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool)
{ {
var oldLen = oldFullText.length; var oldLen = oldFullText.length;
@ -1608,7 +1605,7 @@ Changeset.makeSplice = function (oldFullText, spliceStart, numRemoved, newText,
return Changeset.pack(oldLen, newLen, assem.toString(), newText); return Changeset.pack(oldLen, newLen, assem.toString(), newText);
}; };
Changeset.toSplices = function (cs) Changeset.toSplices = function(cs)
{ {
// get a list of splices, [startChar, endChar, newText] // get a list of splices, [startChar, endChar, newText]
var unpacked = Changeset.unpack(cs); var unpacked = Changeset.unpack(cs);
@ -1648,7 +1645,7 @@ Changeset.toSplices = function (cs)
return splices; return splices;
}; };
Changeset.characterRangeFollow = function (cs, startChar, endChar, insertionsAfter) Changeset.characterRangeFollow = function(cs, startChar, endChar, insertionsAfter)
{ {
var newStartChar = startChar; var newStartChar = startChar;
var newEndChar = endChar; var newEndChar = endChar;
@ -1708,7 +1705,7 @@ Changeset.characterRangeFollow = function (cs, startChar, endChar, insertionsAft
return [newStartChar, newEndChar]; return [newStartChar, newEndChar];
}; };
Changeset.moveOpsToNewPool = function (cs, oldPool, newPool) Changeset.moveOpsToNewPool = function(cs, oldPool, newPool)
{ {
// works on changeset or attribution string // works on changeset or attribution string
var dollarPos = cs.indexOf('$'); var dollarPos = cs.indexOf('$');
@ -1719,7 +1716,7 @@ Changeset.moveOpsToNewPool = function (cs, oldPool, newPool)
var upToDollar = cs.substring(0, dollarPos); var upToDollar = cs.substring(0, dollarPos);
var fromDollar = cs.substring(dollarPos); var fromDollar = cs.substring(dollarPos);
// order of attribs stays the same // order of attribs stays the same
return upToDollar.replace(/\*([0-9a-z]+)/g, function (_, a) return upToDollar.replace(/\*([0-9a-z]+)/g, function(_, a)
{ {
var oldNum = Changeset.parseNum(a); var oldNum = Changeset.parseNum(a);
var pair = oldPool.getAttrib(oldNum); var pair = oldPool.getAttrib(oldNum);
@ -1728,7 +1725,7 @@ Changeset.moveOpsToNewPool = function (cs, oldPool, newPool)
}) + fromDollar; }) + fromDollar;
}; };
Changeset.makeAttribution = function (text) Changeset.makeAttribution = function(text)
{ {
var assem = Changeset.smartOpAssembler(); var assem = Changeset.smartOpAssembler();
assem.appendOpWithText('+', text); assem.appendOpWithText('+', text);
@ -1736,7 +1733,7 @@ Changeset.makeAttribution = function (text)
}; };
// callable on a changeset, attribution string, or attribs property of an op // callable on a changeset, attribution string, or attribs property of an op
Changeset.eachAttribNumber = function (cs, func) Changeset.eachAttribNumber = function(cs, func)
{ {
var dollarPos = cs.indexOf('$'); var dollarPos = cs.indexOf('$');
if (dollarPos < 0) if (dollarPos < 0)
@ -1745,7 +1742,7 @@ Changeset.eachAttribNumber = function (cs, func)
} }
var upToDollar = cs.substring(0, dollarPos); var upToDollar = cs.substring(0, dollarPos);
upToDollar.replace(/\*([0-9a-z]+)/g, function (_, a) upToDollar.replace(/\*([0-9a-z]+)/g, function(_, a)
{ {
func(Changeset.parseNum(a)); func(Changeset.parseNum(a));
return ''; return '';
@ -1754,12 +1751,12 @@ Changeset.eachAttribNumber = function (cs, func)
// callable on a changeset, attribution string, or attribs property of an op, // callable on a changeset, attribution string, or attribs property of an op,
// though it may easily create adjacent ops that can be merged. // though it may easily create adjacent ops that can be merged.
Changeset.filterAttribNumbers = function (cs, filter) Changeset.filterAttribNumbers = function(cs, filter)
{ {
return Changeset.mapAttribNumbers(cs, filter); return Changeset.mapAttribNumbers(cs, filter);
}; };
Changeset.mapAttribNumbers = function (cs, func) Changeset.mapAttribNumbers = function(cs, func)
{ {
var dollarPos = cs.indexOf('$'); var dollarPos = cs.indexOf('$');
if (dollarPos < 0) if (dollarPos < 0)
@ -1768,7 +1765,7 @@ Changeset.mapAttribNumbers = function (cs, func)
} }
var upToDollar = cs.substring(0, dollarPos); var upToDollar = cs.substring(0, dollarPos);
var newUpToDollar = upToDollar.replace(/\*([0-9a-z]+)/g, function (s, a) var newUpToDollar = upToDollar.replace(/\*([0-9a-z]+)/g, function(s, a)
{ {
var n = func(Changeset.parseNum(a)); var n = func(Changeset.parseNum(a));
if (n === true) if (n === true)
@ -1788,7 +1785,7 @@ Changeset.mapAttribNumbers = function (cs, func)
return newUpToDollar + cs.substring(dollarPos); return newUpToDollar + cs.substring(dollarPos);
}; };
Changeset.makeAText = function (text, attribs) Changeset.makeAText = function(text, attribs)
{ {
return { return {
text: text, text: text,
@ -1796,7 +1793,7 @@ Changeset.makeAText = function (text, attribs)
}; };
}; };
Changeset.applyToAText = function (cs, atext, pool) Changeset.applyToAText = function(cs, atext, pool)
{ {
return { return {
text: Changeset.applyToText(cs, atext.text), text: Changeset.applyToText(cs, atext.text),
@ -1804,7 +1801,7 @@ Changeset.applyToAText = function (cs, atext, pool)
}; };
}; };
Changeset.cloneAText = function (atext) Changeset.cloneAText = function(atext)
{ {
return { return {
text: atext.text, text: atext.text,
@ -1812,13 +1809,13 @@ Changeset.cloneAText = function (atext)
}; };
}; };
Changeset.copyAText = function (atext1, atext2) Changeset.copyAText = function(atext1, atext2)
{ {
atext2.text = atext1.text; atext2.text = atext1.text;
atext2.attribs = atext1.attribs; atext2.attribs = atext1.attribs;
}; };
Changeset.appendATextToAssembler = function (atext, assem) Changeset.appendATextToAssembler = function(atext, assem)
{ {
// intentionally skips last newline char of atext // intentionally skips last newline char of atext
var iter = Changeset.opIterator(atext.attribs); var iter = Changeset.opIterator(atext.attribs);
@ -1860,7 +1857,7 @@ Changeset.appendATextToAssembler = function (atext, assem)
} }
}; };
Changeset.prepareForWire = function (cs, pool) Changeset.prepareForWire = function(cs, pool)
{ {
var newPool = new AttribPool(); var newPool = new AttribPool();
var newCs = Changeset.moveOpsToNewPool(cs, pool, newPool); var newCs = Changeset.moveOpsToNewPool(cs, pool, newPool);
@ -1870,23 +1867,23 @@ Changeset.prepareForWire = function (cs, pool)
}; };
}; };
Changeset.isIdentity = function (cs) Changeset.isIdentity = function(cs)
{ {
var unpacked = Changeset.unpack(cs); var unpacked = Changeset.unpack(cs);
return unpacked.ops == "" && unpacked.oldLen == unpacked.newLen; return unpacked.ops == "" && unpacked.oldLen == unpacked.newLen;
}; };
Changeset.opAttributeValue = function (op, key, pool) Changeset.opAttributeValue = function(op, key, pool)
{ {
return Changeset.attribsAttributeValue(op.attribs, key, pool); return Changeset.attribsAttributeValue(op.attribs, key, pool);
}; };
Changeset.attribsAttributeValue = function (attribs, key, pool) Changeset.attribsAttributeValue = function(attribs, key, pool)
{ {
var value = ''; var value = '';
if (attribs) if (attribs)
{ {
Changeset.eachAttribNumber(attribs, function (n) Changeset.eachAttribNumber(attribs, function(n)
{ {
if (pool.getAttribKey(n) == key) if (pool.getAttribKey(n) == key)
{ {
@ -1897,7 +1894,7 @@ Changeset.attribsAttributeValue = function (attribs, key, pool)
return value; return value;
}; };
Changeset.builder = function (oldLen) Changeset.builder = function(oldLen)
{ {
var assem = Changeset.smartOpAssembler(); var assem = Changeset.smartOpAssembler();
var o = Changeset.newOp(); var o = Changeset.newOp();
@ -1905,7 +1902,7 @@ Changeset.builder = function (oldLen)
var self = { var self = {
// attribs are [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in latter case) // attribs are [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in latter case)
keep: function (N, L, attribs, pool) keep: function(N, L, attribs, pool)
{ {
o.opcode = '='; o.opcode = '=';
o.attribs = (attribs && Changeset.makeAttribsString('=', attribs, pool)) || ''; o.attribs = (attribs && Changeset.makeAttribsString('=', attribs, pool)) || '';
@ -1914,18 +1911,18 @@ Changeset.builder = function (oldLen)
assem.append(o); assem.append(o);
return self; return self;
}, },
keepText: function (text, attribs, pool) keepText: function(text, attribs, pool)
{ {
assem.appendOpWithText('=', text, attribs, pool); assem.appendOpWithText('=', text, attribs, pool);
return self; return self;
}, },
insert: function (text, attribs, pool) insert: function(text, attribs, pool)
{ {
assem.appendOpWithText('+', text, attribs, pool); assem.appendOpWithText('+', text, attribs, pool);
charBank.append(text); charBank.append(text);
return self; return self;
}, },
remove: function (N, L) remove: function(N, L)
{ {
o.opcode = '-'; o.opcode = '-';
o.attribs = ''; o.attribs = '';
@ -1934,7 +1931,7 @@ Changeset.builder = function (oldLen)
assem.append(o); assem.append(o);
return self; return self;
}, },
toString: function () toString: function()
{ {
assem.endDocument(); assem.endDocument();
var newLen = oldLen + assem.getLengthChange(); var newLen = oldLen + assem.getLengthChange();
@ -1945,7 +1942,7 @@ Changeset.builder = function (oldLen)
return self; return self;
}; };
Changeset.makeAttribsString = function (opcode, attribs, pool) Changeset.makeAttribsString = function(opcode, attribs, pool)
{ {
// makeAttribsString(opcode, '*3') or makeAttribsString(opcode, [['foo','bar']], myPool) work // makeAttribsString(opcode, '*3') or makeAttribsString(opcode, [['foo','bar']], myPool) work
if (!attribs) if (!attribs)
@ -1977,7 +1974,7 @@ Changeset.makeAttribsString = function (opcode, attribs, pool)
}; };
// like "substring" but on a single-line attribution string // like "substring" but on a single-line attribution string
Changeset.subattribution = function (astr, start, optEnd) Changeset.subattribution = function(astr, start, optEnd)
{ {
var iter = Changeset.opIterator(astr, 0); var iter = Changeset.opIterator(astr, 0);
var assem = Changeset.smartOpAssembler(); var assem = Changeset.smartOpAssembler();
@ -2035,13 +2032,12 @@ Changeset.subattribution = function (astr, start, optEnd)
return assem.toString(); return assem.toString();
}; };
Changeset.inverse = function (cs, lines, alines, pool) Changeset.inverse = function(cs, lines, alines, pool)
{ {
// lines and alines are what the changeset is meant to apply to. // lines and alines are what the changeset is meant to apply to.
// They may be arrays or objects with .get(i) and .length methods. // They may be arrays or objects with .get(i) and .length methods.
// They include final newlines on lines. // They include final newlines on lines.
function lines_get(idx) function lines_get(idx)
{ {
if (lines.get) if (lines.get)
@ -2164,7 +2160,7 @@ Changeset.inverse = function (cs, lines, alines, pool)
{ {
if (curLineOpIter && curLineOpIterLine == curLine) if (curLineOpIter && curLineOpIterLine == curLine)
{ {
consumeAttribRuns(N, function () consumeAttribRuns(N, function()
{}); {});
} }
else else
@ -2197,7 +2193,7 @@ Changeset.inverse = function (cs, lines, alines, pool)
function cachedStrFunc(func) function cachedStrFunc(func)
{ {
var cache = {}; var cache = {};
return function (s) return function(s)
{ {
if (!cache[s]) if (!cache[s])
{ {
@ -2218,12 +2214,12 @@ Changeset.inverse = function (cs, lines, alines, pool)
{ {
attribKeys.length = 0; attribKeys.length = 0;
attribValues.length = 0; attribValues.length = 0;
Changeset.eachAttribNumber(csOp.attribs, function (n) Changeset.eachAttribNumber(csOp.attribs, function(n)
{ {
attribKeys.push(pool.getAttribKey(n)); attribKeys.push(pool.getAttribKey(n));
attribValues.push(pool.getAttribValue(n)); attribValues.push(pool.getAttribValue(n));
}); });
var undoBackToAttribs = cachedStrFunc(function (attribs) var undoBackToAttribs = cachedStrFunc(function(attribs)
{ {
var backAttribs = []; var backAttribs = [];
for (var i = 0; i < attribKeys.length; i++) for (var i = 0; i < attribKeys.length; i++)
@ -2238,7 +2234,7 @@ Changeset.inverse = function (cs, lines, alines, pool)
} }
return Changeset.makeAttribsString('=', backAttribs, pool); return Changeset.makeAttribsString('=', backAttribs, pool);
}); });
consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) consumeAttribRuns(csOp.chars, function(len, attribs, endsLine)
{ {
builder.keep(len, endsLine ? 1 : 0, undoBackToAttribs(attribs)); builder.keep(len, endsLine ? 1 : 0, undoBackToAttribs(attribs));
}); });
@ -2257,7 +2253,7 @@ Changeset.inverse = function (cs, lines, alines, pool)
{ {
var textBank = nextText(csOp.chars); var textBank = nextText(csOp.chars);
var textBankIndex = 0; var textBankIndex = 0;
consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) consumeAttribRuns(csOp.chars, function(len, attribs, endsLine)
{ {
builder.insert(textBank.substr(textBankIndex, len), attribs); builder.insert(textBank.substr(textBankIndex, len), attribs);
textBankIndex += len; textBankIndex += len;

View file

@ -158,266 +158,262 @@
// Create a JSON object only if one does not already exist. We create the // Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables. // methods in a closure to avoid creating global variables.
var JSON; var JSON;
if (!JSON) { if (!JSON)
{
JSON = {}; JSON = {};
} }
(function () { (function()
{
"use strict"; "use strict";
function f(n) { function f(n)
{
// Format integers to have at least two digits. // Format integers to have at least two digits.
return n < 10 ? '0' + n : n; return n < 10 ? '0' + n : n;
} }
if (typeof Date.prototype.toJSON !== 'function') { if (typeof Date.prototype.toJSON !== 'function')
{
Date.prototype.toJSON = function (key) { Date.prototype.toJSON = function(key)
{
return isFinite(this.valueOf()) ? return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null;
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
}; };
String.prototype.toJSON = String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function(key)
Number.prototype.toJSON = {
Boolean.prototype.toJSON = function (key) {
return this.valueOf(); return this.valueOf();
}; };
} }
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap, gap, indent, meta = { // table of character substitutions
indent,
meta = { // table of character substitutions
'\b': '\\b', '\b': '\\b',
'\t': '\\t', '\t': '\\t',
'\n': '\\n', '\n': '\\n',
'\f': '\\f', '\f': '\\f',
'\r': '\\r', '\r': '\\r',
'"' : '\\"', '"': '\\"',
'\\': '\\\\' '\\': '\\\\'
}, },
rep; rep;
function quote(string) { function quote(string)
{
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0; escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a) { return escapable.test(string) ? '"' + string.replace(escapable, function(a)
{
var c = meta[a]; var c = meta[a];
return typeof c === 'string' ? c : return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"'; }) + '"' : '"' + string + '"';
} }
function str(key, holder) { function str(key, holder)
{
// Produce a string from holder[key].
// Produce a string from holder[key].
var i, // The loop counter. var i, // The loop counter.
k, // The member key. k, // The member key.
v, // The member value. v, // The member value.
length, length, mind = gap,
mind = gap, partial, value = holder[key];
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value. // If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' && typeof value.toJSON === 'function')
if (value && typeof value === 'object' && {
typeof value.toJSON === 'function') {
value = value.toJSON(key); value = value.toJSON(key);
} }
// If we were called with a replacer function, then call the replacer to // If we were called with a replacer function, then call the replacer to
// obtain a replacement value. // obtain a replacement value.
if (typeof rep === 'function')
if (typeof rep === 'function') { {
value = rep.call(holder, key, value); value = rep.call(holder, key, value);
} }
// What happens next depends on the value's type. // What happens next depends on the value's type.
switch (typeof value)
switch (typeof value) { {
case 'string': case 'string':
return quote(value); return quote(value);
case 'number': case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null. // JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null'; return isFinite(value) ? String(value) : 'null';
case 'boolean': case 'boolean':
case 'null': case 'null':
// If the value is a boolean or null, convert it to a string. Note: // If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in // typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday. // the remote chance that this gets fixed someday.
return String(value); return String(value);
// If the type is 'object', we might be dealing with an object or an array or // If the type is 'object', we might be dealing with an object or an array or
// null. // null.
case 'object': case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object', // Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case. // so watch out for that case.
if (!value)
if (!value) { {
return 'null'; return 'null';
} }
// Make an array to hold the partial results of stringifying this object value. // Make an array to hold the partial results of stringifying this object value.
gap += indent; gap += indent;
partial = []; partial = [];
// Is the value an array? // Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]')
if (Object.prototype.toString.apply(value) === '[object Array]') { {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length; length = value.length;
for (i = 0; i < length; i += 1) { for (i = 0; i < length; i += 1)
{
partial[i] = str(i, value) || 'null'; partial[i] = str(i, value) || 'null';
} }
// Join all of the elements together, separated with commas, and wrap them in // Join all of the elements together, separated with commas, and wrap them in
// brackets. // brackets.
v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']';
v = partial.length === 0 ? '[]' : gap ?
'[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
'[' + partial.join(',') + ']';
gap = mind; gap = mind;
return v; return v;
} }
// If the replacer is an array, use it to select the members to be stringified. // If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object')
if (rep && typeof rep === 'object') { {
length = rep.length; length = rep.length;
for (i = 0; i < length; i += 1) { for (i = 0; i < length; i += 1)
if (typeof rep[i] === 'string') { {
if (typeof rep[i] === 'string')
{
k = rep[i]; k = rep[i];
v = str(k, value); v = str(k, value);
if (v) { if (v)
{
partial.push(quote(k) + (gap ? ': ' : ':') + v); partial.push(quote(k) + (gap ? ': ' : ':') + v);
} }
} }
} }
} else { }
else
{
// Otherwise, iterate through all of the keys in the object. // Otherwise, iterate through all of the keys in the object.
for (k in value)
for (k in value) { {
if (Object.prototype.hasOwnProperty.call(value, k)) { if (Object.prototype.hasOwnProperty.call(value, k))
{
v = str(k, value); v = str(k, value);
if (v) { if (v)
{
partial.push(quote(k) + (gap ? ': ' : ':') + v); partial.push(quote(k) + (gap ? ': ' : ':') + v);
} }
} }
} }
} }
// Join all of the member texts together, separated with commas, // Join all of the member texts together, separated with commas,
// and wrap them in braces. // and wrap them in braces.
v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}';
v = partial.length === 0 ? '{}' : gap ?
'{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
'{' + partial.join(',') + '}';
gap = mind; gap = mind;
return v; return v;
} }
} }
// If the JSON object does not yet have a stringify method, give it one. // If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function')
if (typeof JSON.stringify !== 'function') { {
JSON.stringify = function (value, replacer, space) { JSON.stringify = function(value, replacer, space)
{
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i; var i;
gap = ''; gap = '';
indent = ''; indent = '';
// If the space parameter is a number, make an indent string containing that // If the space parameter is a number, make an indent string containing that
// many spaces. // many spaces.
if (typeof space === 'number')
if (typeof space === 'number') { {
for (i = 0; i < space; i += 1) { for (i = 0; i < space; i += 1)
{
indent += ' '; indent += ' ';
} }
// If the space parameter is a string, it will be used as the indent string. // If the space parameter is a string, it will be used as the indent string.
}
} else if (typeof space === 'string') { else if (typeof space === 'string')
{
indent = space; indent = space;
} }
// If there is a replacer, it must be a function or an array. // If there is a replacer, it must be a function or an array.
// Otherwise, throw an error. // Otherwise, throw an error.
rep = replacer; rep = replacer;
if (replacer && typeof replacer !== 'function' && if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number'))
(typeof replacer !== 'object' || {
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify'); throw new Error('JSON.stringify');
} }
// Make a fake root object containing our value under the key of ''. // Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value. // Return the result of stringifying the value.
return str('', {
return str('', {'': value}); '': value
});
}; };
} }
// If the JSON object does not yet have a parse method, give it one. // If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function')
if (typeof JSON.parse !== 'function') { {
JSON.parse = function (text, reviver) { JSON.parse = function(text, reviver)
{
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j; var j;
function walk(holder, key) { function walk(holder, key)
{
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key]; var k, v, value = holder[key];
if (value && typeof value === 'object') { if (value && typeof value === 'object')
for (k in value) { {
if (Object.prototype.hasOwnProperty.call(value, k)) { for (k in value)
{
if (Object.prototype.hasOwnProperty.call(value, k))
{
v = walk(value, k); v = walk(value, k);
if (v !== undefined) { if (v !== undefined)
{
value[k] = v; value[k] = v;
} else { }
else
{
delete value[k]; delete value[k];
} }
} }
@ -427,53 +423,48 @@ if (!JSON) {
} }
// Parsing happens in four stages. In the first stage, we replace certain // Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters // Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings. // incorrectly, either silently deleting them, or treating them as line endings.
text = String(text); text = String(text);
cx.lastIndex = 0; cx.lastIndex = 0;
if (cx.test(text)) { if (cx.test(text))
text = text.replace(cx, function (a) { {
return '\\u' + text = text.replace(cx, function(a)
('0000' + a.charCodeAt(0).toString(16)).slice(-4); {
return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}); });
} }
// In the second stage, we run the text against regular expressions that look // In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new' // for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation. // because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms. // But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we
// crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all
// replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally,
// open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or
// we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, '')))
{
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')'); j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing // In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation. // each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ? walk(
return typeof reviver === 'function' ? {
walk({'': j}, '') : j; '': j
}, '') : j;
} }
// If the text is not JSON parseable, then a SyntaxError is thrown. // If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse'); throw new SyntaxError('JSON.parse');
}; };
} }

View file

@ -1,7 +1,6 @@
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.linestylefilter // THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.linestylefilter
// %APPJET%: import("etherpad.collab.ace.easysync2.Changeset"); // %APPJET%: import("etherpad.collab.ace.easysync2.Changeset");
// %APPJET%: import("etherpad.admin.plugins"); // %APPJET%: import("etherpad.admin.plugins");
/** /**
* Copyright 2009 Google Inc. * Copyright 2009 Google Inc.
* *
@ -22,32 +21,36 @@
// requires: top // requires: top
// requires: plugins // requires: plugins
// requires: undefined // requires: undefined
var linestylefilter = {}; var linestylefilter = {};
linestylefilter.ATTRIB_CLASSES = { linestylefilter.ATTRIB_CLASSES = {
'bold':'tag:b', 'bold': 'tag:b',
'italic':'tag:i', 'italic': 'tag:i',
'underline':'tag:u', 'underline': 'tag:u',
'strikethrough':'tag:s' 'strikethrough': 'tag:s'
}; };
linestylefilter.getAuthorClassName = function(author) { linestylefilter.getAuthorClassName = function(author)
return "author-"+author.replace(/[^a-y0-9]/g, function(c) { {
return "author-" + author.replace(/[^a-y0-9]/g, function(c)
{
if (c == ".") return "-"; if (c == ".") return "-";
return 'z'+c.charCodeAt(0)+'z'; return 'z' + c.charCodeAt(0) + 'z';
}); });
}; };
// lineLength is without newline; aline includes newline, // lineLength is without newline; aline includes newline,
// but may be falsy if lineLength == 0 // but may be falsy if lineLength == 0
linestylefilter.getLineStyleFilter = function(lineLength, aline, linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFunc, apool)
textAndClassFunc, apool) { {
var plugins_; var plugins_;
if (typeof(plugins)!='undefined') { if (typeof(plugins) != 'undefined')
{
plugins_ = plugins; plugins_ = plugins;
} else { }
else
{
plugins_ = parent.parent.plugins; plugins_ = parent.parent.plugins;
} }
@ -55,29 +58,43 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline,
var nextAfterAuthorColors = textAndClassFunc; var nextAfterAuthorColors = textAndClassFunc;
var authorColorFunc = (function() { var authorColorFunc = (function()
{
var lineEnd = lineLength; var lineEnd = lineLength;
var curIndex = 0; var curIndex = 0;
var extraClasses; var extraClasses;
var leftInAuthor; var leftInAuthor;
function attribsToClasses(attribs) { function attribsToClasses(attribs)
{
var classes = ''; var classes = '';
Changeset.eachAttribNumber(attribs, function(n) { Changeset.eachAttribNumber(attribs, function(n)
{
var key = apool.getAttribKey(n); var key = apool.getAttribKey(n);
if (key) { if (key)
{
var value = apool.getAttribValue(n); var value = apool.getAttribValue(n);
if (value) { if (value)
if (key == 'author') { {
classes += ' '+linestylefilter.getAuthorClassName(value); if (key == 'author')
{
classes += ' ' + linestylefilter.getAuthorClassName(value);
} }
else if (key == 'list') { else if (key == 'list')
classes += ' list:'+value; {
classes += ' list:' + value;
} }
else if (linestylefilter.ATTRIB_CLASSES[key]) { else if (linestylefilter.ATTRIB_CLASSES[key])
classes += ' '+linestylefilter.ATTRIB_CLASSES[key]; {
} else { classes += ' ' + linestylefilter.ATTRIB_CLASSES[key];
classes += plugins_.callHookStr("aceAttribsToClasses", {linestylefilter:linestylefilter, key:key, value:value}, " ", " ", ""); }
else
{
classes += plugins_.callHookStr("aceAttribsToClasses", {
linestylefilter: linestylefilter,
key: key,
value: value
}, " ", " ", "");
} }
} }
} }
@ -87,17 +104,23 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline,
var attributionIter = Changeset.opIterator(aline); var attributionIter = Changeset.opIterator(aline);
var nextOp, nextOpClasses; var nextOp, nextOpClasses;
function goNextOp() {
function goNextOp()
{
nextOp = attributionIter.next(); nextOp = attributionIter.next();
nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs)); nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs));
} }
goNextOp(); goNextOp();
function nextClasses() {
if (curIndex < lineEnd) { function nextClasses()
{
if (curIndex < lineEnd)
{
extraClasses = nextOpClasses; extraClasses = nextOpClasses;
leftInAuthor = nextOp.chars; leftInAuthor = nextOp.chars;
goNextOp(); goNextOp();
while (nextOp.opcode && nextOpClasses == extraClasses) { while (nextOp.opcode && nextOpClasses == extraClasses)
{
leftInAuthor += nextOp.chars; leftInAuthor += nextOp.chars;
goNextOp(); goNextOp();
} }
@ -105,22 +128,27 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline,
} }
nextClasses(); nextClasses();
return function(txt, cls) { return function(txt, cls)
while (txt.length > 0) { {
if (leftInAuthor <= 0) { while (txt.length > 0)
{
if (leftInAuthor <= 0)
{
// prevent infinite loop if something funny's going on // prevent infinite loop if something funny's going on
return nextAfterAuthorColors(txt, cls); return nextAfterAuthorColors(txt, cls);
} }
var spanSize = txt.length; var spanSize = txt.length;
if (spanSize > leftInAuthor) { if (spanSize > leftInAuthor)
{
spanSize = leftInAuthor; spanSize = leftInAuthor;
} }
var curTxt = txt.substring(0, spanSize); var curTxt = txt.substring(0, spanSize);
txt = txt.substring(spanSize); txt = txt.substring(spanSize);
nextAfterAuthorColors(curTxt, (cls&&cls+" ")+extraClasses); nextAfterAuthorColors(curTxt, (cls && cls + " ") + extraClasses);
curIndex += spanSize; curIndex += spanSize;
leftInAuthor -= spanSize; leftInAuthor -= spanSize;
if (leftInAuthor == 0) { if (leftInAuthor == 0)
{
nextClasses(); nextClasses();
} }
} }
@ -129,33 +157,38 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline,
return authorColorFunc; return authorColorFunc;
}; };
linestylefilter.getAtSignSplitterFilter = function(lineText, linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc)
textAndClassFunc) { {
var at = /@/g; var at = /@/g;
at.lastIndex = 0; at.lastIndex = 0;
var splitPoints = null; var splitPoints = null;
var execResult; var execResult;
while ((execResult = at.exec(lineText))) { while ((execResult = at.exec(lineText)))
if (! splitPoints) { {
if (!splitPoints)
{
splitPoints = []; splitPoints = [];
} }
splitPoints.push(execResult.index); splitPoints.push(execResult.index);
} }
if (! splitPoints) return textAndClassFunc; if (!splitPoints) return textAndClassFunc;
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
splitPoints);
}; };
linestylefilter.getRegexpFilter = function (regExp, tag) { linestylefilter.getRegexpFilter = function(regExp, tag)
return function (lineText, textAndClassFunc) { {
return function(lineText, textAndClassFunc)
{
regExp.lastIndex = 0; regExp.lastIndex = 0;
var regExpMatchs = null; var regExpMatchs = null;
var splitPoints = null; var splitPoints = null;
var execResult; var execResult;
while ((execResult = regExp.exec(lineText))) { while ((execResult = regExp.exec(lineText)))
if (! regExpMatchs) { {
if (!regExpMatchs)
{
regExpMatchs = []; regExpMatchs = [];
splitPoints = []; splitPoints = [];
} }
@ -165,73 +198,85 @@ linestylefilter.getRegexpFilter = function (regExp, tag) {
splitPoints.push(startIndex, startIndex + regExpMatch.length); splitPoints.push(startIndex, startIndex + regExpMatch.length);
} }
if (! regExpMatchs) return textAndClassFunc; if (!regExpMatchs) return textAndClassFunc;
function regExpMatchForIndex(idx) { function regExpMatchForIndex(idx)
for(var k=0; k<regExpMatchs.length; k++) { {
for (var k = 0; k < regExpMatchs.length; k++)
{
var u = regExpMatchs[k]; var u = regExpMatchs[k];
if (idx >= u[0] && idx < u[0]+u[1].length) { if (idx >= u[0] && idx < u[0] + u[1].length)
{
return u[1]; return u[1];
} }
} }
return false; return false;
} }
var handleRegExpMatchsAfterSplit = (function() { var handleRegExpMatchsAfterSplit = (function()
{
var curIndex = 0; var curIndex = 0;
return function(txt, cls) { return function(txt, cls)
{
var txtlen = txt.length; var txtlen = txt.length;
var newCls = cls; var newCls = cls;
var regExpMatch = regExpMatchForIndex(curIndex); var regExpMatch = regExpMatchForIndex(curIndex);
if (regExpMatch) { if (regExpMatch)
newCls += " "+tag+":"+regExpMatch; {
newCls += " " + tag + ":" + regExpMatch;
} }
textAndClassFunc(txt, newCls); textAndClassFunc(txt, newCls);
curIndex += txtlen; curIndex += txtlen;
}; };
})(); })();
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, splitPoints);
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_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_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.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.getURLFilter = linestylefilter.getRegexpFilter(
linestylefilter.REGEX_URL, 'url'); linestylefilter.REGEX_URL, 'url');
linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt) { linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt)
{
var nextPointIndex = 0; var nextPointIndex = 0;
var idx = 0; var idx = 0;
// don't split at 0 // don't split at 0
while (splitPointsOpt && while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0)
nextPointIndex < splitPointsOpt.length && {
splitPointsOpt[nextPointIndex] == 0) {
nextPointIndex++; nextPointIndex++;
} }
function spanHandler(txt, cls) { function spanHandler(txt, cls)
if ((! splitPointsOpt) || nextPointIndex >= splitPointsOpt.length) { {
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length)
{
func(txt, cls); func(txt, cls);
idx += txt.length; idx += txt.length;
} }
else { else
{
var splitPoints = splitPointsOpt; var splitPoints = splitPointsOpt;
var pointLocInSpan = splitPoints[nextPointIndex] - idx; var pointLocInSpan = splitPoints[nextPointIndex] - idx;
var txtlen = txt.length; var txtlen = txt.length;
if (pointLocInSpan >= txtlen) { if (pointLocInSpan >= txtlen)
{
func(txt, cls); func(txt, cls);
idx += txt.length; idx += txt.length;
if (pointLocInSpan == txtlen) { if (pointLocInSpan == txtlen)
{
nextPointIndex++; nextPointIndex++;
} }
} }
else { else
if (pointLocInSpan > 0) { {
if (pointLocInSpan > 0)
{
func(txt.substring(0, pointLocInSpan), cls); func(txt.substring(0, pointLocInSpan), cls);
idx += pointLocInSpan; idx += pointLocInSpan;
} }
@ -244,23 +289,31 @@ linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt) {
return spanHandler; return spanHandler;
}; };
linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser) { linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser)
{
var func = linestylefilter.getURLFilter(lineText, textAndClassFunc); var func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
var plugins_; var plugins_;
if (typeof(plugins)!='undefined') { if (typeof(plugins) != 'undefined')
{
plugins_ = plugins; plugins_ = plugins;
} else { }
else
{
plugins_ = parent.parent.plugins; plugins_ = parent.parent.plugins;
} }
var hookFilters = plugins_.callHook( var hookFilters = plugins_.callHook("aceGetFilterStack", {
"aceGetFilterStack", {linestylefilter:linestylefilter, browser:browser}); linestylefilter: linestylefilter,
hookFilters.map(function (hookFilter) { browser: browser
});
hookFilters.map(function(hookFilter)
{
func = hookFilter(lineText, func); func = hookFilter(lineText, func);
}); });
if (browser !== undefined && browser.msie) { if (browser !== undefined && browser.msie)
{
// IE7+ will take an e-mail address like <foo@bar.com> and linkify it to foo@bar.com. // 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 // We then normalize it back to text with no angle brackets. It's weird. So always
// break spans at an "at" sign. // break spans at an "at" sign.
@ -271,20 +324,21 @@ linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser) {
}; };
// domLineObj is like that returned by domline.createDomLine // domLineObj is like that returned by domline.createDomLine
linestylefilter.populateDomLine = function(textLine, aline, apool, linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj)
domLineObj) { {
// remove final newline from text if any // remove final newline from text if any
var text = textLine; var text = textLine;
if (text.slice(-1) == '\n') { if (text.slice(-1) == '\n')
text = text.substring(0, text.length-1); {
text = text.substring(0, text.length - 1);
} }
function textAndClassFunc(tokenText, tokenClass) { function textAndClassFunc(tokenText, tokenClass)
{
domLineObj.appendSpan(tokenText, tokenClass); domLineObj.appendSpan(tokenText, tokenClass);
} }
var func = linestylefilter.getFilterStack(text, textAndClassFunc); var func = linestylefilter.getFilterStack(text, textAndClassFunc);
func = linestylefilter.getLineStyleFilter(text.length, aline, func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
func, apool);
func(text, ''); func(text, '');
}; };

View file

@ -28,9 +28,9 @@ linestylefilter.ATTRIB_CLASSES = {
'strikethrough': 'tag:s' 'strikethrough': 'tag:s'
}; };
linestylefilter.getAuthorClassName = function (author) linestylefilter.getAuthorClassName = function(author)
{ {
return "author-" + author.replace(/[^a-y0-9]/g, function (c) return "author-" + author.replace(/[^a-y0-9]/g, function(c)
{ {
if (c == ".") return "-"; if (c == ".") return "-";
return 'z' + c.charCodeAt(0) + 'z'; return 'z' + c.charCodeAt(0) + 'z';
@ -39,11 +39,11 @@ linestylefilter.getAuthorClassName = function (author)
// lineLength is without newline; aline includes newline, // lineLength is without newline; aline includes newline,
// but may be falsy if lineLength == 0 // but may be falsy if lineLength == 0
linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFunc, apool) linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFunc, apool)
{ {
var plugins_; var plugins_;
if (typeof (plugins) != 'undefined') if (typeof(plugins) != 'undefined')
{ {
plugins_ = plugins; plugins_ = plugins;
} }
@ -56,7 +56,7 @@ linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFu
var nextAfterAuthorColors = textAndClassFunc; var nextAfterAuthorColors = textAndClassFunc;
var authorColorFunc = (function () var authorColorFunc = (function()
{ {
var lineEnd = lineLength; var lineEnd = lineLength;
var curIndex = 0; var curIndex = 0;
@ -66,7 +66,7 @@ linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFu
function attribsToClasses(attribs) function attribsToClasses(attribs)
{ {
var classes = ''; var classes = '';
Changeset.eachAttribNumber(attribs, function (n) Changeset.eachAttribNumber(attribs, function(n)
{ {
var key = apool.getAttribKey(n); var key = apool.getAttribKey(n);
if (key) if (key)
@ -126,7 +126,7 @@ linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFu
} }
nextClasses(); nextClasses();
return function (txt, cls) return function(txt, cls)
{ {
while (txt.length > 0) while (txt.length > 0)
{ {
@ -155,7 +155,7 @@ linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFu
return authorColorFunc; return authorColorFunc;
}; };
linestylefilter.getAtSignSplitterFilter = function (lineText, textAndClassFunc) linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc)
{ {
var at = /@/g; var at = /@/g;
at.lastIndex = 0; at.lastIndex = 0;
@ -175,9 +175,9 @@ linestylefilter.getAtSignSplitterFilter = function (lineText, textAndClassFunc)
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints); return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
}; };
linestylefilter.getRegexpFilter = function (regExp, tag) linestylefilter.getRegexpFilter = function(regExp, tag)
{ {
return function (lineText, textAndClassFunc) return function(lineText, textAndClassFunc)
{ {
regExp.lastIndex = 0; regExp.lastIndex = 0;
var regExpMatchs = null; var regExpMatchs = null;
@ -211,10 +211,10 @@ linestylefilter.getRegexpFilter = function (regExp, tag)
return false; return false;
} }
var handleRegExpMatchsAfterSplit = (function () var handleRegExpMatchsAfterSplit = (function()
{ {
var curIndex = 0; var curIndex = 0;
return function (txt, cls) return function(txt, cls)
{ {
var txtlen = txt.length; var txtlen = txt.length;
var newCls = cls; var newCls = cls;
@ -239,7 +239,7 @@ linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter( linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(
linestylefilter.REGEX_URL, 'url'); linestylefilter.REGEX_URL, 'url');
linestylefilter.textAndClassFuncSplitter = function (func, splitPointsOpt) linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt)
{ {
var nextPointIndex = 0; var nextPointIndex = 0;
var idx = 0; var idx = 0;
@ -287,12 +287,12 @@ linestylefilter.textAndClassFuncSplitter = function (func, splitPointsOpt)
return spanHandler; return spanHandler;
}; };
linestylefilter.getFilterStack = function (lineText, textAndClassFunc, browser) linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser)
{ {
var func = linestylefilter.getURLFilter(lineText, textAndClassFunc); var func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
var plugins_; var plugins_;
if (typeof (plugins) != 'undefined') if (typeof(plugins) != 'undefined')
{ {
plugins_ = plugins; plugins_ = plugins;
} }
@ -305,7 +305,7 @@ linestylefilter.getFilterStack = function (lineText, textAndClassFunc, browser)
linestylefilter: linestylefilter, linestylefilter: linestylefilter,
browser: browser browser: browser
}); });
hookFilters.map(function (hookFilter) hookFilters.map(function(hookFilter)
{ {
func = hookFilter(lineText, func); func = hookFilter(lineText, func);
}); });
@ -322,7 +322,7 @@ linestylefilter.getFilterStack = function (lineText, textAndClassFunc, browser)
}; };
// domLineObj is like that returned by domline.createDomLine // domLineObj is like that returned by domline.createDomLine
linestylefilter.populateDomLine = function (textLine, aline, apool, domLineObj) linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj)
{ {
// remove final newline from text if any // remove final newline from text if any
var text = textLine; var text = textLine;

View file

@ -18,42 +18,50 @@
var socket; var socket;
$(document).ready(function() { $(document).ready(function()
{
handshake(); handshake();
}); });
$(window).unload(function() { $(window).unload(function()
{
pad.dispose(); pad.dispose();
}); });
function createCookie(name,value,days) { function createCookie(name, value, days)
if (days) { {
if (days)
{
var date = new Date(); var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000)); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires="+date.toGMTString(); var expires = "; expires=" + date.toGMTString();
} }
else var expires = ""; else var expires = "";
document.cookie = name+"="+value+expires+"; path=/"; document.cookie = name + "=" + value + expires + "; path=/";
} }
function readCookie(name) { function readCookie(name)
{
var nameEQ = name + "="; var nameEQ = name + "=";
var ca = document.cookie.split(';'); var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) { for (var i = 0; i < ca.length; i++)
{
var c = ca[i]; var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length); while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
} }
return null; return null;
} }
function randomString() { function randomString()
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
var string_length = 20; var string_length = 20;
var randomstring = ''; var randomstring = '';
for (var i=0; i<string_length; i++) { for (var i = 0; i < string_length; i++)
{
var rnum = Math.floor(Math.random() * chars.length); var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum,rnum+1); randomstring += chars.substring(rnum, rnum + 1);
} }
return "t." + randomstring; return "t." + randomstring;
} }
@ -67,56 +75,61 @@ function handshake()
var url = loc.protocol + "//" + loc.hostname + ":" + port + "/"; var url = loc.protocol + "//" + loc.hostname + ":" + port + "/";
//find out in which subfolder we are //find out in which subfolder we are
console.log(loc.pathname); console.log(loc.pathname);
var resource = loc.pathname.substr(1,loc.pathname.indexOf("/p/")) + "socket.io"; var resource = loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "socket.io";
console.log(resource); console.log(resource);
//connect //connect
socket = io.connect(url, {resource: resource}); socket = io.connect(url, {
resource: resource
});
socket.on('connect', function(){ socket.on('connect', function()
var padId= document.URL.substring(document.URL.lastIndexOf("/")+1); {
var padId = document.URL.substring(document.URL.lastIndexOf("/") + 1);
document.title = document.title + " | " + padId; document.title = document.title + " | " + padId;
var token = readCookie("token"); var token = readCookie("token");
if(token == null) if (token == null)
{ {
token = randomString(); token = randomString();
createCookie("token", token, 60); createCookie("token", token, 60);
} }
var msg = { "component" : "pad", var msg = {
"type":"CLIENT_READY", "component": "pad",
"type": "CLIENT_READY",
"padId": padId, "padId": padId,
"token": token, "token": token,
"protocolVersion": 2}; "protocolVersion": 2
};
socket.json.send(msg); socket.json.send(msg);
}); });
var receivedClientVars=false; var receivedClientVars = false;
var initalized = false; var initalized = false;
socket.on('message', function(obj){ socket.on('message', function(obj)
//if we haven't recieved the clientVars yet, then this message should it be
if(!receivedClientVars)
{ {
if(window.console) //if we haven't recieved the clientVars yet, then this message should it be
console.log(obj); if (!receivedClientVars)
{
if (window.console) console.log(obj);
receivedClientVars=true; receivedClientVars = true;
clientVars = obj; clientVars = obj;
clientVars.userAgent=navigator.userAgent; clientVars.userAgent = navigator.userAgent;
clientVars.collab_client_vars.clientAgent=navigator.userAgent; clientVars.collab_client_vars.clientAgent = navigator.userAgent;
pad.init(); pad.init();
initalized=true; initalized = true;
} }
//This handles every Message after the clientVars //This handles every Message after the clientVars
else else
{ {
if(obj.disconnect) if (obj.disconnect)
{ {
socket.disconnect(); socket.disconnect();
padconnectionstatus.disconnected("userdup"); padconnectionstatus.disconnected("userdup");
@ -142,43 +155,75 @@ var pad = {
padOptions: {}, padOptions: {},
// these don't require init; clientVars should all go through here // these don't require init; clientVars should all go through here
getPadId: function() { return clientVars.padId; }, getPadId: function()
getClientIp: function() { return clientVars.clientIp; }, {
getIsProPad: function() { return clientVars.isProPad; }, return clientVars.padId;
getColorPalette: function() { return clientVars.colorPalette; }, },
getDisplayUserAgent: function() { getClientIp: function()
{
return clientVars.clientIp;
},
getIsProPad: function()
{
return clientVars.isProPad;
},
getColorPalette: function()
{
return clientVars.colorPalette;
},
getDisplayUserAgent: function()
{
return padutils.uaDisplay(clientVars.userAgent); return padutils.uaDisplay(clientVars.userAgent);
}, },
getIsDebugEnabled: function() { return clientVars.debugEnabled; }, getIsDebugEnabled: function()
getPrivilege: function(name) { return clientVars.accountPrivs[name]; }, {
getUserIsGuest: function() { return clientVars.userIsGuest; }, return clientVars.debugEnabled;
},
getPrivilege: function(name)
{
return clientVars.accountPrivs[name];
},
getUserIsGuest: function()
{
return clientVars.userIsGuest;
},
// //
getUserId: function()
getUserId: function() { return pad.myUserInfo.userId; }, {
getUserName: function() { return pad.myUserInfo.name; }, return pad.myUserInfo.userId;
sendClientMessage: function(msg) { },
getUserName: function()
{
return pad.myUserInfo.name;
},
sendClientMessage: function(msg)
{
pad.collabClient.sendClientMessage(msg); pad.collabClient.sendClientMessage(msg);
}, },
init: function() { init: function()
{
pad.diagnosticInfo.uniqueId = padutils.uniqueId(); pad.diagnosticInfo.uniqueId = padutils.uniqueId();
pad.initTime = +(new Date()); pad.initTime = +(new Date());
pad.padOptions = clientVars.initialOptions; pad.padOptions = clientVars.initialOptions;
if ((! $.browser.msie) && if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0)))
(! ($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) { {
document.domain = document.domain; // for comet document.domain = document.domain; // for comet
} }
// for IE // for IE
if ($.browser.msie) { if ($.browser.msie)
try { {
try
{
doc.execCommand("BackgroundImageCache", false, true); doc.execCommand("BackgroundImageCache", false, true);
} catch (e) {} }
catch (e)
{}
} }
// order of inits is important here: // order of inits is important here:
padcookie.init(clientVars.cookiePrefsToSet); padcookie.init(clientVars.cookiePrefsToSet);
$("#widthprefcheck").click(pad.toggleWidthPref); $("#widthprefcheck").click(pad.toggleWidthPref);
@ -191,16 +236,19 @@ var pad = {
colorId: clientVars.userColor, colorId: clientVars.userColor,
userAgent: pad.getDisplayUserAgent() userAgent: pad.getDisplayUserAgent()
}; };
if (clientVars.specialKey) { if (clientVars.specialKey)
{
pad.myUserInfo.specialKey = clientVars.specialKey; pad.myUserInfo.specialKey = clientVars.specialKey;
if (clientVars.specialKeyTranslation) { if (clientVars.specialKeyTranslation)
$("#specialkeyarea").html("mode: "+ {
String(clientVars.specialKeyTranslation).toUpperCase()); $("#specialkeyarea").html("mode: " + String(clientVars.specialKeyTranslation).toUpperCase());
} }
} }
paddocbar.init({isTitleEditable: pad.getIsProPad(), paddocbar.init(
initialTitle:clientVars.initialTitle, {
initialPassword:clientVars.initialPassword, isTitleEditable: pad.getIsProPad(),
initialTitle: clientVars.initialTitle,
initialPassword: clientVars.initialPassword,
guestPolicy: pad.padOptions.guestPolicy guestPolicy: pad.padOptions.guestPolicy
}); });
padimpexp.init(); padimpexp.init();
@ -209,15 +257,13 @@ var pad = {
padeditor.init(postAceInit, pad.padOptions.view || {}); padeditor.init(postAceInit, pad.padOptions.view || {});
paduserlist.init(pad.myUserInfo); paduserlist.init(pad.myUserInfo);
// padchat.init(clientVars.chatHistory, pad.myUserInfo); // padchat.init(clientVars.chatHistory, pad.myUserInfo);
padconnectionstatus.init(); padconnectionstatus.init();
padmodals.init(); padmodals.init();
pad.collabClient = pad.collabClient = getCollabClient(padeditor.ace, clientVars.collab_client_vars, pad.myUserInfo, {
getCollabClient(padeditor.ace, colorPalette: pad.getColorPalette()
clientVars.collab_client_vars, });
pad.myUserInfo,
{ colorPalette: pad.getColorPalette() });
pad.collabClient.setOnUserJoin(pad.handleUserJoin); pad.collabClient.setOnUserJoin(pad.handleUserJoin);
pad.collabClient.setOnUpdateUserInfo(pad.handleUserUpdate); pad.collabClient.setOnUpdateUserInfo(pad.handleUserUpdate);
pad.collabClient.setOnUserLeave(pad.handleUserLeave); pad.collabClient.setOnUserLeave(pad.handleUserLeave);
@ -226,180 +272,235 @@ var pad = {
pad.collabClient.setOnChannelStateChange(pad.handleChannelStateChange); pad.collabClient.setOnChannelStateChange(pad.handleChannelStateChange);
pad.collabClient.setOnInternalAction(pad.handleCollabAction); pad.collabClient.setOnInternalAction(pad.handleCollabAction);
function postAceInit() { function postAceInit()
{
padeditbar.init(); padeditbar.init();
setTimeout(function() { padeditor.ace.focus(); }, 0); setTimeout(function()
{
padeditor.ace.focus();
}, 0);
} }
}, },
dispose: function() { dispose: function()
{
padeditor.dispose(); padeditor.dispose();
}, },
notifyChangeName: function(newName) { notifyChangeName: function(newName)
{
pad.myUserInfo.name = newName; pad.myUserInfo.name = newName;
pad.collabClient.updateUserInfo(pad.myUserInfo); pad.collabClient.updateUserInfo(pad.myUserInfo);
//padchat.handleUserJoinOrUpdate(pad.myUserInfo); //padchat.handleUserJoinOrUpdate(pad.myUserInfo);
}, },
notifyChangeColor: function(newColorId) { notifyChangeColor: function(newColorId)
{
pad.myUserInfo.colorId = newColorId; pad.myUserInfo.colorId = newColorId;
pad.collabClient.updateUserInfo(pad.myUserInfo); pad.collabClient.updateUserInfo(pad.myUserInfo);
//padchat.handleUserJoinOrUpdate(pad.myUserInfo); //padchat.handleUserJoinOrUpdate(pad.myUserInfo);
}, },
notifyChangeTitle: function(newTitle) { notifyChangeTitle: function(newTitle)
pad.collabClient.sendClientMessage({ {
pad.collabClient.sendClientMessage(
{
type: 'padtitle', type: 'padtitle',
title: newTitle, title: newTitle,
changedBy: pad.myUserInfo.name || "unnamed" changedBy: pad.myUserInfo.name || "unnamed"
}); });
}, },
notifyChangePassword: function(newPass) { notifyChangePassword: function(newPass)
pad.collabClient.sendClientMessage({ {
pad.collabClient.sendClientMessage(
{
type: 'padpassword', type: 'padpassword',
password: newPass, password: newPass,
changedBy: pad.myUserInfo.name || "unnamed" changedBy: pad.myUserInfo.name || "unnamed"
}); });
}, },
changePadOption: function(key, value) { changePadOption: function(key, value)
{
var options = {}; var options = {};
options[key] = value; options[key] = value;
pad.handleOptionsChange(options); pad.handleOptionsChange(options);
pad.collabClient.sendClientMessage({ pad.collabClient.sendClientMessage(
{
type: 'padoptions', type: 'padoptions',
options: options, options: options,
changedBy: pad.myUserInfo.name || "unnamed" changedBy: pad.myUserInfo.name || "unnamed"
}); });
}, },
changeViewOption: function(key, value) { changeViewOption: function(key, value)
var options = {view: {}}; {
var options = {
view: {}
};
options.view[key] = value; options.view[key] = value;
pad.handleOptionsChange(options); pad.handleOptionsChange(options);
pad.collabClient.sendClientMessage({ pad.collabClient.sendClientMessage(
{
type: 'padoptions', type: 'padoptions',
options: options, options: options,
changedBy: pad.myUserInfo.name || "unnamed" changedBy: pad.myUserInfo.name || "unnamed"
}); });
}, },
handleOptionsChange: function(opts) { handleOptionsChange: function(opts)
{
// opts object is a full set of options or just // opts object is a full set of options or just
// some options to change // some options to change
if (opts.view) { if (opts.view)
if (! pad.padOptions.view) { {
if (!pad.padOptions.view)
{
pad.padOptions.view = {}; pad.padOptions.view = {};
} }
for(var k in opts.view) { for (var k in opts.view)
{
pad.padOptions.view[k] = opts.view[k]; pad.padOptions.view[k] = opts.view[k];
} }
padeditor.setViewOptions(pad.padOptions.view); padeditor.setViewOptions(pad.padOptions.view);
} }
if (opts.guestPolicy) { if (opts.guestPolicy)
{
// order important here // order important here
pad.padOptions.guestPolicy = opts.guestPolicy; pad.padOptions.guestPolicy = opts.guestPolicy;
paddocbar.setGuestPolicy(opts.guestPolicy); paddocbar.setGuestPolicy(opts.guestPolicy);
} }
}, },
getPadOptions: function() { getPadOptions: function()
{
// caller shouldn't mutate the object // caller shouldn't mutate the object
return pad.padOptions; return pad.padOptions;
}, },
isPadPublic: function() { isPadPublic: function()
return (! pad.getIsProPad()) || (pad.getPadOptions().guestPolicy == 'allow'); {
return (!pad.getIsProPad()) || (pad.getPadOptions().guestPolicy == 'allow');
}, },
suggestUserName: function(userId, name) { suggestUserName: function(userId, name)
pad.collabClient.sendClientMessage({ {
pad.collabClient.sendClientMessage(
{
type: 'suggestUserName', type: 'suggestUserName',
unnamedId: userId, unnamedId: userId,
newName: name newName: name
}); });
}, },
handleUserJoin: function(userInfo) { handleUserJoin: function(userInfo)
{
paduserlist.userJoinOrUpdate(userInfo); paduserlist.userJoinOrUpdate(userInfo);
//padchat.handleUserJoinOrUpdate(userInfo); //padchat.handleUserJoinOrUpdate(userInfo);
}, },
handleUserUpdate: function(userInfo) { handleUserUpdate: function(userInfo)
{
paduserlist.userJoinOrUpdate(userInfo); paduserlist.userJoinOrUpdate(userInfo);
//padchat.handleUserJoinOrUpdate(userInfo); //padchat.handleUserJoinOrUpdate(userInfo);
}, },
handleUserLeave: function(userInfo) { handleUserLeave: function(userInfo)
{
paduserlist.userLeave(userInfo); paduserlist.userLeave(userInfo);
//padchat.handleUserLeave(userInfo); //padchat.handleUserLeave(userInfo);
}, },
handleClientMessage: function(msg) { handleClientMessage: function(msg)
if (msg.type == 'suggestUserName') { {
if (msg.unnamedId == pad.myUserInfo.userId && msg.newName && if (msg.type == 'suggestUserName')
! pad.myUserInfo.name) { {
if (msg.unnamedId == pad.myUserInfo.userId && msg.newName && !pad.myUserInfo.name)
{
pad.notifyChangeName(msg.newName); pad.notifyChangeName(msg.newName);
paduserlist.setMyUserInfo(pad.myUserInfo); paduserlist.setMyUserInfo(pad.myUserInfo);
} }
} }
else if (msg.type == 'chat') { else if (msg.type == 'chat')
{
//padchat.receiveChat(msg); //padchat.receiveChat(msg);
} }
else if (msg.type == 'padtitle') { else if (msg.type == 'padtitle')
{
paddocbar.changeTitle(msg.title); paddocbar.changeTitle(msg.title);
} }
else if (msg.type == 'padpassword') { else if (msg.type == 'padpassword')
{
paddocbar.changePassword(msg.password); paddocbar.changePassword(msg.password);
} }
else if (msg.type == 'newRevisionList') { else if (msg.type == 'newRevisionList')
{
padsavedrevs.newRevisionList(msg.revisionList); padsavedrevs.newRevisionList(msg.revisionList);
} }
else if (msg.type == 'revisionLabel') { else if (msg.type == 'revisionLabel')
{
padsavedrevs.newRevisionList(msg.revisionList); padsavedrevs.newRevisionList(msg.revisionList);
} }
else if (msg.type == 'padoptions') { else if (msg.type == 'padoptions')
{
var opts = msg.options; var opts = msg.options;
pad.handleOptionsChange(opts); pad.handleOptionsChange(opts);
} }
else if (msg.type == 'guestanswer') { else if (msg.type == 'guestanswer')
{
// someone answered a prompt, remove it // someone answered a prompt, remove it
paduserlist.removeGuestPrompt(msg.guestId); paduserlist.removeGuestPrompt(msg.guestId);
} }
}, },
editbarClick: function(cmd) { editbarClick: function(cmd)
if (padeditbar) { {
if (padeditbar)
{
padeditbar.toolbarClick(cmd); padeditbar.toolbarClick(cmd);
} }
}, },
dmesg: function(m) { dmesg: function(m)
if (pad.getIsDebugEnabled()) { {
if (pad.getIsDebugEnabled())
{
var djs = $('#djs').get(0); var djs = $('#djs').get(0);
var wasAtBottom = (djs.scrollTop - (djs.scrollHeight - $(djs).height()) var wasAtBottom = (djs.scrollTop - (djs.scrollHeight - $(djs).height()) >= -20);
>= -20); $('#djs').append('<p>' + m + '</p>');
$('#djs').append('<p>'+m+'</p>'); if (wasAtBottom)
if (wasAtBottom) { {
djs.scrollTop = djs.scrollHeight; djs.scrollTop = djs.scrollHeight;
} }
} }
}, },
handleServerMessage: function(m) { handleServerMessage: function(m)
if (m.type == 'NOTICE') { {
if (m.text) { if (m.type == 'NOTICE')
alertBar.displayMessage(function (abar) { {
abar.find("#servermsgdate").html(" ("+padutils.simpleDateTime(new Date)+")"); if (m.text)
{
alertBar.displayMessage(function(abar)
{
abar.find("#servermsgdate").html(" (" + padutils.simpleDateTime(new Date) + ")");
abar.find("#servermsgtext").html(m.text); abar.find("#servermsgtext").html(m.text);
}); });
} }
if (m.js) { if (m.js)
window['ev'+'al'](m.js); {
window['ev' + 'al'](m.js);
} }
} }
else if (m.type == 'GUEST_PROMPT') { else if (m.type == 'GUEST_PROMPT')
{
paduserlist.showGuestPrompt(m.userId, m.displayName); paduserlist.showGuestPrompt(m.userId, m.displayName);
} }
}, },
handleChannelStateChange: function(newState, message) { handleChannelStateChange: function(newState, message)
{
var oldFullyConnected = !! padconnectionstatus.isFullyConnected(); var oldFullyConnected = !! padconnectionstatus.isFullyConnected();
var wasConnecting = (padconnectionstatus.getStatus().what == 'connecting'); var wasConnecting = (padconnectionstatus.getStatus().what == 'connecting');
if (newState == "CONNECTED") { if (newState == "CONNECTED")
{
padconnectionstatus.connected(); padconnectionstatus.connected();
} }
else if (newState == "RECONNECTING") { else if (newState == "RECONNECTING")
{
padconnectionstatus.reconnecting(); padconnectionstatus.reconnecting();
} }
else if (newState == "DISCONNECTED") { else if (newState == "DISCONNECTED")
{
pad.diagnosticInfo.disconnectedMessage = message; pad.diagnosticInfo.disconnectedMessage = message;
pad.diagnosticInfo.padInitTime = pad.initTime; pad.diagnosticInfo.padInitTime = pad.initTime;
pad.asyncSendDiagnosticInfo(); pad.asyncSendDiagnosticInfo();
if (typeof window.ajlog == "string") { window.ajlog += ("Disconnected: "+message+'\n'); } if (typeof window.ajlog == "string")
{
window.ajlog += ("Disconnected: " + message + '\n');
}
padeditor.disable(); padeditor.disable();
padeditbar.disable(); padeditbar.disable();
paddocbar.disable(); paddocbar.disable();
@ -408,16 +509,21 @@ var pad = {
padconnectionstatus.disconnected(message); padconnectionstatus.disconnected(message);
} }
var newFullyConnected = !! padconnectionstatus.isFullyConnected(); var newFullyConnected = !! padconnectionstatus.isFullyConnected();
if (newFullyConnected != oldFullyConnected) { if (newFullyConnected != oldFullyConnected)
{
pad.handleIsFullyConnected(newFullyConnected, wasConnecting); pad.handleIsFullyConnected(newFullyConnected, wasConnecting);
} }
}, },
handleIsFullyConnected: function(isConnected, isInitialConnect) { handleIsFullyConnected: function(isConnected, isInitialConnect)
{
// load all images referenced from CSS, one at a time, // load all images referenced from CSS, one at a time,
// starting one second after connection is first established. // starting one second after connection is first established.
if (isConnected && ! pad.preloadedImages) { if (isConnected && !pad.preloadedImages)
window.setTimeout(function() { {
if (! pad.preloadedImages) { window.setTimeout(function()
{
if (!pad.preloadedImages)
{
pad.preloadImages(); pad.preloadImages();
pad.preloadedImages = true; pad.preloadedImages = true;
} }
@ -426,158 +532,194 @@ var pad = {
padsavedrevs.handleIsFullyConnected(isConnected); padsavedrevs.handleIsFullyConnected(isConnected);
pad.determineSidebarVisibility(isConnected && ! isInitialConnect); pad.determineSidebarVisibility(isConnected && !isInitialConnect);
}, },
determineSidebarVisibility: function(asNowConnectedFeedback) { determineSidebarVisibility: function(asNowConnectedFeedback)
if (pad.isFullyConnected()) { {
var setSidebarVisibility = if (pad.isFullyConnected())
padutils.getCancellableAction( {
"set-sidebar-visibility", var setSidebarVisibility = padutils.getCancellableAction("set-sidebar-visibility", function()
function() { {
$("body").toggleClass('hidesidebar', $("body").toggleClass('hidesidebar', !! padcookie.getPref('hideSidebar'));
!! padcookie.getPref('hideSidebar'));
}); });
window.setTimeout(setSidebarVisibility, window.setTimeout(setSidebarVisibility, asNowConnectedFeedback ? 3000 : 0);
asNowConnectedFeedback ? 3000 : 0);
} }
else { else
{
padutils.cancelActions("set-sidebar-visibility"); padutils.cancelActions("set-sidebar-visibility");
$("body").removeClass('hidesidebar'); $("body").removeClass('hidesidebar');
} }
}, },
handleCollabAction: function(action) { handleCollabAction: function(action)
if (action == "commitPerformed") { {
if (action == "commitPerformed")
{
padeditbar.setSyncStatus("syncing"); padeditbar.setSyncStatus("syncing");
} }
else if (action == "newlyIdle") { else if (action == "newlyIdle")
{
padeditbar.setSyncStatus("done"); padeditbar.setSyncStatus("done");
} }
}, },
hideServerMessage: function() { hideServerMessage: function()
{
alertBar.hideMessage(); alertBar.hideMessage();
}, },
asyncSendDiagnosticInfo: function() { asyncSendDiagnosticInfo: function()
{
pad.diagnosticInfo.collabDiagnosticInfo = pad.collabClient.getDiagnosticInfo(); pad.diagnosticInfo.collabDiagnosticInfo = pad.collabClient.getDiagnosticInfo();
window.setTimeout(function() { window.setTimeout(function()
$.ajax({ {
$.ajax(
{
type: 'post', type: 'post',
url: '/ep/pad/connection-diagnostic-info', url: '/ep/pad/connection-diagnostic-info',
data: {padId: pad.getPadId(), diagnosticInfo: JSON.stringify(pad.diagnosticInfo)}, data: {
success: function() {}, padId: pad.getPadId(),
error: function() {} diagnosticInfo: JSON.stringify(pad.diagnosticInfo)
},
success: function()
{},
error: function()
{}
}); });
}, 0); }, 0);
}, },
forceReconnect: function() { forceReconnect: function()
{
$('form#reconnectform input.padId').val(pad.getPadId()); $('form#reconnectform input.padId').val(pad.getPadId());
pad.diagnosticInfo.collabDiagnosticInfo = pad.collabClient.getDiagnosticInfo(); pad.diagnosticInfo.collabDiagnosticInfo = pad.collabClient.getDiagnosticInfo();
$('form#reconnectform input.diagnosticInfo').val(JSON.stringify(pad.diagnosticInfo)); $('form#reconnectform input.diagnosticInfo').val(JSON.stringify(pad.diagnosticInfo));
$('form#reconnectform input.missedChanges').val(JSON.stringify(pad.collabClient.getMissedChanges())); $('form#reconnectform input.missedChanges').val(JSON.stringify(pad.collabClient.getMissedChanges()));
$('form#reconnectform').submit(); $('form#reconnectform').submit();
}, },
toggleWidthPref: function() { toggleWidthPref: function()
var newValue = ! padcookie.getPref('fullWidth'); {
var newValue = !padcookie.getPref('fullWidth');
padcookie.setPref('fullWidth', newValue); padcookie.setPref('fullWidth', newValue);
$("#widthprefcheck").toggleClass('widthprefchecked', !!newValue).toggleClass( $("#widthprefcheck").toggleClass('widthprefchecked', !! newValue).toggleClass('widthprefunchecked', !newValue);
'widthprefunchecked', !newValue);
pad.handleWidthChange(); pad.handleWidthChange();
}, },
toggleSidebar: function() { toggleSidebar: function()
var newValue = ! padcookie.getPref('hideSidebar'); {
var newValue = !padcookie.getPref('hideSidebar');
padcookie.setPref('hideSidebar', newValue); padcookie.setPref('hideSidebar', newValue);
$("#sidebarcheck").toggleClass('sidebarchecked', !newValue).toggleClass( $("#sidebarcheck").toggleClass('sidebarchecked', !newValue).toggleClass('sidebarunchecked', !! newValue);
'sidebarunchecked', !!newValue);
pad.determineSidebarVisibility(); pad.determineSidebarVisibility();
}, },
handleWidthChange: function() { handleWidthChange: function()
{
var isFullWidth = padcookie.getPref('fullWidth'); var isFullWidth = padcookie.getPref('fullWidth');
if (isFullWidth) { if (isFullWidth)
$("body").addClass('fullwidth').removeClass('limwidth').removeClass( {
'squish1width').removeClass('squish2width'); $("body").addClass('fullwidth').removeClass('limwidth').removeClass('squish1width').removeClass('squish2width');
} }
else { else
{
$("body").addClass('limwidth').removeClass('fullwidth'); $("body").addClass('limwidth').removeClass('fullwidth');
var pageWidth = $(window).width(); var pageWidth = $(window).width();
$("body").toggleClass('squish1width', (pageWidth < 912 && pageWidth > 812)).toggleClass( $("body").toggleClass('squish1width', (pageWidth < 912 && pageWidth > 812)).toggleClass('squish2width', (pageWidth <= 812));
'squish2width', (pageWidth <= 812));
} }
}, },
// this is called from code put into a frame from the server: // this is called from code put into a frame from the server:
handleImportExportFrameCall: function(callName, varargs) { handleImportExportFrameCall: function(callName, varargs)
padimpexp.handleFrameCall.call(padimpexp, callName, {
Array.prototype.slice.call(arguments, 1)); padimpexp.handleFrameCall.call(padimpexp, callName, Array.prototype.slice.call(arguments, 1));
}, },
callWhenNotCommitting: function(f) { callWhenNotCommitting: function(f)
{
pad.collabClient.callWhenNotCommitting(f); pad.collabClient.callWhenNotCommitting(f);
}, },
getCollabRevisionNumber: function() { getCollabRevisionNumber: function()
{
return pad.collabClient.getCurrentRevisionNumber(); return pad.collabClient.getCurrentRevisionNumber();
}, },
isFullyConnected: function() { isFullyConnected: function()
{
return padconnectionstatus.isFullyConnected(); return padconnectionstatus.isFullyConnected();
}, },
addHistoricalAuthors: function(data) { addHistoricalAuthors: function(data)
if (! pad.collabClient) { {
window.setTimeout(function() { pad.addHistoricalAuthors(data); }, if (!pad.collabClient)
1000); {
window.setTimeout(function()
{
pad.addHistoricalAuthors(data);
}, 1000);
} }
else { else
{
pad.collabClient.addHistoricalAuthors(data); pad.collabClient.addHistoricalAuthors(data);
} }
}, },
preloadImages: function() { preloadImages: function()
var images = [ {
'../static/img/colorpicker.gif' var images = ['../static/img/colorpicker.gif'];
];
function loadNextImage() { function loadNextImage()
if (images.length == 0) { {
if (images.length == 0)
{
return; return;
} }
var img = new Image(); var img = new Image();
img.src = images.shift(); img.src = images.shift();
if (img.complete) { if (img.complete)
{
scheduleLoadNextImage(); scheduleLoadNextImage();
} }
else { else
{
$(img).bind('error load onreadystatechange', scheduleLoadNextImage); $(img).bind('error load onreadystatechange', scheduleLoadNextImage);
} }
} }
function scheduleLoadNextImage() {
function scheduleLoadNextImage()
{
window.setTimeout(loadNextImage, 0); window.setTimeout(loadNextImage, 0);
} }
scheduleLoadNextImage(); scheduleLoadNextImage();
} }
}; };
var alertBar = (function() { var alertBar = (function()
{
var animator = padutils.makeShowHideAnimator(arriveAtAnimationState, false, 25, 400); var animator = padutils.makeShowHideAnimator(arriveAtAnimationState, false, 25, 400);
function arriveAtAnimationState(state) { function arriveAtAnimationState(state)
if (state == -1) { {
if (state == -1)
{
$("#alertbar").css('opacity', 0).css('display', 'block'); $("#alertbar").css('opacity', 0).css('display', 'block');
} }
else if (state == 0) { else if (state == 0)
{
$("#alertbar").css('opacity', 1); $("#alertbar").css('opacity', 1);
} }
else if (state == 1) { else if (state == 1)
{
$("#alertbar").css('opacity', 0).css('display', 'none'); $("#alertbar").css('opacity', 0).css('display', 'none');
} }
else if (state < 0) { else if (state < 0)
$("#alertbar").css('opacity', state+1); {
$("#alertbar").css('opacity', state + 1);
} }
else if (state > 0) { else if (state > 0)
{
$("#alertbar").css('opacity', 1 - state); $("#alertbar").css('opacity', 1 - state);
} }
} }
var self = { var self = {
displayMessage: function(setupFunc) { displayMessage: function(setupFunc)
{
animator.show(); animator.show();
setupFunc($("#alertbar")); setupFunc($("#alertbar"));
}, },
hideMessage: function() { hideMessage: function()
{
animator.hide(); animator.hide();
} }
}; };

View file

@ -14,44 +14,64 @@
* limitations under the License. * limitations under the License.
*/ */
var padconnectionstatus = (function() { var padconnectionstatus = (function()
{
var status = {what: 'connecting'}; var status = {
what: 'connecting'
};
var self = { var self = {
init: function() { init: function()
$('button#forcereconnect').click(function() { {
$('button#forcereconnect').click(function()
{
window.location.reload(); window.location.reload();
}); });
}, },
connected: function() { connected: function()
status = {what: 'connected'}; {
status = {
what: 'connected'
};
padmodals.hideModal(500); padmodals.hideModal(500);
}, },
reconnecting: function() { reconnecting: function()
status = {what: 'reconnecting'}; {
status = {
what: 'reconnecting'
};
$("#connectionbox").get(0).className = 'modaldialog cboxreconnecting'; $("#connectionbox").get(0).className = 'modaldialog cboxreconnecting';
padmodals.showModal("#connectionbox", 500); padmodals.showModal("#connectionbox", 500);
}, },
disconnected: function(msg) { disconnected: function(msg)
status = {what: 'disconnected', why: msg}; {
status = {
what: 'disconnected',
why: msg
};
var k = String(msg).toLowerCase(); // known reason why var k = String(msg).toLowerCase(); // known reason why
if (!(k == 'userdup' || k == 'looping' || k == 'slowcommit' || if (!(k == 'userdup' || k == 'looping' || k == 'slowcommit' || k == 'initsocketfail' || k == 'unauth'))
k == 'initsocketfail' || k == 'unauth')) { {
k = 'unknown'; k = 'unknown';
} }
var cls = 'modaldialog cboxdisconnected cboxdisconnected_'+k; var cls = 'modaldialog cboxdisconnected cboxdisconnected_' + k;
$("#connectionbox").get(0).className = cls; $("#connectionbox").get(0).className = cls;
padmodals.showModal("#connectionbox", 500); padmodals.showModal("#connectionbox", 500);
$('button#forcereconnect').click(function() { $('button#forcereconnect').click(function()
{
window.location.reload(); window.location.reload();
}); });
}, },
isFullyConnected: function() { isFullyConnected: function()
{
return status.what == 'connected'; return status.what == 'connected';
}, },
getStatus: function() { return status; } getStatus: function()
{
return status;
}
}; };
return self; return self;
}()); }());

View file

@ -15,49 +15,61 @@
*/ */
var padcookie = (function(){ var padcookie = (function()
function getRawCookie() { {
function getRawCookie()
{
// returns null if can't get cookie text // returns null if can't get cookie text
if (! document.cookie) { if (!document.cookie)
{
return null; return null;
} }
// look for (start of string OR semicolon) followed by whitespace followed by prefs=(something); // look for (start of string OR semicolon) followed by whitespace followed by prefs=(something);
var regexResult = document.cookie.match(/(?:^|;)\s*prefs=([^;]*)(?:;|$)/); var regexResult = document.cookie.match(/(?:^|;)\s*prefs=([^;]*)(?:;|$)/);
if ((! regexResult) || (! regexResult[1])) { if ((!regexResult) || (!regexResult[1]))
{
return null; return null;
} }
return regexResult[1]; return regexResult[1];
} }
function setRawCookie(safeText) {
function setRawCookie(safeText)
{
var expiresDate = new Date(); var expiresDate = new Date();
expiresDate.setFullYear(3000); expiresDate.setFullYear(3000);
document.cookie = ('prefs='+safeText+';expires='+expiresDate.toGMTString()); document.cookie = ('prefs=' + safeText + ';expires=' + expiresDate.toGMTString());
} }
function parseCookie(text) {
// returns null if can't parse cookie.
try { function parseCookie(text)
{
// returns null if can't parse cookie.
try
{
var cookieData = JSON.parse(unescape(text)); var cookieData = JSON.parse(unescape(text));
return cookieData; return cookieData;
} }
catch (e) { catch (e)
{
return null; return null;
} }
} }
function stringifyCookie(data) {
function stringifyCookie(data)
{
return escape(JSON.stringify(data)); return escape(JSON.stringify(data));
} }
function saveCookie() {
if (! inited) { function saveCookie()
{
if (!inited)
{
return; return;
} }
setRawCookie(stringifyCookie(cookieData)); setRawCookie(stringifyCookie(cookieData));
if (pad.getIsProPad() && (! getRawCookie()) && (! alreadyWarnedAboutNoCookies)) { if (pad.getIsProPad() && (!getRawCookie()) && (!alreadyWarnedAboutNoCookies))
alert("Warning: it appears that your browser does not have cookies enabled."+ {
" EtherPad uses cookies to keep track of unique users for the purpose"+ alert("Warning: it appears that your browser does not have cookies enabled." + " EtherPad uses cookies to keep track of unique users for the purpose" + " of putting a quota on the number of active users. Using EtherPad without " + " cookies may fill up your server's user quota faster than expected.");
" of putting a quota on the number of active users. Using EtherPad without "+
" cookies may fill up your server's user quota faster than expected.");
alreadyWarnedAboutNoCookies = true; alreadyWarnedAboutNoCookies = true;
} }
} }
@ -68,11 +80,14 @@ var padcookie = (function(){
var inited = false; var inited = false;
var self = { var self = {
init: function(prefsToSet) { init: function(prefsToSet)
{
var rawCookie = getRawCookie(); var rawCookie = getRawCookie();
if (rawCookie) { if (rawCookie)
{
var cookieObj = parseCookie(rawCookie); var cookieObj = parseCookie(rawCookie);
if (cookieObj) { if (cookieObj)
{
wasNoCookie = false; // there was a cookie wasNoCookie = false; // there was a cookie
delete cookieObj.userId; delete cookieObj.userId;
delete cookieObj.name; delete cookieObj.name;
@ -81,18 +96,24 @@ var padcookie = (function(){
} }
} }
for(var k in prefsToSet) { for (var k in prefsToSet)
{
cookieData[k] = prefsToSet[k]; cookieData[k] = prefsToSet[k];
} }
inited = true; inited = true;
saveCookie(); saveCookie();
}, },
wasNoCookie: function() { return wasNoCookie; }, wasNoCookie: function()
getPref: function(prefName) { {
return wasNoCookie;
},
getPref: function(prefName)
{
return cookieData[prefName]; return cookieData[prefName];
}, },
setPref: function(prefName, value) { setPref: function(prefName, value)
{
cookieData[prefName] = value; cookieData[prefName] = value;
saveCookie(); saveCookie();
} }

View file

@ -15,50 +15,66 @@
*/ */
var paddocbar = (function() { var paddocbar = (function()
{
var isTitleEditable = false; var isTitleEditable = false;
var isEditingTitle = false; var isEditingTitle = false;
var isEditingPassword = false; var isEditingPassword = false;
var enabled = false; var enabled = false;
function getPanelOpenCloseAnimator(panelName, panelHeight) { function getPanelOpenCloseAnimator(panelName, panelHeight)
var wrapper = $("#"+panelName+"-wrapper"); {
var openingClass = "docbar"+panelName+"-opening"; var wrapper = $("#" + panelName + "-wrapper");
var openClass = "docbar"+panelName+"-open"; var openingClass = "docbar" + panelName + "-opening";
var closingClass = "docbar"+panelName+"-closing"; var openClass = "docbar" + panelName + "-open";
function setPanelState(action) { var closingClass = "docbar" + panelName + "-closing";
function setPanelState(action)
{
$("#docbar").removeClass(openingClass).removeClass(openClass). $("#docbar").removeClass(openingClass).removeClass(openClass).
removeClass(closingClass); removeClass(closingClass);
if (action != "closed") { if (action != "closed")
$("#docbar").addClass("docbar"+panelName+"-"+action); {
$("#docbar").addClass("docbar" + panelName + "-" + action);
} }
} }
function openCloseAnimate(state) { function openCloseAnimate(state)
function pow(x) { x = 1-x; x *= x*x; return 1-x; } {
function pow(x)
{
x = 1 - x;
x *= x * x;
return 1 - x;
}
if (state == -1) { if (state == -1)
{
// startng to open // startng to open
setPanelState("opening"); setPanelState("opening");
wrapper.css('height', '0'); wrapper.css('height', '0');
} }
else if (state < 0) { else if (state < 0)
{
// opening // opening
var height = Math.round(pow(state+1)*(panelHeight-1))+'px'; var height = Math.round(pow(state + 1) * (panelHeight - 1)) + 'px';
wrapper.css('height', height); wrapper.css('height', height);
} }
else if (state == 0) { else if (state == 0)
{
// open // open
setPanelState("open"); setPanelState("open");
wrapper.css('height', panelHeight-1); wrapper.css('height', panelHeight - 1);
} }
else if (state < 1) { else if (state < 1)
{
// closing // closing
setPanelState("closing"); setPanelState("closing");
var height = Math.round((1-pow(state))*(panelHeight-1))+'px'; var height = Math.round((1 - pow(state)) * (panelHeight - 1)) + 'px';
wrapper.css('height', height); wrapper.css('height', height);
} }
else if (state == 1) { else if (state == 1)
{
// closed // closed
setPanelState("closed"); setPanelState("closed");
wrapper.css('height', '0'); wrapper.css('height', '0');
@ -70,16 +86,21 @@ var paddocbar = (function() {
var currentPanel = null; var currentPanel = null;
function setCurrentPanel(newCurrentPanel) {
if (currentPanel != newCurrentPanel) { function setCurrentPanel(newCurrentPanel)
{
if (currentPanel != newCurrentPanel)
{
currentPanel = newCurrentPanel; currentPanel = newCurrentPanel;
padutils.cancelActions("hide-docbar-panel"); padutils.cancelActions("hide-docbar-panel");
} }
} }
var panels; var panels;
function changePassword(newPass) { function changePassword(newPass)
if ((newPass || null) != (self.password || null)) { {
if ((newPass || null) != (self.password || null))
{
self.password = (newPass || null); self.password = (newPass || null);
pad.notifyChangePassword(newPass); pad.notifyChangePassword(newPass);
} }
@ -89,37 +110,72 @@ var paddocbar = (function() {
var self = { var self = {
title: null, title: null,
password: null, password: null,
init: function(opts) { init: function(opts)
{
panels = { panels = {
impexp: { animator: getPanelOpenCloseAnimator("impexp", 160) }, impexp: {
savedrevs: { animator: getPanelOpenCloseAnimator("savedrevs", 79) }, animator: getPanelOpenCloseAnimator("impexp", 160)
options: { animator: getPanelOpenCloseAnimator( },
"options", 114) }, savedrevs: {
security: { animator: getPanelOpenCloseAnimator("security", 130) } animator: getPanelOpenCloseAnimator("savedrevs", 79)
},
options: {
animator: getPanelOpenCloseAnimator("options", 114)
},
security: {
animator: getPanelOpenCloseAnimator("security", 130)
}
}; };
isTitleEditable = opts.isTitleEditable; isTitleEditable = opts.isTitleEditable;
self.title = opts.initialTitle; self.title = opts.initialTitle;
self.password = opts.initialPassword; self.password = opts.initialPassword;
$("#docbarimpexp").click(function() {self.togglePanel("impexp");}); $("#docbarimpexp").click(function()
$("#docbarsavedrevs").click(function() {self.togglePanel("savedrevs");}); {
$("#docbaroptions").click(function() {self.togglePanel("options");}); self.togglePanel("impexp");
$("#docbarsecurity").click(function() {self.togglePanel("security");}); });
$("#docbarsavedrevs").click(function()
{
self.togglePanel("savedrevs");
});
$("#docbaroptions").click(function()
{
self.togglePanel("options");
});
$("#docbarsecurity").click(function()
{
self.togglePanel("security");
});
$("#docbarrenamelink").click(self.editTitle); $("#docbarrenamelink").click(self.editTitle);
$("#padtitlesave").click(function() { self.closeTitleEdit(true); }); $("#padtitlesave").click(function()
$("#padtitlecancel").click(function() { self.closeTitleEdit(false); }); {
padutils.bindEnterAndEscape($("#padtitleedit"), self.closeTitleEdit(true);
function() { });
$("#padtitlesave").trigger('click'); }, $("#padtitlecancel").click(function()
function() { {
$("#padtitlecancel").trigger('click'); }); self.closeTitleEdit(false);
});
padutils.bindEnterAndEscape($("#padtitleedit"), function()
{
$("#padtitlesave").trigger('click');
}, function()
{
$("#padtitlecancel").trigger('click');
});
$("#options-close").click(function() {self.setShownPanel(null);}); $("#options-close").click(function()
$("#security-close").click(function() {self.setShownPanel(null);}); {
self.setShownPanel(null);
});
$("#security-close").click(function()
{
self.setShownPanel(null);
});
if (pad.getIsProPad()) { if (pad.getIsProPad())
{
self.initPassword(); self.initPassword();
} }
@ -127,135 +183,171 @@ var paddocbar = (function() {
self.render(); self.render();
// public/private // public/private
$("#security-access input").bind("change click", function(evt) { $("#security-access input").bind("change click", function(evt)
pad.changePadOption('guestPolicy', {
$("#security-access input[name='padaccess']:checked").val()); pad.changePadOption('guestPolicy', $("#security-access input[name='padaccess']:checked").val());
}); });
self.setGuestPolicy(opts.guestPolicy); self.setGuestPolicy(opts.guestPolicy);
}, },
setGuestPolicy: function(newPolicy) { setGuestPolicy: function(newPolicy)
$("#security-access input[value='"+newPolicy+"']").attr("checked", {
"checked"); $("#security-access input[value='" + newPolicy + "']").attr("checked", "checked");
self.render(); self.render();
}, },
initPassword: function() { initPassword: function()
{
self.renderPassword(); self.renderPassword();
$("#password-clearlink").click(function() { $("#password-clearlink").click(function()
{
changePassword(null); changePassword(null);
}); });
$("#password-setlink, #password-display").click(function() { $("#password-setlink, #password-display").click(function()
{
self.enterPassword(); self.enterPassword();
}); });
$("#password-cancellink").click(function() { $("#password-cancellink").click(function()
{
self.exitPassword(false); self.exitPassword(false);
}); });
$("#password-savelink").click(function() { $("#password-savelink").click(function()
{
self.exitPassword(true); self.exitPassword(true);
}); });
padutils.bindEnterAndEscape($("#security-passwordedit"), padutils.bindEnterAndEscape($("#security-passwordedit"), function()
function() { {
self.exitPassword(true); self.exitPassword(true);
}, }, function()
function() { {
self.exitPassword(false); self.exitPassword(false);
}); });
}, },
enterPassword: function() { enterPassword: function()
{
isEditingPassword = true; isEditingPassword = true;
$("#security-passwordedit").val(self.password || ''); $("#security-passwordedit").val(self.password || '');
self.renderPassword(); self.renderPassword();
$("#security-passwordedit").focus().select(); $("#security-passwordedit").focus().select();
}, },
exitPassword: function(accept) { exitPassword: function(accept)
{
isEditingPassword = false; isEditingPassword = false;
if (accept) { if (accept)
{
changePassword($("#security-passwordedit").val()); changePassword($("#security-passwordedit").val());
} }
else { else
{
self.renderPassword(); self.renderPassword();
} }
}, },
renderPassword: function() { renderPassword: function()
if (isEditingPassword) { {
if (isEditingPassword)
{
$("#password-nonedit").hide(); $("#password-nonedit").hide();
$("#password-inedit").show(); $("#password-inedit").show();
} }
else { else
$("#password-nonedit").toggleClass('nopassword', ! self.password); {
$("#password-nonedit").toggleClass('nopassword', !self.password);
$("#password-setlink").html(self.password ? "Change..." : "Set..."); $("#password-setlink").html(self.password ? "Change..." : "Set...");
if (self.password) { if (self.password)
{
$("#password-display").html(self.password.replace(/./g, '&#8226;')); $("#password-display").html(self.password.replace(/./g, '&#8226;'));
} }
else { else
{
$("#password-display").html("None"); $("#password-display").html("None");
} }
$("#password-inedit").hide(); $("#password-inedit").hide();
$("#password-nonedit").show(); $("#password-nonedit").show();
} }
}, },
togglePanel: function(panelName) { togglePanel: function(panelName)
if (panelName in panels) { {
if (currentPanel == panelName) { if (panelName in panels)
{
if (currentPanel == panelName)
{
self.setShownPanel(null); self.setShownPanel(null);
} }
else { else
{
self.setShownPanel(panelName); self.setShownPanel(panelName);
} }
} }
}, },
setShownPanel: function(panelName) { setShownPanel: function(panelName)
function animateHidePanel(panelName, next) { {
function animateHidePanel(panelName, next)
{
var delay = 0; var delay = 0;
if (panelName == 'options' && isEditingPassword) { if (panelName == 'options' && isEditingPassword)
{
// give user feedback that the password they've // give user feedback that the password they've
// typed in won't actually take effect // typed in won't actually take effect
self.exitPassword(false); self.exitPassword(false);
delay = 500; delay = 500;
} }
window.setTimeout(function() { window.setTimeout(function()
{
panels[panelName].animator.hide(); panels[panelName].animator.hide();
if (next) { if (next)
{
next(); next();
} }
}, delay); }, delay);
} }
if (! panelName) { if (!panelName)
if (currentPanel) { {
if (currentPanel)
{
animateHidePanel(currentPanel); animateHidePanel(currentPanel);
setCurrentPanel(null); setCurrentPanel(null);
} }
} }
else if (panelName in panels) { else if (panelName in panels)
if (currentPanel != panelName) { {
if (currentPanel) { if (currentPanel != panelName)
animateHidePanel(currentPanel, function() { {
if (currentPanel)
{
animateHidePanel(currentPanel, function()
{
panels[panelName].animator.show(); panels[panelName].animator.show();
setCurrentPanel(panelName); setCurrentPanel(panelName);
}); });
} }
else { else
{
panels[panelName].animator.show(); panels[panelName].animator.show();
setCurrentPanel(panelName); setCurrentPanel(panelName);
} }
} }
} }
}, },
isPanelShown: function(panelName) { isPanelShown: function(panelName)
if (! panelName) { {
return ! currentPanel; if (!panelName)
{
return !currentPanel;
} }
else { else
{
return (panelName == currentPanel); return (panelName == currentPanel);
} }
}, },
changeTitle: function(newTitle) { changeTitle: function(newTitle)
{
self.title = newTitle; self.title = newTitle;
self.render(); self.render();
}, },
editTitle: function() { editTitle: function()
if (! enabled) { {
if (!enabled)
{
return; return;
} }
$("#padtitleedit").val(self.title); $("#padtitleedit").val(self.title);
@ -263,13 +355,17 @@ var paddocbar = (function() {
self.render(); self.render();
$("#padtitleedit").focus().select(); $("#padtitleedit").focus().select();
}, },
closeTitleEdit: function(accept) { closeTitleEdit: function(accept)
if (! enabled) { {
if (!enabled)
{
return; return;
} }
if (accept) { if (accept)
{
var newTitle = $("#padtitleedit").val(); var newTitle = $("#padtitleedit").val();
if (newTitle) { if (newTitle)
{
newTitle = newTitle.substring(0, 80); newTitle = newTitle.substring(0, 80);
self.title = newTitle; self.title = newTitle;
@ -280,65 +376,74 @@ var paddocbar = (function() {
isEditingTitle = false; isEditingTitle = false;
self.render(); self.render();
}, },
changePassword: function(newPass) { changePassword: function(newPass)
if (newPass) { {
if (newPass)
{
self.password = newPass; self.password = newPass;
} }
else { else
{
self.password = null; self.password = null;
} }
self.renderPassword(); self.renderPassword();
}, },
render: function() { render: function()
if (isEditingTitle) { {
if (isEditingTitle)
{
$("#docbarpadtitle").hide(); $("#docbarpadtitle").hide();
$("#docbarrenamelink").hide(); $("#docbarrenamelink").hide();
$("#padtitleedit").show(); $("#padtitleedit").show();
$("#padtitlebuttons").show(); $("#padtitlebuttons").show();
if (! enabled) { if (!enabled)
{
$("#padtitleedit").attr('disabled', 'disabled'); $("#padtitleedit").attr('disabled', 'disabled');
} }
else { else
{
$("#padtitleedit").removeAttr('disabled'); $("#padtitleedit").removeAttr('disabled');
} }
} }
else { else
{
$("#padtitleedit").hide(); $("#padtitleedit").hide();
$("#padtitlebuttons").hide(); $("#padtitlebuttons").hide();
var titleSpan = $("#docbarpadtitle span"); var titleSpan = $("#docbarpadtitle span");
titleSpan.html(padutils.escapeHtml(self.title)); titleSpan.html(padutils.escapeHtml(self.title));
$("#docbarpadtitle").attr('title', $("#docbarpadtitle").attr('title', (pad.isPadPublic() ? "Public Pad: " : "") + self.title);
(pad.isPadPublic() ? "Public Pad: " : "")+
self.title);
$("#docbarpadtitle").show(); $("#docbarpadtitle").show();
if (isTitleEditable) { if (isTitleEditable)
var titleRight = $("#docbarpadtitle").position().left + {
$("#docbarpadtitle span").position().left + var titleRight = $("#docbarpadtitle").position().left + $("#docbarpadtitle span").position().left + Math.min($("#docbarpadtitle").width(), $("#docbarpadtitle span").width());
Math.min($("#docbarpadtitle").width(),
$("#docbarpadtitle span").width());
$("#docbarrenamelink").css('left', titleRight + 10).show(); $("#docbarrenamelink").css('left', titleRight + 10).show();
} }
if (pad.isPadPublic()) { if (pad.isPadPublic())
{
$("#docbar").addClass("docbar-public"); $("#docbar").addClass("docbar-public");
} }
else { else
{
$("#docbar").removeClass("docbar-public"); $("#docbar").removeClass("docbar-public");
} }
} }
}, },
disable: function() { disable: function()
{
enabled = false; enabled = false;
self.render(); self.render();
}, },
handleResizePage: function() { handleResizePage: function()
{
padsavedrevs.handleResizePage(); padsavedrevs.handleResizePage();
}, },
hideLaterIfNoOtherInteraction: function() { hideLaterIfNoOtherInteraction: function()
return padutils.getCancellableAction('hide-docbar-panel', {
function() { return padutils.getCancellableAction('hide-docbar-panel', function()
{
self.setShownPanel(null); self.setShownPanel(null);
}); });
} }

View file

@ -15,50 +15,61 @@
*/ */
var padeditbar = (function(){ var padeditbar = (function()
{
var syncAnimation = (function() { var syncAnimation = (function()
{
var SYNCING = -100; var SYNCING = -100;
var DONE = 100; var DONE = 100;
var state = DONE; var state = DONE;
var fps = 25; var fps = 25;
var step = 1/fps; var step = 1 / fps;
var T_START = -0.5; var T_START = -0.5;
var T_FADE = 1.0; var T_FADE = 1.0;
var T_GONE = 1.5; var T_GONE = 1.5;
var animator = padutils.makeAnimationScheduler(function() { var animator = padutils.makeAnimationScheduler(function()
if (state == SYNCING || state == DONE) { {
if (state == SYNCING || state == DONE)
{
return false; return false;
} }
else if (state >= T_GONE) { else if (state >= T_GONE)
{
state = DONE; state = DONE;
$("#syncstatussyncing").css('display', 'none'); $("#syncstatussyncing").css('display', 'none');
$("#syncstatusdone").css('display', 'none'); $("#syncstatusdone").css('display', 'none');
return false; return false;
} }
else if (state < 0) { else if (state < 0)
{
state += step; state += step;
if (state >= 0) { if (state >= 0)
{
$("#syncstatussyncing").css('display', 'none'); $("#syncstatussyncing").css('display', 'none');
$("#syncstatusdone").css('display', 'block').css('opacity', 1); $("#syncstatusdone").css('display', 'block').css('opacity', 1);
} }
return true; return true;
} }
else { else
{
state += step; state += step;
if (state >= T_FADE) { if (state >= T_FADE)
{
$("#syncstatusdone").css('opacity', (T_GONE - state) / (T_GONE - T_FADE)); $("#syncstatusdone").css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
} }
return true; return true;
} }
}, step*1000); }, step * 1000);
return { return {
syncing: function() { syncing: function()
{
state = SYNCING; state = SYNCING;
$("#syncstatussyncing").css('display', 'block'); $("#syncstatussyncing").css('display', 'block');
$("#syncstatusdone").css('display', 'none'); $("#syncstatusdone").css('display', 'none');
}, },
done: function() { done: function()
{
state = T_START; state = T_START;
animator.scheduleAnimation(); animator.scheduleAnimation();
} }
@ -66,23 +77,30 @@ var padeditbar = (function(){
}()); }());
var self = { var self = {
init: function() { init: function()
{
$("#editbar .editbarbutton").attr("unselectable", "on"); // for IE $("#editbar .editbarbutton").attr("unselectable", "on"); // for IE
$("#editbar").removeClass("disabledtoolbar").addClass("enabledtoolbar"); $("#editbar").removeClass("disabledtoolbar").addClass("enabledtoolbar");
}, },
isEnabled: function() { isEnabled: function()
return ! $("#editbar").hasClass('disabledtoolbar'); {
return !$("#editbar").hasClass('disabledtoolbar');
}, },
disable: function() { disable: function()
{
$("#editbar").addClass('disabledtoolbar').removeClass("enabledtoolbar"); $("#editbar").addClass('disabledtoolbar').removeClass("enabledtoolbar");
}, },
toolbarClick: function(cmd) { toolbarClick: function(cmd)
if (self.isEnabled()) { {
if (cmd == 'showusers') { if (self.isEnabled())
{
if (cmd == 'showusers')
{
// show users shows the current users on teh pad // show users shows the current users on teh pad
// get current height // get current height
var editbarheight = $('#users').css('display'); var editbarheight = $('#users').css('display');
if (editbarheight == "none"){ if (editbarheight == "none")
{
// increase the size of the editbar // increase the size of the editbar
//$('#editbar').animate({height:'72px'}); //$('#editbar').animate({height:'72px'});
//$('#editorcontainerbox').animate({top:'72px'}); //$('#editorcontainerbox').animate({top:'72px'});
@ -97,18 +115,23 @@ var padeditbar = (function(){
$('#users').slideUp("fast"); $('#users').slideUp("fast");
} }
} }
if (cmd == 'embed') { if (cmd == 'embed')
{
// embed shows the embed link // embed shows the embed link
// get current height // get current height
var editbarheight = $('#embed').css('display'); var editbarheight = $('#embed').css('display');
if (editbarheight == "none"){ if (editbarheight == "none")
{
// increase the size of the editbar // increase the size of the editbar
//$('#editbar').animate({height:'72px'}); //$('#editbar').animate({height:'72px'});
$('#editorcontainerbox').animate({top:'72px'}); $('#editorcontainerbox').animate(
{
top: '72px'
});
// get the pad url // get the pad url
padurl = document.location; padurl = document.location;
// change the div contents to include the pad url in an input box // change the div contents to include the pad url in an input box
$('#embed').html('<div id="embedcode">Embed code:<input id="embedinput" type="text" value="<iframe src=&quot;'+padurl+'&quot; width=500 height=400>"</iframe></div>'); $('#embed').html('<div id="embedcode">Embed code:<input id="embedinput" type="text" value="<iframe src=&quot;' + padurl + '&quot; width=500 height=400>"</iframe></div>');
$('#users').slideUp("fast"); $('#users').slideUp("fast");
$('#embed').slideDown("fast"); $('#embed').slideDown("fast");
} }
@ -116,33 +139,48 @@ var padeditbar = (function(){
{ {
// increase the size of the editbar // increase the size of the editbar
//$('#editbar').animate({height:'36px'}); //$('#editbar').animate({height:'36px'});
$('#editorcontainerbox').animate({top:'36px'}); $('#editorcontainerbox').animate(
{
top: '36px'
});
$('#embed').hide(); $('#embed').hide();
} }
} }
if (cmd == 'save') { if (cmd == 'save')
{
padsavedrevs.saveNow(); padsavedrevs.saveNow();
} else { }
padeditor.ace.callWithAce(function (ace) { else
if (cmd == 'bold' || cmd == 'italic' || cmd == 'underline' || cmd == 'strikethrough') {
ace.ace_toggleAttributeOnSelection(cmd); padeditor.ace.callWithAce(function(ace)
else if (cmd == 'undo' || cmd == 'redo') {
ace.ace_doUndoRedo(cmd); if (cmd == 'bold' || cmd == 'italic' || cmd == 'underline' || cmd == 'strikethrough') ace.ace_toggleAttributeOnSelection(cmd);
else if (cmd == 'insertunorderedlist') else if (cmd == 'undo' || cmd == 'redo') ace.ace_doUndoRedo(cmd);
ace.ace_doInsertUnorderedList(); else if (cmd == 'insertunorderedlist') ace.ace_doInsertUnorderedList();
else if (cmd == 'indent') { else if (cmd == 'indent')
if (! ace.ace_doIndentOutdent(false)) { {
if (!ace.ace_doIndentOutdent(false))
{
ace.ace_doInsertUnorderedList(); ace.ace_doInsertUnorderedList();
} }
} else if (cmd == 'outdent') { }
else if (cmd == 'outdent')
{
ace.ace_doIndentOutdent(true); ace.ace_doIndentOutdent(true);
} else if (cmd == 'clearauthorship') {
if ((!(ace.ace_getRep().selStart && ace.ace_getRep().selEnd)) || ace.ace_isCaret()) {
if (window.confirm("Clear authorship colors on entire document?")) {
ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length,
[['author', '']]);
} }
} else { else if (cmd == 'clearauthorship')
{
if ((!(ace.ace_getRep().selStart && ace.ace_getRep().selEnd)) || ace.ace_isCaret())
{
if (window.confirm("Clear authorship colors on entire document?"))
{
ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length, [
['author', '']
]);
}
}
else
{
ace.ace_setAttributeOnSelection('author', ''); ace.ace_setAttributeOnSelection('author', '');
} }
} }
@ -151,11 +189,14 @@ var padeditbar = (function(){
} }
padeditor.ace.focus(); padeditor.ace.focus();
}, },
setSyncStatus: function(status) { setSyncStatus: function(status)
if (status == "syncing") { {
if (status == "syncing")
{
syncAnimation.syncing(); syncAnimation.syncing();
} }
else if (status == "done") { else if (status == "done")
{
syncAnimation.done(); syncAnimation.done();
} }
} }

View file

@ -15,15 +15,20 @@
*/ */
var padeditor = (function(){ var padeditor = (function()
{
var self = { var self = {
ace: null, // this is accessed directly from other files ace: null,
// this is accessed directly from other files
viewZoom: 100, viewZoom: 100,
init: function(readyFunc, initialViewOptions) { init: function(readyFunc, initialViewOptions)
{
function aceReady() { function aceReady()
{
$("#editorloadingbox").hide(); $("#editorloadingbox").hide();
if (readyFunc) { if (readyFunc)
{
readyFunc(); readyFunc();
} }
} }
@ -31,7 +36,8 @@ var padeditor = (function(){
self.ace = new Ace2Editor(); self.ace = new Ace2Editor();
self.ace.init("editorcontainer", "", aceReady); self.ace.init("editorcontainer", "", aceReady);
self.ace.setProperty("wraps", true); self.ace.setProperty("wraps", true);
if (pad.getIsDebugEnabled()) { if (pad.getIsDebugEnabled())
{
self.ace.setProperty("dmesg", pad.dmesg); self.ace.setProperty("dmesg", pad.dmesg);
} }
self.initViewOptions(); self.initViewOptions();
@ -41,22 +47,25 @@ var padeditor = (function(){
self.initViewZoom(); self.initViewZoom();
$("#viewbarcontents").show(); $("#viewbarcontents").show();
}, },
initViewOptions: function() { initViewOptions: function()
padutils.bindCheckboxChange($("#options-linenoscheck"), function() { {
pad.changeViewOption('showLineNumbers', padutils.bindCheckboxChange($("#options-linenoscheck"), function()
padutils.getCheckbox($("#options-linenoscheck"))); {
pad.changeViewOption('showLineNumbers', padutils.getCheckbox($("#options-linenoscheck")));
}); });
padutils.bindCheckboxChange($("#options-colorscheck"), function() { padutils.bindCheckboxChange($("#options-colorscheck"), function()
pad.changeViewOption('showAuthorColors', {
padutils.getCheckbox("#options-colorscheck")); pad.changeViewOption('showAuthorColors', padutils.getCheckbox("#options-colorscheck"));
}); });
$("#viewfontmenu").change(function() { $("#viewfontmenu").change(function()
pad.changeViewOption('useMonospaceFont', {
$("#viewfontmenu").val() == 'monospace'); pad.changeViewOption('useMonospaceFont', $("#viewfontmenu").val() == 'monospace');
}); });
}, },
setViewOptions: function(newOptions) { setViewOptions: function(newOptions)
function getOption(key, defaultValue) { {
function getOption(key, defaultValue)
{
var value = String(newOptions[key]); var value = String(newOptions[key]);
if (value == "true") return true; if (value == "true") return true;
if (value == "false") return false; if (value == "false") return false;
@ -73,52 +82,59 @@ var padeditor = (function(){
padutils.setCheckbox($("#options-colorscheck"), v); padutils.setCheckbox($("#options-colorscheck"), v);
v = getOption('useMonospaceFont', false); v = getOption('useMonospaceFont', false);
self.ace.setProperty("textface", self.ace.setProperty("textface", (v ? "monospace" : "Arial, sans-serif"));
(v ? "monospace" : "Arial, sans-serif"));
$("#viewfontmenu").val(v ? "monospace" : "normal"); $("#viewfontmenu").val(v ? "monospace" : "normal");
}, },
initViewZoom: function() { initViewZoom: function()
{
var viewZoom = Number(padcookie.getPref('viewZoom')); var viewZoom = Number(padcookie.getPref('viewZoom'));
if ((! viewZoom) || isNaN(viewZoom)) { if ((!viewZoom) || isNaN(viewZoom))
{
viewZoom = 100; viewZoom = 100;
} }
self.setViewZoom(viewZoom); self.setViewZoom(viewZoom);
$("#viewzoommenu").change(function(evt) { $("#viewzoommenu").change(function(evt)
{
// strip initial 'z' from val // strip initial 'z' from val
self.setViewZoom(Number($("#viewzoommenu").val().substring(1))); self.setViewZoom(Number($("#viewzoommenu").val().substring(1)));
}); });
}, },
setViewZoom: function(percent) { setViewZoom: function(percent)
if (! (percent >= 50 && percent <= 1000)) { {
if (!(percent >= 50 && percent <= 1000))
{
// percent is out of sane range or NaN (which fails comparisons) // percent is out of sane range or NaN (which fails comparisons)
return; return;
} }
self.viewZoom = percent; self.viewZoom = percent;
$("#viewzoommenu").val('z'+percent); $("#viewzoommenu").val('z' + percent);
var baseSize = 13; var baseSize = 13;
self.ace.setProperty('textsize', self.ace.setProperty('textsize', Math.round(baseSize * self.viewZoom / 100));
Math.round(baseSize * self.viewZoom / 100));
padcookie.setPref('viewZoom', percent); padcookie.setPref('viewZoom', percent);
}, },
dispose: function() { dispose: function()
if (self.ace) { {
if (self.ace)
{
self.ace.destroy(); self.ace.destroy();
} }
}, },
disable: function() { disable: function()
if (self.ace) { {
if (self.ace)
{
self.ace.setProperty("grayedOut", true); self.ace.setProperty("grayedOut", true);
self.ace.setEditable(false); self.ace.setEditable(false);
} }
}, },
restoreRevisionText: function(dataFromServer) { restoreRevisionText: function(dataFromServer)
{
pad.addHistoricalAuthors(dataFromServer.historicalAuthorData); pad.addHistoricalAuthors(dataFromServer.historicalAuthorData);
self.ace.importAText(dataFromServer.atext, dataFromServer.apool, true); self.ace.importAText(dataFromServer.atext, dataFromServer.apool, true);
} }
}; };
return self; return self;
}()); }());

View file

@ -15,121 +15,182 @@
*/ */
var padimpexp = (function() { var padimpexp = (function()
{
///// import ///// import
var currentImportTimer = null; var currentImportTimer = null;
var hidePanelCall = null; var hidePanelCall = null;
function addImportFrames() { function addImportFrames()
{
$("#impexp-import .importframe").remove(); $("#impexp-import .importframe").remove();
$('#impexp-import').append( $('#impexp-import').append(
$('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>')); $('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>'));
} }
function fileInputUpdated() {
function fileInputUpdated()
{
$('#importformfilediv').addClass('importformenabled'); $('#importformfilediv').addClass('importformenabled');
$('#importsubmitinput').removeAttr('disabled'); $('#importsubmitinput').removeAttr('disabled');
$('#importmessagefail').fadeOut("fast"); $('#importmessagefail').fadeOut("fast");
$('#importarrow').show(); $('#importarrow').show();
$('#importarrow').animate({paddingLeft:"0px"}, 500) $('#importarrow').animate(
.animate({paddingLeft:"10px"}, 150, 'swing') {
.animate({paddingLeft:"0px"}, 150, 'swing') paddingLeft: "0px"
.animate({paddingLeft:"10px"}, 150, 'swing') }, 500).animate(
.animate({paddingLeft:"0px"}, 150, 'swing') {
.animate({paddingLeft:"10px"}, 150, 'swing') paddingLeft: "10px"
.animate({paddingLeft:"0px"}, 150, 'swing'); }, 150, 'swing').animate(
{
paddingLeft: "0px"
}, 150, 'swing').animate(
{
paddingLeft: "10px"
}, 150, 'swing').animate(
{
paddingLeft: "0px"
}, 150, 'swing').animate(
{
paddingLeft: "10px"
}, 150, 'swing').animate(
{
paddingLeft: "0px"
}, 150, 'swing');
} }
function fileInputSubmit() {
function fileInputSubmit()
{
$('#importmessagefail').fadeOut("fast"); $('#importmessagefail').fadeOut("fast");
var ret = window.confirm( var ret = window.confirm("Importing a file will overwrite the current text of the pad." + " Are you sure you want to proceed?");
"Importing a file will overwrite the current text of the pad."+ if (ret)
" Are you sure you want to proceed?"); {
if (ret) {
hidePanelCall = paddocbar.hideLaterIfNoOtherInteraction(); hidePanelCall = paddocbar.hideLaterIfNoOtherInteraction();
currentImportTimer = window.setTimeout(function() { currentImportTimer = window.setTimeout(function()
if (! currentImportTimer) { {
if (!currentImportTimer)
{
return; return;
} }
currentImportTimer = null; currentImportTimer = null;
importFailed("Request timed out."); importFailed("Request timed out.");
}, 25000); // time out after some number of seconds }, 25000); // time out after some number of seconds
$('#importsubmitinput').attr({disabled: true}).val("Importing..."); $('#importsubmitinput').attr(
window.setTimeout(function() { {
$('#importfileinput').attr({disabled: true}); }, 0); disabled: true
}).val("Importing...");
window.setTimeout(function()
{
$('#importfileinput').attr(
{
disabled: true
});
}, 0);
$('#importarrow').stop(true, true).hide(); $('#importarrow').stop(true, true).hide();
$('#importstatusball').show(); $('#importstatusball').show();
} }
return ret; return ret;
} }
function importFailed(msg) {
function importFailed(msg)
{
importErrorMessage(msg); importErrorMessage(msg);
importDone(); importDone();
addImportFrames(); addImportFrames();
} }
function importDone() {
function importDone()
{
$('#importsubmitinput').removeAttr('disabled').val("Import Now"); $('#importsubmitinput').removeAttr('disabled').val("Import Now");
window.setTimeout(function() { window.setTimeout(function()
$('#importfileinput').removeAttr('disabled'); }, 0); {
$('#importfileinput').removeAttr('disabled');
}, 0);
$('#importstatusball').hide(); $('#importstatusball').hide();
importClearTimeout(); importClearTimeout();
} }
function importClearTimeout() {
if (currentImportTimer) { function importClearTimeout()
{
if (currentImportTimer)
{
window.clearTimeout(currentImportTimer); window.clearTimeout(currentImportTimer);
currentImportTimer = null; currentImportTimer = null;
} }
} }
function importErrorMessage(msg) {
function showError(fade) { function importErrorMessage(msg)
$('#importmessagefail').html( {
'<strong style="color: red">Import failed:</strong> '+ function showError(fade)
(msg || 'Please try a different file.'))[(fade?"fadeIn":"show")](); {
$('#importmessagefail').html('<strong style="color: red">Import failed:</strong> ' + (msg || 'Please try a different file.'))[(fade ? "fadeIn" : "show")]();
} }
if ($('#importexport .importmessage').is(':visible')) { if ($('#importexport .importmessage').is(':visible'))
{
$('#importmessagesuccess').fadeOut("fast"); $('#importmessagesuccess').fadeOut("fast");
$('#importmessagefail').fadeOut("fast", function() { $('#importmessagefail').fadeOut("fast", function()
showError(true); }); {
} else { showError(true);
});
}
else
{
showError(); showError();
} }
} }
function importSuccessful(token) {
$.ajax({ function importSuccessful(token)
{
$.ajax(
{
type: 'post', type: 'post',
url: '/ep/pad/impexp/import2', url: '/ep/pad/impexp/import2',
data: {token: token, padId: pad.getPadId()}, data: {
token: token,
padId: pad.getPadId()
},
success: importApplicationSuccessful, success: importApplicationSuccessful,
error: importApplicationFailed, error: importApplicationFailed,
timeout: 25000 timeout: 25000
}); });
addImportFrames(); addImportFrames();
} }
function importApplicationFailed(xhr, textStatus, errorThrown) {
function importApplicationFailed(xhr, textStatus, errorThrown)
{
importErrorMessage("Error during conversion."); importErrorMessage("Error during conversion.");
importDone(); importDone();
} }
function importApplicationSuccessful(data, textStatus) {
if (data.substr(0, 2) == "ok") { function importApplicationSuccessful(data, textStatus)
if ($('#importexport .importmessage').is(':visible')) { {
if (data.substr(0, 2) == "ok")
{
if ($('#importexport .importmessage').is(':visible'))
{
$('#importexport .importmessage').hide(); $('#importexport .importmessage').hide();
} }
$('#importmessagesuccess').html( $('#importmessagesuccess').html('<strong style="color: green">Import successful!</strong>').show();
'<strong style="color: green">Import successful!</strong>').show();
$('#importformfilediv').hide(); $('#importformfilediv').hide();
window.setTimeout(function() { window.setTimeout(function()
$('#importmessagesuccess').fadeOut("slow", function() { {
$('#importmessagesuccess').fadeOut("slow", function()
{
$('#importformfilediv').show(); $('#importformfilediv').show();
}); });
if (hidePanelCall) { if (hidePanelCall)
{
hidePanelCall(); hidePanelCall();
} }
}, 3000); }, 3000);
} else if (data.substr(0, 4) == "fail") { }
importErrorMessage( else if (data.substr(0, 4) == "fail")
"Couldn't update pad contents. This can happen if your web browser has \"cookies\" disabled."); {
} else if (data.substr(0, 4) == "msg:") { importErrorMessage("Couldn't update pad contents. This can happen if your web browser has \"cookies\" disabled.");
}
else if (data.substr(0, 4) == "msg:")
{
importErrorMessage(data.substr(4)); importErrorMessage(data.substr(4));
} }
importDone(); importDone();
@ -137,47 +198,62 @@ var padimpexp = (function() {
///// export ///// export
function cantExport() { function cantExport()
{
var type = $(this); var type = $(this);
if (type.hasClass("exporthrefpdf")) { if (type.hasClass("exporthrefpdf"))
{
type = "PDF"; type = "PDF";
} else if (type.hasClass("exporthrefdoc")) { }
else if (type.hasClass("exporthrefdoc"))
{
type = "Microsoft Word"; type = "Microsoft Word";
} else if (type.hasClass("exporthrefodt")) { }
else if (type.hasClass("exporthrefodt"))
{
type = "OpenDocument"; type = "OpenDocument";
} else { }
else
{
type = "this file"; type = "this file";
} }
alert("Exporting as "+type+" format is disabled. Please contact your"+ alert("Exporting as " + type + " format is disabled. Please contact your" + " system administrator for details.");
" system administrator for details.");
return false; return false;
} }
///// /////
var self = { var self = {
init: function() { init: function()
$("#impexp-close").click(function() {paddocbar.setShownPanel(null);}); {
$("#impexp-close").click(function()
{
paddocbar.setShownPanel(null);
});
addImportFrames(); addImportFrames();
$("#importfileinput").change(fileInputUpdated); $("#importfileinput").change(fileInputUpdated);
$('#importform').submit(fileInputSubmit); $('#importform').submit(fileInputSubmit);
$('.disabledexport').click(cantExport); $('.disabledexport').click(cantExport);
}, },
handleFrameCall: function(callName, argsArray) { handleFrameCall: function(callName, argsArray)
if (callName == 'importFailed') { {
if (callName == 'importFailed')
{
importFailed(argsArray[0]); importFailed(argsArray[0]);
} }
else if (callName == 'importSuccessful') { else if (callName == 'importSuccessful')
{
importSuccessful(argsArray[0]); importSuccessful(argsArray[0]);
} }
}, },
disable: function() { disable: function()
{
$("#impexp-disabled-clickcatcher").show(); $("#impexp-disabled-clickcatcher").show();
$("#impexp-import").css('opacity', 0.5); $("#impexp-import").css('opacity', 0.5);
$("#impexp-export").css('opacity', 0.5); $("#impexp-export").css('opacity', 0.5);
}, },
enable: function() { enable: function()
{
$("#impexp-disabled-clickcatcher").hide(); $("#impexp-disabled-clickcatcher").hide();
$("#impexp-import").css('opacity', 1); $("#impexp-import").css('opacity', 1);
$("#impexp-export").css('opacity', 1); $("#impexp-export").css('opacity', 1);

View file

@ -14,9 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
var padmodals = (function() { var padmodals = (function()
{
/*var clearFeedbackEmail = function() {}; /*var clearFeedbackEmail = function() {};
function clearFeedback() { function clearFeedback() {
clearFeedbackEmail(); clearFeedbackEmail();
$("#feedbackbox-message").val(''); $("#feedbackbox-message").val('');
@ -37,138 +38,185 @@ var padmodals = (function() {
}*/ }*/
var sendingInvite = false; var sendingInvite = false;
function setSendingInvite(v) {
function setSendingInvite(v)
{
v = !! v; v = !! v;
if (sendingInvite != v) { if (sendingInvite != v)
{
sendingInvite = v; sendingInvite = v;
if (v) { if (v)
{
$(".sharebox-send").css('opacity', 0.75); $(".sharebox-send").css('opacity', 0.75);
} }
else { else
{
$("#sharebox-send").css('opacity', 1); $("#sharebox-send").css('opacity', 1);
} }
} }
} }
var clearShareBoxTo = function() {}; var clearShareBoxTo = function()
function clearShareBox() { {};
function clearShareBox()
{
clearShareBoxTo(); clearShareBoxTo();
} }
var self = { var self = {
init: function() { init: function()
{
self.initFeedback(); self.initFeedback();
self.initShareBox(); self.initShareBox();
}, },
initFeedback: function() { initFeedback: function()
/*var emailField = $("#feedbackbox-email"); {
/*var emailField = $("#feedbackbox-email");
clearFeedbackEmail = clearFeedbackEmail =
padutils.makeFieldLabeledWhenEmpty(emailField, '(your email address)').clear; padutils.makeFieldLabeledWhenEmpty(emailField, '(your email address)').clear;
clearFeedback();*/ clearFeedback();*/
$("#feedbackbox-hide").click(function() { $("#feedbackbox-hide").click(function()
{
self.hideModal(); self.hideModal();
}); });
/*$("#feedbackbox-send").click(function() { /*$("#feedbackbox-send").click(function() {
self.sendFeedbackEmail(); self.sendFeedbackEmail();
});*/ });*/
$("#feedbackbutton").click(function() { $("#feedbackbutton").click(function()
{
self.showFeedback(); self.showFeedback();
}); });
}, },
initShareBox: function() { initShareBox: function()
$("#sharebutton").click(function() { {
$("#sharebutton").click(function()
{
self.showShareBox(); self.showShareBox();
}); });
$("#sharebox-hide").click(function() { $("#sharebox-hide").click(function()
{
self.hideModal(); self.hideModal();
}); });
$("#sharebox-send").click(function() { $("#sharebox-send").click(function()
{
self.sendInvite(); self.sendInvite();
}); });
$("#sharebox-url").click(function() { $("#sharebox-url").click(function()
{
$("#sharebox-url").focus().select(); $("#sharebox-url").focus().select();
}); });
clearShareBoxTo = clearShareBoxTo = padutils.makeFieldLabeledWhenEmpty($("#sharebox-to"), "(email addresses)").clear;
padutils.makeFieldLabeledWhenEmpty($("#sharebox-to"),
"(email addresses)").clear;
clearShareBox(); clearShareBox();
$("#sharebox-subject").val(self.getDefaultShareBoxSubjectForName(pad.getUserName())); $("#sharebox-subject").val(self.getDefaultShareBoxSubjectForName(pad.getUserName()));
$("#sharebox-message").val(self.getDefaultShareBoxMessageForName(pad.getUserName())); $("#sharebox-message").val(self.getDefaultShareBoxMessageForName(pad.getUserName()));
$("#sharebox-stripe .setsecurity").click(function() { $("#sharebox-stripe .setsecurity").click(function()
{
self.hideModal(); self.hideModal();
paddocbar.setShownPanel('security'); paddocbar.setShownPanel('security');
}); });
}, },
getDefaultShareBoxMessageForName: function(name) { getDefaultShareBoxMessageForName: function(name)
return (name || "Somebody")+" has shared an EtherPad document with you."+ {
"\n\n"+"View it here:\n\n"+ return (name || "Somebody") + " has shared an EtherPad document with you." + "\n\n" + "View it here:\n\n" + padutils.escapeHtml($(".sharebox-url").val() + "\n");
padutils.escapeHtml($(".sharebox-url").val()+"\n");
}, },
getDefaultShareBoxSubjectForName: function(name) { getDefaultShareBoxSubjectForName: function(name)
return (name || "Somebody")+" invited you to an EtherPad document"; {
return (name || "Somebody") + " invited you to an EtherPad document";
}, },
relayoutWithBottom: function(px) { relayoutWithBottom: function(px)
{
$("#modaloverlay").height(px); $("#modaloverlay").height(px);
$("#sharebox").css('left', $("#sharebox").css('left', Math.floor(($(window).width() - $("#sharebox").outerWidth()) / 2));
Math.floor(($(window).width() - $("#feedbackbox").css('left', Math.floor(($(window).width() - $("#feedbackbox").outerWidth()) / 2));
$("#sharebox").outerWidth())/2));
$("#feedbackbox").css('left',
Math.floor(($(window).width() -
$("#feedbackbox").outerWidth())/2));
}, },
showFeedback: function() { showFeedback: function()
{
self.showModal("#feedbackbox"); self.showModal("#feedbackbox");
}, },
showShareBox: function() { showShareBox: function()
{
// when showing the dialog, if it still says "Somebody" invited you // when showing the dialog, if it still says "Somebody" invited you
// then we fill in the updated username if there is one; // then we fill in the updated username if there is one;
// otherwise, we don't touch it, perhaps the user is happy with it // otherwise, we don't touch it, perhaps the user is happy with it
var msgbox = $("#sharebox-message"); var msgbox = $("#sharebox-message");
if (msgbox.val() == self.getDefaultShareBoxMessageForName(null)) { if (msgbox.val() == self.getDefaultShareBoxMessageForName(null))
{
msgbox.val(self.getDefaultShareBoxMessageForName(pad.getUserName())); msgbox.val(self.getDefaultShareBoxMessageForName(pad.getUserName()));
} }
var subjBox = $("#sharebox-subject"); var subjBox = $("#sharebox-subject");
if (subjBox.val() == self.getDefaultShareBoxSubjectForName(null)) { if (subjBox.val() == self.getDefaultShareBoxSubjectForName(null))
{
subjBox.val(self.getDefaultShareBoxSubjectForName(pad.getUserName())); subjBox.val(self.getDefaultShareBoxSubjectForName(pad.getUserName()));
} }
if (pad.isPadPublic()) { if (pad.isPadPublic())
{
$("#sharebox-stripe").get(0).className = 'sharebox-stripe-public'; $("#sharebox-stripe").get(0).className = 'sharebox-stripe-public';
} }
else { else
{
$("#sharebox-stripe").get(0).className = 'sharebox-stripe-private'; $("#sharebox-stripe").get(0).className = 'sharebox-stripe-private';
} }
self.showModal("#sharebox", 500); self.showModal("#sharebox", 500);
$("#sharebox-url").focus().select(); $("#sharebox-url").focus().select();
}, },
showModal: function(modalId, duration) { showModal: function(modalId, duration)
{
$(".modaldialog").hide(); $(".modaldialog").hide();
$(modalId).show().css({'opacity': 0}).animate({'opacity': 1}, duration); $(modalId).show().css(
$("#modaloverlay").show().css({'opacity': 0}).animate({'opacity': 1}, duration); {
'opacity': 0
}).animate(
{
'opacity': 1
}, duration);
$("#modaloverlay").show().css(
{
'opacity': 0
}).animate(
{
'opacity': 1
}, duration);
}, },
hideModal: function(duration) { hideModal: function(duration)
{
padutils.cancelActions('hide-feedbackbox'); padutils.cancelActions('hide-feedbackbox');
padutils.cancelActions('hide-sharebox'); padutils.cancelActions('hide-sharebox');
$("#sharebox-response").hide(); $("#sharebox-response").hide();
$(".modaldialog").animate({'opacity': 0}, duration, function () { $("#modaloverlay").hide(); }); $(".modaldialog").animate(
$("#modaloverlay").animate({'opacity': 0}, duration, function () { $("#modaloverlay").hide(); }); {
'opacity': 0
}, duration, function()
{
$("#modaloverlay").hide();
});
$("#modaloverlay").animate(
{
'opacity': 0
}, duration, function()
{
$("#modaloverlay").hide();
});
}, },
hideFeedbackLaterIfNoOtherInteraction: function() { hideFeedbackLaterIfNoOtherInteraction: function()
return padutils.getCancellableAction('hide-feedbackbox', {
function() { return padutils.getCancellableAction('hide-feedbackbox', function()
{
self.hideModal(); self.hideModal();
}); });
}, },
hideShareboxLaterIfNoOtherInteraction: function() { hideShareboxLaterIfNoOtherInteraction: function()
return padutils.getCancellableAction('hide-sharebox', {
function() { return padutils.getCancellableAction('hide-sharebox', function()
{
self.hideModal(); self.hideModal();
}); });
}, },
@ -217,38 +265,44 @@ var padmodals = (function() {
$("#feedbackbox-response").show(); $("#feedbackbox-response").show();
} }
},*/ },*/
sendInvite: function() { sendInvite: function()
if (sendingInvite) { {
if (sendingInvite)
{
return; return;
} }
if (! pad.isFullyConnected()) { if (!pad.isFullyConnected())
{
displayErrorMessage("Error: Connection to the server is down or flaky."); displayErrorMessage("Error: Connection to the server is down or flaky.");
return; return;
} }
var message = $("#sharebox-message").val(); var message = $("#sharebox-message").val();
if (! message) { if (!message)
{
displayErrorMessage("Please enter a message body before sending."); displayErrorMessage("Please enter a message body before sending.");
return; return;
} }
var emails = ($("#sharebox-to").hasClass('editempty') ? '' : var emails = ($("#sharebox-to").hasClass('editempty') ? '' : $("#sharebox-to").val()) || '';
$("#sharebox-to").val()) || '';
// find runs of characters that aren't obviously non-email punctuation // find runs of characters that aren't obviously non-email punctuation
var emailArray = emails.match(/[^\s,:;<>\"\'\/\(\)\[\]{}]+/g) || []; var emailArray = emails.match(/[^\s,:;<>\"\'\/\(\)\[\]{}]+/g) || [];
if (emailArray.length == 0) { if (emailArray.length == 0)
{
displayErrorMessage('Please enter at least one "To:" address.'); displayErrorMessage('Please enter at least one "To:" address.');
$("#sharebox-to").focus().select(); $("#sharebox-to").focus().select();
return; return;
} }
for(var i=0;i<emailArray.length;i++) { for (var i = 0; i < emailArray.length; i++)
{
var addr = emailArray[i]; var addr = emailArray[i];
if (! addr.match(/^[\w\.\_\+\-]+\@[\w\_\-]+\.[\w\_\-\.]+$/)) { if (!addr.match(/^[\w\.\_\+\-]+\@[\w\_\-]+\.[\w\_\-\.]+$/))
displayErrorMessage('"'+padutils.escapeHtml(addr) + {
'" does not appear to be a valid email address.'); displayErrorMessage('"' + padutils.escapeHtml(addr) + '" does not appear to be a valid email address.');
return; return;
} }
} }
var subject = $("#sharebox-subject").val(); var subject = $("#sharebox-subject").val();
if (! subject) { if (!subject)
{
subject = self.getDefaultShareBoxSubjectForName(pad.getUserName()); subject = self.getDefaultShareBoxSubjectForName(pad.getUserName());
$("#sharebox-subject").val(subject); // force the default subject $("#sharebox-subject").val(subject); // force the default subject
} }
@ -258,7 +312,8 @@ var padmodals = (function() {
setSendingInvite(true); setSendingInvite(true);
$("#sharebox-response").html("Sending...").get(0).className = ''; $("#sharebox-response").html("Sending...").get(0).className = '';
$("#sharebox-response").show(); $("#sharebox-response").show();
$.ajax({ $.ajax(
{
type: 'post', type: 'post',
url: '/ep/pad/emailinvite', url: '/ep/pad/emailinvite',
data: { data: {
@ -272,22 +327,30 @@ var padmodals = (function() {
error: error error: error
}); });
var hideCall = self.hideShareboxLaterIfNoOtherInteraction(); var hideCall = self.hideShareboxLaterIfNoOtherInteraction();
function success(msg) {
function success(msg)
{
setSendingInvite(false); setSendingInvite(false);
$("#sharebox-response").html("Email invitation sent!").get(0).className = 'goodresponse'; $("#sharebox-response").html("Email invitation sent!").get(0).className = 'goodresponse';
$("#sharebox-response").show(); $("#sharebox-response").show();
window.setTimeout(function() { window.setTimeout(function()
$("#sharebox-response").fadeOut('slow', function() { {
$("#sharebox-response").fadeOut('slow', function()
{
hideCall(); hideCall();
}); });
}, 1500); }, 1500);
} }
function error(e) {
function error(e)
{
setSendingFeedback(false); setSendingFeedback(false);
$("#sharebox-response").html("An error occurred; no email was sent.").get(0).className = 'badresponse'; $("#sharebox-response").html("An error occurred; no email was sent.").get(0).className = 'badresponse';
$("#sharebox-response").show(); $("#sharebox-response").show();
} }
function displayErrorMessage(msgHtml) {
function displayErrorMessage(msgHtml)
{
$("#sharebox-response").html(msgHtml).get(0).className = 'badresponse'; $("#sharebox-response").html(msgHtml).get(0).className = 'badresponse';
$("#sharebox-response").show(); $("#sharebox-response").show();
} }

View file

@ -15,46 +15,51 @@
*/ */
var padsavedrevs = (function() { var padsavedrevs = (function()
{
function reversedCopy(L) { function reversedCopy(L)
{
var L2 = L.slice(); var L2 = L.slice();
L2.reverse(); L2.reverse();
return L2; return L2;
} }
function makeRevisionBox(revisionInfo, rnum) { function makeRevisionBox(revisionInfo, rnum)
var box = $('<div class="srouterbox">'+ {
'<div class="srinnerbox">'+ var box = $('<div class="srouterbox">' + '<div class="srinnerbox">' + '<a href="javascript:void(0)" class="srname"><!-- --></a>' + '<div class="sractions"><a class="srview" href="javascript:void(0)" target="_blank">view</a> | <a class="srrestore" href="javascript:void(0)">restore</a></div>' + '<div class="srtime"><!-- --></div>' + '<div class="srauthor"><!-- --></div>' + '<img class="srtwirly" src="static/img/misc/status-ball.gif">' + '</div></div>');
'<a href="javascript:void(0)" class="srname"><!-- --></a>'+
'<div class="sractions"><a class="srview" href="javascript:void(0)" target="_blank">view</a> | <a class="srrestore" href="javascript:void(0)">restore</a></div>'+
'<div class="srtime"><!-- --></div>'+
'<div class="srauthor"><!-- --></div>'+
'<img class="srtwirly" src="static/img/misc/status-ball.gif">'+
'</div></div>');
setBoxLabel(box, revisionInfo.label); setBoxLabel(box, revisionInfo.label);
setBoxTimestamp(box, revisionInfo.timestamp); setBoxTimestamp(box, revisionInfo.timestamp);
box.find(".srauthor").html("by "+padutils.escapeHtml(revisionInfo.savedBy)); box.find(".srauthor").html("by " + padutils.escapeHtml(revisionInfo.savedBy));
var viewLink = '/ep/pad/view/'+pad.getPadId()+'/'+revisionInfo.id; var viewLink = '/ep/pad/view/' + pad.getPadId() + '/' + revisionInfo.id;
box.find(".srview").attr('href', viewLink); box.find(".srview").attr('href', viewLink);
var restoreLink = 'javascript:void padsavedrevs.restoreRevision('+rnum+');'; var restoreLink = 'javascript:void padsavedrevs.restoreRevision(' + rnum + ');';
box.find(".srrestore").attr('href', restoreLink); box.find(".srrestore").attr('href', restoreLink);
box.find(".srname").click(function(evt) { box.find(".srname").click(function(evt)
{
editRevisionLabel(rnum, box); editRevisionLabel(rnum, box);
}); });
return box; return box;
} }
function setBoxLabel(box, label) {
function setBoxLabel(box, label)
{
box.find(".srname").html(padutils.escapeHtml(label)).attr('title', label); box.find(".srname").html(padutils.escapeHtml(label)).attr('title', label);
} }
function setBoxTimestamp(box, timestamp) {
function setBoxTimestamp(box, timestamp)
{
box.find(".srtime").html(padutils.escapeHtml( box.find(".srtime").html(padutils.escapeHtml(
padutils.timediff(new Date(timestamp)))); padutils.timediff(new Date(timestamp))));
} }
function getNthBox(n) {
function getNthBox(n)
{
return $("#savedrevisions .srouterbox").eq(n); return $("#savedrevisions .srouterbox").eq(n);
} }
function editRevisionLabel(rnum, box) {
function editRevisionLabel(rnum, box)
{
var input = $('<input type="text" class="srnameedit"/>'); var input = $('<input type="text" class="srnameedit"/>');
box.find(".srnameedit").remove(); // just in case box.find(".srnameedit").remove(); // just in case
var label = box.find(".srname"); var label = box.find(".srname");
@ -64,114 +69,148 @@ var padsavedrevs = (function() {
input.css('left', label.position().left); input.css('left', label.position().left);
label.after(input); label.after(input);
label.css('opacity', 0); label.css('opacity', 0);
function endEdit() {
function endEdit()
{
input.remove(); input.remove();
label.css('opacity', 1); label.css('opacity', 1);
} }
var rev = currentRevisionList[rnum]; var rev = currentRevisionList[rnum];
var oldLabel = rev.label; var oldLabel = rev.label;
input.blur(function() { input.blur(function()
{
var newLabel = input.val(); var newLabel = input.val();
if (newLabel && newLabel != oldLabel) { if (newLabel && newLabel != oldLabel)
{
relabelRevision(rnum, newLabel); relabelRevision(rnum, newLabel);
} }
endEdit(); endEdit();
}); });
input.val(rev.label).focus().select(); input.val(rev.label).focus().select();
padutils.bindEnterAndEscape(input, function onEnter() { padutils.bindEnterAndEscape(input, function onEnter()
{
input.blur(); input.blur();
}, function onEscape() { }, function onEscape()
{
input.val('').blur(); input.val('').blur();
}); });
} }
function relabelRevision(rnum, newLabel) {
function relabelRevision(rnum, newLabel)
{
var rev = currentRevisionList[rnum]; var rev = currentRevisionList[rnum];
$.ajax({ $.ajax(
{
type: 'post', type: 'post',
url: '/ep/pad/saverevisionlabel', url: '/ep/pad/saverevisionlabel',
data: {userId: pad.getUserId(), data: {
userId: pad.getUserId(),
padId: pad.getPadId(), padId: pad.getPadId(),
revId: rev.id, revId: rev.id,
newLabel: newLabel}, newLabel: newLabel
},
success: success, success: success,
error: error error: error
}); });
function success(text) {
function success(text)
{
var newRevisionList = JSON.parse(text); var newRevisionList = JSON.parse(text);
self.newRevisionList(newRevisionList); self.newRevisionList(newRevisionList);
pad.sendClientMessage({ pad.sendClientMessage(
{
type: 'revisionLabel', type: 'revisionLabel',
revisionList: reversedCopy(currentRevisionList), revisionList: reversedCopy(currentRevisionList),
savedBy: pad.getUserName(), savedBy: pad.getUserName(),
newLabel: newLabel newLabel: newLabel
}); });
} }
function error(e) {
function error(e)
{
alert("Oops! There was an error saving that revision label. Please try again later."); alert("Oops! There was an error saving that revision label. Please try again later.");
} }
} }
var currentRevisionList = []; var currentRevisionList = [];
function setRevisionList(newRevisionList, noAnimation) {
function setRevisionList(newRevisionList, noAnimation)
{
// deals with changed labels and new added revisions // deals with changed labels and new added revisions
for(var i=0; i<currentRevisionList.length; i++) { for (var i = 0; i < currentRevisionList.length; i++)
{
var a = currentRevisionList[i]; var a = currentRevisionList[i];
var b = newRevisionList[i]; var b = newRevisionList[i];
if (b.label != a.label) { if (b.label != a.label)
{
setBoxLabel(getNthBox(i), b.label); setBoxLabel(getNthBox(i), b.label);
} }
} }
for(var j=currentRevisionList.length; j<newRevisionList.length; j++) { for (var j = currentRevisionList.length; j < newRevisionList.length; j++)
{
var newBox = makeRevisionBox(newRevisionList[j], j); var newBox = makeRevisionBox(newRevisionList[j], j);
$("#savedrevs-scrollinner").append(newBox); $("#savedrevs-scrollinner").append(newBox);
newBox.css('left', j * REVISION_BOX_WIDTH); newBox.css('left', j * REVISION_BOX_WIDTH);
} }
var newOnes = (newRevisionList.length > currentRevisionList.length); var newOnes = (newRevisionList.length > currentRevisionList.length);
currentRevisionList = newRevisionList; currentRevisionList = newRevisionList;
if (newOnes) { if (newOnes)
{
setDesiredScroll(getMaxScroll()); setDesiredScroll(getMaxScroll());
if (noAnimation) { if (noAnimation)
{
setScroll(desiredScroll); setScroll(desiredScroll);
} }
if (! noAnimation) { if (!noAnimation)
var nameOfLast = currentRevisionList[currentRevisionList.length-1].label; {
var nameOfLast = currentRevisionList[currentRevisionList.length - 1].label;
displaySavedTip(nameOfLast); displaySavedTip(nameOfLast);
} }
} }
} }
function refreshRevisionList() {
for(var i=0;i<currentRevisionList.length; i++) { function refreshRevisionList()
{
for (var i = 0; i < currentRevisionList.length; i++)
{
var r = currentRevisionList[i]; var r = currentRevisionList[i];
var box = getNthBox(i); var box = getNthBox(i);
setBoxTimestamp(box, r.timestamp); setBoxTimestamp(box, r.timestamp);
} }
} }
var savedTipAnimator = padutils.makeShowHideAnimator(function(state) { var savedTipAnimator = padutils.makeShowHideAnimator(function(state)
if (state == -1) { {
if (state == -1)
{
$("#revision-notifier").css('opacity', 0).css('display', 'block'); $("#revision-notifier").css('opacity', 0).css('display', 'block');
} }
else if (state == 0) { else if (state == 0)
{
$("#revision-notifier").css('opacity', 1); $("#revision-notifier").css('opacity', 1);
} }
else if (state == 1) { else if (state == 1)
{
$("#revision-notifier").css('opacity', 0).css('display', 'none'); $("#revision-notifier").css('opacity', 0).css('display', 'none');
} }
else if (state < 0) { else if (state < 0)
{
$("#revision-notifier").css('opacity', 1); $("#revision-notifier").css('opacity', 1);
} }
else if (state > 0) { else if (state > 0)
{
$("#revision-notifier").css('opacity', 1 - state); $("#revision-notifier").css('opacity', 1 - state);
} }
}, false, 25, 300); }, false, 25, 300);
function displaySavedTip(text) { function displaySavedTip(text)
{
$("#revision-notifier .name").html(padutils.escapeHtml(text)); $("#revision-notifier .name").html(padutils.escapeHtml(text));
savedTipAnimator.show(); savedTipAnimator.show();
padutils.cancelActions("hide-revision-notifier"); padutils.cancelActions("hide-revision-notifier");
var hideLater = padutils.getCancellableAction("hide-revision-notifier", var hideLater = padutils.getCancellableAction("hide-revision-notifier", function()
function() { {
savedTipAnimator.hide(); savedTipAnimator.hide();
}); });
window.setTimeout(hideLater, 3000); window.setTimeout(hideLater, 3000);
@ -180,79 +219,106 @@ var padsavedrevs = (function() {
var REVISION_BOX_WIDTH = 120; var REVISION_BOX_WIDTH = 120;
var curScroll = 0; // distance between left of revisions and right of view var curScroll = 0; // distance between left of revisions and right of view
var desiredScroll = 0; var desiredScroll = 0;
function getScrollWidth() {
function getScrollWidth()
{
return REVISION_BOX_WIDTH * currentRevisionList.length; return REVISION_BOX_WIDTH * currentRevisionList.length;
} }
function getViewportWidth() {
function getViewportWidth()
{
return $("#savedrevs-scrollouter").width(); return $("#savedrevs-scrollouter").width();
} }
function getMinScroll() {
function getMinScroll()
{
return Math.min(getViewportWidth(), getScrollWidth()); return Math.min(getViewportWidth(), getScrollWidth());
} }
function getMaxScroll() {
function getMaxScroll()
{
return getScrollWidth(); return getScrollWidth();
} }
function setScroll(newScroll) {
function setScroll(newScroll)
{
curScroll = newScroll; curScroll = newScroll;
$("#savedrevs-scrollinner").css('right', newScroll); $("#savedrevs-scrollinner").css('right', newScroll);
updateScrollArrows(); updateScrollArrows();
} }
function setDesiredScroll(newDesiredScroll, dontUpdate) {
desiredScroll = Math.min(getMaxScroll(), Math.max(getMinScroll(), function setDesiredScroll(newDesiredScroll, dontUpdate)
newDesiredScroll)); {
if (! dontUpdate) { desiredScroll = Math.min(getMaxScroll(), Math.max(getMinScroll(), newDesiredScroll));
if (!dontUpdate)
{
updateScroll(); updateScroll();
} }
} }
function updateScroll() {
function updateScroll()
{
updateScrollArrows(); updateScrollArrows();
scrollAnimator.scheduleAnimation(); scrollAnimator.scheduleAnimation();
} }
function updateScrollArrows() {
$("#savedrevs-scrollleft").toggleClass("disabledscrollleft", function updateScrollArrows()
desiredScroll <= getMinScroll()); {
$("#savedrevs-scrollright").toggleClass("disabledscrollright", $("#savedrevs-scrollleft").toggleClass("disabledscrollleft", desiredScroll <= getMinScroll());
desiredScroll >= getMaxScroll()); $("#savedrevs-scrollright").toggleClass("disabledscrollright", desiredScroll >= getMaxScroll());
} }
var scrollAnimator = padutils.makeAnimationScheduler(function() { var scrollAnimator = padutils.makeAnimationScheduler(function()
{
setDesiredScroll(desiredScroll, true); // re-clamp setDesiredScroll(desiredScroll, true); // re-clamp
if (Math.abs(desiredScroll - curScroll) < 1) { if (Math.abs(desiredScroll - curScroll) < 1)
{
setScroll(desiredScroll); setScroll(desiredScroll);
return false; return false;
} }
else { else
setScroll(curScroll + (desiredScroll - curScroll)*0.5); {
setScroll(curScroll + (desiredScroll - curScroll) * 0.5);
return true; return true;
} }
}, 50, 2); }, 50, 2);
var isSaving = false; var isSaving = false;
function setIsSaving(v) {
function setIsSaving(v)
{
isSaving = v; isSaving = v;
rerenderButton(); rerenderButton();
} }
function haveReachedRevLimit() { function haveReachedRevLimit()
{
var mv = pad.getPrivilege('maxRevisions'); var mv = pad.getPrivilege('maxRevisions');
return (!(mv < 0 || mv > currentRevisionList.length)); return (!(mv < 0 || mv > currentRevisionList.length));
} }
function rerenderButton() {
if (isSaving || (! pad.isFullyConnected()) || function rerenderButton()
haveReachedRevLimit()) { {
if (isSaving || (!pad.isFullyConnected()) || haveReachedRevLimit())
{
$("#savedrevs-savenow").css('opacity', 0.75); $("#savedrevs-savenow").css('opacity', 0.75);
} }
else { else
{
$("#savedrevs-savenow").css('opacity', 1); $("#savedrevs-savenow").css('opacity', 1);
} }
} }
var scrollRepeatTimer = null; var scrollRepeatTimer = null;
var scrollStartTime = 0; var scrollStartTime = 0;
function setScrollRepeatTimer(dir) {
function setScrollRepeatTimer(dir)
{
clearScrollRepeatTimer(); clearScrollRepeatTimer();
scrollStartTime = +new Date; scrollStartTime = +new Date;
scrollRepeatTimer = window.setTimeout(function f() { scrollRepeatTimer = window.setTimeout(function f()
if (! scrollRepeatTimer) { {
if (!scrollRepeatTimer)
{
return; return;
} }
self.scroll(dir); self.scroll(dir);
@ -262,8 +328,11 @@ var padsavedrevs = (function() {
}, 300); }, 300);
$(document).bind('mouseup', clearScrollRepeatTimer); $(document).bind('mouseup', clearScrollRepeatTimer);
} }
function clearScrollRepeatTimer() {
if (scrollRepeatTimer) { function clearScrollRepeatTimer()
{
if (scrollRepeatTimer)
{
window.clearTimeout(scrollRepeatTimer); window.clearTimeout(scrollRepeatTimer);
scrollRepeatTimer = null; scrollRepeatTimer = null;
} }
@ -271,78 +340,103 @@ var padsavedrevs = (function() {
} }
var self = { var self = {
init: function(initialRevisions) { init: function(initialRevisions)
{
self.newRevisionList(initialRevisions, true); self.newRevisionList(initialRevisions, true);
$("#savedrevs-savenow").click(function() { self.saveNow(); }); $("#savedrevs-savenow").click(function()
$("#savedrevs-scrollleft").mousedown(function() { {
self.saveNow();
});
$("#savedrevs-scrollleft").mousedown(function()
{
self.scroll('left'); self.scroll('left');
setScrollRepeatTimer('left'); setScrollRepeatTimer('left');
}); });
$("#savedrevs-scrollright").mousedown(function() { $("#savedrevs-scrollright").mousedown(function()
{
self.scroll('right'); self.scroll('right');
setScrollRepeatTimer('right'); setScrollRepeatTimer('right');
}); });
$("#savedrevs-close").click(function() {paddocbar.setShownPanel(null);}); $("#savedrevs-close").click(function()
{
paddocbar.setShownPanel(null);
});
// update "saved n minutes ago" times // update "saved n minutes ago" times
window.setInterval(function() { window.setInterval(function()
{
refreshRevisionList(); refreshRevisionList();
}, 60*1000); }, 60 * 1000);
}, },
restoreRevision: function(rnum) { restoreRevision: function(rnum)
{
var rev = currentRevisionList[rnum]; var rev = currentRevisionList[rnum];
var warning = ("Restoring this revision will overwrite the current" var warning = ("Restoring this revision will overwrite the current" + " text of the pad. " + "Are you sure you want to continue?");
+ " text of the pad. "+
"Are you sure you want to continue?");
var hidePanel = paddocbar.hideLaterIfNoOtherInteraction(); var hidePanel = paddocbar.hideLaterIfNoOtherInteraction();
var box = getNthBox(rnum); var box = getNthBox(rnum);
if (confirm(warning)) { if (confirm(warning))
{
box.find(".srtwirly").show(); box.find(".srtwirly").show();
$.ajax({ $.ajax(
{
type: 'get', type: 'get',
url: '/ep/pad/getrevisionatext', url: '/ep/pad/getrevisionatext',
data: {padId: pad.getPadId(), revId: rev.id}, data: {
padId: pad.getPadId(),
revId: rev.id
},
success: success, success: success,
error: error error: error
}); });
} }
function success(resultJson) {
function success(resultJson)
{
untwirl(); untwirl();
var result = JSON.parse(resultJson); var result = JSON.parse(resultJson);
padeditor.restoreRevisionText(result); padeditor.restoreRevisionText(result);
window.setTimeout(function() { window.setTimeout(function()
{
hidePanel(); hidePanel();
}, 0); }, 0);
} }
function error(e) {
function error(e)
{
untwirl(); untwirl();
alert("Oops! There was an error retreiving the text (revNum= "+ alert("Oops! There was an error retreiving the text (revNum= " + rev.revNum + "; padId=" + pad.getPadId());
rev.revNum+"; padId="+pad.getPadId());
} }
function untwirl() {
function untwirl()
{
box.find(".srtwirly").hide(); box.find(".srtwirly").hide();
} }
}, },
showReachedLimit: function() { showReachedLimit: function()
alert("Sorry, you do not have privileges to save more than "+ {
pad.getPrivilege('maxRevisions')+" revisions."); alert("Sorry, you do not have privileges to save more than " + pad.getPrivilege('maxRevisions') + " revisions.");
}, },
newRevisionList: function(lst, noAnimation) { newRevisionList: function(lst, noAnimation)
{
// server gives us list with newest first; // server gives us list with newest first;
// we want chronological order // we want chronological order
var L = reversedCopy(lst); var L = reversedCopy(lst);
setRevisionList(L, noAnimation); setRevisionList(L, noAnimation);
rerenderButton(); rerenderButton();
}, },
saveNow: function() { saveNow: function()
if (isSaving) { {
if (isSaving)
{
return; return;
} }
if (! pad.isFullyConnected()) { if (!pad.isFullyConnected())
{
return; return;
} }
if (haveReachedRevLimit()) { if (haveReachedRevLimit())
{
self.showReachedLimit(); self.showReachedLimit();
return; return;
} }
@ -350,8 +444,10 @@ var padsavedrevs = (function() {
var savedBy = pad.getUserName() || "unnamed"; var savedBy = pad.getUserName() || "unnamed";
pad.callWhenNotCommitting(submitSave); pad.callWhenNotCommitting(submitSave);
function submitSave() { function submitSave()
$.ajax({ {
$.ajax(
{
type: 'post', type: 'post',
url: '/ep/pad/saverevision', url: '/ep/pad/saverevision',
data: { data: {
@ -364,42 +460,52 @@ var padsavedrevs = (function() {
error: error error: error
}); });
} }
function success(text) {
function success(text)
{
setIsSaving(false); setIsSaving(false);
var newRevisionList = JSON.parse(text); var newRevisionList = JSON.parse(text);
self.newRevisionList(newRevisionList); self.newRevisionList(newRevisionList);
pad.sendClientMessage({ pad.sendClientMessage(
{
type: 'newRevisionList', type: 'newRevisionList',
revisionList: newRevisionList, revisionList: newRevisionList,
savedBy: savedBy savedBy: savedBy
}); });
} }
function error(e) {
function error(e)
{
setIsSaving(false); setIsSaving(false);
alert("Oops! The server failed to save the revision. Please try again later."); alert("Oops! The server failed to save the revision. Please try again later.");
} }
}, },
handleResizePage: function() { handleResizePage: function()
{
updateScrollArrows(); updateScrollArrows();
}, },
handleIsFullyConnected: function(isConnected) { handleIsFullyConnected: function(isConnected)
{
rerenderButton(); rerenderButton();
}, },
scroll: function(dir) { scroll: function(dir)
{
var minScroll = getMinScroll(); var minScroll = getMinScroll();
var maxScroll = getMaxScroll(); var maxScroll = getMaxScroll();
if (dir == 'left') { if (dir == 'left')
if (desiredScroll > minScroll) { {
var n = Math.floor((desiredScroll - 1 - minScroll) / if (desiredScroll > minScroll)
REVISION_BOX_WIDTH); {
setDesiredScroll(Math.max(0, n)*REVISION_BOX_WIDTH + minScroll); var n = Math.floor((desiredScroll - 1 - minScroll) / REVISION_BOX_WIDTH);
setDesiredScroll(Math.max(0, n) * REVISION_BOX_WIDTH + minScroll);
} }
} }
else if (dir == 'right') { else if (dir == 'right')
if (desiredScroll < maxScroll) { {
var n = Math.floor((maxScroll - desiredScroll - 1) / if (desiredScroll < maxScroll)
REVISION_BOX_WIDTH); {
setDesiredScroll(maxScroll - Math.max(0, n)*REVISION_BOX_WIDTH); var n = Math.floor((maxScroll - desiredScroll - 1) / REVISION_BOX_WIDTH);
setDesiredScroll(maxScroll - Math.max(0, n) * REVISION_BOX_WIDTH);
} }
} }
} }

View file

@ -21,117 +21,150 @@ var colorPickerSetup = false;
var previousColorId = 0; var previousColorId = 0;
var paduserlist = (function() { var paduserlist = (function()
{
var rowManager = (function() { var rowManager = (function()
{
// The row manager handles rendering rows of the user list and animating // The row manager handles rendering rows of the user list and animating
// their insertion, removal, and reordering. It manipulates TD height // their insertion, removal, and reordering. It manipulates TD height
// and TD opacity. // and TD opacity.
function nextRowId() { function nextRowId()
return "usertr"+(nextRowId.counter++); {
return "usertr" + (nextRowId.counter++);
} }
nextRowId.counter = 1; nextRowId.counter = 1;
// objects are shared; fields are "domId","data","animationStep" // objects are shared; fields are "domId","data","animationStep"
var rowsFadingOut = []; // unordered set var rowsFadingOut = []; // unordered set
var rowsFadingIn = []; // unordered set var rowsFadingIn = []; // unordered set
var rowsPresent = []; // in order var rowsPresent = []; // in order
var ANIMATION_START = -12; // just starting to fade in var ANIMATION_START = -12; // just starting to fade in
var ANIMATION_END = 12; // just finishing fading out var ANIMATION_END = 12; // just finishing fading out
function getAnimationHeight(step, power) {
var a = Math.abs(step/12);
if (power == 2) a = a*a; function getAnimationHeight(step, power)
else if (power == 3) a = a*a*a; {
else if (power == 4) a = a*a*a*a; var a = Math.abs(step / 12);
else if (power >= 5) a = a*a*a*a*a; if (power == 2) a = a * a;
return Math.round(26*(1-a)); else if (power == 3) a = a * a * a;
else if (power == 4) a = a * a * a * a;
else if (power >= 5) a = a * a * a * a * a;
return Math.round(26 * (1 - a));
} }
var OPACITY_STEPS = 6; var OPACITY_STEPS = 6;
var ANIMATION_STEP_TIME = 20; var ANIMATION_STEP_TIME = 20;
var LOWER_FRAMERATE_FACTOR = 2; var LOWER_FRAMERATE_FACTOR = 2;
var scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, var scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR).scheduleAnimation;
LOWER_FRAMERATE_FACTOR).scheduleAnimation;
var NUMCOLS = 4; var NUMCOLS = 4;
// we do lots of manipulation of table rows and stuff that JQuery makes ok, despite // we do lots of manipulation of table rows and stuff that JQuery makes ok, despite
// IE's poor handling when manipulating the DOM directly. // IE's poor handling when manipulating the DOM directly.
function getEmptyRowHtml(height) { function getEmptyRowHtml(height)
return '<td colspan="'+NUMCOLS+'" style="border:0;height:'+height+'px"><!-- --></td>'; {
return '<td colspan="' + NUMCOLS + '" style="border:0;height:' + height + 'px"><!-- --></td>';
} }
function isNameEditable(data) {
return (! data.name) && (data.status != 'Disconnected'); function isNameEditable(data)
{
return (!data.name) && (data.status != 'Disconnected');
} }
function replaceUserRowContents(tr, height, data) {
function replaceUserRowContents(tr, height, data)
{
var tds = getUserRowHtml(height, data).match(/<td.*?<\/td>/gi); var tds = getUserRowHtml(height, data).match(/<td.*?<\/td>/gi);
if (isNameEditable(data) && tr.find("td.usertdname input:enabled").length > 0) { if (isNameEditable(data) && tr.find("td.usertdname input:enabled").length > 0)
{
// preserve input field node // preserve input field node
for(var i=0; i<tds.length; i++) { for (var i = 0; i < tds.length; i++)
{
var oldTd = $(tr.find("td").get(i)); var oldTd = $(tr.find("td").get(i));
if (! oldTd.hasClass('usertdname')) { if (!oldTd.hasClass('usertdname'))
{
oldTd.replaceWith(tds[i]); oldTd.replaceWith(tds[i]);
} }
} }
} }
else { else
{
tr.html(tds.join('')); tr.html(tds.join(''));
} }
return tr; return tr;
} }
function getUserRowHtml(height, data) {
function getUserRowHtml(height, data)
{
var nameHtml; var nameHtml;
var isGuest = (data.id.charAt(0) != 'p'); var isGuest = (data.id.charAt(0) != 'p');
if (data.name) { if (data.name)
{
nameHtml = padutils.escapeHtml(data.name); nameHtml = padutils.escapeHtml(data.name);
if (isGuest && pad.getIsProPad()) { if (isGuest && pad.getIsProPad())
{
nameHtml += ' (Guest)'; nameHtml += ' (Guest)';
} }
} }
else { else
nameHtml = '<input type="text" class="editempty newinput" value="unnamed" '+ {
(isNameEditable(data) ? '' : 'disabled="disabled" ')+ nameHtml = '<input type="text" class="editempty newinput" value="unnamed" ' + (isNameEditable(data) ? '' : 'disabled="disabled" ') + '/>';
'/>';
} }
return ['<td style="height:',height,'px" class="usertdswatch"><div class="swatch" style="background:'+data.color+'">&nbsp;</div></td>', return ['<td style="height:', height, 'px" class="usertdswatch"><div class="swatch" style="background:' + data.color + '">&nbsp;</div></td>', '<td style="height:', height, 'px" class="usertdname">', nameHtml, '</td>', '<td style="height:', height, 'px" class="usertdstatus">', padutils.escapeHtml(data.status), '</td>', '<td style="height:', height, 'px" class="activity">', padutils.escapeHtml(data.activity), '</td>'].join('');
'<td style="height:',height,'px" class="usertdname">',nameHtml,'</td>',
'<td style="height:',height,'px" class="usertdstatus">',padutils.escapeHtml(data.status),'</td>',
'<td style="height:',height,'px" class="activity">',padutils.escapeHtml(data.activity),'</td>'].join('');
} }
function getRowHtml(id, innerHtml) {
return '<tr id="'+id+'">'+innerHtml+'</tr>'; function getRowHtml(id, innerHtml)
{
return '<tr id="' + id + '">' + innerHtml + '</tr>';
} }
function rowNode(row) {
return $("#"+row.domId); function rowNode(row)
{
return $("#" + row.domId);
} }
function handleRowData(row) {
if (row.data && row.data.status == 'Disconnected') { function handleRowData(row)
{
if (row.data && row.data.status == 'Disconnected')
{
row.opacity = 0.5; row.opacity = 0.5;
} }
else { else
{
delete row.opacity; delete row.opacity;
} }
} }
function handleRowNode(tr, data) {
if (data.titleText) { function handleRowNode(tr, data)
{
if (data.titleText)
{
var titleText = data.titleText; var titleText = data.titleText;
window.setTimeout(function() { tr.attr('title', titleText )}, 0); window.setTimeout(function()
{
tr.attr('title', titleText)
}, 0);
} }
else { else
{
tr.removeAttr('title'); tr.removeAttr('title');
} }
} }
function handleOtherUserInputs() {
function handleOtherUserInputs()
{
// handle 'INPUT' elements for naming other unnamed users // handle 'INPUT' elements for naming other unnamed users
$("#otheruserstable input.newinput").each(function() { $("#otheruserstable input.newinput").each(function()
{
var input = $(this); var input = $(this);
var tr = input.closest("tr"); var tr = input.closest("tr");
if (tr.length > 0) { if (tr.length > 0)
{
var index = tr.parent().children().index(tr); var index = tr.parent().children().index(tr);
if (index >= 0) { if (index >= 0)
{
var userId = rowsPresent[index].data.id; var userId = rowsPresent[index].data.id;
rowManagerMakeNameEditor($(this), userId); rowManagerMakeNameEditor($(this), userId);
} }
@ -140,33 +173,45 @@ var paduserlist = (function() {
} }
// animationPower is 0 to skip animation, 1 for linear, 2 for quadratic, etc. // animationPower is 0 to skip animation, 1 for linear, 2 for quadratic, etc.
function insertRow(position, data, animationPower) {
function insertRow(position, data, animationPower)
{
position = Math.max(0, Math.min(rowsPresent.length, position)); position = Math.max(0, Math.min(rowsPresent.length, position));
animationPower = (animationPower === undefined ? 4 : animationPower); animationPower = (animationPower === undefined ? 4 : animationPower);
var domId = nextRowId(); var domId = nextRowId();
var row = {data: data, animationStep: ANIMATION_START, domId: domId, var row = {
animationPower: animationPower}; data: data,
animationStep: ANIMATION_START,
domId: domId,
animationPower: animationPower
};
handleRowData(row); handleRowData(row);
rowsPresent.splice(position, 0, row); rowsPresent.splice(position, 0, row);
var tr; var tr;
if (animationPower == 0) { if (animationPower == 0)
{
tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data))); tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data)));
row.animationStep = 0; row.animationStep = 0;
} }
else { else
{
rowsFadingIn.push(row); rowsFadingIn.push(row);
tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START)))); tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START))));
} }
handleRowNode(tr, data); handleRowNode(tr, data);
if (position == 0) { if (position == 0)
{
$("table#otheruserstable").prepend(tr); $("table#otheruserstable").prepend(tr);
} }
else { else
rowNode(rowsPresent[position-1]).after(tr); {
rowNode(rowsPresent[position - 1]).after(tr);
} }
if (animationPower != 0) { if (animationPower != 0)
{
scheduleAnimation(); scheduleAnimation();
} }
@ -175,32 +220,38 @@ var paduserlist = (function() {
return row; return row;
} }
function updateRow(position, data) { function updateRow(position, data)
{
var row = rowsPresent[position]; var row = rowsPresent[position];
if (row) { if (row)
{
row.data = data; row.data = data;
handleRowData(row); handleRowData(row);
if (row.animationStep == 0) { if (row.animationStep == 0)
{
// not currently animating // not currently animating
var tr = rowNode(row); var tr = rowNode(row);
replaceUserRowContents(tr, getAnimationHeight(0), row.data).find( replaceUserRowContents(tr, getAnimationHeight(0), row.data).find("td").css('opacity', (row.opacity === undefined ? 1 : row.opacity));
"td").css('opacity', (row.opacity === undefined ? 1 : row.opacity));
handleRowNode(tr, data); handleRowNode(tr, data);
handleOtherUserInputs(); handleOtherUserInputs();
} }
} }
} }
function removeRow(position, animationPower) { function removeRow(position, animationPower)
{
animationPower = (animationPower === undefined ? 4 : animationPower); animationPower = (animationPower === undefined ? 4 : animationPower);
var row = rowsPresent[position]; var row = rowsPresent[position];
if (row) { if (row)
{
rowsPresent.splice(position, 1); // remove rowsPresent.splice(position, 1); // remove
if (animationPower == 0) { if (animationPower == 0)
{
rowNode(row).remove(); rowNode(row).remove();
} }
else { else
row.animationStep = - row.animationStep; // use symmetry {
row.animationStep = -row.animationStep; // use symmetry
row.animationPower = animationPower; row.animationPower = animationPower;
rowsFadingOut.push(row); rowsFadingOut.push(row);
scheduleAnimation(); scheduleAnimation();
@ -209,59 +260,72 @@ var paduserlist = (function() {
} }
// newPosition is position after the row has been removed // newPosition is position after the row has been removed
function moveRow(oldPosition, newPosition, animationPower) {
function moveRow(oldPosition, newPosition, animationPower)
{
animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best
var row = rowsPresent[oldPosition]; var row = rowsPresent[oldPosition];
if (row && oldPosition != newPosition) { if (row && oldPosition != newPosition)
{
var rowData = row.data; var rowData = row.data;
removeRow(oldPosition, animationPower); removeRow(oldPosition, animationPower);
insertRow(newPosition, rowData, animationPower); insertRow(newPosition, rowData, animationPower);
} }
} }
function animateStep() { function animateStep()
{
// animation must be symmetrical // animation must be symmetrical
for(var i=rowsFadingIn.length-1;i>=0;i--) { // backwards to allow removal for (var i = rowsFadingIn.length - 1; i >= 0; i--)
{ // backwards to allow removal
var row = rowsFadingIn[i]; var row = rowsFadingIn[i];
var step = ++row.animationStep; var step = ++row.animationStep;
var animHeight = getAnimationHeight(step, row.animationPower); var animHeight = getAnimationHeight(step, row.animationPower);
var node = rowNode(row); var node = rowNode(row);
var baseOpacity = (row.opacity === undefined ? 1 : row.opacity); var baseOpacity = (row.opacity === undefined ? 1 : row.opacity);
if (step <= -OPACITY_STEPS) { if (step <= -OPACITY_STEPS)
{
node.find("td").height(animHeight); node.find("td").height(animHeight);
} }
else if (step == -OPACITY_STEPS+1) { else if (step == -OPACITY_STEPS + 1)
node.html(getUserRowHtml(animHeight, row.data)).find("td").css( {
'opacity', baseOpacity*1/OPACITY_STEPS); node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1 / OPACITY_STEPS);
handleRowNode(node, row.data); handleRowNode(node, row.data);
} }
else if (step < 0) { else if (step < 0)
node.find("td").css('opacity', baseOpacity*(OPACITY_STEPS-(-step))/OPACITY_STEPS).height(animHeight); {
node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - (-step)) / OPACITY_STEPS).height(animHeight);
} }
else if (step == 0) { else if (step == 0)
{
// set HTML in case modified during animation // set HTML in case modified during animation
node.html(getUserRowHtml(animHeight, row.data)).find("td").css( node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1).height(animHeight);
'opacity', baseOpacity*1).height(animHeight);
handleRowNode(node, row.data); handleRowNode(node, row.data);
rowsFadingIn.splice(i, 1); // remove from set rowsFadingIn.splice(i, 1); // remove from set
} }
} }
for(var i=rowsFadingOut.length-1;i>=0;i--) { // backwards to allow removal for (var i = rowsFadingOut.length - 1; i >= 0; i--)
{ // backwards to allow removal
var row = rowsFadingOut[i]; var row = rowsFadingOut[i];
var step = ++row.animationStep; var step = ++row.animationStep;
var node = rowNode(row); var node = rowNode(row);
var animHeight = getAnimationHeight(step, row.animationPower); var animHeight = getAnimationHeight(step, row.animationPower);
var baseOpacity = (row.opacity === undefined ? 1 : row.opacity); var baseOpacity = (row.opacity === undefined ? 1 : row.opacity);
if (step < OPACITY_STEPS) { if (step < OPACITY_STEPS)
node.find("td").css('opacity', baseOpacity*(OPACITY_STEPS - step)/OPACITY_STEPS).height(animHeight); {
node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - step) / OPACITY_STEPS).height(animHeight);
} }
else if (step == OPACITY_STEPS) { else if (step == OPACITY_STEPS)
{
node.html(getEmptyRowHtml(animHeight)); node.html(getEmptyRowHtml(animHeight));
} }
else if (step <= ANIMATION_END) { else if (step <= ANIMATION_END)
{
node.find("td").height(animHeight); node.find("td").height(animHeight);
} }
else { else
{
rowsFadingOut.splice(i, 1); // remove from set rowsFadingOut.splice(i, 1); // remove from set
node.remove(); node.remove();
} }
@ -280,35 +344,44 @@ var paduserlist = (function() {
}; };
return self; return self;
}()); ////////// rowManager }()); ////////// rowManager
var otherUsersInfo = []; var otherUsersInfo = [];
var otherUsersData = []; var otherUsersData = [];
function rowManagerMakeNameEditor(jnode, userId) { function rowManagerMakeNameEditor(jnode, userId)
setUpEditable(jnode, function() { {
setUpEditable(jnode, function()
{
var existingIndex = findExistingIndex(userId); var existingIndex = findExistingIndex(userId);
if (existingIndex >= 0) { if (existingIndex >= 0)
{
return otherUsersInfo[existingIndex].name || ''; return otherUsersInfo[existingIndex].name || '';
} }
else { else
{
return ''; return '';
} }
}, function(newName) { }, function(newName)
if (! newName) { {
if (!newName)
{
jnode.addClass("editempty"); jnode.addClass("editempty");
jnode.val("unnamed"); jnode.val("unnamed");
} }
else { else
{
jnode.attr('disabled', 'disabled'); jnode.attr('disabled', 'disabled');
pad.suggestUserName(userId, newName); pad.suggestUserName(userId, newName);
} }
}); });
} }
function findExistingIndex(userId) { function findExistingIndex(userId)
{
var existingIndex = -1; var existingIndex = -1;
for(var i=0;i<otherUsersInfo.length;i++) { for (var i = 0; i < otherUsersInfo.length; i++)
if (otherUsersInfo[i].userId == userId) { {
if (otherUsersInfo[i].userId == userId)
{
existingIndex = i; existingIndex = i;
break; break;
} }
@ -316,32 +389,41 @@ var paduserlist = (function() {
return existingIndex; return existingIndex;
} }
function setUpEditable(jqueryNode, valueGetter, valueSetter) { function setUpEditable(jqueryNode, valueGetter, valueSetter)
jqueryNode.bind('focus', function(evt) { {
jqueryNode.bind('focus', function(evt)
{
var oldValue = valueGetter(); var oldValue = valueGetter();
if (jqueryNode.val() !== oldValue) { if (jqueryNode.val() !== oldValue)
{
jqueryNode.val(oldValue); jqueryNode.val(oldValue);
} }
jqueryNode.addClass("editactive").removeClass("editempty"); jqueryNode.addClass("editactive").removeClass("editempty");
}); });
jqueryNode.bind('blur', function(evt) { jqueryNode.bind('blur', function(evt)
{
var newValue = jqueryNode.removeClass("editactive").val(); var newValue = jqueryNode.removeClass("editactive").val();
valueSetter(newValue); valueSetter(newValue);
}); });
padutils.bindEnterAndEscape(jqueryNode, function onEnter() { padutils.bindEnterAndEscape(jqueryNode, function onEnter()
{
jqueryNode.blur(); jqueryNode.blur();
}, function onEscape() { }, function onEscape()
{
jqueryNode.val(valueGetter()).blur(); jqueryNode.val(valueGetter()).blur();
}); });
jqueryNode.removeAttr('disabled').addClass('editable'); jqueryNode.removeAttr('disabled').addClass('editable');
} }
function updateInviteNotice() { function updateInviteNotice()
if (otherUsersInfo.length == 0) { {
if (otherUsersInfo.length == 0)
{
$("#otheruserstable").hide(); $("#otheruserstable").hide();
$("#nootherusers").show(); $("#nootherusers").show();
} }
else { else
{
$("#nootherusers").hide(); $("#nootherusers").hide();
$("#otheruserstable").show(); $("#otheruserstable").show();
} }
@ -350,17 +432,22 @@ var paduserlist = (function() {
var knocksToIgnore = {}; var knocksToIgnore = {};
var guestPromptFlashState = 0; var guestPromptFlashState = 0;
var guestPromptFlash = padutils.makeAnimationScheduler( var guestPromptFlash = padutils.makeAnimationScheduler(
function () {
function()
{
var prompts = $("#guestprompts .guestprompt"); var prompts = $("#guestprompts .guestprompt");
if (prompts.length == 0) { if (prompts.length == 0)
{
return false; // no more to do return false; // no more to do
} }
guestPromptFlashState = 1 - guestPromptFlashState; guestPromptFlashState = 1 - guestPromptFlashState;
if (guestPromptFlashState) { if (guestPromptFlashState)
{
prompts.css('background', '#ffa'); prompts.css('background', '#ffa');
} }
else { else
{
prompts.css('background', '#ffe'); prompts.css('background', '#ffe');
} }
@ -368,23 +455,26 @@ var paduserlist = (function() {
}, 1000); }, 1000);
var self = { var self = {
init: function(myInitialUserInfo) { init: function(myInitialUserInfo)
{
self.setMyUserInfo(myInitialUserInfo); self.setMyUserInfo(myInitialUserInfo);
$("#otheruserstable tr").remove(); $("#otheruserstable tr").remove();
if (pad.getUserIsGuest()) { if (pad.getUserIsGuest())
{
$("#myusernameedit").addClass('myusernameedithoverable'); $("#myusernameedit").addClass('myusernameedithoverable');
setUpEditable($("#myusernameedit"), setUpEditable($("#myusernameedit"), function()
function() { {
return myUserInfo.name || ''; return myUserInfo.name || '';
}, }, function(newValue)
function(newValue) { {
myUserInfo.name = newValue; myUserInfo.name = newValue;
pad.notifyChangeName(newValue); pad.notifyChangeName(newValue);
// wrap with setTimeout to do later because we get // wrap with setTimeout to do later because we get
// a double "blur" fire in IE... // a double "blur" fire in IE...
window.setTimeout(function() { window.setTimeout(function()
{
self.renderMyUserInfo(); self.renderMyUserInfo();
}, 0); }, 0);
}); });
@ -392,26 +482,32 @@ var paduserlist = (function() {
// color picker // color picker
$("#myswatchbox").click(showColorPicker); $("#myswatchbox").click(showColorPicker);
$("#mycolorpicker .pickerswatchouter").click(function() { $("#mycolorpicker .pickerswatchouter").click(function()
{
$("#mycolorpicker .pickerswatchouter").removeClass('picked'); $("#mycolorpicker .pickerswatchouter").removeClass('picked');
$(this).addClass('picked'); $(this).addClass('picked');
}); });
$("#mycolorpickersave").click(function() { $("#mycolorpickersave").click(function()
{
closeColorPicker(true); closeColorPicker(true);
}); });
$("#mycolorpickercancel").click(function() { $("#mycolorpickercancel").click(function()
{
closeColorPicker(false); closeColorPicker(false);
}); });
// //
}, },
setMyUserInfo: function(info) { setMyUserInfo: function(info)
myUserInfo = $.extend({}, info); {
myUserInfo = $.extend(
{}, info);
self.renderMyUserInfo(); self.renderMyUserInfo();
}, },
userJoinOrUpdate: function(info) { userJoinOrUpdate: function(info)
if ((! info.userId) || (info.userId == myUserInfo.userId)) { {
if ((!info.userId) || (info.userId == myUserInfo.userId))
{
// not sure how this would happen // not sure how this would happen
return; return;
} }
@ -423,36 +519,41 @@ var paduserlist = (function() {
userData.activity = ''; userData.activity = '';
userData.id = info.userId; userData.id = info.userId;
// Firefox ignores \n in title text; Safari does a linebreak // Firefox ignores \n in title text; Safari does a linebreak
userData.titleText = [info.userAgent||'', info.ip||''].join(' \n'); userData.titleText = [info.userAgent || '', info.ip || ''].join(' \n');
var existingIndex = findExistingIndex(info.userId); var existingIndex = findExistingIndex(info.userId);
var numUsersBesides = otherUsersInfo.length; var numUsersBesides = otherUsersInfo.length;
if (existingIndex >= 0) { if (existingIndex >= 0)
{
numUsersBesides--; numUsersBesides--;
} }
var newIndex = padutils.binarySearch(numUsersBesides, function(n) { var newIndex = padutils.binarySearch(numUsersBesides, function(n)
if (existingIndex >= 0 && n >= existingIndex) { {
if (existingIndex >= 0 && n >= existingIndex)
{
// pretend existingIndex isn't there // pretend existingIndex isn't there
n++; n++;
} }
var infoN = otherUsersInfo[n]; var infoN = otherUsersInfo[n];
var nameN = (infoN.name||'').toLowerCase(); var nameN = (infoN.name || '').toLowerCase();
var nameThis = (info.name||'').toLowerCase(); var nameThis = (info.name || '').toLowerCase();
var idN = infoN.userId; var idN = infoN.userId;
var idThis = info.userId; var idThis = info.userId;
return (nameN > nameThis) || (nameN == nameThis && return (nameN > nameThis) || (nameN == nameThis && idN > idThis);
idN > idThis);
}); });
if (existingIndex >= 0) { if (existingIndex >= 0)
{
// update // update
if (existingIndex == newIndex) { if (existingIndex == newIndex)
{
otherUsersInfo[existingIndex] = info; otherUsersInfo[existingIndex] = info;
otherUsersData[existingIndex] = userData; otherUsersData[existingIndex] = userData;
rowManager.updateRow(existingIndex, userData); rowManager.updateRow(existingIndex, userData);
} }
else { else
{
otherUsersInfo.splice(existingIndex, 1); otherUsersInfo.splice(existingIndex, 1);
otherUsersData.splice(existingIndex, 1); otherUsersData.splice(existingIndex, 1);
otherUsersInfo.splice(newIndex, 0, info); otherUsersInfo.splice(newIndex, 0, info);
@ -461,7 +562,8 @@ var paduserlist = (function() {
rowManager.moveRow(existingIndex, newIndex); rowManager.moveRow(existingIndex, newIndex);
} }
} }
else { else
{
otherUsersInfo.splice(newIndex, 0, info); otherUsersInfo.splice(newIndex, 0, info);
otherUsersData.splice(newIndex, 0, userData); otherUsersData.splice(newIndex, 0, userData);
rowManager.insertRow(newIndex, userData); rowManager.insertRow(newIndex, userData);
@ -471,10 +573,12 @@ var paduserlist = (function() {
self.updateNumberOfOnlineUsers(); self.updateNumberOfOnlineUsers();
}, },
updateNumberOfOnlineUsers: function(){ updateNumberOfOnlineUsers: function()
{
var online = 1; // you are always online! var online = 1; // you are always online!
for(var i=0;i<otherUsersData.length;i++) { for (var i = 0; i < otherUsersData.length; i++)
if(otherUsersData[i].status == "") {
if (otherUsersData[i].status == "")
{ {
online++; online++;
} }
@ -483,25 +587,30 @@ var paduserlist = (function() {
return online; return online;
}, },
userLeave: function(info) { userLeave: function(info)
{
var existingIndex = findExistingIndex(info.userId); var existingIndex = findExistingIndex(info.userId);
if (existingIndex >= 0) { if (existingIndex >= 0)
{
var userData = otherUsersData[existingIndex]; var userData = otherUsersData[existingIndex];
userData.status = 'Disconnected'; userData.status = 'Disconnected';
rowManager.updateRow(existingIndex, userData); rowManager.updateRow(existingIndex, userData);
if (userData.leaveTimer) { if (userData.leaveTimer)
{
window.clearTimeout(userData.leaveTimer); window.clearTimeout(userData.leaveTimer);
} }
// set up a timer that will only fire if no leaves, // set up a timer that will only fire if no leaves,
// joins, or updates happen for this user in the // joins, or updates happen for this user in the
// next N seconds, to remove the user from the list. // next N seconds, to remove the user from the list.
var thisUserId = info.userId; var thisUserId = info.userId;
var thisLeaveTimer = window.setTimeout(function() { var thisLeaveTimer = window.setTimeout(function()
{
var newExistingIndex = findExistingIndex(thisUserId); var newExistingIndex = findExistingIndex(thisUserId);
if (newExistingIndex >= 0) { if (newExistingIndex >= 0)
{
var newUserData = otherUsersData[newExistingIndex]; var newUserData = otherUsersData[newExistingIndex];
if (newUserData.status == 'Disconnected' && if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer)
newUserData.leaveTimer == thisLeaveTimer) { {
otherUsersInfo.splice(newExistingIndex, 1); otherUsersInfo.splice(newExistingIndex, 1);
otherUsersData.splice(newExistingIndex, 1); otherUsersData.splice(newExistingIndex, 1);
rowManager.removeRow(newExistingIndex); rowManager.removeRow(newExistingIndex);
@ -515,46 +624,54 @@ var paduserlist = (function() {
self.updateNumberOfOnlineUsers(); self.updateNumberOfOnlineUsers();
}, },
showGuestPrompt: function(userId, displayName) { showGuestPrompt: function(userId, displayName)
if (knocksToIgnore[userId]) { {
if (knocksToIgnore[userId])
{
return; return;
} }
var encodedUserId = padutils.encodeUserId(userId); var encodedUserId = padutils.encodeUserId(userId);
var actionName = 'hide-guest-prompt-'+encodedUserId; var actionName = 'hide-guest-prompt-' + encodedUserId;
padutils.cancelActions(actionName); padutils.cancelActions(actionName);
var box = $("#guestprompt-"+encodedUserId); var box = $("#guestprompt-" + encodedUserId);
if (box.length == 0) { if (box.length == 0)
{
// make guest prompt box // make guest prompt box
box = $('<div id="guestprompt-'+encodedUserId+'" class="guestprompt"><div class="choices"><a href="javascript:void(paduserlist.answerGuestPrompt(\''+encodedUserId+'\',false))">Deny</a> <a href="javascript:void(paduserlist.answerGuestPrompt(\''+encodedUserId+'\',true))">Approve</a></div><div class="guestname"><strong>Guest:</strong> '+padutils.escapeHtml(displayName)+'</div></div>'); box = $('<div id="guestprompt-' + encodedUserId + '" class="guestprompt"><div class="choices"><a href="javascript:void(paduserlist.answerGuestPrompt(\'' + encodedUserId + '\',false))">Deny</a> <a href="javascript:void(paduserlist.answerGuestPrompt(\'' + encodedUserId + '\',true))">Approve</a></div><div class="guestname"><strong>Guest:</strong> ' + padutils.escapeHtml(displayName) + '</div></div>');
$("#guestprompts").append(box); $("#guestprompts").append(box);
} }
else { else
{
// update display name // update display name
box.find(".guestname").html('<strong>Guest:</strong> '+padutils.escapeHtml(displayName)); box.find(".guestname").html('<strong>Guest:</strong> ' + padutils.escapeHtml(displayName));
} }
var hideLater = padutils.getCancellableAction(actionName, function() { var hideLater = padutils.getCancellableAction(actionName, function()
{
self.removeGuestPrompt(userId); self.removeGuestPrompt(userId);
}); });
window.setTimeout(hideLater, 15000); // time-out with no knock window.setTimeout(hideLater, 15000); // time-out with no knock
guestPromptFlash.scheduleAnimation(); guestPromptFlash.scheduleAnimation();
}, },
removeGuestPrompt: function(userId) { removeGuestPrompt: function(userId)
var box = $("#guestprompt-"+padutils.encodeUserId(userId)); {
var box = $("#guestprompt-" + padutils.encodeUserId(userId));
// remove ID now so a new knock by same user gets new, unfaded box // remove ID now so a new knock by same user gets new, unfaded box
box.removeAttr('id').fadeOut("fast", function() { box.removeAttr('id').fadeOut("fast", function()
{
box.remove(); box.remove();
}); });
knocksToIgnore[userId] = true; knocksToIgnore[userId] = true;
window.setTimeout(function() { window.setTimeout(function()
{
delete knocksToIgnore[userId]; delete knocksToIgnore[userId];
}, 5000); }, 5000);
}, },
answerGuestPrompt: function(encodedUserId, approve) { answerGuestPrompt: function(encodedUserId, approve)
{
var guestId = padutils.decodeUserId(encodedUserId); var guestId = padutils.decodeUserId(encodedUserId);
var msg = { var msg = {
@ -567,22 +684,24 @@ var paduserlist = (function() {
self.removeGuestPrompt(guestId); self.removeGuestPrompt(guestId);
}, },
renderMyUserInfo: function() { renderMyUserInfo: function()
if (myUserInfo.name) { {
if (myUserInfo.name)
{
$("#myusernameedit").removeClass("editempty").val( $("#myusernameedit").removeClass("editempty").val(
myUserInfo.name); myUserInfo.name);
} }
else { else
$("#myusernameedit").addClass("editempty").val( {
"Enter your name"); $("#myusernameedit").addClass("editempty").val("Enter your name");
} }
if (colorPickerOpen) { if (colorPickerOpen)
$("#myswatchbox").addClass('myswatchboxunhoverable').removeClass( {
'myswatchboxhoverable'); $("#myswatchbox").addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable');
} }
else { else
$("#myswatchbox").addClass('myswatchboxhoverable').removeClass( {
'myswatchboxunhoverable'); $("#myswatchbox").addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable');
} }
$("#myswatch").css('background', pad.getColorPalette()[myUserInfo.colorId]); $("#myswatch").css('background', pad.getColorPalette()[myUserInfo.colorId]);
} }
@ -590,20 +709,27 @@ var paduserlist = (function() {
return self; return self;
}()); }());
function getColorPickerSwatchIndex(jnode) { function getColorPickerSwatchIndex(jnode)
// return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1; {
// return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1;
return $("#colorpickerswatches li").index(jnode); return $("#colorpickerswatches li").index(jnode);
} }
function closeColorPicker(accept) {
if (accept) { function closeColorPicker(accept)
{
if (accept)
{
var newColorId = getColorPickerSwatchIndex($("#colorpickerswatches .picked")); var newColorId = getColorPickerSwatchIndex($("#colorpickerswatches .picked"));
if (newColorId >= 0) { // fails on NaN if (newColorId >= 0)
{ // fails on NaN
myUserInfo.colorId = newColorId; myUserInfo.colorId = newColorId;
pad.notifyChangeColor(newColorId); pad.notifyChangeColor(newColorId);
} }
paduserlist.renderMyUserInfo(); paduserlist.renderMyUserInfo();
} else { }
else
{
pad.notifyChangeColor(previousColorId); pad.notifyChangeColor(previousColorId);
paduserlist.renderMyUserInfo(); paduserlist.renderMyUserInfo();
} }
@ -612,23 +738,28 @@ function closeColorPicker(accept) {
$("#mycolorpicker").fadeOut("fast"); $("#mycolorpicker").fadeOut("fast");
} }
function showColorPicker() { function showColorPicker()
previousColorId = myUserInfo.colorId ; {
previousColorId = myUserInfo.colorId;
if (! colorPickerOpen) { if (!colorPickerOpen)
{
var palette = pad.getColorPalette(); var palette = pad.getColorPalette();
if(!colorPickerSetup) { if (!colorPickerSetup)
{
var colorsList = $("#colorpickerswatches") var colorsList = $("#colorpickerswatches")
for(var i=0;i<palette.length;i++) { for (var i = 0; i < palette.length; i++)
{
var li = $('<li>', { var li = $('<li>', {
style: 'background: '+palette[i]+';' style: 'background: ' + palette[i] + ';'
}); });
li.appendTo(colorsList); li.appendTo(colorsList);
li.bind('click', function(event){ li.bind('click', function(event)
{
$("#colorpickerswatches li").removeClass('picked'); $("#colorpickerswatches li").removeClass('picked');
$(event.target).addClass("picked"); $(event.target).addClass("picked");

View file

@ -15,65 +15,81 @@
*/ */
var padutils = { var padutils = {
escapeHtml: function(x) { escapeHtml: function(x)
{
return String(x).replace(/\</g, '&lt;').replace(/\>/g, '&gt;'); return String(x).replace(/\</g, '&lt;').replace(/\>/g, '&gt;');
}, },
uniqueId: function() { uniqueId: function()
function encodeNum(n, width) { {
function encodeNum(n, width)
{
// returns string that is exactly 'width' chars, padding with zeros // returns string that is exactly 'width' chars, padding with zeros
// and taking rightmost digits // and taking rightmost digits
return (Array(width+1).join('0') + Number(n).toString(35)).slice(-width); return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width);
} }
return [pad.getClientIp(), return [pad.getClientIp(), encodeNum(+new Date, 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.');
encodeNum(+new Date, 7),
encodeNum(Math.floor(Math.random()*1e9), 4)].join('.');
}, },
uaDisplay: function(ua) { uaDisplay: function(ua)
{
var m; var m;
function clean(a) { function clean(a)
{
var maxlen = 16; var maxlen = 16;
a = a.replace(/[^a-zA-Z0-9\.]/g, ''); a = a.replace(/[^a-zA-Z0-9\.]/g, '');
if (a.length > maxlen) { if (a.length > maxlen)
a = a.substr(0,maxlen); {
a = a.substr(0, maxlen);
} }
return a; return a;
} }
function checkver(name) { function checkver(name)
{
var m = ua.match(RegExp(name + '\\/([\\d\\.]+)')); var m = ua.match(RegExp(name + '\\/([\\d\\.]+)'));
if (m && m.length > 1) { if (m && m.length > 1)
return clean(name+m[1]); {
return clean(name + m[1]);
} }
return null; return null;
} }
// firefox // firefox
if (checkver('Firefox')) { return checkver('Firefox'); } if (checkver('Firefox'))
{
return checkver('Firefox');
}
// misc browsers, including IE // misc browsers, including IE
m = ua.match(/compatible; ([^;]+);/); m = ua.match(/compatible; ([^;]+);/);
if (m && m.length > 1) { if (m && m.length > 1)
{
return clean(m[1]); return clean(m[1]);
} }
// iphone // iphone
if (ua.match(/\(iPhone;/)) { if (ua.match(/\(iPhone;/))
{
return 'iPhone'; return 'iPhone';
} }
// chrome // chrome
if (checkver('Chrome')) { return checkver('Chrome'); } if (checkver('Chrome'))
{
return checkver('Chrome');
}
// safari // safari
m = ua.match(/Safari\/[\d\.]+/); m = ua.match(/Safari\/[\d\.]+/);
if (m) { if (m)
{
var v = '?'; var v = '?';
m = ua.match(/Version\/([\d\.]+)/); m = ua.match(/Version\/([\d\.]+)/);
if (m && m.length > 1) { if (m && m.length > 1)
{
v = m[1]; v = m[1];
} }
return clean('Safari'+v); return clean('Safari' + v);
} }
// everything else // everything else
@ -83,41 +99,49 @@ var padutils = {
// "func" is a function over 0..(numItems-1) that is monotonically // "func" is a function over 0..(numItems-1) that is monotonically
// "increasing" with index (false, then true). Finds the boundary // "increasing" with index (false, then true). Finds the boundary
// between false and true, a number between 0 and numItems inclusive. // between false and true, a number between 0 and numItems inclusive.
binarySearch: function (numItems, func) { binarySearch: function(numItems, func)
{
if (numItems < 1) return 0; if (numItems < 1) return 0;
if (func(0)) return 0; if (func(0)) return 0;
if (! func(numItems-1)) return numItems; if (!func(numItems - 1)) return numItems;
var low = 0; // func(low) is always false var low = 0; // func(low) is always false
var high = numItems-1; // func(high) is always true var high = numItems - 1; // func(high) is always true
while ((high - low) > 1) { while ((high - low) > 1)
var x = Math.floor((low+high)/2); // x != low, x != high {
var x = Math.floor((low + high) / 2); // x != low, x != high
if (func(x)) high = x; if (func(x)) high = x;
else low = x; else low = x;
} }
return high; return high;
}, },
// e.g. "Thu Jun 18 2009 13:09" // e.g. "Thu Jun 18 2009 13:09"
simpleDateTime: function(date) { simpleDateTime: function(date)
{
var d = new Date(+date); // accept either number or date var d = new Date(+date); // accept either number or date
var dayOfWeek = (['Sun','Mon','Tue','Wed','Thu','Fri','Sat'])[d.getDay()]; var dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()];
var month = (['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'])[d.getMonth()]; var month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()];
var dayOfMonth = d.getDate(); var dayOfMonth = d.getDate();
var year = d.getFullYear(); var year = d.getFullYear();
var hourmin = d.getHours()+":"+("0"+d.getMinutes()).slice(-2); var hourmin = d.getHours() + ":" + ("0" + d.getMinutes()).slice(-2);
return dayOfWeek+' '+month+' '+dayOfMonth+' '+year+' '+hourmin; return dayOfWeek + ' ' + month + ' ' + dayOfMonth + ' ' + year + ' ' + hourmin;
}, },
findURLs: function(text) { findURLs: function(text)
{
// copied from ACE // copied from ACE
var _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]/; var _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]/;
var _REGEX_URLCHAR = new RegExp('('+/[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source+'|'+_REGEX_WORDCHAR.source+')'); var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source+_REGEX_URLCHAR.source+'*(?![:.,;])'+_REGEX_URLCHAR.source, 'g'); var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
function _findURLs(text) {
function _findURLs(text)
{
_REGEX_URL.lastIndex = 0; _REGEX_URL.lastIndex = 0;
var urls = null; var urls = null;
var execResult; var execResult;
while ((execResult = _REGEX_URL.exec(text))) { while ((execResult = _REGEX_URL.exec(text)))
{
urls = (urls || []); urls = (urls || []);
var startIndex = execResult.index; var startIndex = execResult.index;
var url = execResult[0]; var url = execResult[0];
@ -129,23 +153,28 @@ var padutils = {
return _findURLs(text); return _findURLs(text);
}, },
escapeHtmlWithClickableLinks: function(text, target) { escapeHtmlWithClickableLinks: function(text, target)
{
var idx = 0; var idx = 0;
var pieces = []; var pieces = [];
var urls = padutils.findURLs(text); var urls = padutils.findURLs(text);
function advanceTo(i) {
if (i > idx) { function advanceTo(i)
{
if (i > idx)
{
pieces.push(padutils.escapeHtml(text.substring(idx, i))); pieces.push(padutils.escapeHtml(text.substring(idx, i)));
idx = i; idx = i;
} }
} }
if (urls) { if (urls)
for(var j=0;j<urls.length;j++) { {
for (var j = 0; j < urls.length; j++)
{
var startIndex = urls[j][0]; var startIndex = urls[j][0];
var href = urls[j][1]; var href = urls[j][1];
advanceTo(startIndex); advanceTo(startIndex);
pieces.push('<a ', (target?'target="'+target+'" ':''), pieces.push('<a ', (target ? 'target="' + target + '" ' : ''), 'href="', href.replace(/\"/g, '&quot;'), '">');
'href="', href.replace(/\"/g, '&quot;'), '">');
advanceTo(startIndex + href.length); advanceTo(startIndex + href.length);
pieces.push('</a>'); pieces.push('</a>');
} }
@ -153,209 +182,280 @@ var padutils = {
advanceTo(text.length); advanceTo(text.length);
return pieces.join(''); return pieces.join('');
}, },
bindEnterAndEscape: function(node, onEnter, onEscape) { bindEnterAndEscape: function(node, onEnter, onEscape)
{
// Use keypress instead of keyup in bindEnterAndEscape // Use keypress instead of keyup in bindEnterAndEscape
// Keyup event is fired on enter in IME (Input Method Editor), But // Keyup event is fired on enter in IME (Input Method Editor), But
// keypress is not. So, I changed to use keypress instead of keyup. // keypress is not. So, I changed to use keypress instead of keyup.
// It is work on Windows (IE8, Chrome 6.0.472), CentOs (Firefox 3.0) and Mac OSX (Firefox 3.6.10, Chrome 6.0.472, Safari 5.0). // It is work on Windows (IE8, Chrome 6.0.472), CentOs (Firefox 3.0) and Mac OSX (Firefox 3.6.10, Chrome 6.0.472, Safari 5.0).
if (onEnter)
if (onEnter) { {
node.keypress( function(evt) { node.keypress(function(evt)
if (evt.which == 13) { {
if (evt.which == 13)
{
onEnter(evt); onEnter(evt);
} }
}); });
} }
if (onEscape) { if (onEscape)
node.keydown( function(evt) { {
if (evt.which == 27) { node.keydown(function(evt)
{
if (evt.which == 27)
{
onEscape(evt); onEscape(evt);
} }
}); });
} }
}, },
timediff: function(d) { timediff: function(d)
function format(n, word) { {
function format(n, word)
{
n = Math.round(n); n = Math.round(n);
return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago'); return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago');
} }
d = Math.max(0, (+(new Date) - (+d) - pad.clientTimeOffset) / 1000); d = Math.max(0, (+(new Date) - (+d) - pad.clientTimeOffset) / 1000);
if (d < 60) { return format(d, 'second'); } if (d < 60)
{
return format(d, 'second');
}
d /= 60; d /= 60;
if (d < 60) { return format(d, 'minute'); } if (d < 60)
{
return format(d, 'minute');
}
d /= 60; d /= 60;
if (d < 24) { return format(d, 'hour'); } if (d < 24)
{
return format(d, 'hour');
}
d /= 24; d /= 24;
return format(d, 'day'); return format(d, 'day');
}, },
makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce) { makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce)
if (stepsAtOnce === undefined) { {
if (stepsAtOnce === undefined)
{
stepsAtOnce = 1; stepsAtOnce = 1;
} }
var animationTimer = null; var animationTimer = null;
function scheduleAnimation() { function scheduleAnimation()
if (! animationTimer) { {
animationTimer = window.setTimeout(function() { if (!animationTimer)
{
animationTimer = window.setTimeout(function()
{
animationTimer = null; animationTimer = null;
var n = stepsAtOnce; var n = stepsAtOnce;
var moreToDo = true; var moreToDo = true;
while (moreToDo && n > 0) { while (moreToDo && n > 0)
{
moreToDo = funcToAnimateOneStep(); moreToDo = funcToAnimateOneStep();
n--; n--;
} }
if (moreToDo) { if (moreToDo)
{
// more to do // more to do
scheduleAnimation(); scheduleAnimation();
} }
}, stepTime*stepsAtOnce); }, stepTime * stepsAtOnce);
} }
} }
return { scheduleAnimation: scheduleAnimation }; return {
scheduleAnimation: scheduleAnimation
};
}, },
makeShowHideAnimator: function(funcToArriveAtState, initiallyShown, fps, totalMs) { makeShowHideAnimator: function(funcToArriveAtState, initiallyShown, fps, totalMs)
{
var animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out var animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out
var animationFrameDelay = 1000 / fps; var animationFrameDelay = 1000 / fps;
var animationStep = animationFrameDelay / totalMs; var animationStep = animationFrameDelay / totalMs;
var scheduleAnimation = var scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
function doShow() { function doShow()
{
animationState = -1; animationState = -1;
funcToArriveAtState(animationState); funcToArriveAtState(animationState);
scheduleAnimation(); scheduleAnimation();
} }
function doQuickShow() { // start showing without losing any fade-in progress function doQuickShow()
if (animationState < -1) { { // start showing without losing any fade-in progress
if (animationState < -1)
{
animationState = -1; animationState = -1;
} }
else if (animationState <= 0) { else if (animationState <= 0)
{
animationState = animationState; animationState = animationState;
} }
else { else
animationState = Math.max(-1, Math.min(0, - animationState)); {
animationState = Math.max(-1, Math.min(0, -animationState));
} }
funcToArriveAtState(animationState); funcToArriveAtState(animationState);
scheduleAnimation(); scheduleAnimation();
} }
function doHide() { function doHide()
if (animationState >= -1 && animationState <= 0) { {
if (animationState >= -1 && animationState <= 0)
{
animationState = 1e-6; animationState = 1e-6;
scheduleAnimation(); scheduleAnimation();
} }
} }
function animateOneStep() { function animateOneStep()
if (animationState < -1 || animationState == 0) { {
if (animationState < -1 || animationState == 0)
{
return false; return false;
} }
else if (animationState < 0) { else if (animationState < 0)
{
// animate show // animate show
animationState += animationStep; animationState += animationStep;
if (animationState >= 0) { if (animationState >= 0)
{
animationState = 0; animationState = 0;
funcToArriveAtState(animationState); funcToArriveAtState(animationState);
return false; return false;
} }
else { else
{
funcToArriveAtState(animationState); funcToArriveAtState(animationState);
return true; return true;
} }
} }
else if (animationState > 0) { else if (animationState > 0)
{
// animate hide // animate hide
animationState += animationStep; animationState += animationStep;
if (animationState >= 1) { if (animationState >= 1)
{
animationState = 1; animationState = 1;
funcToArriveAtState(animationState); funcToArriveAtState(animationState);
animationState = -2; animationState = -2;
return false; return false;
} }
else { else
{
funcToArriveAtState(animationState); funcToArriveAtState(animationState);
return true; return true;
} }
} }
} }
return {show: doShow, hide: doHide, quickShow: doQuickShow}; return {
show: doShow,
hide: doHide,
quickShow: doQuickShow
};
}, },
_nextActionId: 1, _nextActionId: 1,
uncanceledActions: {}, uncanceledActions: {},
getCancellableAction: function(actionType, actionFunc) { getCancellableAction: function(actionType, actionFunc)
{
var o = padutils.uncanceledActions[actionType]; var o = padutils.uncanceledActions[actionType];
if (! o) { if (!o)
{
o = {}; o = {};
padutils.uncanceledActions[actionType] = o; padutils.uncanceledActions[actionType] = o;
} }
var actionId = (padutils._nextActionId++); var actionId = (padutils._nextActionId++);
o[actionId] = true; o[actionId] = true;
return function() { return function()
{
var p = padutils.uncanceledActions[actionType]; var p = padutils.uncanceledActions[actionType];
if (p && p[actionId]) { if (p && p[actionId])
{
actionFunc(); actionFunc();
} }
}; };
}, },
cancelActions: function(actionType) { cancelActions: function(actionType)
{
var o = padutils.uncanceledActions[actionType]; var o = padutils.uncanceledActions[actionType];
if (o) { if (o)
{
// clear it // clear it
delete padutils.uncanceledActions[actionType]; delete padutils.uncanceledActions[actionType];
} }
}, },
makeFieldLabeledWhenEmpty: function(field, labelText) { makeFieldLabeledWhenEmpty: function(field, labelText)
{
field = $(field); field = $(field);
function clear() {
function clear()
{
field.addClass('editempty'); field.addClass('editempty');
field.val(labelText); field.val(labelText);
} }
field.focus(function() { field.focus(function()
if (field.hasClass('editempty')) { {
if (field.hasClass('editempty'))
{
field.val(''); field.val('');
} }
field.removeClass('editempty'); field.removeClass('editempty');
}); });
field.blur(function() { field.blur(function()
if (! field.val()) { {
if (!field.val())
{
clear(); clear();
} }
}); });
return {clear:clear}; return {
clear: clear
};
}, },
getCheckbox: function(node) { getCheckbox: function(node)
{
return $(node).is(':checked'); return $(node).is(':checked');
}, },
setCheckbox: function(node, value) { setCheckbox: function(node, value)
if (value) { {
if (value)
{
$(node).attr('checked', 'checked'); $(node).attr('checked', 'checked');
} }
else { else
{
$(node).removeAttr('checked'); $(node).removeAttr('checked');
} }
}, },
bindCheckboxChange: function(node, func) { bindCheckboxChange: function(node, func)
{
$(node).bind("click change", func); $(node).bind("click change", func);
}, },
encodeUserId: function(userId) { encodeUserId: function(userId)
return userId.replace(/[^a-y0-9]/g, function(c) { {
return userId.replace(/[^a-y0-9]/g, function(c)
{
if (c == ".") return "-"; if (c == ".") return "-";
return 'z'+c.charCodeAt(0)+'z'; return 'z' + c.charCodeAt(0) + 'z';
}); });
}, },
decodeUserId: function(encodedUserId) { decodeUserId: function(encodedUserId)
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc) { {
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc)
{
if (cc == '-') return '.'; if (cc == '-') return '.';
else if (cc.charAt(0) == 'z') { else if (cc.charAt(0) == 'z')
return String.fromCharCode(Number(cc.slice(1,-1))); {
return String.fromCharCode(Number(cc.slice(1, -1)));
} }
else { else
{
return cc; return cc;
} }
}); });

View file

@ -1,22 +1,26 @@
plugins = { plugins = {
callHook: function (hookName, args) { callHook: function(hookName, args)
{
var hook = clientVars.hooks[hookName]; var hook = clientVars.hooks[hookName];
if (hook === undefined) if (hook === undefined) return [];
return [];
var res = []; var res = [];
for (var i = 0, N=hook.length; i < N; i++) { for (var i = 0, N = hook.length; i < N; i++)
{
var plugin = hook[i]; var plugin = hook[i];
var pluginRes = eval(plugin.plugin)[plugin.original || hookName](args); var pluginRes = eval(plugin.plugin)[plugin.original || hookName](args);
if (pluginRes != undefined && pluginRes != null) if (pluginRes != undefined && pluginRes != null) res = res.concat(pluginRes);
res = res.concat(pluginRes);
} }
return res; return res;
}, },
callHookStr: function (hookName, args, sep, pre, post) { callHookStr: function(hookName, args, sep, pre, post)
{
if (sep == undefined) sep = ''; if (sep == undefined) sep = '';
if (pre == undefined) pre = ''; if (pre == undefined) pre = '';
if (post == undefined) post = ''; if (post == undefined) post = '';
return plugins.callHook(hookName, args).map(function (x) { return pre + x + post}).join(sep || ""); return plugins.callHook(hookName, args).map(function(x)
{
return pre + x + post
}).join(sep || "");
} }
}; };

View file

@ -18,16 +18,43 @@
function newSkipList() { function newSkipList()
{
var PROFILER = window.PROFILER; var PROFILER = window.PROFILER;
if (!PROFILER) { if (!PROFILER)
PROFILER = function() { return {start:noop, mark:noop, literal:noop, end:noop, cancel:noop}; }; {
PROFILER = function()
{
return {
start: noop,
mark: noop,
literal: noop,
end: noop,
cancel: noop
};
};
} }
function noop() {}
function noop()
{}
// if there are N elements in the skiplist, "start" is element -1 and "end" is element N // if there are N elements in the skiplist, "start" is element -1 and "end" is element N
var start = {key:null, levels: 1, upPtrs:[null], downPtrs:[null], downSkips:[1], downSkipWidths:[0]}; var start = {
var end = {key:null, levels: 1, upPtrs:[null], downPtrs:[null], downSkips:[null], downSkipWidths:[null]}; key: null,
levels: 1,
upPtrs: [null],
downPtrs: [null],
downSkips: [1],
downSkipWidths: [0]
};
var end = {
key: null,
levels: 1,
upPtrs: [null],
downPtrs: [null],
downSkips: [null],
downSkipWidths: [null]
};
var numNodes = 0; var numNodes = 0;
var totalWidth = 0; var totalWidth = 0;
var keyToNodeMap = {}; var keyToNodeMap = {};
@ -38,20 +65,25 @@ function newSkipList() {
// After an insert or delete using point P, the point is still valid and points // After an insert or delete using point P, the point is still valid and points
// to the same index in the skiplist. Other operations with other points invalidate // to the same index in the skiplist. Other operations with other points invalidate
// this point. // this point.
function _getPoint(targetLoc) {
function _getPoint(targetLoc)
{
var numLevels = start.levels; var numLevels = start.levels;
var lvl = numLevels-1; var lvl = numLevels - 1;
var i = -1, ws = 0; var i = -1,
ws = 0;
var nodes = new Array(numLevels); var nodes = new Array(numLevels);
var idxs = new Array(numLevels); var idxs = new Array(numLevels);
var widthSkips = new Array(numLevels); var widthSkips = new Array(numLevels);
nodes[lvl] = start; nodes[lvl] = start;
idxs[lvl] = -1; idxs[lvl] = -1;
widthSkips[lvl] = 0; widthSkips[lvl] = 0;
while (lvl >= 0) { while (lvl >= 0)
{
var n = nodes[lvl]; var n = nodes[lvl];
while (n.downPtrs[lvl] && while (n.downPtrs[lvl] && (i + n.downSkips[lvl] < targetLoc))
(i + n.downSkips[lvl] < targetLoc)) { {
i += n.downSkips[lvl]; i += n.downSkips[lvl];
ws += n.downSkipWidths[lvl]; ws += n.downSkipWidths[lvl];
n = n.downPtrs[lvl]; n = n.downPtrs[lvl];
@ -60,19 +92,32 @@ function newSkipList() {
idxs[lvl] = i; idxs[lvl] = i;
widthSkips[lvl] = ws; widthSkips[lvl] = ws;
lvl--; lvl--;
if (lvl >= 0) { if (lvl >= 0)
{
nodes[lvl] = n; nodes[lvl] = n;
} }
} }
return {nodes:nodes, idxs:idxs, loc:targetLoc, widthSkips:widthSkips, toString: function() { return {
return "getPoint("+targetLoc+")"; } }; nodes: nodes,
idxs: idxs,
loc: targetLoc,
widthSkips: widthSkips,
toString: function()
{
return "getPoint(" + targetLoc + ")";
} }
function _getNodeAtOffset(targetOffset) { };
}
function _getNodeAtOffset(targetOffset)
{
var i = 0; var i = 0;
var n = start; var n = start;
var lvl = start.levels-1; var lvl = start.levels - 1;
while (lvl >= 0 && n.downPtrs[lvl]) { while (lvl >= 0 && n.downPtrs[lvl])
while (n.downPtrs[lvl] && (i + n.downSkipWidths[lvl] <= targetOffset)) { {
while (n.downPtrs[lvl] && (i + n.downSkipWidths[lvl] <= targetOffset))
{
i += n.downSkipWidths[lvl]; i += n.downSkipWidths[lvl];
n = n.downPtrs[lvl]; n = n.downPtrs[lvl];
} }
@ -82,10 +127,23 @@ function newSkipList() {
else if (n === end) return (targetOffset == totalWidth ? (end.upPtrs[0] || null) : null); else if (n === end) return (targetOffset == totalWidth ? (end.upPtrs[0] || null) : null);
return n; return n;
} }
function _entryWidth(e) { return (e && e.width) || 0; }
function _insertKeyAtPoint(point, newKey, entry) { function _entryWidth(e)
{
return (e && e.width) || 0;
}
function _insertKeyAtPoint(point, newKey, entry)
{
var p = PROFILER("insertKey", false); var p = PROFILER("insertKey", false);
var newNode = {key:newKey, levels: 0, upPtrs:[], downPtrs:[], downSkips:[], downSkipWidths:[]}; var newNode = {
key: newKey,
levels: 0,
upPtrs: [],
downPtrs: [],
downSkips: [],
downSkipWidths: []
};
p.mark("donealloc"); p.mark("donealloc");
var pNodes = point.nodes; var pNodes = point.nodes;
var pIdxs = point.idxs; var pIdxs = point.idxs;
@ -93,10 +151,12 @@ function newSkipList() {
var widthLoc = point.widthSkips[0] + point.nodes[0].downSkipWidths[0]; var widthLoc = point.widthSkips[0] + point.nodes[0].downSkipWidths[0];
var newWidth = _entryWidth(entry); var newWidth = _entryWidth(entry);
p.mark("loop1"); p.mark("loop1");
while (newNode.levels == 0 || Math.random() < 0.01) { while (newNode.levels == 0 || Math.random() < 0.01)
{
var lvl = newNode.levels; var lvl = newNode.levels;
newNode.levels++; newNode.levels++;
if (lvl == pNodes.length) { if (lvl == pNodes.length)
{
// assume we have just passed the end of point.nodes, and reached one level greater // assume we have just passed the end of point.nodes, and reached one level greater
// than the skiplist currently supports // than the skiplist currently supports
pNodes[lvl] = start; pNodes[lvl] = start;
@ -105,7 +165,7 @@ function newSkipList() {
end.levels++; end.levels++;
start.downPtrs[lvl] = end; start.downPtrs[lvl] = end;
end.upPtrs[lvl] = start; end.upPtrs[lvl] = start;
start.downSkips[lvl] = numNodes+1; start.downSkips[lvl] = numNodes + 1;
start.downSkipWidths[lvl] = totalWidth; start.downSkipWidths[lvl] = totalWidth;
point.widthSkips[lvl] = 0; point.widthSkips[lvl] = 0;
} }
@ -127,35 +187,46 @@ function newSkipList() {
} }
p.mark("loop2"); p.mark("loop2");
p.literal(pNodes.length, "PNL"); p.literal(pNodes.length, "PNL");
for(var lvl=newNode.levels; lvl<pNodes.length; lvl++) { for (var lvl = newNode.levels; lvl < pNodes.length; lvl++)
{
var up = pNodes[lvl]; var up = pNodes[lvl];
up.downSkips[lvl]++; up.downSkips[lvl]++;
up.downSkipWidths[lvl] += newWidth; up.downSkipWidths[lvl] += newWidth;
} }
p.mark("map"); p.mark("map");
keyToNodeMap['$KEY$'+newKey] = newNode; keyToNodeMap['$KEY$' + newKey] = newNode;
numNodes++; numNodes++;
totalWidth += newWidth; totalWidth += newWidth;
p.end(); p.end();
} }
function _getNodeAtPoint(point) {
function _getNodeAtPoint(point)
{
return point.nodes[0].downPtrs[0]; return point.nodes[0].downPtrs[0];
} }
function _incrementPoint(point) {
function _incrementPoint(point)
{
point.loc++; point.loc++;
for(var i=0;i<point.nodes.length;i++) { for (var i = 0; i < point.nodes.length; i++)
if (point.idxs[i] + point.nodes[i].downSkips[i] < point.loc) { {
if (point.idxs[i] + point.nodes[i].downSkips[i] < point.loc)
{
point.idxs[i] += point.nodes[i].downSkips[i]; point.idxs[i] += point.nodes[i].downSkips[i];
point.widthSkips[i] += point.nodes[i].downSkipWidths[i]; point.widthSkips[i] += point.nodes[i].downSkipWidths[i];
point.nodes[i] = point.nodes[i].downPtrs[i]; point.nodes[i] = point.nodes[i].downPtrs[i];
} }
} }
} }
function _deleteKeyAtPoint(point) {
function _deleteKeyAtPoint(point)
{
var elem = point.nodes[0].downPtrs[0]; var elem = point.nodes[0].downPtrs[0];
var elemWidth = _entryWidth(elem.entry); var elemWidth = _entryWidth(elem.entry);
for(var i=0;i<point.nodes.length;i++) { for (var i = 0; i < point.nodes.length; i++)
if (i < elem.levels) { {
if (i < elem.levels)
{
var up = elem.upPtrs[i]; var up = elem.upPtrs[i];
var down = elem.downPtrs[i]; var down = elem.downPtrs[i];
var totalSkip = up.downSkips[i] + elem.downSkips[i] - 1; var totalSkip = up.downSkips[i] + elem.downSkips[i] - 1;
@ -165,44 +236,52 @@ function newSkipList() {
var totalWidthSkip = up.downSkipWidths[i] + elem.downSkipWidths[i] - elemWidth; var totalWidthSkip = up.downSkipWidths[i] + elem.downSkipWidths[i] - elemWidth;
up.downSkipWidths[i] = totalWidthSkip; up.downSkipWidths[i] = totalWidthSkip;
} }
else { else
{
var up = point.nodes[i]; var up = point.nodes[i];
var down = up.downPtrs[i]; var down = up.downPtrs[i];
up.downSkips[i]--; up.downSkips[i]--;
up.downSkipWidths[i] -= elemWidth; up.downSkipWidths[i] -= elemWidth;
} }
} }
delete keyToNodeMap['$KEY$'+elem.key]; delete keyToNodeMap['$KEY$' + elem.key];
numNodes--; numNodes--;
totalWidth -= elemWidth; totalWidth -= elemWidth;
} }
function _propagateWidthChange(node) {
function _propagateWidthChange(node)
{
var oldWidth = node.downSkipWidths[0]; var oldWidth = node.downSkipWidths[0];
var newWidth = _entryWidth(node.entry); var newWidth = _entryWidth(node.entry);
var widthChange = newWidth - oldWidth; var widthChange = newWidth - oldWidth;
var n = node; var n = node;
var lvl = 0; var lvl = 0;
while (lvl < n.levels) { while (lvl < n.levels)
{
n.downSkipWidths[lvl] += widthChange; n.downSkipWidths[lvl] += widthChange;
lvl++; lvl++;
while (lvl >= n.levels && n.upPtrs[lvl-1]) { while (lvl >= n.levels && n.upPtrs[lvl - 1])
n = n.upPtrs[lvl-1]; {
n = n.upPtrs[lvl - 1];
} }
} }
totalWidth += widthChange; totalWidth += widthChange;
} }
function _getNodeIndex(node, byWidth) {
function _getNodeIndex(node, byWidth)
{
var dist = (byWidth ? 0 : -1); var dist = (byWidth ? 0 : -1);
var n = node; var n = node;
while (n !== start) { while (n !== start)
var lvl = n.levels-1; {
var lvl = n.levels - 1;
n = n.upPtrs[lvl]; n = n.upPtrs[lvl];
if (byWidth) dist += n.downSkipWidths[lvl]; if (byWidth) dist += n.downSkipWidths[lvl];
else dist += n.downSkips[lvl]; else dist += n.downSkips[lvl];
} }
return dist; return dist;
} }
/*function _debugToString() { /*function _debugToString() {
var array = [start]; var array = [start];
while (array[array.length-1] !== end) { while (array[array.length-1] !== end) {
array[array.length] = array[array.length-1].downPtrs[0]; array[array.length] = array[array.length-1].downPtrs[0];
@ -224,32 +303,40 @@ function newSkipList() {
return map(processedArray, function (x) { return x.toSource(); }).join("\n"); return map(processedArray, function (x) { return x.toSource(); }).join("\n");
}*/ }*/
function _getNodeByKey(key) { function _getNodeByKey(key)
return keyToNodeMap['$KEY$'+key]; {
return keyToNodeMap['$KEY$' + key];
} }
// Returns index of first entry such that entryFunc(entry) is truthy, // Returns index of first entry such that entryFunc(entry) is truthy,
// or length() if no such entry. Assumes all falsy entries come before // or length() if no such entry. Assumes all falsy entries come before
// all truthy entries. // all truthy entries.
function _search(entryFunc) {
function _search(entryFunc)
{
var low = start; var low = start;
var lvl = start.levels-1; var lvl = start.levels - 1;
var lowIndex = -1; var lowIndex = -1;
function f(node) {
function f(node)
{
if (node === start) return false; if (node === start) return false;
else if (node === end) return true; else if (node === end) return true;
else return entryFunc(node.entry); else return entryFunc(node.entry);
} }
while (lvl >= 0) { while (lvl >= 0)
{
var nextLow = low.downPtrs[lvl]; var nextLow = low.downPtrs[lvl];
while (!f(nextLow)) { while (!f(nextLow))
{
lowIndex += low.downSkips[lvl]; lowIndex += low.downSkips[lvl];
low = nextLow; low = nextLow;
nextLow = low.downPtrs[lvl]; nextLow = low.downPtrs[lvl];
} }
lvl--; lvl--;
} }
return lowIndex+1; return lowIndex + 1;
} }
/* /*
@ -257,43 +344,55 @@ The skip-list contains "entries", JavaScript objects that each must have a uniqu
that is a string. that is a string.
*/ */
var self = { var self = {
length: function() { return numNodes; }, length: function()
atIndex: function(i) { {
if (i < 0) console.warn("atIndex("+i+")"); return numNodes;
if (i >= numNodes) console.warn("atIndex("+i+">="+numNodes+")"); },
atIndex: function(i)
{
if (i < 0) console.warn("atIndex(" + i + ")");
if (i >= numNodes) console.warn("atIndex(" + i + ">=" + numNodes + ")");
return _getNodeAtPoint(_getPoint(i)).entry; return _getNodeAtPoint(_getPoint(i)).entry;
}, },
// differs from Array.splice() in that new elements are in an array, not varargs // differs from Array.splice() in that new elements are in an array, not varargs
splice: function(start, deleteCount, newEntryArray) { splice: function(start, deleteCount, newEntryArray)
if (start < 0) console.warn("splice("+start+", ...)"); {
if (start + deleteCount > numNodes) { if (start < 0) console.warn("splice(" + start + ", ...)");
console.warn("splice("+start+", "+deleteCount+", ...), N="+numNodes); if (start + deleteCount > numNodes)
{
console.warn("splice(" + start + ", " + deleteCount + ", ...), N=" + numNodes);
console.warn("%s %s %s", typeof start, typeof deleteCount, typeof numNodes); console.warn("%s %s %s", typeof start, typeof deleteCount, typeof numNodes);
console.trace(); console.trace();
} }
if (! newEntryArray) newEntryArray = []; if (!newEntryArray) newEntryArray = [];
var pt = _getPoint(start); var pt = _getPoint(start);
for(var i=0;i<deleteCount;i++) { for (var i = 0; i < deleteCount; i++)
{
_deleteKeyAtPoint(pt); _deleteKeyAtPoint(pt);
} }
for(var i=(newEntryArray.length-1);i>=0;i--) { for (var i = (newEntryArray.length - 1); i >= 0; i--)
{
var entry = newEntryArray[i]; var entry = newEntryArray[i];
_insertKeyAtPoint(pt, entry.key, entry); _insertKeyAtPoint(pt, entry.key, entry);
var node = _getNodeByKey(entry.key); var node = _getNodeByKey(entry.key);
node.entry = entry; node.entry = entry;
} }
}, },
next: function (entry) { next: function(entry)
{
return _getNodeByKey(entry.key).downPtrs[0].entry || null; return _getNodeByKey(entry.key).downPtrs[0].entry || null;
}, },
prev: function (entry) { prev: function(entry)
{
return _getNodeByKey(entry.key).upPtrs[0].entry || null; return _getNodeByKey(entry.key).upPtrs[0].entry || null;
}, },
push: function(entry) { push: function(entry)
{
self.splice(numNodes, 0, [entry]); self.splice(numNodes, 0, [entry]);
}, },
slice: function(start, end) { slice: function(start, end)
{
// act like Array.slice() // act like Array.slice()
if (start === undefined) start = 0; if (start === undefined) start = 0;
else if (start < 0) start += numNodes; else if (start < 0) start += numNodes;
@ -305,43 +404,81 @@ that is a string.
if (end < 0) end = 0; if (end < 0) end = 0;
if (end > numNodes) end = numNodes; if (end > numNodes) end = numNodes;
dmesg(String([start,end,numNodes])); dmesg(String([start, end, numNodes]));
if (end <= start) return []; if (end <= start) return [];
var n = self.atIndex(start); var n = self.atIndex(start);
var array = [n]; var array = [n];
for(var i=1;i<(end-start);i++) { for (var i = 1; i < (end - start); i++)
{
n = self.next(n); n = self.next(n);
array.push(n); array.push(n);
} }
return array; return array;
}, },
atKey: function(key) { return _getNodeByKey(key).entry; }, atKey: function(key)
indexOfKey: function(key) { return _getNodeIndex(_getNodeByKey(key)); }, {
indexOfEntry: function (entry) { return self.indexOfKey(entry.key); }, return _getNodeByKey(key).entry;
containsKey: function(key) { return !!(_getNodeByKey(key)); }, },
indexOfKey: function(key)
{
return _getNodeIndex(_getNodeByKey(key));
},
indexOfEntry: function(entry)
{
return self.indexOfKey(entry.key);
},
containsKey: function(key)
{
return !!(_getNodeByKey(key));
},
// gets the last entry starting at or before the offset // gets the last entry starting at or before the offset
atOffset: function(offset) { return _getNodeAtOffset(offset).entry; }, atOffset: function(offset)
keyAtOffset: function(offset) { return self.atOffset(offset).key; }, {
offsetOfKey: function(key) { return _getNodeIndex(_getNodeByKey(key), true); }, return _getNodeAtOffset(offset).entry;
offsetOfEntry: function(entry) { return self.offsetOfKey(entry.key); }, },
setEntryWidth: function(entry, width) { entry.width = width; _propagateWidthChange(_getNodeByKey(entry.key)); }, keyAtOffset: function(offset)
totalWidth: function() { return totalWidth; }, {
offsetOfIndex: function(i) { return self.atOffset(offset).key;
},
offsetOfKey: function(key)
{
return _getNodeIndex(_getNodeByKey(key), true);
},
offsetOfEntry: function(entry)
{
return self.offsetOfKey(entry.key);
},
setEntryWidth: function(entry, width)
{
entry.width = width;
_propagateWidthChange(_getNodeByKey(entry.key));
},
totalWidth: function()
{
return totalWidth;
},
offsetOfIndex: function(i)
{
if (i < 0) return 0; if (i < 0) return 0;
if (i >= numNodes) return totalWidth; if (i >= numNodes) return totalWidth;
return self.offsetOfEntry(self.atIndex(i)); return self.offsetOfEntry(self.atIndex(i));
}, },
indexOfOffset: function(offset) { indexOfOffset: function(offset)
{
if (offset <= 0) return 0; if (offset <= 0) return 0;
if (offset >= totalWidth) return numNodes; if (offset >= totalWidth) return numNodes;
return self.indexOfEntry(self.atOffset(offset)); return self.indexOfEntry(self.atOffset(offset));
}, },
search: function(entryFunc) { search: function(entryFunc)
{
return _search(entryFunc); return _search(entryFunc);
}, },
//debugToString: _debugToString, //debugToString: _debugToString,
debugGetPoint: _getPoint, debugGetPoint: _getPoint,
debugDepth: function() { return start.levels; } debugDepth: function()
{
return start.levels;
}
} }
return self; return self;
} }

View file

@ -14,12 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
if (window._orig_windowOpen) { if (window._orig_windowOpen)
{
window.open = _orig_windowOpen; window.open = _orig_windowOpen;
} }
if (window._orig_windowSetTimeout) { if (window._orig_windowSetTimeout)
{
window.setTimeout = _orig_windowSetTimeout; window.setTimeout = _orig_windowSetTimeout;
} }
if (window._orig_windowSetInterval) { if (window._orig_windowSetInterval)
{
window.setInterval = _orig_windowSetInterval; window.setInterval = _orig_windowSetInterval;
} }

View file

@ -15,8 +15,10 @@
*/ */
undoModule = (function() { undoModule = (function()
var stack = (function() { {
var stack = (function()
{
var stackElements = []; var stackElements = [];
// two types of stackElements: // two types of stackElements:
// 1) { elementType: UNDOABLE_EVENT, eventType: "anything", [backset: <changeset>,] // 1) { elementType: UNDOABLE_EVENT, eventType: "anything", [backset: <changeset>,]
@ -28,105 +30,137 @@ undoModule = (function() {
var UNDOABLE_EVENT = "undoableEvent"; var UNDOABLE_EVENT = "undoableEvent";
var EXTERNAL_CHANGE = "externalChange"; var EXTERNAL_CHANGE = "externalChange";
function clearStack() { function clearStack()
{
stackElements.length = 0; stackElements.length = 0;
stackElements.push({ elementType: UNDOABLE_EVENT, eventType: "bottom" }); stackElements.push(
{
elementType: UNDOABLE_EVENT,
eventType: "bottom"
});
numUndoableEvents = 1; numUndoableEvents = 1;
} }
clearStack(); clearStack();
function pushEvent(event) { function pushEvent(event)
var e = extend({}, event); {
var e = extend(
{}, event);
e.elementType = UNDOABLE_EVENT; e.elementType = UNDOABLE_EVENT;
stackElements.push(e); stackElements.push(e);
numUndoableEvents++; numUndoableEvents++;
//dmesg("pushEvent backset: "+event.backset); //dmesg("pushEvent backset: "+event.backset);
} }
function pushExternalChange(cs) { function pushExternalChange(cs)
var idx = stackElements.length-1; {
if (stackElements[idx].elementType == EXTERNAL_CHANGE) { var idx = stackElements.length - 1;
if (stackElements[idx].elementType == EXTERNAL_CHANGE)
{
stackElements[idx].changeset = Changeset.compose(stackElements[idx].changeset, cs, getAPool()); stackElements[idx].changeset = Changeset.compose(stackElements[idx].changeset, cs, getAPool());
} }
else { else
stackElements.push({elementType: EXTERNAL_CHANGE, changeset: cs}); {
stackElements.push(
{
elementType: EXTERNAL_CHANGE,
changeset: cs
});
} }
} }
function _exposeEvent(nthFromTop) { function _exposeEvent(nthFromTop)
{
// precond: 0 <= nthFromTop < numUndoableEvents // precond: 0 <= nthFromTop < numUndoableEvents
var targetIndex = stackElements.length - 1 - nthFromTop; var targetIndex = stackElements.length - 1 - nthFromTop;
var idx = stackElements.length - 1; var idx = stackElements.length - 1;
while (idx > targetIndex || stackElements[idx].elementType == EXTERNAL_CHANGE) { while (idx > targetIndex || stackElements[idx].elementType == EXTERNAL_CHANGE)
if (stackElements[idx].elementType == EXTERNAL_CHANGE) { {
if (stackElements[idx].elementType == EXTERNAL_CHANGE)
{
var ex = stackElements[idx]; var ex = stackElements[idx];
var un = stackElements[idx-1]; var un = stackElements[idx - 1];
if (un.backset) { if (un.backset)
{
var excs = ex.changeset; var excs = ex.changeset;
var unbs = un.backset; var unbs = un.backset;
un.backset = Changeset.follow(excs, un.backset, false, getAPool()); un.backset = Changeset.follow(excs, un.backset, false, getAPool());
ex.changeset = Changeset.follow(unbs, ex.changeset, true, getAPool()); ex.changeset = Changeset.follow(unbs, ex.changeset, true, getAPool());
if ((typeof un.selStart) == "number") { if ((typeof un.selStart) == "number")
{
var newSel = Changeset.characterRangeFollow(excs, un.selStart, un.selEnd); var newSel = Changeset.characterRangeFollow(excs, un.selStart, un.selEnd);
un.selStart = newSel[0]; un.selStart = newSel[0];
un.selEnd = newSel[1]; un.selEnd = newSel[1];
if (un.selStart == un.selEnd) { if (un.selStart == un.selEnd)
{
un.selFocusAtStart = false; un.selFocusAtStart = false;
} }
} }
} }
stackElements[idx-1] = ex; stackElements[idx - 1] = ex;
stackElements[idx] = un; stackElements[idx] = un;
if (idx >= 2 && stackElements[idx-2].elementType == EXTERNAL_CHANGE) { if (idx >= 2 && stackElements[idx - 2].elementType == EXTERNAL_CHANGE)
ex.changeset = Changeset.compose(stackElements[idx-2].changeset, {
ex.changeset, getAPool()); ex.changeset = Changeset.compose(stackElements[idx - 2].changeset, ex.changeset, getAPool());
stackElements.splice(idx-2, 1); stackElements.splice(idx - 2, 1);
idx--; idx--;
} }
} }
else { else
{
idx--; idx--;
} }
} }
} }
function getNthFromTop(n) { function getNthFromTop(n)
{
// precond: 0 <= n < numEvents() // precond: 0 <= n < numEvents()
_exposeEvent(n); _exposeEvent(n);
return stackElements[stackElements.length - 1 - n]; return stackElements[stackElements.length - 1 - n];
} }
function numEvents() { function numEvents()
{
return numUndoableEvents; return numUndoableEvents;
} }
function popEvent() { function popEvent()
{
// precond: numEvents() > 0 // precond: numEvents() > 0
_exposeEvent(0); _exposeEvent(0);
numUndoableEvents--; numUndoableEvents--;
return stackElements.pop(); return stackElements.pop();
} }
return {numEvents:numEvents, popEvent:popEvent, pushEvent: pushEvent, return {
pushExternalChange: pushExternalChange, clearStack: clearStack, numEvents: numEvents,
getNthFromTop:getNthFromTop}; popEvent: popEvent,
pushEvent: pushEvent,
pushExternalChange: pushExternalChange,
clearStack: clearStack,
getNthFromTop: getNthFromTop
};
})(); })();
// invariant: stack always has at least one undoable event // invariant: stack always has at least one undoable event
var undoPtr = 0; // zero-index from top of stack, 0 == top var undoPtr = 0; // zero-index from top of stack, 0 == top
function clearHistory() { function clearHistory()
{
stack.clearStack(); stack.clearStack();
undoPtr = 0; undoPtr = 0;
} }
function _charOccurrences(str, c) { function _charOccurrences(str, c)
{
var i = 0; var i = 0;
var count = 0; var count = 0;
while (i >= 0 && i < str.length) { while (i >= 0 && i < str.length)
{
i = str.indexOf(c, i); i = str.indexOf(c, i);
if (i >= 0) { if (i >= 0)
{
count++; count++;
i++; i++;
} }
@ -134,13 +168,15 @@ undoModule = (function() {
return count; return count;
} }
function _opcodeOccurrences(cs, opcode) { function _opcodeOccurrences(cs, opcode)
{
return _charOccurrences(Changeset.unpack(cs).ops, opcode); return _charOccurrences(Changeset.unpack(cs).ops, opcode);
} }
function _mergeChangesets(cs1, cs2) { function _mergeChangesets(cs1, cs2)
if (! cs1) return cs2; {
if (! cs2) return cs1; if (!cs1) return cs2;
if (!cs2) return cs1;
// Rough heuristic for whether changesets should be considered one action: // Rough heuristic for whether changesets should be considered one action:
// each does exactly one insertion, no dels, and the composition does also; or // each does exactly one insertion, no dels, and the composition does also; or
@ -152,51 +188,63 @@ undoModule = (function() {
var plusCount2 = _opcodeOccurrences(cs2, '+'); var plusCount2 = _opcodeOccurrences(cs2, '+');
var minusCount1 = _opcodeOccurrences(cs1, '-'); var minusCount1 = _opcodeOccurrences(cs1, '-');
var minusCount2 = _opcodeOccurrences(cs2, '-'); var minusCount2 = _opcodeOccurrences(cs2, '-');
if (plusCount1 == 1 && plusCount2 == 1 && minusCount1 == 0 && minusCount2 == 0) { if (plusCount1 == 1 && plusCount2 == 1 && minusCount1 == 0 && minusCount2 == 0)
{
var merge = Changeset.compose(cs1, cs2, getAPool()); var merge = Changeset.compose(cs1, cs2, getAPool());
var plusCount3 = _opcodeOccurrences(merge, '+'); var plusCount3 = _opcodeOccurrences(merge, '+');
var minusCount3 = _opcodeOccurrences(merge, '-'); var minusCount3 = _opcodeOccurrences(merge, '-');
if (plusCount3 == 1 && minusCount3 == 0) { if (plusCount3 == 1 && minusCount3 == 0)
{
return merge; return merge;
} }
} }
else if (plusCount1 == 0 && plusCount2 == 0 && minusCount1 == 1 && minusCount2 == 1) { else if (plusCount1 == 0 && plusCount2 == 0 && minusCount1 == 1 && minusCount2 == 1)
{
var merge = Changeset.compose(cs1, cs2, getAPool()); var merge = Changeset.compose(cs1, cs2, getAPool());
var plusCount3 = _opcodeOccurrences(merge, '+'); var plusCount3 = _opcodeOccurrences(merge, '+');
var minusCount3 = _opcodeOccurrences(merge, '-'); var minusCount3 = _opcodeOccurrences(merge, '-');
if (plusCount3 == 0 && minusCount3 == 1) { if (plusCount3 == 0 && minusCount3 == 1)
{
return merge; return merge;
} }
} }
return null; return null;
} }
function reportEvent(event) { function reportEvent(event)
{
var topEvent = stack.getNthFromTop(0); var topEvent = stack.getNthFromTop(0);
function applySelectionToTop() { function applySelectionToTop()
if ((typeof event.selStart) == "number") { {
if ((typeof event.selStart) == "number")
{
topEvent.selStart = event.selStart; topEvent.selStart = event.selStart;
topEvent.selEnd = event.selEnd; topEvent.selEnd = event.selEnd;
topEvent.selFocusAtStart = event.selFocusAtStart; topEvent.selFocusAtStart = event.selFocusAtStart;
} }
} }
if ((! event.backset) || Changeset.isIdentity(event.backset)) { if ((!event.backset) || Changeset.isIdentity(event.backset))
{
applySelectionToTop(); applySelectionToTop();
} }
else { else
{
var merged = false; var merged = false;
if (topEvent.eventType == event.eventType) { if (topEvent.eventType == event.eventType)
{
var merge = _mergeChangesets(event.backset, topEvent.backset); var merge = _mergeChangesets(event.backset, topEvent.backset);
if (merge) { if (merge)
{
topEvent.backset = merge; topEvent.backset = merge;
//dmesg("reportEvent merge: "+merge); //dmesg("reportEvent merge: "+merge);
applySelectionToTop(); applySelectionToTop();
merged = true; merged = true;
} }
} }
if (! merged) { if (!merged)
{
stack.pushEvent(event); stack.pushEvent(event);
} }
undoPtr = 0; undoPtr = 0;
@ -204,19 +252,27 @@ undoModule = (function() {
} }
function reportExternalChange(changeset) { function reportExternalChange(changeset)
if (changeset && ! Changeset.isIdentity(changeset)) { {
if (changeset && !Changeset.isIdentity(changeset))
{
stack.pushExternalChange(changeset); stack.pushExternalChange(changeset);
} }
} }
function _getSelectionInfo(event) { function _getSelectionInfo(event)
if ((typeof event.selStart) != "number") { {
if ((typeof event.selStart) != "number")
{
return null; return null;
} }
else { else
return {selStart: event.selStart, selEnd: event.selEnd, {
selFocusAtStart: event.selFocusAtStart}; return {
selStart: event.selStart,
selEnd: event.selEnd,
selFocusAtStart: event.selFocusAtStart
};
} }
} }
@ -226,10 +282,12 @@ undoModule = (function() {
// or can be called with no arguments to mean that no undo is possible. // or can be called with no arguments to mean that no undo is possible.
// "eventFunc" will be called exactly once. // "eventFunc" will be called exactly once.
function performUndo(eventFunc) { function performUndo(eventFunc)
if (undoPtr < stack.numEvents()-1) { {
if (undoPtr < stack.numEvents() - 1)
{
var backsetEvent = stack.getNthFromTop(undoPtr); var backsetEvent = stack.getNthFromTop(undoPtr);
var selectionEvent = stack.getNthFromTop(undoPtr+1); var selectionEvent = stack.getNthFromTop(undoPtr + 1);
var undoEvent = eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent)); var undoEvent = eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent));
stack.pushEvent(undoEvent); stack.pushEvent(undoEvent);
undoPtr += 2; undoPtr += 2;
@ -237,8 +295,10 @@ undoModule = (function() {
else eventFunc(); else eventFunc();
} }
function performRedo(eventFunc) { function performRedo(eventFunc)
if (undoPtr >= 2) { {
if (undoPtr >= 2)
{
var backsetEvent = stack.getNthFromTop(0); var backsetEvent = stack.getNthFromTop(0);
var selectionEvent = stack.getNthFromTop(1); var selectionEvent = stack.getNthFromTop(1);
eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent)); eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent));
@ -248,11 +308,18 @@ undoModule = (function() {
else eventFunc(); else eventFunc();
} }
function getAPool() { function getAPool()
{
return undoModule.apool; return undoModule.apool;
} }
return {clearHistory:clearHistory, reportEvent:reportEvent, reportExternalChange:reportExternalChange, return {
performUndo:performUndo, performRedo:performRedo, enabled: true, clearHistory: clearHistory,
apool: null}; // apool is filled in by caller reportEvent: reportEvent,
reportExternalChange: reportExternalChange,
performUndo: performUndo,
performRedo: performRedo,
enabled: true,
apool: null
}; // apool is filled in by caller
})(); })();

View file

@ -14,7 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
function makeVirtualLineView(lineNode) { function makeVirtualLineView(lineNode)
{
// how much to jump forward or backward at once in a charSeeker before // how much to jump forward or backward at once in a charSeeker before
// constructing a DOM node and checking the coordinates (which takes a // constructing a DOM node and checking the coordinates (which takes a
@ -26,12 +27,15 @@ function makeVirtualLineView(lineNode) {
var maxCharIncrement = 20; var maxCharIncrement = 20;
var seekerAtEnd = null; var seekerAtEnd = null;
function getNumChars() { function getNumChars()
{
return lineNode.textContent.length; return lineNode.textContent.length;
} }
function getNumVirtualLines() { function getNumVirtualLines()
if (! seekerAtEnd) { {
if (!seekerAtEnd)
{
var seeker = makeCharSeeker(); var seeker = makeCharSeeker();
seeker.forwardByWhile(maxCharIncrement); seeker.forwardByWhile(maxCharIncrement);
seekerAtEnd = seeker; seekerAtEnd = seeker;
@ -39,73 +43,110 @@ function makeVirtualLineView(lineNode) {
return seekerAtEnd.getVirtualLine() + 1; return seekerAtEnd.getVirtualLine() + 1;
} }
function getVLineAndOffsetForChar(lineChar) { function getVLineAndOffsetForChar(lineChar)
{
var seeker = makeCharSeeker(); var seeker = makeCharSeeker();
seeker.forwardByWhile(maxCharIncrement, null, lineChar); seeker.forwardByWhile(maxCharIncrement, null, lineChar);
var theLine = seeker.getVirtualLine(); var theLine = seeker.getVirtualLine();
seeker.backwardByWhile(8, function() { return seeker.getVirtualLine() == theLine; }); seeker.backwardByWhile(8, function()
seeker.forwardByWhile(1, function() { return seeker.getVirtualLine() != theLine; }); {
return seeker.getVirtualLine() == theLine;
});
seeker.forwardByWhile(1, function()
{
return seeker.getVirtualLine() != theLine;
});
var lineStartChar = seeker.getOffset(); var lineStartChar = seeker.getOffset();
return {vline:theLine, offset:(lineChar - lineStartChar)}; return {
vline: theLine,
offset: (lineChar - lineStartChar)
};
} }
function getCharForVLineAndOffset(vline, offset) { function getCharForVLineAndOffset(vline, offset)
{
// returns revised vline and offset as well as absolute char index within line. // returns revised vline and offset as well as absolute char index within line.
// if offset is beyond end of line, for example, will give new offset at end of line. // if offset is beyond end of line, for example, will give new offset at end of line.
var seeker = makeCharSeeker(); var seeker = makeCharSeeker();
// go to start of line // go to start of line
seeker.binarySearch(function() { seeker.binarySearch(function()
{
return seeker.getVirtualLine() >= vline; return seeker.getVirtualLine() >= vline;
}); });
var lineStart = seeker.getOffset(); var lineStart = seeker.getOffset();
var theLine = seeker.getVirtualLine(); var theLine = seeker.getVirtualLine();
// go to offset, overshooting the virtual line only if offset is too large for it // go to offset, overshooting the virtual line only if offset is too large for it
seeker.forwardByWhile(maxCharIncrement, null, lineStart+offset); seeker.forwardByWhile(maxCharIncrement, null, lineStart + offset);
// get back into line // get back into line
seeker.backwardByWhile(1, function() { return seeker.getVirtualLine() != theLine; }, lineStart); seeker.backwardByWhile(1, function()
{
return seeker.getVirtualLine() != theLine;
}, lineStart);
var lineChar = seeker.getOffset(); var lineChar = seeker.getOffset();
var theOffset = lineChar - lineStart; var theOffset = lineChar - lineStart;
// handle case of last virtual line; should be able to be at end of it // handle case of last virtual line; should be able to be at end of it
if (theOffset < offset && theLine == (getNumVirtualLines()-1)) { if (theOffset < offset && theLine == (getNumVirtualLines() - 1))
{
var lineLen = getNumChars(); var lineLen = getNumChars();
theOffset += lineLen-lineChar; theOffset += lineLen - lineChar;
lineChar = lineLen; lineChar = lineLen;
} }
return { vline:theLine, offset:theOffset, lineChar:lineChar }; return {
vline: theLine,
offset: theOffset,
lineChar: lineChar
};
} }
return {getNumVirtualLines:getNumVirtualLines, getVLineAndOffsetForChar:getVLineAndOffsetForChar, return {
getCharForVLineAndOffset:getCharForVLineAndOffset, getNumVirtualLines: getNumVirtualLines,
makeCharSeeker: function() { return makeCharSeeker(); } }; getVLineAndOffsetForChar: getVLineAndOffsetForChar,
getCharForVLineAndOffset: getCharForVLineAndOffset,
makeCharSeeker: function()
{
return makeCharSeeker();
}
};
function deepFirstChildTextNode(nd) { function deepFirstChildTextNode(nd)
{
nd = nd.firstChild; nd = nd.firstChild;
while (nd && nd.firstChild) nd = nd.firstChild; while (nd && nd.firstChild) nd = nd.firstChild;
if (nd.data) return nd; if (nd.data) return nd;
return null; return null;
} }
function makeCharSeeker(/*lineNode*/) { function makeCharSeeker( /*lineNode*/ )
{
function charCoords(tnode, i) { function charCoords(tnode, i)
{
var container = tnode.parentNode; var container = tnode.parentNode;
// treat space specially; a space at the end of a virtual line // treat space specially; a space at the end of a virtual line
// will have weird coordinates // will have weird coordinates
var isSpace = (tnode.nodeValue.charAt(i) === " "); var isSpace = (tnode.nodeValue.charAt(i) === " ");
if (isSpace) { if (isSpace)
if (i == 0) { {
if (container.previousSibling && deepFirstChildTextNode(container.previousSibling)) { if (i == 0)
{
if (container.previousSibling && deepFirstChildTextNode(container.previousSibling))
{
tnode = deepFirstChildTextNode(container.previousSibling); tnode = deepFirstChildTextNode(container.previousSibling);
i = tnode.length-1; i = tnode.length - 1;
container = tnode.parentNode; container = tnode.parentNode;
} }
else { else
return {top:container.offsetTop, left:container.offsetLeft}; {
return {
top: container.offsetTop,
left: container.offsetLeft
};
} }
} }
else { else
{
i--; // use previous char i--; // use previous char
} }
} }
@ -119,12 +160,14 @@ function makeVirtualLineView(lineNode) {
frag.appendChild(document.createTextNode(tnodeText.substring(0, i))); frag.appendChild(document.createTextNode(tnodeText.substring(0, i)));
charWrapper.appendChild(document.createTextNode(tnodeText.substr(i, 1))); charWrapper.appendChild(document.createTextNode(tnodeText.substr(i, 1)));
frag.appendChild(charWrapper); frag.appendChild(charWrapper);
frag.appendChild(document.createTextNode(tnodeText.substring(i+1))); frag.appendChild(document.createTextNode(tnodeText.substring(i + 1)));
container.replaceChild(frag, tnode); container.replaceChild(frag, tnode);
var result = {top:charWrapper.offsetTop, var result = {
left:charWrapper.offsetLeft + (isSpace ? charWrapper.offsetWidth : 0), top: charWrapper.offsetTop,
height:charWrapper.offsetHeight}; left: charWrapper.offsetLeft + (isSpace ? charWrapper.offsetWidth : 0),
height: charWrapper.offsetHeight
};
while (container.firstChild) container.removeChild(container.firstChild); while (container.firstChild) container.removeChild(container.firstChild);
container.appendChild(tnode); container.appendChild(tnode);
@ -143,34 +186,41 @@ function makeVirtualLineView(lineNode) {
var approxLineHeight; var approxLineHeight;
var whichLine = 0; var whichLine = 0;
function nextNode() { function nextNode()
{
var n = curNode; var n = curNode;
if (! n) n = lineNode.firstChild; if (!n) n = lineNode.firstChild;
else n = n.nextSibling; else n = n.nextSibling;
while (n && ! deepFirstChildTextNode(n)) { while (n && !deepFirstChildTextNode(n))
{
n = n.nextSibling; n = n.nextSibling;
} }
return n; return n;
} }
function prevNode() {
function prevNode()
{
var n = curNode; var n = curNode;
if (! n) n = lineNode.lastChild; if (!n) n = lineNode.lastChild;
else n = n.previousSibling; else n = n.previousSibling;
while (n && ! deepFirstChildTextNode(n)) { while (n && !deepFirstChildTextNode(n))
{
n = n.previousSibling; n = n.previousSibling;
} }
return n; return n;
} }
var seeker; var seeker;
if (lineLength > 0) { if (lineLength > 0)
{
curNode = nextNode(); curNode = nextNode();
var firstCharData = charCoords(deepFirstChildTextNode(curNode), 0); var firstCharData = charCoords(deepFirstChildTextNode(curNode), 0);
approxLineHeight = firstCharData.height; approxLineHeight = firstCharData.height;
curTop = firstCharData.top; curTop = firstCharData.top;
curLeft = firstCharData.left; curLeft = firstCharData.left;
function updateCharData(tnode, i) { function updateCharData(tnode, i)
{
var coords = charCoords(tnode, i); var coords = charCoords(tnode, i);
whichLine += Math.round((coords.top - curTop) / approxLineHeight); whichLine += Math.round((coords.top - curTop) / approxLineHeight);
curTop = coords.top; curTop = coords.top;
@ -178,23 +228,26 @@ function makeVirtualLineView(lineNode) {
} }
seeker = { seeker = {
forward: function(numChars) { forward: function(numChars)
{
var oldChar = curChar; var oldChar = curChar;
var newChar = curChar + numChars; var newChar = curChar + numChars;
if (newChar > (lineLength-1)) if (newChar > (lineLength - 1)) newChar = lineLength - 1;
newChar = lineLength-1; while (curChar < newChar)
while (curChar < newChar) { {
var curNodeLength = deepFirstChildTextNode(curNode).length; var curNodeLength = deepFirstChildTextNode(curNode).length;
var toGo = curNodeLength - curCharWithinNode; var toGo = curNodeLength - curCharWithinNode;
if (curChar + toGo > newChar || ! nextNode()) { if (curChar + toGo > newChar || !nextNode())
{
// going to next node would be too far // going to next node would be too far
var n = newChar - curChar; var n = newChar - curChar;
if (n >= toGo) n = toGo-1; if (n >= toGo) n = toGo - 1;
curChar += n; curChar += n;
curCharWithinNode += n; curCharWithinNode += n;
break; break;
} }
else { else
{
// go to next node // go to next node
curChar += toGo; curChar += toGo;
curCharWithinNode = 0; curCharWithinNode = 0;
@ -204,12 +257,15 @@ function makeVirtualLineView(lineNode) {
updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode); updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode);
return curChar - oldChar; return curChar - oldChar;
}, },
backward: function(numChars) { backward: function(numChars)
{
var oldChar = curChar; var oldChar = curChar;
var newChar = curChar - numChars; var newChar = curChar - numChars;
if (newChar < 0) newChar = 0; if (newChar < 0) newChar = 0;
while (curChar > newChar) { while (curChar > newChar)
if (curChar - curCharWithinNode <= newChar || !prevNode()) { {
if (curChar - curCharWithinNode <= newChar || !prevNode())
{
// going to prev node would be too far // going to prev node would be too far
var n = curChar - newChar; var n = curChar - newChar;
if (n > curCharWithinNode) n = curCharWithinNode; if (n > curCharWithinNode) n = curCharWithinNode;
@ -217,43 +273,74 @@ function makeVirtualLineView(lineNode) {
curCharWithinNode -= n; curCharWithinNode -= n;
break; break;
} }
else { else
{
// go to prev node // go to prev node
curChar -= curCharWithinNode+1; curChar -= curCharWithinNode + 1;
curNode = prevNode(); curNode = prevNode();
curCharWithinNode = deepFirstChildTextNode(curNode).length-1; curCharWithinNode = deepFirstChildTextNode(curNode).length - 1;
} }
} }
updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode); updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode);
return oldChar - curChar; return oldChar - curChar;
}, },
getVirtualLine: function() { return whichLine; }, getVirtualLine: function()
getLeftCoord: function() { return curLeft; } {
return whichLine;
},
getLeftCoord: function()
{
return curLeft;
}
}; };
} }
else { else
{
curLeft = lineNode.offsetLeft; curLeft = lineNode.offsetLeft;
seeker = { forward: function(numChars) { return 0; }, seeker = {
backward: function(numChars) { return 0; }, forward: function(numChars)
getVirtualLine: function() { return 0; }, {
getLeftCoord: function() { return curLeft; } return 0;
},
backward: function(numChars)
{
return 0;
},
getVirtualLine: function()
{
return 0;
},
getLeftCoord: function()
{
return curLeft;
}
}; };
} }
seeker.getOffset = function() { return curChar; }; seeker.getOffset = function()
seeker.getLineLength = function() { return lineLength; }; {
seeker.toString = function() { return curChar;
return "seeker[curChar: "+curChar+"("+lineText.charAt(curChar)+"), left: "+seeker.getLeftCoord()+", vline: "+seeker.getVirtualLine()+"]"; };
seeker.getLineLength = function()
{
return lineLength;
};
seeker.toString = function()
{
return "seeker[curChar: " + curChar + "(" + lineText.charAt(curChar) + "), left: " + seeker.getLeftCoord() + ", vline: " + seeker.getVirtualLine() + "]";
}; };
function moveByWhile(isBackward, amount, optCondFunc, optCharLimit) { function moveByWhile(isBackward, amount, optCondFunc, optCharLimit)
{
var charsMovedLast = null; var charsMovedLast = null;
var hasCondFunc = ((typeof optCondFunc) == "function"); var hasCondFunc = ((typeof optCondFunc) == "function");
var condFunc = optCondFunc; var condFunc = optCondFunc;
var hasCharLimit = ((typeof optCharLimit) == "number"); var hasCharLimit = ((typeof optCharLimit) == "number");
var charLimit = optCharLimit; var charLimit = optCharLimit;
while (charsMovedLast !== 0 && ((! hasCondFunc) || condFunc())) { while (charsMovedLast !== 0 && ((!hasCondFunc) || condFunc()))
{
var toMove = amount; var toMove = amount;
if (hasCharLimit) { if (hasCharLimit)
{
var untilLimit = (isBackward ? curChar - charLimit : charLimit - curChar); var untilLimit = (isBackward ? curChar - charLimit : charLimit - curChar);
if (untilLimit < toMove) toMove = untilLimit; if (untilLimit < toMove) toMove = untilLimit;
} }
@ -262,17 +349,23 @@ function makeVirtualLineView(lineNode) {
} }
} }
seeker.forwardByWhile = function(amount, optCondFunc, optCharLimit) { seeker.forwardByWhile = function(amount, optCondFunc, optCharLimit)
{
moveByWhile(false, amount, optCondFunc, optCharLimit); moveByWhile(false, amount, optCondFunc, optCharLimit);
} }
seeker.backwardByWhile = function(amount, optCondFunc, optCharLimit) { seeker.backwardByWhile = function(amount, optCondFunc, optCharLimit)
{
moveByWhile(true, amount, optCondFunc, optCharLimit); moveByWhile(true, amount, optCondFunc, optCharLimit);
} }
seeker.binarySearch = function(condFunc) { seeker.binarySearch = function(condFunc)
{
// returns index of boundary between false chars and true chars; // returns index of boundary between false chars and true chars;
// positions seeker at first true char, or else last char // positions seeker at first true char, or else last char
var trueFunc = condFunc; var trueFunc = condFunc;
var falseFunc = function() { return ! condFunc(); }; var falseFunc = function()
{
return !condFunc();
};
seeker.forwardByWhile(20, falseFunc); seeker.forwardByWhile(20, falseFunc);
seeker.backwardByWhile(20, trueFunc); seeker.backwardByWhile(20, trueFunc);
seeker.forwardByWhile(10, falseFunc); seeker.forwardByWhile(10, falseFunc);