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: undefined
Ace2Editor.registry = {
nextId: 1
};
Ace2Editor.registry = { nextId: 1 };
function Ace2Editor() {
function Ace2Editor()
{
var thisFunctionsName = "Ace2Editor";
var ace2 = Ace2Editor;
var editor = {};
var info = { editor: editor, id: (ace2.registry.nextId++) };
var info = {
editor: editor,
id: (ace2.registry.nextId++)
};
var loaded = false;
var actionsPendingInit = [];
function pendingInit(func, optDoNow) {
return function() {
function pendingInit(func, optDoNow)
{
return function()
{
var that = this;
var args = arguments;
function action() {
function action()
{
func.apply(that, args);
}
if (optDoNow) {
if (optDoNow)
{
optDoNow.apply(that, args);
}
if (loaded) {
if (loaded)
{
action();
}
else {
else
{
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 = [];
@ -57,30 +73,69 @@ function Ace2Editor() {
ace2.registry[info.id] = info;
editor.importText = pendingInit(function(newCode, undoable) {
info.ace_importText(newCode, undoable); });
editor.importAText = pendingInit(function(newCode, apoolJsonObj, undoable) {
info.ace_importAText(newCode, apoolJsonObj, undoable); });
editor.exportText = function() {
editor.importText = pendingInit(function(newCode, undoable)
{
info.ace_importText(newCode, undoable);
});
editor.importAText = pendingInit(function(newCode, apoolJsonObj, undoable)
{
info.ace_importAText(newCode, apoolJsonObj, undoable);
});
editor.exportText = function()
{
if (!loaded) return "(awaiting init)\n";
return info.ace_exportText();
};
editor.getFrame = function() { return info.frame || null; };
editor.focus = pendingInit(function() { 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.getFrame = function()
{
return info.frame || null;
};
editor.focus = pendingInit(function()
{
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.getDebugProperty = function(prop) { return info.ace_getDebugProperty(prop); };
editor.setProperty = pendingInit(function(key, value)
{
info.ace_setProperty(key, value);
});
editor.getDebugProperty = function(prop)
{
return info.ace_getDebugProperty(prop);
};
editor.setBaseText = pendingInit(function(txt) { info.ace_setBaseText(txt); });
editor.setBaseAttributedText = pendingInit(function(atxt, apoolJsonObj) {
info.ace_setBaseAttributedText(atxt, apoolJsonObj); });
editor.applyChangesToBase = pendingInit(function (changes, optAuthor,apoolJsonObj) {
info.ace_applyChangesToBase(changes, optAuthor, apoolJsonObj); });
editor.setBaseText = pendingInit(function(txt)
{
info.ace_setBaseText(txt);
});
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:
// 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).
@ -90,36 +145,48 @@ function Ace2Editor() {
// to prepareUserChangeset will return an updated changeset that takes into account the
// latest user changes, and modify the changeset to be applied by applyPreparedChangesetToBase
// accordingly.
editor.prepareUserChangeset = function() {
editor.prepareUserChangeset = function()
{
if (!loaded) return null;
return info.ace_prepareUserChangeset();
};
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);
});
editor.setAuthorInfo = pendingInit(function(author, authorInfo) {
editor.setAuthorInfo = pendingInit(function(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);
});
editor.getUnhandledErrors = function() {
editor.getUnhandledErrors = function()
{
if (!loaded) return [];
// returns array of {error: <browser Error object>, time: +new Date()}
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);
});
editor.execCommand = pendingInit(function(cmd, arg1) {
editor.execCommand = pendingInit(function(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);
});
@ -127,49 +194,57 @@ function Ace2Editor() {
// 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
// source here.
var $$INCLUDE_CSS = function(fileName) {
var $$INCLUDE_CSS = function(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>';
};
var $$INCLUDE_JS_DEV = $$INCLUDE_JS;
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 + '"/>\'';
};
var $$INCLUDE_JS_Q = function(fileName) {
var $$INCLUDE_JS_Q = function(fileName)
{
return '\'\\x3cscript type="text/javascript" src="' + fileName + '">\\x3c/script>\'';
};
var $$INCLUDE_JS_Q_DEV = $$INCLUDE_JS_Q;
var $$INCLUDE_CSS_Q_DEV = $$INCLUDE_CSS_Q;
editor.destroy = pendingInit(function() {
editor.destroy = pendingInit(function()
{
info.ace_dispose();
info.frame.parentNode.removeChild(info.frame);
delete ace2.registry[info.id];
info = null; // prevent IE 6 closure memory leaks
});
editor.init = function(containerId, initialCode, doneFunc) {
editor.init = function(containerId, initialCode, doneFunc)
{
editor.importText(initialCode);
info.onEditorReady = function() {
info.onEditorReady = function()
{
loaded = true;
doActionsPendingInit();
doneFunc();
};
(function() {
(function()
{
var doctype = "<!doctype html>";
var iframeHTML = ["'" + doctype + "<html><head>'"];
plugins.callHook(
"aceInitInnerdocbodyHead", {iframeHTML:iframeHTML});
plugins.callHook("aceInitInnerdocbodyHead", {
iframeHTML: iframeHTML
});
// 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"));
@ -191,35 +266,22 @@ function Ace2Editor() {
iframeHTML.push('\'\\n<style type="text/css" title="dynamicsyntax"></style>\\n\'');
iframeHTML.push('\'</head><body id="innerdocbody" class="syntax" spellcheck="false">&nbsp;</body></html>\'');
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
'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 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
'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>',
$$INCLUDE_CSS("static/css/editor.css"),
var outerHTML = [doctype, '<html><head>', $$INCLUDE_CSS("static/css/editor.css"),
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
// (throbs busy while typing)
'<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>'];
'<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>'];
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();
var len = this.length;
var res = new Array(len);
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);
}
return res;

View File

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

@ -104,7 +104,6 @@ function loadBroadcastJS()
var userId = "hiddenUser" + randomString();
var socketId;
//var socket;
var channelState = "DISCONNECTED";
var appLevelDisconnectReason = null;
@ -319,6 +318,8 @@ function loadBroadcastJS()
return str;
}
var date = new Date(padContents.currentTime);
var dateFormat = function()
{
@ -333,6 +334,8 @@ function loadBroadcastJS()
$('#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(" ")
@ -464,7 +467,11 @@ function loadBroadcastJS()
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;
@ -708,6 +715,8 @@ function loadBroadcastJS()
BroadcastSlider.onSlider(goToRevisionIfEnabled);
(function()

View File

@ -16,7 +16,6 @@
// revision info is a skip list whos entries represent a particular revision
// of the document. These revisions are connected together by various
// changesets, or deltas, between any two revisions.
var global = this;
function loadBroadcastRevisionsJS()

View File

@ -48,6 +48,8 @@ function loadBroadcastSliderJS()
var updateSliderElements = function()
{
for (var i = 0; i < savedRevisions.length; i++)
@ -60,6 +62,8 @@ function loadBroadcastSliderJS()
var addSavedRevision = function(position, info)
{
var newSavedRevision = $('<div></div>');
@ -146,7 +150,6 @@ function loadBroadcastSliderJS()
// just take over the whole slider screen with a reconnect message
function showReconnectUI()
{
if (!clientVars.sliderEnabled || !clientVars.supportsSlider)

View File

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

View File

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

View File

@ -1,6 +1,5 @@
// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/colorutils.js
// THIS FILE IS ALSO SERVED AS CLIENT-SIDE JS
/**
* Copyright 2009 Google Inc.
*
@ -20,20 +19,23 @@
var colorutils = {};
// "#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);
function hexToFloat(hh) {
function hexToFloat(hh)
{
return Number("0x" + hh) / 255;
}
return [hexToFloat(sixHex.substr(0,2)),
hexToFloat(sixHex.substr(2,2)),
hexToFloat(sixHex.substr(4,2))];
return [hexToFloat(sixHex.substr(0, 2)), hexToFloat(sixHex.substr(2, 2)), hexToFloat(sixHex.substr(4, 2))];
}
// "#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];
if (h.length != 6) {
if (h.length != 6)
{
var a = h.charAt(0);
var b = h.charAt(1);
var c = h.charAt(2);
@ -43,50 +45,71 @@ colorutils.css2sixhex = function(cssColor) {
}
// [1.0, 1.0, 1.0] -> "#ffffff"
colorutils.triple2css = function(triple) {
function floatToHex(n) {
colorutils.triple2css = function(triple)
{
function floatToHex(n)
{
var n2 = colorutils.clamp(Math.round(n * 255), 0, 255);
return ("0" + n2.toString(16)).slice(-2);
}
return "#" + floatToHex(triple[0]) +
floatToHex(triple[1]) + floatToHex(triple[2]);
return "#" + floatToHex(triple[0]) + floatToHex(triple[1]) + floatToHex(triple[2]);
}
colorutils.clamp = function(v,bot,top) { return v < bot ? bot : (v > top ? top : v); };
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); };
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.clamp = function(v, bot, top)
{
return v < bot ? bot : (v > top ? top : v);
};
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);
};
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) {
return [colorutils.scale(c[0], bot, top),
colorutils.scale(c[1], bot, top),
colorutils.scale(c[2], bot, top)];
colorutils.scaleColor = function(c, bot, top)
{
return [colorutils.scale(c[0], bot, top), colorutils.scale(c[1], bot, top), colorutils.scale(c[2], bot, top)];
}
colorutils.unscaleColor = function(c, bot, top) {
return [colorutils.unscale(c[0], bot, top),
colorutils.unscale(c[1], bot, top),
colorutils.unscale(c[2], bot, top)];
colorutils.unscaleColor = function(c, bot, top)
{
return [colorutils.unscale(c[0], bot, top), colorutils.unscale(c[1], 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
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 max = colorutils.colorMax(c);
if (max - min <= 0) return [1.0, 1.0, 1.0];
return colorutils.unscaleColor(c, min, max);
}
colorutils.blend = function(c1, c2, t) {
return [colorutils.scale(t, c1[0], c2[0]),
colorutils.scale(t, c1[1], c2[1]),
colorutils.scale(t, c1[2], c2[2])];
colorutils.blend = function(c1, c2, t)
{
return [colorutils.scale(t, c1[0], c2[0]), colorutils.scale(t, c1[1], c2[1]), colorutils.scale(t, c1[2], c2[2])];
}

View File

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

View File

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

View File

@ -46,7 +46,6 @@ function makeCSSManager(emptyStylesheetTitle)
var browserSheet = getSheetByTitle(emptyStylesheetTitle);
//var browserTag = getSheetTagByTitle(emptyStylesheetTitle);
function browserRules()
{
return (browserSheet.cssRules || browserSheet.rules);

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -580,7 +580,6 @@ if (_opt)
var assem = _opt.opAssembler();
// this function allows op to be mutated later (doesn't keep a ref)
function append(op)
{
assem.append(op.opcode, op.chars, op.lines, op.attribs);
@ -609,7 +608,6 @@ else
var pieces = [];
// this function allows op to be mutated later (doesn't keep a ref)
function append(op)
{
pieces.push(op.attribs);
@ -743,7 +741,6 @@ Changeset.textLinesMutator = function (lines)
}
// can be unimplemented if removeLines's return value not needed
function lines_slice(start, end)
{
if (lines.slice)
@ -2041,7 +2038,6 @@ Changeset.inverse = function (cs, lines, alines, pool)
// They may be arrays or objects with .get(i) and .length methods.
// They include final newlines on lines.
function lines_get(idx)
{
if (lines.get)

View File

@ -158,45 +158,40 @@
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
var JSON;
if (!JSON) {
if (!JSON)
{
JSON = {};
}
(function () {
(function()
{
"use strict";
function f(n) {
function f(n)
{
// Format integers to have at least two digits.
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()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
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;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function(key)
{
return this.valueOf();
};
}
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,
gap,
indent,
meta = { // table of character substitutions
gap, indent, meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
@ -208,58 +203,54 @@ if (!JSON) {
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.
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];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
function str(key, holder)
{
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
length, mind = gap,
partial, value = holder[key];
// 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);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
if (typeof rep === 'function')
{
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
switch (typeof value)
{
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
@ -268,69 +259,70 @@ if (!JSON) {
// 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
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
if (!value)
{
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// 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.
length = value.length;
for (i = 0; i < length; i += 1) {
for (i = 0; i < length; i += 1)
{
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// 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;
return v;
}
// 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;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
for (i = 0; i < length; i += 1)
{
if (typeof rep[i] === 'string')
{
k = rep[i];
v = str(k, value);
if (v) {
if (v)
{
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
}
else
{
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
for (k in value)
{
if (Object.prototype.hasOwnProperty.call(value, k))
{
v = str(k, value);
if (v) {
if (v)
{
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
@ -339,85 +331,89 @@ if (!JSON) {
// Join all of the member texts together, separated with commas,
// 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;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
if (typeof JSON.stringify !== 'function')
{
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.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
if (typeof space === 'number')
{
for (i = 0; i < space; i += 1)
{
indent += ' ';
}
// 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;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number'))
{
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
return str('', {
'': value
});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
if (typeof JSON.parse !== 'function')
{
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.
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.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
if (value && typeof value === 'object')
{
for (k in value)
{
if (Object.prototype.hasOwnProperty.call(value, k))
{
v = walk(value, k);
if (v !== undefined) {
if (v !== undefined)
{
value[k] = v;
} else {
}
else
{
delete value[k];
}
}
@ -430,13 +426,13 @@ if (!JSON) {
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
if (cx.test(text))
{
text = text.replace(cx, function(a)
{
return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
@ -444,7 +440,6 @@ if (!JSON) {
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// 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
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
@ -452,28 +447,24 @@ if (!JSON) {
// 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
// ',' 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.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
return typeof reviver === 'function' ? walk(
{
'': j
}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}

View File

@ -1,7 +1,6 @@
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.linestylefilter
// %APPJET%: import("etherpad.collab.ace.easysync2.Changeset");
// %APPJET%: import("etherpad.admin.plugins");
/**
* Copyright 2009 Google Inc.
*
@ -22,7 +21,6 @@
// requires: top
// requires: plugins
// requires: undefined
var linestylefilter = {};
linestylefilter.ATTRIB_CLASSES = {
@ -32,8 +30,10 @@ linestylefilter.ATTRIB_CLASSES = {
'strikethrough': 'tag:s'
};
linestylefilter.getAuthorClassName = function(author) {
return "author-"+author.replace(/[^a-y0-9]/g, function(c) {
linestylefilter.getAuthorClassName = function(author)
{
return "author-" + author.replace(/[^a-y0-9]/g, function(c)
{
if (c == ".") return "-";
return 'z' + c.charCodeAt(0) + 'z';
});
@ -41,13 +41,16 @@ linestylefilter.getAuthorClassName = function(author) {
// lineLength is without newline; aline includes newline,
// but may be falsy if lineLength == 0
linestylefilter.getLineStyleFilter = function(lineLength, aline,
textAndClassFunc, apool) {
linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFunc, apool)
{
var plugins_;
if (typeof(plugins)!='undefined') {
if (typeof(plugins) != 'undefined')
{
plugins_ = plugins;
} else {
}
else
{
plugins_ = parent.parent.plugins;
}
@ -55,29 +58,43 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline,
var nextAfterAuthorColors = textAndClassFunc;
var authorColorFunc = (function() {
var authorColorFunc = (function()
{
var lineEnd = lineLength;
var curIndex = 0;
var extraClasses;
var leftInAuthor;
function attribsToClasses(attribs) {
function attribsToClasses(attribs)
{
var classes = '';
Changeset.eachAttribNumber(attribs, function(n) {
Changeset.eachAttribNumber(attribs, function(n)
{
var key = apool.getAttribKey(n);
if (key) {
if (key)
{
var value = apool.getAttribValue(n);
if (value) {
if (key == 'author') {
if (value)
{
if (key == 'author')
{
classes += ' ' + linestylefilter.getAuthorClassName(value);
}
else if (key == 'list') {
else if (key == 'list')
{
classes += ' list:' + value;
}
else if (linestylefilter.ATTRIB_CLASSES[key]) {
else if (linestylefilter.ATTRIB_CLASSES[key])
{
classes += ' ' + linestylefilter.ATTRIB_CLASSES[key];
} else {
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 nextOp, nextOpClasses;
function goNextOp() {
function goNextOp()
{
nextOp = attributionIter.next();
nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs));
}
goNextOp();
function nextClasses() {
if (curIndex < lineEnd) {
function nextClasses()
{
if (curIndex < lineEnd)
{
extraClasses = nextOpClasses;
leftInAuthor = nextOp.chars;
goNextOp();
while (nextOp.opcode && nextOpClasses == extraClasses) {
while (nextOp.opcode && nextOpClasses == extraClasses)
{
leftInAuthor += nextOp.chars;
goNextOp();
}
@ -105,14 +128,18 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline,
}
nextClasses();
return function(txt, cls) {
while (txt.length > 0) {
if (leftInAuthor <= 0) {
return function(txt, cls)
{
while (txt.length > 0)
{
if (leftInAuthor <= 0)
{
// prevent infinite loop if something funny's going on
return nextAfterAuthorColors(txt, cls);
}
var spanSize = txt.length;
if (spanSize > leftInAuthor) {
if (spanSize > leftInAuthor)
{
spanSize = leftInAuthor;
}
var curTxt = txt.substring(0, spanSize);
@ -120,7 +147,8 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline,
nextAfterAuthorColors(curTxt, (cls && cls + " ") + extraClasses);
curIndex += spanSize;
leftInAuthor -= spanSize;
if (leftInAuthor == 0) {
if (leftInAuthor == 0)
{
nextClasses();
}
}
@ -129,14 +157,16 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline,
return authorColorFunc;
};
linestylefilter.getAtSignSplitterFilter = function(lineText,
textAndClassFunc) {
linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc)
{
var at = /@/g;
at.lastIndex = 0;
var splitPoints = null;
var execResult;
while ((execResult = at.exec(lineText))) {
if (! splitPoints) {
while ((execResult = at.exec(lineText)))
{
if (!splitPoints)
{
splitPoints = [];
}
splitPoints.push(execResult.index);
@ -144,18 +174,21 @@ linestylefilter.getAtSignSplitterFilter = function(lineText,
if (!splitPoints) return textAndClassFunc;
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc,
splitPoints);
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
};
linestylefilter.getRegexpFilter = function (regExp, tag) {
return function (lineText, textAndClassFunc) {
linestylefilter.getRegexpFilter = function(regExp, tag)
{
return function(lineText, textAndClassFunc)
{
regExp.lastIndex = 0;
var regExpMatchs = null;
var splitPoints = null;
var execResult;
while ((execResult = regExp.exec(lineText))) {
if (! regExpMatchs) {
while ((execResult = regExp.exec(lineText)))
{
if (!regExpMatchs)
{
regExpMatchs = [];
splitPoints = [];
}
@ -167,23 +200,29 @@ linestylefilter.getRegexpFilter = function (regExp, tag) {
if (!regExpMatchs) return textAndClassFunc;
function regExpMatchForIndex(idx) {
for(var k=0; k<regExpMatchs.length; k++) {
function regExpMatchForIndex(idx)
{
for (var k = 0; k < regExpMatchs.length; k++)
{
var u = regExpMatchs[k];
if (idx >= u[0] && idx < u[0]+u[1].length) {
if (idx >= u[0] && idx < u[0] + u[1].length)
{
return u[1];
}
}
return false;
}
var handleRegExpMatchsAfterSplit = (function() {
var handleRegExpMatchsAfterSplit = (function()
{
var curIndex = 0;
return function(txt, cls) {
return function(txt, cls)
{
var txtlen = txt.length;
var newCls = cls;
var regExpMatch = regExpMatchForIndex(curIndex);
if (regExpMatch) {
if (regExpMatch)
{
newCls += " " + tag + ":" + regExpMatch;
}
textAndClassFunc(txt, newCls);
@ -191,8 +230,7 @@ linestylefilter.getRegexpFilter = function (regExp, tag) {
};
})();
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit,
splitPoints);
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, splitPoints);
};
};
@ -203,35 +241,42 @@ linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(
linestylefilter.REGEX_URL, 'url');
linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt) {
linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt)
{
var nextPointIndex = 0;
var idx = 0;
// don't split at 0
while (splitPointsOpt &&
nextPointIndex < splitPointsOpt.length &&
splitPointsOpt[nextPointIndex] == 0) {
while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0)
{
nextPointIndex++;
}
function spanHandler(txt, cls) {
if ((! splitPointsOpt) || nextPointIndex >= splitPointsOpt.length) {
function spanHandler(txt, cls)
{
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length)
{
func(txt, cls);
idx += txt.length;
}
else {
else
{
var splitPoints = splitPointsOpt;
var pointLocInSpan = splitPoints[nextPointIndex] - idx;
var txtlen = txt.length;
if (pointLocInSpan >= txtlen) {
if (pointLocInSpan >= txtlen)
{
func(txt, cls);
idx += txt.length;
if (pointLocInSpan == txtlen) {
if (pointLocInSpan == txtlen)
{
nextPointIndex++;
}
}
else {
if (pointLocInSpan > 0) {
else
{
if (pointLocInSpan > 0)
{
func(txt.substring(0, pointLocInSpan), cls);
idx += pointLocInSpan;
}
@ -244,23 +289,31 @@ linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt) {
return spanHandler;
};
linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser) {
linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser)
{
var func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
var plugins_;
if (typeof(plugins)!='undefined') {
if (typeof(plugins) != 'undefined')
{
plugins_ = plugins;
} else {
}
else
{
plugins_ = parent.parent.plugins;
}
var hookFilters = plugins_.callHook(
"aceGetFilterStack", {linestylefilter:linestylefilter, browser:browser});
hookFilters.map(function (hookFilter) {
var hookFilters = plugins_.callHook("aceGetFilterStack", {
linestylefilter: linestylefilter,
browser: browser
});
hookFilters.map(function(hookFilter)
{
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.
// We then normalize it back to text with no angle brackets. It's weird. So always
// break spans at an "at" sign.
@ -271,20 +324,21 @@ linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser) {
};
// 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
var text = textLine;
if (text.slice(-1) == '\n') {
if (text.slice(-1) == '\n')
{
text = text.substring(0, text.length - 1);
}
function textAndClassFunc(tokenText, tokenClass) {
function textAndClassFunc(tokenText, tokenClass)
{
domLineObj.appendSpan(tokenText, tokenClass);
}
var func = linestylefilter.getFilterStack(text, textAndClassFunc);
func = linestylefilter.getLineStyleFilter(text.length, aline,
func, apool);
func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
func(text, '');
};

View File

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

View File

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

View File

@ -15,49 +15,61 @@
*/
var padcookie = (function(){
function getRawCookie() {
var padcookie = (function()
{
function getRawCookie()
{
// returns null if can't get cookie text
if (! document.cookie) {
if (!document.cookie)
{
return null;
}
// look for (start of string OR semicolon) followed by whitespace followed by prefs=(something);
var regexResult = document.cookie.match(/(?:^|;)\s*prefs=([^;]*)(?:;|$)/);
if ((! regexResult) || (! regexResult[1])) {
if ((!regexResult) || (!regexResult[1]))
{
return null;
}
return regexResult[1];
}
function setRawCookie(safeText) {
function setRawCookie(safeText)
{
var expiresDate = new Date();
expiresDate.setFullYear(3000);
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));
return cookieData;
}
catch (e) {
catch (e)
{
return null;
}
}
function stringifyCookie(data) {
function stringifyCookie(data)
{
return escape(JSON.stringify(data));
}
function saveCookie() {
if (! inited) {
function saveCookie()
{
if (!inited)
{
return;
}
setRawCookie(stringifyCookie(cookieData));
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"+
" 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.");
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" + " 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;
}
}
@ -68,11 +80,14 @@ var padcookie = (function(){
var inited = false;
var self = {
init: function(prefsToSet) {
init: function(prefsToSet)
{
var rawCookie = getRawCookie();
if (rawCookie) {
if (rawCookie)
{
var cookieObj = parseCookie(rawCookie);
if (cookieObj) {
if (cookieObj)
{
wasNoCookie = false; // there was a cookie
delete cookieObj.userId;
delete cookieObj.name;
@ -81,18 +96,24 @@ var padcookie = (function(){
}
}
for(var k in prefsToSet) {
for (var k in prefsToSet)
{
cookieData[k] = prefsToSet[k];
}
inited = true;
saveCookie();
},
wasNoCookie: function() { return wasNoCookie; },
getPref: function(prefName) {
wasNoCookie: function()
{
return wasNoCookie;
},
getPref: function(prefName)
{
return cookieData[prefName];
},
setPref: function(prefName, value) {
setPref: function(prefName, value)
{
cookieData[prefName] = value;
saveCookie();
}

View File

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

View File

@ -15,9 +15,11 @@
*/
var padeditbar = (function(){
var padeditbar = (function()
{
var syncAnimation = (function() {
var syncAnimation = (function()
{
var SYNCING = -100;
var DONE = 100;
var state = DONE;
@ -26,39 +28,48 @@ var padeditbar = (function(){
var T_START = -0.5;
var T_FADE = 1.0;
var T_GONE = 1.5;
var animator = padutils.makeAnimationScheduler(function() {
if (state == SYNCING || state == DONE) {
var animator = padutils.makeAnimationScheduler(function()
{
if (state == SYNCING || state == DONE)
{
return false;
}
else if (state >= T_GONE) {
else if (state >= T_GONE)
{
state = DONE;
$("#syncstatussyncing").css('display', 'none');
$("#syncstatusdone").css('display', 'none');
return false;
}
else if (state < 0) {
else if (state < 0)
{
state += step;
if (state >= 0) {
if (state >= 0)
{
$("#syncstatussyncing").css('display', 'none');
$("#syncstatusdone").css('display', 'block').css('opacity', 1);
}
return true;
}
else {
else
{
state += step;
if (state >= T_FADE) {
if (state >= T_FADE)
{
$("#syncstatusdone").css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
}
return true;
}
}, step * 1000);
return {
syncing: function() {
syncing: function()
{
state = SYNCING;
$("#syncstatussyncing").css('display', 'block');
$("#syncstatusdone").css('display', 'none');
},
done: function() {
done: function()
{
state = T_START;
animator.scheduleAnimation();
}
@ -66,23 +77,30 @@ var padeditbar = (function(){
}());
var self = {
init: function() {
init: function()
{
$("#editbar .editbarbutton").attr("unselectable", "on"); // for IE
$("#editbar").removeClass("disabledtoolbar").addClass("enabledtoolbar");
},
isEnabled: function() {
isEnabled: function()
{
return !$("#editbar").hasClass('disabledtoolbar');
},
disable: function() {
disable: function()
{
$("#editbar").addClass('disabledtoolbar').removeClass("enabledtoolbar");
},
toolbarClick: function(cmd) {
if (self.isEnabled()) {
if (cmd == 'showusers') {
toolbarClick: function(cmd)
{
if (self.isEnabled())
{
if (cmd == 'showusers')
{
// show users shows the current users on teh pad
// get current height
var editbarheight = $('#users').css('display');
if (editbarheight == "none"){
if (editbarheight == "none")
{
// increase the size of the editbar
//$('#editbar').animate({height:'72px'});
//$('#editorcontainerbox').animate({top:'72px'});
@ -97,14 +115,19 @@ var padeditbar = (function(){
$('#users').slideUp("fast");
}
}
if (cmd == 'embed') {
if (cmd == 'embed')
{
// embed shows the embed link
// get current height
var editbarheight = $('#embed').css('display');
if (editbarheight == "none"){
if (editbarheight == "none")
{
// increase the size of the editbar
//$('#editbar').animate({height:'72px'});
$('#editorcontainerbox').animate({top:'72px'});
$('#editorcontainerbox').animate(
{
top: '72px'
});
// get the pad url
padurl = document.location;
// change the div contents to include the pad url in an input box
@ -116,33 +139,48 @@ var padeditbar = (function(){
{
// increase the size of the editbar
//$('#editbar').animate({height:'36px'});
$('#editorcontainerbox').animate({top:'36px'});
$('#editorcontainerbox').animate(
{
top: '36px'
});
$('#embed').hide();
}
}
if (cmd == 'save') {
if (cmd == 'save')
{
padsavedrevs.saveNow();
} else {
padeditor.ace.callWithAce(function (ace) {
if (cmd == 'bold' || cmd == 'italic' || cmd == 'underline' || cmd == 'strikethrough')
ace.ace_toggleAttributeOnSelection(cmd);
else if (cmd == 'undo' || cmd == 'redo')
ace.ace_doUndoRedo(cmd);
else if (cmd == 'insertunorderedlist')
ace.ace_doInsertUnorderedList();
else if (cmd == 'indent') {
if (! ace.ace_doIndentOutdent(false)) {
}
else
{
padeditor.ace.callWithAce(function(ace)
{
if (cmd == 'bold' || cmd == 'italic' || cmd == 'underline' || cmd == 'strikethrough') ace.ace_toggleAttributeOnSelection(cmd);
else if (cmd == 'undo' || cmd == 'redo') ace.ace_doUndoRedo(cmd);
else if (cmd == 'insertunorderedlist') ace.ace_doInsertUnorderedList();
else if (cmd == 'indent')
{
if (!ace.ace_doIndentOutdent(false))
{
ace.ace_doInsertUnorderedList();
}
} else if (cmd == 'outdent') {
}
else if (cmd == 'outdent')
{
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', '');
}
}
@ -151,11 +189,14 @@ var padeditbar = (function(){
}
padeditor.ace.focus();
},
setSyncStatus: function(status) {
if (status == "syncing") {
setSyncStatus: function(status)
{
if (status == "syncing")
{
syncAnimation.syncing();
}
else if (status == "done") {
else if (status == "done")
{
syncAnimation.done();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -21,14 +21,17 @@ var colorPickerSetup = false;
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
// their insertion, removal, and reordering. It manipulates TD height
// and TD opacity.
function nextRowId() {
function nextRowId()
{
return "usertr" + (nextRowId.counter++);
}
nextRowId.counter = 1;
@ -36,10 +39,12 @@ var paduserlist = (function() {
var rowsFadingOut = []; // unordered set
var rowsFadingIn = []; // unordered set
var rowsPresent = []; // in order
var ANIMATION_START = -12; // just starting to fade in
var ANIMATION_END = 12; // just finishing fading out
function getAnimationHeight(step, power) {
function getAnimationHeight(step, power)
{
var a = Math.abs(step / 12);
if (power == 2) a = a * a;
else if (power == 3) a = a * a * a;
@ -51,87 +56,115 @@ var paduserlist = (function() {
var ANIMATION_STEP_TIME = 20;
var LOWER_FRAMERATE_FACTOR = 2;
var scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME,
LOWER_FRAMERATE_FACTOR).scheduleAnimation;
var scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR).scheduleAnimation;
var NUMCOLS = 4;
// we do lots of manipulation of table rows and stuff that JQuery makes ok, despite
// 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>';
}
function isNameEditable(data) {
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);
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
for(var i=0; i<tds.length; i++) {
for (var i = 0; i < tds.length; i++)
{
var oldTd = $(tr.find("td").get(i));
if (! oldTd.hasClass('usertdname')) {
if (!oldTd.hasClass('usertdname'))
{
oldTd.replaceWith(tds[i]);
}
}
}
else {
else
{
tr.html(tds.join(''));
}
return tr;
}
function getUserRowHtml(height, data) {
function getUserRowHtml(height, data)
{
var nameHtml;
var isGuest = (data.id.charAt(0) != 'p');
if (data.name) {
if (data.name)
{
nameHtml = padutils.escapeHtml(data.name);
if (isGuest && pad.getIsProPad()) {
if (isGuest && pad.getIsProPad())
{
nameHtml += ' (Guest)';
}
}
else {
nameHtml = '<input type="text" class="editempty newinput" value="unnamed" '+
(isNameEditable(data) ? '' : 'disabled="disabled" ')+
'/>';
else
{
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>',
'<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('');
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('');
}
function getRowHtml(id, innerHtml) {
function getRowHtml(id, innerHtml)
{
return '<tr id="' + id + '">' + innerHtml + '</tr>';
}
function rowNode(row) {
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;
}
else {
else
{
delete row.opacity;
}
}
function handleRowNode(tr, data) {
if (data.titleText) {
function handleRowNode(tr, data)
{
if (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');
}
}
function handleOtherUserInputs() {
function handleOtherUserInputs()
{
// handle 'INPUT' elements for naming other unnamed users
$("#otheruserstable input.newinput").each(function() {
$("#otheruserstable input.newinput").each(function()
{
var input = $(this);
var tr = input.closest("tr");
if (tr.length > 0) {
if (tr.length > 0)
{
var index = tr.parent().children().index(tr);
if (index >= 0) {
if (index >= 0)
{
var userId = rowsPresent[index].data.id;
rowManagerMakeNameEditor($(this), userId);
}
@ -140,33 +173,45 @@ var paduserlist = (function() {
}
// 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));
animationPower = (animationPower === undefined ? 4 : animationPower);
var domId = nextRowId();
var row = {data: data, animationStep: ANIMATION_START, domId: domId,
animationPower: animationPower};
var row = {
data: data,
animationStep: ANIMATION_START,
domId: domId,
animationPower: animationPower
};
handleRowData(row);
rowsPresent.splice(position, 0, row);
var tr;
if (animationPower == 0) {
if (animationPower == 0)
{
tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data)));
row.animationStep = 0;
}
else {
else
{
rowsFadingIn.push(row);
tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START))));
}
handleRowNode(tr, data);
if (position == 0) {
if (position == 0)
{
$("table#otheruserstable").prepend(tr);
}
else {
else
{
rowNode(rowsPresent[position - 1]).after(tr);
}
if (animationPower != 0) {
if (animationPower != 0)
{
scheduleAnimation();
}
@ -175,31 +220,37 @@ var paduserlist = (function() {
return row;
}
function updateRow(position, data) {
function updateRow(position, data)
{
var row = rowsPresent[position];
if (row) {
if (row)
{
row.data = data;
handleRowData(row);
if (row.animationStep == 0) {
if (row.animationStep == 0)
{
// not currently animating
var tr = rowNode(row);
replaceUserRowContents(tr, getAnimationHeight(0), row.data).find(
"td").css('opacity', (row.opacity === undefined ? 1 : row.opacity));
replaceUserRowContents(tr, getAnimationHeight(0), row.data).find("td").css('opacity', (row.opacity === undefined ? 1 : row.opacity));
handleRowNode(tr, data);
handleOtherUserInputs();
}
}
}
function removeRow(position, animationPower) {
function removeRow(position, animationPower)
{
animationPower = (animationPower === undefined ? 4 : animationPower);
var row = rowsPresent[position];
if (row) {
if (row)
{
rowsPresent.splice(position, 1); // remove
if (animationPower == 0) {
if (animationPower == 0)
{
rowNode(row).remove();
}
else {
else
{
row.animationStep = -row.animationStep; // use symmetry
row.animationPower = animationPower;
rowsFadingOut.push(row);
@ -209,59 +260,72 @@ var paduserlist = (function() {
}
// 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
var row = rowsPresent[oldPosition];
if (row && oldPosition != newPosition) {
if (row && oldPosition != newPosition)
{
var rowData = row.data;
removeRow(oldPosition, animationPower);
insertRow(newPosition, rowData, animationPower);
}
}
function animateStep() {
function animateStep()
{
// 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 step = ++row.animationStep;
var animHeight = getAnimationHeight(step, row.animationPower);
var node = rowNode(row);
var baseOpacity = (row.opacity === undefined ? 1 : row.opacity);
if (step <= -OPACITY_STEPS) {
if (step <= -OPACITY_STEPS)
{
node.find("td").height(animHeight);
}
else if (step == -OPACITY_STEPS+1) {
node.html(getUserRowHtml(animHeight, row.data)).find("td").css(
'opacity', baseOpacity*1/OPACITY_STEPS);
else if (step == -OPACITY_STEPS + 1)
{
node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1 / OPACITY_STEPS);
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);
}
else if (step == 0) {
else if (step == 0)
{
// set HTML in case modified during animation
node.html(getUserRowHtml(animHeight, row.data)).find("td").css(
'opacity', baseOpacity*1).height(animHeight);
node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1).height(animHeight);
handleRowNode(node, row.data);
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 step = ++row.animationStep;
var node = rowNode(row);
var animHeight = getAnimationHeight(step, row.animationPower);
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);
}
else if (step == OPACITY_STEPS) {
else if (step == OPACITY_STEPS)
{
node.html(getEmptyRowHtml(animHeight));
}
else if (step <= ANIMATION_END) {
else if (step <= ANIMATION_END)
{
node.find("td").height(animHeight);
}
else {
else
{
rowsFadingOut.splice(i, 1); // remove from set
node.remove();
}
@ -280,35 +344,44 @@ var paduserlist = (function() {
};
return self;
}()); ////////// rowManager
var otherUsersInfo = [];
var otherUsersData = [];
function rowManagerMakeNameEditor(jnode, userId) {
setUpEditable(jnode, function() {
function rowManagerMakeNameEditor(jnode, userId)
{
setUpEditable(jnode, function()
{
var existingIndex = findExistingIndex(userId);
if (existingIndex >= 0) {
if (existingIndex >= 0)
{
return otherUsersInfo[existingIndex].name || '';
}
else {
else
{
return '';
}
}, function(newName) {
if (! newName) {
}, function(newName)
{
if (!newName)
{
jnode.addClass("editempty");
jnode.val("unnamed");
}
else {
else
{
jnode.attr('disabled', 'disabled');
pad.suggestUserName(userId, newName);
}
});
}
function findExistingIndex(userId) {
function findExistingIndex(userId)
{
var existingIndex = -1;
for(var i=0;i<otherUsersInfo.length;i++) {
if (otherUsersInfo[i].userId == userId) {
for (var i = 0; i < otherUsersInfo.length; i++)
{
if (otherUsersInfo[i].userId == userId)
{
existingIndex = i;
break;
}
@ -316,32 +389,41 @@ var paduserlist = (function() {
return existingIndex;
}
function setUpEditable(jqueryNode, valueGetter, valueSetter) {
jqueryNode.bind('focus', function(evt) {
function setUpEditable(jqueryNode, valueGetter, valueSetter)
{
jqueryNode.bind('focus', function(evt)
{
var oldValue = valueGetter();
if (jqueryNode.val() !== oldValue) {
if (jqueryNode.val() !== oldValue)
{
jqueryNode.val(oldValue);
}
jqueryNode.addClass("editactive").removeClass("editempty");
});
jqueryNode.bind('blur', function(evt) {
jqueryNode.bind('blur', function(evt)
{
var newValue = jqueryNode.removeClass("editactive").val();
valueSetter(newValue);
});
padutils.bindEnterAndEscape(jqueryNode, function onEnter() {
padutils.bindEnterAndEscape(jqueryNode, function onEnter()
{
jqueryNode.blur();
}, function onEscape() {
}, function onEscape()
{
jqueryNode.val(valueGetter()).blur();
});
jqueryNode.removeAttr('disabled').addClass('editable');
}
function updateInviteNotice() {
if (otherUsersInfo.length == 0) {
function updateInviteNotice()
{
if (otherUsersInfo.length == 0)
{
$("#otheruserstable").hide();
$("#nootherusers").show();
}
else {
else
{
$("#nootherusers").hide();
$("#otheruserstable").show();
}
@ -350,17 +432,22 @@ var paduserlist = (function() {
var knocksToIgnore = {};
var guestPromptFlashState = 0;
var guestPromptFlash = padutils.makeAnimationScheduler(
function () {
function()
{
var prompts = $("#guestprompts .guestprompt");
if (prompts.length == 0) {
if (prompts.length == 0)
{
return false; // no more to do
}
guestPromptFlashState = 1 - guestPromptFlashState;
if (guestPromptFlashState) {
if (guestPromptFlashState)
{
prompts.css('background', '#ffa');
}
else {
else
{
prompts.css('background', '#ffe');
}
@ -368,23 +455,26 @@ var paduserlist = (function() {
}, 1000);
var self = {
init: function(myInitialUserInfo) {
init: function(myInitialUserInfo)
{
self.setMyUserInfo(myInitialUserInfo);
$("#otheruserstable tr").remove();
if (pad.getUserIsGuest()) {
if (pad.getUserIsGuest())
{
$("#myusernameedit").addClass('myusernameedithoverable');
setUpEditable($("#myusernameedit"),
function() {
setUpEditable($("#myusernameedit"), function()
{
return myUserInfo.name || '';
},
function(newValue) {
}, function(newValue)
{
myUserInfo.name = newValue;
pad.notifyChangeName(newValue);
// wrap with setTimeout to do later because we get
// a double "blur" fire in IE...
window.setTimeout(function() {
window.setTimeout(function()
{
self.renderMyUserInfo();
}, 0);
});
@ -392,26 +482,32 @@ var paduserlist = (function() {
// color picker
$("#myswatchbox").click(showColorPicker);
$("#mycolorpicker .pickerswatchouter").click(function() {
$("#mycolorpicker .pickerswatchouter").click(function()
{
$("#mycolorpicker .pickerswatchouter").removeClass('picked');
$(this).addClass('picked');
});
$("#mycolorpickersave").click(function() {
$("#mycolorpickersave").click(function()
{
closeColorPicker(true);
});
$("#mycolorpickercancel").click(function() {
$("#mycolorpickercancel").click(function()
{
closeColorPicker(false);
});
//
},
setMyUserInfo: function(info) {
myUserInfo = $.extend({}, info);
setMyUserInfo: function(info)
{
myUserInfo = $.extend(
{}, info);
self.renderMyUserInfo();
},
userJoinOrUpdate: function(info) {
if ((! info.userId) || (info.userId == myUserInfo.userId)) {
userJoinOrUpdate: function(info)
{
if ((!info.userId) || (info.userId == myUserInfo.userId))
{
// not sure how this would happen
return;
}
@ -428,11 +524,14 @@ var paduserlist = (function() {
var existingIndex = findExistingIndex(info.userId);
var numUsersBesides = otherUsersInfo.length;
if (existingIndex >= 0) {
if (existingIndex >= 0)
{
numUsersBesides--;
}
var newIndex = padutils.binarySearch(numUsersBesides, function(n) {
if (existingIndex >= 0 && n >= existingIndex) {
var newIndex = padutils.binarySearch(numUsersBesides, function(n)
{
if (existingIndex >= 0 && n >= existingIndex)
{
// pretend existingIndex isn't there
n++;
}
@ -441,18 +540,20 @@ var paduserlist = (function() {
var nameThis = (info.name || '').toLowerCase();
var idN = infoN.userId;
var idThis = info.userId;
return (nameN > nameThis) || (nameN == nameThis &&
idN > idThis);
return (nameN > nameThis) || (nameN == nameThis && idN > idThis);
});
if (existingIndex >= 0) {
if (existingIndex >= 0)
{
// update
if (existingIndex == newIndex) {
if (existingIndex == newIndex)
{
otherUsersInfo[existingIndex] = info;
otherUsersData[existingIndex] = userData;
rowManager.updateRow(existingIndex, userData);
}
else {
else
{
otherUsersInfo.splice(existingIndex, 1);
otherUsersData.splice(existingIndex, 1);
otherUsersInfo.splice(newIndex, 0, info);
@ -461,7 +562,8 @@ var paduserlist = (function() {
rowManager.moveRow(existingIndex, newIndex);
}
}
else {
else
{
otherUsersInfo.splice(newIndex, 0, info);
otherUsersData.splice(newIndex, 0, userData);
rowManager.insertRow(newIndex, userData);
@ -471,9 +573,11 @@ var paduserlist = (function() {
self.updateNumberOfOnlineUsers();
},
updateNumberOfOnlineUsers: function(){
updateNumberOfOnlineUsers: function()
{
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 == "")
{
online++;
@ -483,25 +587,30 @@ var paduserlist = (function() {
return online;
},
userLeave: function(info) {
userLeave: function(info)
{
var existingIndex = findExistingIndex(info.userId);
if (existingIndex >= 0) {
if (existingIndex >= 0)
{
var userData = otherUsersData[existingIndex];
userData.status = 'Disconnected';
rowManager.updateRow(existingIndex, userData);
if (userData.leaveTimer) {
if (userData.leaveTimer)
{
window.clearTimeout(userData.leaveTimer);
}
// set up a timer that will only fire if no leaves,
// joins, or updates happen for this user in the
// next N seconds, to remove the user from the list.
var thisUserId = info.userId;
var thisLeaveTimer = window.setTimeout(function() {
var thisLeaveTimer = window.setTimeout(function()
{
var newExistingIndex = findExistingIndex(thisUserId);
if (newExistingIndex >= 0) {
if (newExistingIndex >= 0)
{
var newUserData = otherUsersData[newExistingIndex];
if (newUserData.status == 'Disconnected' &&
newUserData.leaveTimer == thisLeaveTimer) {
if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer)
{
otherUsersInfo.splice(newExistingIndex, 1);
otherUsersData.splice(newExistingIndex, 1);
rowManager.removeRow(newExistingIndex);
@ -515,8 +624,10 @@ var paduserlist = (function() {
self.updateNumberOfOnlineUsers();
},
showGuestPrompt: function(userId, displayName) {
if (knocksToIgnore[userId]) {
showGuestPrompt: function(userId, displayName)
{
if (knocksToIgnore[userId])
{
return;
}
@ -526,35 +637,41 @@ var paduserlist = (function() {
padutils.cancelActions(actionName);
var box = $("#guestprompt-" + encodedUserId);
if (box.length == 0) {
if (box.length == 0)
{
// 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>');
$("#guestprompts").append(box);
}
else {
else
{
// update display name
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);
});
window.setTimeout(hideLater, 15000); // time-out with no knock
guestPromptFlash.scheduleAnimation();
},
removeGuestPrompt: function(userId) {
removeGuestPrompt: function(userId)
{
var box = $("#guestprompt-" + padutils.encodeUserId(userId));
// 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();
});
knocksToIgnore[userId] = true;
window.setTimeout(function() {
window.setTimeout(function()
{
delete knocksToIgnore[userId];
}, 5000);
},
answerGuestPrompt: function(encodedUserId, approve) {
answerGuestPrompt: function(encodedUserId, approve)
{
var guestId = padutils.decodeUserId(encodedUserId);
var msg = {
@ -567,22 +684,24 @@ var paduserlist = (function() {
self.removeGuestPrompt(guestId);
},
renderMyUserInfo: function() {
if (myUserInfo.name) {
renderMyUserInfo: function()
{
if (myUserInfo.name)
{
$("#myusernameedit").removeClass("editempty").val(
myUserInfo.name);
}
else {
$("#myusernameedit").addClass("editempty").val(
"Enter your name");
else
{
$("#myusernameedit").addClass("editempty").val("Enter your name");
}
if (colorPickerOpen) {
$("#myswatchbox").addClass('myswatchboxunhoverable').removeClass(
'myswatchboxhoverable');
if (colorPickerOpen)
{
$("#myswatchbox").addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable');
}
else {
$("#myswatchbox").addClass('myswatchboxhoverable').removeClass(
'myswatchboxunhoverable');
else
{
$("#myswatchbox").addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable');
}
$("#myswatch").css('background', pad.getColorPalette()[myUserInfo.colorId]);
}
@ -590,20 +709,27 @@ var paduserlist = (function() {
return self;
}());
function getColorPickerSwatchIndex(jnode) {
function getColorPickerSwatchIndex(jnode)
{
// return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1;
return $("#colorpickerswatches li").index(jnode);
}
function closeColorPicker(accept) {
if (accept) {
function closeColorPicker(accept)
{
if (accept)
{
var newColorId = getColorPickerSwatchIndex($("#colorpickerswatches .picked"));
if (newColorId >= 0) { // fails on NaN
if (newColorId >= 0)
{ // fails on NaN
myUserInfo.colorId = newColorId;
pad.notifyChangeColor(newColorId);
}
paduserlist.renderMyUserInfo();
} else {
}
else
{
pad.notifyChangeColor(previousColorId);
paduserlist.renderMyUserInfo();
}
@ -612,15 +738,19 @@ function closeColorPicker(accept) {
$("#mycolorpicker").fadeOut("fast");
}
function showColorPicker() {
function showColorPicker()
{
previousColorId = myUserInfo.colorId;
if (! colorPickerOpen) {
if (!colorPickerOpen)
{
var palette = pad.getColorPalette();
if(!colorPickerSetup) {
if (!colorPickerSetup)
{
var colorsList = $("#colorpickerswatches")
for(var i=0;i<palette.length;i++) {
for (var i = 0; i < palette.length; i++)
{
var li = $('<li>', {
style: 'background: ' + palette[i] + ';'
@ -628,7 +758,8 @@ function showColorPicker() {
li.appendTo(colorsList);
li.bind('click', function(event){
li.bind('click', function(event)
{
$("#colorpickerswatches li").removeClass('picked');
$(event.target).addClass("picked");

View File

@ -15,62 +15,78 @@
*/
var padutils = {
escapeHtml: function(x) {
escapeHtml: function(x)
{
return String(x).replace(/\</g, '&lt;').replace(/\>/g, '&gt;');
},
uniqueId: function() {
function encodeNum(n, width) {
uniqueId: function()
{
function encodeNum(n, width)
{
// returns string that is exactly 'width' chars, padding with zeros
// and taking rightmost digits
return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width);
}
return [pad.getClientIp(),
encodeNum(+new Date, 7),
encodeNum(Math.floor(Math.random()*1e9), 4)].join('.');
return [pad.getClientIp(), encodeNum(+new Date, 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.');
},
uaDisplay: function(ua) {
uaDisplay: function(ua)
{
var m;
function clean(a) {
function clean(a)
{
var maxlen = 16;
a = a.replace(/[^a-zA-Z0-9\.]/g, '');
if (a.length > maxlen) {
if (a.length > maxlen)
{
a = a.substr(0, maxlen);
}
return a;
}
function checkver(name) {
function checkver(name)
{
var m = ua.match(RegExp(name + '\\/([\\d\\.]+)'));
if (m && m.length > 1) {
if (m && m.length > 1)
{
return clean(name + m[1]);
}
return null;
}
// firefox
if (checkver('Firefox')) { return checkver('Firefox'); }
if (checkver('Firefox'))
{
return checkver('Firefox');
}
// misc browsers, including IE
m = ua.match(/compatible; ([^;]+);/);
if (m && m.length > 1) {
if (m && m.length > 1)
{
return clean(m[1]);
}
// iphone
if (ua.match(/\(iPhone;/)) {
if (ua.match(/\(iPhone;/))
{
return 'iPhone';
}
// chrome
if (checkver('Chrome')) { return checkver('Chrome'); }
if (checkver('Chrome'))
{
return checkver('Chrome');
}
// safari
m = ua.match(/Safari\/[\d\.]+/);
if (m) {
if (m)
{
var v = '?';
m = ua.match(/Version\/([\d\.]+)/);
if (m && m.length > 1) {
if (m && m.length > 1)
{
v = m[1];
}
return clean('Safari' + v);
@ -83,13 +99,15 @@ var padutils = {
// "func" is a function over 0..(numItems-1) that is monotonically
// "increasing" with index (false, then true). Finds the boundary
// 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 (func(0)) return 0;
if (!func(numItems - 1)) return numItems;
var low = 0; // func(low) is always false
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
if (func(x)) high = x;
else low = x;
@ -97,7 +115,8 @@ var padutils = {
return high;
},
// 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 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()];
@ -106,18 +125,23 @@ var padutils = {
var hourmin = d.getHours() + ":" + ("0" + d.getMinutes()).slice(-2);
return dayOfWeek + ' ' + month + ' ' + dayOfMonth + ' ' + year + ' ' + hourmin;
},
findURLs: function(text) {
findURLs: function(text)
{
// 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_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');
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
function _findURLs(text) {
function _findURLs(text)
{
_REGEX_URL.lastIndex = 0;
var urls = null;
var execResult;
while ((execResult = _REGEX_URL.exec(text))) {
while ((execResult = _REGEX_URL.exec(text)))
{
urls = (urls || []);
var startIndex = execResult.index;
var url = execResult[0];
@ -129,23 +153,28 @@ var padutils = {
return _findURLs(text);
},
escapeHtmlWithClickableLinks: function(text, target) {
escapeHtmlWithClickableLinks: function(text, target)
{
var idx = 0;
var pieces = [];
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)));
idx = i;
}
}
if (urls) {
for(var j=0;j<urls.length;j++) {
if (urls)
{
for (var j = 0; j < urls.length; j++)
{
var startIndex = urls[j][0];
var href = urls[j][1];
advanceTo(startIndex);
pieces.push('<a ', (target?'target="'+target+'" ':''),
'href="', href.replace(/\"/g, '&quot;'), '">');
pieces.push('<a ', (target ? 'target="' + target + '" ' : ''), 'href="', href.replace(/\"/g, '&quot;'), '">');
advanceTo(startIndex + href.length);
pieces.push('</a>');
}
@ -153,209 +182,280 @@ var padutils = {
advanceTo(text.length);
return pieces.join('');
},
bindEnterAndEscape: function(node, onEnter, onEscape) {
bindEnterAndEscape: function(node, onEnter, onEscape)
{
// Use keypress instead of keyup in bindEnterAndEscape
// Keyup event is fired on enter in IME (Input Method Editor), But
// 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).
if (onEnter) {
node.keypress( function(evt) {
if (evt.which == 13) {
if (onEnter)
{
node.keypress(function(evt)
{
if (evt.which == 13)
{
onEnter(evt);
}
});
}
if (onEscape) {
node.keydown( function(evt) {
if (evt.which == 27) {
if (onEscape)
{
node.keydown(function(evt)
{
if (evt.which == 27)
{
onEscape(evt);
}
});
}
},
timediff: function(d) {
function format(n, word) {
timediff: function(d)
{
function format(n, word)
{
n = Math.round(n);
return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago');
}
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;
if (d < 60) { return format(d, 'minute'); }
if (d < 60)
{
return format(d, 'minute');
}
d /= 60;
if (d < 24) { return format(d, 'hour'); }
if (d < 24)
{
return format(d, 'hour');
}
d /= 24;
return format(d, 'day');
},
makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce) {
if (stepsAtOnce === undefined) {
makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce)
{
if (stepsAtOnce === undefined)
{
stepsAtOnce = 1;
}
var animationTimer = null;
function scheduleAnimation() {
if (! animationTimer) {
animationTimer = window.setTimeout(function() {
function scheduleAnimation()
{
if (!animationTimer)
{
animationTimer = window.setTimeout(function()
{
animationTimer = null;
var n = stepsAtOnce;
var moreToDo = true;
while (moreToDo && n > 0) {
while (moreToDo && n > 0)
{
moreToDo = funcToAnimateOneStep();
n--;
}
if (moreToDo) {
if (moreToDo)
{
// more to do
scheduleAnimation();
}
}, 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 animationFrameDelay = 1000 / fps;
var animationStep = animationFrameDelay / totalMs;
var scheduleAnimation =
padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
var scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
function doShow() {
function doShow()
{
animationState = -1;
funcToArriveAtState(animationState);
scheduleAnimation();
}
function doQuickShow() { // start showing without losing any fade-in progress
if (animationState < -1) {
function doQuickShow()
{ // start showing without losing any fade-in progress
if (animationState < -1)
{
animationState = -1;
}
else if (animationState <= 0) {
else if (animationState <= 0)
{
animationState = animationState;
}
else {
else
{
animationState = Math.max(-1, Math.min(0, -animationState));
}
funcToArriveAtState(animationState);
scheduleAnimation();
}
function doHide() {
if (animationState >= -1 && animationState <= 0) {
function doHide()
{
if (animationState >= -1 && animationState <= 0)
{
animationState = 1e-6;
scheduleAnimation();
}
}
function animateOneStep() {
if (animationState < -1 || animationState == 0) {
function animateOneStep()
{
if (animationState < -1 || animationState == 0)
{
return false;
}
else if (animationState < 0) {
else if (animationState < 0)
{
// animate show
animationState += animationStep;
if (animationState >= 0) {
if (animationState >= 0)
{
animationState = 0;
funcToArriveAtState(animationState);
return false;
}
else {
else
{
funcToArriveAtState(animationState);
return true;
}
}
else if (animationState > 0) {
else if (animationState > 0)
{
// animate hide
animationState += animationStep;
if (animationState >= 1) {
if (animationState >= 1)
{
animationState = 1;
funcToArriveAtState(animationState);
animationState = -2;
return false;
}
else {
else
{
funcToArriveAtState(animationState);
return true;
}
}
}
return {show: doShow, hide: doHide, quickShow: doQuickShow};
return {
show: doShow,
hide: doHide,
quickShow: doQuickShow
};
},
_nextActionId: 1,
uncanceledActions: {},
getCancellableAction: function(actionType, actionFunc) {
getCancellableAction: function(actionType, actionFunc)
{
var o = padutils.uncanceledActions[actionType];
if (! o) {
if (!o)
{
o = {};
padutils.uncanceledActions[actionType] = o;
}
var actionId = (padutils._nextActionId++);
o[actionId] = true;
return function() {
return function()
{
var p = padutils.uncanceledActions[actionType];
if (p && p[actionId]) {
if (p && p[actionId])
{
actionFunc();
}
};
},
cancelActions: function(actionType) {
cancelActions: function(actionType)
{
var o = padutils.uncanceledActions[actionType];
if (o) {
if (o)
{
// clear it
delete padutils.uncanceledActions[actionType];
}
},
makeFieldLabeledWhenEmpty: function(field, labelText) {
makeFieldLabeledWhenEmpty: function(field, labelText)
{
field = $(field);
function clear() {
function clear()
{
field.addClass('editempty');
field.val(labelText);
}
field.focus(function() {
if (field.hasClass('editempty')) {
field.focus(function()
{
if (field.hasClass('editempty'))
{
field.val('');
}
field.removeClass('editempty');
});
field.blur(function() {
if (! field.val()) {
field.blur(function()
{
if (!field.val())
{
clear();
}
});
return {clear:clear};
return {
clear: clear
};
},
getCheckbox: function(node) {
getCheckbox: function(node)
{
return $(node).is(':checked');
},
setCheckbox: function(node, value) {
if (value) {
setCheckbox: function(node, value)
{
if (value)
{
$(node).attr('checked', 'checked');
}
else {
else
{
$(node).removeAttr('checked');
}
},
bindCheckboxChange: function(node, func) {
bindCheckboxChange: function(node, func)
{
$(node).bind("click change", func);
},
encodeUserId: function(userId) {
return userId.replace(/[^a-y0-9]/g, function(c) {
encodeUserId: function(userId)
{
return userId.replace(/[^a-y0-9]/g, function(c)
{
if (c == ".") return "-";
return 'z' + c.charCodeAt(0) + 'z';
});
},
decodeUserId: function(encodedUserId) {
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc) {
decodeUserId: function(encodedUserId)
{
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc)
{
if (cc == '-') return '.';
else if (cc.charAt(0) == 'z') {
else if (cc.charAt(0) == 'z')
{
return String.fromCharCode(Number(cc.slice(1, -1)));
}
else {
else
{
return cc;
}
});

View File

@ -1,22 +1,26 @@
plugins = {
callHook: function (hookName, args) {
callHook: function(hookName, args)
{
var hook = clientVars.hooks[hookName];
if (hook === undefined)
return [];
if (hook === undefined) return [];
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 pluginRes = eval(plugin.plugin)[plugin.original || hookName](args);
if (pluginRes != undefined && pluginRes != null)
res = res.concat(pluginRes);
if (pluginRes != undefined && pluginRes != null) res = res.concat(pluginRes);
}
return res;
},
callHookStr: function (hookName, args, sep, pre, post) {
callHookStr: function(hookName, args, sep, pre, post)
{
if (sep == undefined) sep = '';
if (pre == undefined) pre = '';
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;
if (!PROFILER) {
PROFILER = function() { return {start:noop, mark:noop, literal:noop, end:noop, cancel:noop}; };
if (!PROFILER)
{
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
var start = {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 start = {
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 totalWidth = 0;
var keyToNodeMap = {};
@ -38,20 +65,25 @@ function newSkipList() {
// 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
// this point.
function _getPoint(targetLoc) {
function _getPoint(targetLoc)
{
var numLevels = start.levels;
var lvl = numLevels - 1;
var i = -1, ws = 0;
var i = -1,
ws = 0;
var nodes = new Array(numLevels);
var idxs = new Array(numLevels);
var widthSkips = new Array(numLevels);
nodes[lvl] = start;
idxs[lvl] = -1;
widthSkips[lvl] = 0;
while (lvl >= 0) {
while (lvl >= 0)
{
var n = nodes[lvl];
while (n.downPtrs[lvl] &&
(i + n.downSkips[lvl] < targetLoc)) {
while (n.downPtrs[lvl] && (i + n.downSkips[lvl] < targetLoc))
{
i += n.downSkips[lvl];
ws += n.downSkipWidths[lvl];
n = n.downPtrs[lvl];
@ -60,19 +92,32 @@ function newSkipList() {
idxs[lvl] = i;
widthSkips[lvl] = ws;
lvl--;
if (lvl >= 0) {
if (lvl >= 0)
{
nodes[lvl] = n;
}
}
return {nodes:nodes, idxs:idxs, loc:targetLoc, widthSkips:widthSkips, toString: function() {
return "getPoint("+targetLoc+")"; } };
return {
nodes: nodes,
idxs: idxs,
loc: targetLoc,
widthSkips: widthSkips,
toString: function()
{
return "getPoint(" + targetLoc + ")";
}
function _getNodeAtOffset(targetOffset) {
};
}
function _getNodeAtOffset(targetOffset)
{
var i = 0;
var n = start;
var lvl = start.levels - 1;
while (lvl >= 0 && n.downPtrs[lvl]) {
while (n.downPtrs[lvl] && (i + n.downSkipWidths[lvl] <= targetOffset)) {
while (lvl >= 0 && n.downPtrs[lvl])
{
while (n.downPtrs[lvl] && (i + n.downSkipWidths[lvl] <= targetOffset))
{
i += n.downSkipWidths[lvl];
n = n.downPtrs[lvl];
}
@ -82,10 +127,23 @@ function newSkipList() {
else if (n === end) return (targetOffset == totalWidth ? (end.upPtrs[0] || null) : null);
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 newNode = {key:newKey, levels: 0, upPtrs:[], downPtrs:[], downSkips:[], downSkipWidths:[]};
var newNode = {
key: newKey,
levels: 0,
upPtrs: [],
downPtrs: [],
downSkips: [],
downSkipWidths: []
};
p.mark("donealloc");
var pNodes = point.nodes;
var pIdxs = point.idxs;
@ -93,10 +151,12 @@ function newSkipList() {
var widthLoc = point.widthSkips[0] + point.nodes[0].downSkipWidths[0];
var newWidth = _entryWidth(entry);
p.mark("loop1");
while (newNode.levels == 0 || Math.random() < 0.01) {
while (newNode.levels == 0 || Math.random() < 0.01)
{
var lvl = 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
// than the skiplist currently supports
pNodes[lvl] = start;
@ -127,7 +187,8 @@ function newSkipList() {
}
p.mark("loop2");
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];
up.downSkips[lvl]++;
up.downSkipWidths[lvl] += newWidth;
@ -138,24 +199,34 @@ function newSkipList() {
totalWidth += newWidth;
p.end();
}
function _getNodeAtPoint(point) {
function _getNodeAtPoint(point)
{
return point.nodes[0].downPtrs[0];
}
function _incrementPoint(point) {
function _incrementPoint(point)
{
point.loc++;
for(var i=0;i<point.nodes.length;i++) {
if (point.idxs[i] + point.nodes[i].downSkips[i] < point.loc) {
for (var i = 0; i < point.nodes.length; i++)
{
if (point.idxs[i] + point.nodes[i].downSkips[i] < point.loc)
{
point.idxs[i] += point.nodes[i].downSkips[i];
point.widthSkips[i] += point.nodes[i].downSkipWidths[i];
point.nodes[i] = point.nodes[i].downPtrs[i];
}
}
}
function _deleteKeyAtPoint(point) {
function _deleteKeyAtPoint(point)
{
var elem = point.nodes[0].downPtrs[0];
var elemWidth = _entryWidth(elem.entry);
for(var i=0;i<point.nodes.length;i++) {
if (i < elem.levels) {
for (var i = 0; i < point.nodes.length; i++)
{
if (i < elem.levels)
{
var up = elem.upPtrs[i];
var down = elem.downPtrs[i];
var totalSkip = up.downSkips[i] + elem.downSkips[i] - 1;
@ -165,7 +236,8 @@ function newSkipList() {
var totalWidthSkip = up.downSkipWidths[i] + elem.downSkipWidths[i] - elemWidth;
up.downSkipWidths[i] = totalWidthSkip;
}
else {
else
{
var up = point.nodes[i];
var down = up.downPtrs[i];
up.downSkips[i]--;
@ -176,25 +248,32 @@ function newSkipList() {
numNodes--;
totalWidth -= elemWidth;
}
function _propagateWidthChange(node) {
function _propagateWidthChange(node)
{
var oldWidth = node.downSkipWidths[0];
var newWidth = _entryWidth(node.entry);
var widthChange = newWidth - oldWidth;
var n = node;
var lvl = 0;
while (lvl < n.levels) {
while (lvl < n.levels)
{
n.downSkipWidths[lvl] += widthChange;
lvl++;
while (lvl >= n.levels && n.upPtrs[lvl-1]) {
while (lvl >= n.levels && n.upPtrs[lvl - 1])
{
n = n.upPtrs[lvl - 1];
}
}
totalWidth += widthChange;
}
function _getNodeIndex(node, byWidth) {
function _getNodeIndex(node, byWidth)
{
var dist = (byWidth ? 0 : -1);
var n = node;
while (n !== start) {
while (n !== start)
{
var lvl = n.levels - 1;
n = n.upPtrs[lvl];
if (byWidth) dist += n.downSkipWidths[lvl];
@ -224,25 +303,33 @@ function newSkipList() {
return map(processedArray, function (x) { return x.toSource(); }).join("\n");
}*/
function _getNodeByKey(key) {
function _getNodeByKey(key)
{
return keyToNodeMap['$KEY$' + key];
}
// Returns index of first entry such that entryFunc(entry) is truthy,
// or length() if no such entry. Assumes all falsy entries come before
// all truthy entries.
function _search(entryFunc) {
function _search(entryFunc)
{
var low = start;
var lvl = start.levels - 1;
var lowIndex = -1;
function f(node) {
function f(node)
{
if (node === start) return false;
else if (node === end) return true;
else return entryFunc(node.entry);
}
while (lvl >= 0) {
while (lvl >= 0)
{
var nextLow = low.downPtrs[lvl];
while (!f(nextLow)) {
while (!f(nextLow))
{
lowIndex += low.downSkips[lvl];
low = nextLow;
nextLow = low.downPtrs[lvl];
@ -257,16 +344,22 @@ The skip-list contains "entries", JavaScript objects that each must have a uniqu
that is a string.
*/
var self = {
length: function() { return numNodes; },
atIndex: function(i) {
length: function()
{
return numNodes;
},
atIndex: function(i)
{
if (i < 0) console.warn("atIndex(" + i + ")");
if (i >= numNodes) console.warn("atIndex(" + i + ">=" + numNodes + ")");
return _getNodeAtPoint(_getPoint(i)).entry;
},
// 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 + deleteCount > numNodes)
{
console.warn("splice(" + start + ", " + deleteCount + ", ...), N=" + numNodes);
console.warn("%s %s %s", typeof start, typeof deleteCount, typeof numNodes);
console.trace();
@ -274,26 +367,32 @@ that is a string.
if (!newEntryArray) newEntryArray = [];
var pt = _getPoint(start);
for(var i=0;i<deleteCount;i++) {
for (var i = 0; i < deleteCount; i++)
{
_deleteKeyAtPoint(pt);
}
for(var i=(newEntryArray.length-1);i>=0;i--) {
for (var i = (newEntryArray.length - 1); i >= 0; i--)
{
var entry = newEntryArray[i];
_insertKeyAtPoint(pt, entry.key, entry);
var node = _getNodeByKey(entry.key);
node.entry = entry;
}
},
next: function (entry) {
next: function(entry)
{
return _getNodeByKey(entry.key).downPtrs[0].entry || null;
},
prev: function (entry) {
prev: function(entry)
{
return _getNodeByKey(entry.key).upPtrs[0].entry || null;
},
push: function(entry) {
push: function(entry)
{
self.splice(numNodes, 0, [entry]);
},
slice: function(start, end) {
slice: function(start, end)
{
// act like Array.slice()
if (start === undefined) start = 0;
else if (start < 0) start += numNodes;
@ -309,39 +408,77 @@ that is a string.
if (end <= start) return [];
var n = self.atIndex(start);
var array = [n];
for(var i=1;i<(end-start);i++) {
for (var i = 1; i < (end - start); i++)
{
n = self.next(n);
array.push(n);
}
return array;
},
atKey: function(key) { return _getNodeByKey(key).entry; },
indexOfKey: function(key) { return _getNodeIndex(_getNodeByKey(key)); },
indexOfEntry: function (entry) { return self.indexOfKey(entry.key); },
containsKey: function(key) { return !!(_getNodeByKey(key)); },
atKey: function(key)
{
return _getNodeByKey(key).entry;
},
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
atOffset: function(offset) { return _getNodeAtOffset(offset).entry; },
keyAtOffset: function(offset) { 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) {
atOffset: function(offset)
{
return _getNodeAtOffset(offset).entry;
},
keyAtOffset: function(offset)
{
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 >= numNodes) return totalWidth;
return self.offsetOfEntry(self.atIndex(i));
},
indexOfOffset: function(offset) {
indexOfOffset: function(offset)
{
if (offset <= 0) return 0;
if (offset >= totalWidth) return numNodes;
return self.indexOfEntry(self.atOffset(offset));
},
search: function(entryFunc) {
search: function(entryFunc)
{
return _search(entryFunc);
},
//debugToString: _debugToString,
debugGetPoint: _getPoint,
debugDepth: function() { return start.levels; }
debugDepth: function()
{
return start.levels;
}
}
return self;
}

View File

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

View File

@ -15,8 +15,10 @@
*/
undoModule = (function() {
var stack = (function() {
undoModule = (function()
{
var stack = (function()
{
var stackElements = [];
// two types of stackElements:
// 1) { elementType: UNDOABLE_EVENT, eventType: "anything", [backset: <changeset>,]
@ -28,105 +30,137 @@ undoModule = (function() {
var UNDOABLE_EVENT = "undoableEvent";
var EXTERNAL_CHANGE = "externalChange";
function clearStack() {
function clearStack()
{
stackElements.length = 0;
stackElements.push({ elementType: UNDOABLE_EVENT, eventType: "bottom" });
stackElements.push(
{
elementType: UNDOABLE_EVENT,
eventType: "bottom"
});
numUndoableEvents = 1;
}
clearStack();
function pushEvent(event) {
var e = extend({}, event);
function pushEvent(event)
{
var e = extend(
{}, event);
e.elementType = UNDOABLE_EVENT;
stackElements.push(e);
numUndoableEvents++;
//dmesg("pushEvent backset: "+event.backset);
}
function pushExternalChange(cs) {
function pushExternalChange(cs)
{
var idx = stackElements.length - 1;
if (stackElements[idx].elementType == EXTERNAL_CHANGE) {
if (stackElements[idx].elementType == EXTERNAL_CHANGE)
{
stackElements[idx].changeset = Changeset.compose(stackElements[idx].changeset, cs, getAPool());
}
else {
stackElements.push({elementType: EXTERNAL_CHANGE, changeset: cs});
else
{
stackElements.push(
{
elementType: EXTERNAL_CHANGE,
changeset: cs
});
}
}
function _exposeEvent(nthFromTop) {
function _exposeEvent(nthFromTop)
{
// precond: 0 <= nthFromTop < numUndoableEvents
var targetIndex = stackElements.length - 1 - nthFromTop;
var idx = stackElements.length - 1;
while (idx > targetIndex || stackElements[idx].elementType == EXTERNAL_CHANGE) {
if (stackElements[idx].elementType == EXTERNAL_CHANGE) {
while (idx > targetIndex || stackElements[idx].elementType == EXTERNAL_CHANGE)
{
if (stackElements[idx].elementType == EXTERNAL_CHANGE)
{
var ex = stackElements[idx];
var un = stackElements[idx - 1];
if (un.backset) {
if (un.backset)
{
var excs = ex.changeset;
var unbs = un.backset;
un.backset = Changeset.follow(excs, un.backset, false, 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);
un.selStart = newSel[0];
un.selEnd = newSel[1];
if (un.selStart == un.selEnd) {
if (un.selStart == un.selEnd)
{
un.selFocusAtStart = false;
}
}
}
stackElements[idx - 1] = ex;
stackElements[idx] = un;
if (idx >= 2 && stackElements[idx-2].elementType == EXTERNAL_CHANGE) {
ex.changeset = Changeset.compose(stackElements[idx-2].changeset,
ex.changeset, getAPool());
if (idx >= 2 && stackElements[idx - 2].elementType == EXTERNAL_CHANGE)
{
ex.changeset = Changeset.compose(stackElements[idx - 2].changeset, ex.changeset, getAPool());
stackElements.splice(idx - 2, 1);
idx--;
}
}
else {
else
{
idx--;
}
}
}
function getNthFromTop(n) {
function getNthFromTop(n)
{
// precond: 0 <= n < numEvents()
_exposeEvent(n);
return stackElements[stackElements.length - 1 - n];
}
function numEvents() {
function numEvents()
{
return numUndoableEvents;
}
function popEvent() {
function popEvent()
{
// precond: numEvents() > 0
_exposeEvent(0);
numUndoableEvents--;
return stackElements.pop();
}
return {numEvents:numEvents, popEvent:popEvent, pushEvent: pushEvent,
pushExternalChange: pushExternalChange, clearStack: clearStack,
getNthFromTop:getNthFromTop};
return {
numEvents: numEvents,
popEvent: popEvent,
pushEvent: pushEvent,
pushExternalChange: pushExternalChange,
clearStack: clearStack,
getNthFromTop: getNthFromTop
};
})();
// invariant: stack always has at least one undoable event
var undoPtr = 0; // zero-index from top of stack, 0 == top
function clearHistory() {
function clearHistory()
{
stack.clearStack();
undoPtr = 0;
}
function _charOccurrences(str, c) {
function _charOccurrences(str, c)
{
var i = 0;
var count = 0;
while (i >= 0 && i < str.length) {
while (i >= 0 && i < str.length)
{
i = str.indexOf(c, i);
if (i >= 0) {
if (i >= 0)
{
count++;
i++;
}
@ -134,11 +168,13 @@ undoModule = (function() {
return count;
}
function _opcodeOccurrences(cs, opcode) {
function _opcodeOccurrences(cs, opcode)
{
return _charOccurrences(Changeset.unpack(cs).ops, opcode);
}
function _mergeChangesets(cs1, cs2) {
function _mergeChangesets(cs1, cs2)
{
if (!cs1) return cs2;
if (!cs2) return cs1;
@ -152,51 +188,63 @@ undoModule = (function() {
var plusCount2 = _opcodeOccurrences(cs2, '+');
var minusCount1 = _opcodeOccurrences(cs1, '-');
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 plusCount3 = _opcodeOccurrences(merge, '+');
var minusCount3 = _opcodeOccurrences(merge, '-');
if (plusCount3 == 1 && minusCount3 == 0) {
if (plusCount3 == 1 && minusCount3 == 0)
{
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 plusCount3 = _opcodeOccurrences(merge, '+');
var minusCount3 = _opcodeOccurrences(merge, '-');
if (plusCount3 == 0 && minusCount3 == 1) {
if (plusCount3 == 0 && minusCount3 == 1)
{
return merge;
}
}
return null;
}
function reportEvent(event) {
function reportEvent(event)
{
var topEvent = stack.getNthFromTop(0);
function applySelectionToTop() {
if ((typeof event.selStart) == "number") {
function applySelectionToTop()
{
if ((typeof event.selStart) == "number")
{
topEvent.selStart = event.selStart;
topEvent.selEnd = event.selEnd;
topEvent.selFocusAtStart = event.selFocusAtStart;
}
}
if ((! event.backset) || Changeset.isIdentity(event.backset)) {
if ((!event.backset) || Changeset.isIdentity(event.backset))
{
applySelectionToTop();
}
else {
else
{
var merged = false;
if (topEvent.eventType == event.eventType) {
if (topEvent.eventType == event.eventType)
{
var merge = _mergeChangesets(event.backset, topEvent.backset);
if (merge) {
if (merge)
{
topEvent.backset = merge;
//dmesg("reportEvent merge: "+merge);
applySelectionToTop();
merged = true;
}
}
if (! merged) {
if (!merged)
{
stack.pushEvent(event);
}
undoPtr = 0;
@ -204,19 +252,27 @@ undoModule = (function() {
}
function reportExternalChange(changeset) {
if (changeset && ! Changeset.isIdentity(changeset)) {
function reportExternalChange(changeset)
{
if (changeset && !Changeset.isIdentity(changeset))
{
stack.pushExternalChange(changeset);
}
}
function _getSelectionInfo(event) {
if ((typeof event.selStart) != "number") {
function _getSelectionInfo(event)
{
if ((typeof event.selStart) != "number")
{
return null;
}
else {
return {selStart: event.selStart, selEnd: event.selEnd,
selFocusAtStart: event.selFocusAtStart};
else
{
return {
selStart: event.selStart,
selEnd: event.selEnd,
selFocusAtStart: event.selFocusAtStart
};
}
}
@ -226,8 +282,10 @@ undoModule = (function() {
// or can be called with no arguments to mean that no undo is possible.
// "eventFunc" will be called exactly once.
function performUndo(eventFunc) {
if (undoPtr < stack.numEvents()-1) {
function performUndo(eventFunc)
{
if (undoPtr < stack.numEvents() - 1)
{
var backsetEvent = stack.getNthFromTop(undoPtr);
var selectionEvent = stack.getNthFromTop(undoPtr + 1);
var undoEvent = eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent));
@ -237,8 +295,10 @@ undoModule = (function() {
else eventFunc();
}
function performRedo(eventFunc) {
if (undoPtr >= 2) {
function performRedo(eventFunc)
{
if (undoPtr >= 2)
{
var backsetEvent = stack.getNthFromTop(0);
var selectionEvent = stack.getNthFromTop(1);
eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent));
@ -248,11 +308,18 @@ undoModule = (function() {
else eventFunc();
}
function getAPool() {
function getAPool()
{
return undoModule.apool;
}
return {clearHistory:clearHistory, reportEvent:reportEvent, reportExternalChange:reportExternalChange,
performUndo:performUndo, performRedo:performRedo, enabled: true,
apool: null}; // apool is filled in by caller
return {
clearHistory: clearHistory,
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.
*/
function makeVirtualLineView(lineNode) {
function makeVirtualLineView(lineNode)
{
// how much to jump forward or backward at once in a charSeeker before
// constructing a DOM node and checking the coordinates (which takes a
@ -26,12 +27,15 @@ function makeVirtualLineView(lineNode) {
var maxCharIncrement = 20;
var seekerAtEnd = null;
function getNumChars() {
function getNumChars()
{
return lineNode.textContent.length;
}
function getNumVirtualLines() {
if (! seekerAtEnd) {
function getNumVirtualLines()
{
if (!seekerAtEnd)
{
var seeker = makeCharSeeker();
seeker.forwardByWhile(maxCharIncrement);
seekerAtEnd = seeker;
@ -39,22 +43,34 @@ function makeVirtualLineView(lineNode) {
return seekerAtEnd.getVirtualLine() + 1;
}
function getVLineAndOffsetForChar(lineChar) {
function getVLineAndOffsetForChar(lineChar)
{
var seeker = makeCharSeeker();
seeker.forwardByWhile(maxCharIncrement, null, lineChar);
var theLine = seeker.getVirtualLine();
seeker.backwardByWhile(8, function() { return seeker.getVirtualLine() == theLine; });
seeker.forwardByWhile(1, function() { return seeker.getVirtualLine() != theLine; });
seeker.backwardByWhile(8, function()
{
return seeker.getVirtualLine() == theLine;
});
seeker.forwardByWhile(1, function()
{
return seeker.getVirtualLine() != theLine;
});
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.
// if offset is beyond end of line, for example, will give new offset at end of line.
var seeker = makeCharSeeker();
// go to start of line
seeker.binarySearch(function() {
seeker.binarySearch(function()
{
return seeker.getVirtualLine() >= vline;
});
var lineStart = seeker.getOffset();
@ -62,50 +78,75 @@ function makeVirtualLineView(lineNode) {
// go to offset, overshooting the virtual line only if offset is too large for it
seeker.forwardByWhile(maxCharIncrement, null, lineStart + offset);
// 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 theOffset = lineChar - lineStart;
// 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();
theOffset += lineLen - lineChar;
lineChar = lineLen;
}
return { vline:theLine, offset:theOffset, lineChar:lineChar };
return {
vline: theLine,
offset: theOffset,
lineChar: lineChar
};
}
return {getNumVirtualLines:getNumVirtualLines, getVLineAndOffsetForChar:getVLineAndOffsetForChar,
return {
getNumVirtualLines: getNumVirtualLines,
getVLineAndOffsetForChar: getVLineAndOffsetForChar,
getCharForVLineAndOffset: getCharForVLineAndOffset,
makeCharSeeker: function() { return makeCharSeeker(); } };
makeCharSeeker: function()
{
return makeCharSeeker();
}
};
function deepFirstChildTextNode(nd) {
function deepFirstChildTextNode(nd)
{
nd = nd.firstChild;
while (nd && nd.firstChild) nd = nd.firstChild;
if (nd.data) return nd;
return null;
}
function makeCharSeeker(/*lineNode*/) {
function makeCharSeeker( /*lineNode*/ )
{
function charCoords(tnode, i) {
function charCoords(tnode, i)
{
var container = tnode.parentNode;
// treat space specially; a space at the end of a virtual line
// will have weird coordinates
var isSpace = (tnode.nodeValue.charAt(i) === " ");
if (isSpace) {
if (i == 0) {
if (container.previousSibling && deepFirstChildTextNode(container.previousSibling)) {
if (isSpace)
{
if (i == 0)
{
if (container.previousSibling && deepFirstChildTextNode(container.previousSibling))
{
tnode = deepFirstChildTextNode(container.previousSibling);
i = tnode.length - 1;
container = tnode.parentNode;
}
else {
return {top:container.offsetTop, left:container.offsetLeft};
else
{
return {
top: container.offsetTop,
left: container.offsetLeft
};
}
}
else {
else
{
i--; // use previous char
}
}
@ -122,9 +163,11 @@ function makeVirtualLineView(lineNode) {
frag.appendChild(document.createTextNode(tnodeText.substring(i + 1)));
container.replaceChild(frag, tnode);
var result = {top:charWrapper.offsetTop,
var result = {
top: charWrapper.offsetTop,
left: charWrapper.offsetLeft + (isSpace ? charWrapper.offsetWidth : 0),
height:charWrapper.offsetHeight};
height: charWrapper.offsetHeight
};
while (container.firstChild) container.removeChild(container.firstChild);
container.appendChild(tnode);
@ -143,34 +186,41 @@ function makeVirtualLineView(lineNode) {
var approxLineHeight;
var whichLine = 0;
function nextNode() {
function nextNode()
{
var n = curNode;
if (!n) n = lineNode.firstChild;
else n = n.nextSibling;
while (n && ! deepFirstChildTextNode(n)) {
while (n && !deepFirstChildTextNode(n))
{
n = n.nextSibling;
}
return n;
}
function prevNode() {
function prevNode()
{
var n = curNode;
if (!n) n = lineNode.lastChild;
else n = n.previousSibling;
while (n && ! deepFirstChildTextNode(n)) {
while (n && !deepFirstChildTextNode(n))
{
n = n.previousSibling;
}
return n;
}
var seeker;
if (lineLength > 0) {
if (lineLength > 0)
{
curNode = nextNode();
var firstCharData = charCoords(deepFirstChildTextNode(curNode), 0);
approxLineHeight = firstCharData.height;
curTop = firstCharData.top;
curLeft = firstCharData.left;
function updateCharData(tnode, i) {
function updateCharData(tnode, i)
{
var coords = charCoords(tnode, i);
whichLine += Math.round((coords.top - curTop) / approxLineHeight);
curTop = coords.top;
@ -178,15 +228,17 @@ function makeVirtualLineView(lineNode) {
}
seeker = {
forward: function(numChars) {
forward: function(numChars)
{
var oldChar = curChar;
var newChar = curChar + numChars;
if (newChar > (lineLength-1))
newChar = lineLength-1;
while (curChar < newChar) {
if (newChar > (lineLength - 1)) newChar = lineLength - 1;
while (curChar < newChar)
{
var curNodeLength = deepFirstChildTextNode(curNode).length;
var toGo = curNodeLength - curCharWithinNode;
if (curChar + toGo > newChar || ! nextNode()) {
if (curChar + toGo > newChar || !nextNode())
{
// going to next node would be too far
var n = newChar - curChar;
if (n >= toGo) n = toGo - 1;
@ -194,7 +246,8 @@ function makeVirtualLineView(lineNode) {
curCharWithinNode += n;
break;
}
else {
else
{
// go to next node
curChar += toGo;
curCharWithinNode = 0;
@ -204,12 +257,15 @@ function makeVirtualLineView(lineNode) {
updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode);
return curChar - oldChar;
},
backward: function(numChars) {
backward: function(numChars)
{
var oldChar = curChar;
var newChar = curChar - numChars;
if (newChar < 0) newChar = 0;
while (curChar > newChar) {
if (curChar - curCharWithinNode <= newChar || !prevNode()) {
while (curChar > newChar)
{
if (curChar - curCharWithinNode <= newChar || !prevNode())
{
// going to prev node would be too far
var n = curChar - newChar;
if (n > curCharWithinNode) n = curCharWithinNode;
@ -217,7 +273,8 @@ function makeVirtualLineView(lineNode) {
curCharWithinNode -= n;
break;
}
else {
else
{
// go to prev node
curChar -= curCharWithinNode + 1;
curNode = prevNode();
@ -227,33 +284,63 @@ function makeVirtualLineView(lineNode) {
updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode);
return oldChar - curChar;
},
getVirtualLine: function() { return whichLine; },
getLeftCoord: function() { return curLeft; }
getVirtualLine: function()
{
return whichLine;
},
getLeftCoord: function()
{
return curLeft;
}
};
}
else {
else
{
curLeft = lineNode.offsetLeft;
seeker = { forward: function(numChars) { return 0; },
backward: function(numChars) { return 0; },
getVirtualLine: function() { return 0; },
getLeftCoord: function() { return curLeft; }
seeker = {
forward: function(numChars)
{
return 0;
},
backward: function(numChars)
{
return 0;
},
getVirtualLine: function()
{
return 0;
},
getLeftCoord: function()
{
return curLeft;
}
};
}
seeker.getOffset = function() { return curChar; };
seeker.getLineLength = function() { return lineLength; };
seeker.toString = function() {
seeker.getOffset = function()
{
return curChar;
};
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 hasCondFunc = ((typeof optCondFunc) == "function");
var condFunc = optCondFunc;
var hasCharLimit = ((typeof optCharLimit) == "number");
var charLimit = optCharLimit;
while (charsMovedLast !== 0 && ((! hasCondFunc) || condFunc())) {
while (charsMovedLast !== 0 && ((!hasCondFunc) || condFunc()))
{
var toMove = amount;
if (hasCharLimit) {
if (hasCharLimit)
{
var untilLimit = (isBackward ? curChar - charLimit : charLimit - curChar);
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);
}
seeker.backwardByWhile = function(amount, optCondFunc, optCharLimit) {
seeker.backwardByWhile = function(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;
// positions seeker at first true char, or else last char
var trueFunc = condFunc;
var falseFunc = function() { return ! condFunc(); };
var falseFunc = function()
{
return !condFunc();
};
seeker.forwardByWhile(20, falseFunc);
seeker.backwardByWhile(20, trueFunc);
seeker.forwardByWhile(10, falseFunc);