diff --git a/wysiwyg/config.js b/wysiwyg/config.js
index 46affd19..18a2c909 100644
--- a/wysiwyg/config.js
+++ b/wysiwyg/config.js
@@ -45,7 +45,7 @@ CKEDITOR.editorConfig = function( config ) {
- config.extraPlugins= 'tableresize,uploadimage,image2,codemirror,hianamedia,sourcelocation';
+ config.extraPlugins= 'tableresize,uploadimage,image2,codemirror,hianamedia,textselection';
config.codemirror = {
showFormatButton: false,
showCommentButton: false,
diff --git a/wysiwyg/plugins/codemirror/plugin.js b/wysiwyg/plugins/codemirror/plugin.js
index 6b51a934..542f828b 100644
--- a/wysiwyg/plugins/codemirror/plugin.js
+++ b/wysiwyg/plugins/codemirror/plugin.js
@@ -249,7 +249,7 @@
var start, end;
start = OffSetToLineChannel(window["codemirror_" + editor.id], textRange.startOffset);
-
+ window["codemirror_" + editor.id].doc.setCursor(start.line + 12);
if (typeof (textRange.endOffset) == "undefined") {
window["codemirror_" + editor.id].focus();
window["codemirror_" + editor.id].setCursor(start);
@@ -1073,6 +1073,7 @@
var start, end;
start = OffSetToLineChannel(window["codemirror_" + editor.id], textRange.startOffset);
+ window["codemirror_" + editor.id].doc.setCursor(start.line + 12);
if (typeof (textRange.endOffset) == "undefined") {
window["codemirror_" + editor.id].focus();
diff --git a/wysiwyg/plugins/textselection/plugin.js b/wysiwyg/plugins/textselection/plugin.js
new file mode 100644
index 00000000..f0fc3641
--- /dev/null
+++ b/wysiwyg/plugins/textselection/plugin.js
@@ -0,0 +1,454 @@
+/*
+ * @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;