diff --git a/www/common/cursor.js b/www/common/cursor.js index 1bb9224c3..81ae2bba2 100644 --- a/www/common/cursor.js +++ b/www/common/cursor.js @@ -127,12 +127,90 @@ define([ }; } } else { - error("[seekToDelta] el is undefined"); + error("[cursor.seekToDelta] el is undefined"); } return result; }; - /* cursor.update takes notes about wherever the cursor was last seen + var checkFromEnd = cursor.checkFromEnd = function (root, predicate) { + var last = Tree.rightmostNode(root); + var success = false; + var i = 0; + while (last && !success) { + success = predicate(last, i++); + last = Tree.previousNode(last, root); + } + return success; + }; + + /* uses closure to capture the root node because I'm lazy + + */ + var getTrailingNodes = cursor.getTrailingNodes = function (el) { + var trailing = []; + var success = cursor.checkFromEnd(inner, function (cur) { + if (cur === el) { + trailing.push(cur); + return true; + } else { trailing.push(cur); } + }); + return success && trailing; + }; + + var recoverNodeByTrailing = cursor.recoverNodeByTrailing = function (trailing) { + // clone the array + var T = trailing.slice(0); + var L = T.length; + var el = null; + cursor.checkFromEnd(inner, function (cur, i) { + if (i >= L) { + //console.log("[cursor.recoverNodeByTrailing] out of bounds"); + return true; + } else { + if (cur.nodeName !== T[i].nodeName) { + console.log("[cursor.recoverNodeByTrailing] false name"); + console.log(cur); + return true; + } else if (cur.nodeName === T[i].nodeName && i === L - 1) { + el = cur; + return true; + } else { + return false; + } + } + }); + return el; + }; + + /* cursor.find uses information produced by side effects of 'update' + to recover the cursor + */ + cursor.find = function () { + ['start', 'end'].forEach(function (pos) { + // is this metric even reliable? + var node; + var trailing = Range[pos].trailing; + if (Range[pos].lost) { + if (trailing.length) { + // use the trailing nodes to find the node.. + node = recoverNodeByTrailing(trailing); + } + if (node) { + // if that worked....great! + Range[pos].el = node; + + } else { + // if not, try falling back to the nearest parent? + + } + } else { + // it wasn't lost, you don't need to do anything. + } + + }); + }; + + /* cursor.update takes notes about wherever the cursor was last seen in the event of a cursor loss, the information produced by side effects of this function should be used to recover the cursor @@ -142,31 +220,97 @@ define([ verbose("cursor.update"); root = root || inner; sel = sel || Rangy.getSelection(root); - //if (!sel.rangeCount) { return 'no ranges found'; } + // FIXME under what circumstances are no ranges found? + if (!sel.rangeCount) { + error('[cursor.update] no ranges found'); + //return 'no ranges found'; + } var range = sel.getRangeAt(0); // Big R Range is caught in closure, and maintains persistent state - Range.start.el = range.startContainer; - Range.start.offset = range.startOffset; - Range.start.parents = Tree.parentsOf(Range.start.el, root); - Range.end.el = range.endContainer; - Range.end.offset = range.endOffset; - Range.end.parents = Tree.parentsOf(Range.end.el, root); + ['start', 'end'].forEach(function (pos) { + Range[pos].el = range[pos+'Container']; + Range[pos].offset = range[pos+'Offset']; + Range[pos].parents = Tree.parentsOf(Range[pos].el, root); + + // trailing is either an array or false + var trailing = getTrailingNodes(Range[pos].el); + + // if it's false, then the cursor has been lost + // TODO do a more careful check to see if the cursor has been destroyed. + Range[pos].lost = !trailing; + + if (Range[pos].lost) { + // don't overwrite the previous trailing nodes + // that array will be used to recover the cursor + + } else { + // if you haven't lost the cursor, update the node list + Range[pos].trailing = trailing; + } + }); }; - /* cursor.find uses information produced by side effects of 'update' - to recover the cursor - */ - cursor.find = function () { }; + /* 0 -> neither is lost + 1 -> start is lost + 2 -> end is lost + 3 -> both are lost */ + var isLost = cursor.isLost = function () { + var state = ['start', 'end'].map(function (pos, i) { + return Tree.contains(Range[pos].el, inner)? 0 : i + 1; + }); + return state[0] | state[1]; + }; - /* + var recover = cursor.recover = function (lost) { + var sel = Rangy.getSelection(inner); - */ - cursor.recover = function () { }; + // create a range to modify + var range = Rangy.createRange(); + + /* if range is backwards, cursor.delta fails + so check if they're in the expected order + before setting the new range */ + + if (lost & 1) { + Range.start.el = recoverNodeByTrailing(Range.start.trailing); + } + if (lost & 2) { + Range.end.el = recoverNodeByTrailing(Range.end.trailing); + } + + // el.parentNode is null + var order = Tree.orderOfNodes(Range.start.el, Range.end.el, inner); + var backward; + + // this could all be one line but nobody would be able to read it + if (order === -1) { + // definitely backward + backward = true; + } else if (order === 0) { + // might be backward, check offsets to know for sure + backward = (Range.start.offset > Range.end.offset); + } else { + // definitely not backward + backward = false; + } - cursor.delta = function (delta, collapse) { + if (backward) { + range.setStart(Range.end.el, Range.end.offset); + range.setEnd(Range.start.el, Range.start.offset); + } else { + range.setStart(Range.start.el, Range.start.offset); + range.setEnd(Range.end.el, Range.end.offset); + } + + // actually set the cursor to the new range + sel.setSingleRange(range); + }; + + cursor.delta = function (delta1, delta2) { var sel = Rangy.getSelection(inner); + delta2 = (typeof delta2 !== 'undefined') ? delta2 : delta1; // update returns errors if there are problems // and updates the persistent Range object @@ -184,13 +328,29 @@ define([ // using infromation about wherever you were last... // move both parts by some delta - var start = seekToDelta(Range.start.el, delta, Range.start.offset); - var end = seekToDelta(Range.end.el, delta, Range.end.offset); + var start = seekToDelta(Range.start.el, delta1, Range.start.offset); + var end = seekToDelta(Range.end.el, delta2, Range.end.offset); /* if range is backwards, cursor.delta fails so check if they're in the expected order before setting the new range */ - if (Tree.orderOfNodes(start.el, end.el, inner) === -1) { + + var order = Tree.orderOfNodes(start.el, end.el, inner); + var backward; + + // this could all be one line but nobody would be able to read it + if (order === -1) { + // definitely backward + backward = true; + } else if (order === 0) { + // might be backward, check offsets to know for sure + backward = (start.offset > end.offset); + } else { + // definitely not backward + backward = false; + } + + if (backward) { range.setStart(end.el, end.offset); range.setEnd(start.el, start.offset); } else { @@ -200,12 +360,10 @@ define([ // actually set the cursor to the new range sel.setSingleRange(range); - if (delta < 0) { - // seeking left, so start might have an error - return start.error; - } else { - return end.error; - } + return { + startError: start.error, + endError: end.error + }; }; return cursor;