This commit is contained in:
John McLear 2014-03-05 21:27:12 +00:00
commit 89d7ba0dc0
118 changed files with 4890 additions and 5102 deletions

View file

@ -61,7 +61,7 @@ body {
margin: 0;
white-space: nowrap;
word-wrap: normal;
}
}
#outerdocbody {
background-color: #fff;
@ -159,8 +159,6 @@ p {
font-family: monospace; /* overridden by lineMetricsDiv.style */
}
#overlaysdiv { position: absolute; left: -1000px; top: -1000px; }
/* Stops super long lines without being spaces such as aaaaaaaaaaaaaa*100 breaking the editor
Commented out because it stops IE from being able to render the document, crazy IE bug is crazy. */
/*

View file

@ -1067,3 +1067,14 @@ input[type=checkbox] {
}
/* End of gritter stuff */
.activeButton{
background: #eee;
background: -webkit-linear-gradient(#ddd, #fff);
background: -moz-linear-gradient(#ddd, #fff);
background: -o-linear-gradient(#ddd, #fff);
background: -ms-linear-gradient(#ddd, #fff);
background: linear-gradient(#ddd, #fff);
-webkit-box-shadow: 0 0 8px rgba(0,0,0,.1) inset;
-moz-box-shadow: 0 0 8px rgba(0,0,0,.1) inset;
box-shadow: 0 0 8px rgba(0,0,0,.1) inset;
}

View file

@ -673,9 +673,9 @@ exports.textLinesMutator = function (lines) {
}
//print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length);
/*if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) {
print("BLAH");
putCurLineInSplice();
}*/
print("BLAH");
putCurLineInSplice();
}*/
// tests case foo in remove(), which isn't otherwise covered in current impl
}
//debugPrint("skip");
@ -1841,14 +1841,6 @@ exports.inverse = function (cs, lines, alines, pool) {
}
}
function lines_length() {
if ((typeof lines.length) == "number") {
return lines.length;
} else {
return lines.length();
}
}
function alines_get(idx) {
if (alines.get) {
return alines.get(idx);
@ -1857,14 +1849,6 @@ exports.inverse = function (cs, lines, alines, pool) {
}
}
function alines_length() {
if ((typeof alines.length) == "number") {
return alines.length;
} else {
return alines.length();
}
}
var curLine = 0;
var curChar = 0;
var curLineOpIter = null;

View file

@ -124,6 +124,7 @@ function Ace2Editor()
editor.getInInternationalComposition = function()
{
if (!loaded) return false;
return info.ace_getInInternationalComposition();
};
@ -313,7 +314,7 @@ window.onload = function () {\n\
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
// (throbs busy while typing)
outerHTML.push('<style type="text/css" title="dynamicsyntax"></style>', '<link rel="stylesheet" type="text/css" href="data:text/css,"/>', scriptTag(outerScript), '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>');
outerHTML.push('<style type="text/css" title="dynamicsyntax"></style>', '<link rel="stylesheet" type="text/css" href="data:text/css,"/>', scriptTag(outerScript), '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div></body></html>');
var outerFrame = document.createElement("IFRAME");
outerFrame.name = "ace_outer";

View file

@ -51,7 +51,6 @@ function Ace2Inner(){
var linestylefilter = require('./linestylefilter').linestylefilter;
var SkipList = require('./skiplist');
var undoModule = require('./undomodule').undoModule;
var makeVirtualLineView = require('./virtual_lines').makeVirtualLineView;
var AttributeManager = require('./AttributeManager');
var DEBUG = false; //$$ build script replaces the string "var DEBUG=true;//$$" with "var DEBUG=false;"
@ -79,7 +78,6 @@ function Ace2Inner(){
iframe.ace_outerWin = null; // prevent IE 6 memory leak
var sideDiv = iframe.nextSibling;
var lineMetricsDiv = sideDiv.nextSibling;
var overlaysdiv = lineMetricsDiv.nextSibling;
initLineNumbers();
var outsideKeyDown = noop;
@ -102,13 +100,13 @@ function Ace2Inner(){
apool: new AttribPool()
};
// lines, alltext, alines, and DOM are set up in setup()
// lines, alltext, alines, and DOM are set up in init()
if (undoModule.enabled)
{
undoModule.apool = rep.apool;
}
var root, doc; // set in setup()
var root, doc; // set in init()
var isEditable = true;
var doesWrap = true;
var hasLineNumbers = true;
@ -346,19 +344,6 @@ function Ace2Inner(){
}
}
function boldColorFromColor(lightColorCSS)
{
var color = colorutils.css2triple(lightColorCSS);
// amp up the saturation to full
color = colorutils.saturate(color);
// normalize brightness based on luminosity
color = colorutils.scaleColor(color, 0, 0.5 / colorutils.luminosity(color));
return colorutils.triple2css(color);
}
function fadeColor(colorCSS, fadeFrac)
{
var color = colorutils.css2triple(colorCSS);
@ -551,22 +536,6 @@ function Ace2Inner(){
}
editorInfo.ace_inCallStackIfNecessary = inCallStackIfNecessary;
function recolorLineByKey(key)
{
if (rep.lines.containsKey(key))
{
var offset = rep.lines.offsetOfKey(key);
var width = rep.lines.atKey(key).width;
recolorLinesInRange(offset, offset + width);
}
}
function getLineKeyForOffset(charOffset)
{
return rep.lines.atOffset(charOffset).key;
}
function dispose()
{
disposed = true;
@ -1173,34 +1142,6 @@ function Ace2Inner(){
}
editorInfo.ace_fastIncorp = fastIncorp;
function incorpIfQuick()
{
var me = incorpIfQuick;
var failures = (me.failures || 0);
if (failures < 5)
{
var isTimeUp = newTimeLimit(40);
var madeChanges = incorporateUserChanges(isTimeUp);
if (isTimeUp())
{
me.failures = failures + 1;
}
return true;
}
else
{
var skipCount = (me.skipCount || 0);
skipCount++;
if (skipCount == 20)
{
skipCount = 0;
me.failures = 0;
}
me.skipCount = skipCount;
}
return false;
}
var idleWorkTimer = makeIdleAction(function()
{
@ -1727,7 +1668,7 @@ function Ace2Inner(){
{
//var id = n.uniqueId();
// parent of n may not be "root" in IE due to non-tree-shaped DOM (wtf)
n.parentNode.removeChild(n);
if(n.parentNode) n.parentNode.removeChild(n);
//dmesg(htmlPrettyEscape(htmlForRemovedChild(n)));
//console.log("removed: "+id);
@ -1807,13 +1748,6 @@ function Ace2Inner(){
return domChanges;
}
function htmlForRemovedChild(n)
{
var div = doc.createElement("DIV");
div.appendChild(n);
return div.innerHTML;
}
var STYLE_ATTRIBS = {
bold: true,
italic: true,
@ -2372,6 +2306,76 @@ function Ace2Inner(){
}
editorInfo.ace_setAttributeOnSelection = setAttributeOnSelection;
function getAttributeOnSelection(attributeName){
if (!(rep.selStart && rep.selEnd)) return;
var selectionAllHasIt = true;
var withIt = Changeset.makeAttribsString('+', [
[attributeName, 'true']
], rep.apool);
var withItRegex = new RegExp(withIt.replace(/\*/g, '\\*') + "(\\*|$)");
function hasIt(attribs)
{
return withItRegex.test(attribs);
}
var selStartLine = rep.selStart[0];
var selEndLine = rep.selEnd[0];
for (var n = selStartLine; n <= selEndLine; n++)
{
var opIter = Changeset.opIterator(rep.alines[n]);
var indexIntoLine = 0;
var selectionStartInLine = 0;
var selectionEndInLine = rep.lines.atIndex(n).text.length; // exclude newline
if(rep.lines.atIndex(n).text.length == 0){
return false; // If the line length is 0 we basically treat it as having no formatting
}
if(rep.selStart[1] == rep.selEnd[1] && rep.selStart[1] == rep.lines.atIndex(n).text.length){
return false; // If we're at the end of a line we treat it as having no formatting
}
if(rep.selStart[1] == 0 && rep.selEnd[1] == 0){
rep.selEnd[1] == 1;
}
if(rep.selEnd[1] == -1){
rep.selEnd[1] = 1; // sometimes rep.selEnd is -1, not sure why.. When it is we should look at the first char
}
if (n == selStartLine)
{
selectionStartInLine = rep.selStart[1];
}
if (n == selEndLine)
{
selectionEndInLine = rep.selEnd[1];
}
while (opIter.hasNext())
{
var op = opIter.next();
var opStartInLine = indexIntoLine;
var opEndInLine = opStartInLine + op.chars;
if (!hasIt(op.attribs))
{
// does op overlap selection?
if (!(opEndInLine <= selectionStartInLine || opStartInLine >= selectionEndInLine))
{
selectionAllHasIt = false;
break;
}
}
indexIntoLine = opEndInLine;
}
if (!selectionAllHasIt)
{
break;
}
}
if(selectionAllHasIt){
return true;
}else{
return false;
}
}
editorInfo.ace_getAttributeOnSelection = getAttributeOnSelection;
function toggleAttributeOnSelection(attributeName)
{
if (!(rep.selStart && rep.selEnd)) return;
@ -3950,6 +3954,7 @@ function Ace2Inner(){
selection.focusAtStart = !! rep.selFocusAtStart;
setSelection(selection);
}
editorInfo.ace_updateBrowserSelectionFromRep = updateBrowserSelectionFromRep;
function nodeMaxIndex(nd)
{
@ -4187,7 +4192,7 @@ function Ace2Inner(){
selection.startPoint.index+" / "+
selection.endPoint.node.uniqueId()+","+
selection.endPoint.index);
}*/
}*/
}
return selection;
}
@ -4833,54 +4838,6 @@ function Ace2Inner(){
else $(elem).removeClass(className);
}
function setup()
{
doc = document; // defined as a var in scope outside
inCallStackIfNecessary("setup", function()
{
var body = doc.getElementById("innerdocbody");
root = body; // defined as a var in scope outside
if (browser.mozilla) addClass(root, "mozilla");
if (browser.safari) addClass(root, "safari");
if (browser.msie) addClass(root, "msie");
if (browser.msie)
{
// cache CSS background images
try
{
doc.execCommand("BackgroundImageCache", false, true);
}
catch (e)
{ /* throws an error in some IE 6 but not others! */
}
}
setClassPresence(root, "authorColors", true);
setClassPresence(root, "doesWrap", doesWrap);
initDynamicCSS();
enforceEditability();
// set up dom and rep
while (root.firstChild) root.removeChild(root.firstChild);
var oneEntry = createDomLineEntry("");
doRepLineSplice(0, rep.lines.length(), [oneEntry]);
insertDomLines(null, [oneEntry.domInfo], null);
rep.alines = Changeset.splitAttributionLines(
Changeset.makeAttribution("\n"), "\n");
bindTheEventHandlers();
});
scheduler.setTimeout(function()
{
parent.readyFunc(); // defined in code that sets up the inner iframe
}, 0);
isSetUp = true;
}
function focus()
{
window.focus();
@ -5255,7 +5212,7 @@ function Ace2Inner(){
if(h){ // apply style to div
div.style.height = h +"px";
}
}
div.appendChild(odoc.createTextNode(String(n)));
fragment.appendChild(div);

View file

@ -81,7 +81,7 @@ $(document).ready(function () {
if(attr == "name"){ // Hack to rewrite URLS into name
row.find(".name").html("<a target='_blank' title='Plugin details' href='https://npmjs.org/package/"+plugin['name']+"'>"+plugin['name'].substr(3)+"</a>"); // remove 'ep_'
}else{
row.find("." + attr).html(plugin[attr]);
row.find("." + attr).text(plugin[attr]);
}
}
row.find(".version").html( plugin.version );

View file

@ -77,7 +77,6 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
}
var socketId;
//var socket;
var channelState = "DISCONNECTED";

View file

@ -461,10 +461,10 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
var startPos = clientVars.collab_client_vars.rev;
if(window.location.hash.length > 1)
{
var hashRev = Number(window.location.hash.substr(1));
if(!isNaN(hashRev))
{
// this is necessary because of the socket.io-event which loads the changesets
var hashRev = Number(window.location.hash.substr(1));
if(!isNaN(hashRev))
{
// this is necessary because of the socket.io-event which loads the changesets
setTimeout(function() { setSliderPosition(hashRev); }, 1);
}
}

View file

@ -162,7 +162,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
}
else
{
// add forEach function to Array.prototype for IE8
if (!('forEach' in Array.prototype)) {
Array.prototype.forEach= function(action, that /*opt*/) {
@ -171,7 +171,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
action.call(that, this[i], i, this);
};
}
// Get my authorID
var authorId = parent.parent.pad.myUserInfo.userId;

View file

@ -183,12 +183,12 @@ var chat = (function()
self.send();
}
});
// initial messages are loaded in pad.js' _afterHandshake
$("#chatcounter").text(0);
$("#chatloadmessagesbutton").click(function()
{
// initial messages are loaded in pad.js' _afterHandshake
$("#chatcounter").text(0);
$("#chatloadmessagesbutton").click(function()
{
var start = Math.max(self.historyPointer - 20, 0);
var end = self.historyPointer;
@ -200,7 +200,7 @@ var chat = (function()
pad.collabClient.sendMessage({"type": "GET_CHAT_MESSAGES", "start": start, "end": end});
self.historyPointer = start;
});
});
}
}

View file

@ -40,11 +40,9 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
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;
@ -52,12 +50,10 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
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 = [];
@ -196,7 +192,6 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
changeset: userChangesData.changeset,
apool: userChangesData.apool
};
stateMessageSocketId = socketId;
sendMessage(stateMessage);
sentMessage = true;
callbacks.onInternalAction("commitPerformed");
@ -209,17 +204,6 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
}
}
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()
{
hiccupCount = 0;
@ -505,16 +489,6 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
}
}
function keys(obj)
{
var array = [];
$.each(obj, function(k, v)
{
array.push(k);
});
return array;
}
function valuesArray(obj)
{
var array = [];
@ -593,7 +567,6 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
{
obj.committedChangeset = stateMessage.changeset;
obj.committedChangesetAPool = stateMessage.apool;
obj.committedChangesetSocketId = stateMessageSocketId;
editor.applyPreparedChangesetToBase();
}
var userChangesData = editor.prepareUserChangeset();

View file

@ -398,7 +398,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
if (endPoint && node == endPoint.node)
{
selEnd = _pointHere(0, state);
}
}
}
while (txt.length > 0)
{
@ -467,7 +467,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
var startNewLine= (typeof(induceLineBreak)=='object'&&induceLineBreak.length==0)?true:induceLineBreak[0];
if(startNewLine){
cc.startNewLine(state);
}
}
}
else if (tname == "script" || tname == "style")
{

View file

@ -258,6 +258,7 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
result.node.innerHTML = curHTML;
}
if (lineClass !== null) result.node.className = lineClass;
hooks.callAll("acePostWriteDomLineHTML", {
node: result.node
});

View file

@ -2,7 +2,7 @@
(function ($) {
var __debug = false;
var __factor = 0.5;
var __factor = 0.8;
$.fn.farbtastic = function (options) {
$.farbtastic(this, options);

View file

@ -97,21 +97,21 @@ window.html10n = (function(window, document, undefined) {
* MicroEvent - to make any js object an event emitter (server or browser)
*/
var MicroEvent = function(){}
MicroEvent.prototype = {
bind : function(event, fct){
var MicroEvent = function(){}
MicroEvent.prototype = {
bind: function(event, fct){
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
unbind : function(event, fct){
unbind: function(event, fct){
this._events = this._events || {};
if( event in this._events === false ) return;
if( event in this._events === false ) return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
},
trigger : function(event /* , args... */){
trigger: function(event /* , args... */){
this._events = this._events || {};
if( event in this._events === false ) return;
if( event in this._events === false ) return;
for(var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1))
}
@ -121,8 +121,8 @@ window.html10n = (function(window, document, undefined) {
* mixin will delegate all MicroEvent.js function in the destination object
* @param {Object} the object which will support MicroEvent
*/
MicroEvent.mixin = function(destObject){
var props = ['bind', 'unbind', 'trigger'];
MicroEvent.mixin = function(destObject){
var props = ['bind', 'unbind', 'trigger'];
if(!destObject) return;
for(var i = 0; i < props.length; i ++){
destObject[props[i]] = MicroEvent.prototype[props[i]];
@ -191,9 +191,18 @@ window.html10n = (function(window, document, undefined) {
return
}
// dat alng ain't here, man!
if (!data[lang]) {
cb(new Error('Couldn\'t find translations for '+lang))
return
var msg = 'Couldn\'t find translations for '+lang
, l
if(~lang.indexOf('-')) lang = lang.split('-')[0] // then let's try related langs
for(l in data) {
if(lang != l && l.indexOf(lang) === 0 && data[l]) {
lang = l
break;
}
}
if(lang != l) return cb(new Error(msg))
}
if ('string' == typeof data[lang]) {
@ -898,11 +907,22 @@ window.html10n = (function(window, document, undefined) {
var lang
langs.reverse()
// loop through priority array...
// loop through the priority array...
for (var i=0, n=langs.length; i < n; i++) {
lang = langs[i]
if(!lang || !(lang in that.loader.langs)) continue;
if(!lang) continue;
if(!(lang in that.loader.langs)) {// uh, we don't have this lang availbable..
// then check for related langs
if(~lang.indexOf('-')) lang = lang.split('-')[0];
for(var l in that.loader.langs) {
if(lang != l && l.indexOf(lang) === 0) {
lang = l
break;
}
}
if(lang != l) continue;
}
// ... and apply all strings of the current lang in the list
// to our build object

View file

@ -146,7 +146,7 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
return function(txt, cls)
{
var disableAuthColorForThisLine = hooks.callAll("disableAuthorColorsForThisLine", {
linestylefilter: linestylefilter,
text: txt,
@ -259,7 +259,7 @@ linestylefilter.getRegexpFilter = function(regExp, tag)
linestylefilter.REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
linestylefilter.REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#!;()$]/.source + '|' + linestylefilter.REGEX_WORDCHAR.source + ')');
linestylefilter.REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#!;()\[\]$]/.source + '|' + linestylefilter.REGEX_WORDCHAR.source + ')');
linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|mailto:|www\.)/.source + linestylefilter.REGEX_URLCHAR.source + '*(?![:.,;])' + linestylefilter.REGEX_URLCHAR.source, 'g');
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(
linestylefilter.REGEX_URL, 'url');

View file

@ -21,7 +21,6 @@
*/
var padmodals = require('./pad_modals').padmodals;
var padeditbar = require('./pad_editbar').padeditbar;
var padconnectionstatus = (function()
{

View file

@ -140,7 +140,7 @@ var padeditbar = (function()
}
else if (cmd == 'import_export')
{
self.toggleDropDown("importexport");
self.toggleDropDown("importexport");
}
else if (cmd == 'savedRevision')
{

View file

@ -77,6 +77,7 @@ var padimpexp = (function()
}
currentImportTimer = null;
importFailed("Request timed out.");
importDone();
}, 25000); // time out after some number of seconds
$('#importsubmitinput').attr(
{

View file

@ -20,7 +20,6 @@
* limitations under the License.
*/
var padutils = require('./pad_utils').padutils;
var padeditbar = require('./pad_editbar').padeditbar;
var padmodals = (function()
@ -39,10 +38,10 @@ var padmodals = (function()
padeditbar.toggleDropDown("connectivity");
});
},
showOverlay: function(duration) {
showOverlay: function() {
$("#overlay").show();
},
hideOverlay: function(duration) {
hideOverlay: function() {
$("#overlay").hide();
}
};

View file

@ -61,7 +61,7 @@ exports.flatten = function (lst) {
if (lst[i] != undefined && lst[i] != null) {
for (var j = 0; j < lst[i].length; j++) {
res.push(lst[i][j]);
}
}
}
}
}

View file

@ -28,7 +28,6 @@ JSON = require('./json2');
var createCookie = require('./pad_utils').createCookie;
var readCookie = require('./pad_utils').readCookie;
var randomString = require('./pad_utils').randomString;
var _ = require('./underscore');
var hooks = require('./pluginfw/hooks');
var token, padId, export_links;

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;