(function() {
'use strict';
if (CKEDITOR.plugins.get('ae_selectionregion')) {
return;
}
CKEDITOR.SELECTION_TOP_TO_BOTTOM = 0;
CKEDITOR.SELECTION_BOTTOM_TO_TOP = 1;
CKEDITOR.SELECTION_LEFT_TO_RIGHT = 2;
CKEDITOR.SELECTION_RIGHT_TO_LEFT = 3;
/**
* SelectionRegion utility class which provides metadata about the selection. The metadata may be the start and end
* rectangles, caret region, etc. **This class is not intended to be used standalone. Its functions will
* be merged into each editor instance, so the developer may use them directly via the editor, without making
* an instance of this class**.
*
* @class CKEDITOR.plugins.ae_selectionregion
* @constructor
*/
function SelectionRegion() {}
SelectionRegion.prototype = {
constructor: SelectionRegion,
/**
* Creates selection from two points in page coordinates.
*
* @method createSelectionFromPoint
* @param {Number} x X point in page coordinates.
* @param {Number} y Y point in page coordinates.
*/
createSelectionFromPoint: function(x, y) {
this.createSelectionFromRange(x, y, x, y);
},
/**
* Creates selection from range. A range consists from two points in page coordinates.
*
* @method createSelectionFromRange
* @param {Number} startX X coordinate of the first point.
* @param {Number} startY Y coordinate of the first point.
* @param {Number} endX X coordinate of the second point.
* @param {Number} endY Y coordinate of the second point.
*/
createSelectionFromRange: function(startX, startY, endX, endY) {
var end;
var endContainer;
var endOffset;
var range;
var start;
var startContainer;
var startOffset;
if (typeof document.caretPositionFromPoint === 'function') {
start = document.caretPositionFromPoint(startX, startY);
end = document.caretPositionFromPoint(endX, endY);
startContainer = start.offsetNode;
endContainer = end.offsetNode;
startOffset = start.offset;
endOffset = end.offset;
range = this.createRange();
} else if (typeof document.caretRangeFromPoint === 'function') {
start = document.caretRangeFromPoint(startX, startY);
end = document.caretRangeFromPoint(endX, endY);
startContainer = start.startContainer;
endContainer = end.startContainer;
startOffset = start.startOffset;
endOffset = end.startOffset;
range = this.createRange();
}
if (range && document.getSelection) {
range.setStart(new CKEDITOR.dom.node(startContainer), startOffset);
range.setEnd(new CKEDITOR.dom.node(endContainer), endOffset);
this.getSelection().selectRanges([range]);
} else if (typeof document.body.createTextRange === 'function') {
var selection = this.getSelection();
selection.unlock();
range = document.body.createTextRange();
range.moveToPoint(startX, startY);
var endRange = range.duplicate();
endRange.moveToPoint(endX, endY);
range.setEndPoint('EndToEnd', endRange);
range.select();
this.getSelection().lock();
}
},
/**
* Returns the region of the current position of the caret. The points are in page coordinates.
*
* @method getCaretRegion
* @return {Object} Returns object with the following properties:
* - bottom
* - left
* - right
* - top
*/
getCaretRegion: function() {
var selection = this.getSelection();
var region = {
bottom: 0,
left: 0,
right: 0,
top: 0
};
var bookmarks = selection.createBookmarks();
if (!bookmarks.length) {
return region;
}
var bookmarkNodeEl = bookmarks[0].startNode.$;
bookmarkNodeEl.style.display = 'inline-block';
region = new CKEDITOR.dom.element(bookmarkNodeEl).getClientRect();
bookmarkNodeEl.parentNode.removeChild(bookmarkNodeEl);
var scrollPos = new CKEDITOR.dom.window(window).getScrollPosition();
region.bottom = scrollPos.y + region.bottom;
region.left = scrollPos.x + region.left;
region.right = scrollPos.x + region.right;
region.top = scrollPos.y + region.top;
return region;
},
/**
* Returns data for the current selection.
*
* @method getSelectionData
* @return {Object|null} Returns an object with the following data:
* - element - The currently selected element, if any
* - text - The selected text
* - region - The data, returned from {{#crossLink "CKEDITOR.plugins.ae_selectionregion/getSelectionRegion:method"}}{{/crossLink}}
*/
getSelectionData: function() {
var selection = this.getSelection();
if (!selection.getNative()) {
return null;
}
var result = {
element: selection.getSelectedElement(),
text: selection.getSelectedText()
};
result.region = this.getSelectionRegion(selection);
return result;
},
/**
* Returns the region of the current selection.
*
* @method getSelectionRegion
* @return {Object} Returns object which is being returned from
* {{#crossLink "CKEDITOR.plugins.ae_selectionregion/getClientRectsRegion:method"}}{{/crossLink}} with three more properties:
* - direction - the direction of the selection. Can be one of these:
* 1. CKEDITOR.SELECTION_TOP_TO_BOTTOM
* 2. CKEDITOR.SELECTION_BOTTOM_TO_TOP
* - height - The height of the selection region
* - width - The width of the selection region
*/
getSelectionRegion: function() {
var region = this.getClientRectsRegion();
region.direction = this.getSelectionDirection();
region.height = region.bottom - region.top;
region.width = region.right - region.left;
return region;
},
/**
* Returns true if the current selection is empty, false otherwise.
*
* @method isSelectionEmpty
* @return {Boolean} Returns true if the current selection is empty, false otherwise.
*/
isSelectionEmpty: function() {
var ranges;
var selection = this.getSelection();
return (selection.getType() === CKEDITOR.SELECTION_NONE) ||
((ranges = selection.getRanges()) && ranges.length === 1 && ranges[0].collapsed);
},
/**
* Returns object with data about the [client rectangles](https://developer.mozilla.org/en-US/docs/Web/API/Element.getClientRects) of the selection,
* normalized across browses. All offsets below are in page coordinates.
*
* @method getClientRectsRegion
* @return {Object} Returns object with the following data:
* - bottom - bottom offset of all client rectangles
* - left - left offset of all client rectangles
* - right - right offset of all client rectangles
* - top - top offset of all client rectangles
* - startRect - An Object, which contains the following information:
* + bottom - bottom offset
* + height - the height of the rectangle
* + left - left offset of the selection
* + right - right offset of the selection
* + top - top offset of the selection
* + width - the width of the rectangle
* - endRect - An Object, which contains the following information:
* + bottom - bottom offset
* + height - the height of the rectangle
* + left - left offset of the selection
* + right - right offset of the selection
* + top - top offset of the selection
* + width - the width of the rectangle
*
* If there is no native selection, the objects will be filled with 0.
*/
getClientRectsRegion: function() {
var selection = this.getSelection();
var nativeSelection = selection.getNative();
var defaultRect = {
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0
};
var region = {
bottom: 0,
endRect: defaultRect,
left: 0,
right: 0,
top: 0,
startRect: defaultRect
};
if (!nativeSelection) {
return region;
}
var bottom = 0;
var clientRects;
var left = Infinity;
var rangeCount;
var right = -Infinity;
var top = Infinity;
if (nativeSelection.createRange) {
clientRects = nativeSelection.createRange().getClientRects();
} else {
rangeCount = nativeSelection.rangeCount;
clientRects = (nativeSelection.rangeCount > 0) ? nativeSelection.getRangeAt(0).getClientRects() : [];
}
if (clientRects.length === 0) {
region = this.getCaretRegion();
} else {
for (var i = 0, length = clientRects.length; i < length; i++) {
var item = clientRects[i];
if (item.left < left) {
left = item.left;
}
if (item.right > right) {
right = item.right;
}
if (item.top < top) {
top = item.top;
}
if (item.bottom > bottom) {
bottom = item.bottom;
}
}
var scrollPos = new CKEDITOR.dom.window(window).getScrollPosition();
region.bottom = scrollPos.y + bottom;
region.left = scrollPos.x + left;
region.right = scrollPos.x + right;
region.top = scrollPos.y + top;
if (clientRects.length) {
var endRect = clientRects[clientRects.length - 1];
var startRect = clientRects[0];
region.endRect = {
bottom: scrollPos.y + endRect.bottom,
height: endRect.height,
left: scrollPos.x + endRect.left,
right: scrollPos.x + endRect.right,
top: scrollPos.y + endRect.top,
width: endRect.width
};
region.startRect = {
bottom: scrollPos.y + startRect.bottom,
height: startRect.height,
left: scrollPos.x + startRect.left,
right: scrollPos.x + startRect.right,
top: scrollPos.y + startRect.top,
width: startRect.width
};
}
}
return region;
},
/**
* Retrieves the direction of the selection. The direction is from top to bottom or from bottom to top.
* For IE < 9 it is not possible, so the direction for these browsers will be always CKEDITOR.SELECTION_TOP_TO_BOTTOM.
*
* @method getSelectionDirection
* @return {Number} Returns a number which represents selection direction. It might be one of these:
* - CKEDITOR.SELECTION_TOP_TO_BOTTOM;
* - CKEDITOR.SELECTION_BOTTOM_TO_TOP;
*/
getSelectionDirection: function() {
var direction = CKEDITOR.SELECTION_TOP_TO_BOTTOM;
var selection = this.getSelection();
var nativeSelection = selection.getNative();
if (!nativeSelection) {
return direction;
}
var anchorNode;
if ((anchorNode = nativeSelection.anchorNode) && anchorNode.compareDocumentPosition) {
var position = anchorNode.compareDocumentPosition(nativeSelection.focusNode);
if (!position && nativeSelection.anchorOffset > nativeSelection.focusOffset || position === Node.DOCUMENT_POSITION_PRECEDING) {
direction = CKEDITOR.SELECTION_BOTTOM_TO_TOP;
}
}
return direction;
}
};
CKEDITOR.plugins.add(
'ae_selectionregion', {
/**
* Initializer lifecycle implementation for the SelectionRegion plugin.
*
* @method init
* @protected
* @param {Object} editor The current CKEditor instance.
*/
init: function(editor) {
var attr,
hasOwnProperty;
hasOwnProperty = Object.prototype.hasOwnProperty;
for (attr in SelectionRegion.prototype) {
if (hasOwnProperty.call(SelectionRegion.prototype, attr) && typeof editor[attr] === 'undefined') {
editor[attr] = SelectionRegion.prototype[attr];
}
}
}
}
);
}());