/*
Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/
(function () {
/**
* Represent plain text selection range.
*/
CKEDITOR.plugins.add('textselection',
{
init: function (editor) {
var sourceBookmark, wysiwygBookmark,
textRange; // Corresponding text range of wysiwyg bookmark.
// Auto sync text selection with 'WYSIWYG' mode selection range.
if (editor.config.syncSelection
&& CKEDITOR.plugins.sourcearea) {
editor.on('beforeModeUnload', function (evt) {
if (editor.mode == 'source') {
var range = editor.getTextSelection();
// Fly the range when create bookmark.
delete range.element;
range.createBookmark();
sourceBookmark = true;
evt.data = range.content;
}
});
editor.on('mode', function (evt) {
if (editor.mode == 'wysiwyg' && sourceBookmark) {
editor.focus();
var doc = editor.document,
range = new CKEDITOR.dom.range(editor.document),
walker,
startNode,
endNode;
range.setStartAt(doc.getBody(), CKEDITOR.POSITION_AFTER_START);
range.setEndAt(doc.getBody(), CKEDITOR.POSITION_BEFORE_END);
walker = new CKEDITOR.dom.walker(range);
walker.type = CKEDITOR.NODE_COMMENT;
walker.evaluator = function (comment) {
var match = /cke_bookmark_\d+(\w)/.exec(comment.$.nodeValue);
if (match) {
if (match[1] == 'S')
startNode = comment;
else (match[1] == 'E')
{
endNode = comment;
return false;
}
}
};
walker.lastForward();
range.setStartAfter(startNode);
range.setEndBefore(endNode);
range.select();
// Scroll into view for non-IE.
if (!CKEDITOR.env.ie || (CKEDITOR.env.ie && CKEDITOR.env.version == 9))
editor.getSelection().getStartElement().scrollIntoView(true);
// Remove the comments node which are out of range.
startNode.remove();
endNode.remove();
}
}, null, null, 10);
editor.on('beforeGetModeData', function (evt) {
if (editor.mode == 'wysiwyg') {
var sel = editor.getSelection(), range;
if (sel && (range = sel.getRanges()[0]))
wysiwygBookmark = range.createBookmark(true);
}
});
// Build text range right after wysiwyg has unloaded.
editor.on('afterModeUnload', function (evt) {
if (editor.mode == 'wysiwyg' && wysiwygBookmark) {
textRange = new CKEDITOR.dom.textRange(evt.data);
textRange.moveToBookmark(wysiwygBookmark);
evt.data = textRange.content;
}
});
editor.on('mode', function (evt) {
if (editor.mode == 'source' && textRange) {
textRange.element = new CKEDITOR.dom.element(editor._.editable.$);
textRange.select();
}
});
}
}
});
/**
* Gets the current text selection from the editing area when in Source mode.
* @returns {CKEDITOR.dom.textRange} Text range represent the caret positoins.
* @example
* var textSelection = CKEDITOR.instances.editor1.getTextSelection();
* alert( textSelection.startOffset );
* alert( textSelection.endOffset );
*/
CKEDITOR.editor.prototype.getTextSelection = function () {
return this._.editable && getTextSelection(this._.editable.$) || null;
};
/**
* Returns the caret position of the specified textfield/textarea.
* @param {HTMLTextArea|HTMLTextInput} element
*/
function getTextSelection(element) {
var startOffset, endOffset;
if (!CKEDITOR.env.ie) {
startOffset = element.selectionStart;
endOffset = element.selectionEnd;
}
else {
element.focus();
// The current selection
var range = document.selection.createRange(),
textLength = range.text.length;
// Create a 'measuring' range to help calculate the start offset by
// stretching it from start to current position.
var measureRange = range.duplicate();
measureRange.moveToElementText(element);
measureRange.setEndPoint('EndToEnd', range);
endOffset = measureRange.text.length;
startOffset = endOffset - textLength;
}
return new CKEDITOR.dom.textRange(
new CKEDITOR.dom.element(element), startOffset, endOffset);
}
/**
* Represent the selection range within a html textfield/textarea element,
* or even a flyweight string content represent the text content.
* @constructor
* @param {CKEDITOR.dom.element|String} element
* @param {Number} start
* @param {Number} end
*/
CKEDITOR.dom.textRange = function (element, start, end) {
if (element instanceof CKEDITOR.dom.element
&& (element.is('textarea')
|| element.is('input') && element.getAttribute('type') == 'text')) {
this.element = element;
this.content = element.$.value;
} else if (typeof element == 'string')
this.content = element;
else
throw 'Unkown "element" type.';
this.startOffset = start || 0;
this.endOffset = end || 0;
};
CKEDITOR.dom.textRange.prototype =
{
/**
* Sets the text selection of the specified textfield/textarea.
* @param {HTMLTextArea|HTMLTextInput} element
* @param {CKEDITOR.dom.textRange} range
*/
select: function () {
var startOffset = this.startOffset,
endOffset = this.endOffset,
element = this.element.$;
if (endOffset == undefined) {
endOffset = startOffset;
}
if (CKEDITOR.env.ie && CKEDITOR.env.version == 9) {
element.focus();
element.selectionStart = startOffset;
element.selectionEnd = startOffset;
setTimeout(function() {
element.selectionStart = startOffset;
element.selectionEnd = endOffset;
}, 20);
}
else {
if (element.setSelectionRange) {
if (CKEDITOR.env.ie) {
element.focus();
}
element.setSelectionRange(startOffset, endOffset);
if (!CKEDITOR.env.ie) {
element.focus();
}
}
else if (element.createTextRange) {
element.focus();
var range = element.createTextRange();
range.collapse(true);
range.moveStart('character', startOffset);
range.moveEnd('character', endOffset - startOffset);
range.select();
}
}
},
/**
* Select the range included within the bookmark text with the bookmark
* text removed.
* @param {Object} bookmark Exactly the one created by CKEDITOR.dom.range.createBookmark( true ).
*/
moveToBookmark: function (bookmark) {
var content = this.content;
function removeBookmarkText(bookmarkId) {
var bookmarkRegex = new RegExp(''),
offset;
content = content.replace(bookmarkRegex, function (str, index) {
offset = index;
return '';
});
return offset;
}
this.startOffset = removeBookmarkText(bookmark.startNode);
this.endOffset = removeBookmarkText(bookmark.endNode);
this.content = content;
this.updateElement();
},
/**
* If startOffset/endOffset anchor inside element tag, start the range before/after the element
*/
enlarge: function () {
var htmlTagRegexp = /<[^>]+>/g;
var content = this.content,
start = this.startOffset,
end = this.endOffset,
match,
tagStartIndex,
tagEndIndex;
// Adjust offset position on parsing result.
while (match = htmlTagRegexp.exec(content)) {
tagStartIndex = match.index;
tagEndIndex = tagStartIndex + match[0].length;
if (start > tagStartIndex && start < tagEndIndex)
start = tagStartIndex;
if (end > tagStartIndex && end < tagEndIndex) {
end = tagEndIndex;
break;
}
}
this.startOffset = start;
this.endOffset = end;
},
createBookmark: function () {
// Enlarge the range to avoid tag partial selection.
this.enlarge();
var content = this.content,
start = this.startOffset,
end = this.endOffset,
id = CKEDITOR.tools.getNextNumber(),
bookmarkTemplate = '';
content = content.substring(0, start) + bookmarkTemplate.replace('%1', id + 'S')
+ content.substring(start, end) + bookmarkTemplate.replace('%1', id + 'E')
+ content.substring(end);
this.content = content;
this.updateElement();
},
updateElement: function () {
if (this.element)
this.element.$.value = this.content;
}
};
})();
// Seamless selection range across different modes.
CKEDITOR.config.syncSelection = true;