The Big Renaming - etherpad is now an NPM module

This commit is contained in:
Egil Moeller 2012-02-26 13:07:51 +01:00
parent 1955bdec9a
commit 1239ce7f28
116 changed files with 9721 additions and 30 deletions

View file

@ -1,90 +0,0 @@
/**
* This code represents the Attribute Pool Object of the original Etherpad.
* 90% of the code is still like in the original Etherpad
* Look at https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js
* You can find a explanation what a attribute pool is here:
* https://github.com/Pita/etherpad-lite/blob/master/doc/easysync/easysync-notes.txt
*/
/*
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
exports.createAttributePool = function () {
var p = {};
p.numToAttrib = {}; // e.g. {0: ['foo','bar']}
p.attribToNum = {}; // e.g. {'foo,bar': 0}
p.nextNum = 0;
p.putAttrib = function (attrib, dontAddIfAbsent) {
var str = String(attrib);
if (str in p.attribToNum) {
return p.attribToNum[str];
}
if (dontAddIfAbsent) {
return -1;
}
var num = p.nextNum++;
p.attribToNum[str] = num;
p.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')];
return 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) {
var pair = p.numToAttrib[num];
if (!pair) return '';
return pair[0];
};
p.getAttribValue = function (num) {
var pair = p.numToAttrib[num];
if (!pair) return '';
return pair[1];
};
p.eachAttrib = function (func) {
for (var n in p.numToAttrib) {
var pair = p.numToAttrib[n];
func(pair[0], pair[1]);
}
};
p.toJsonable = function () {
return {
numToAttrib: p.numToAttrib,
nextNum: p.nextNum
};
};
p.fromJsonable = function (obj) {
p.numToAttrib = obj.numToAttrib;
p.nextNum = obj.nextNum;
p.attribToNum = {};
for (var n in p.numToAttrib) {
p.attribToNum[String(p.numToAttrib[n])] = Number(n);
}
return p;
};
return p;
}

File diff suppressed because it is too large Load diff

View file

@ -1,370 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// requires: top
// requires: plugins
// requires: undefined
Ace2Editor.registry = {
nextId: 1
};
var plugins = require('/plugins').plugins;
function Ace2Editor()
{
var ace2 = Ace2Editor;
var editor = {};
var info = {
editor: editor,
id: (ace2.registry.nextId++)
};
var loaded = false;
var actionsPendingInit = [];
function pendingInit(func, optDoNow)
{
return function()
{
var that = this;
var args = arguments;
function action()
{
func.apply(that, args);
}
if (optDoNow)
{
optDoNow.apply(that, args);
}
if (loaded)
{
action();
}
else
{
actionsPendingInit.push(action);
}
};
}
function doActionsPendingInit()
{
for (var i = 0; i < actionsPendingInit.length; i++)
{
actionsPendingInit[i]();
}
actionsPendingInit = [];
}
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";
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.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);
});
// 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).
// If this method returns a truthy value, then applyPreparedChangesetToBase can be called
// at some later point to consider these changes part of the base, after which prepareUserChangeset
// must be called again before applyPreparedChangesetToBase. Multiple consecutive calls
// 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;
return info.ace_prepareUserChangeset();
};
editor.applyPreparedChangesetToBase = pendingInit(
function()
{
info.ace_applyPreparedChangesetToBase();
});
editor.setUserChangeNotificationCallback = pendingInit(function(callback)
{
info.ace_setUserChangeNotificationCallback(callback);
});
editor.setAuthorInfo = pendingInit(function(author, authorInfo)
{
info.ace_setAuthorInfo(author, authorInfo);
});
editor.setAuthorSelectionRange = pendingInit(function(author, start, end)
{
info.ace_setAuthorSelectionRange(author, start, end);
});
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)
{
return info.ace_callWithAce(fn, callStack, normalize);
});
editor.execCommand = pendingInit(function(cmd, arg1)
{
info.ace_execCommand(cmd, arg1);
});
editor.replaceRange = pendingInit(function(start, end, text)
{
info.ace_replaceRange(start, end, text);
});
function sortFilesByEmbeded(files) {
var embededFiles = [];
var remoteFiles = [];
if (Ace2Editor.EMBEDED) {
for (var i = 0, ii = files.length; i < ii; i++) {
var file = files[i];
if (Object.prototype.hasOwnProperty.call(Ace2Editor.EMBEDED, file)) {
embededFiles.push(file);
} else {
remoteFiles.push(file);
}
}
} else {
remoteFiles = files;
}
return {embeded: embededFiles, remote: remoteFiles};
}
function pushRequireScriptTo(buffer) {
var KERNEL_SOURCE = '../static/js/require-kernel.js';
var KERNEL_BOOT = 'require.setRootURI("../minified/");\nrequire.setGlobalKeyPath("require");'
if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) {
buffer.push('<script type="text/javascript">');
buffer.push(Ace2Editor.EMBEDED[KERNEL_SOURCE]);
buffer.push(KERNEL_BOOT);
buffer.push('<\/script>');
}
}
function pushScriptsTo(buffer) {
/* Folling is for packaging regular expression. */
/* $$INCLUDE_JS("../static/js/ace2_inner.js"); */
var ACE_SOURCE = '../static/js/ace2_inner.js';
if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[ACE_SOURCE]) {
buffer.push('<script type="text/javascript">');
buffer.push(Ace2Editor.EMBEDED[ACE_SOURCE]);
buffer.push('require("/ace2_inner");');
buffer.push('<\/script>');
} else {
file = ACE_SOURCE;
file = file.replace(/^\.\.\/static\/js\//, '../minified/');
buffer.push('<script type="application/javascript" src="' + file + '"><\/script>');
buffer.push('<script type="text/javascript">');
buffer.push('require("/ace2_inner");');
buffer.push('<\/script>');
}
}
function pushStyleTagsFor(buffer, files) {
var sorted = sortFilesByEmbeded(files);
var embededFiles = sorted.embeded;
var remoteFiles = sorted.remote;
if (embededFiles.length > 0) {
buffer.push('<style type="text/css">');
for (var i = 0, ii = embededFiles.length; i < ii; i++) {
var file = embededFiles[i];
buffer.push(Ace2Editor.EMBEDED[file].replace(/<\//g, '<\\/'));
}
buffer.push('<\/style>');
}
for (var i = 0, ii = remoteFiles.length; i < ii; i++) {
var file = remoteFiles[i];
buffer.push('<link rel="stylesheet" type="text/css" href="' + file + '"\/>');
}
}
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.importText(initialCode);
info.onEditorReady = function()
{
loaded = true;
doActionsPendingInit();
doneFunc();
};
(function()
{
var doctype = "<!doctype html>";
var iframeHTML = [];
iframeHTML.push(doctype);
iframeHTML.push("<html><head>");
// For compatability's sake transform in and out.
for (var i = 0, ii = iframeHTML.length; i < ii; i++) {
iframeHTML[i] = JSON.stringify(iframeHTML[i]);
}
plugins.callHook("aceInitInnerdocbodyHead", {
iframeHTML: iframeHTML
});
for (var i = 0, ii = iframeHTML.length; i < ii; i++) {
iframeHTML[i] = JSON.parse(iframeHTML[i]);
}
// 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.
// these lines must conform to a specific format because they are passed by the build script:
var includedCSS = [];
var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)};
$$INCLUDE_CSS("../static/css/iframe_editor.css");
$$INCLUDE_CSS("../static/css/pad.css");
$$INCLUDE_CSS("../static/custom/pad.css");
pushStyleTagsFor(iframeHTML, includedCSS);
var includedJS = [];
var $$INCLUDE_JS = function(filename) {includedJS.push(filename)};
pushRequireScriptTo(iframeHTML);
// Inject my plugins into my child.
iframeHTML.push('\
<script type="text/javascript">\
require.define("/plugins", null);\n\
require.define("/plugins.js", function (require, exports, module) {\
module.exports = parent.parent.require("/plugins");\
});\
</script>\
');
pushScriptsTo(iframeHTML);
iframeHTML.push('<style type="text/css" title="dynamicsyntax"></style>');
iframeHTML.push('</head><body id="innerdocbody" class="syntax" spellcheck="false">&nbsp;</body></html>');
// Expose myself to global for my child frame.
var thisFunctionsName = "ChildAccessibleAce2Editor";
(function () {return this}())[thisFunctionsName] = Ace2Editor;
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 = (' + JSON.stringify(iframeHTML.join('\n')) + ');doc.write(text); doc.close(); ' + '}, 0); }';
var outerHTML = [doctype, '<html><head>']
var includedCSS = [];
var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)};
$$INCLUDE_CSS("../static/css/iframe_editor.css");
$$INCLUDE_CSS("../static/css/pad.css");
$$INCLUDE_CSS("../static/custom/pad.css");
pushStyleTagsFor(outerHTML, includedCSS);
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
// (throbs busy while typing)
outerHTML.push('<link rel="stylesheet" type="text/css" href="data:text/css,"/>', '\x3cscript>\n', outerScript.replace(/<\//g, '<\\/'), '\n\x3c/script>', '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>');
var outerFrame = document.createElement("IFRAME");
outerFrame.frameBorder = 0; // for IE
info.frame = outerFrame;
document.getElementById(containerId).appendChild(outerFrame);
var editorDocument = outerFrame.contentWindow.document;
editorDocument.open();
editorDocument.write(outerHTML.join(''));
editorDocument.close();
})();
};
return editor;
}
exports.Ace2Editor = Ace2Editor;

View file

@ -1,157 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Security = require('/security');
function isNodeText(node)
{
return (node.nodeType == 3);
}
function object(o)
{
var f = function()
{};
f.prototype = o;
return new f();
}
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++)
{
var result = func(array[i], i);
if (result) break;
}
}
function map(array, func)
{
var result = [];
// must remain compatible with "arguments" pseudo-array
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)
{
var result = [];
// must remain compatible with "arguments" pseudo-array
for (var i = 0; i < array.length; i++)
{
if (func(array[i], i)) result.push(array[i]);
}
return result;
}
function isArray(testObject)
{
return testObject && typeof testObject === 'object' && !(testObject.propertyIsEnumerable('length')) && typeof testObject.length === 'number';
}
var userAgent = (((function () {return this;})().navigator || {}).userAgent || 'node-js').toLowerCase();
// Figure out what browser is being used (stolen from jquery 1.2.1)
var browser = {
version: (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
safari: /webkit/.test(userAgent),
opera: /opera/.test(userAgent),
msie: /msie/.test(userAgent) && !/opera/.test(userAgent),
mozilla: /mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent),
windows: /windows/.test(userAgent),
mobile: /mobile/.test(userAgent) || /android/.test(userAgent)
};
function getAssoc(obj, name)
{
return obj["_magicdom_" + name];
}
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;
}
// "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)
{
if (numItems < 1) return 0;
if (func(0)) return 0;
if (!func(numItems - 1)) return numItems;
var low = 0; // func(low) is always false
var high = numItems - 1; // func(high) is always true
while ((high - low) > 1)
{
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)
{
var i = 0;
while (!func(i)) i += expectedLength;
return binarySearch(i, func);
}
function htmlPrettyEscape(str)
{
return Security.escapeHTML(str).replace(/\r?\n/g, '\\n');
}
exports.isNodeText = isNodeText;
exports.object = object;
exports.extend = extend;
exports.forEach = forEach;
exports.map = map;
exports.filter = filter;
exports.isArray = isArray;
exports.browser = browser;
exports.getAssoc = getAssoc;
exports.setAssoc = setAssoc;
exports.binarySearch = binarySearch;
exports.binarySearchInfinite = binarySearchInfinite;
exports.htmlPrettyEscape = htmlPrettyEscape;
exports.map = map;

File diff suppressed because it is too large Load diff

View file

@ -1,774 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var makeCSSManager = require('/cssmanager').makeCSSManager;
var domline = require('/domline').domline;
var AttribPool = require('/AttributePoolFactory').createAttributePool;
var Changeset = require('/Changeset');
var linestylefilter = require('/linestylefilter').linestylefilter;
var colorutils = require('/colorutils').colorutils;
// These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate.
function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider)
{
var changesetLoader = undefined;
// just in case... (todo: this must be somewhere else in the client code.)
// Below Array#map code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_map.htm
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/ )
{
var len = this.length >>> 0;
if (typeof fun != "function") throw new TypeError();
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this) res[i] = fun.call(thisp, this[i], i, this);
}
return res;
};
}
// Below Array#forEach code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_foreach.htm
if (!Array.prototype.forEach)
{
Array.prototype.forEach = function(fun /*, thisp*/ )
{
var len = this.length >>> 0;
if (typeof fun != "function") throw new TypeError();
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this) fun.call(thisp, this[i], i, this);
}
};
}
// Below Array#indexOf code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_indexof.htm
if (!Array.prototype.indexOf)
{
Array.prototype.indexOf = function(elt /*, from*/ )
{
var len = this.length >>> 0;
var from = Number(arguments[1]) || 0;
from = (from < 0) ? Math.ceil(from) : Math.floor(from);
if (from < 0) from += len;
for (; from < len; from++)
{
if (from in this && this[from] === elt) return from;
}
return -1;
};
}
function debugLog()
{
try
{
if (window.console) console.log.apply(console, arguments);
}
catch (e)
{
if (window.console) console.log("error printing: ", e);
}
}
function randomString()
{
return "_" + Math.floor(Math.random() * 1000000);
}
// for IE
if ($.browser.msie)
{
try
{
document.execCommand("BackgroundImageCache", false, true);
}
catch (e)
{}
}
var userId = "hiddenUser" + randomString();
var socketId;
//var socket;
var channelState = "DISCONNECTED";
var appLevelDisconnectReason = null;
var padContents = {
currentRevision: clientVars.revNum,
currentTime: clientVars.currentTime,
currentLines: Changeset.splitTextLines(clientVars.initialStyledContents.atext.text),
currentDivs: null,
// to be filled in once the dom loads
apool: (new AttribPool()).fromJsonable(clientVars.initialStyledContents.apool),
alines: Changeset.splitAttributionLines(
clientVars.initialStyledContents.atext.attribs, clientVars.initialStyledContents.atext.text),
// generates a jquery element containing HTML for a line
lineToElement: function(line, aline)
{
var element = document.createElement("div");
var emptyLine = (line == '\n');
var domInfo = domline.createDomLine(!emptyLine, true);
linestylefilter.populateDomLine(line, aline, this.apool, domInfo);
domInfo.prepareForAdd();
element.className = domInfo.node.className;
element.innerHTML = domInfo.node.innerHTML;
element.id = Math.random();
return $(element);
},
applySpliceToDivs: function(start, numRemoved, newLines)
{
// remove spliced-out lines from DOM
for (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++)
{
debugLog("removing", this.currentDivs[i].attr('id'));
this.currentDivs[i].remove();
}
// remove spliced-out line divs from currentDivs array
this.currentDivs.splice(start, numRemoved);
var newDivs = [];
for (var i = 0; i < newLines.length; i++)
{
newDivs.push(this.lineToElement(newLines[i], this.alines[start + i]));
}
// grab the div just before the first one
var startDiv = this.currentDivs[start - 1] || null;
// insert the div elements into the correct place, in the correct order
for (var i = 0; i < newDivs.length; i++)
{
if (startDiv)
{
startDiv.after(newDivs[i]);
}
else
{
$("#padcontent").prepend(newDivs[i]);
}
startDiv = newDivs[i];
}
// insert new divs into currentDivs array
newDivs.unshift(0); // remove 0 elements
newDivs.unshift(start);
this.currentDivs.splice.apply(this.currentDivs, newDivs);
return this;
},
// splice the lines
splice: function(start, numRemoved, newLinesVA)
{
var newLines = Array.prototype.slice.call(arguments, 2).map(
function(s)
{
return s;
});
// apply this splice to the divs
this.applySpliceToDivs(start, numRemoved, newLines);
// call currentLines.splice, to keep the currentLines array up to date
newLines.unshift(numRemoved);
newLines.unshift(start);
this.currentLines.splice.apply(this.currentLines, arguments);
},
// returns the contents of the specified line I
get: function(i)
{
return this.currentLines[i];
},
// returns the number of lines in the document
length: function()
{
return this.currentLines.length;
},
getActiveAuthors: function()
{
var self = this;
var authors = [];
var seenNums = {};
var alines = self.alines;
for (var i = 0; i < alines.length; i++)
{
Changeset.eachAttribNumber(alines[i], function(n)
{
if (!seenNums[n])
{
seenNums[n] = true;
if (self.apool.getAttribKey(n) == 'author')
{
var a = self.apool.getAttribValue(n);
if (a)
{
authors.push(a);
}
}
}
});
}
authors.sort();
return authors;
}
};
function callCatchingErrors(catcher, func)
{
try
{
wrapRecordingErrors(catcher, func)();
}
catch (e)
{ /*absorb*/
}
}
function wrapRecordingErrors(catcher, func)
{
return function()
{
try
{
return func.apply(this, Array.prototype.slice.call(arguments));
}
catch (e)
{
// caughtErrors.push(e);
// caughtErrorCatchers.push(catcher);
// caughtErrorTimes.push(+new Date());
// console.dir({catcher: catcher, e: e});
debugLog(e); // TODO(kroo): added temporary, to catch errors
throw e;
}
};
}
function loadedNewChangeset(changesetForward, changesetBackward, revision, timeDelta)
{
var broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest);
debugLog("broadcasting:", broadcasting, BroadcastSlider.getSliderPosition(), revisionInfo.latest, revision);
revisionInfo.addChangeset(revision, revision + 1, changesetForward, changesetBackward, timeDelta);
BroadcastSlider.setSliderLength(revisionInfo.latest);
if (broadcasting) applyChangeset(changesetForward, revision + 1, false, timeDelta);
}
/*
At this point, we must be certain that the changeset really does map from
the current revision to the specified revision. Any mistakes here will
cause the whole slider to get out of sync.
*/
function applyChangeset(changeset, revision, preventSliderMovement, timeDelta)
{
// disable the next 'gotorevision' call handled by a timeslider update
if (!preventSliderMovement)
{
goToRevisionIfEnabledCount++;
BroadcastSlider.setSliderPosition(revision);
}
try
{
// must mutate attribution lines before text lines
Changeset.mutateAttributionLines(changeset, padContents.alines, padContents.apool);
}
catch (e)
{
debugLog(e);
}
Changeset.mutateTextLines(changeset, padContents);
padContents.currentRevision = revision;
padContents.currentTime += timeDelta * 1000;
debugLog('Time Delta: ', timeDelta)
updateTimer();
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name)
{
return authorData[name];
}));
}
function updateTimer()
{
var zpad = function(str, length)
{
str = str + "";
while (str.length < length)
str = '0' + str;
return str;
}
var date = new Date(padContents.currentTime);
var dateFormat = function()
{
var month = zpad(date.getMonth() + 1, 2);
var day = zpad(date.getDate(), 2);
var year = (date.getFullYear());
var hours = zpad(date.getHours(), 2);
var minutes = zpad(date.getMinutes(), 2);
var seconds = zpad(date.getSeconds(), 2);
return ([month, '/', day, '/', year, ' ', hours, ':', minutes, ':', seconds].join(""));
}
$('#timer').html(dateFormat());
var revisionDate = ["Saved", ["Jan", "Feb", "March", "April", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec"][date.getMonth()], date.getDate() + ",", date.getFullYear()].join(" ")
$('#revision_date').html(revisionDate)
}
updateTimer();
function goToRevision(newRevision)
{
padContents.targetRevision = newRevision;
var self = this;
var path = revisionInfo.getPath(padContents.currentRevision, newRevision);
debugLog('newRev: ', padContents.currentRevision, path);
if (path.status == 'complete')
{
var cs = path.changesets;
debugLog("status: complete, changesets: ", cs, "path:", path);
var changeset = cs[0];
var timeDelta = path.times[0];
for (var i = 1; i < cs.length; i++)
{
changeset = Changeset.compose(changeset, cs[i], padContents.apool);
timeDelta += path.times[i];
}
if (changeset) applyChangeset(changeset, path.rev, true, timeDelta);
}
else if (path.status == "partial")
{
debugLog('partial');
var sliderLocation = padContents.currentRevision;
// callback is called after changeset information is pulled from server
// this may never get called, if the changeset has already been loaded
var update = function(start, end)
{
// if we've called goToRevision in the time since, don't goToRevision
goToRevision(padContents.targetRevision);
};
// do our best with what we have...
var cs = path.changesets;
var changeset = cs[0];
var timeDelta = path.times[0];
for (var i = 1; i < cs.length; i++)
{
changeset = Changeset.compose(changeset, cs[i], padContents.apool);
timeDelta += path.times[i];
}
if (changeset) applyChangeset(changeset, path.rev, true, timeDelta);
if (BroadcastSlider.getSliderLength() > 10000)
{
var start = (Math.floor((newRevision) / 10000) * 10000); // revision 0 to 10
changesetLoader.queueUp(start, 100);
}
if (BroadcastSlider.getSliderLength() > 1000)
{
var start = (Math.floor((newRevision) / 1000) * 1000); // (start from -1, go to 19) + 1
changesetLoader.queueUp(start, 10);
}
start = (Math.floor((newRevision) / 100) * 100);
changesetLoader.queueUp(start, 1, update);
}
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name)
{
return authorData[name];
}));
}
changesetLoader = {
running: false,
resolved: [],
requestQueue1: [],
requestQueue2: [],
requestQueue3: [],
reqCallbacks: [],
queueUp: function(revision, width, callback)
{
if (revision < 0) revision = 0;
// if(changesetLoader.requestQueue.indexOf(revision) != -1)
// return; // already in the queue.
if (changesetLoader.resolved.indexOf(revision + "_" + width) != -1) return; // already loaded from the server
changesetLoader.resolved.push(revision + "_" + width);
var requestQueue = width == 1 ? changesetLoader.requestQueue3 : width == 10 ? changesetLoader.requestQueue2 : changesetLoader.requestQueue1;
requestQueue.push(
{
'rev': revision,
'res': width,
'callback': callback
});
if (!changesetLoader.running)
{
changesetLoader.running = true;
setTimeout(changesetLoader.loadFromQueue, 10);
}
},
loadFromQueue: function()
{
var self = changesetLoader;
var requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null;
if (!requestQueue)
{
self.running = false;
return;
}
var request = requestQueue.pop();
var granularity = request.res;
var callback = request.callback;
var start = request.rev;
var requestID = Math.floor(Math.random() * 100000);
/*var msg = { "component" : "timeslider",
"type":"CHANGESET_REQ",
"padId": padId,
"token": token,
"protocolVersion": 2,
"data"
{
"start": start,
"granularity": granularity
}};
socket.send(msg);*/
sendSocketMsg("CHANGESET_REQ", {
"start": start,
"granularity": granularity,
"requestID": requestID
});
self.reqCallbacks[requestID] = callback;
/*debugLog("loadinging revision", start, "through ajax");
$.getJSON("/ep/pad/changes/" + clientVars.padIdForUrl + "?s=" + start + "&g=" + granularity, function (data, textStatus)
{
if (textStatus !== "success")
{
console.log(textStatus);
BroadcastSlider.showReconnectUI();
}
self.handleResponse(data, start, granularity, callback);
setTimeout(self.loadFromQueue, 10); // load the next ajax function
});*/
},
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);
},
handleResponse: function(data, start, granularity, callback)
{
debugLog("response: ", data);
var pool = (new AttribPool()).fromJsonable(data.apool);
for (var i = 0; i < data.forwardsChangesets.length; i++)
{
var astart = start + i * granularity - 1; // rev -1 is a blank single line
var aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision
if (aend > data.actualEndNum - 1) aend = data.actualEndNum - 1;
debugLog("adding changeset:", astart, aend);
var forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
var backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]);
}
if (callback) callback(start - 1, start + data.forwardsChangesets.length * granularity - 1);
}
};
function handleMessageFromServer()
{
debugLog("handleMessage:", arguments);
var obj = arguments[0]['data'];
var expectedType = "COLLABROOM";
obj = JSON.parse(obj);
if (obj['type'] == expectedType)
{
obj = obj['data'];
if (obj['type'] == "NEW_CHANGES")
{
debugLog(obj);
var changeset = Changeset.moveOpsToNewPool(
obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
var changesetBack = Changeset.moveOpsToNewPool(
obj.changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta);
}
else if (obj['type'] == "NEW_AUTHORDATA")
{
var authorMap = {};
authorMap[obj.author] = obj.data;
receiveAuthorData(authorMap);
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name)
{
return authorData[name];
}));
}
else if (obj['type'] == "NEW_SAVEDREV")
{
var savedRev = obj.savedRev;
BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev);
}
}
else
{
debugLog("incorrect message type: " + obj['type'] + ", expected " + expectedType);
}
}
function handleSocketClosed(params)
{
debugLog("socket closed!", params);
socket = null;
BroadcastSlider.showReconnectUI();
// var reason = appLevelDisconnectReason || params.reason;
// var shouldReconnect = params.reconnect;
// if (shouldReconnect) {
// // determine if this is a tight reconnect loop due to weird connectivity problems
// // reconnectTimes.push(+new Date());
// var TOO_MANY_RECONNECTS = 8;
// var TOO_SHORT_A_TIME_MS = 10000;
// if (reconnectTimes.length >= TOO_MANY_RECONNECTS &&
// ((+new Date()) - reconnectTimes[reconnectTimes.length-TOO_MANY_RECONNECTS]) <
// TOO_SHORT_A_TIME_MS) {
// setChannelState("DISCONNECTED", "looping");
// }
// else {
// setChannelState("RECONNECTING", reason);
// setUpSocket();
// }
// }
// else {
// BroadcastSlider.showReconnectUI();
// setChannelState("DISCONNECTED", reason);
// }
}
function sendMessage(msg)
{
socket.postMessage(JSON.stringify(
{
type: "COLLABROOM",
data: msg
}));
}
/*function setUpSocket()
{
// required for Comet
if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0)))
{
document.domain = document.domain; // for comet
}
var success = false;
callCatchingErrors("setUpSocket", function ()
{
appLevelDisconnectReason = null;
socketId = String(Math.floor(Math.random() * 1e12));
socket = new WebSocket(socketId);
socket.onmessage = wrapRecordingErrors("socket.onmessage", handleMessageFromServer);
socket.onclosed = wrapRecordingErrors("socket.onclosed", handleSocketClosed);
socket.onopen = wrapRecordingErrors("socket.onopen", function ()
{
setChannelState("CONNECTED");
var msg = {
type: "CLIENT_READY",
roomType: 'padview',
roomName: 'padview/' + clientVars.viewId,
data: {
lastRev: clientVars.revNum,
userInfo: {
userId: userId
}
}
};
sendMessage(msg);
});
// socket.onhiccup = wrapRecordingErrors("socket.onhiccup", handleCometHiccup);
// socket.onlogmessage = function(x) {debugLog(x); };
socket.connect();
success = true;
});
if (success)
{
//initialStartConnectTime = +new Date();
}
else
{
abandonConnection("initsocketfail");
}
}*/
function setChannelState(newChannelState, moreInfo)
{
if (newChannelState != channelState)
{
channelState = newChannelState;
// callbacks.onChannelStateChange(channelState, moreInfo);
}
}
function abandonConnection(reason)
{
if (socket)
{
socket.onclosed = function()
{};
socket.onhiccup = function()
{};
socket.disconnect();
}
socket = null;
setChannelState("DISCONNECTED", reason);
}
/*window['onloadFuncts'] = [];
window.onload = function ()
{
window['isloaded'] = true;
window['onloadFuncts'].forEach(function (funct)
{
funct();
});
};*/
// to start upon window load, just push a function onto this array
//window['onloadFuncts'].push(setUpSocket);
//window['onloadFuncts'].push(function ()
fireWhenAllScriptsAreLoaded.push(function()
{
// set up the currentDivs and DOM
padContents.currentDivs = [];
$("#padcontent").html("");
for (var i = 0; i < padContents.currentLines.length; i++)
{
var div = padContents.lineToElement(padContents.currentLines[i], padContents.alines[i]);
padContents.currentDivs.push(div);
$("#padcontent").append(div);
}
debugLog(padContents.currentDivs);
});
// this is necessary to keep infinite loops of events firing,
// since goToRevision changes the slider position
var goToRevisionIfEnabledCount = 0;
var goToRevisionIfEnabled = function()
{
if (goToRevisionIfEnabledCount > 0)
{
goToRevisionIfEnabledCount--;
}
else
{
goToRevision.apply(goToRevision, arguments);
}
}
BroadcastSlider.onSlider(goToRevisionIfEnabled);
(function()
{
for (var i = 0; i < clientVars.initialChangesets.length; i++)
{
var csgroup = clientVars.initialChangesets[i];
var start = clientVars.initialChangesets[i].start;
var granularity = clientVars.initialChangesets[i].granularity;
debugLog("loading changest on startup: ", start, granularity, csgroup);
changesetLoader.handleResponse(csgroup, start, granularity, null);
}
})();
var dynamicCSS = makeCSSManager('dynamicsyntax');
var authorData = {};
function receiveAuthorData(newAuthorData)
{
for (var author in newAuthorData)
{
var data = newAuthorData[author];
var bgcolor = typeof data.colorId == "number" ? clientVars.colorPalette[data.colorId] : data.colorId;
if (bgcolor && dynamicCSS)
{
var selector = dynamicCSS.selectorStyle('.' + linestylefilter.getAuthorClassName(author));
selector.backgroundColor = bgcolor
selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5) ? '#ffffff' : '#000000'; //see ace2_inner.js for the other part
}
authorData[author] = data;
}
}
receiveAuthorData(clientVars.historicalAuthorData);
return changesetLoader;
}
exports.loadBroadcastJS = loadBroadcastJS;

View file

@ -1,128 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// revision info is a skip list whos entries represent a particular revision
// of the document. These revisions are connected together by various
// changesets, or deltas, between any two revisions.
function loadBroadcastRevisionsJS()
{
function Revision(revNum)
{
this.rev = revNum;
this.changesets = [];
}
Revision.prototype.addChangeset = function(destIndex, changeset, timeDelta)
{
var changesetWrapper = {
deltaRev: destIndex - this.rev,
deltaTime: timeDelta,
getValue: function()
{
return changeset;
}
};
this.changesets.push(changesetWrapper);
this.changesets.sort(function(a, b)
{
return (b.deltaRev - a.deltaRev)
});
}
revisionInfo = {};
revisionInfo.addChangeset = function(fromIndex, toIndex, changeset, backChangeset, timeDelta)
{
var startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
var endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex);
startRevision.addChangeset(toIndex, changeset, timeDelta);
endRevision.addChangeset(fromIndex, backChangeset, -1 * timeDelta);
}
revisionInfo.latest = clientVars.totalRevs || -1;
revisionInfo.createNew = function(index)
{
revisionInfo[index] = new Revision(index);
if (index > revisionInfo.latest)
{
revisionInfo.latest = index;
}
return revisionInfo[index];
}
// assuming that there is a path from fromIndex to toIndex, and that the links
// are laid out in a skip-list format
revisionInfo.getPath = function(fromIndex, toIndex)
{
var changesets = [];
var spans = [];
var times = [];
var elem = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
if (elem.changesets.length != 0 && fromIndex != toIndex)
{
var reverse = !(fromIndex < toIndex)
while (((elem.rev < toIndex) && !reverse) || ((elem.rev > toIndex) && reverse))
{
var couldNotContinue = false;
var oldRev = elem.rev;
for (var i = reverse ? elem.changesets.length - 1 : 0;
reverse ? i >= 0 : i < elem.changesets.length;
i += reverse ? -1 : 1)
{
if (((elem.changesets[i].deltaRev < 0) && !reverse) || ((elem.changesets[i].deltaRev > 0) && reverse))
{
couldNotContinue = true;
break;
}
if (((elem.rev + elem.changesets[i].deltaRev <= toIndex) && !reverse) || ((elem.rev + elem.changesets[i].deltaRev >= toIndex) && reverse))
{
var topush = elem.changesets[i];
changesets.push(topush.getValue());
spans.push(elem.changesets[i].deltaRev);
times.push(topush.deltaTime);
elem = revisionInfo[elem.rev + elem.changesets[i].deltaRev];
break;
}
}
if (couldNotContinue || oldRev == elem.rev) break;
}
}
var status = 'partial';
if (elem.rev == toIndex) status = 'complete';
return {
'fromRev': fromIndex,
'rev': elem.rev,
'status': status,
'changesets': changesets,
'spans': spans,
'times': times
};
}
}
exports.loadBroadcastRevisionsJS = loadBroadcastRevisionsJS;

View file

@ -1,504 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate.
function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
{
var BroadcastSlider;
(function()
{ // wrap this code in its own namespace
var sliderLength = 1000;
var sliderPos = 0;
var sliderActive = false;
var slidercallbacks = [];
var savedRevisions = [];
var sliderPlaying = false;
function disableSelection(element)
{
element.onselectstart = function()
{
return false;
};
element.unselectable = "on";
element.style.MozUserSelect = "none";
element.style.cursor = "default";
}
var _callSliderCallbacks = function(newval)
{
sliderPos = newval;
for (var i = 0; i < slidercallbacks.length; i++)
{
slidercallbacks[i](newval);
}
}
var updateSliderElements = function()
{
for (var i = 0; i < savedRevisions.length; i++)
{
var position = parseInt(savedRevisions[i].attr('pos'));
savedRevisions[i].css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1);
}
$("#ui-slider-handle").css('left', sliderPos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0));
}
var addSavedRevision = function(position, info)
{
var newSavedRevision = $('<div></div>');
newSavedRevision.addClass("star");
newSavedRevision.attr('pos', position);
newSavedRevision.css('position', 'absolute');
newSavedRevision.css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1);
$("#timeslider-slider").append(newSavedRevision);
newSavedRevision.mouseup(function(evt)
{
BroadcastSlider.setSliderPosition(position);
});
savedRevisions.push(newSavedRevision);
};
var removeSavedRevision = function(position)
{
var element = $("div.star [pos=" + position + "]");
savedRevisions.remove(element);
element.remove();
return element;
};
/* Begin small 'API' */
function onSlider(callback)
{
slidercallbacks.push(callback);
}
function getSliderPosition()
{
return sliderPos;
}
function setSliderPosition(newpos)
{
newpos = Number(newpos);
if (newpos < 0 || newpos > sliderLength) return;
$("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0));
$("a.tlink").map(function()
{
$(this).attr('href', $(this).attr('thref').replace("%revision%", newpos));
});
$("#revision_label").html("Version " + newpos);
if (newpos == 0)
{
$("#leftstar").css('opacity', .5);
$("#leftstep").css('opacity', .5);
}
else
{
$("#leftstar").css('opacity', 1);
$("#leftstep").css('opacity', 1);
}
if (newpos == sliderLength)
{
$("#rightstar").css('opacity', .5);
$("#rightstep").css('opacity', .5);
}
else
{
$("#rightstar").css('opacity', 1);
$("#rightstep").css('opacity', 1);
}
sliderPos = newpos;
_callSliderCallbacks(newpos);
}
function getSliderLength()
{
return sliderLength;
}
function setSliderLength(newlength)
{
sliderLength = newlength;
updateSliderElements();
}
// just take over the whole slider screen with a reconnect message
function showReconnectUI()
{
if (!clientVars.sliderEnabled || !clientVars.supportsSlider)
{
$("#padmain, #rightbars").css('top', "130px");
$("#timeslider").show();
}
$('#error').show();
}
function setAuthors(authors)
{
$("#authorstable").empty();
var numAnonymous = 0;
var numNamed = 0;
authors.forEach(function(author)
{
if (author.name)
{
numNamed++;
var tr = $('<tr></tr>');
var swatchtd = $('<td></td>');
var swatch = $('<div class="swatch"></div>');
swatch.css('background-color', clientVars.colorPalette[author.colorId]);
swatchtd.append(swatch);
tr.append(swatchtd);
var nametd = $('<td></td>');
nametd.text(author.name || "unnamed");
tr.append(nametd);
$("#authorstable").append(tr);
}
else
{
numAnonymous++;
}
});
if (numAnonymous > 0)
{
var html = "<tr><td colspan=\"2\" style=\"color:#999; padding-left: 10px\">" + (numNamed > 0 ? "...and " : "") + numAnonymous + " unnamed author" + (numAnonymous > 1 ? "s" : "") + "</td></tr>";
$("#authorstable").append($(html));
}
if (authors.length == 0)
{
$("#authorstable").append($("<tr><td colspan=\"2\" style=\"color:#999; padding-left: 10px\">No Authors</td></tr>"))
}
}
BroadcastSlider = {
onSlider: onSlider,
getSliderPosition: getSliderPosition,
setSliderPosition: setSliderPosition,
getSliderLength: getSliderLength,
setSliderLength: setSliderLength,
isSliderActive: function()
{
return sliderActive;
},
playpause: playpause,
addSavedRevision: addSavedRevision,
showReconnectUI: showReconnectUI,
setAuthors: setAuthors
}
function playButtonUpdater()
{
if (sliderPlaying)
{
if (getSliderPosition() + 1 > sliderLength)
{
$("#playpause_button_icon").toggleClass('pause');
sliderPlaying = false;
return;
}
setSliderPosition(getSliderPosition() + 1);
setTimeout(playButtonUpdater, 100);
}
}
function playpause()
{
$("#playpause_button_icon").toggleClass('pause');
if (!sliderPlaying)
{
if (getSliderPosition() == sliderLength) setSliderPosition(0);
sliderPlaying = true;
playButtonUpdater();
}
else
{
sliderPlaying = false;
}
}
// assign event handlers to html UI elements after page load
//$(window).load(function ()
fireWhenAllScriptsAreLoaded.push(function()
{
disableSelection($("#playpause_button")[0]);
disableSelection($("#timeslider")[0]);
if (clientVars.sliderEnabled && clientVars.supportsSlider)
{
$(document).keyup(function(e)
{
var code = -1;
if (!e) var e = window.event;
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
if (code == 37)
{ // left
if (!e.shiftKey)
{
setSliderPosition(getSliderPosition() - 1);
}
else
{
var nextStar = 0; // default to first revision in document
for (var i = 0; i < savedRevisions.length; i++)
{
var pos = parseInt(savedRevisions[i].attr('pos'));
if (pos < getSliderPosition() && nextStar < pos) nextStar = pos;
}
setSliderPosition(nextStar);
}
}
else if (code == 39)
{
if (!e.shiftKey)
{
setSliderPosition(getSliderPosition() + 1);
}
else
{
var nextStar = sliderLength; // default to last revision in document
for (var i = 0; i < savedRevisions.length; i++)
{
var pos = parseInt(savedRevisions[i].attr('pos'));
if (pos > getSliderPosition() && nextStar > pos) nextStar = pos;
}
setSliderPosition(nextStar);
}
}
else if (code == 32) playpause();
});
}
$(window).resize(function()
{
updateSliderElements();
});
$("#ui-slider-bar").mousedown(function(evt)
{
setSliderPosition(Math.floor((evt.clientX - $("#ui-slider-bar").offset().left) * sliderLength / 742));
$("#ui-slider-handle").css('left', (evt.clientX - $("#ui-slider-bar").offset().left));
$("#ui-slider-handle").trigger(evt);
});
// Slider dragging
$("#ui-slider-handle").mousedown(function(evt)
{
this.startLoc = evt.clientX;
this.currentLoc = parseInt($(this).css('left'));
var self = this;
sliderActive = true;
$(document).mousemove(function(evt2)
{
$(self).css('pointer', 'move')
var newloc = self.currentLoc + (evt2.clientX - self.startLoc);
if (newloc < 0) newloc = 0;
if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2);
$("#revision_label").html("Version " + Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2)));
$(self).css('left', newloc);
if (getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))) _callSliderCallbacks(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2)))
});
$(document).mouseup(function(evt2)
{
$(document).unbind('mousemove');
$(document).unbind('mouseup');
sliderActive = false;
var newloc = self.currentLoc + (evt2.clientX - self.startLoc);
if (newloc < 0) newloc = 0;
if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2);
$(self).css('left', newloc);
// if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2)))
setSliderPosition(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2)))
self.currentLoc = parseInt($(self).css('left'));
});
})
// play/pause toggling
$("#playpause_button").mousedown(function(evt)
{
var self = this;
$(self).css('background-image', 'url(/static/img/crushed_button_depressed.png)');
$(self).mouseup(function(evt2)
{
$(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)');
$(self).unbind('mouseup');
BroadcastSlider.playpause();
});
$(document).mouseup(function(evt2)
{
$(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)');
$(document).unbind('mouseup');
});
});
// next/prev saved revision and changeset
$('.stepper').mousedown(function(evt)
{
var self = this;
var origcss = $(self).css('background-position');
if (!origcss)
{
origcss = $(self).css('background-position-x') + " " + $(self).css('background-position-y');
}
var origpos = parseInt(origcss.split(" ")[1]);
var newpos = (origpos - 43);
if (newpos < 0) newpos += 87;
var newcss = (origcss.split(" ")[0] + " " + newpos + "px");
if ($(self).css('opacity') != 1.0) newcss = origcss;
$(self).css('background-position', newcss)
$(self).mouseup(function(evt2)
{
$(self).css('background-position', origcss);
$(self).unbind('mouseup');
$(document).unbind('mouseup');
if ($(self).attr("id") == ("leftstep"))
{
setSliderPosition(getSliderPosition() - 1);
}
else if ($(self).attr("id") == ("rightstep"))
{
setSliderPosition(getSliderPosition() + 1);
}
else if ($(self).attr("id") == ("leftstar"))
{
var nextStar = 0; // default to first revision in document
for (var i = 0; i < savedRevisions.length; i++)
{
var pos = parseInt(savedRevisions[i].attr('pos'));
if (pos < getSliderPosition() && nextStar < pos) nextStar = pos;
}
setSliderPosition(nextStar);
}
else if ($(self).attr("id") == ("rightstar"))
{
var nextStar = sliderLength; // default to last revision in document
for (var i = 0; i < savedRevisions.length; i++)
{
var pos = parseInt(savedRevisions[i].attr('pos'));
if (pos > getSliderPosition() && nextStar > pos) nextStar = pos;
}
setSliderPosition(nextStar);
}
});
$(document).mouseup(function(evt2)
{
$(self).css('background-position', origcss);
$(self).unbind('mouseup');
$(document).unbind('mouseup');
});
})
if (clientVars)
{
if (clientVars.fullWidth)
{
$("#padpage").css('width', '100%');
$("#revision").css('position', "absolute")
$("#revision").css('right', "20px")
$("#revision").css('top', "20px")
$("#padmain").css('left', '0px');
$("#padmain").css('right', '197px');
$("#padmain").css('width', 'auto');
$("#rightbars").css('right', '7px');
$("#rightbars").css('margin-right', '0px');
$("#timeslider").css('width', 'auto');
}
if (clientVars.disableRightBar)
{
$("#rightbars").css('display', 'none');
$('#padmain').css('width', 'auto');
if (clientVars.fullWidth) $("#padmain").css('right', '7px');
else $("#padmain").css('width', '860px');
$("#revision").css('position', "absolute");
$("#revision").css('right', "20px");
$("#revision").css('top', "20px");
}
if (clientVars.sliderEnabled)
{
if (clientVars.supportsSlider)
{
$("#padmain, #rightbars").css('top', "130px");
$("#timeslider").show();
setSliderLength(clientVars.totalRevs);
setSliderPosition(clientVars.revNum);
clientVars.savedRevisions.forEach(function(revision)
{
addSavedRevision(revision.revNum, revision);
})
}
else
{
// slider is not supported
$("#padmain, #rightbars").css('top', "130px");
$("#timeslider").show();
$("#error").html("The timeslider feature is not supported on this pad. <a href=\"/ep/about/faq#disabledslider\">Why not?</a>");
$("#error").show();
}
}
else
{
if (clientVars.supportsSlider)
{
setSliderLength(clientVars.totalRevs);
setSliderPosition(clientVars.revNum);
}
}
}
});
})();
BroadcastSlider.onSlider(function(loc)
{
$("#viewlatest").html(loc == BroadcastSlider.getSliderLength() ? "Viewing latest content" : "View latest content");
})
return BroadcastSlider;
}
exports.loadBroadcastSliderJS = loadBroadcastSliderJS;

View file

@ -1,213 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var AttribPool = require('/AttributePoolFactory').createAttributePool;
var Changeset = require('/Changeset');
function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
{
// latest official text from server
var baseAText = Changeset.makeAText("\n");
// changes applied to baseText that have been submitted
var submittedChangeset = null;
// changes applied to submittedChangeset since it was prepared
var userChangeset = Changeset.identity(1);
// is the changesetTracker enabled
var tracking = false;
// stack state flag so that when we change the rep we don't
// handle the notification recursively. When setting, always
// unset in a "finally" block. When set to true, the setter
// takes change of userChangeset.
var applyingNonUserChanges = false;
var changeCallback = null;
var changeCallbackTimeout = null;
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;
}
}, 0);
}
}
var self;
return self = {
isTracking: function()
{
return tracking;
},
setBaseText: function(text)
{
self.setBaseAttributedText(Changeset.makeAText(text), null);
},
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);
}
submittedChangeset = null;
userChangeset = Changeset.identity(atext.text.length);
applyingNonUserChanges = true;
try
{
callbacks.setDocumentAttributedText(atext);
}
finally
{
applyingNonUserChanges = false;
}
});
},
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;
aceCallbacksProvider.withCallbacks("applyChangesToBase", function(callbacks)
{
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);
}
var preferInsertingAfterUserChanges = true;
var oldUserChangeset = userChangeset;
userChangeset = Changeset.follow(c2, oldUserChangeset, preferInsertingAfterUserChanges, apool);
var postChange = Changeset.follow(oldUserChangeset, c2, !preferInsertingAfterUserChanges, apool);
var preferInsertionAfterCaret = true; //(optAuthor && optAuthor > thisAuthor);
applyingNonUserChanges = true;
try
{
callbacks.applyChangesetToDocument(postChange, preferInsertionAfterCaret);
}
finally
{
applyingNonUserChanges = false;
}
});
},
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);
}
else
{
if (Changeset.isIdentity(userChangeset)) toSubmit = null;
else toSubmit = userChangeset;
}
var cs = null;
if (toSubmit)
{
submittedChangeset = toSubmit;
userChangeset = Changeset.identity(Changeset.newLen(toSubmit));
cs = toSubmit;
}
var wireApool = null;
if (cs)
{
var forWire = Changeset.prepareForWire(cs, apool);
wireApool = forWire.pool.toJsonable();
cs = forWire.translated;
}
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");
}
//bumpDebug("applying committed changeset: "+submittedChangeset.encodeToString(false));
baseAText = Changeset.applyToAText(submittedChangeset, baseAText, apool);
submittedChangeset = null;
},
setUserChangeNotificationCallback: function(callback)
{
changeCallback = callback;
},
hasUncommittedChanges: function()
{
return !!(submittedChangeset || (!Changeset.isIdentity(userChangeset)));
}
};
}
exports.makeChangesetTracker = makeChangesetTracker;

View file

@ -1,162 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
var padcookie = require('/pad_cookie').padcookie;
var chat = (function()
{
var isStuck = false;
var chatMentions = 0;
var title = document.title;
var self = {
show: function ()
{
$("#chaticon").hide();
$("#chatbox").show();
self.scrollDown();
chatMentions = 0;
document.title = title;
},
stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen
{
chat.show();
if(!isStuck || fromInitialCall) { // Stick it to
padcookie.setPref("chatAlwaysVisible", true);
$('#chatbox').css({"right":"0px", "top":"36px", "border-radius":"0px", "height":"auto", "border-right":"none", "border-left":"1px solid #ccc", "border-top":"none", "background-color":"#f1f1f1", "width":"185px"});
$('#chattext').css({"top":"0px"});
$('#editorcontainer').css({"right":"192px", "width":"auto"});
isStuck = true;
} else { // Unstick it
padcookie.setPref("chatAlwaysVisible", false);
$('#chatbox').css({"right":"20px", "top":"auto", "border-top-left-radius":"5px", "border-top-right-radius":"5px", "border-right":"1px solid #999", "height":"200px", "border-top":"1px solid #999", "background-color":"#f7f7f7"});
$('#chattext').css({"top":"25px"});
$('#editorcontainer').css({"right":"0px", "width":"100%"});
isStuck = false;
}
},
hide: function ()
{
$("#chatcounter").text("0");
$("#chaticon").show();
$("#chatbox").hide();
},
scrollDown: function()
{
if($('#chatbox').css("display") != "none")
$('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, "slow");
},
send: function()
{
var text = $("#chatinput").val();
this._pad.collabClient.sendMessage({"type": "CHAT_MESSAGE", "text": text});
$("#chatinput").val("");
},
addMessage: function(msg, increment)
{
//correct the time
msg.time += this._pad.clientTimeOffset;
//create the time string
var minutes = "" + new Date(msg.time).getMinutes();
var hours = "" + new Date(msg.time).getHours();
if(minutes.length == 1)
minutes = "0" + minutes ;
if(hours.length == 1)
hours = "0" + hours ;
var timeStr = hours + ":" + minutes;
//create the authorclass
var authorClass = "author-" + msg.userId.replace(/[^a-y0-9]/g, function(c)
{
if (c == ".") return "-";
return 'z' + c.charCodeAt(0) + 'z';
});
var text = padutils.escapeHtmlWithClickableLinks(msg.text, "_blank");
/* Performs an action if your name is mentioned */
var myName = $('#myusernameedit').val();
myName = myName.toLowerCase();
var chatText = text.toLowerCase();
var wasMentioned = false;
if (chatText.indexOf(myName) !== -1 && myName != "undefined"){
wasMentioned = true;
}
/* End of new action */
var authorName = msg.userName == null ? "unnamed" : padutils.escapeHtml(msg.userName);
var html = "<p class='" + authorClass + "'><b>" + authorName + ":</b><span class='time " + authorClass + "'>" + timeStr + "</span> " + text + "</p>";
$("#chattext").append(html);
//should we increment the counter??
if(increment)
{
var count = Number($("#chatcounter").text());
count++;
$("#chatcounter").text(count);
// chat throb stuff -- Just make it throw for twice as long
if(wasMentioned)
{ // If the user was mentioned show for twice as long and flash the browser window
if (chatMentions == 0){
title = document.title;
}
$('#chatthrob').html("<b>"+authorName+"</b>" + ": " + text).show().delay(4000).hide(400);
chatMentions++;
document.title = "("+chatMentions+") " + title;
}
else
{
$('#chatthrob').html("<b>"+authorName+"</b>" + ": " + text).show().delay(2000).hide(400);
}
}
self.scrollDown();
},
init: function(pad)
{
this._pad = pad;
$("#chatinput").keypress(function(evt)
{
//if the user typed enter, fire the send
if(evt.which == 13)
{
evt.preventDefault();
self.send();
}
});
for(var i in clientVars.chatHistory)
{
this.addMessage(clientVars.chatHistory[i], false);
}
$("#chatcounter").text(clientVars.chatHistory.length);
}
}
return self;
}());
exports.chat = chat;

View file

@ -1,713 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var chat = require('/chat').chat;
// Dependency fill on init. This exists for `pad.socket` only.
// TODO: bind directly to the socket.
var pad = undefined;
function getSocket() {
return pad && pad.socket;
}
/** Call this when the document is ready, and a new Ace2Editor() has been created and inited.
ACE's ready callback does not need to have fired yet.
"serverVars" are from calling doc.getCollabClientVars() on the server. */
function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
{
var editor = ace2editor;
pad = _pad; // Inject pad to avoid a circular dependency.
var rev = serverVars.rev;
var padId = serverVars.padId;
var globalPadId = serverVars.globalPadId;
var state = "IDLE";
var stateMessage;
var stateMessageSocketId;
var channelState = "CONNECTING";
var appLevelDisconnectReason = null;
var lastCommitTime = 0;
var initialStartConnectTime = 0;
var userId = initialUserInfo.userId;
var socketId;
//var socket;
var userSet = {}; // userId -> userInfo
userSet[userId] = initialUserInfo;
var reconnectTimes = [];
var caughtErrors = [];
var caughtErrorCatchers = [];
var caughtErrorTimes = [];
var debugMessages = [];
tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData);
tellAceActiveAuthorInfo(initialUserInfo);
var callbacks = {
onUserJoin: function()
{},
onUserLeave: function()
{},
onUpdateUserInfo: function()
{},
onChannelStateChange: function()
{},
onClientMessage: function()
{},
onInternalAction: function()
{},
onConnectionTrouble: function()
{},
onServerMessage: function()
{}
};
$(window).bind("unload", function()
{
if (getSocket())
{
setChannelState("DISCONNECTED", "unload");
}
});
if ($.browser.mozilla)
{
// Prevent "escape" from taking effect and canceling a comet connection;
// doesn't work if focus is on an iframe.
$(window).bind("keydown", function(evt)
{
if (evt.which == 27)
{
evt.preventDefault()
}
});
}
editor.setProperty("userAuthor", userId);
editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool);
editor.setUserChangeNotificationCallback(wrapRecordingErrors("handleUserChanges", handleUserChanges));
function dmesg(str)
{
if (typeof window.ajlog == "string") window.ajlog += str + '\n';
debugMessages.push(str);
}
function handleUserChanges()
{
if ((!getSocket()) || channelState == "CONNECTING")
{
if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000))
{
setChannelState("DISCONNECTED", "initsocketfail");
}
else
{
// check again in a bit
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 1000);
}
return;
}
var t = (+new Date());
if (state != "IDLE")
{
if (state == "COMMITTING" && (t - lastCommitTime) > 20000)
{
// a commit is taking too long
setChannelState("DISCONNECTED", "slowcommit");
}
else if (state == "COMMITTING" && (t - lastCommitTime) > 5000)
{
callbacks.onConnectionTrouble("SLOW");
}
else
{
// run again in a few seconds, to detect a disconnect
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000);
}
return;
}
var earliestCommit = lastCommitTime + 500;
if (t < earliestCommit)
{
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), earliestCommit - t);
return;
}
var sentMessage = false;
var userChangesData = editor.prepareUserChangeset();
if (userChangesData.changeset)
{
lastCommitTime = t;
state = "COMMITTING";
stateMessage = {
type: "USER_CHANGES",
baseRev: rev,
changeset: userChangesData.changeset,
apool: userChangesData.apool
};
stateMessageSocketId = socketId;
sendMessage(stateMessage);
sentMessage = true;
callbacks.onInternalAction("commitPerformed");
}
if (sentMessage)
{
// run again in a few seconds, to detect a disconnect
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000);
}
}
function getStats()
{
var stats = {};
stats.screen = [$(window).width(), $(window).height(), window.screen.availWidth, window.screen.availHeight, window.screen.width, window.screen.height].join(',');
stats.ip = serverVars.clientIp;
stats.useragent = serverVars.clientAgent;
return stats;
}
function setUpSocket()
{
//oldSocketId = String(Math.floor(Math.random()*1e12));
//socketId = String(Math.floor(Math.random()*1e12));
/*socket = new io.Socket();
socket.connect();*/
//socket.on('connect', function(){
hiccupCount = 0;
setChannelState("CONNECTED");
/*var msg = { type:"CLIENT_READY", roomType:'padpage',
roomName:'padpage/'+globalPadId,
data: {
lastRev:rev,
userInfo:userSet[userId],
stats: getStats() } };
if (oldSocketId) {
msg.data.isReconnectOf = oldSocketId;
msg.data.isCommitPending = (state == "COMMITTING");
}
sendMessage(msg);*/
doDeferredActions();
initialStartConnectTime = +new Date();
// });
/*socket.on('message', function(obj){
if(window.console)
console.log(obj);
handleMessageFromServer(obj);
});*/
/*var success = false;
callCatchingErrors("setUpSocket", function() {
appLevelDisconnectReason = null;
var oldSocketId = socketId;
socketId = String(Math.floor(Math.random()*1e12));
socket = new WebSocket(socketId);
socket.onmessage = wrapRecordingErrors("socket.onmessage", handleMessageFromServer);
socket.onclosed = wrapRecordingErrors("socket.onclosed", handleSocketClosed);
socket.onopen = wrapRecordingErrors("socket.onopen", function() {
hiccupCount = 0;
setChannelState("CONNECTED");
var msg = { type:"CLIENT_READY", roomType:'padpage',
roomName:'padpage/'+globalPadId,
data: {
lastRev:rev,
userInfo:userSet[userId],
stats: getStats() } };
if (oldSocketId) {
msg.data.isReconnectOf = oldSocketId;
msg.data.isCommitPending = (state == "COMMITTING");
}
sendMessage(msg);
doDeferredActions();
});
socket.onhiccup = wrapRecordingErrors("socket.onhiccup", handleCometHiccup);
socket.onlogmessage = dmesg;
socket.connect();
success = true;
});
if (success) {
initialStartConnectTime = +new Date();
}
else {
abandonConnection("initsocketfail");
}*/
}
var hiccupCount = 0;
function handleCometHiccup(params)
{
dmesg("HICCUP (connected:" + ( !! params.connected) + ")");
var connectedNow = params.connected;
if (!connectedNow)
{
hiccupCount++;
// skip first "cut off from server" notification
if (hiccupCount > 1)
{
setChannelState("RECONNECTING");
}
}
else
{
hiccupCount = 0;
setChannelState("CONNECTED");
}
}
function sendMessage(msg)
{
getSocket().json.send(
{
type: "COLLABROOM",
component: "pad",
data: msg
});
}
function wrapRecordingErrors(catcher, func)
{
return function()
{
try
{
return func.apply(this, Array.prototype.slice.call(arguments));
}
catch (e)
{
caughtErrors.push(e);
caughtErrorCatchers.push(catcher);
caughtErrorTimes.push(+new Date());
//console.dir({catcher: catcher, e: e});
throw e;
}
};
}
function callCatchingErrors(catcher, func)
{
try
{
wrapRecordingErrors(catcher, func)();
}
catch (e)
{ /*absorb*/
}
}
function handleMessageFromServer(evt)
{
if (window.console) console.log(evt);
if (!getSocket()) return;
if (!evt.data) return;
var wrapper = evt;
if (wrapper.type != "COLLABROOM") return;
var msg = wrapper.data;
if (msg.type == "NEW_CHANGES")
{
var newRev = msg.newRev;
var changeset = msg.changeset;
var author = (msg.author || '');
var apool = msg.apool;
if (newRev != (rev + 1))
{
dmesg("bad message revision on NEW_CHANGES: " + newRev + " not " + (rev + 1));
setChannelState("DISCONNECTED", "badmessage_newchanges");
return;
}
rev = newRev;
editor.applyChangesToBase(changeset, author, apool);
}
else if (msg.type == "ACCEPT_COMMIT")
{
var newRev = msg.newRev;
if (newRev != (rev + 1))
{
dmesg("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (rev + 1));
setChannelState("DISCONNECTED", "badmessage_acceptcommit");
return;
}
rev = newRev;
editor.applyPreparedChangesetToBase();
setStateIdle();
callCatchingErrors("onInternalAction", function()
{
callbacks.onInternalAction("commitAcceptedByServer");
});
callCatchingErrors("onConnectionTrouble", function()
{
callbacks.onConnectionTrouble("OK");
});
handleUserChanges();
}
else if (msg.type == "NO_COMMIT_PENDING")
{
if (state == "COMMITTING")
{
// server missed our commit message; abort that commit
setStateIdle();
handleUserChanges();
}
}
else if (msg.type == "USER_NEWINFO")
{
var userInfo = msg.userInfo;
var id = userInfo.userId;
if (userSet[id])
{
userSet[id] = userInfo;
callbacks.onUpdateUserInfo(userInfo);
dmesgUsers();
}
else
{
userSet[id] = userInfo;
callbacks.onUserJoin(userInfo);
dmesgUsers();
}
tellAceActiveAuthorInfo(userInfo);
}
else if (msg.type == "USER_LEAVE")
{
var userInfo = msg.userInfo;
var id = userInfo.userId;
if (userSet[id])
{
delete userSet[userInfo.userId];
fadeAceAuthorInfo(userInfo);
callbacks.onUserLeave(userInfo);
dmesgUsers();
}
}
else if (msg.type == "DISCONNECT_REASON")
{
appLevelDisconnectReason = msg.reason;
}
else if (msg.type == "CLIENT_MESSAGE")
{
callbacks.onClientMessage(msg.payload);
}
else if (msg.type == "CHAT_MESSAGE")
{
chat.addMessage(msg, true);
}
else if (msg.type == "SERVER_MESSAGE")
{
callbacks.onServerMessage(msg.payload);
}
}
function updateUserInfo(userInfo)
{
userInfo.userId = userId;
userSet[userId] = userInfo;
tellAceActiveAuthorInfo(userInfo);
if (!getSocket()) return;
sendMessage(
{
type: "USERINFO_UPDATE",
userInfo: userInfo
});
}
function tellAceActiveAuthorInfo(userInfo)
{
tellAceAuthorInfo(userInfo.userId, userInfo.colorId);
}
function tellAceAuthorInfo(userId, colorId, inactive)
{
if(typeof colorId == "number")
{
colorId = clientVars.colorPalette[colorId];
}
var cssColor = colorId;
if (inactive)
{
editor.setAuthorInfo(userId, {
bgcolor: cssColor,
fade: 0.5
});
}
else
{
editor.setAuthorInfo(userId, {
bgcolor: cssColor
});
}
}
function fadeAceAuthorInfo(userInfo)
{
tellAceAuthorInfo(userInfo.userId, userInfo.colorId, true);
}
function getConnectedUsers()
{
return valuesArray(userSet);
}
function tellAceAboutHistoricalAuthors(hadata)
{
for (var author in hadata)
{
var data = hadata[author];
if (!userSet[author])
{
tellAceAuthorInfo(author, data.colorId, true);
}
}
}
function dmesgUsers()
{
//pad.dmesg($.map(getConnectedUsers(), function(u) { return u.userId.slice(-2); }).join(','));
}
function setChannelState(newChannelState, moreInfo)
{
if (newChannelState != channelState)
{
channelState = newChannelState;
callbacks.onChannelStateChange(channelState, moreInfo);
}
}
function keys(obj)
{
var array = [];
$.each(obj, function(k, v)
{
array.push(k);
});
return array;
}
function valuesArray(obj)
{
var array = [];
$.each(obj, function(k, v)
{
array.push(v);
});
return array;
}
// We need to present a working interface even before the socket
// is connected for the first time.
var deferredActions = [];
function defer(func, tag)
{
return function()
{
var that = this;
var args = arguments;
function action()
{
func.apply(that, args);
}
action.tag = tag;
if (channelState == "CONNECTING")
{
deferredActions.push(action);
}
else
{
action();
}
}
}
function doDeferredActions(tag)
{
var newArray = [];
for (var i = 0; i < deferredActions.length; i++)
{
var a = deferredActions[i];
if ((!tag) || (tag == a.tag))
{
a();
}
else
{
newArray.push(a);
}
}
deferredActions = newArray;
}
function sendClientMessage(msg)
{
sendMessage(
{
type: "CLIENT_MESSAGE",
payload: msg
});
}
function getCurrentRevisionNumber()
{
return rev;
}
function getMissedChanges()
{
var obj = {};
obj.userInfo = userSet[userId];
obj.baseRev = rev;
if (state == "COMMITTING" && stateMessage)
{
obj.committedChangeset = stateMessage.changeset;
obj.committedChangesetAPool = stateMessage.apool;
obj.committedChangesetSocketId = stateMessageSocketId;
editor.applyPreparedChangesetToBase();
}
var userChangesData = editor.prepareUserChangeset();
if (userChangesData.changeset)
{
obj.furtherChangeset = userChangesData.changeset;
obj.furtherChangesetAPool = userChangesData.apool;
}
return obj;
}
function setStateIdle()
{
state = "IDLE";
callbacks.onInternalAction("newlyIdle");
schedulePerhapsCallIdleFuncs();
}
function callWhenNotCommitting(func)
{
idleFuncs.push(func);
schedulePerhapsCallIdleFuncs();
}
var idleFuncs = [];
function schedulePerhapsCallIdleFuncs()
{
setTimeout(function()
{
if (state == "IDLE")
{
while (idleFuncs.length > 0)
{
var f = idleFuncs.shift();
f();
}
}
}, 0);
}
var self = {
setOnUserJoin: function(cb)
{
callbacks.onUserJoin = cb;
},
setOnUserLeave: function(cb)
{
callbacks.onUserLeave = cb;
},
setOnUpdateUserInfo: function(cb)
{
callbacks.onUpdateUserInfo = cb;
},
setOnChannelStateChange: function(cb)
{
callbacks.onChannelStateChange = cb;
},
setOnClientMessage: function(cb)
{
callbacks.onClientMessage = cb;
},
setOnInternalAction: function(cb)
{
callbacks.onInternalAction = cb;
},
setOnConnectionTrouble: function(cb)
{
callbacks.onConnectionTrouble = cb;
},
setOnServerMessage: function(cb)
{
callbacks.onServerMessage = cb;
},
updateUserInfo: defer(updateUserInfo),
handleMessageFromServer: handleMessageFromServer,
getConnectedUsers: getConnectedUsers,
sendClientMessage: sendClientMessage,
sendMessage: sendMessage,
getCurrentRevisionNumber: getCurrentRevisionNumber,
getMissedChanges: getMissedChanges,
callWhenNotCommitting: callWhenNotCommitting,
addHistoricalAuthors: tellAceAboutHistoricalAuthors,
setChannelState: setChannelState
};
$(document).ready(setUpSocket);
return self;
}
function selectElementContents(elem)
{
if ($.browser.msie)
{
var range = document.body.createTextRange();
range.moveToElementText(elem);
range.select();
}
else
{
if (window.getSelection)
{
var browserSelection = window.getSelection();
if (browserSelection)
{
var range = document.createRange();
range.selectNodeContents(elem);
browserSelection.removeAllRanges();
browserSelection.addRange(range);
}
}
}
}
exports.getCollabClient = getCollabClient;
exports.selectElementContents = selectElementContents;

View file

@ -1,123 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/colorutils.js
// THIS FILE IS ALSO SERVED AS CLIENT-SIDE JS
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var colorutils = {};
// "#ffffff" or "#fff" or "ffffff" or "fff" to [1.0, 1.0, 1.0]
colorutils.css2triple = function(cssColor)
{
var sixHex = colorutils.css2sixhex(cssColor);
function hexToFloat(hh)
{
return Number("0x" + hh) / 255;
}
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)
{
var h = /[0-9a-fA-F]+/.exec(cssColor)[0];
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;
}
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);
}
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.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.luminosity = function(c)
{
// rule of thumb for RGB brightness; 1.0 is white
return c[0] * 0.30 + c[1] * 0.59 + c[2] * 0.11;
}
colorutils.saturate = function(c)
{
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])];
}
exports.colorutils = colorutils;

View file

@ -1,692 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
// 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.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var _MAX_LIST_LEVEL = 8;
var Changeset = require('/Changeset');
var plugins = require('/plugins').plugins;
function sanitizeUnicode(s)
{
return s.replace(/[\uffff\ufffe\ufeff\ufdd0-\ufdef\ud800-\udfff]/g, '?');
}
function makeContentCollector(collectStyles, browser, apool, domInterface, className2Author)
{
browser = browser || {};
var plugins_ = plugins;
var dom = domInterface || {
isNodeText: function(n)
{
return (n.nodeType == 3);
},
nodeTagName: function(n)
{
return n.tagName;
},
nodeValue: function(n)
{
return n.nodeValue;
},
nodeNumChildren: function(n)
{
return n.childNodes.length;
},
nodeChild: function(n, i)
{
return n.childNodes.item(i);
},
nodeProp: function(n, p)
{
return n[p];
},
nodeAttr: function(n, a)
{
return n.getAttribute(a);
},
optNodeInnerHTML: function(n)
{
return n.innerHTML;
}
};
var _blockElems = {
"div": 1,
"p": 1,
"pre": 1,
"li": 1
};
function isBlockElement(n)
{
return !!_blockElems[(dom.nodeTagName(n) || "").toLowerCase()];
}
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);
}
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] === "";
},
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;
//dmesg(txt+" / "+attrString);
op.attribs = attrString;
op.chars = txt.length;
attribsBuilder.append(op);
},
textLines: function()
{
return textArray.slice();
},
attribLines: function()
{
return attribsArray;
},
// call flush only when you're done
flush: function(withNewline)
{
if (attribsBuilder)
{
attribsArray.push(attribsBuilder.toString());
attribsBuilder = null;
}
}
};
self.startNew();
return self;
}());
var cc = {};
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)
{
// 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)
{
var child = dom.nodeChild(node, 0);
_reachPoint(child, 0, state);
_reachPoint(child, 1, state);
}
return true;
}
return false;
}
function _pointHere(charsAfter, state)
{
var ln = lines.length() - 1;
var chr = lines.textOfLine(ln).length;
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 _reachPoint(nd, idx, state)
{
if (startPoint && nd == startPoint.node && startPoint.index == idx)
{
selStart = _pointHere(0, state);
}
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.decrementFlag = function(state, flagName)
{
state.flags[flagName]--;
}
cc.incrementAttrib = function(state, attribName)
{
if (!state.attribs[attribName])
{
state.attribs[attribName] = 1;
}
else
{
state.attribs[attribName]++;
}
_recalcAttribString(state);
}
cc.decrementAttrib = function(state, attribName)
{
state.attribs[attribName]--;
_recalcAttribString(state);
}
function _enterList(state, listType)
{
var oldListType = state.listType;
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)
{
state.listLevel--;
if (state.listType != 'none')
{
state.listNesting--;
}
state.listType = oldListType;
_recalcAttribString(state);
}
function _enterAuthor(state, author)
{
var oldAuthor = state.author;
state.authorLevel = (state.authorLevel || 0) + 1;
state.author = author;
_recalcAttribString(state);
return oldAuthor;
}
function _exitAuthor(state, oldAuthor)
{
state.authorLevel--;
state.author = oldAuthor;
_recalcAttribString(state);
}
function _recalcAttribString(state)
{
var lst = [];
for (var a in state.attribs)
{
if (state.attribs[a])
{
lst.push([a, 'true']);
}
}
if (state.authorLevel > 0)
{
var authorAttrib = ['author', state.author];
if (apool.putAttrib(authorAttrib, true) >= 0)
{
// require that author already be in pool
// (don't add authors from other documents, etc.)
lst.push(authorAttrib);
}
}
state.attribString = Changeset.makeAttribsString('+', lst, 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')
{
_produceListMarker(state);
}
}
lines.startNew();
}
cc.notifySelection = function(sel)
{
if (sel)
{
selection = sel;
startPoint = selection.startPoint;
endPoint = selection.endPoint;
}
};
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: ''
};
}
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;
_reachBlockPoint(node, 0, state);
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)
{
selStart = _pointHere(0, state);
}
if (endPoint && node == endPoint.node)
{
selEnd = _pointHere(0, state);
}
}
while (txt.length > 0)
{
var consumed = 0;
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);
}
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))
{
// 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)
{
// newlines in the source mustn't become spaces at beginning of line box
txt2 = txt2.replace(/^\n*/, '');
}
if (atBeginningOfLine && state.listType && state.listType != 'none')
{
_produceListMarker(state);
}
lines.appendText(textify(txt2), state.attribString);
x += consumed;
txt = rest;
if (txt.length > 0)
{
cc.startNewLine(state);
}
}
}
else
{
var tname = (dom.nodeTagName(node) || "").toLowerCase();
if (tname == "br")
{
cc.startNewLine(state);
}
else if (tname == "script" || tname == "style")
{
// ignore
}
else if (!isEmpty)
{
var styl = dom.nodeAttr(node, "style");
var cls = dom.nodeProp(node, "className");
var isPre = (tname == "pre");
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 (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 == "s" || (styl && /\btext-decoration:\s*line-through\b/i.exec(styl)) || tname == "del")
{
cc.doAttrib(state, "strikethrough");
}
if (tname == "ul" || tname == "ol")
{
var type;
var rr = cls && /(?:^| )list-([a-z]+[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/))
{
oldListTypeOrNull = (_enterList(state, type) || 'none');
}
if (className2Author && cls)
{
var classes = cls.match(/\S+/g);
if (classes && classes.length > 0)
{
for (var i = 0; i < classes.length; i++)
{
var c = classes[i];
var a = className2Author(c);
if (a)
{
oldAuthorOrNull = (_enterAuthor(state, a) || 'none');
break;
}
}
}
}
}
var nc = dom.nodeNumChildren(node);
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 (isPre) cc.decrementFlag(state, 'preMode');
if (state.localAttribs)
{
for (var i = 0; i < state.localAttribs.length; i++)
{
cc.decrementAttrib(state, state.localAttribs[i]);
}
}
if (oldListTypeOrNull)
{
_exitList(state, oldListTypeOrNull);
}
if (oldAuthorOrNull)
{
_exitAuthor(state, oldAuthorOrNull);
}
}
}
if (!browser.msie)
{
_reachBlockPoint(node, 1, state);
}
if (isBlock)
{
if (lines.length() - 1 == startLine)
{
cc.startNewLine(state);
}
else
{
_ensureColumnZero(state);
}
}
if (browser.msie)
{
// in IE, a point immediately after a DIV appears on the next line
_reachBlockPoint(node, 1, state);
}
state.localAttribs = localAttribs;
};
// can pass a falsy value for end of doc
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)))
{
_ensureColumnZero(null);
}
};
// each returns [line, char] or [-1,-1]
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.finish = function()
{
lines.flush();
var lineAttribs = lines.attribLines();
var lineStrings = cc.getLines();
lineStrings.length--;
lineAttribs.length--;
var ss = getSelectionStart();
var se = getSelectionEnd();
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;
newStrings.unshift(i, 1);
lineStrings.splice.apply(lineStrings, newStrings);
newAttribStrings.unshift(i, 1);
lineAttribs.splice.apply(lineAttribs, newAttribStrings);
}
}
return {
linesWrapped: linesWrapped,
numLinesAfter: numLinesAfter
};
}
var wrapData = fixLongLines();
return {
selStart: ss,
selEnd: se,
linesWrapped: wrapData.linesWrapped,
numLinesAfter: wrapData.numLinesAfter,
lines: lineStrings,
lineAttribs: lineAttribs
};
}
return cc;
}
exports.sanitizeUnicode = sanitizeUnicode;
exports.makeContentCollector = makeContentCollector;

View file

@ -1,122 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function makeCSSManager(emptyStylesheetTitle, top)
{
function getSheetByTitle(title, top)
{
if(top)
var allSheets = window.parent.parent.document.styleSheets;
else
var allSheets = document.styleSheets;
for (var i = 0; i < allSheets.length; i++)
{
var s = allSheets[i];
if (s.title == title)
{
return s;
}
}
return null;
}
/*function getSheetTagByTitle(title) {
var allStyleTags = document.getElementsByTagName("style");
for(var i=0;i<allStyleTags.length;i++) {
var t = allStyleTags[i];
if (t.title == title) {
return t;
}
}
return null;
}*/
var browserSheet = getSheetByTitle(emptyStylesheetTitle, top);
//var browserTag = getSheetTagByTitle(emptyStylesheetTitle);
function browserRules()
{
return (browserSheet.cssRules || browserSheet.rules);
}
function browserDeleteRule(i)
{
if (browserSheet.deleteRule) browserSheet.deleteRule(i);
else browserSheet.removeRule(i);
}
function browserInsertRule(i, selector)
{
if (browserSheet.insertRule) browserSheet.insertRule(selector + ' {}', i);
else browserSheet.addRule(selector, null, i);
}
var selectorList = [];
function indexOfSelector(selector)
{
for (var i = 0; i < selectorList.length; i++)
{
if (selectorList[i] == selector)
{
return i;
}
}
return -1;
}
function selectorStyle(selector)
{
var i = indexOfSelector(selector);
if (i < 0)
{
// add selector
browserInsertRule(0, selector);
selectorList.splice(0, 0, selector);
i = 0;
}
return browserRules().item(i).style;
}
function removeSelectorStyle(selector)
{
var i = indexOfSelector(selector);
if (i >= 0)
{
browserDeleteRule(i);
selectorList.splice(i, 1);
}
}
return {
selectorStyle: selectorStyle,
removeSelectorStyle: removeSelectorStyle,
info: function()
{
return selectorList.length + ":" + browserRules().length;
}
};
}
exports.makeCSSManager = makeCSSManager;

View file

@ -1,290 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.domline
// %APPJET%: import("etherpad.admin.plugins");
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// requires: top
// requires: plugins
// requires: undefined
var Security = require('/security');
var plugins = require('/plugins').plugins;
var map = require('/ace2_common').map;
var domline = {};
domline.noop = function()
{};
domline.identity = function(x)
{
return x;
};
domline.addToLineClass = function(lineClass, cls)
{
// an "empty span" at any point can be used to add classes to
// the line, using line:className. otherwise, we ignore
// the span.
cls.replace(/\S+/g, function(c)
{
if (c.indexOf("line:") == 0)
{
// add class to line
lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5);
}
});
return lineClass;
}
// if "document" is falsy we don't create a DOM node, just
// an object with innerHTML and className
domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
{
var result = {
node: null,
appendSpan: domline.noop,
prepareForAdd: domline.noop,
notifyAdded: domline.noop,
clearSpans: domline.noop,
finishUpdate: domline.noop,
lineMarker: 0
};
var browser = (optBrowser || {});
var document = optDocument;
if (document)
{
result.node = document.createElement("div");
}
else
{
result.node = {
innerHTML: '',
className: ''
};
}
var html = [];
var preHtml, postHtml;
var curHTML = null;
function processSpaces(s)
{
return domline.processSpaces(s, doesWrap);
}
var identity = domline.identity;
var perTextNodeProcess = (doesWrap ? identity : processSpaces);
var perHtmlLineProcess = (doesWrap ? processSpaces : identity);
var lineClass = 'ace-line';
result.appendSpan = function(txt, cls)
{
if (cls.indexOf('list') >= 0)
{
var listType = /(?:^| )list:(\S+)/.exec(cls);
var start = /(?:^| )start:(\S+)/.exec(cls);
if (listType)
{
listType = listType[1];
start = start?'start="'+Security.escapeHTMLAttribute(start[1])+'"':'';
if (listType)
{
if(listType.indexOf("number") < 0)
{
preHtml = '<ul class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>';
postHtml = '</li></ul>';
}
else
{
preHtml = '<ol '+start+' class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>';
postHtml = '</li></ol>';
}
}
result.lineMarker += txt.length;
return; // don't append any text
}
}
var href = null;
var simpleTags = null;
if (cls.indexOf('url') >= 0)
{
cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url)
{
href = url;
return space + "url";
});
}
if (cls.indexOf('tag') >= 0)
{
cls = cls.replace(/(^| )tag:(\S+)/g, function(x0, space, tag)
{
if (!simpleTags) simpleTags = [];
simpleTags.push(tag.toLowerCase());
return space + tag;
});
}
var extraOpenTags = "";
var extraCloseTags = "";
var plugins_ = plugins;
map(plugins_.callHook("aceCreateDomLine", {
domline: domline,
cls: cls
}), function(modifier)
{
cls = modifier.cls;
extraOpenTags = extraOpenTags + modifier.extraOpenTags;
extraCloseTags = modifier.extraCloseTags + extraCloseTags;
});
if ((!txt) && cls)
{
lineClass = domline.addToLineClass(lineClass, cls);
}
else if (txt)
{
if (href)
{
if(!~href.indexOf("http")) // if the url doesn't include http or https etc prefix it.
{
href = "http://"+href;
}
extraOpenTags = extraOpenTags + '<a href="' + Security.escapeHTMLAttribute(href) + '">';
extraCloseTags = '</a>' + extraCloseTags;
}
if (simpleTags)
{
simpleTags.sort();
extraOpenTags = extraOpenTags + '<' + simpleTags.join('><') + '>';
simpleTags.reverse();
extraCloseTags = '</' + simpleTags.join('></') + '>' + extraCloseTags;
}
html.push('<span class="', Security.escapeHTMLAttribute(cls || ''), '">', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '</span>');
}
};
result.clearSpans = function()
{
html = [];
lineClass = ''; // non-null to cause update
result.lineMarker = 0;
};
function writeHTML()
{
var newHTML = perHtmlLineProcess(html.join(''));
if (!newHTML)
{
if ((!document) || (!optBrowser))
{
newHTML += '&nbsp;';
}
else if (!browser.msie)
{
newHTML += '<br/>';
}
}
if (nonEmpty)
{
newHTML = (preHtml || '') + newHTML + (postHtml || '');
}
html = preHtml = postHtml = null; // free memory
if (newHTML !== curHTML)
{
curHTML = newHTML;
result.node.innerHTML = curHTML;
}
if (lineClass !== null) result.node.className = lineClass;
}
result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML;
result.getInnerHTML = function()
{
return curHTML || '';
};
return result;
};
domline.processSpaces = function(s, doesWrap)
{
if (s.indexOf("<") < 0 && !doesWrap)
{
// short-cut
return s.replace(/ /g, '&nbsp;');
}
var parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, function(m)
{
parts.push(m);
});
if (doesWrap)
{
var endOfLine = true;
var beforeSpace = false;
// last space in a run is normal, others are nbsp,
// end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--)
{
var p = parts[i];
if (p == " ")
{
if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
}
else if (p.charAt(0) != "<")
{
endOfLine = false;
beforeSpace = false;
}
}
// beginning of line is nbsp
for (var i = 0; i < parts.length; i++)
{
var p = parts[i];
if (p == " ")
{
parts[i] = '&nbsp;';
break;
}
else if (p.charAt(0) != "<")
{
break;
}
}
}
else
{
for (var i = 0; i < parts.length; i++)
{
var p = parts[i];
if (p == " ")
{
parts[i] = '&nbsp;';
}
}
}
return parts.join('');
};
exports.domline = domline;

View file

@ -1,197 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function makeDraggable(jqueryNodes, eventHandler)
{
jqueryNodes.each(function()
{
var node = $(this);
var state = {};
var inDrag = false;
function dragStart(evt)
{
if (inDrag)
{
return;
}
inDrag = true;
if (eventHandler('dragstart', evt, state) !== false)
{
$(document).bind('mousemove', dragUpdate);
$(document).bind('mouseup', dragEnd);
}
evt.preventDefault();
return false;
}
function dragUpdate(evt)
{
if (!inDrag)
{
return;
}
eventHandler('dragupdate', evt, state);
evt.preventDefault();
return false;
}
function dragEnd(evt)
{
if (!inDrag)
{
return;
}
inDrag = false;
try
{
eventHandler('dragend', evt, state);
}
finally
{
$(document).unbind('mousemove', dragUpdate);
$(document).unbind('mouseup', dragEnd);
evt.preventDefault();
}
return false;
}
node.bind('mousedown', dragStart);
});
}
function makeResizableVPane(top, sep, bottom, minTop, minBottom, callback)
{
if (minTop === undefined) minTop = 0;
if (minBottom === undefined) minBottom = 0;
makeDraggable($(sep), function(eType, evt, state)
{
if (eType == 'dragstart')
{
state.startY = evt.pageY;
state.topHeight = $(top).height();
state.bottomHeight = $(bottom).height();
state.minTop = minTop;
state.maxTop = (state.topHeight + state.bottomHeight) - minBottom;
}
else if (eType == 'dragupdate')
{
var change = evt.pageY - state.startY;
var topHeight = state.topHeight + change;
if (topHeight < state.minTop)
{
topHeight = state.minTop;
}
if (topHeight > state.maxTop)
{
topHeight = state.maxTop;
}
change = topHeight - state.topHeight;
var bottomHeight = state.bottomHeight - change;
var sepHeight = $(sep).height();
var totalHeight = topHeight + sepHeight + bottomHeight;
topHeight = 100.0 * topHeight / totalHeight;
sepHeight = 100.0 * sepHeight / totalHeight;
bottomHeight = 100.0 * bottomHeight / totalHeight;
$(top).css('bottom', 'auto');
$(top).css('height', topHeight + "%");
$(sep).css('top', topHeight + "%");
$(bottom).css('top', (topHeight + sepHeight) + '%');
$(bottom).css('height', 'auto');
if (callback) callback();
}
});
}
function makeResizableHPane(left, sep, right, minLeft, minRight, sepWidth, sepOffset, callback)
{
if (minLeft === undefined) minLeft = 0;
if (minRight === undefined) minRight = 0;
makeDraggable($(sep), function(eType, evt, state)
{
if (eType == 'dragstart')
{
state.startX = evt.pageX;
state.leftWidth = $(left).width();
state.rightWidth = $(right).width();
state.minLeft = minLeft;
state.maxLeft = (state.leftWidth + state.rightWidth) - minRight;
}
else if (eType == 'dragend' || eType == 'dragupdate')
{
var change = evt.pageX - state.startX;
var leftWidth = state.leftWidth + change;
if (leftWidth < state.minLeft)
{
leftWidth = state.minLeft;
}
if (leftWidth > state.maxLeft)
{
leftWidth = state.maxLeft;
}
change = leftWidth - state.leftWidth;
var rightWidth = state.rightWidth - change;
newSepWidth = sepWidth;
if (newSepWidth == undefined) newSepWidth = $(sep).width();
newSepOffset = sepOffset;
if (newSepOffset == undefined) newSepOffset = 0;
if (change == 0)
{
if (rightWidth != minRight || state.lastRightWidth == undefined)
{
state.lastRightWidth = rightWidth;
rightWidth = minRight;
}
else
{
rightWidth = state.lastRightWidth;
state.lastRightWidth = minRight;
}
change = state.rightWidth - rightWidth;
leftWidth = change + state.leftWidth;
}
var totalWidth = leftWidth + newSepWidth + rightWidth;
leftWidth = 100.0 * leftWidth / totalWidth;
newSepWidth = 100.0 * newSepWidth / totalWidth;
newSepOffset = 100.0 * newSepOffset / totalWidth;
rightWidth = 100.0 * rightWidth / totalWidth;
$(left).css('right', 'auto');
$(left).css('width', leftWidth + "%");
$(sep).css('left', (leftWidth + newSepOffset) + "%");
$(right).css('left', (leftWidth + newSepWidth) + '%');
$(right).css('width', 'auto');
if (callback) callback();
}
});
}
exports.makeDraggable = makeDraggable;

View file

@ -1,35 +0,0 @@
// Copyright 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
document.createElement("canvas").getContext||(function(){var s=Math,j=s.round,F=s.sin,G=s.cos,V=s.abs,W=s.sqrt,k=10,v=k/2;function X(){return this.context_||(this.context_=new H(this))}var L=Array.prototype.slice;function Y(b,a){var c=L.call(arguments,2);return function(){return b.apply(a,c.concat(L.call(arguments)))}}var M={init:function(b){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var a=b||document;a.createElement("canvas");a.attachEvent("onreadystatechange",Y(this.init_,this,a))}},init_:function(b){b.namespaces.g_vml_||
b.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML");b.namespaces.g_o_||b.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML");if(!b.styleSheets.ex_canvas_){var a=b.createStyleSheet();a.owningElement.id="ex_canvas_";a.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}g_o_\\:*{behavior:url(#default#VML)}"}var c=b.getElementsByTagName("canvas"),d=0;for(;d<c.length;d++)this.initElement(c[d])},
initElement:function(b){if(!b.getContext){b.getContext=X;b.innerHTML="";b.attachEvent("onpropertychange",Z);b.attachEvent("onresize",$);var a=b.attributes;if(a.width&&a.width.specified)b.style.width=a.width.nodeValue+"px";else b.width=b.clientWidth;if(a.height&&a.height.specified)b.style.height=a.height.nodeValue+"px";else b.height=b.clientHeight}return b}};function Z(b){var a=b.srcElement;switch(b.propertyName){case "width":a.style.width=a.attributes.width.nodeValue+"px";a.getContext().clearRect();
break;case "height":a.style.height=a.attributes.height.nodeValue+"px";a.getContext().clearRect();break}}function $(b){var a=b.srcElement;if(a.firstChild){a.firstChild.style.width=a.clientWidth+"px";a.firstChild.style.height=a.clientHeight+"px"}}M.init();var N=[],B=0;for(;B<16;B++){var C=0;for(;C<16;C++)N[B*16+C]=B.toString(16)+C.toString(16)}function I(){return[[1,0,0],[0,1,0],[0,0,1]]}function y(b,a){var c=I(),d=0;for(;d<3;d++){var f=0;for(;f<3;f++){var h=0,g=0;for(;g<3;g++)h+=b[d][g]*a[g][f];c[d][f]=
h}}return c}function O(b,a){a.fillStyle=b.fillStyle;a.lineCap=b.lineCap;a.lineJoin=b.lineJoin;a.lineWidth=b.lineWidth;a.miterLimit=b.miterLimit;a.shadowBlur=b.shadowBlur;a.shadowColor=b.shadowColor;a.shadowOffsetX=b.shadowOffsetX;a.shadowOffsetY=b.shadowOffsetY;a.strokeStyle=b.strokeStyle;a.globalAlpha=b.globalAlpha;a.arcScaleX_=b.arcScaleX_;a.arcScaleY_=b.arcScaleY_;a.lineScale_=b.lineScale_}function P(b){var a,c=1;b=String(b);if(b.substring(0,3)=="rgb"){var d=b.indexOf("(",3),f=b.indexOf(")",d+
1),h=b.substring(d+1,f).split(",");a="#";var g=0;for(;g<3;g++)a+=N[Number(h[g])];if(h.length==4&&b.substr(3,1)=="a")c=h[3]}else a=b;return{color:a,alpha:c}}function aa(b){switch(b){case "butt":return"flat";case "round":return"round";case "square":default:return"square"}}function H(b){this.m_=I();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.fillStyle=this.strokeStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=k*1;this.globalAlpha=1;this.canvas=b;
var a=b.ownerDocument.createElement("div");a.style.width=b.clientWidth+"px";a.style.height=b.clientHeight+"px";a.style.overflow="hidden";a.style.position="absolute";b.appendChild(a);this.element_=a;this.lineScale_=this.arcScaleY_=this.arcScaleX_=1}var i=H.prototype;i.clearRect=function(){this.element_.innerHTML=""};i.beginPath=function(){this.currentPath_=[]};i.moveTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"moveTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y};
i.lineTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"lineTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y};i.bezierCurveTo=function(b,a,c,d,f,h){var g=this.getCoords_(f,h),l=this.getCoords_(b,a),e=this.getCoords_(c,d);Q(this,l,e,g)};function Q(b,a,c,d){b.currentPath_.push({type:"bezierCurveTo",cp1x:a.x,cp1y:a.y,cp2x:c.x,cp2y:c.y,x:d.x,y:d.y});b.currentX_=d.x;b.currentY_=d.y}i.quadraticCurveTo=function(b,a,c,d){var f=this.getCoords_(b,a),h=this.getCoords_(c,d),g={x:this.currentX_+
0.6666666666666666*(f.x-this.currentX_),y:this.currentY_+0.6666666666666666*(f.y-this.currentY_)};Q(this,g,{x:g.x+(h.x-this.currentX_)/3,y:g.y+(h.y-this.currentY_)/3},h)};i.arc=function(b,a,c,d,f,h){c*=k;var g=h?"at":"wa",l=b+G(d)*c-v,e=a+F(d)*c-v,m=b+G(f)*c-v,r=a+F(f)*c-v;if(l==m&&!h)l+=0.125;var n=this.getCoords_(b,a),o=this.getCoords_(l,e),q=this.getCoords_(m,r);this.currentPath_.push({type:g,x:n.x,y:n.y,radius:c,xStart:o.x,yStart:o.y,xEnd:q.x,yEnd:q.y})};i.rect=function(b,a,c,d){this.moveTo(b,
a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath()};i.strokeRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.stroke();this.currentPath_=f};i.fillRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.fill();this.currentPath_=f};i.createLinearGradient=function(b,
a,c,d){var f=new D("gradient");f.x0_=b;f.y0_=a;f.x1_=c;f.y1_=d;return f};i.createRadialGradient=function(b,a,c,d,f,h){var g=new D("gradientradial");g.x0_=b;g.y0_=a;g.r0_=c;g.x1_=d;g.y1_=f;g.r1_=h;return g};i.drawImage=function(b){var a,c,d,f,h,g,l,e,m=b.runtimeStyle.width,r=b.runtimeStyle.height;b.runtimeStyle.width="auto";b.runtimeStyle.height="auto";var n=b.width,o=b.height;b.runtimeStyle.width=m;b.runtimeStyle.height=r;if(arguments.length==3){a=arguments[1];c=arguments[2];h=g=0;l=d=n;e=f=o}else if(arguments.length==
5){a=arguments[1];c=arguments[2];d=arguments[3];f=arguments[4];h=g=0;l=n;e=o}else if(arguments.length==9){h=arguments[1];g=arguments[2];l=arguments[3];e=arguments[4];a=arguments[5];c=arguments[6];d=arguments[7];f=arguments[8]}else throw Error("Invalid number of arguments");var q=this.getCoords_(a,c),t=[];t.push(" <g_vml_:group",' coordsize="',k*10,",",k*10,'"',' coordorigin="0,0"',' style="width:',10,"px;height:",10,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]){var E=[];E.push("M11=",
this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",j(q.x/k),",","Dy=",j(q.y/k),"");var p=q,z=this.getCoords_(a+d,c),w=this.getCoords_(a,c+f),x=this.getCoords_(a+d,c+f);p.x=s.max(p.x,z.x,w.x,x.x);p.y=s.max(p.y,z.y,w.y,x.y);t.push("padding:0 ",j(p.x/k),"px ",j(p.y/k),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",E.join(""),", sizingmethod='clip');")}else t.push("top:",j(q.y/k),"px;left:",j(q.x/k),"px;");t.push(' ">','<g_vml_:image src="',b.src,
'"',' style="width:',k*d,"px;"," height:",k*f,'px;"',' cropleft="',h/n,'"',' croptop="',g/o,'"',' cropright="',(n-h-l)/n,'"',' cropbottom="',(o-g-e)/o,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",t.join(""))};i.stroke=function(b){var a=[],c=P(b?this.fillStyle:this.strokeStyle),d=c.color,f=c.alpha*this.globalAlpha;a.push("<g_vml_:shape",' filled="',!!b,'"',' style="position:absolute;width:',10,"px;height:",10,'px;"',' coordorigin="0 0" coordsize="',k*10," ",k*10,'"',' stroked="',
!b,'"',' path="');var h={x:null,y:null},g={x:null,y:null},l=0;for(;l<this.currentPath_.length;l++){var e=this.currentPath_[l];switch(e.type){case "moveTo":a.push(" m ",j(e.x),",",j(e.y));break;case "lineTo":a.push(" l ",j(e.x),",",j(e.y));break;case "close":a.push(" x ");e=null;break;case "bezierCurveTo":a.push(" c ",j(e.cp1x),",",j(e.cp1y),",",j(e.cp2x),",",j(e.cp2y),",",j(e.x),",",j(e.y));break;case "at":case "wa":a.push(" ",e.type," ",j(e.x-this.arcScaleX_*e.radius),",",j(e.y-this.arcScaleY_*e.radius),
" ",j(e.x+this.arcScaleX_*e.radius),",",j(e.y+this.arcScaleY_*e.radius)," ",j(e.xStart),",",j(e.yStart)," ",j(e.xEnd),",",j(e.yEnd));break}if(e){if(h.x==null||e.x<h.x)h.x=e.x;if(g.x==null||e.x>g.x)g.x=e.x;if(h.y==null||e.y<h.y)h.y=e.y;if(g.y==null||e.y>g.y)g.y=e.y}}a.push(' ">');if(b)if(typeof this.fillStyle=="object"){var m=this.fillStyle,r=0,n={x:0,y:0},o=0,q=1;if(m.type_=="gradient"){var t=m.x1_/this.arcScaleX_,E=m.y1_/this.arcScaleY_,p=this.getCoords_(m.x0_/this.arcScaleX_,m.y0_/this.arcScaleY_),
z=this.getCoords_(t,E);r=Math.atan2(z.x-p.x,z.y-p.y)*180/Math.PI;if(r<0)r+=360;if(r<1.0E-6)r=0}else{var p=this.getCoords_(m.x0_,m.y0_),w=g.x-h.x,x=g.y-h.y;n={x:(p.x-h.x)/w,y:(p.y-h.y)/x};w/=this.arcScaleX_*k;x/=this.arcScaleY_*k;var R=s.max(w,x);o=2*m.r0_/R;q=2*m.r1_/R-o}var u=m.colors_;u.sort(function(ba,ca){return ba.offset-ca.offset});var J=u.length,da=u[0].color,ea=u[J-1].color,fa=u[0].alpha*this.globalAlpha,ga=u[J-1].alpha*this.globalAlpha,S=[],l=0;for(;l<J;l++){var T=u[l];S.push(T.offset*q+
o+" "+T.color)}a.push('<g_vml_:fill type="',m.type_,'"',' method="none" focus="100%"',' color="',da,'"',' color2="',ea,'"',' colors="',S.join(","),'"',' opacity="',ga,'"',' g_o_:opacity2="',fa,'"',' angle="',r,'"',' focusposition="',n.x,",",n.y,'" />')}else a.push('<g_vml_:fill color="',d,'" opacity="',f,'" />');else{var K=this.lineScale_*this.lineWidth;if(K<1)f*=K;a.push("<g_vml_:stroke",' opacity="',f,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',aa(this.lineCap),
'"',' weight="',K,'px"',' color="',d,'" />')}a.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",a.join(""))};i.fill=function(){this.stroke(true)};i.closePath=function(){this.currentPath_.push({type:"close"})};i.getCoords_=function(b,a){var c=this.m_;return{x:k*(b*c[0][0]+a*c[1][0]+c[2][0])-v,y:k*(b*c[0][1]+a*c[1][1]+c[2][1])-v}};i.save=function(){var b={};O(this,b);this.aStack_.push(b);this.mStack_.push(this.m_);this.m_=y(I(),this.m_)};i.restore=function(){O(this.aStack_.pop(),
this);this.m_=this.mStack_.pop()};function ha(b){var a=0;for(;a<3;a++){var c=0;for(;c<2;c++)if(!isFinite(b[a][c])||isNaN(b[a][c]))return false}return true}function A(b,a,c){if(!!ha(a)){b.m_=a;if(c)b.lineScale_=W(V(a[0][0]*a[1][1]-a[0][1]*a[1][0]))}}i.translate=function(b,a){A(this,y([[1,0,0],[0,1,0],[b,a,1]],this.m_),false)};i.rotate=function(b){var a=G(b),c=F(b);A(this,y([[a,c,0],[-c,a,0],[0,0,1]],this.m_),false)};i.scale=function(b,a){this.arcScaleX_*=b;this.arcScaleY_*=a;A(this,y([[b,0,0],[0,a,
0],[0,0,1]],this.m_),true)};i.transform=function(b,a,c,d,f,h){A(this,y([[b,a,0],[c,d,0],[f,h,1]],this.m_),true)};i.setTransform=function(b,a,c,d,f,h){A(this,[[b,a,0],[c,d,0],[f,h,1]],true)};i.clip=function(){};i.arcTo=function(){};i.createPattern=function(){return new U};function D(b){this.type_=b;this.r1_=this.y1_=this.x1_=this.r0_=this.y0_=this.x0_=0;this.colors_=[]}D.prototype.addColorStop=function(b,a){a=P(a);this.colors_.push({offset:b,color:a.color,alpha:a.alpha})};function U(){}G_vmlCanvasManager=
M;CanvasRenderingContext2D=H;CanvasGradient=D;CanvasPattern=U})();

View file

@ -1,524 +0,0 @@
// Farbtastic 2.0 alpha
(function ($) {
var __debug = false;
var __factor = 0.5;
$.fn.farbtastic = function (options) {
$.farbtastic(this, options);
return this;
};
$.farbtastic = function (container, options) {
var container = $(container)[0];
return container.farbtastic || (container.farbtastic = new $._farbtastic(container, options));
}
$._farbtastic = function (container, options) {
var fb = this;
/////////////////////////////////////////////////////
/**
* Link to the given element(s) or callback.
*/
fb.linkTo = function (callback) {
// Unbind previous nodes
if (typeof fb.callback == 'object') {
$(fb.callback).unbind('keyup', fb.updateValue);
}
// Reset color
fb.color = null;
// Bind callback or elements
if (typeof callback == 'function') {
fb.callback = callback;
}
else if (typeof callback == 'object' || typeof callback == 'string') {
fb.callback = $(callback);
fb.callback.bind('keyup', fb.updateValue);
if (fb.callback[0].value) {
fb.setColor(fb.callback[0].value);
}
}
return this;
}
fb.updateValue = function (event) {
if (this.value && this.value != fb.color) {
fb.setColor(this.value);
}
}
/**
* Change color with HTML syntax #123456
*/
fb.setColor = function (color) {
var unpack = fb.unpack(color);
if (fb.color != color && unpack) {
fb.color = color;
fb.rgb = unpack;
fb.hsl = fb.RGBToHSL(fb.rgb);
fb.updateDisplay();
}
return this;
}
/**
* Change color with HSL triplet [0..1, 0..1, 0..1]
*/
fb.setHSL = function (hsl) {
fb.hsl = hsl;
var convertedHSL = [hsl[0]]
convertedHSL[1] = hsl[1]*__factor+((1-__factor)/2);
convertedHSL[2] = hsl[2]*__factor+((1-__factor)/2);
fb.rgb = fb.HSLToRGB(convertedHSL);
fb.color = fb.pack(fb.rgb);
fb.updateDisplay();
return this;
}
/////////////////////////////////////////////////////
//excanvas-compatible building of canvases
fb._makeCanvas = function(className){
var c = document.createElement('canvas');
if (!c.getContext) { // excanvas hack
c = window.G_vmlCanvasManager.initElement(c);
c.getContext(); //this creates the excanvas children
}
$(c).addClass(className);
return c;
}
/**
* Initialize the color picker widget.
*/
fb.initWidget = function () {
// Insert markup and size accordingly.
var dim = {
width: options.width,
height: options.width
};
$(container)
.html(
'<div class="farbtastic" style="position: relative">' +
'<div class="farbtastic-solid"></div>' +
'</div>'
)
.children('.farbtastic')
.append(fb._makeCanvas('farbtastic-mask'))
.append(fb._makeCanvas('farbtastic-overlay'))
.end()
.find('*').attr(dim).css(dim).end()
.find('div>*').css('position', 'absolute');
// Determine layout
fb.radius = (options.width - options.wheelWidth) / 2 - 1;
fb.square = Math.floor((fb.radius - options.wheelWidth / 2) * 0.7) - 1;
fb.mid = Math.floor(options.width / 2);
fb.markerSize = options.wheelWidth * 0.3;
fb.solidFill = $('.farbtastic-solid', container).css({
width: fb.square * 2 - 1,
height: fb.square * 2 - 1,
left: fb.mid - fb.square,
top: fb.mid - fb.square
});
// Set up drawing context.
fb.cnvMask = $('.farbtastic-mask', container);
fb.ctxMask = fb.cnvMask[0].getContext('2d');
fb.cnvOverlay = $('.farbtastic-overlay', container);
fb.ctxOverlay = fb.cnvOverlay[0].getContext('2d');
fb.ctxMask.translate(fb.mid, fb.mid);
fb.ctxOverlay.translate(fb.mid, fb.mid);
// Draw widget base layers.
fb.drawCircle();
fb.drawMask();
}
/**
* Draw the color wheel.
*/
fb.drawCircle = function () {
var tm = +(new Date());
// Draw a hue circle with a bunch of gradient-stroked beziers.
// Have to use beziers, as gradient-stroked arcs don't work.
var n = 24,
r = fb.radius,
w = options.wheelWidth,
nudge = 8 / r / n * Math.PI, // Fudge factor for seams.
m = fb.ctxMask,
angle1 = 0, color1, d1;
m.save();
m.lineWidth = w / r;
m.scale(r, r);
// Each segment goes from angle1 to angle2.
for (var i = 0; i <= n; ++i) {
var d2 = i / n,
angle2 = d2 * Math.PI * 2,
// Endpoints
x1 = Math.sin(angle1), y1 = -Math.cos(angle1);
x2 = Math.sin(angle2), y2 = -Math.cos(angle2),
// Midpoint chosen so that the endpoints are tangent to the circle.
am = (angle1 + angle2) / 2,
tan = 1 / Math.cos((angle2 - angle1) / 2),
xm = Math.sin(am) * tan, ym = -Math.cos(am) * tan,
// New color
color2 = fb.pack(fb.HSLToRGB([d2, 1, 0.5]));
if (i > 0) {
if ($.browser.msie) {
// IE's gradient calculations mess up the colors. Correct along the diagonals.
var corr = (1 + Math.min(Math.abs(Math.tan(angle1)), Math.abs(Math.tan(Math.PI / 2 - angle1)))) / n;
color1 = fb.pack(fb.HSLToRGB([d1 - 0.15 * corr, 1, 0.5]));
color2 = fb.pack(fb.HSLToRGB([d2 + 0.15 * corr, 1, 0.5]));
// Create gradient fill between the endpoints.
var grad = m.createLinearGradient(x1, y1, x2, y2);
grad.addColorStop(0, color1);
grad.addColorStop(1, color2);
m.fillStyle = grad;
// Draw quadratic curve segment as a fill.
var r1 = (r + w / 2) / r, r2 = (r - w / 2) / r; // inner/outer radius.
m.beginPath();
m.moveTo(x1 * r1, y1 * r1);
m.quadraticCurveTo(xm * r1, ym * r1, x2 * r1, y2 * r1);
m.lineTo(x2 * r2, y2 * r2);
m.quadraticCurveTo(xm * r2, ym * r2, x1 * r2, y1 * r2);
m.fill();
}
else {
// Create gradient fill between the endpoints.
var grad = m.createLinearGradient(x1, y1, x2, y2);
grad.addColorStop(0, color1);
grad.addColorStop(1, color2);
m.strokeStyle = grad;
// Draw quadratic curve segment.
m.beginPath();
m.moveTo(x1, y1);
m.quadraticCurveTo(xm, ym, x2, y2);
m.stroke();
}
}
// Prevent seams where curves join.
angle1 = angle2 - nudge; color1 = color2; d1 = d2;
}
m.restore();
__debug && $('body').append('<div>drawCircle '+ (+(new Date()) - tm) +'ms');
};
/**
* Draw the saturation/luminance mask.
*/
fb.drawMask = function () {
var tm = +(new Date());
// Iterate over sat/lum space and calculate appropriate mask pixel values.
var size = fb.square * 2, sq = fb.square;
function calculateMask(sizex, sizey, outputPixel) {
var isx = 1 / sizex, isy = 1 / sizey;
for (var y = 0; y <= sizey; ++y) {
var l = 1 - y * isy;
for (var x = 0; x <= sizex; ++x) {
var s = 1 - x * isx;
// From sat/lum to alpha and color (grayscale)
var a = 1 - 2 * Math.min(l * s, (1 - l) * s);
var c = (a > 0) ? ((2 * l - 1 + a) * .5 / a) : 0;
a = a*__factor+(1-__factor)/2;
c = c*__factor+(1-__factor)/2;
outputPixel(x, y, c, a);
}
}
}
// Method #1: direct pixel access (new Canvas).
if (fb.ctxMask.getImageData) {
// Create half-resolution buffer.
var sz = Math.floor(size / 2);
var buffer = document.createElement('canvas');
buffer.width = buffer.height = sz + 1;
var ctx = buffer.getContext('2d');
var frame = ctx.getImageData(0, 0, sz + 1, sz + 1);
var i = 0;
calculateMask(sz, sz, function (x, y, c, a) {
frame.data[i++] = frame.data[i++] = frame.data[i++] = c * 255;
frame.data[i++] = a * 255;
});
ctx.putImageData(frame, 0, 0);
fb.ctxMask.drawImage(buffer, 0, 0, sz + 1, sz + 1, -sq, -sq, sq * 2, sq * 2);
}
// Method #2: drawing commands (old Canvas).
else if (!$.browser.msie) {
// Render directly at half-resolution
var sz = Math.floor(size / 2);
calculateMask(sz, sz, function (x, y, c, a) {
c = Math.round(c * 255);
fb.ctxMask.fillStyle = 'rgba(' + c + ', ' + c + ', ' + c + ', ' + a +')';
fb.ctxMask.fillRect(x * 2 - sq - 1, y * 2 - sq - 1, 2, 2);
});
}
// Method #3: vertical DXImageTransform gradient strips (IE).
else {
var cache_last, cache, w = 6; // Each strip is 6 pixels wide.
var sizex = Math.floor(size / w);
// 6 vertical pieces of gradient per strip.
calculateMask(sizex, 6, function (x, y, c, a) {
if (x == 0) {
cache_last = cache;
cache = [];
}
c = Math.round(c * 255);
a = Math.round(a * 255);
// We can only start outputting gradients once we have two rows of pixels.
if (y > 0) {
var c_last = cache_last[x][0],
a_last = cache_last[x][1],
color1 = fb.packDX(c_last, a_last),
color2 = fb.packDX(c, a),
y1 = Math.round(fb.mid + ((y - 1) * .333 - 1) * sq),
y2 = Math.round(fb.mid + (y * .333 - 1) * sq);
$('<div>').css({
position: 'absolute',
filter: "progid:DXImageTransform.Microsoft.Gradient(StartColorStr="+ color1 +", EndColorStr="+ color2 +", GradientType=0)",
top: y1,
height: y2 - y1,
// Avoid right-edge sticking out.
left: fb.mid + (x * w - sq - 1),
width: w - (x == sizex ? Math.round(w / 2) : 0)
}).appendTo(fb.cnvMask);
}
cache.push([c, a]);
});
}
__debug && $('body').append('<div>drawMask '+ (+(new Date()) - tm) +'ms');
}
/**
* Draw the selection markers.
*/
fb.drawMarkers = function () {
// Determine marker dimensions
var sz = options.width, lw = Math.ceil(fb.markerSize / 4), r = fb.markerSize - lw + 1;
var angle = fb.hsl[0] * 6.28,
x1 = Math.sin(angle) * fb.radius,
y1 = -Math.cos(angle) * fb.radius,
x2 = 2 * fb.square * (.5 - fb.hsl[1]),
y2 = 2 * fb.square * (.5 - fb.hsl[2]),
c1 = fb.invert ? '#fff' : '#000',
c2 = fb.invert ? '#000' : '#fff';
var circles = [
{ x: x1, y: y1, r: r, c: '#000', lw: lw + 1 },
{ x: x1, y: y1, r: fb.markerSize, c: '#fff', lw: lw },
{ x: x2, y: y2, r: r, c: c2, lw: lw + 1 },
{ x: x2, y: y2, r: fb.markerSize, c: c1, lw: lw },
];
// Update the overlay canvas.
fb.ctxOverlay.clearRect(-fb.mid, -fb.mid, sz, sz);
for (i in circles) {
var c = circles[i];
fb.ctxOverlay.lineWidth = c.lw;
fb.ctxOverlay.strokeStyle = c.c;
fb.ctxOverlay.beginPath();
fb.ctxOverlay.arc(c.x, c.y, c.r, 0, Math.PI * 2, true);
fb.ctxOverlay.stroke();
}
}
/**
* Update the markers and styles
*/
fb.updateDisplay = function () {
// Determine whether labels/markers should invert.
fb.invert = (fb.rgb[0] * 0.3 + fb.rgb[1] * .59 + fb.rgb[2] * .11) <= 0.6;
// Update the solid background fill.
fb.solidFill.css('backgroundColor', fb.pack(fb.HSLToRGB([fb.hsl[0], 1, 0.5])));
// Draw markers
fb.drawMarkers();
// Linked elements or callback
if (typeof fb.callback == 'object') {
// Set background/foreground color
$(fb.callback).css({
backgroundColor: fb.color,
color: fb.invert ? '#fff' : '#000'
});
// Change linked value
$(fb.callback).each(function() {
if ((typeof this.value == 'string') && this.value != fb.color) {
this.value = fb.color;
}
});
}
else if (typeof fb.callback == 'function') {
fb.callback.call(fb, fb.color);
}
}
/**
* Helper for returning coordinates relative to the center.
*/
fb.widgetCoords = function (event) {
return {
x: event.pageX - fb.offset.left - fb.mid,
y: event.pageY - fb.offset.top - fb.mid
};
}
/**
* Mousedown handler
*/
fb.mousedown = function (event) {
// Capture mouse
if (!$._farbtastic.dragging) {
$(document).bind('mousemove', fb.mousemove).bind('mouseup', fb.mouseup);
$._farbtastic.dragging = true;
}
// Update the stored offset for the widget.
fb.offset = $(container).offset();
// Check which area is being dragged
var pos = fb.widgetCoords(event);
fb.circleDrag = Math.max(Math.abs(pos.x), Math.abs(pos.y)) > (fb.square + 2);
// Process
fb.mousemove(event);
return false;
}
/**
* Mousemove handler
*/
fb.mousemove = function (event) {
// Get coordinates relative to color picker center
var pos = fb.widgetCoords(event);
// Set new HSL parameters
if (fb.circleDrag) {
var hue = Math.atan2(pos.x, -pos.y) / 6.28;
fb.setHSL([(hue + 1) % 1, fb.hsl[1], fb.hsl[2]]);
}
else {
var sat = Math.max(0, Math.min(1, -(pos.x / fb.square / 2) + .5));
var lum = Math.max(0, Math.min(1, -(pos.y / fb.square / 2) + .5));
fb.setHSL([fb.hsl[0], sat, lum]);
}
return false;
}
/**
* Mouseup handler
*/
fb.mouseup = function () {
// Uncapture mouse
$(document).unbind('mousemove', fb.mousemove);
$(document).unbind('mouseup', fb.mouseup);
$._farbtastic.dragging = false;
}
/* Various color utility functions */
fb.dec2hex = function (x) {
return (x < 16 ? '0' : '') + x.toString(16);
}
fb.packDX = function (c, a) {
return '#' + fb.dec2hex(a) + fb.dec2hex(c) + fb.dec2hex(c) + fb.dec2hex(c);
};
fb.pack = function (rgb) {
var r = Math.round(rgb[0] * 255);
var g = Math.round(rgb[1] * 255);
var b = Math.round(rgb[2] * 255);
return '#' + fb.dec2hex(r) + fb.dec2hex(g) + fb.dec2hex(b);
};
fb.unpack = function (color) {
if (color.length == 7) {
function x(i) {
return parseInt(color.substring(i, i + 2), 16) / 255;
}
return [ x(1), x(3), x(5) ];
}
else if (color.length == 4) {
function x(i) {
return parseInt(color.substring(i, i + 1), 16) / 15;
}
return [ x(1), x(2), x(3) ];
}
};
fb.HSLToRGB = function (hsl) {
var m1, m2, r, g, b;
var h = hsl[0], s = hsl[1], l = hsl[2];
m2 = (l <= 0.5) ? l * (s + 1) : l + s - l * s;
m1 = l * 2 - m2;
return [
this.hueToRGB(m1, m2, h + 0.33333),
this.hueToRGB(m1, m2, h),
this.hueToRGB(m1, m2, h - 0.33333)
];
};
fb.hueToRGB = function (m1, m2, h) {
h = (h + 1) % 1;
if (h * 6 < 1) return m1 + (m2 - m1) * h * 6;
if (h * 2 < 1) return m2;
if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6;
return m1;
};
fb.RGBToHSL = function (rgb) {
var r = rgb[0], g = rgb[1], b = rgb[2],
min = Math.min(r, g, b),
max = Math.max(r, g, b),
delta = max - min,
h = 0,
s = 0,
l = (min + max) / 2;
if (l > 0 && l < 1) {
s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l));
}
if (delta > 0) {
if (max == r && max != g) h += (g - b) / delta;
if (max == g && max != b) h += (2 + (b - r) / delta);
if (max == b && max != r) h += (4 + (r - g) / delta);
h /= 6;
}
return [h, s, l];
};
// Parse options.
if (!options.callback) {
options = { callback: options };
}
options = $.extend({
width: 300,
wheelWidth: (options.width || 300) / 10,
callback: null
}, options);
// Initialize.
fb.initWidget();
// Install mousedown handler (the others are set on the document on-demand)
$('canvas.farbtastic-overlay', container).mousedown(fb.mousedown);
// Set linked elements/callback
if (options.callback) {
fb.linkTo(options.callback);
}
// Set to gray.
fb.setColor('#808080');
}
})(jQuery);

View file

@ -1,473 +0,0 @@
/*
http://www.JSON.org/json2.js
2011-02-23
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, strict: false, regexp: false */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// 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 = {};
}
(function()
{
"use strict";
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 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);
}
// What happens next depends on the value's type.
switch (typeof value)
{
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value)
{
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]')
{
// 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
});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function')
{
JSON.parse = function(text, reviver)
{
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key)
{
// 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');
};
}
}());
module.exports = JSON;

View file

@ -1,345 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
// 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.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// requires: easysync2.Changeset
// requires: top
// requires: plugins
// requires: undefined
var Changeset = require('/Changeset');
var plugins = require('/plugins').plugins;
var map = require('/ace2_common').map;
var linestylefilter = {};
linestylefilter.ATTRIB_CLASSES = {
'bold': 'tag:b',
'italic': 'tag:i',
'underline': 'tag:u',
'strikethrough': 'tag:s'
};
linestylefilter.getAuthorClassName = function(author)
{
return "author-" + author.replace(/[^a-y0-9]/g, function(c)
{
if (c == ".") return "-";
return 'z' + c.charCodeAt(0) + 'z';
});
};
// lineLength is without newline; aline includes newline,
// but may be falsy if lineLength == 0
linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFunc, apool)
{
var plugins_ = plugins;
if (lineLength == 0) return textAndClassFunc;
var nextAfterAuthorColors = textAndClassFunc;
var authorColorFunc = (function()
{
var lineEnd = lineLength;
var curIndex = 0;
var extraClasses;
var leftInAuthor;
function attribsToClasses(attribs)
{
var classes = '';
Changeset.eachAttribNumber(attribs, function(n)
{
var key = apool.getAttribKey(n);
if (key)
{
var value = apool.getAttribValue(n);
if (value)
{
if (key == 'author')
{
classes += ' ' + linestylefilter.getAuthorClassName(value);
}
else if (key == 'list')
{
classes += ' list:' + value;
}
else if (key == 'start')
{
classes += ' start:' + value;
}
else if (linestylefilter.ATTRIB_CLASSES[key])
{
classes += ' ' + linestylefilter.ATTRIB_CLASSES[key];
}
else
{
classes += plugins_.callHookStr("aceAttribsToClasses", {
linestylefilter: linestylefilter,
key: key,
value: value
}, " ", " ", "");
}
}
}
});
return classes.substring(1);
}
var attributionIter = Changeset.opIterator(aline);
var nextOp, nextOpClasses;
function goNextOp()
{
nextOp = attributionIter.next();
nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs));
}
goNextOp();
function nextClasses()
{
if (curIndex < lineEnd)
{
extraClasses = nextOpClasses;
leftInAuthor = nextOp.chars;
goNextOp();
while (nextOp.opcode && nextOpClasses == extraClasses)
{
leftInAuthor += nextOp.chars;
goNextOp();
}
}
}
nextClasses();
return function(txt, cls)
{
while (txt.length > 0)
{
if (leftInAuthor <= 0)
{
// prevent infinite loop if something funny's going on
return nextAfterAuthorColors(txt, cls);
}
var spanSize = txt.length;
if (spanSize > leftInAuthor)
{
spanSize = leftInAuthor;
}
var curTxt = txt.substring(0, spanSize);
txt = txt.substring(spanSize);
nextAfterAuthorColors(curTxt, (cls && cls + " ") + extraClasses);
curIndex += spanSize;
leftInAuthor -= spanSize;
if (leftInAuthor == 0)
{
nextClasses();
}
}
};
})();
return authorColorFunc;
};
linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc)
{
var at = /@/g;
at.lastIndex = 0;
var splitPoints = null;
var execResult;
while ((execResult = at.exec(lineText)))
{
if (!splitPoints)
{
splitPoints = [];
}
splitPoints.push(execResult.index);
}
if (!splitPoints) return textAndClassFunc;
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
};
linestylefilter.getRegexpFilter = function(regExp, tag)
{
return function(lineText, textAndClassFunc)
{
regExp.lastIndex = 0;
var regExpMatchs = null;
var splitPoints = null;
var execResult;
while ((execResult = regExp.exec(lineText)))
{
if (!regExpMatchs)
{
regExpMatchs = [];
splitPoints = [];
}
var startIndex = execResult.index;
var regExpMatch = execResult[0];
regExpMatchs.push([startIndex, regExpMatch]);
splitPoints.push(startIndex, startIndex + regExpMatch.length);
}
if (!regExpMatchs) return textAndClassFunc;
function regExpMatchForIndex(idx)
{
for (var k = 0; k < regExpMatchs.length; k++)
{
var u = regExpMatchs[k];
if (idx >= u[0] && idx < u[0] + u[1].length)
{
return u[1];
}
}
return false;
}
var handleRegExpMatchsAfterSplit = (function()
{
var curIndex = 0;
return function(txt, cls)
{
var txtlen = txt.length;
var newCls = cls;
var regExpMatch = regExpMatchForIndex(curIndex);
if (regExpMatch)
{
newCls += " " + tag + ":" + regExpMatch;
}
textAndClassFunc(txt, newCls);
curIndex += txtlen;
};
})();
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, splitPoints);
};
};
linestylefilter.REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
linestylefilter.REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + linestylefilter.REGEX_WORDCHAR.source + ')');
linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:|www\.)/.source + linestylefilter.REGEX_URLCHAR.source + '*(?![:.,;])' + linestylefilter.REGEX_URLCHAR.source, 'g');
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(
linestylefilter.REGEX_URL, 'url');
linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt)
{
var nextPointIndex = 0;
var idx = 0;
// don't split at 0
while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0)
{
nextPointIndex++;
}
function spanHandler(txt, cls)
{
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length)
{
func(txt, cls);
idx += txt.length;
}
else
{
var splitPoints = splitPointsOpt;
var pointLocInSpan = splitPoints[nextPointIndex] - idx;
var txtlen = txt.length;
if (pointLocInSpan >= txtlen)
{
func(txt, cls);
idx += txt.length;
if (pointLocInSpan == txtlen)
{
nextPointIndex++;
}
}
else
{
if (pointLocInSpan > 0)
{
func(txt.substring(0, pointLocInSpan), cls);
idx += pointLocInSpan;
}
nextPointIndex++;
// recurse
spanHandler(txt.substring(pointLocInSpan), cls);
}
}
}
return spanHandler;
};
linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser)
{
var func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
var plugins_ = plugins;
var hookFilters = plugins_.callHook("aceGetFilterStack", {
linestylefilter: linestylefilter,
browser: browser
});
map(hookFilters, function(hookFilter)
{
func = hookFilter(lineText, func);
});
if (browser !== undefined && browser.msie)
{
// IE7+ will take an e-mail address like <foo@bar.com> and linkify it to foo@bar.com.
// We then normalize it back to text with no angle brackets. It's weird. So always
// break spans at an "at" sign.
func = linestylefilter.getAtSignSplitterFilter(
lineText, func);
}
return func;
};
// domLineObj is like that returned by domline.createDomLine
linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj)
{
// remove final newline from text if any
var text = textLine;
if (text.slice(-1) == '\n')
{
text = text.substring(0, text.length - 1);
}
function textAndClassFunc(tokenText, tokenClass)
{
domLineObj.appendSpan(tokenText, tokenClass);
}
var func = linestylefilter.getFilterStack(text, textAndClassFunc);
func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
func(text, '');
};
exports.linestylefilter = linestylefilter;

View file

@ -1,974 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* global $, window */
var socket;
// These jQuery things should create local references, but for now `require()`
// assigns to the global `$` and augments it with plugins.
require('/jquery');
require('/farbtastic');
require('/excanvas');
JSON = require('/json2');
require('/undo-xpopup');
require('/prefixfree');
var chat = require('/chat').chat;
var getCollabClient = require('/collab_client').getCollabClient;
var padconnectionstatus = require('/pad_connectionstatus').padconnectionstatus;
var padcookie = require('/pad_cookie').padcookie;
var paddocbar = require('/pad_docbar').paddocbar;
var padeditbar = require('/pad_editbar').padeditbar;
var padeditor = require('/pad_editor').padeditor;
var padimpexp = require('/pad_impexp').padimpexp;
var padmodals = require('/pad_modals').padmodals;
var padsavedrevs = require('/pad_savedrevs').padsavedrevs;
var paduserlist = require('/pad_userlist').paduserlist;
var padutils = require('/pad_utils').padutils;
var createCookie = require('/pad_utils').createCookie;
var readCookie = require('/pad_utils').readCookie;
var randomString = require('/pad_utils').randomString;
function getParams()
{
var params = getUrlVars()
var showControls = params["showControls"];
var showChat = params["showChat"];
var userName = params["userName"];
var showLineNumbers = params["showLineNumbers"];
var useMonospaceFont = params["useMonospaceFont"];
var IsnoColors = params["noColors"];
var hideQRCode = params["hideQRCode"];
var rtl = params["rtl"];
var alwaysShowChat = params["alwaysShowChat"];
if(IsnoColors)
{
if(IsnoColors == "true")
{
settings.noColors = true;
$('#clearAuthorship').hide();
}
}
if(showControls)
{
if(showControls == "false")
{
$('#editbar').hide();
$('#editorcontainer').css({"top":"0px"});
}
}
if(showChat)
{
if(showChat == "false")
{
$('#chaticon').hide();
}
}
if(showLineNumbers)
{
if(showLineNumbers == "false")
{
settings.LineNumbersDisabled = true;
}
}
if(useMonospaceFont)
{
if(useMonospaceFont == "true")
{
settings.useMonospaceFontGlobal = true;
}
}
if(userName)
{
// If the username is set as a parameter we should set a global value that we can call once we have initiated the pad.
settings.globalUserName = decodeURIComponent(userName);
}
if(hideQRCode)
{
$('#qrcode').hide();
}
if(rtl)
{
if(rtl == "true")
{
settings.rtlIsTrue = true
}
}
if(alwaysShowChat)
{
if(alwaysShowChat == "true")
{
chat.stickToScreen();
}
}
}
function getUrlVars()
{
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++)
{
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
}
function savePassword()
{
//set the password cookie
createCookie("password",$("#passwordinput").val(),null,document.location.pathname);
//reload
document.location=document.location;
}
function handshake()
{
var loc = document.location;
//get the correct port
var port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port;
//create the url
var url = loc.protocol + "//" + loc.hostname + ":" + port + "/";
//find out in which subfolder we are
var resource = loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "socket.io";
//connect
socket = pad.socket = io.connect(url, {
resource: resource,
'max reconnection attempts': 3
});
function sendClientReady(isReconnect)
{
var padId = document.location.pathname.substring(document.location.pathname.lastIndexOf("/") + 1);
padId = decodeURIComponent(padId); // unescape neccesary due to Safari and Opera interpretation of spaces
if(!isReconnect)
document.title = padId.replace(/_+/g, ' ') + " | " + document.title;
var token = readCookie("token");
if (token == null)
{
token = "t." + randomString();
createCookie("token", token, 60);
}
var sessionID = readCookie("sessionID");
var password = readCookie("password");
var msg = {
"component": "pad",
"type": "CLIENT_READY",
"padId": padId,
"sessionID": sessionID,
"password": password,
"token": token,
"protocolVersion": 2
};
//this is a reconnect, lets tell the server our revisionnumber
if(isReconnect == true)
{
msg.client_rev=pad.collabClient.getCurrentRevisionNumber();
msg.reconnect=true;
}
socket.json.send(msg);
};
var disconnectTimeout;
socket.once('connect', function () {
sendClientReady(false);
});
socket.on('reconnect', function () {
//reconnect is before the timeout, lets stop the timeout
if(disconnectTimeout)
{
clearTimeout(disconnectTimeout);
}
pad.collabClient.setChannelState("CONNECTED");
sendClientReady(true);
});
socket.on('disconnect', function () {
function disconnectEvent()
{
pad.collabClient.setChannelState("DISCONNECTED", "reconnect_timeout");
}
pad.collabClient.setChannelState("RECONNECTING");
disconnectTimeout = setTimeout(disconnectEvent, 10000);
});
var receivedClientVars = false;
var initalized = false;
socket.on('message', function(obj)
{
//the access was not granted, give the user a message
if(!receivedClientVars && obj.accessStatus)
{
if(obj.accessStatus == "deny")
{
$("#editorloadingbox").html("<b>You do not have permission to access this pad</b>");
}
else if(obj.accessStatus == "needPassword")
{
$("#editorloadingbox").html("<b>You need a password to access this pad</b><br>" +
"<input id='passwordinput' type='password' name='password'>"+
"<button type='button' onclick=\"" + padutils.escapeHtml('require('+JSON.stringify(module.id)+").savePassword()") + "\">ok</button>");
}
else if(obj.accessStatus == "wrongPassword")
{
$("#editorloadingbox").html("<b>You're password was wrong</b><br>" +
"<input id='passwordinput' type='password' name='password'>"+
"<button type='button' onclick=\"" + padutils.escapeHtml('require('+JSON.stringify(module.id)+").savePassword()") + "\">ok</button>");
}
}
//if we haven't recieved the clientVars yet, then this message should it be
else if (!receivedClientVars)
{
//log the message
if (window.console) console.log(obj);
receivedClientVars = true;
//set some client vars
clientVars = obj;
clientVars.userAgent = "Anonymous";
clientVars.collab_client_vars.clientAgent = "Anonymous";
//initalize the pad
pad._afterHandshake();
initalized = true;
// If the LineNumbersDisabled value is set to true then we need to hide the Line Numbers
if (settings.LineNumbersDisabled == true)
{
pad.changeViewOption('showLineNumbers', false);
}
// If the noColors value is set to true then we need to hide the backround colors on the ace spans
if (settings.noColors == true)
{
pad.changeViewOption('noColors', true);
}
if (settings.rtlIsTrue == true)
{
pad.changeViewOption('rtl', true);
}
// If the Monospacefont value is set to true then change it to monospace.
if (settings.useMonospaceFontGlobal == true)
{
pad.changeViewOption('useMonospaceFont', true);
}
// if the globalUserName value is set we need to tell the server and the client about the new authorname
if (settings.globalUserName !== false)
{
pad.notifyChangeName(settings.globalUserName); // Notifies the server
pad.myUserInfo.name = settings.globalUserName;
$('#myusernameedit').attr({"value":settings.globalUserName}); // Updates the current users UI
}
}
//This handles every Message after the clientVars
else
{
//this message advices the client to disconnect
if (obj.disconnect)
{
padconnectionstatus.disconnected(obj.disconnect);
socket.disconnect();
return;
}
else
{
pad.collabClient.handleMessageFromServer(obj);
}
}
});
// Bind the colorpicker
var fb = $('#colorpicker').farbtastic({ callback: '#mycolorpickerpreview', width: 220});
}
var pad = {
// don't access these directly from outside this file, except
// for debugging
collabClient: null,
myUserInfo: null,
diagnosticInfo: {},
initTime: 0,
clientTimeOffset: null,
preloadedImages: false,
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()
{
return padutils.uaDisplay(clientVars.userAgent);
},
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)
{
pad.collabClient.sendClientMessage(msg);
},
init: function()
{
padutils.setupGlobalExceptionHandler();
$(document).ready(function()
{
// start the custom js
if (typeof customStart == "function") customStart();
getParams();
handshake();
});
$(window).unload(function()
{
pad.dispose();
});
},
_afterHandshake: function()
{
pad.clientTimeOffset = new Date().getTime() - clientVars.serverTimestamp;
//initialize the chat
chat.init(this);
pad.initTime = +(new Date());
pad.padOptions = clientVars.initialOptions;
if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0)))
{
document.domain = document.domain; // for comet
}
// for IE
if ($.browser.msie)
{
try
{
doc.execCommand("BackgroundImageCache", false, true);
}
catch (e)
{}
}
// order of inits is important here:
padcookie.init(clientVars.cookiePrefsToSet, this);
$("#widthprefcheck").click(pad.toggleWidthPref);
// $("#sidebarcheck").click(pad.togglewSidebar);
pad.myUserInfo = {
userId: clientVars.userId,
name: clientVars.userName,
ip: pad.getClientIp(),
colorId: clientVars.userColor,
userAgent: pad.getDisplayUserAgent()
};
if (clientVars.specialKey)
{
pad.myUserInfo.specialKey = clientVars.specialKey;
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
}, this);
padimpexp.init(this);
padsavedrevs.init(clientVars.initialRevisionList, this);
padeditor.init(postAceInit, pad.padOptions.view || {}, this);
paduserlist.init(pad.myUserInfo, this);
// padchat.init(clientVars.chatHistory, pad.myUserInfo);
padconnectionstatus.init();
padmodals.init(this);
pad.collabClient = getCollabClient(padeditor.ace, clientVars.collab_client_vars, pad.myUserInfo, {
colorPalette: pad.getColorPalette()
}, pad);
pad.collabClient.setOnUserJoin(pad.handleUserJoin);
pad.collabClient.setOnUpdateUserInfo(pad.handleUserUpdate);
pad.collabClient.setOnUserLeave(pad.handleUserLeave);
pad.collabClient.setOnClientMessage(pad.handleClientMessage);
pad.collabClient.setOnServerMessage(pad.handleServerMessage);
pad.collabClient.setOnChannelStateChange(pad.handleChannelStateChange);
pad.collabClient.setOnInternalAction(pad.handleCollabAction);
function postAceInit()
{
padeditbar.init();
setTimeout(function()
{
padeditor.ace.focus();
}, 0);
}
},
dispose: function()
{
padeditor.dispose();
},
notifyChangeName: function(newName)
{
pad.myUserInfo.name = newName;
pad.collabClient.updateUserInfo(pad.myUserInfo);
//padchat.handleUserJoinOrUpdate(pad.myUserInfo);
},
notifyChangeColor: function(newColorId)
{
pad.myUserInfo.colorId = newColorId;
pad.collabClient.updateUserInfo(pad.myUserInfo);
//padchat.handleUserJoinOrUpdate(pad.myUserInfo);
},
notifyChangeTitle: function(newTitle)
{
pad.collabClient.sendClientMessage(
{
type: 'padtitle',
title: newTitle,
changedBy: pad.myUserInfo.name || "unnamed"
});
},
notifyChangePassword: function(newPass)
{
pad.collabClient.sendClientMessage(
{
type: 'padpassword',
password: newPass,
changedBy: pad.myUserInfo.name || "unnamed"
});
},
changePadOption: function(key, value)
{
var options = {};
options[key] = value;
pad.handleOptionsChange(options);
pad.collabClient.sendClientMessage(
{
type: 'padoptions',
options: options,
changedBy: pad.myUserInfo.name || "unnamed"
});
},
changeViewOption: function(key, value)
{
var options = {
view: {}
};
options.view[key] = value;
pad.handleOptionsChange(options);
},
handleOptionsChange: function(opts)
{
// opts object is a full set of options or just
// some options to change
if (opts.view)
{
if (!pad.padOptions.view)
{
pad.padOptions.view = {};
}
for (var k in opts.view)
{
pad.padOptions.view[k] = opts.view[k];
}
padeditor.setViewOptions(pad.padOptions.view);
}
if (opts.guestPolicy)
{
// order important here
pad.padOptions.guestPolicy = opts.guestPolicy;
paddocbar.setGuestPolicy(opts.guestPolicy);
}
},
getPadOptions: function()
{
// caller shouldn't mutate the object
return pad.padOptions;
},
isPadPublic: function()
{
return (!pad.getIsProPad()) || (pad.getPadOptions().guestPolicy == 'allow');
},
suggestUserName: function(userId, name)
{
pad.collabClient.sendClientMessage(
{
type: 'suggestUserName',
unnamedId: userId,
newName: name
});
},
handleUserJoin: function(userInfo)
{
paduserlist.userJoinOrUpdate(userInfo);
//padchat.handleUserJoinOrUpdate(userInfo);
},
handleUserUpdate: function(userInfo)
{
paduserlist.userJoinOrUpdate(userInfo);
//padchat.handleUserJoinOrUpdate(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)
{
pad.notifyChangeName(msg.newName);
paduserlist.setMyUserInfo(pad.myUserInfo);
}
}
else if (msg.type == 'chat')
{
//padchat.receiveChat(msg);
}
else if (msg.type == 'padtitle')
{
paddocbar.changeTitle(msg.title);
}
else if (msg.type == 'padpassword')
{
paddocbar.changePassword(msg.password);
}
else if (msg.type == 'newRevisionList')
{
padsavedrevs.newRevisionList(msg.revisionList);
}
else if (msg.type == 'revisionLabel')
{
padsavedrevs.newRevisionList(msg.revisionList);
}
else if (msg.type == 'padoptions')
{
var opts = msg.options;
pad.handleOptionsChange(opts);
}
else if (msg.type == 'guestanswer')
{
// someone answered a prompt, remove it
paduserlist.removeGuestPrompt(msg.guestId);
}
},
editbarClick: function(cmd)
{
if (padeditbar)
{
padeditbar.toolbarClick(cmd);
}
},
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)
{
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) + ")");
abar.find("#servermsgtext").html(m.text);
});
}
if (m.js)
{
window['ev' + 'al'](m.js);
}
}
else if (m.type == 'GUEST_PROMPT')
{
paduserlist.showGuestPrompt(m.userId, m.displayName);
}
},
handleChannelStateChange: function(newState, message)
{
var oldFullyConnected = !! padconnectionstatus.isFullyConnected();
var wasConnecting = (padconnectionstatus.getStatus().what == 'connecting');
if (newState == "CONNECTED")
{
padconnectionstatus.connected();
}
else if (newState == "RECONNECTING")
{
padconnectionstatus.reconnecting();
}
else if (newState == "DISCONNECTED")
{
pad.diagnosticInfo.disconnectedMessage = message;
pad.diagnosticInfo.padId = pad.getPadId();
pad.diagnosticInfo.socket = {};
//we filter non objects from the socket object and put them in the diagnosticInfo
//this ensures we have no cyclic data - this allows us to stringify the data
for(var i in socket.socket)
{
var value = socket.socket[i];
var type = typeof value;
if(type == "string" || type == "number")
{
pad.diagnosticInfo.socket[i] = value;
}
}
pad.asyncSendDiagnosticInfo();
if (typeof window.ajlog == "string")
{
window.ajlog += ("Disconnected: " + message + '\n');
}
padeditor.disable();
padeditbar.disable();
paddocbar.disable();
padimpexp.disable();
padconnectionstatus.disconnected(message);
}
var newFullyConnected = !! padconnectionstatus.isFullyConnected();
if (newFullyConnected != oldFullyConnected)
{
pad.handleIsFullyConnected(newFullyConnected, wasConnecting);
}
},
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)
{
pad.preloadImages();
pad.preloadedImages = true;
}
}, 1000);
}
padsavedrevs.handleIsFullyConnected(isConnected);
// pad.determineSidebarVisibility(isConnected && !isInitialConnect);
pad.determineChatVisibility(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);
}
else
{
padutils.cancelActions("set-sidebar-visibility");
$("body").removeClass('hidesidebar');
}
},
*/
determineChatVisibility: function(asNowConnectedFeedback){
var chatVisCookie = padcookie.getPref('chatAlwaysVisible');
if(chatVisCookie){ // if the cookie is set for chat always visible
chat.stickToScreen(true); // stick it to the screen
$('#options-stickychat').prop("checked", true); // set the checkbox to on
}
else{
$('#options-stickychat').prop("checked", false); // set the checkbox for off
}
},
handleCollabAction: function(action)
{
if (action == "commitPerformed")
{
padeditbar.setSyncStatus("syncing");
}
else if (action == "newlyIdle")
{
padeditbar.setSyncStatus("done");
}
},
hideServerMessage: function()
{
alertBar.hideMessage();
},
asyncSendDiagnosticInfo: function()
{
window.setTimeout(function()
{
$.ajax(
{
type: 'post',
url: '/ep/pad/connection-diagnostic-info',
data: {
diagnosticInfo: JSON.stringify(pad.diagnosticInfo)
},
success: function()
{},
error: function()
{}
});
}, 0);
},
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');
padcookie.setPref('fullWidth', newValue);
$("#widthprefcheck").toggleClass('widthprefchecked', !! newValue).toggleClass('widthprefunchecked', !newValue);
pad.handleWidthChange();
},
/*
toggleSidebar: function()
{
var newValue = !padcookie.getPref('hideSidebar');
padcookie.setPref('hideSidebar', newValue);
$("#sidebarcheck").toggleClass('sidebarchecked', !newValue).toggleClass('sidebarunchecked', !! newValue);
pad.determineSidebarVisibility();
},
*/
handleWidthChange: function()
{
var isFullWidth = padcookie.getPref('fullWidth');
if (isFullWidth)
{
$("body").addClass('fullwidth').removeClass('limwidth').removeClass('squish1width').removeClass('squish2width');
}
else
{
$("body").addClass('limwidth').removeClass('fullwidth');
var pageWidth = $(window).width();
$("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));
},
callWhenNotCommitting: function(f)
{
pad.collabClient.callWhenNotCommitting(f);
},
getCollabRevisionNumber: function()
{
return pad.collabClient.getCurrentRevisionNumber();
},
isFullyConnected: function()
{
return padconnectionstatus.isFullyConnected();
},
addHistoricalAuthors: function(data)
{
if (!pad.collabClient)
{
window.setTimeout(function()
{
pad.addHistoricalAuthors(data);
}, 1000);
}
else
{
pad.collabClient.addHistoricalAuthors(data);
}
},
preloadImages: function()
{
var images = ["../static/img/connectingbar.gif"];
function loadNextImage()
{
if (images.length == 0)
{
return;
}
var img = new Image();
img.src = images.shift();
if (img.complete)
{
scheduleLoadNextImage();
}
else
{
$(img).bind('error load onreadystatechange', scheduleLoadNextImage);
}
}
function scheduleLoadNextImage()
{
window.setTimeout(loadNextImage, 0);
}
scheduleLoadNextImage();
}
};
var alertBar = (function()
{
var animator = padutils.makeShowHideAnimator(arriveAtAnimationState, false, 25, 400);
function arriveAtAnimationState(state)
{
if (state == -1)
{
$("#alertbar").css('opacity', 0).css('display', 'block');
}
else if (state == 0)
{
$("#alertbar").css('opacity', 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', 1 - state);
}
}
var self = {
displayMessage: function(setupFunc)
{
animator.show();
setupFunc($("#alertbar"));
},
hideMessage: function()
{
animator.hide();
}
};
return self;
}());
function init() {
return pad.init();
}
var settings = {
LineNumbersDisabled: false
, noColors: false
, useMonospaceFontGlobal: false
, globalUserName: false
, hideQRCode: false
, rtlIsTrue: false
};
pad.settings = settings;
exports.settings = settings;
exports.createCookie = createCookie;
exports.readCookie = readCookie;
exports.randomString = randomString;
exports.getParams = getParams;
exports.getUrlVars = getUrlVars;
exports.savePassword = savePassword;
exports.handshake = handshake;
exports.pad = pad;
exports.init = init;
exports.alertBar = alertBar;

View file

@ -1,91 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var padmodals = require('/pad_modals').padmodals;
var padconnectionstatus = (function()
{
var status = {
what: 'connecting'
};
var self = {
init: function()
{
$('button#forcereconnect').click(function()
{
window.location.reload();
});
},
connected: function()
{
status = {
what: 'connected'
};
padmodals.hideModal(500);
},
reconnecting: function()
{
status = {
what: 'reconnecting'
};
$("#connectionbox").get(0).className = 'modaldialog cboxreconnecting';
padmodals.showModal("#connectionbox", 500);
},
disconnected: function(msg)
{
if(status.what == "disconnected")
return;
status = {
what: 'disconnected',
why: msg
};
var k = String(msg).toLowerCase(); // known reason why
if (!(k == 'userdup' || k == 'deleted' || k == 'looping' || k == 'slowcommit' || k == 'initsocketfail' || k == 'unauth'))
{
k = 'unknown';
}
var cls = 'modaldialog cboxdisconnected cboxdisconnected_' + k;
$("#connectionbox").get(0).className = cls;
padmodals.showModal("#connectionbox", 500);
$('button#forcereconnect').click(function()
{
window.location.reload();
});
},
isFullyConnected: function()
{
return status.what == 'connected';
},
getStatus: function()
{
return status;
}
};
return self;
}());
exports.padconnectionstatus = padconnectionstatus;

View file

@ -1,133 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var padcookie = (function()
{
function getRawCookie()
{
// returns null if can't get cookie text
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]))
{
return null;
}
return regexResult[1];
}
function setRawCookie(safeText)
{
var expiresDate = new Date();
expiresDate.setFullYear(3000);
document.cookie = ('prefs=' + safeText + ';expires=' + expiresDate.toGMTString());
}
function parseCookie(text)
{
// returns null if can't parse cookie.
try
{
var cookieData = JSON.parse(unescape(text));
return cookieData;
}
catch (e)
{
return null;
}
}
function stringifyCookie(data)
{
return escape(JSON.stringify(data));
}
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.");
alreadyWarnedAboutNoCookies = true;
}
}
var wasNoCookie = true;
var cookieData = {};
var alreadyWarnedAboutNoCookies = false;
var inited = false;
var pad = undefined;
var self = {
init: function(prefsToSet, _pad)
{
pad = _pad;
var rawCookie = getRawCookie();
if (rawCookie)
{
var cookieObj = parseCookie(rawCookie);
if (cookieObj)
{
wasNoCookie = false; // there was a cookie
delete cookieObj.userId;
delete cookieObj.name;
delete cookieObj.colorId;
cookieData = cookieObj;
}
}
for (var k in prefsToSet)
{
cookieData[k] = prefsToSet[k];
}
inited = true;
saveCookie();
},
wasNoCookie: function()
{
return wasNoCookie;
},
getPref: function(prefName)
{
return cookieData[prefName];
},
setPref: function(prefName, value)
{
cookieData[prefName] = value;
saveCookie();
}
};
return self;
}());
exports.padcookie = padcookie;

View file

@ -1,466 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
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)
{
$("#docbar").removeClass(openingClass).removeClass(openClass).
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;
}
if (state == -1)
{
// startng to open
setPanelState("opening");
wrapper.css('height', '0');
}
else if (state < 0)
{
// opening
var height = Math.round(pow(state + 1) * (panelHeight - 1)) + 'px';
wrapper.css('height', height);
}
else if (state == 0)
{
// open
setPanelState("open");
wrapper.css('height', panelHeight - 1);
}
else if (state < 1)
{
// closing
setPanelState("closing");
var height = Math.round((1 - pow(state)) * (panelHeight - 1)) + 'px';
wrapper.css('height', height);
}
else if (state == 1)
{
// closed
setPanelState("closed");
wrapper.css('height', '0');
}
}
return padutils.makeShowHideAnimator(openCloseAnimate, false, 25, 500);
}
var currentPanel = null;
function setCurrentPanel(newCurrentPanel)
{
if (currentPanel != newCurrentPanel)
{
currentPanel = newCurrentPanel;
padutils.cancelActions("hide-docbar-panel");
}
}
var panels;
function changePassword(newPass)
{
if ((newPass || null) != (self.password || null))
{
self.password = (newPass || null);
pad.notifyChangePassword(newPass);
}
self.renderPassword();
}
var pad = undefined;
var self = {
title: null,
password: null,
init: function(opts, _pad)
{
pad = _pad;
panels = {
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");
});
$("#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');
});
$("#options-close").click(function()
{
self.setShownPanel(null);
});
$("#security-close").click(function()
{
self.setShownPanel(null);
});
if (pad.getIsProPad())
{
self.initPassword();
}
enabled = true;
self.render();
// public/private
$("#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");
self.render();
},
initPassword: function()
{
self.renderPassword();
$("#password-clearlink").click(function()
{
changePassword(null);
});
$("#password-setlink, #password-display").click(function()
{
self.enterPassword();
});
$("#password-cancellink").click(function()
{
self.exitPassword(false);
});
$("#password-savelink").click(function()
{
self.exitPassword(true);
});
padutils.bindEnterAndEscape($("#security-passwordedit"), function()
{
self.exitPassword(true);
}, function()
{
self.exitPassword(false);
});
},
enterPassword: function()
{
isEditingPassword = true;
$("#security-passwordedit").val(self.password || '');
self.renderPassword();
$("#security-passwordedit").focus().select();
},
exitPassword: function(accept)
{
isEditingPassword = false;
if (accept)
{
changePassword($("#security-passwordedit").val());
}
else
{
self.renderPassword();
}
},
renderPassword: function()
{
if (isEditingPassword)
{
$("#password-nonedit").hide();
$("#password-inedit").show();
}
else
{
$("#password-nonedit").toggleClass('nopassword', !self.password);
$("#password-setlink").html(self.password ? "Change..." : "Set...");
if (self.password)
{
$("#password-display").html(self.password.replace(/./g, '&#8226;'));
}
else
{
$("#password-display").html("None");
}
$("#password-inedit").hide();
$("#password-nonedit").show();
}
},
togglePanel: function(panelName)
{
if (panelName in panels)
{
if (currentPanel == panelName)
{
self.setShownPanel(null);
}
else
{
self.setShownPanel(panelName);
}
}
},
setShownPanel: function(panelName)
{
function animateHidePanel(panelName, next)
{
var delay = 0;
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()
{
panels[panelName].animator.hide();
if (next)
{
next();
}
}, delay);
}
if (!panelName)
{
if (currentPanel)
{
animateHidePanel(currentPanel);
setCurrentPanel(null);
}
}
else if (panelName in panels)
{
if (currentPanel != panelName)
{
if (currentPanel)
{
animateHidePanel(currentPanel, function()
{
panels[panelName].animator.show();
setCurrentPanel(panelName);
});
}
else
{
panels[panelName].animator.show();
setCurrentPanel(panelName);
}
}
}
},
isPanelShown: function(panelName)
{
if (!panelName)
{
return !currentPanel;
}
else
{
return (panelName == currentPanel);
}
},
changeTitle: function(newTitle)
{
self.title = newTitle;
self.render();
},
editTitle: function()
{
if (!enabled)
{
return;
}
$("#padtitleedit").val(self.title);
isEditingTitle = true;
self.render();
$("#padtitleedit").focus().select();
},
closeTitleEdit: function(accept)
{
if (!enabled)
{
return;
}
if (accept)
{
var newTitle = $("#padtitleedit").val();
if (newTitle)
{
newTitle = newTitle.substring(0, 80);
self.title = newTitle;
pad.notifyChangeTitle(newTitle);
}
}
isEditingTitle = false;
self.render();
},
changePassword: function(newPass)
{
if (newPass)
{
self.password = newPass;
}
else
{
self.password = null;
}
self.renderPassword();
},
render: function()
{
if (isEditingTitle)
{
$("#docbarpadtitle").hide();
$("#docbarrenamelink").hide();
$("#padtitleedit").show();
$("#padtitlebuttons").show();
if (!enabled)
{
$("#padtitleedit").attr('disabled', 'disabled');
}
else
{
$("#padtitleedit").removeAttr('disabled');
}
}
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").show();
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())
{
$("#docbar").addClass("docbar-public");
}
else
{
$("#docbar").removeClass("docbar-public");
}
}
},
disable: function()
{
enabled = false;
self.render();
},
handleResizePage: function()
{
// Side-step circular reference. This should be injected.
var padsavedrevs = require('/pad_savedrevs').padsavedrevs;
padsavedrevs.handleResizePage();
},
hideLaterIfNoOtherInteraction: function()
{
return padutils.getCancellableAction('hide-docbar-panel', function()
{
self.setShownPanel(null);
});
}
};
return self;
}());
exports.paddocbar = paddocbar;

View file

@ -1,256 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
var padeditor = require('/pad_editor').padeditor;
var padsavedrevs = require('/pad_savedrevs').padsavedrevs;
function indexOf(array, value) {
for (var i = 0, ii = array.length; i < ii; i++) {
if (array[i] == value) {
return i;
}
}
return -1;
}
var padeditbar = (function()
{
var syncAnimation = (function()
{
var SYNCING = -100;
var DONE = 100;
var state = DONE;
var fps = 25;
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)
{
return false;
}
else if (state >= T_GONE)
{
state = DONE;
$("#syncstatussyncing").css('display', 'none');
$("#syncstatusdone").css('display', 'none');
return false;
}
else if (state < 0)
{
state += step;
if (state >= 0)
{
$("#syncstatussyncing").css('display', 'none');
$("#syncstatusdone").css('display', 'block').css('opacity', 1);
}
return true;
}
else
{
state += step;
if (state >= T_FADE)
{
$("#syncstatusdone").css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
}
return true;
}
}, step * 1000);
return {
syncing: function()
{
state = SYNCING;
$("#syncstatussyncing").css('display', 'block');
$("#syncstatusdone").css('display', 'none');
},
done: function()
{
state = T_START;
animator.scheduleAnimation();
}
};
}());
var self = {
init: function()
{
$("#editbar .editbarbutton").attr("unselectable", "on"); // for IE
$("#editbar").removeClass("disabledtoolbar").addClass("enabledtoolbar");
},
isEnabled: function()
{
// return !$("#editbar").hasClass('disabledtoolbar');
return true;
},
disable: function()
{
$("#editbar").addClass('disabledtoolbar').removeClass("enabledtoolbar");
},
toolbarClick: function(cmd)
{
if (self.isEnabled())
{
if(cmd == "showusers")
{
self.toogleDropDown("users");
}
else if (cmd == 'settings')
{
self.toogleDropDown("settingsmenu");
}
else if (cmd == 'embed')
{
self.setEmbedLinks();
$('#linkinput').focus().select();
self.toogleDropDown("embed");
}
else if (cmd == 'import_export')
{
self.toogleDropDown("importexport");
}
else 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 == 'insertorderedlist') ace.ace_doInsertOrderedList();
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);
}
}
if(padeditor.ace) padeditor.ace.focus();
},
toogleDropDown: function(moduleName)
{
var modules = ["settingsmenu", "importexport", "embed", "users"];
//hide all modules
if(moduleName == "none")
{
$("#editbar ul#menu_right > li").removeClass("selected");
for(var i=0;i<modules.length;i++)
{
//skip the userlist
if(modules[i] == "users")
continue;
var module = $("#" + modules[i]);
if(module.css('display') != "none")
{
module.slideUp("fast");
}
}
}
else
{
var nth_child = indexOf(modules, moduleName) + 1;
if (nth_child > 0 && nth_child <= 3) {
$("#editbar ul#menu_right li:not(:nth-child(" + nth_child + "))").removeClass("selected");
$("#editbar ul#menu_right li:nth-child(" + nth_child + ")").toggleClass("selected");
}
//hide all modules that are not selected and show the selected one
for(var i=0;i<modules.length;i++)
{
var module = $("#" + modules[i]);
if(module.css('display') != "none")
{
module.slideUp("fast");
}
else if(modules[i]==moduleName)
{
module.slideDown("fast");
}
}
}
},
setSyncStatus: function(status)
{
if (status == "syncing")
{
syncAnimation.syncing();
}
else if (status == "done")
{
syncAnimation.done();
}
},
setEmbedLinks: function()
{
if ($('#readonlyinput').is(':checked'))
{
var basePath = document.location.href.substring(0, document.location.href.indexOf("/p/"));
var readonlyLink = basePath + "/ro/" + clientVars.readOnlyId;
$('#embedinput').val("<iframe src='" + readonlyLink + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400>");
$('#linkinput').val(readonlyLink);
$('#embedreadonlyqr').attr("src","https://chart.googleapis.com/chart?chs=200x200&cht=qr&chld=H|0&chl=" + readonlyLink);
}
else
{
var padurl = window.location.href.split("?")[0];
$('#embedinput').val("<iframe src='" + padurl + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400>");
$('#linkinput').val(padurl);
$('#embedreadonlyqr').attr("src","https://chart.googleapis.com/chart?chs=200x200&cht=qr&chld=H|0&chl=" + padurl);
}
}
};
return self;
}());
exports.padeditbar = padeditbar;

View file

@ -1,162 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var padcookie = require('/pad_cookie').padcookie;
var padutils = require('/pad_utils').padutils;
var padeditor = (function()
{
var Ace2Editor = undefined;
var pad = undefined;
var settings = undefined;
var self = {
ace: null,
// this is accessed directly from other files
viewZoom: 100,
init: function(readyFunc, initialViewOptions, _pad)
{
Ace2Editor = require('/ace').Ace2Editor;
pad = _pad;
settings = pad.settings;
function aceReady()
{
$("#editorloadingbox").hide();
if (readyFunc)
{
readyFunc();
}
}
self.ace = new Ace2Editor();
self.ace.init("editorcontainer", "", aceReady);
self.ace.setProperty("wraps", true);
if (pad.getIsDebugEnabled())
{
self.ace.setProperty("dmesg", pad.dmesg);
}
self.initViewOptions();
self.setViewOptions(initialViewOptions);
// view bar
self.initViewZoom();
$("#viewbarcontents").show();
},
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"));
});
$("#viewfontmenu").change(function()
{
pad.changeViewOption('useMonospaceFont', $("#viewfontmenu").val() == 'monospace');
});
},
setViewOptions: function(newOptions)
{
function getOption(key, defaultValue)
{
var value = String(newOptions[key]);
if (value == "true") return true;
if (value == "false") return false;
return defaultValue;
}
self.ace.setProperty("showsauthorcolors", !settings.noColors);
self.ace.setProperty("rtlIsTrue", settings.rtlIsTrue);
var v;
v = getOption('showLineNumbers', true);
self.ace.setProperty("showslinenumbers", v);
padutils.setCheckbox($("#options-linenoscheck"), v);
v = getOption('showAuthorColors', true);
self.ace.setProperty("showsauthorcolors", v);
padutils.setCheckbox($("#options-colorscheck"), v);
v = getOption('useMonospaceFont', false);
self.ace.setProperty("textface", (v ? "monospace" : "Arial, sans-serif"));
$("#viewfontmenu").val(v ? "monospace" : "normal");
},
initViewZoom: function()
{
var viewZoom = Number(padcookie.getPref('viewZoom'));
if ((!viewZoom) || isNaN(viewZoom))
{
viewZoom = 100;
}
self.setViewZoom(viewZoom);
$("#viewzoommenu").change(function(evt)
{
// strip initial 'z' from val
self.setViewZoom(Number($("#viewzoommenu").val().substring(1)));
});
},
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);
var baseSize = 13;
self.ace.setProperty('textsize', Math.round(baseSize * self.viewZoom / 100));
padcookie.setPref('viewZoom', percent);
},
dispose: function()
{
if (self.ace)
{
self.ace.destroy();
self.ace = null;
}
},
disable: function()
{
if (self.ace)
{
self.ace.setProperty("grayedOut", true);
self.ace.setEditable(false);
}
},
restoreRevisionText: function(dataFromServer)
{
pad.addHistoricalAuthors(dataFromServer.historicalAuthorData);
self.ace.importAText(dataFromServer.atext, dataFromServer.apool, true);
}
};
return self;
}());
exports.padeditor = padeditor;

View file

@ -1,333 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var paddocbar = require('/pad_docbar').paddocbar;
var padimpexp = (function()
{
///// import
var currentImportTimer = null;
var hidePanelCall = null;
function addImportFrames()
{
$("#import .importframe").remove();
var iframe = $('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>');
$('#import').append(iframe);
}
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');
}
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)
{
hidePanelCall = paddocbar.hideLaterIfNoOtherInteraction();
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);
$('#importarrow').stop(true, true).hide();
$('#importstatusball').show();
$("#import .importframe").load(function()
{
importDone();
});
}
return ret;
}
function importFailed(msg)
{
importErrorMessage(msg);
importDone();
addImportFrames();
}
function importDone()
{
$('#importsubmitinput').removeAttr('disabled').val("Import Now");
window.setTimeout(function()
{
$('#importfileinput').removeAttr('disabled');
}, 0);
$('#importstatusball').hide();
importClearTimeout();
}
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")]();
}
if ($('#importexport .importmessage').is(':visible'))
{
$('#importmessagesuccess').fadeOut("fast");
$('#importmessagefail').fadeOut("fast", function()
{
showError(true);
});
}
else
{
showError();
}
}
function importSuccessful(token)
{
$.ajax(
{
type: 'post',
url: '/ep/pad/impexp/import2',
data: {
token: token,
padId: pad.getPadId()
},
success: importApplicationSuccessful,
error: importApplicationFailed,
timeout: 25000
});
addImportFrames();
}
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'))
{
$('#importexport .importmessage').hide();
}
$('#importmessagesuccess').html('<strong style="color: green">Import successful!</strong>').show();
$('#importformfilediv').hide();
window.setTimeout(function()
{
$('#importmessagesuccess').fadeOut("slow", function()
{
$('#importformfilediv').show();
});
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:")
{
importErrorMessage(data.substr(4));
}
importDone();
}
///// export
function cantExport()
{
var type = $(this);
if (type.hasClass("exporthrefpdf"))
{
type = "PDF";
}
else if (type.hasClass("exporthrefdoc"))
{
type = "Microsoft Word";
}
else if (type.hasClass("exporthrefodt"))
{
type = "OpenDocument";
}
else
{
type = "this file";
}
alert("Exporting as " + type + " format is disabled. Please contact your" + " system administrator for details.");
return false;
}
/////
var pad = undefined;
var self = {
init: function(_pad)
{
pad = _pad;
//get /p/padname
var pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname)
//get http://example.com/p/padname
var pad_root_url = document.location.href.replace(document.location.pathname, pad_root_path)
// build the export links
$("#exporthtmla").attr("href", pad_root_path + "/export/html");
$("#exportplaina").attr("href", pad_root_path + "/export/txt");
$("#exportwordlea").attr("href", pad_root_path + "/export/wordle");
$("#exportdokuwikia").attr("href", pad_root_path + "/export/dokuwiki");
//hide stuff thats not avaible if abiword is disabled
if(clientVars.abiwordAvailable == "no")
{
$("#exportworda").remove();
$("#exportpdfa").remove();
$("#exportopena").remove();
$("#importexport").css({"height":"115px"});
$("#importexportline").css({"height":"115px"});
$("#import").html("Import is not available. To enable import please install abiword");
}
else if(clientVars.abiwordAvailable == "withoutPDF")
{
$("#exportpdfa").remove();
$("#exportworda").attr("href", pad_root_path + "/export/doc");
$("#exportopena").attr("href", pad_root_path + "/export/odt");
$("#importexport").css({"height":"142px"});
$("#importexportline").css({"height":"142px"});
$("#importform").attr('action', pad_root_url + "/import");
}
else
{
$("#exportworda").attr("href", pad_root_path + "/export/doc");
$("#exportpdfa").attr("href", pad_root_path + "/export/pdf");
$("#exportopena").attr("href", pad_root_path + "/export/odt");
$("#importform").attr('action', pad_root_path + "/import");
}
$("#impexp-close").click(function()
{
paddocbar.setShownPanel(null);
});
addImportFrames();
$("#importfileinput").change(fileInputUpdated);
$('#importform').submit(fileInputSubmit);
$('.disabledexport').click(cantExport);
},
handleFrameCall: function(callName, argsArray)
{
if (callName == 'importFailed')
{
importFailed(argsArray[0]);
}
else if (callName == 'importSuccessful')
{
importSuccessful(argsArray[0]);
}
},
disable: function()
{
$("#impexp-disabled-clickcatcher").show();
$("#import").css('opacity', 0.5);
$("#impexp-export").css('opacity', 0.5);
},
enable: function()
{
$("#impexp-disabled-clickcatcher").hide();
$("#import").css('opacity', 1);
$("#impexp-export").css('opacity', 1);
},
export2Wordle: function()
{
var padUrl = $('#exportwordlea').attr('href').replace(/\/wordle$/, '/txt')
$.get(padUrl, function(data)
{
$('.result').html(data);
$('#text').html(data);
$('#wordlepost').submit();
});
}
};
return self;
}());
exports.padimpexp = padimpexp;

View file

@ -1,374 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
var paddocbar = require('/pad_docbar').paddocbar;
var padmodals = (function()
{
/*var clearFeedbackEmail = function() {};
function clearFeedback() {
clearFeedbackEmail();
$("#feedbackbox-message").val('');
}
var sendingFeedback = false;
function setSendingFeedback(v) {
v = !! v;
if (sendingFeedback != v) {
sendingFeedback = v;
if (v) {
$("#feedbackbox-send").css('opacity', 0.75);
}
else {
$("#feedbackbox-send").css('opacity', 1);
}
}
}*/
var sendingInvite = false;
function setSendingInvite(v)
{
v = !! v;
if (sendingInvite != v)
{
sendingInvite = v;
if (v)
{
$(".sharebox-send").css('opacity', 0.75);
}
else
{
$("#sharebox-send").css('opacity', 1);
}
}
}
var clearShareBoxTo = function()
{};
function clearShareBox()
{
clearShareBoxTo();
}
var pad = undefined;
var self = {
init: function(_pad)
{
pad = _pad;
self.initFeedback();
self.initShareBox();
},
initFeedback: function()
{
/*var emailField = $("#feedbackbox-email");
clearFeedbackEmail =
padutils.makeFieldLabeledWhenEmpty(emailField, '(your email address)').clear;
clearFeedback();*/
$("#feedbackbox-hide").click(function()
{
self.hideModal();
});
/*$("#feedbackbox-send").click(function() {
self.sendFeedbackEmail();
});*/
$("#feedbackbutton").click(function()
{
self.showFeedback();
});
},
initShareBox: function()
{
$("#sharebutton").click(function()
{
self.showShareBox();
});
$("#sharebox-hide").click(function()
{
self.hideModal();
});
$("#sharebox-send").click(function()
{
self.sendInvite();
});
$("#sharebox-url").click(function()
{
$("#sharebox-url").focus().select();
});
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()
{
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");
},
getDefaultShareBoxSubjectForName: function(name)
{
return (name || "Somebody") + " invited you to an EtherPad document";
},
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));
},
showFeedback: function()
{
self.showModal("#feedbackbox");
},
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))
{
msgbox.val(self.getDefaultShareBoxMessageForName(pad.getUserName()));
}
var subjBox = $("#sharebox-subject");
if (subjBox.val() == self.getDefaultShareBoxSubjectForName(null))
{
subjBox.val(self.getDefaultShareBoxSubjectForName(pad.getUserName()));
}
if (pad.isPadPublic())
{
$("#sharebox-stripe").get(0).className = 'sharebox-stripe-public';
}
else
{
$("#sharebox-stripe").get(0).className = 'sharebox-stripe-private';
}
self.showModal("#sharebox", 500);
$("#sharebox-url").focus().select();
},
showModal: function(modalId, duration)
{
$(".modaldialog").hide();
$(modalId).show().css(
{
'opacity': 0
}).animate(
{
'opacity': 1
}, duration);
$("#modaloverlay").show().css(
{
'opacity': 0
}).animate(
{
'opacity': 1
}, 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();
});
},
hideFeedbackLaterIfNoOtherInteraction: function()
{
return padutils.getCancellableAction('hide-feedbackbox', function()
{
self.hideModal();
});
},
hideShareboxLaterIfNoOtherInteraction: function()
{
return padutils.getCancellableAction('hide-sharebox', function()
{
self.hideModal();
});
},
/* sendFeedbackEmail: function() {
if (sendingFeedback) {
return;
}
var message = $("#feedbackbox-message").val();
if (! message) {
return;
}
var email = ($("#feedbackbox-email").hasClass('editempty') ? '' :
$("#feedbackbox-email").val());
var padId = pad.getPadId();
var username = pad.getUserName();
setSendingFeedback(true);
$("#feedbackbox-response").html("Sending...").get(0).className = '';
$("#feedbackbox-response").show();
$.ajax({
type: 'post',
url: '/ep/pad/feedback',
data: {
feedback: message,
padId: padId,
username: username,
email: email
},
success: success,
error: error
});
var hideCall = self.hideFeedbackLaterIfNoOtherInteraction();
function success(msg) {
setSendingFeedback(false);
clearFeedback();
$("#feedbackbox-response").html("Thanks for your feedback").get(0).className = 'goodresponse';
$("#feedbackbox-response").show();
window.setTimeout(function() {
$("#feedbackbox-response").fadeOut('slow', function() {
hideCall();
});
}, 1500);
}
function error(e) {
setSendingFeedback(false);
$("#feedbackbox-response").html("Could not send feedback. Please email us at feedback"+"@"+"etherpad.com instead.").get(0).className = 'badresponse';
$("#feedbackbox-response").show();
}
},*/
sendInvite: function()
{
if (sendingInvite)
{
return;
}
if (!pad.isFullyConnected())
{
displayErrorMessage("Error: Connection to the server is down or flaky.");
return;
}
var message = $("#sharebox-message").val();
if (!message)
{
displayErrorMessage("Please enter a message body before sending.");
return;
}
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)
{
displayErrorMessage('Please enter at least one "To:" address.');
$("#sharebox-to").focus().select();
return;
}
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.');
return;
}
}
var subject = $("#sharebox-subject").val();
if (!subject)
{
subject = self.getDefaultShareBoxSubjectForName(pad.getUserName());
$("#sharebox-subject").val(subject); // force the default subject
}
var padId = pad.getPadId();
var username = pad.getUserName();
setSendingInvite(true);
$("#sharebox-response").html("Sending...").get(0).className = '';
$("#sharebox-response").show();
$.ajax(
{
type: 'post',
url: '/ep/pad/emailinvite',
data: {
message: message,
toEmails: emailArray.join(','),
subject: subject,
username: username,
padId: padId
},
success: success,
error: error
});
var hideCall = self.hideShareboxLaterIfNoOtherInteraction();
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()
{
hideCall();
});
}, 1500);
}
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)
{
$("#sharebox-response").html(msgHtml).get(0).className = 'badresponse';
$("#sharebox-response").show();
}
}
};
return self;
}());
exports.padmodals = padmodals;

View file

@ -1,526 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
var paddocbar = require('/pad_docbar').paddocbar;
var padsavedrevs = (function()
{
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>');
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(".srview").attr('href', viewLink);
var restoreLink = 'javascript:void(require('+JSON.stringify(module.id)+').padsavedrevs.restoreRevision(' + JSON.stringify(rnum) + ');';
box.find(".srrestore").attr('href', restoreLink);
box.find(".srname").click(function(evt)
{
editRevisionLabel(rnum, box);
});
return box;
}
function setBoxLabel(box, label)
{
box.find(".srname").html(padutils.escapeHtml(label)).attr('title', label);
}
function setBoxTimestamp(box, timestamp)
{
box.find(".srtime").html(padutils.escapeHtml(
padutils.timediff(new Date(timestamp))));
}
function getNthBox(n)
{
return $("#savedrevisions .srouterbox").eq(n);
}
function editRevisionLabel(rnum, box)
{
var input = $('<input type="text" class="srnameedit"/>');
box.find(".srnameedit").remove(); // just in case
var label = box.find(".srname");
input.width(label.width());
input.height(label.height());
input.css('top', label.position().top);
input.css('left', label.position().left);
label.after(input);
label.css('opacity', 0);
function endEdit()
{
input.remove();
label.css('opacity', 1);
}
var rev = currentRevisionList[rnum];
var oldLabel = rev.label;
input.blur(function()
{
var newLabel = input.val();
if (newLabel && newLabel != oldLabel)
{
relabelRevision(rnum, newLabel);
}
endEdit();
});
input.val(rev.label).focus().select();
padutils.bindEnterAndEscape(input, function onEnter()
{
input.blur();
}, function onEscape()
{
input.val('').blur();
});
}
function relabelRevision(rnum, newLabel)
{
var rev = currentRevisionList[rnum];
$.ajax(
{
type: 'post',
url: '/ep/pad/saverevisionlabel',
data: {
userId: pad.getUserId(),
padId: pad.getPadId(),
revId: rev.id,
newLabel: newLabel
},
success: success,
error: error
});
function success(text)
{
var newRevisionList = JSON.parse(text);
self.newRevisionList(newRevisionList);
pad.sendClientMessage(
{
type: 'revisionLabel',
revisionList: reversedCopy(currentRevisionList),
savedBy: pad.getUserName(),
newLabel: newLabel
});
}
function error(e)
{
alert("Oops! There was an error saving that revision label. Please try again later.");
}
}
var currentRevisionList = [];
function setRevisionList(newRevisionList, noAnimation)
{
// deals with changed labels and new added revisions
for (var i = 0; i < currentRevisionList.length; i++)
{
var a = currentRevisionList[i];
var b = newRevisionList[i];
if (b.label != a.label)
{
setBoxLabel(getNthBox(i), b.label);
}
}
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)
{
setDesiredScroll(getMaxScroll());
if (noAnimation)
{
setScroll(desiredScroll);
}
if (!noAnimation)
{
var nameOfLast = currentRevisionList[currentRevisionList.length - 1].label;
displaySavedTip(nameOfLast);
}
}
}
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)
{
$("#revision-notifier").css('opacity', 0).css('display', 'block');
}
else if (state == 0)
{
$("#revision-notifier").css('opacity', 1);
}
else if (state == 1)
{
$("#revision-notifier").css('opacity', 0).css('display', 'none');
}
else if (state < 0)
{
$("#revision-notifier").css('opacity', 1);
}
else if (state > 0)
{
$("#revision-notifier").css('opacity', 1 - state);
}
}, false, 25, 300);
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();
});
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()
{
return REVISION_BOX_WIDTH * currentRevisionList.length;
}
function getViewportWidth()
{
return $("#savedrevs-scrollouter").width();
}
function getMinScroll()
{
return Math.min(getViewportWidth(), getScrollWidth());
}
function getMaxScroll()
{
return getScrollWidth();
}
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)
{
updateScroll();
}
}
function updateScroll()
{
updateScrollArrows();
scrollAnimator.scheduleAnimation();
}
function updateScrollArrows()
{
$("#savedrevs-scrollleft").toggleClass("disabledscrollleft", desiredScroll <= getMinScroll());
$("#savedrevs-scrollright").toggleClass("disabledscrollright", desiredScroll >= getMaxScroll());
}
var scrollAnimator = padutils.makeAnimationScheduler(function()
{
setDesiredScroll(desiredScroll, true); // re-clamp
if (Math.abs(desiredScroll - curScroll) < 1)
{
setScroll(desiredScroll);
return false;
}
else
{
setScroll(curScroll + (desiredScroll - curScroll) * 0.5);
return true;
}
}, 50, 2);
var isSaving = false;
function setIsSaving(v)
{
isSaving = v;
rerenderButton();
}
function haveReachedRevLimit()
{
var mv = pad.getPrivilege('maxRevisions');
return (!(mv < 0 || mv > currentRevisionList.length));
}
function rerenderButton()
{
if (isSaving || (!pad.isFullyConnected()) || haveReachedRevLimit())
{
$("#savedrevs-savenow").css('opacity', 0.75);
}
else
{
$("#savedrevs-savenow").css('opacity', 1);
}
}
var scrollRepeatTimer = null;
var scrollStartTime = 0;
function setScrollRepeatTimer(dir)
{
clearScrollRepeatTimer();
scrollStartTime = +new Date;
scrollRepeatTimer = window.setTimeout(function f()
{
if (!scrollRepeatTimer)
{
return;
}
self.scroll(dir);
var scrollTime = (+new Date) - scrollStartTime;
var delay = (scrollTime > 2000 ? 50 : 300);
scrollRepeatTimer = window.setTimeout(f, delay);
}, 300);
$(document).bind('mouseup', clearScrollRepeatTimer);
}
function clearScrollRepeatTimer()
{
if (scrollRepeatTimer)
{
window.clearTimeout(scrollRepeatTimer);
scrollRepeatTimer = null;
}
$(document).unbind('mouseup', clearScrollRepeatTimer);
}
var pad = undefined;
var self = {
init: function(initialRevisions, _pad)
{
pad = _pad;
self.newRevisionList(initialRevisions, true);
$("#savedrevs-savenow").click(function()
{
self.saveNow();
});
$("#savedrevs-scrollleft").mousedown(function()
{
self.scroll('left');
setScrollRepeatTimer('left');
});
$("#savedrevs-scrollright").mousedown(function()
{
self.scroll('right');
setScrollRepeatTimer('right');
});
$("#savedrevs-close").click(function()
{
paddocbar.setShownPanel(null);
});
// update "saved n minutes ago" times
window.setInterval(function()
{
refreshRevisionList();
}, 60 * 1000);
},
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 hidePanel = paddocbar.hideLaterIfNoOtherInteraction();
var box = getNthBox(rnum);
if (confirm(warning))
{
box.find(".srtwirly").show();
$.ajax(
{
type: 'get',
url: '/ep/pad/getrevisionatext',
data: {
padId: pad.getPadId(),
revId: rev.id
},
success: success,
error: error
});
}
function success(resultJson)
{
untwirl();
var result = JSON.parse(resultJson);
padeditor.restoreRevisionText(result);
window.setTimeout(function()
{
hidePanel();
}, 0);
}
function error(e)
{
untwirl();
alert("Oops! There was an error retreiving the text (revNum= " + rev.revNum + "; padId=" + pad.getPadId());
}
function untwirl()
{
box.find(".srtwirly").hide();
}
},
showReachedLimit: function()
{
alert("Sorry, you do not have privileges to save more than " + pad.getPrivilege('maxRevisions') + " revisions.");
},
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)
{
return;
}
if (!pad.isFullyConnected())
{
return;
}
if (haveReachedRevLimit())
{
self.showReachedLimit();
return;
}
setIsSaving(true);
var savedBy = pad.getUserName() || "unnamed";
pad.callWhenNotCommitting(submitSave);
function submitSave()
{
$.ajax(
{
type: 'post',
url: '/ep/pad/saverevision',
data: {
padId: pad.getPadId(),
savedBy: savedBy,
savedById: pad.getUserId(),
revNum: pad.getCollabRevisionNumber()
},
success: success,
error: error
});
}
function success(text)
{
setIsSaving(false);
var newRevisionList = JSON.parse(text);
self.newRevisionList(newRevisionList);
pad.sendClientMessage(
{
type: 'newRevisionList',
revisionList: newRevisionList,
savedBy: savedBy
});
}
function error(e)
{
setIsSaving(false);
alert("Oops! The server failed to save the revision. Please try again later.");
}
},
handleResizePage: function()
{
updateScrollArrows();
},
handleIsFullyConnected: function(isConnected)
{
rerenderButton();
},
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);
}
}
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;
}());
exports.padsavedrevs = padsavedrevs;

View file

@ -1,814 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
var myUserInfo = {};
var colorPickerOpen = false;
var colorPickerSetup = false;
var previousColorId = 0;
var paduserlist = (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++);
}
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));
}
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 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 isNameEditable(data)
{
return (!data.name) && (data.status != 'Disconnected');
}
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)
{
// preserve input field node
for (var i = 0; i < tds.length; i++)
{
var oldTd = $(tr.find("td").get(i));
if (!oldTd.hasClass('usertdname'))
{
oldTd.replaceWith(tds[i]);
}
}
}
else
{
tr.html(tds.join(''));
}
return tr;
}
function getUserRowHtml(height, data)
{
var nameHtml;
var isGuest = (data.id.charAt(0) != 'p');
if (data.name)
{
nameHtml = padutils.escapeHtml(data.name);
if (isGuest && pad.getIsProPad())
{
nameHtml += ' (Guest)';
}
}
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('');
}
function getRowHtml(id, innerHtml)
{
return '<tr id="' + id + '">' + innerHtml + '</tr>';
}
function rowNode(row)
{
return $("#" + row.domId);
}
function handleRowData(row)
{
if (row.data && row.data.status == 'Disconnected')
{
row.opacity = 0.5;
}
else
{
delete row.opacity;
}
}
function handleRowNode(tr, data)
{
if (data.titleText)
{
var titleText = data.titleText;
window.setTimeout(function()
{
/* tr.attr('title', titleText)*/
}, 0);
}
else
{
tr.removeAttr('title');
}
}
function handleOtherUserInputs()
{
// handle 'INPUT' elements for naming other unnamed users
$("#otheruserstable input.newinput").each(function()
{
var input = $(this);
var tr = input.closest("tr");
if (tr.length > 0)
{
var index = tr.parent().children().index(tr);
if (index >= 0)
{
var userId = rowsPresent[index].data.id;
rowManagerMakeNameEditor($(this), userId);
}
}
}).removeClass('newinput');
}
// animationPower is 0 to skip animation, 1 for linear, 2 for quadratic, etc.
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
};
handleRowData(row);
rowsPresent.splice(position, 0, row);
var tr;
if (animationPower == 0)
{
tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data)));
row.animationStep = 0;
}
else
{
rowsFadingIn.push(row);
tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START))));
}
handleRowNode(tr, data);
if (position == 0)
{
$("table#otheruserstable").prepend(tr);
}
else
{
rowNode(rowsPresent[position - 1]).after(tr);
}
if (animationPower != 0)
{
scheduleAnimation();
}
handleOtherUserInputs();
return row;
}
function updateRow(position, data)
{
var row = rowsPresent[position];
if (row)
{
row.data = data;
handleRowData(row);
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));
handleRowNode(tr, data);
handleOtherUserInputs();
}
}
}
function removeRow(position, animationPower)
{
animationPower = (animationPower === undefined ? 4 : animationPower);
var row = rowsPresent[position];
if (row)
{
rowsPresent.splice(position, 1); // remove
if (animationPower == 0)
{
rowNode(row).remove();
}
else
{
row.animationStep = -row.animationStep; // use symmetry
row.animationPower = animationPower;
rowsFadingOut.push(row);
scheduleAnimation();
}
}
}
// newPosition is position after the row has been removed
function moveRow(oldPosition, newPosition, animationPower)
{
animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best
var row = rowsPresent[oldPosition];
if (row && oldPosition != newPosition)
{
var rowData = row.data;
removeRow(oldPosition, animationPower);
insertRow(newPosition, rowData, animationPower);
}
}
function animateStep()
{
// animation must be symmetrical
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)
{
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);
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)
{
// set HTML in case modified during animation
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
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);
}
else if (step == OPACITY_STEPS)
{
node.html(getEmptyRowHtml(animHeight));
}
else if (step <= ANIMATION_END)
{
node.find("td").height(animHeight);
}
else
{
rowsFadingOut.splice(i, 1); // remove from set
node.remove();
}
}
handleOtherUserInputs();
return (rowsFadingIn.length > 0) || (rowsFadingOut.length > 0); // is more to do
}
var self = {
insertRow: insertRow,
removeRow: removeRow,
moveRow: moveRow,
updateRow: updateRow
};
return self;
}()); ////////// rowManager
var otherUsersInfo = [];
var otherUsersData = [];
function rowManagerMakeNameEditor(jnode, userId)
{
setUpEditable(jnode, function()
{
var existingIndex = findExistingIndex(userId);
if (existingIndex >= 0)
{
return otherUsersInfo[existingIndex].name || '';
}
else
{
return '';
}
}, function(newName)
{
if (!newName)
{
jnode.addClass("editempty");
jnode.val("unnamed");
}
else
{
jnode.attr('disabled', 'disabled');
pad.suggestUserName(userId, newName);
}
});
}
function findExistingIndex(userId)
{
var existingIndex = -1;
for (var i = 0; i < otherUsersInfo.length; i++)
{
if (otherUsersInfo[i].userId == userId)
{
existingIndex = i;
break;
}
}
return existingIndex;
}
function setUpEditable(jqueryNode, valueGetter, valueSetter)
{
jqueryNode.bind('focus', function(evt)
{
var oldValue = valueGetter();
if (jqueryNode.val() !== oldValue)
{
jqueryNode.val(oldValue);
}
jqueryNode.addClass("editactive").removeClass("editempty");
});
jqueryNode.bind('blur', function(evt)
{
var newValue = jqueryNode.removeClass("editactive").val();
valueSetter(newValue);
});
padutils.bindEnterAndEscape(jqueryNode, function onEnter()
{
jqueryNode.blur();
}, function onEscape()
{
jqueryNode.val(valueGetter()).blur();
});
jqueryNode.removeAttr('disabled').addClass('editable');
}
function updateInviteNotice()
{
if (otherUsersInfo.length == 0)
{
$("#otheruserstable").hide();
$("#nootherusers").show();
}
else
{
$("#nootherusers").hide();
$("#otheruserstable").show();
}
}
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');
}
return true;
}, 1000);
var pad = undefined;
var self = {
init: function(myInitialUserInfo, _pad)
{
pad = _pad;
self.setMyUserInfo(myInitialUserInfo);
$("#otheruserstable tr").remove();
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);
});
}
// color picker
$("#myswatchbox").click(showColorPicker);
$("#mycolorpicker .pickerswatchouter").click(function()
{
$("#mycolorpicker .pickerswatchouter").removeClass('picked');
$(this).addClass('picked');
});
$("#mycolorpickersave").click(function()
{
closeColorPicker(true);
});
$("#mycolorpickercancel").click(function()
{
closeColorPicker(false);
});
//
},
setMyUserInfo: function(info)
{
//translate the colorId
if(typeof info.colorId == "number")
{
info.colorId = clientVars.colorPalette[info.colorId];
}
myUserInfo = $.extend(
{}, info);
self.renderMyUserInfo();
},
userJoinOrUpdate: function(info)
{
if ((!info.userId) || (info.userId == myUserInfo.userId))
{
// not sure how this would happen
return;
}
var userData = {};
userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId;
userData.name = info.name;
userData.status = '';
userData.activity = '';
userData.id = info.userId;
// Firefox ignores \n in title text; Safari does a linebreak
userData.titleText = [info.userAgent || '', info.ip || ''].join(' \n');
var existingIndex = findExistingIndex(info.userId);
var numUsersBesides = otherUsersInfo.length;
if (existingIndex >= 0)
{
numUsersBesides--;
}
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 idN = infoN.userId;
var idThis = info.userId;
return (nameN > nameThis) || (nameN == nameThis && idN > idThis);
});
if (existingIndex >= 0)
{
// update
if (existingIndex == newIndex)
{
otherUsersInfo[existingIndex] = info;
otherUsersData[existingIndex] = userData;
rowManager.updateRow(existingIndex, userData);
}
else
{
otherUsersInfo.splice(existingIndex, 1);
otherUsersData.splice(existingIndex, 1);
otherUsersInfo.splice(newIndex, 0, info);
otherUsersData.splice(newIndex, 0, userData);
rowManager.updateRow(existingIndex, userData);
rowManager.moveRow(existingIndex, newIndex);
}
}
else
{
otherUsersInfo.splice(newIndex, 0, info);
otherUsersData.splice(newIndex, 0, userData);
rowManager.insertRow(newIndex, userData);
}
updateInviteNotice();
self.updateNumberOfOnlineUsers();
},
updateNumberOfOnlineUsers: function()
{
var online = 1; // you are always online!
for (var i = 0; i < otherUsersData.length; i++)
{
if (otherUsersData[i].status == "")
{
online++;
}
}
$("#online_count").text(online);
return online;
},
userLeave: function(info)
{
var existingIndex = findExistingIndex(info.userId);
if (existingIndex >= 0)
{
var userData = otherUsersData[existingIndex];
userData.status = 'Disconnected';
rowManager.updateRow(existingIndex, userData);
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 newExistingIndex = findExistingIndex(thisUserId);
if (newExistingIndex >= 0)
{
var newUserData = otherUsersData[newExistingIndex];
if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer)
{
otherUsersInfo.splice(newExistingIndex, 1);
otherUsersData.splice(newExistingIndex, 1);
rowManager.removeRow(newExistingIndex);
updateInviteNotice();
}
}
}, 8000); // how long to wait
userData.leaveTimer = thisLeaveTimer;
}
updateInviteNotice();
self.updateNumberOfOnlineUsers();
},
showGuestPrompt: function(userId, displayName)
{
if (knocksToIgnore[userId])
{
return;
}
var encodedUserId = padutils.encodeUserId(userId);
var actionName = 'hide-guest-prompt-' + encodedUserId;
padutils.cancelActions(actionName);
var box = $("#guestprompt-" + encodedUserId);
if (box.length == 0)
{
// make guest prompt box
box = $('<div id="'+padutils.escapeHtml('guestprompt-' + encodedUserId) + '" class="guestprompt"><div class="choices"><a href="' + padutils.escapeHtml('javascript:void(require('+JSON.stringify(module.id)+').paduserlist.answerGuestPrompt(' + JSON.stringify(encodedUserId) + ',false))')+'">Deny</a> <a href="' + padutils.escapeHtml('javascript:void(require('+JSON.stringify(module.id)+').paduserlist.answerGuestPrompt(' + JSON.stringify(encodedUserId) + ',true))') + '">Approve</a></div><div class="guestname"><strong>Guest:</strong> ' + padutils.escapeHtml(displayName) + '</div></div>');
$("#guestprompts").append(box);
}
else
{
// update display name
box.find(".guestname").html('<strong>Guest:</strong> ' + padutils.escapeHtml(displayName));
}
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));
// remove ID now so a new knock by same user gets new, unfaded box
box.removeAttr('id').fadeOut("fast", function()
{
box.remove();
});
knocksToIgnore[userId] = true;
window.setTimeout(function()
{
delete knocksToIgnore[userId];
}, 5000);
},
answerGuestPrompt: function(encodedUserId, approve)
{
var guestId = padutils.decodeUserId(encodedUserId);
var msg = {
type: 'guestanswer',
authId: pad.getUserId(),
guestId: guestId,
answer: (approve ? "approved" : "denied")
};
pad.sendClientMessage(msg);
self.removeGuestPrompt(guestId);
},
renderMyUserInfo: function()
{
if (myUserInfo.name)
{
$("#myusernameedit").removeClass("editempty").val(
myUserInfo.name);
}
else
{
$("#myusernameedit").addClass("editempty").val("Enter your name");
}
if (colorPickerOpen)
{
$("#myswatchbox").addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable');
}
else
{
$("#myswatchbox").addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable');
}
$("#myswatch").css({'background-color': myUserInfo.colorId});
if ($.browser.msie && parseInt($.browser.version) <= 8) {
$("#usericon").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId,'background-color': myUserInfo.colorId});
}
else
{
$("#usericon").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId});
}
}
};
return self;
}());
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)
{
var newColor = $("#mycolorpickerpreview").css("background-color");
var parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
// parts now should be ["rgb(0, 70, 255", "0", "70", "255"]
delete (parts[0]);
for (var i = 1; i <= 3; ++i) {
parts[i] = parseInt(parts[i]).toString(16);
if (parts[i].length == 1) parts[i] = '0' + parts[i];
}
var newColor = "#" +parts.join(''); // "0070ff"
myUserInfo.colorId = newColor;
pad.notifyChangeColor(newColor);
paduserlist.renderMyUserInfo();
}
else
{
//pad.notifyChangeColor(previousColorId);
//paduserlist.renderMyUserInfo();
}
colorPickerOpen = false;
$("#mycolorpicker").fadeOut("fast");
}
function showColorPicker()
{
previousColorId = myUserInfo.colorId;
if (!colorPickerOpen)
{
var palette = pad.getColorPalette();
if (!colorPickerSetup)
{
var colorsList = $("#colorpickerswatches")
for (var i = 0; i < palette.length; i++)
{
var li = $('<li>', {
style: 'background: ' + palette[i] + ';'
});
li.appendTo(colorsList);
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
}
}
exports.paduserlist = paduserlist;

View file

@ -1,528 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Security = require('/security');
/**
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
*/
function randomString(len)
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = '';
len = len || 20
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
}
function createCookie(name, value, days, path)
{
if (days)
{
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
}
else var expires = "";
if(!path)
path = "/";
document.cookie = name + "=" + value + expires + "; path=" + 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;
}
var padutils = {
escapeHtml: function(x)
{
return Security.escapeHTML(String(x));
},
uniqueId: function()
{
var pad = require('/pad').pad; // Sidestep circular dependency
function encodeNum(n, width)
{
// returns string that is exactly 'width' chars, padding with zeros
// and taking rightmost digits
return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width);
}
return [pad.getClientIp(), encodeNum(+new Date, 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.');
},
uaDisplay: function(ua)
{
var m;
function clean(a)
{
var maxlen = 16;
a = a.replace(/[^a-zA-Z0-9\.]/g, '');
if (a.length > maxlen)
{
a = a.substr(0, maxlen);
}
return a;
}
function checkver(name)
{
var m = ua.match(RegExp(name + '\\/([\\d\\.]+)'));
if (m && m.length > 1)
{
return clean(name + m[1]);
}
return null;
}
// firefox
if (checkver('Firefox'))
{
return checkver('Firefox');
}
// misc browsers, including IE
m = ua.match(/compatible; ([^;]+);/);
if (m && m.length > 1)
{
return clean(m[1]);
}
// iphone
if (ua.match(/\(iPhone;/))
{
return 'iPhone';
}
// chrome
if (checkver('Chrome'))
{
return checkver('Chrome');
}
// safari
m = ua.match(/Safari\/[\d\.]+/);
if (m)
{
var v = '?';
m = ua.match(/Version\/([\d\.]+)/);
if (m && m.length > 1)
{
v = m[1];
}
return clean('Safari' + v);
}
// everything else
var x = ua.split(' ')[0];
return clean(x);
},
// e.g. "Thu Jun 18 2009 13:09"
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 dayOfMonth = d.getDate();
var year = d.getFullYear();
var hourmin = d.getHours() + ":" + ("0" + d.getMinutes()).slice(-2);
return dayOfWeek + ' ' + month + ' ' + dayOfMonth + ' ' + year + ' ' + hourmin;
},
findURLs: function(text)
{
// copied from ACE
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
function _findURLs(text)
{
_REGEX_URL.lastIndex = 0;
var urls = null;
var execResult;
while ((execResult = _REGEX_URL.exec(text)))
{
urls = (urls || []);
var startIndex = execResult.index;
var url = execResult[0];
urls.push([startIndex, url]);
}
return urls;
}
return _findURLs(text);
},
escapeHtmlWithClickableLinks: function(text, target)
{
var idx = 0;
var pieces = [];
var urls = padutils.findURLs(text);
function advanceTo(i)
{
if (i > idx)
{
pieces.push(Security.escapeHTML(text.substring(idx, i)));
idx = i;
}
}
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="' + Security.escapeHTMLAttribute(target) + '" ' : ''), 'href="', Security.escapeHTMLAttribute(href), '">');
advanceTo(startIndex + href.length);
pieces.push('</a>');
}
}
advanceTo(text.length);
return pieces.join('');
},
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)
{
onEnter(evt);
}
});
}
if (onEscape)
{
node.keydown(function(evt)
{
if (evt.which == 27)
{
onEscape(evt);
}
});
}
},
timediff: function(d)
{
var pad = require('/pad').pad; // Sidestep circular dependency
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');
}
d /= 60;
if (d < 60)
{
return format(d, 'minute');
}
d /= 60;
if (d < 24)
{
return format(d, 'hour');
}
d /= 24;
return format(d, 'day');
},
makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce)
{
if (stepsAtOnce === undefined)
{
stepsAtOnce = 1;
}
var animationTimer = null;
function scheduleAnimation()
{
if (!animationTimer)
{
animationTimer = window.setTimeout(function()
{
animationTimer = null;
var n = stepsAtOnce;
var moreToDo = true;
while (moreToDo && n > 0)
{
moreToDo = funcToAnimateOneStep();
n--;
}
if (moreToDo)
{
// more to do
scheduleAnimation();
}
}, stepTime * stepsAtOnce);
}
}
return {
scheduleAnimation: scheduleAnimation
};
},
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;
function doShow()
{
animationState = -1;
funcToArriveAtState(animationState);
scheduleAnimation();
}
function doQuickShow()
{ // start showing without losing any fade-in progress
if (animationState < -1)
{
animationState = -1;
}
else if (animationState <= 0)
{
animationState = animationState;
}
else
{
animationState = Math.max(-1, Math.min(0, -animationState));
}
funcToArriveAtState(animationState);
scheduleAnimation();
}
function doHide()
{
if (animationState >= -1 && animationState <= 0)
{
animationState = 1e-6;
scheduleAnimation();
}
}
function animateOneStep()
{
if (animationState < -1 || animationState == 0)
{
return false;
}
else if (animationState < 0)
{
// animate show
animationState += animationStep;
if (animationState >= 0)
{
animationState = 0;
funcToArriveAtState(animationState);
return false;
}
else
{
funcToArriveAtState(animationState);
return true;
}
}
else if (animationState > 0)
{
// animate hide
animationState += animationStep;
if (animationState >= 1)
{
animationState = 1;
funcToArriveAtState(animationState);
animationState = -2;
return false;
}
else
{
funcToArriveAtState(animationState);
return true;
}
}
}
return {
show: doShow,
hide: doHide,
quickShow: doQuickShow
};
},
_nextActionId: 1,
uncanceledActions: {},
getCancellableAction: function(actionType, actionFunc)
{
var o = padutils.uncanceledActions[actionType];
if (!o)
{
o = {};
padutils.uncanceledActions[actionType] = o;
}
var actionId = (padutils._nextActionId++);
o[actionId] = true;
return function()
{
var p = padutils.uncanceledActions[actionType];
if (p && p[actionId])
{
actionFunc();
}
};
},
cancelActions: function(actionType)
{
var o = padutils.uncanceledActions[actionType];
if (o)
{
// clear it
delete padutils.uncanceledActions[actionType];
}
},
makeFieldLabeledWhenEmpty: function(field, labelText)
{
field = $(field);
function clear()
{
field.addClass('editempty');
field.val(labelText);
}
field.focus(function()
{
if (field.hasClass('editempty'))
{
field.val('');
}
field.removeClass('editempty');
});
field.blur(function()
{
if (!field.val())
{
clear();
}
});
return {
clear: clear
};
},
getCheckbox: function(node)
{
return $(node).is(':checked');
},
setCheckbox: function(node, value)
{
if (value)
{
$(node).attr('checked', 'checked');
}
else
{
$(node).removeAttr('checked');
}
},
bindCheckboxChange: function(node, func)
{
$(node).bind("click change", func);
},
encodeUserId: function(userId)
{
return userId.replace(/[^a-y0-9]/g, function(c)
{
if (c == ".") return "-";
return 'z' + c.charCodeAt(0) + 'z';
});
},
decodeUserId: function(encodedUserId)
{
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc)
{
if (cc == '-') return '.';
else if (cc.charAt(0) == 'z')
{
return String.fromCharCode(Number(cc.slice(1, -1)));
}
else
{
return cc;
}
});
}
};
var globalExceptionHandler = undefined;
function setupGlobalExceptionHandler() {
//send javascript errors to the server
if (!globalExceptionHandler) {
globalExceptionHandler = function test (msg, url, linenumber)
{
var errObj = {errorInfo: JSON.stringify({msg: msg, url: url, linenumber: linenumber, userAgent: navigator.userAgent})};
var loc = document.location;
var url = loc.protocol + "//" + loc.hostname + ":" + loc.port + "/" + loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "jserror";
$.post(url, errObj);
return false;
};
window.onerror = globalExceptionHandler;
}
}
padutils.setupGlobalExceptionHandler = setupGlobalExceptionHandler;
padutils.binarySearch = require('/ace2_common').binarySearch;
exports.randomString = randomString;
exports.createCookie = createCookie;
exports.readCookie = readCookie;
exports.padutils = padutils;

View file

@ -1,37 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
var plugins = {
callHook: function(hookName, args)
{
var global = (function () {return this}());
var hook = ((global.clientVars || {}).hooks || {})[hookName];
if (hook === undefined) return [];
var res = [];
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);
}
return res;
},
callHookStr: function(hookName, args, sep, pre, post)
{
if (sep == undefined) sep = '';
if (pre == undefined) pre = '';
if (post == undefined) post = '';
var newCallhooks = [];
var callhooks = plugins.callHook(hookName, args);
for (var i = 0, ii = callhooks.length; i < ii; i++) {
newCallhooks[i] = pre + callhooks[i] + post;
}
return newCallhooks.join(sep || "");
}
};
exports.plugins = plugins;

View file

@ -1,54 +0,0 @@
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var HTML_ENTITY_MAP = {
'&': '&amp;'
, '<': '&lt;'
, '>': '&gt;'
, '"': '&quot;'
, "'": '&#x27;'
, '/': '&#x2F;'
};
// OSWASP Guidlines: &, <, >, ", ' plus forward slash.
var HTML_CHARACTERS_EXPRESSION = /[&"'<>\/]/g;
function escapeHTML(text) {
return text && text.replace(HTML_CHARACTERS_EXPRESSION, function (c) {
return HTML_ENTITY_MAP[c] || c;
});
}
// OSWASP Guidlines: escape all non alphanumeric characters in ASCII space.
var HTML_ATTRIBUTE_CHARACTERS_EXPRESSION =
/[\x00-\x2F\x3A-\x40\5B-\x60\x7B-\xFF]/g;
function escapeHTMLAttribute(text) {
return text && text.replace(HTML_ATTRIBUTE_CHARACTERS_EXPRESSION, function (c) {
return "&#x" + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + ";";
});
};
// OSWASP Guidlines: escape all non alphanumeric characters in ASCII space.
var JAVASCRIPT_CHARACTERS_EXPRESSION =
/[\x00-\x2F\x3A-\x40\5B-\x60\x7B-\xFF]/g;
function escapeJavaScriptData(text) {
return text && text.replace(JAVASCRIPT_CHARACTERS_EXPRESSION, function (c) {
return "\\x" + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
});
}
exports.escapeHTML = escapeHTML;
exports.escapeHTMLAttribute = escapeHTMLAttribute;
exports.escapeJavaScriptData = escapeJavaScriptData;

View file

@ -1,492 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function newSkipList()
{
var PROFILER = window.PROFILER;
if (!PROFILER)
{
PROFILER = function()
{
return {
start: noop,
mark: noop,
literal: noop,
end: noop,
cancel: 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 numNodes = 0;
var totalWidth = 0;
var keyToNodeMap = {};
start.downPtrs[0] = end;
end.upPtrs[0] = start;
// a "point" object at location x allows modifications immediately after the first
// x elements of the skiplist, such as multiple inserts or deletes.
// 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)
{
var numLevels = start.levels;
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)
{
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];
}
nodes[lvl] = n;
idxs[lvl] = i;
widthSkips[lvl] = ws;
lvl--;
if (lvl >= 0)
{
nodes[lvl] = n;
}
}
return {
nodes: nodes,
idxs: idxs,
loc: targetLoc,
widthSkips: widthSkips,
toString: function()
{
return "getPoint(" + targetLoc + ")";
}
};
}
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];
}
lvl--;
}
if (n === start) return (start.downPtrs[0] || null);
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)
{
var p = PROFILER("insertKey", false);
var newNode = {
key: newKey,
levels: 0,
upPtrs: [],
downPtrs: [],
downSkips: [],
downSkipWidths: []
};
p.mark("donealloc");
var pNodes = point.nodes;
var pIdxs = point.idxs;
var pLoc = point.loc;
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)
{
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;
}
var me = newNode;
var up = pNodes[lvl];
var down = up.downPtrs[lvl];
var skip1 = pLoc - pIdxs[lvl];
var skip2 = up.downSkips[lvl] + 1 - skip1;
up.downSkips[lvl] = skip1;
up.downPtrs[lvl] = me;
me.downSkips[lvl] = skip2;
me.upPtrs[lvl] = up;
me.downPtrs[lvl] = down;
down.upPtrs[lvl] = me;
var widthSkip1 = widthLoc - point.widthSkips[lvl];
var widthSkip2 = up.downSkipWidths[lvl] + newWidth - widthSkip1;
up.downSkipWidths[lvl] = widthSkip1;
me.downSkipWidths[lvl] = widthSkip2;
}
p.mark("loop2");
p.literal(pNodes.length, "PNL");
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;
numNodes++;
totalWidth += newWidth;
p.end();
}
function _getNodeAtPoint(point)
{
return point.nodes[0].downPtrs[0];
}
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];
}
}
}
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;
}
else
{
var up = point.nodes[i];
var down = up.downPtrs[i];
up.downSkips[i]--;
up.downSkipWidths[i] -= elemWidth;
}
}
delete keyToNodeMap['$KEY$' + elem.key];
numNodes--;
totalWidth -= elemWidth;
}
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)
{
n.downSkipWidths[lvl] += widthChange;
lvl++;
while (lvl >= n.levels && n.upPtrs[lvl - 1])
{
n = n.upPtrs[lvl - 1];
}
}
totalWidth += widthChange;
}
function _getNodeIndex(node, byWidth)
{
var dist = (byWidth ? 0 : -1);
var n = node;
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() {
var array = [start];
while (array[array.length-1] !== end) {
array[array.length] = array[array.length-1].downPtrs[0];
}
function getIndex(node) {
if (!node) return null;
for(var i=0;i<array.length;i++) {
if (array[i] === node)
return i-1;
}
return false;
}
var processedArray = map(array, function(node) {
var x = {key:node.key, levels: node.levels, downSkips: node.downSkips,
upPtrs: map(node.upPtrs, getIndex), downPtrs: map(node.downPtrs, getIndex),
downSkipWidths: node.downSkipWidths};
return x;
});
return map(processedArray, function (x) { return x.toSource(); }).join("\n");
}*/
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)
{
var low = start;
var lvl = start.levels - 1;
var lowIndex = -1;
function f(node)
{
if (node === start) return false;
else if (node === end) return true;
else return entryFunc(node.entry);
}
while (lvl >= 0)
{
var nextLow = low.downPtrs[lvl];
while (!f(nextLow))
{
lowIndex += low.downSkips[lvl];
low = nextLow;
nextLow = low.downPtrs[lvl];
}
lvl--;
}
return lowIndex + 1;
}
/*
The skip-list contains "entries", JavaScript objects that each must have a unique "key" property
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 + ")");
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();
}
if (!newEntryArray) newEntryArray = [];
var pt = _getPoint(start);
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;
}
},
next: function(entry)
{
return _getNodeByKey(entry.key).downPtrs[0].entry || null;
},
prev: function(entry)
{
return _getNodeByKey(entry.key).upPtrs[0].entry || null;
},
push: function(entry)
{
self.splice(numNodes, 0, [entry]);
},
slice: function(start, end)
{
// act like Array.slice()
if (start === undefined) start = 0;
else if (start < 0) start += numNodes;
if (end === undefined) end = numNodes;
else if (end < 0) end += numNodes;
if (start < 0) start = 0;
if (start > numNodes) start = numNodes;
if (end < 0) end = 0;
if (end > numNodes) 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);
}
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));
},
// 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)
{
if (i < 0) return 0;
if (i >= numNodes) return totalWidth;
return self.offsetOfEntry(self.atIndex(i));
},
indexOfOffset: function(offset)
{
if (offset <= 0) return 0;
if (offset >= totalWidth) return numNodes;
return self.indexOfEntry(self.atOffset(offset));
},
search: function(entryFunc)
{
return _search(entryFunc);
},
//debugToString: _debugToString,
debugGetPoint: _getPoint,
debugDepth: function()
{
return start.levels;
}
}
return self;
}
exports.newSkipList = newSkipList;

View file

@ -1,154 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// These jQuery things should create local references, but for now `require()`
// assigns to the global `$` and augments it with plugins.
require('/jquery');
JSON = require('/json2');
require('/undo-xpopup');
var createCookie = require('/pad_utils').createCookie;
var readCookie = require('/pad_utils').readCookie;
var randomString = require('/pad_utils').randomString;
var socket, token, padId, export_links;
function init() {
$(document).ready(function ()
{
// start the custom js
if (typeof customStart == "function") customStart();
//get the padId out of the url
var urlParts= document.location.pathname.split("/");
padId = decodeURIComponent(urlParts[urlParts.length-2]);
//set the title
document.title = padId.replace(/_+/g, ' ') + " | " + document.title;
//ensure we have a token
token = readCookie("token");
if(token == null)
{
token = "t." + randomString();
createCookie("token", token, 60);
}
var loc = document.location;
//get the correct port
var port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port;
//create the url
var url = loc.protocol + "//" + loc.hostname + ":" + port + "/";
//find out in which subfolder we are
var resource = loc.pathname.substr(1,loc.pathname.indexOf("/p/")) + "socket.io";
//build up the socket io connection
socket = io.connect(url, {resource: resource});
//send the ready message once we're connected
socket.on('connect', function()
{
sendSocketMsg("CLIENT_READY", {});
});
//route the incoming messages
socket.on('message', function(message)
{
if(window.console) console.log(message);
if(message.type == "CLIENT_VARS")
{
handleClientVars(message);
}
else if(message.type == "CHANGESET_REQ")
{
changesetLoader.handleSocketResponse(message);
}
else if(message.accessStatus)
{
$("body").html("<h2>You have no permission to access this pad</h2>")
}
});
//get all the export links
export_links = $('#export > .exportlink')
if(document.referrer.length > 0 && document.referrer.substring(document.referrer.lastIndexOf("/")-1,document.referrer.lastIndexOf("/")) === "p") {
$("#returnbutton").attr("href", document.referrer);
} else {
$("#returnbutton").attr("href", document.location.href.substring(0,document.location.href.lastIndexOf("/")));
}
});
}
//sends a message over the socket
function sendSocketMsg(type, data)
{
var sessionID = readCookie("sessionID");
var password = readCookie("password");
var msg = { "component" : "timeslider",
"type": type,
"data": data,
"padId": padId,
"token": token,
"sessionID": sessionID,
"password": password,
"protocolVersion": 2};
socket.json.send(msg);
}
var fireWhenAllScriptsAreLoaded = [];
var BroadcastSlider, changesetLoader;
function handleClientVars(message)
{
//save the client Vars
clientVars = message.data;
//load all script that doesn't work without the clientVars
BroadcastSlider = require('/broadcast_slider').loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded);
require('/broadcast_revisions').loadBroadcastRevisionsJS();
changesetLoader = require('/broadcast').loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider);
//initialize export ui
require('/pad_impexp').padimpexp.init();
//change export urls when the slider moves
var export_rev_regex = /(\/\d+)?\/export/
BroadcastSlider.onSlider(function(revno)
{
export_links.each(function()
{
this.setAttribute('href', this.href.replace(export_rev_regex, '/' + revno + '/export'));
});
});
//fire all start functions of these scripts, formerly fired with window.load
for(var i=0;i < fireWhenAllScriptsAreLoaded.length;i++)
{
fireWhenAllScriptsAreLoaded[i]();
}
}
exports.init = init;

View file

@ -1,34 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (window._orig_windowOpen)
{
window.open = _orig_windowOpen;
}
if (window._orig_windowSetTimeout)
{
window.setTimeout = _orig_windowSetTimeout;
}
if (window._orig_windowSetInterval)
{
window.setInterval = _orig_windowSetInterval;
}

View file

@ -1,335 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Changeset = require('/Changeset');
var extend = require('/ace2_common').extend;
var undoModule = (function()
{
var stack = (function()
{
var stackElements = [];
// two types of stackElements:
// 1) { elementType: UNDOABLE_EVENT, eventType: "anything", [backset: <changeset>,]
// [selStart: <char number>, selEnd: <char number>, selFocusAtStart: <boolean>] }
// 2) { elementType: EXTERNAL_CHANGE, changeset: <changeset> }
// invariant: no two consecutive EXTERNAL_CHANGEs
var numUndoableEvents = 0;
var UNDOABLE_EVENT = "undoableEvent";
var EXTERNAL_CHANGE = "externalChange";
function clearStack()
{
stackElements.length = 0;
stackElements.push(
{
elementType: UNDOABLE_EVENT,
eventType: "bottom"
});
numUndoableEvents = 1;
}
clearStack();
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());
}
else
{
stackElements.push(
{
elementType: EXTERNAL_CHANGE,
changeset: cs
});
}
}
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--;
}
}
}
function getNthFromTop(n)
{
// precond: 0 <= n < numEvents()
_exposeEvent(n);
return stackElements[stackElements.length - 1 - n];
}
function numEvents()
{
return numUndoableEvents;
}
function popEvent()
{
// precond: numEvents() > 0
_exposeEvent(0);
numUndoableEvents--;
return stackElements.pop();
}
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()
{
stack.clearStack();
undoPtr = 0;
}
function _charOccurrences(str, c)
{
var i = 0;
var count = 0;
while (i >= 0 && i < str.length)
{
i = str.indexOf(c, i);
if (i >= 0)
{
count++;
i++;
}
}
return count;
}
function _opcodeOccurrences(cs, opcode)
{
return _charOccurrences(Changeset.unpack(cs).ops, opcode);
}
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
// each does exactly one deletion, no ins, and the composition does also.
// A little weird in that it won't merge "make bold" with "insert char"
// but will merge "make bold and insert char" with "insert char",
// though that isn't expected to come up.
var plusCount1 = _opcodeOccurrences(cs1, '+');
var plusCount2 = _opcodeOccurrences(cs2, '+');
var minusCount1 = _opcodeOccurrences(cs1, '-');
var minusCount2 = _opcodeOccurrences(cs2, '-');
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;
}
}
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;
}
}
return null;
}
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;
}
}
if ((!event.backset) || Changeset.isIdentity(event.backset))
{
applySelectionToTop();
}
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 (!merged)
{
stack.pushEvent(event);
}
undoPtr = 0;
}
}
function reportExternalChange(changeset)
{
if (changeset && !Changeset.isIdentity(changeset))
{
stack.pushExternalChange(changeset);
}
}
function _getSelectionInfo(event)
{
if ((typeof event.selStart) != "number")
{
return null;
}
else
{
return {
selStart: event.selStart,
selEnd: event.selEnd,
selFocusAtStart: event.selFocusAtStart
};
}
}
// For "undo" and "redo", the change event must be returned
// by eventFunc and NOT reported through the normal mechanism.
// "eventFunc" should take a changeset and an optional selection info object,
// 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)
{
var backsetEvent = stack.getNthFromTop(undoPtr);
var selectionEvent = stack.getNthFromTop(undoPtr + 1);
var undoEvent = eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent));
stack.pushEvent(undoEvent);
undoPtr += 2;
}
else eventFunc();
}
function performRedo(eventFunc)
{
if (undoPtr >= 2)
{
var backsetEvent = stack.getNthFromTop(0);
var selectionEvent = stack.getNthFromTop(1);
eventFunc(backsetEvent.backset, _getSelectionInfo(selectionEvent));
stack.popEvent();
undoPtr -= 2;
}
else eventFunc();
}
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
})();
exports.undoModule = undoModule;

View file

@ -1,388 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function 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
// coordinates and the approximate line height we can estimate how
// many lines we have moved. We risk being off if the number of lines
// we move is on the order of the line height in pixels. Fortunately,
// when the user boosts the font-size they increase both.
var maxCharIncrement = 20;
var seekerAtEnd = null;
function getNumChars()
{
return lineNode.textContent.length;
}
function getNumVirtualLines()
{
if (!seekerAtEnd)
{
var seeker = makeCharSeeker();
seeker.forwardByWhile(maxCharIncrement);
seekerAtEnd = seeker;
}
return seekerAtEnd.getVirtualLine() + 1;
}
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;
});
var lineStartChar = seeker.getOffset();
return {
vline: theLine,
offset: (lineChar - lineStartChar)
};
}
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()
{
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);
// get back into line
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))
{
var lineLen = getNumChars();
theOffset += lineLen - lineChar;
lineChar = lineLen;
}
return {
vline: theLine,
offset: theOffset,
lineChar: lineChar
};
}
return {
getNumVirtualLines: getNumVirtualLines,
getVLineAndOffsetForChar: getVLineAndOffsetForChar,
getCharForVLineAndOffset: getCharForVLineAndOffset,
makeCharSeeker: function()
{
return makeCharSeeker();
}
};
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)
{
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
}
}
var charWrapper = document.createElement("SPAN");
// wrap the character
var tnodeText = tnode.nodeValue;
var frag = document.createDocumentFragment();
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)));
container.replaceChild(frag, tnode);
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;
}
var lineText = lineNode.textContent;
var lineLength = lineText.length;
var curNode = null;
var curChar = 0;
var curCharWithinNode = 0
var curTop;
var curLeft;
var approxLineHeight;
var whichLine = 0;
function nextNode()
{
var n = curNode;
if (!n) n = lineNode.firstChild;
else n = n.nextSibling;
while (n && !deepFirstChildTextNode(n))
{
n = n.nextSibling;
}
return n;
}
function prevNode()
{
var n = curNode;
if (!n) n = lineNode.lastChild;
else n = n.previousSibling;
while (n && !deepFirstChildTextNode(n))
{
n = n.previousSibling;
}
return n;
}
var seeker;
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;
}
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;
}
};
}
else
{
curLeft = lineNode.offsetLeft;
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() + "]";
};
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));
}
}
seeker.forwardByWhile = function(amount, optCondFunc, optCharLimit)
{
moveByWhile(false, amount, optCondFunc, optCharLimit);
}
seeker.backwardByWhile = function(amount, optCondFunc, optCharLimit)
{
moveByWhile(true, amount, optCondFunc, optCharLimit);
}
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();
};
seeker.forwardByWhile(20, falseFunc);
seeker.backwardByWhile(20, trueFunc);
seeker.forwardByWhile(10, falseFunc);
seeker.backwardByWhile(5, trueFunc);
seeker.forwardByWhile(1, falseFunc);
return seeker.getOffset() + (condFunc() ? 0 : 1);
}
return seeker;
}
}
exports.makeVirtualLineView = makeVirtualLineView;