/* * @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;