/*
* @license Copyright (c) CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.html or http://ckeditor.com/license
*/
/**
* Represent plain text selection range.
*/
CKEDITOR.plugins.add('textselection',
{
version: "1.08.0",
init: function (editor) {
if (editor.config.fullPage) {
return;
}
// Corresponding text range of WYSIWYG bookmark.
var wysiwygBookmark;
// 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') {
if (editor.mode === 'source' && !editor.plugins.codemirror) {
var range = editor.getTextSelection();
// Fly the range when create bookmark.
delete range.element;
range.createBookmark(editor);
sourceBookmark = true;
evt.data = range.content;
}
}
});
editor.on('mode', function () {
if (editor.mode === 'wysiwyg' && sourceBookmark) {
editor.focus();
var doc = editor.document,
range = new CKEDITOR.dom.range(editor.document),
startNode,
endNode,
isTextNode = false;
range.setStartAt(doc.getBody(), CKEDITOR.POSITION_AFTER_START);
range.setEndAt(doc.getBody(), CKEDITOR.POSITION_BEFORE_END);
var walker = new CKEDITOR.dom.walker(range);
// walker.type = CKEDITOR.NODE_COMMENT;
walker.evaluator = function (node) {
//
var match = /cke_bookmark_\d+(\w)/.exec(node.$.nodeValue);
if (match) {
if (unescape(node.$.nodeValue)
.match(/.*/)){
isTextNode = true;
startNode = endNode = node;
return false;
} else {
if (match[1] === 'S') {
startNode = node;
} else if (match[1] === 'E') {
endNode = node;
return false;
}
}
}
return false;
};
walker.lastForward();
try {
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.
if (isTextNode) {
//remove all of our bookmarks from the text node
//then remove all of the cke_protected bits that added because we had a comment
//whatever code is supposed to clean these cke_protected up doesn't work
//when there's two comments in a row like:
startNode.$.nodeValue = unescape(startNode.$.nodeValue).
replace(//g, '').
replace(//g, '');
} else {
//just remove the comment nodes
startNode.remove();
endNode.remove();
}
} catch (excec) {
}
}
}, null, null, 10);
editor.on('beforeGetModeData', function () {
if (editor.mode === 'wysiwyg' && editor.getData()) {
if (CKEDITOR.env.gecko && !editor.focusManager.hasFocus) {
return;
}
var sel = editor.getSelection(), range;
if (sel && (range = sel.getRanges()[0])) {
wysiwygBookmark = range.createBookmark(editor);
}
}
});
// 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, editor);
evt.data = textRange.content;
}
});
editor.on('mode', function () {
if (editor.mode === 'source' && textRange && !editor.plugins.codemirror) {
textRange.element = new CKEDITOR.dom.element(editor._.editable.$);
textRange.select();
}
});
editor.on('destroy', function () {
textRange = null;
sourceBookmark = null;
});
}
}
});
/**
* Gets the current text selection from the editing area when in Source mode.
* @returns {CKEDITOR.dom.textRange} Text range represent the caret positions.
* @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
if (window.getSelection) {
startOffset = element.selectionStart;
endOffset = element.selectionEnd;
} else {
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 'Unknown "element" type.';
this.startOffset = start || 0;
this.endOffset = end || 0;
};
/**
* Changes the editing mode of this editor instance. (Override of the original function)
*
* **Note:** The mode switch could be asynchronous depending on the mode provider.
* Use the `callback` to hook subsequent code.
*
* // Switch to "source" view.
* CKEDITOR.instances.editor1.setMode( 'source' );
* // Switch to "WYSIWYG" view and be notified on completion.
* CKEDITOR.instances.editor1.setMode( 'wysiwyg', function() { alert( 'WYSIWYG mode loaded!' ); } );
*
* @param {String} [newMode] If not specified, the {@link CKEDITOR.config#startupMode} will be used.
* @param {Function} [callback] Optional callback function which is invoked once the mode switch has succeeded.
*/
CKEDITOR.editor.prototype.setMode = function (newMode, callback) {
var editor = this;
var modes = this._.modes;
// Mode loading quickly fails.
if (newMode == editor.mode || !modes || !modes[newMode])
return;
editor.fire('beforeSetMode', newMode);
if (editor.mode) {
var isDirty = editor.checkDirty();
editor._.previousMode = editor.mode;
// Get cached data, which was set while detaching editable.
editor._.previousModeData = editor.getData();
editor.fire('beforeModeUnload');
editor.fire('beforeGetModeData');
var data = editor.getData();
data = editor.fire('beforeModeUnload', data);
data = editor.fire('afterModeUnload', data);
// Detach the current editable.
editor.editable(0);
editor._.data = data;
// Clear up the mode space.
editor.ui.space('contents').setHtml('');
editor.mode = '';
}
// Fire the mode handler.
this._.modes[newMode](function () {
// Set the current mode.
editor.mode = newMode;
if (isDirty !== undefined) {
!isDirty && editor.resetDirty();
}
// Delay to avoid race conditions (setMode inside setMode).
setTimeout(function () {
editor.fire('mode');
callback && callback.call(editor);
}, 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, editor) {
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);
var savedContent = editor._.previousModeData;
if (savedContent.length > 0) {
var diff = content.length - savedContent.length;
this.startOffset = this.startOffset - diff;
if (diff > 0) {
//this.endOffset = this.endOffset - diff;
}
content = editor._.previousModeData;
this.content = content;
this.updateElement();
if (editor.undoManager) {
editor.undoManager.unlock();
}
}
},
/**
* If startOffset/endOffset anchor inside element tag, start the range before/after the element
*/
enlarge: function() {
var htmlOpenTagRegexp = /<[a-zA-Z]+(>|.*?[^?]>)/g;
var htmlCloseTagRegexp = /<\/[^>]+>/g;
var content = this.content,
start = this.startOffset,
end = this.endOffset,
match,
tagStartIndex,
tagEndIndex;
while (match = htmlCloseTagRegexp.exec(content)) {
tagStartIndex = match.index;
tagEndIndex = tagStartIndex + match[0].length;
if (tagEndIndex < start) {
continue;
}
if (tagStartIndex <= start) {
start = tagStartIndex;
end = tagStartIndex;
break;
}
}
while (match = htmlOpenTagRegexp.exec(content)) {
tagStartIndex = match.index;
tagEndIndex = tagStartIndex + match[0].length;
if (tagEndIndex < start) {
continue;
}
if (tagStartIndex <= start) {
start = tagEndIndex;
end = tagEndIndex;
break;
}
}
this.startOffset = start;
this.endOffset = end;
},
createBookmark: function (editor) {
// 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);
if (editor.undoManager) {
editor.undoManager.lock();
}
this.content = content;
this.updateElement();
},
updateElement: function() {
if (this.element)
this.element.$.value = this.content;
}
};
var Browser = {
Version: function() {
var version = 999;
if (navigator.appVersion.indexOf("MSIE") != -1)
version = parseFloat(navigator.appVersion.split("MSIE")[1]);
return version;
}
};
// Seamless selection range across different modes.
CKEDITOR.config.syncSelection = true;
var textRange,sourceBookmark;