From c2214b5e7cb1c659ff3236e65ab8ef1c85bde4dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=B9=E8=AF=9A=E8=AF=9A?= Date: Thu, 24 Oct 2019 14:15:42 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E7=9A=84=E6=96=87?= =?UTF-8?q?=E5=AD=97=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wysiwyg/config.js | 2 +- wysiwyg/plugins/codemirror/plugin.js | 3 +- wysiwyg/plugins/textselection/plugin.js | 454 ++++++++++++++++++++++++ 3 files changed, 457 insertions(+), 2 deletions(-) create mode 100644 wysiwyg/plugins/textselection/plugin.js 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;