231 lines
7.4 KiB
JavaScript
231 lines
7.4 KiB
JavaScript
var Leaf, Normalizer, Range, Selection, dom, _;
|
|
|
|
_ = require('lodash');
|
|
|
|
dom = require('../lib/dom');
|
|
|
|
Leaf = require('./leaf');
|
|
|
|
Normalizer = require('../lib/normalizer');
|
|
|
|
Range = require('../lib/range');
|
|
|
|
Selection = (function() {
|
|
function Selection(doc, iframe, emitter) {
|
|
this.doc = doc;
|
|
this.iframe = iframe;
|
|
this.emitter = emitter;
|
|
this.document = this.doc.root.ownerDocument;
|
|
this.focus = false;
|
|
this.range = new Range(0, 0);
|
|
this.nullDelay = false;
|
|
this.update('silent');
|
|
}
|
|
|
|
Selection.prototype.checkFocus = function() {
|
|
return this.document.activeElement === this.doc.root && document.activeElement === this.iframe;
|
|
};
|
|
|
|
Selection.prototype.getRange = function(ignoreFocus) {
|
|
var end, nativeRange, start;
|
|
if (ignoreFocus == null) {
|
|
ignoreFocus = false;
|
|
}
|
|
if (this.checkFocus()) {
|
|
nativeRange = this._getNativeRange();
|
|
if (nativeRange == null) {
|
|
return null;
|
|
}
|
|
start = this._positionToIndex(nativeRange.startContainer, nativeRange.startOffset);
|
|
if (nativeRange.startContainer === nativeRange.endContainer && nativeRange.startOffset === nativeRange.endOffset) {
|
|
end = start;
|
|
} else {
|
|
end = this._positionToIndex(nativeRange.endContainer, nativeRange.endOffset);
|
|
}
|
|
return new Range(Math.min(start, end), Math.max(start, end));
|
|
} else if (ignoreFocus) {
|
|
return this.range;
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
Selection.prototype.preserve = function(fn) {
|
|
var endNode, endOffset, nativeRange, startNode, startOffset, _ref, _ref1, _ref2, _ref3;
|
|
nativeRange = this._getNativeRange();
|
|
if ((nativeRange != null) && this.checkFocus()) {
|
|
_ref = this._encodePosition(nativeRange.startContainer, nativeRange.startOffset), startNode = _ref[0], startOffset = _ref[1];
|
|
_ref1 = this._encodePosition(nativeRange.endContainer, nativeRange.endOffset), endNode = _ref1[0], endOffset = _ref1[1];
|
|
fn();
|
|
_ref2 = this._decodePosition(startNode, startOffset), startNode = _ref2[0], startOffset = _ref2[1];
|
|
_ref3 = this._decodePosition(endNode, endOffset), endNode = _ref3[0], endOffset = _ref3[1];
|
|
return this._setNativeRange(startNode, startOffset, endNode, endOffset);
|
|
} else {
|
|
return fn();
|
|
}
|
|
};
|
|
|
|
Selection.prototype.setRange = function(range, source) {
|
|
var endNode, endOffset, startNode, startOffset, _ref, _ref1, _ref2;
|
|
if (range != null) {
|
|
_ref = this._indexToPosition(range.start), startNode = _ref[0], startOffset = _ref[1];
|
|
if (range.isCollapsed()) {
|
|
_ref1 = [startNode, startOffset], endNode = _ref1[0], endOffset = _ref1[1];
|
|
} else {
|
|
_ref2 = this._indexToPosition(range.end), endNode = _ref2[0], endOffset = _ref2[1];
|
|
}
|
|
this._setNativeRange(startNode, startOffset, endNode, endOffset);
|
|
} else {
|
|
this._setNativeRange(null);
|
|
}
|
|
return this.update(source);
|
|
};
|
|
|
|
Selection.prototype.shiftAfter = function(index, length, fn) {
|
|
var range;
|
|
range = this.getRange();
|
|
fn();
|
|
if (range != null) {
|
|
range.shift(index, length);
|
|
return this.setRange(range, 'silent');
|
|
}
|
|
};
|
|
|
|
Selection.prototype.update = function(source) {
|
|
var emit, focus, range, toEmit;
|
|
focus = this.checkFocus();
|
|
range = this.getRange(true);
|
|
emit = source !== 'silent' && (!Range.compare(range, this.range) || focus !== this.focus);
|
|
toEmit = focus ? range : null;
|
|
if (toEmit === null && source === 'user' && !this.nullDelay) {
|
|
return this.nullDelay = true;
|
|
} else {
|
|
this.nullDelay = false;
|
|
this.range = range;
|
|
this.focus = focus;
|
|
if (emit) {
|
|
return this.emitter.emit(this.emitter.constructor.events.SELECTION_CHANGE, toEmit, source);
|
|
}
|
|
}
|
|
};
|
|
|
|
Selection.prototype._decodePosition = function(node, offset) {
|
|
var childIndex;
|
|
if (dom(node).isElement()) {
|
|
childIndex = _.indexOf(dom(node.parentNode).childNodes(), node);
|
|
offset += childIndex;
|
|
node = node.parentNode;
|
|
}
|
|
return [node, offset];
|
|
};
|
|
|
|
Selection.prototype._encodePosition = function(node, offset) {
|
|
var text;
|
|
while (true) {
|
|
if (dom(node).isTextNode() || node.tagName === dom.DEFAULT_BREAK_TAG || (dom.EMBED_TAGS[node.tagName] != null)) {
|
|
return [node, offset];
|
|
} else if (offset < node.childNodes.length) {
|
|
node = node.childNodes[offset];
|
|
offset = 0;
|
|
} else if (node.childNodes.length === 0) {
|
|
if (Normalizer.TAGS[node.tagName] == null) {
|
|
text = node.ownerDocument.createTextNode('');
|
|
node.appendChild(text);
|
|
node = text;
|
|
}
|
|
return [node, 0];
|
|
} else {
|
|
node = node.lastChild;
|
|
if (dom(node).isElement()) {
|
|
if (node.tagName === dom.DEFAULT_BREAK_TAG || (dom.EMBED_TAGS[node.tagName] != null)) {
|
|
return [node, 1];
|
|
} else {
|
|
offset = node.childNodes.length;
|
|
}
|
|
} else {
|
|
return [node, dom(node).length()];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
Selection.prototype._getNativeRange = function() {
|
|
var range, selection;
|
|
selection = this.document.getSelection();
|
|
if ((selection != null ? selection.rangeCount : void 0) > 0) {
|
|
range = selection.getRangeAt(0);
|
|
if (dom(range.startContainer).isAncestor(this.doc.root, true)) {
|
|
if (range.startContainer === range.endContainer || dom(range.endContainer).isAncestor(this.doc.root, true)) {
|
|
return range;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
Selection.prototype._indexToPosition = function(index) {
|
|
var leaf, offset, _ref;
|
|
if (this.doc.lines.length === 0) {
|
|
return [this.doc.root, 0];
|
|
}
|
|
_ref = this.doc.findLeafAt(index, true), leaf = _ref[0], offset = _ref[1];
|
|
return this._decodePosition(leaf.node, offset);
|
|
};
|
|
|
|
Selection.prototype._positionToIndex = function(node, offset) {
|
|
var leaf, leafNode, leafOffset, line, lineOffset, _ref;
|
|
_ref = this._encodePosition(node, offset), leafNode = _ref[0], offset = _ref[1];
|
|
line = this.doc.findLine(leafNode);
|
|
if (line == null) {
|
|
return 0;
|
|
}
|
|
leaf = line.findLeaf(leafNode);
|
|
lineOffset = 0;
|
|
while (line.prev != null) {
|
|
line = line.prev;
|
|
lineOffset += line.length;
|
|
}
|
|
if (leaf == null) {
|
|
return lineOffset;
|
|
}
|
|
leafOffset = 0;
|
|
while (leaf.prev != null) {
|
|
leaf = leaf.prev;
|
|
leafOffset += leaf.length;
|
|
}
|
|
return lineOffset + leafOffset + offset;
|
|
};
|
|
|
|
Selection.prototype._setNativeRange = function(startNode, startOffset, endNode, endOffset) {
|
|
var nativeRange, selection;
|
|
selection = this.document.getSelection();
|
|
if (!selection) {
|
|
return;
|
|
}
|
|
if (startNode != null) {
|
|
if (!this.checkFocus()) {
|
|
this.doc.root.focus();
|
|
}
|
|
nativeRange = this._getNativeRange();
|
|
if ((nativeRange == null) || startNode !== nativeRange.startContainer || startOffset !== nativeRange.startOffset || endNode !== nativeRange.endContainer || endOffset !== nativeRange.endOffset) {
|
|
selection.removeAllRanges();
|
|
nativeRange = this.document.createRange();
|
|
nativeRange.setStart(startNode, startOffset);
|
|
nativeRange.setEnd(endNode, endOffset);
|
|
selection.addRange(nativeRange);
|
|
if (!this.checkFocus()) {
|
|
return this.doc.root.focus();
|
|
}
|
|
}
|
|
} else {
|
|
selection.removeAllRanges();
|
|
return this.doc.root.blur();
|
|
}
|
|
};
|
|
|
|
return Selection;
|
|
|
|
})();
|
|
|
|
module.exports = Selection;
|