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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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() {
func.apply(that, args);
function action()
{
func.apply(that, args);
}
if (optDoNow) {
optDoNow.apply(that, args);
if (optDoNow)
{
optDoNow.apply(that, args);
}
if (loaded) {
action();
if (loaded)
{
action();
}
else {
actionsPendingInit.push(action);
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() {
if (! loaded) return "(awaiting init)\n";
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() {
if (! loaded) return null;
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() {
if (! loaded) return [];
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,50 +194,58 @@ 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) {
return '<link rel="stylesheet" type="text/css" href="'+fileName+'"/>';
};
var $$INCLUDE_JS = function(fileName) {
return '\x3cscript type="text/javascript" src="'+fileName+'">\x3c/script>';
};
var $$INCLUDE_CSS = function(fileName)
{
return '<link rel="stylesheet" type="text/css" href="' + 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) {
return '\'<link rel="stylesheet" type="text/css" href="'+fileName+'"/>\'';
};
var $$INCLUDE_JS_Q = function(fileName) {
return '\'\\x3cscript type="text/javascript" src="'+fileName+'">\\x3c/script>\'';
};
var $$INCLUDE_CSS_Q = function(fileName)
{
return '\'<link rel="stylesheet" type="text/css" href="' + 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});
var iframeHTML = ["'" + doctype + "<html><head>'"];
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"));
iframeHTML.push($$INCLUDE_CSS_Q("static/css/syntax.css"));
@ -187,39 +262,26 @@ function Ace2Editor() {
iframeHTML.push($$INCLUDE_JS_Q("static/js/linestylefilter.js"));
iframeHTML.push($$INCLUDE_JS_Q("static/js/domline.js"));
iframeHTML.push($$INCLUDE_JS_Q("static/js/ace2_inner.js"));
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"),
// 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>'];
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>'];
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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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;
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,41 +85,48 @@ var browser = {
windows: /windows/.test(userAgent) // dgreensp
};
function getAssoc(obj, name) {
return obj["_magicdom_"+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
obj["_magicdom_"+name] = value;
obj["_magicdom_" + 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;
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) {
var x = Math.floor((low+high)/2); // x != low, x != high
var high = numItems - 1; // func(high) is always true
while ((high - low) > 1)
{
var x = Math.floor((low + high) / 2); // x != low, x != high
if (func(x)) high = x;
else low = x;
}
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

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

View File

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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()
@ -27,25 +26,25 @@ function loadBroadcastRevisionsJS()
this.changesets = [];
}
Revision.prototype.addChangeset = function (destIndex, changeset, timeDelta)
Revision.prototype.addChangeset = function(destIndex, changeset, timeDelta)
{
var changesetWrapper = {
deltaRev: destIndex - this.rev,
deltaTime: timeDelta,
getValue: function ()
getValue: function()
{
return changeset;
}
};
this.changesets.push(changesetWrapper);
this.changesets.sort(function (a, b)
this.changesets.sort(function(a, b)
{
return (b.deltaRev - a.deltaRev)
});
}
revisionInfo = {};
revisionInfo.addChangeset = function (fromIndex, toIndex, changeset, backChangeset, timeDelta)
revisionInfo.addChangeset = function(fromIndex, toIndex, changeset, backChangeset, timeDelta)
{
var startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
var endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex);
@ -55,7 +54,7 @@ function loadBroadcastRevisionsJS()
revisionInfo.latest = clientVars.totalRevs || -1;
revisionInfo.createNew = function (index)
revisionInfo.createNew = function(index)
{
revisionInfo[index] = new Revision(index);
if (index > revisionInfo.latest)
@ -68,7 +67,7 @@ function loadBroadcastRevisionsJS()
// assuming that there is a path from fromIndex to toIndex, and that the links
// are laid out in a skip-list format
revisionInfo.getPath = function (fromIndex, toIndex)
revisionInfo.getPath = function(fromIndex, toIndex)
{
var changesets = [];
var spans = [];

View File

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

View File

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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,136 +35,168 @@ 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 {
changeCallback();
}
finally {
changeCallbackTimeout = null;
}
if (changeCallback && changeCallbackTimeout === null)
{
changeCallbackTimeout = scheduler.setTimeout(function()
{
try
{
changeCallback();
}
finally
{
changeCallbackTimeout = null;
}
}, 0);
}
}
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) {
var wireApool = (new AttribPool()).fromJsonable(apoolJsonObj);
baseAText.attribs = Changeset.moveOpsToNewPool(baseAText.attribs, wireApool, apool);
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 {
applyingNonUserChanges = false;
finally
{
applyingNonUserChanges = false;
}
});
},
composeUserChangeset: function(c) {
if (! tracking) return;
composeUserChangeset: function(c)
{
if (!tracking) return;
if (applyingNonUserChanges) return;
if (Changeset.isIdentity(c)) return;
userChangeset = Changeset.compose(userChangeset, c, apool);
setChangeCallbackTimeout();
},
applyChangesToBase: function (c, optAuthor, apoolJsonObj) {
if (! tracking) return;
applyChangesToBase: function(c, optAuthor, apoolJsonObj)
{
if (!tracking) return;
aceCallbacksProvider.withCallbacks("applyChangesToBase", function(callbacks) {
aceCallbacksProvider.withCallbacks("applyChangesToBase", function(callbacks)
{
if (apoolJsonObj) {
var wireApool = (new AttribPool()).fromJsonable(apoolJsonObj);
c = Changeset.moveOpsToNewPool(c, wireApool, apool);
if (apoolJsonObj)
{
var wireApool = (new AttribPool()).fromJsonable(apoolJsonObj);
c = Changeset.moveOpsToNewPool(c, wireApool, apool);
}
baseAText = Changeset.applyToAText(c, baseAText, apool);
var c2 = c;
if (submittedChangeset) {
var oldSubmittedChangeset = submittedChangeset;
submittedChangeset = Changeset.follow(c, oldSubmittedChangeset, false, apool);
c2 = Changeset.follow(oldSubmittedChangeset, c, true, apool);
if (submittedChangeset)
{
var oldSubmittedChangeset = submittedChangeset;
submittedChangeset = Changeset.follow(c, oldSubmittedChangeset, false, apool);
c2 = Changeset.follow(oldSubmittedChangeset, c, true, apool);
}
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 {
applyingNonUserChanges = false;
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) {
// submission must have been canceled, prepare new changeset
// that includes old submittedChangeset
toSubmit = Changeset.compose(submittedChangeset, userChangeset, apool);
if (submittedChangeset)
{
// submission must have been canceled, prepare new changeset
// that includes old submittedChangeset
toSubmit = Changeset.compose(submittedChangeset, userChangeset, apool);
}
else {
if (Changeset.isIdentity(userChangeset)) toSubmit = null;
else toSubmit = userChangeset;
else
{
if (Changeset.isIdentity(userChangeset)) toSubmit = null;
else toSubmit = userChangeset;
}
var cs = null;
if (toSubmit) {
submittedChangeset = toSubmit;
userChangeset = Changeset.identity(Changeset.newLen(toSubmit));
if (toSubmit)
{
submittedChangeset = toSubmit;
userChangeset = Changeset.identity(Changeset.newLen(toSubmit));
cs = toSubmit;
cs = toSubmit;
}
var wireApool = null;
if (cs) {
var forWire = Changeset.prepareForWire(cs, apool);
wireApool = forWire.pool.toJsonable();
cs = forWire.translated;
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) {
// violation of protocol; use prepareUserChangeset first
throw new Error("applySubmittedChangesToBase: no submitted changes to apply");
applyPreparedChangesetToBase: function()
{
if (!submittedChangeset)
{
// violation of protocol; use prepareUserChangeset first
throw new Error("applySubmittedChangesToBase: no submitted changes to apply");
}
//bumpDebug("applying committed changeset: "+submittedChangeset.encodeToString(false));
baseAText = Changeset.applyToAText(submittedChangeset, baseAText, apool);
submittedChangeset = null;
},
setUserChangeNotificationCallback: function (callback) {
setUserChangeNotificationCallback: function(callback)
{
changeCallback = callback;
},
hasUncommittedChanges: function() {
return !!(submittedChangeset || (! Changeset.isIdentity(userChangeset)));
hasUncommittedChanges: function()
{
return !!(submittedChangeset || (!Changeset.isIdentity(userChangeset)));
}
};

File diff suppressed because it is too large Load Diff

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,73 +19,97 @@
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) {
return Number("0x"+hh)/255;
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);
h = a+a+b+b+c+c;
h = a + a + b + b + c + c;
}
return h;
}
// [1.0, 1.0, 1.0] -> "#ffffff"
colorutils.triple2css = function(triple) {
function floatToHex(n) {
var n2 = colorutils.clamp(Math.round(n*255), 0, 255);
return ("0"+n2.toString(16)).slice(-2);
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;
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) {
return dom.nodeProp(node, "_magicdom_"+name);
str.replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' '));
}
var lines = (function() {
function getAssoc(node, name)
{
return dom.nodeProp(node, "_magicdom_" + name);
}
var lines = (function()
{
var textArray = [];
var attribsArray = [];
var attribsBuilder = null;
var op = Changeset.newOp('+');
var self = {
length: function() { return textArray.length; },
atColumnZero: function() {
return textArray[textArray.length-1] === "";
length: function()
{
return textArray.length;
},
startNew: function() {
atColumnZero: function()
{
return textArray[textArray.length - 1] === "";
},
startNew: function()
{
textArray.push("");
self.flush(true);
attribsBuilder = Changeset.smartOpAssembler();
},
textOfLine: function(i) { return textArray[i]; },
appendText: function(txt, attrString) {
textArray[textArray.length-1] += txt;
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) {
var ln = lines.length()-1;
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) {
if (! dom.isNodeText(nd)) _reachPoint(nd, idx, state);
function _reachBlockPoint(nd, idx, state)
{
if (!dom.isNodeText(nd)) _reachPoint(nd, idx, state);
}
function _reachPoint(nd, idx, state) {
if (startPoint && nd == startPoint.node && startPoint.index == idx) {
function _reachPoint(nd, idx, state)
{
if (startPoint && nd == startPoint.node && startPoint.index == idx)
{
selStart = _pointHere(0, state);
}
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) {
state.flags[flagName] = (state.flags[flagName] || 0)+1;
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') {
state.listNesting = (state.listNesting || 0)+1;
state.listLevel = (state.listLevel || 0) + 1;
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.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]) {
lst.push([a,'true']);
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,155 +301,197 @@ 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) {
var atBeginningOfLine = lines.textOfLine(lines.length()-1).length == 0;
if (atBeginningOfLine && state.listType && state.listType != 'none') {
cc.startNewLine = function(state)
{
if (state)
{
var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
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*/},
localAttribs: null,
attribs: {/*name -> nesting counter*/},
attribString: ''};
cc.collectContent = function(node, state)
{
if (!state)
{
state = {
flags: { /*name -> nesting counter*/
},
localAttribs: null,
attribs: { /*name -> nesting counter*/
},
attribString: ''
};
}
var localAttribs = state.localAttribs;
state.localAttribs = null;
var isBlock = isBlockElement(node);
var isEmpty = _isEmpty(node, state);
if (isBlock) _ensureColumnZero(state);
var startLine = lines.length()-1;
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) {
var firstLine = txt.split('\n',1)[0];
consumed = firstLine.length+1;
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) {
selStart = _pointHere(startPoint.index-x, state);
else
{ /* will only run this loop body once */
}
if (endPoint && node == endPoint.node &&
endPoint.index-x <= txt.length) {
selEnd = _pointHere(endPoint.index-x, state);
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)
{
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
// removing "\n" from pasted HTML will collapse words together.
txt2 = "";
}
var atBeginningOfLine = lines.textOfLine(lines.length()-1).length == 0;
if (atBeginningOfLine) {
var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
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") {
cc.doAttrib(state, "bold");
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") {
cc.doAttrib(state, "italic");
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") {
cc.doAttrib(state, "underline");
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") {
cc.doAttrib(state, "strikethrough");
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));
var rr = cls && /(?:^| )list-(bullet[12345678])\b/.exec(cls);
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,69 +603,85 @@ 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--) {
var oldString = lineStrings[i];
var oldAttribString = lineAttribs[i];
if (oldString.length > lineLimit+buffer) {
var newStrings = [];
var newAttribStrings = [];
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);
}
if (oldString.length > 0) {
newStrings.push(oldString);
newAttribStrings.push(oldAttribString);
}
function fixLineNumber(lineChar) {
if (lineChar[0] < 0) return;
var n = lineChar[0];
var c = lineChar[1];
if (n > i) {
n += (newStrings.length-1);
}
else if (n == i) {
var a = 0;
while (c > newStrings[a].length) {
c -= newStrings[a].length;
a++;
}
n += a;
}
lineChar[0] = n;
lineChar[1] = c;
}
fixLineNumber(ss);
fixLineNumber(se);
linesWrapped++;
numLinesAfter += newStrings.length;
for (var i = lineStrings.length - 1; i >= 0; i--)
{
var oldString = lineStrings[i];
var oldAttribString = lineAttribs[i];
if (oldString.length > lineLimit + buffer)
{
var newStrings = [];
var newAttribStrings = [];
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);
}
if (oldString.length > 0)
{
newStrings.push(oldString);
newAttribStrings.push(oldAttribString);
}
newStrings.unshift(i, 1);
lineStrings.splice.apply(lineStrings, newStrings);
newAttribStrings.unshift(i, 1);
lineAttribs.splice.apply(lineAttribs, newAttribStrings);
}
function fixLineNumber(lineChar)
{
if (lineChar[0] < 0) return;
var n = lineChar[0];
var c = lineChar[1];
if (n > i)
{
n += (newStrings.length - 1);
}
else if (n == i)
{
var a = 0;
while (c > newStrings[a].length)
{
c -= newStrings[a].length;
a++;
}
n += a;
}
lineChar[0] = n;
lineChar[1] = c;
}
fixLineNumber(ss);
fixLineNumber(se);
linesWrapped++;
numLinesAfter += newStrings.length;
newStrings.unshift(i, 1);
lineStrings.splice.apply(lineStrings, newStrings);
newAttribStrings.unshift(i, 1);
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,
numLinesAfter: wrapData.numLinesAfter,
lines: lineStrings, lineAttribs: lineAttribs };
return {
selStart: ss,
selEnd: se,
linesWrapped: wrapData.linesWrapped,
numLinesAfter: wrapData.numLinesAfter,
lines: lineStrings,
lineAttribs: lineAttribs
};
}
return cc;

View File

@ -1,5 +1,3 @@
/**
* Copyright 2009 Google Inc.
*
@ -16,20 +14,24 @@
* 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) {
return s;
if (s.title == title)
{
return s;
}
}
return null;
}
/*function getSheetTagByTitle(title) {
/*function getSheetTagByTitle(title) {
var allStyleTags = document.getElementsByTagName("style");
for(var i=0;i<allStyleTags.length;i++) {
var t = allStyleTags[i];
@ -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) {
if (browserSheet.insertRule) browserSheet.insertRule(selector+' {}', i);
function browserInsertRule(i, selector)
{
if (browserSheet.insertRule) browserSheet.insertRule(selector + ' {}', i);
else browserSheet.addRule(selector, null, i);
}
var selectorList = [];
function indexOfSelector(selector) {
for(var i=0;i<selectorList.length;i++) {
if (selectorList[i] == selector) {
return i;
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 selectorList.length+":"+browserRules().length;
}};
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);
@ -103,7 +102,7 @@ function makeCSSManager(emptyStylesheetTitle)
return {
selectorStyle: selectorStyle,
removeSelectorStyle: removeSelectorStyle,
info: function ()
info: function()
{
return selectorList.length + ":" + browserRules().length;
}

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,19 +19,25 @@
// 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);
lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5);
}
});
return lineClass;
@ -40,42 +45,56 @@ 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,
appendSpan: domline.noop,
prepareForAdd: domline.noop,
notifyAdded: domline.noop,
clearSpans: domline.noop,
finishUpdate: domline.noop,
lineMarker: 0 };
domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
{
var result = {
node: null,
appendSpan: domline.noop,
prepareForAdd: domline.noop,
notifyAdded: domline.noop,
clearSpans: domline.noop,
finishUpdate: domline.noop,
lineMarker: 0
};
var browser = (optBrowser || {});
var document = optDocument;
if (document) {
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) {
preHtml = '<ul class="list-'+listType+'"><li>';
if (listType)
{
preHtml = '<ul class="list-' + listType + '"><li>';
postHtml = '</li></ul>';
}
result.lineMarker += txt.length;
@ -84,17 +103,21 @@ 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) {
href = url;
return 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 (! simpleTags) simpleTags = [];
simpleTags.push(tag.toLowerCase());
return 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;
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;')+'">';
extraCloseTags = '</a>'+extraCloseTags;
else if (txt)
{
if (href)
{
extraOpenTags = extraOpenTags + '<a href="' + href.replace(/\"/g, '&quot;') + '">';
extraCloseTags = '</a>' + extraCloseTags;
}
if (simpleTags) {
simpleTags.sort();
extraOpenTags = extraOpenTags+'<'+simpleTags.join('><')+'>';
simpleTags.reverse();
extraCloseTags = '</'+simpleTags.join('></')+'>'+extraCloseTags;
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) {
newHTML = (preHtml||'')+newHTML+(postHtml||'');
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,51 +223,68 @@ 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;';
endOfLine = false;
beforeSpace = true;
if (p == " ")
{
if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
}
else if (p.charAt(0) != "<") {
endOfLine = false;
beforeSpace = false;
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 == " ") {
parts[i] = '&nbsp;';
break;
if (p == " ")
{
parts[i] = '&nbsp;';
break;
}
else if (p.charAt(0) != "<") {
break;
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 == " ") {
parts[i] = '&nbsp;';
if (p == " ")
{
parts[i] = '&nbsp;';
}
}
}

View File

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

View File

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,7 +16,7 @@
function makeDraggable(jqueryNodes, eventHandler)
{
jqueryNodes.each(function ()
jqueryNodes.each(function()
{
var node = $(this);
var state = {};
@ -77,7 +77,7 @@ function makeResizableVPane(top, sep, bottom, minTop, minBottom, callback)
if (minTop === undefined) minTop = 0;
if (minBottom === undefined) minBottom = 0;
makeDraggable($(sep), function (eType, evt, state)
makeDraggable($(sep), function(eType, evt, state)
{
if (eType == 'dragstart')
{
@ -125,7 +125,7 @@ function makeResizableHPane(left, sep, right, minLeft, minRight, sepWidth, sepOf
if (minLeft === undefined) minLeft = 0;
if (minRight === undefined) minRight = 0;
makeDraggable($(sep), function (eType, evt, state)
makeDraggable($(sep), function(eType, evt, state)
{
if (eType == 'dragstart')
{

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -158,323 +158,314 @@
// 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) {
JSON = {};
if (!JSON)
{
JSON = {};
}
(function () {
"use strict";
(function()
{
"use strict";
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
function f(n)
{
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function')
{
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;
};
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
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"': '\\"',
'\\': '\\\\'
},
rep;
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)
{
var c = meta[a];
return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
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];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' && typeof value.toJSON === 'function')
{
value = value.toJSON(key);
}
if (typeof Date.prototype.toJSON !== 'function') {
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;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function')
{
value = rep.call(holder, key, value);
}
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
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
// What happens next depends on the value's type.
switch (typeof value)
{
case 'string':
return quote(value);
case 'number':
function quote(string) {
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
// 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.
case 'boolean':
case 'null':
escapable.lastIndex = 0;
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);
}) + '"' : '"' + string + '"';
}
// 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':
function str(key, holder) {
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value)
{
return 'null';
}
// Produce a string from holder[key].
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]')
{
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
// 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)
{
partial[i] = str(i, value) || 'null';
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
// 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(',') + ']';
gap = mind;
return v;
}
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object')
{
length = rep.length;
for (i = 0; i < length; i += 1)
{
if (typeof rep[i] === 'string')
{
k = rep[i];
v = str(k, value);
if (v)
{
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
else
{
// Otherwise, iterate through all of the keys in the object.
for (k in value)
{
if (Object.prototype.hasOwnProperty.call(value, k))
{
v = str(k, value);
if (v)
{
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// 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(',') + '}';
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)
{
// 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)
{
indent += ' ';
}
// What happens next depends on the value's type.
// If the space parameter is a string, it will be used as the indent string.
}
else if (typeof space === 'string')
{
indent = space;
}
switch (typeof value) {
case 'string':
return quote(value);
// 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'))
{
throw new Error('JSON.stringify');
}
case 'number':
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {
'': value
});
};
}
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function')
{
JSON.parse = function(text, reviver)
{
case 'boolean':
case 'null':
// 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;
// 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.
function walk(holder, key)
{
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) {
return 'null';
// 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))
{
v = walk(value, k);
if (v !== undefined)
{
value[k] = v;
}
else
{
delete value[k];
}
}
// 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]') {
// 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) {
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(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// 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(',') + '}';
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) {
// 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) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent 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')) {
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 reviver.call(holder, key, value);
}
// If the JSON object does not yet have a parse method, give it one.
// 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 (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// In the second stage, we run the text against regular expressions that look
// 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
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// 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, '')))
{
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
// 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 + ')');
var j;
// 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;
}
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)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// 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);
});
}
// In the second stage, we run the text against regular expressions that look
// 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
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// 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, ''))) {
// 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;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
// 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,32 +21,36 @@
// requires: top
// requires: plugins
// requires: undefined
var linestylefilter = {};
linestylefilter.ATTRIB_CLASSES = {
'bold':'tag:b',
'italic':'tag:i',
'underline':'tag:u',
'strikethrough':'tag:s'
'bold': 'tag:b',
'italic': 'tag:i',
'underline': 'tag:u',
'strikethrough': 'tag:s'
};
linestylefilter.getAuthorClassName = function(author) {
return "author-"+author.replace(/[^a-y0-9]/g, function(c) {
linestylefilter.getAuthorClassName = function(author)
{
return "author-" + author.replace(/[^a-y0-9]/g, function(c)
{
if (c == ".") return "-";
return 'z'+c.charCodeAt(0)+'z';
return 'z' + c.charCodeAt(0) + 'z';
});
};
// lineLength is without newline; aline includes newline,
// but may be falsy if lineLength == 0
linestylefilter.getLineStyleFilter = function(lineLength, aline,
textAndClassFunc, apool) {
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,109 +58,139 @@ 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) {
var key = apool.getAttribKey(n);
if (key) {
var value = apool.getAttribValue(n);
if (value) {
if (key == 'author') {
classes += ' '+linestylefilter.getAuthorClassName(value);
}
else if (key == 'list') {
classes += ' list:'+value;
Changeset.eachAttribNumber(attribs, function(n)
{
var key = apool.getAttribKey(n);
if (key)
{
var value = apool.getAttribValue(n);
if (value)
{
if (key == 'author')
{
classes += ' ' + linestylefilter.getAuthorClassName(value);
}
else if (linestylefilter.ATTRIB_CLASSES[key]) {
classes += ' '+linestylefilter.ATTRIB_CLASSES[key];
} else {
classes += plugins_.callHookStr("aceAttribsToClasses", {linestylefilter:linestylefilter, key:key, value:value}, " ", " ", "");
}
}
}
else if (key == 'list')
{
classes += ' list:' + value;
}
else if (linestylefilter.ATTRIB_CLASSES[key])
{
classes += ' ' + linestylefilter.ATTRIB_CLASSES[key];
}
else
{
classes += plugins_.callHookStr("aceAttribsToClasses", {
linestylefilter: linestylefilter,
key: key,
value: value
}, " ", " ", "");
}
}
}
});
return classes.substring(1);
}
var attributionIter = Changeset.opIterator(aline);
var nextOp, nextOpClasses;
function goNextOp() {
function goNextOp()
{
nextOp = attributionIter.next();
nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs));
}
goNextOp();
function nextClasses() {
if (curIndex < lineEnd) {
extraClasses = nextOpClasses;
leftInAuthor = nextOp.chars;
goNextOp();
while (nextOp.opcode && nextOpClasses == extraClasses) {
leftInAuthor += nextOp.chars;
goNextOp();
}
function nextClasses()
{
if (curIndex < lineEnd)
{
extraClasses = nextOpClasses;
leftInAuthor = nextOp.chars;
goNextOp();
while (nextOp.opcode && nextOpClasses == extraClasses)
{
leftInAuthor += nextOp.chars;
goNextOp();
}
}
}
nextClasses();
return function(txt, cls) {
while (txt.length > 0) {
if (leftInAuthor <= 0) {
// prevent infinite loop if something funny's going on
return nextAfterAuthorColors(txt, cls);
}
var spanSize = txt.length;
if (spanSize > leftInAuthor) {
spanSize = leftInAuthor;
}
var curTxt = txt.substring(0, spanSize);
txt = txt.substring(spanSize);
nextAfterAuthorColors(curTxt, (cls&&cls+" ")+extraClasses);
curIndex += spanSize;
leftInAuthor -= spanSize;
if (leftInAuthor == 0) {
nextClasses();
}
return function(txt, cls)
{
while (txt.length > 0)
{
if (leftInAuthor <= 0)
{
// prevent infinite loop if something funny's going on
return nextAfterAuthorColors(txt, cls);
}
var spanSize = txt.length;
if (spanSize > leftInAuthor)
{
spanSize = leftInAuthor;
}
var curTxt = txt.substring(0, spanSize);
txt = txt.substring(spanSize);
nextAfterAuthorColors(curTxt, (cls && cls + " ") + extraClasses);
curIndex += spanSize;
leftInAuthor -= spanSize;
if (leftInAuthor == 0)
{
nextClasses();
}
}
};
})();
return authorColorFunc;
};
linestylefilter.getAtSignSplitterFilter = function(lineText,
textAndClassFunc) {
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);
}
if (! splitPoints) return textAndClassFunc;
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) {
regExpMatchs = [];
splitPoints = [];
while ((execResult = regExp.exec(lineText)))
{
if (!regExpMatchs)
{
regExpMatchs = [];
splitPoints = [];
}
var startIndex = execResult.index;
var regExpMatch = execResult[0];
@ -165,126 +198,147 @@ linestylefilter.getRegexpFilter = function (regExp, tag) {
splitPoints.push(startIndex, startIndex + regExpMatch.length);
}
if (! regExpMatchs) return textAndClassFunc;
if (!regExpMatchs) return textAndClassFunc;
function regExpMatchForIndex(idx) {
for(var k=0; k<regExpMatchs.length; k++) {
var u = regExpMatchs[k];
if (idx >= u[0] && idx < u[0]+u[1].length) {
return u[1];
}
function regExpMatchForIndex(idx)
{
for (var k = 0; k < regExpMatchs.length; k++)
{
var u = regExpMatchs[k];
if (idx >= u[0] && idx < u[0] + u[1].length)
{
return u[1];
}
}
return false;
}
var handleRegExpMatchsAfterSplit = (function() {
var handleRegExpMatchsAfterSplit = (function()
{
var curIndex = 0;
return function(txt, cls) {
var txtlen = txt.length;
var newCls = cls;
var regExpMatch = regExpMatchForIndex(curIndex);
if (regExpMatch) {
newCls += " "+tag+":"+regExpMatch;
}
textAndClassFunc(txt, newCls);
curIndex += txtlen;
return function(txt, cls)
{
var txtlen = txt.length;
var newCls = cls;
var regExpMatch = regExpMatchForIndex(curIndex);
if (regExpMatch)
{
newCls += " " + tag + ":" + regExpMatch;
}
textAndClassFunc(txt, newCls);
curIndex += txtlen;
};
})();
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit,
splitPoints);
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, splitPoints);
};
};
linestylefilter.REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
linestylefilter.REGEX_URLCHAR = new RegExp('('+/[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source+'|'+linestylefilter.REGEX_WORDCHAR.source+')');
linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source+linestylefilter.REGEX_URLCHAR.source+'*(?![:.,;])'+linestylefilter.REGEX_URLCHAR.source, 'g');
linestylefilter.REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + linestylefilter.REGEX_WORDCHAR.source + ')');
linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + linestylefilter.REGEX_URLCHAR.source + '*(?![:.,;])' + linestylefilter.REGEX_URLCHAR.source, 'g');
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(
linestylefilter.REGEX_URL, 'url');
linestylefilter.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) {
func(txt, cls);
idx += txt.length;
if (pointLocInSpan == txtlen) {
nextPointIndex++;
}
if (pointLocInSpan >= txtlen)
{
func(txt, cls);
idx += txt.length;
if (pointLocInSpan == txtlen)
{
nextPointIndex++;
}
}
else {
if (pointLocInSpan > 0) {
func(txt.substring(0, pointLocInSpan), cls);
idx += pointLocInSpan;
}
nextPointIndex++;
// recurse
spanHandler(txt.substring(pointLocInSpan), cls);
else
{
if (pointLocInSpan > 0)
{
func(txt.substring(0, pointLocInSpan), cls);
idx += pointLocInSpan;
}
nextPointIndex++;
// recurse
spanHandler(txt.substring(pointLocInSpan), cls);
}
}
}
return spanHandler;
};
linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser) {
var func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
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.
func = linestylefilter.getAtSignSplitterFilter(
lineText, func);
lineText, func);
}
return func;
};
// 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') {
text = text.substring(0, text.length-1);
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

@ -28,9 +28,9 @@ linestylefilter.ATTRIB_CLASSES = {
'strikethrough': 'tag:s'
};
linestylefilter.getAuthorClassName = function (author)
linestylefilter.getAuthorClassName = function(author)
{
return "author-" + author.replace(/[^a-y0-9]/g, function (c)
return "author-" + author.replace(/[^a-y0-9]/g, function(c)
{
if (c == ".") return "-";
return 'z' + c.charCodeAt(0) + 'z';
@ -39,11 +39,11 @@ 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;
}
@ -56,7 +56,7 @@ linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFu
var nextAfterAuthorColors = textAndClassFunc;
var authorColorFunc = (function ()
var authorColorFunc = (function()
{
var lineEnd = lineLength;
var curIndex = 0;
@ -66,7 +66,7 @@ linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFu
function attribsToClasses(attribs)
{
var classes = '';
Changeset.eachAttribNumber(attribs, function (n)
Changeset.eachAttribNumber(attribs, function(n)
{
var key = apool.getAttribKey(n);
if (key)
@ -126,7 +126,7 @@ linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFu
}
nextClasses();
return function (txt, cls)
return function(txt, cls)
{
while (txt.length > 0)
{
@ -155,7 +155,7 @@ linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFu
return authorColorFunc;
};
linestylefilter.getAtSignSplitterFilter = function (lineText, textAndClassFunc)
linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc)
{
var at = /@/g;
at.lastIndex = 0;
@ -175,9 +175,9 @@ linestylefilter.getAtSignSplitterFilter = function (lineText, textAndClassFunc)
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
};
linestylefilter.getRegexpFilter = function (regExp, tag)
linestylefilter.getRegexpFilter = function(regExp, tag)
{
return function (lineText, textAndClassFunc)
return function(lineText, textAndClassFunc)
{
regExp.lastIndex = 0;
var regExpMatchs = null;
@ -211,10 +211,10 @@ linestylefilter.getRegexpFilter = function (regExp, tag)
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;
@ -239,7 +239,7 @@ 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;
@ -287,12 +287,12 @@ 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;
}
@ -305,7 +305,7 @@ linestylefilter.getFilterStack = function (lineText, textAndClassFunc, browser)
linestylefilter: linestylefilter,
browser: browser
});
hookFilters.map(function (hookFilter)
hookFilters.map(function(hookFilter)
{
func = hookFilter(lineText, func);
});
@ -322,7 +322,7 @@ 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;

View File

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -18,44 +18,52 @@
var socket;
$(document).ready(function() {
$(document).ready(function()
{
handshake();
});
$(window).unload(function() {
$(window).unload(function()
{
pad.dispose();
});
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();
}
else var expires = "";
document.cookie = name+"="+value+expires+"; path=/";
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();
}
else var expires = "";
document.cookie = name + "=" + value + expires + "; path=/";
}
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
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);
}
return null;
function readCookie(name)
{
var nameEQ = name + "=";
var ca = document.cookie.split(';');
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);
}
return null;
}
function randomString() {
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
var string_length = 20;
var randomstring = '';
for (var i=0; i<string_length; i++) {
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum,rnum+1);
}
return "t." + randomstring;
function randomString()
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
var string_length = 20;
var randomstring = '';
for (var i = 0; i < string_length; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return "t." + randomstring;
}
function handshake()
@ -67,67 +75,72 @@ function handshake()
var url = loc.protocol + "//" + loc.hostname + ":" + port + "/";
//find out in which subfolder we are
console.log(loc.pathname);
var resource = loc.pathname.substr(1,loc.pathname.indexOf("/p/")) + "socket.io";
var resource = loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "socket.io";
console.log(resource);
//connect
socket = io.connect(url, {resource: resource});
socket = io.connect(url, {
resource: resource
});
socket.on('connect', function(){
var padId= document.URL.substring(document.URL.lastIndexOf("/")+1);
document.title = document.title + " | " + padId;
var token = readCookie("token");
if(token == null)
socket.on('connect', function()
{
var padId = document.URL.substring(document.URL.lastIndexOf("/") + 1);
document.title = document.title + " | " + padId;
var token = readCookie("token");
if (token == null)
{
token = randomString();
createCookie("token", token, 60);
}
var msg = {
"component": "pad",
"type": "CLIENT_READY",
"padId": padId,
"token": token,
"protocolVersion": 2
};
socket.json.send(msg);
});
var receivedClientVars = false;
var initalized = false;
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);
receivedClientVars = true;
clientVars = obj;
clientVars.userAgent = navigator.userAgent;
clientVars.collab_client_vars.clientAgent = navigator.userAgent;
pad.init();
initalized = true;
}
//This handles every Message after the clientVars
else
{
if (obj.disconnect)
{
token = randomString();
createCookie("token", token, 60);
socket.disconnect();
padconnectionstatus.disconnected("userdup");
return;
}
var msg = { "component" : "pad",
"type":"CLIENT_READY",
"padId": padId,
"token": token,
"protocolVersion": 2};
socket.json.send(msg);
});
var receivedClientVars=false;
var initalized = false;
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);
receivedClientVars=true;
clientVars = obj;
clientVars.userAgent=navigator.userAgent;
clientVars.collab_client_vars.clientAgent=navigator.userAgent;
pad.init();
initalized=true;
}
//This handles every Message after the clientVars
else
{
if(obj.disconnect)
{
socket.disconnect();
padconnectionstatus.disconnected("userdup");
return;
}
else
{
pad.collabClient.handleMessageFromServer(obj);
}
pad.collabClient.handleMessageFromServer(obj);
}
});
}
});
}
var pad = {
@ -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,33 +236,34 @@ 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(),
initialTitle:clientVars.initialTitle,
initialPassword:clientVars.initialPassword,
guestPolicy: pad.padOptions.guestPolicy
});
paddocbar.init(
{
isTitleEditable: pad.getIsProPad(),
initialTitle: clientVars.initialTitle,
initialPassword: clientVars.initialPassword,
guestPolicy: pad.padOptions.guestPolicy
});
padimpexp.init();
padsavedrevs.init(clientVars.initialRevisionList);
padeditor.init(postAceInit, pad.padOptions.view || {});
paduserlist.init(pad.myUserInfo);
// padchat.init(clientVars.chatHistory, pad.myUserInfo);
// padchat.init(clientVars.chatHistory, pad.myUserInfo);
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() {
return (! pad.getIsProPad()) || (pad.getPadOptions().guestPolicy == 'allow');
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);
$('#djs').append('<p>'+m+'</p>');
if (wasAtBottom) {
var wasAtBottom = (djs.scrollTop - (djs.scrollHeight - $(djs).height()) >= -20);
$('#djs').append('<p>' + m + '</p>');
if (wasAtBottom)
{
djs.scrollTop = djs.scrollHeight;
}
}
},
handleServerMessage: function(m) {
if (m.type == 'NOTICE') {
if (m.text) {
alertBar.displayMessage(function (abar) {
abar.find("#servermsgdate").html(" ("+padutils.simpleDateTime(new Date)+")");
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) {
window['ev'+'al'](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;
}
@ -426,158 +532,194 @@ var pad = {
padsavedrevs.handleIsFullyConnected(isConnected);
pad.determineSidebarVisibility(isConnected && ! isInitialConnect);
pad.determineSidebarVisibility(isConnected && !isInitialConnect);
},
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);
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);
}
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() {
var newValue = ! padcookie.getPref('fullWidth');
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() {
var newValue = ! padcookie.getPref('hideSidebar');
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) {
$("#alertbar").css('opacity', state+1);
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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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;
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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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());
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,21 +96,27 @@ 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();
}
};
return self;
}());
}());

View File

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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) {
var wrapper = $("#"+panelName+"-wrapper");
var openingClass = "docbar"+panelName+"-opening";
var openClass = "docbar"+panelName+"-open";
var closingClass = "docbar"+panelName+"-closing";
function setPanelState(action) {
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)
{
$("#docbar").removeClass(openingClass).removeClass(openClass).
removeClass(closingClass);
if (action != "closed") {
$("#docbar").addClass("docbar"+panelName+"-"+action);
removeClass(closingClass);
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';
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);
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';
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() {
self.exitPassword(true);
},
function() {
self.exitPassword(false);
});
padutils.bindEnterAndEscape($("#security-passwordedit"), function()
{
self.exitPassword(true);
}, 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 {
$("#password-nonedit").toggleClass('nopassword', ! self.password);
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) {
return ! currentPanel;
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,67 +376,76 @@ 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() {
self.setShownPanel(null);
});
hideLaterIfNoOtherInteraction: function()
{
return padutils.getCancellableAction('hide-docbar-panel', function()
{
self.setShownPanel(null);
});
}
};
return self;

View File

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,50 +15,61 @@
*/
var padeditbar = (function(){
var padeditbar = (function()
{
var syncAnimation = (function() {
var syncAnimation = (function()
{
var SYNCING = -100;
var DONE = 100;
var state = DONE;
var fps = 25;
var step = 1/fps;
var step = 1 / fps;
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);
}, 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() {
return ! $("#editbar").hasClass('disabledtoolbar');
isEnabled: function()
{
return !$("#editbar").hasClass('disabledtoolbar');
},
disable: function() {
disable: function()
{
$("#editbar").addClass('disabledtoolbar').removeClass("enabledtoolbar");
},
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"){
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")
{
// increase the size of the editbar
//$('#editbar').animate({height:'72px'});
//$('#editorcontainerbox').animate({top:'72px'});
@ -97,18 +115,23 @@ var padeditbar = (function(){
$('#users').slideUp("fast");
}
}
if (cmd == 'embed') {
// embed shows the embed link
// get current height
var editbarheight = $('#embed').css('display');
if (editbarheight == "none"){
if (cmd == 'embed')
{
// embed shows the embed link
// get current height
var editbarheight = $('#embed').css('display');
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
$('#embed').html('<div id="embedcode">Embed code:<input id="embedinput" type="text" value="<iframe src=&quot;'+padurl+'&quot; width=500 height=400>"</iframe></div>');
$('#embed').html('<div id="embedcode">Embed code:<input id="embedinput" type="text" value="<iframe src=&quot;' + padurl + '&quot; width=500 height=400>"</iframe></div>');
$('#users').slideUp("fast");
$('#embed').slideDown("fast");
}
@ -116,46 +139,64 @@ 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)) {
ace.ace_doInsertUnorderedList();
}
} else if (cmd == 'outdent') {
}
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')
{
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 {
ace.ace_setAttributeOnSelection('author', '');
}
}
}, cmd, 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
{
ace.ace_setAttributeOnSelection('author', '');
}
}
}, cmd, true);
}
}
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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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,52 +82,59 @@ 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;
}
self.viewZoom = percent;
$("#viewzoommenu").val('z'+percent);
$("#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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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>'));
$('<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')) {
$('#importmessagesuccess').fadeOut("fast");
$('#importmessagefail').fadeOut("fast", function() {
showError(true); });
} else {
if ($('#importexport .importmessage').is(':visible'))
{
$('#importmessagesuccess').fadeOut("fast");
$('#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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -14,9 +14,10 @@
* limitations under the License.
*/
var padmodals = (function() {
var padmodals = (function()
{
/*var clearFeedbackEmail = function() {};
/*var clearFeedbackEmail = function() {};
function clearFeedback() {
clearFeedbackEmail();
$("#feedbackbox-message").val('');
@ -37,140 +38,187 @@ 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() {
/*var emailField = $("#feedbackbox-email");
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() {
/*$("#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) {
return (name || "Somebody")+" invited you to an EtherPad document";
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() {
self.hideModal();
});
hideFeedbackLaterIfNoOtherInteraction: function()
{
return padutils.getCancellableAction('hide-feedbackbox', function()
{
self.hideModal();
});
},
hideShareboxLaterIfNoOtherInteraction: function() {
return padutils.getCancellableAction('hide-sharebox',
function() {
self.hideModal();
});
hideShareboxLaterIfNoOtherInteraction: function()
{
return padutils.getCancellableAction('hide-sharebox', function()
{
self.hideModal();
});
},
/* sendFeedbackEmail: function() {
if (sendingFeedback) {
@ -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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,46 +15,51 @@
*/
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));
var viewLink = '/ep/pad/view/'+pad.getPadId()+'/'+revisionInfo.id;
box.find(".srauthor").html("by " + padutils.escapeHtml(revisionInfo.savedBy));
var viewLink = '/ep/pad/view/' + pad.getPadId() + '/' + revisionInfo.id;
box.find(".srview").attr('href', viewLink);
var restoreLink = 'javascript:void padsavedrevs.restoreRevision('+rnum+');';
var restoreLink = 'javascript:void padsavedrevs.restoreRevision(' + rnum + ');';
box.find(".srrestore").attr('href', restoreLink);
box.find(".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))));
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,195 +69,256 @@ 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(),
padId: pad.getPadId(),
revId: rev.id,
newLabel: newLabel},
data: {
userId: pad.getUserId(),
padId: pad.getPadId(),
revId: rev.id,
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) {
var nameOfLast = currentRevisionList[currentRevisionList.length-1].label;
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() {
savedTipAnimator.hide();
});
var hideLater = padutils.getCancellableAction("hide-revision-notifier", function()
{
savedTipAnimator.hide();
});
window.setTimeout(hideLater, 3000);
}
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 {
setScroll(curScroll + (desiredScroll - curScroll)*0.5);
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);
}, 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,59 +444,71 @@ var padsavedrevs = (function() {
var savedBy = pad.getUserName() || "unnamed";
pad.callWhenNotCommitting(submitSave);
function submitSave() {
$.ajax({
function submitSave()
{
$.ajax(
{
type: 'post',
url: '/ep/pad/saverevision',
data: {
padId: pad.getPadId(),
savedBy: savedBy,
savedById: pad.getUserId(),
revNum: pad.getCollabRevisionNumber()
padId: pad.getPadId(),
savedBy: savedBy,
savedById: pad.getUserId(),
revNum: pad.getCollabRevisionNumber()
},
success: success,
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);
setDesiredScroll(Math.max(0, n)*REVISION_BOX_WIDTH + minScroll);
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);
setDesiredScroll(maxScroll - Math.max(0, n)*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);
}
}
}
};
return self;
}());
}());

View File

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -21,117 +21,150 @@ 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() {
return "usertr"+(nextRowId.counter++);
function nextRowId()
{
return "usertr" + (nextRowId.counter++);
}
nextRowId.counter = 1;
// objects are shared; fields are "domId","data","animationStep"
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) {
var a = Math.abs(step/12);
if (power == 2) a = a*a;
else if (power == 3) a = a*a*a;
else if (power == 4) a = a*a*a*a;
else if (power >= 5) a = a*a*a*a*a;
return Math.round(26*(1-a));
function getAnimationHeight(step, power)
{
var a = Math.abs(step / 12);
if (power == 2) a = a * a;
else if (power == 3) a = a * a * a;
else if (power == 4) a = a * a * a * a;
else if (power >= 5) a = a * a * a * a * a;
return Math.round(26 * (1 - a));
}
var OPACITY_STEPS = 6;
var 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) {
return '<td colspan="'+NUMCOLS+'" style="border:0;height:'+height+'px"><!-- --></td>';
function getEmptyRowHtml(height)
{
return '<td colspan="' + NUMCOLS + '" style="border:0;height:' + height + 'px"><!-- --></td>';
}
function isNameEditable(data) {
return (! data.name) && (data.status != 'Disconnected');
function isNameEditable(data)
{
return (!data.name) && (data.status != 'Disconnected');
}
function replaceUserRowContents(tr, height, data) {
function replaceUserRowContents(tr, height, data)
{
var tds = getUserRowHtml(height, data).match(/<td.*?<\/td>/gi);
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) {
return '<tr id="'+id+'">'+innerHtml+'</tr>';
function getRowHtml(id, innerHtml)
{
return '<tr id="' + id + '">' + innerHtml + '</tr>';
}
function rowNode(row) {
return $("#"+row.domId);
function rowNode(row)
{
return $("#" + row.domId);
}
function handleRowData(row) {
if (row.data && row.data.status == 'Disconnected') {
function handleRowData(row)
{
if (row.data && row.data.status == 'Disconnected')
{
row.opacity = 0.5;
}
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 {
rowNode(rowsPresent[position-1]).after(tr);
else
{
rowNode(rowsPresent[position - 1]).after(tr);
}
if (animationPower != 0) {
if (animationPower != 0)
{
scheduleAnimation();
}
@ -175,32 +220,38 @@ 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 {
row.animationStep = - row.animationStep; // use symmetry
else
{
row.animationStep = -row.animationStep; // use symmetry
row.animationPower = animationPower;
rowsFadingOut.push(row);
scheduleAnimation();
@ -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) {
node.find("td").css('opacity', baseOpacity*(OPACITY_STEPS-(-step))/OPACITY_STEPS).height(animHeight);
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) {
node.find("td").css('opacity', baseOpacity*(OPACITY_STEPS - step)/OPACITY_STEPS).height(animHeight);
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,68 +432,82 @@ var paduserlist = (function() {
var knocksToIgnore = {};
var guestPromptFlashState = 0;
var guestPromptFlash = padutils.makeAnimationScheduler(
function () {
var prompts = $("#guestprompts .guestprompt");
if (prompts.length == 0) {
return false; // no more to do
}
guestPromptFlashState = 1 - guestPromptFlashState;
if (guestPromptFlashState) {
prompts.css('background', '#ffa');
}
else {
prompts.css('background', '#ffe');
}
function()
{
var prompts = $("#guestprompts .guestprompt");
if (prompts.length == 0)
{
return false; // no more to do
}
return true;
}, 1000);
guestPromptFlashState = 1 - guestPromptFlashState;
if (guestPromptFlashState)
{
prompts.css('background', '#ffa');
}
else
{
prompts.css('background', '#ffe');
}
return true;
}, 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() {
return myUserInfo.name || '';
},
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() {
self.renderMyUserInfo();
}, 0);
});
setUpEditable($("#myusernameedit"), function()
{
return myUserInfo.name || '';
}, 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()
{
self.renderMyUserInfo();
}, 0);
});
}
// 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;
}
@ -423,36 +519,41 @@ var paduserlist = (function() {
userData.activity = '';
userData.id = info.userId;
// Firefox ignores \n in title text; Safari does a linebreak
userData.titleText = [info.userAgent||'', info.ip||''].join(' \n');
userData.titleText = [info.userAgent || '', info.ip || ''].join(' \n');
var existingIndex = findExistingIndex(info.userId);
var 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++;
}
var infoN = otherUsersInfo[n];
var nameN = (infoN.name||'').toLowerCase();
var nameThis = (info.name||'').toLowerCase();
var nameN = (infoN.name || '').toLowerCase();
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,47 +562,55 @@ var paduserlist = (function() {
rowManager.moveRow(existingIndex, newIndex);
}
}
else {
else
{
otherUsersInfo.splice(newIndex, 0, info);
otherUsersData.splice(newIndex, 0, userData);
rowManager.insertRow(newIndex, userData);
}
updateInviteNotice();
updateInviteNotice();
self.updateNumberOfOnlineUsers();
},
updateNumberOfOnlineUsers: function(){
updateNumberOfOnlineUsers: function()
{
var online = 1; // you are always online!
for(var i=0;i<otherUsersData.length;i++) {
if(otherUsersData[i].status == "")
for (var i = 0; i < otherUsersData.length; i++)
{
if (otherUsersData[i].status == "")
{
online++;
}
}
$("#online_count").text(online);
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);
@ -511,50 +620,58 @@ var paduserlist = (function() {
}, 8000); // how long to wait
userData.leaveTimer = thisLeaveTimer;
}
updateInviteNotice();
self.updateNumberOfOnlineUsers();
updateInviteNotice();
self.updateNumberOfOnlineUsers();
},
showGuestPrompt: function(userId, displayName) {
if (knocksToIgnore[userId]) {
showGuestPrompt: function(userId, displayName)
{
if (knocksToIgnore[userId])
{
return;
}
var encodedUserId = padutils.encodeUserId(userId);
var actionName = 'hide-guest-prompt-'+encodedUserId;
var actionName = 'hide-guest-prompt-' + encodedUserId;
padutils.cancelActions(actionName);
var box = $("#guestprompt-"+encodedUserId);
if (box.length == 0) {
var box = $("#guestprompt-" + encodedUserId);
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>');
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));
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) {
var box = $("#guestprompt-"+padutils.encodeUserId(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);
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,61 +709,73 @@ var paduserlist = (function() {
return self;
}());
function getColorPickerSwatchIndex(jnode) {
// return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1;
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();
}
colorPickerOpen = false;
$("#mycolorpicker").fadeOut("fast");
}
function showColorPicker() {
previousColorId = myUserInfo.colorId ;
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]+';'
style: 'background: ' + palette[i] + ';'
});
li.appendTo(colorsList);
li.bind('click', function(event){
li.bind('click', function(event)
{
$("#colorpickerswatches li").removeClass('picked');
$(event.target).addClass("picked");
var newColorId = getColorPickerSwatchIndex($("#colorpickerswatches .picked"));
pad.notifyChangeColor(newColorId);
});
}
colorPickerSetup = true;
}
$("#mycolorpicker").fadeIn();
colorPickerOpen = true;
$("#colorpickerswatches li").removeClass('picked');
$($("#colorpickerswatches li")[myUserInfo.colorId]).addClass("picked"); //seems weird
}
}
}

View File

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,65 +15,81 @@
*/
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 (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) {
a = a.substr(0,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) {
return clean(name+m[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);
return clean('Safari' + v);
}
// everything else
@ -83,41 +99,49 @@ 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;
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) {
var x = Math.floor((low+high)/2); // x != low, x != high
var high = numItems - 1; // func(high) is always true
while ((high - low) > 1)
{
var x = Math.floor((low + high) / 2); // x != low, x != high
if (func(x)) high = x;
else low = x;
}
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()];
var dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()];
var month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()];
var dayOfMonth = d.getDate();
var year = d.getFullYear();
var hourmin = d.getHours()+":"+("0"+d.getMinutes()).slice(-2);
return dayOfWeek+' '+month+' '+dayOfMonth+' '+year+' '+hourmin;
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');
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);
}, 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 {
animationState = Math.max(-1, Math.min(0, - animationState));
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';
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') {
return String.fromCharCode(Number(cc.slice(1,-1)));
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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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,43 +65,61 @@ 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 lvl = numLevels - 1;
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)) {
i += n.downSkips[lvl];
ws += n.downSkipWidths[lvl];
n = n.downPtrs[lvl];
while (n.downPtrs[lvl] && (i + n.downSkips[lvl] < targetLoc))
{
i += n.downSkips[lvl];
ws += n.downSkipWidths[lvl];
n = n.downPtrs[lvl];
}
nodes[lvl] = n;
idxs[lvl] = i;
widthSkips[lvl] = ws;
lvl--;
if (lvl >= 0) {
nodes[lvl] = n;
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)) {
i += n.downSkipWidths[lvl];
n = n.downPtrs[lvl];
var lvl = start.levels - 1;
while (lvl >= 0 && n.downPtrs[lvl])
{
while (n.downPtrs[lvl] && (i + n.downSkipWidths[lvl] <= targetOffset))
{
i += n.downSkipWidths[lvl];
n = n.downPtrs[lvl];
}
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,21 +151,23 @@ 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) {
// assume we have just passed the end of point.nodes, and reached one level greater
// than the skiplist currently supports
pNodes[lvl] = start;
pIdxs[lvl] = -1;
start.levels++;
end.levels++;
start.downPtrs[lvl] = end;
end.upPtrs[lvl] = start;
start.downSkips[lvl] = numNodes+1;
start.downSkipWidths[lvl] = totalWidth;
point.widthSkips[lvl] = 0;
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;
pIdxs[lvl] = -1;
start.levels++;
end.levels++;
start.downPtrs[lvl] = end;
end.upPtrs[lvl] = start;
start.downSkips[lvl] = numNodes + 1;
start.downSkipWidths[lvl] = totalWidth;
point.widthSkips[lvl] = 0;
}
var me = newNode;
var up = pNodes[lvl];
@ -127,82 +187,101 @@ 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;
}
p.mark("map");
keyToNodeMap['$KEY$'+newKey] = newNode;
keyToNodeMap['$KEY$' + newKey] = newNode;
numNodes++;
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) {
point.idxs[i] += point.nodes[i].downSkips[i];
point.widthSkips[i] += point.nodes[i].downSkipWidths[i];
point.nodes[i] = point.nodes[i].downPtrs[i];
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) {
var up = elem.upPtrs[i];
var down = elem.downPtrs[i];
var totalSkip = up.downSkips[i] + elem.downSkips[i] - 1;
up.downPtrs[i] = down;
down.upPtrs[i] = up;
up.downSkips[i] = totalSkip;
var totalWidthSkip = up.downSkipWidths[i] + elem.downSkipWidths[i] - elemWidth;
up.downSkipWidths[i] = totalWidthSkip;
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;
up.downPtrs[i] = down;
down.upPtrs[i] = up;
up.downSkips[i] = totalSkip;
var totalWidthSkip = up.downSkipWidths[i] + elem.downSkipWidths[i] - elemWidth;
up.downSkipWidths[i] = totalWidthSkip;
}
else {
var up = point.nodes[i];
var down = up.downPtrs[i];
up.downSkips[i]--;
up.downSkipWidths[i] -= elemWidth;
else
{
var up = point.nodes[i];
var down = up.downPtrs[i];
up.downSkips[i]--;
up.downSkipWidths[i] -= elemWidth;
}
}
delete keyToNodeMap['$KEY$'+elem.key];
delete keyToNodeMap['$KEY$' + elem.key];
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]) {
n = 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) {
var lvl = n.levels-1;
while (n !== start)
{
var lvl = n.levels - 1;
n = n.upPtrs[lvl];
if (byWidth) dist += n.downSkipWidths[lvl];
else dist += n.downSkips[lvl];
}
return dist;
}
/*function _debugToString() {
/*function _debugToString() {
var array = [start];
while (array[array.length-1] !== end) {
array[array.length] = array[array.length-1].downPtrs[0];
@ -224,32 +303,40 @@ function newSkipList() {
return map(processedArray, function (x) { return x.toSource(); }).join("\n");
}*/
function _getNodeByKey(key) {
return keyToNodeMap['$KEY$'+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 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)) {
lowIndex += low.downSkips[lvl];
low = nextLow;
nextLow = low.downPtrs[lvl];
while (!f(nextLow))
{
lowIndex += low.downSkips[lvl];
low = nextLow;
nextLow = low.downPtrs[lvl];
}
lvl--;
}
return lowIndex+1;
return lowIndex + 1;
}
/*
@ -257,43 +344,55 @@ The skip-list contains "entries", JavaScript objects that each must have a uniqu
that is a string.
*/
var self = {
length: function() { return numNodes; },
atIndex: function(i) {
if (i < 0) console.warn("atIndex("+i+")");
if (i >= numNodes) console.warn("atIndex("+i+">="+numNodes+")");
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) {
if (start < 0) console.warn("splice("+start+", ...)");
if (start + deleteCount > numNodes) {
console.warn("splice("+start+", "+deleteCount+", ...), N="+numNodes);
console.warn("%s %s %s", typeof start, typeof deleteCount, typeof numNodes);
console.trace();
splice: function(start, deleteCount, newEntryArray)
{
if (start < 0) console.warn("splice(" + start + ", ...)");
if (start + deleteCount > numNodes)
{
console.warn("splice(" + start + ", " + deleteCount + ", ...), N=" + numNodes);
console.warn("%s %s %s", typeof start, typeof deleteCount, typeof numNodes);
console.trace();
}
if (! newEntryArray) newEntryArray = [];
if (!newEntryArray) newEntryArray = [];
var pt = _getPoint(start);
for(var i=0;i<deleteCount;i++) {
_deleteKeyAtPoint(pt);
for (var i = 0; i < deleteCount; i++)
{
_deleteKeyAtPoint(pt);
}
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;
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;
@ -305,43 +404,81 @@ that is a string.
if (end < 0) end = 0;
if (end > numNodes) end = numNodes;
dmesg(String([start,end,numNodes]));
dmesg(String([start, end, numNodes]));
if (end <= start) return [];
var n = self.atIndex(start);
var array = [n];
for(var i=1;i<(end-start);i++) {
n = self.next(n);
array.push(n);
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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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,119 +30,153 @@ 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) {
var idx = stackElements.length-1;
if (stackElements[idx].elementType == EXTERNAL_CHANGE) {
stackElements[idx].changeset = Changeset.compose(stackElements[idx].changeset, cs, getAPool());
function pushExternalChange(cs)
{
var idx = stackElements.length - 1;
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) {
var ex = stackElements[idx];
var un = stackElements[idx-1];
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") {
var newSel = Changeset.characterRangeFollow(excs, un.selStart, un.selEnd);
un.selStart = newSel[0];
un.selEnd = newSel[1];
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());
stackElements.splice(idx-2, 1);
idx--;
}
}
else {
idx--;
}
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)
{
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")
{
var newSel = Changeset.characterRangeFollow(excs, un.selStart, un.selEnd);
un.selStart = newSel[0];
un.selEnd = newSel[1];
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());
stackElements.splice(idx - 2, 1);
idx--;
}
}
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) {
count++;
i++;
if (i >= 0)
{
count++;
i++;
}
}
return count;
}
function _opcodeOccurrences(cs, opcode) {
function _opcodeOccurrences(cs, opcode)
{
return _charOccurrences(Changeset.unpack(cs).ops, opcode);
}
function _mergeChangesets(cs1, cs2) {
if (! cs1) return cs2;
if (! cs2) return cs1;
function _mergeChangesets(cs1, cs2)
{
if (!cs1) return cs2;
if (!cs2) return cs1;
// Rough heuristic for whether changesets should be considered one action:
// each does exactly one insertion, no dels, and the composition does also; or
@ -152,71 +188,91 @@ 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) {
return merge;
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) {
return merge;
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") {
topEvent.selStart = event.selStart;
topEvent.selEnd = event.selEnd;
topEvent.selFocusAtStart = event.selFocusAtStart;
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) {
var merge = _mergeChangesets(event.backset, topEvent.backset);
if (merge) {
topEvent.backset = merge;
//dmesg("reportEvent merge: "+merge);
applySelectionToTop();
merged = true;
}
if (topEvent.eventType == event.eventType)
{
var merge = _mergeChangesets(event.backset, topEvent.backset);
if (merge)
{
topEvent.backset = merge;
//dmesg("reportEvent merge: "+merge);
applySelectionToTop();
merged = true;
}
}
if (! merged) {
stack.pushEvent(event);
if (!merged)
{
stack.pushEvent(event);
}
undoPtr = 0;
}
}
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,10 +282,12 @@ 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 selectionEvent = stack.getNthFromTop(undoPtr + 1);
var undoEvent = eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent));
stack.pushEvent(undoEvent);
undoPtr += 2;
@ -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

@ -1,12 +1,12 @@
/**
* Copyright 2009 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -14,8 +14,9 @@
* 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
// significant fraction of a millisecond). From the
@ -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,75 +43,112 @@ 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();
var theLine = seeker.getVirtualLine();
// go to offset, overshooting the virtual line only if offset is too large for it
seeker.forwardByWhile(maxCharIncrement, null, lineStart+offset);
seeker.forwardByWhile(maxCharIncrement, null, lineStart + offset);
// get back into line
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;
theOffset += lineLen - lineChar;
lineChar = lineLen;
}
return { vline:theLine, offset:theOffset, lineChar:lineChar };
return {
vline: theLine,
offset: theOffset,
lineChar: lineChar
};
}
return {getNumVirtualLines:getNumVirtualLines, getVLineAndOffsetForChar:getVLineAndOffsetForChar,
getCharForVLineAndOffset:getCharForVLineAndOffset,
makeCharSeeker: function() { return makeCharSeeker(); } };
return {
getNumVirtualLines: getNumVirtualLines,
getVLineAndOffsetForChar: getVLineAndOffsetForChar,
getCharForVLineAndOffset: getCharForVLineAndOffset,
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 charCoords(tnode, i) {
function makeCharSeeker( /*lineNode*/ )
{
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)) {
tnode = deepFirstChildTextNode(container.previousSibling);
i = tnode.length-1;
container = tnode.parentNode;
}
else {
return {top:container.offsetTop, left:container.offsetLeft};
}
}
else {
i--; // use previous char
}
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
{
i--; // use previous char
}
}
@ -119,16 +160,18 @@ function makeVirtualLineView(lineNode) {
frag.appendChild(document.createTextNode(tnodeText.substring(0, i)));
charWrapper.appendChild(document.createTextNode(tnodeText.substr(i, 1)));
frag.appendChild(charWrapper);
frag.appendChild(document.createTextNode(tnodeText.substring(i+1)));
frag.appendChild(document.createTextNode(tnodeText.substring(i + 1)));
container.replaceChild(frag, tnode);
var result = {top:charWrapper.offsetTop,
left:charWrapper.offsetLeft + (isSpace ? charWrapper.offsetWidth : 0),
height:charWrapper.offsetHeight};
var result = {
top: charWrapper.offsetTop,
left: charWrapper.offsetLeft + (isSpace ? charWrapper.offsetWidth : 0),
height: charWrapper.offsetHeight
};
while (container.firstChild) container.removeChild(container.firstChild);
container.appendChild(tnode);
return result;
}
@ -143,136 +186,186 @@ function makeVirtualLineView(lineNode) {
var approxLineHeight;
var whichLine = 0;
function nextNode() {
function nextNode()
{
var n = curNode;
if (! n) n = lineNode.firstChild;
if (!n) n = lineNode.firstChild;
else n = n.nextSibling;
while (n && ! deepFirstChildTextNode(n)) {
n = n.nextSibling;
while (n && !deepFirstChildTextNode(n))
{
n = n.nextSibling;
}
return n;
}
function prevNode() {
function prevNode()
{
var n = curNode;
if (! n) n = lineNode.lastChild;
if (!n) n = lineNode.lastChild;
else n = n.previousSibling;
while (n && ! deepFirstChildTextNode(n)) {
n = n.previousSibling;
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) {
var coords = charCoords(tnode, i);
whichLine += Math.round((coords.top - curTop) / approxLineHeight);
curTop = coords.top;
curLeft = coords.left;
function updateCharData(tnode, i)
{
var coords = charCoords(tnode, i);
whichLine += Math.round((coords.top - curTop) / approxLineHeight);
curTop = coords.top;
curLeft = coords.left;
}
seeker = {
forward: function(numChars) {
var oldChar = curChar;
var newChar = curChar + numChars;
if (newChar > (lineLength-1))
newChar = lineLength-1;
while (curChar < newChar) {
var curNodeLength = deepFirstChildTextNode(curNode).length;
var toGo = curNodeLength - curCharWithinNode;
if (curChar + toGo > newChar || ! nextNode()) {
// going to next node would be too far
var n = newChar - curChar;
if (n >= toGo) n = toGo-1;
curChar += n;
curCharWithinNode += n;
break;
}
else {
// go to next node
curChar += toGo;
curCharWithinNode = 0;
curNode = nextNode();
}
}
updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode);
return curChar - oldChar;
},
backward: function(numChars) {
var oldChar = curChar;
var newChar = curChar - numChars;
if (newChar < 0) newChar = 0;
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;
curChar -= n;
curCharWithinNode -= n;
break;
}
else {
// go to prev node
curChar -= curCharWithinNode+1;
curNode = prevNode();
curCharWithinNode = deepFirstChildTextNode(curNode).length-1;
}
}
updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode);
return oldChar - curChar;
},
getVirtualLine: function() { return whichLine; },
getLeftCoord: function() { return curLeft; }
forward: function(numChars)
{
var oldChar = curChar;
var newChar = curChar + numChars;
if (newChar > (lineLength - 1)) newChar = lineLength - 1;
while (curChar < newChar)
{
var curNodeLength = deepFirstChildTextNode(curNode).length;
var toGo = curNodeLength - curCharWithinNode;
if (curChar + toGo > newChar || !nextNode())
{
// going to next node would be too far
var n = newChar - curChar;
if (n >= toGo) n = toGo - 1;
curChar += n;
curCharWithinNode += n;
break;
}
else
{
// go to next node
curChar += toGo;
curCharWithinNode = 0;
curNode = nextNode();
}
}
updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode);
return curChar - oldChar;
},
backward: function(numChars)
{
var oldChar = curChar;
var newChar = curChar - numChars;
if (newChar < 0) newChar = 0;
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;
curChar -= n;
curCharWithinNode -= n;
break;
}
else
{
// go to prev node
curChar -= curCharWithinNode + 1;
curNode = prevNode();
curCharWithinNode = deepFirstChildTextNode(curNode).length - 1;
}
}
updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode);
return oldChar - curChar;
},
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() {
return "seeker[curChar: "+curChar+"("+lineText.charAt(curChar)+"), left: "+seeker.getLeftCoord()+", vline: "+seeker.getVirtualLine()+"]";
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())) {
var toMove = amount;
if (hasCharLimit) {
var untilLimit = (isBackward ? curChar - charLimit : charLimit - curChar);
if (untilLimit < toMove) toMove = untilLimit;
}
if (toMove < 0) break;
charsMovedLast = (isBackward ? seeker.backward(toMove) : seeker.forward(toMove));
while (charsMovedLast !== 0 && ((!hasCondFunc) || condFunc()))
{
var toMove = amount;
if (hasCharLimit)
{
var untilLimit = (isBackward ? curChar - charLimit : charLimit - curChar);
if (untilLimit < toMove) toMove = untilLimit;
}
if (toMove < 0) break;
charsMovedLast = (isBackward ? seeker.backward(toMove) : seeker.forward(toMove));
}
}
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);
@ -280,7 +373,7 @@ function makeVirtualLineView(lineNode) {
seeker.forwardByWhile(1, falseFunc);
return seeker.getOffset() + (condFunc() ? 0 : 1);
}
return seeker;
}