From a4beadedd32e540c60923b37aa5c7d5a715b66a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Reinmar=20Koszuli=C5=84ski?= Date: Mon, 18 Feb 2013 14:28:10 +0100 Subject: [PATCH 1/2] Filter should take enterMode into consideration when stripping elements. --- core/filter.js | 91 ++++++++++++++++++++++++++++++++--------- core/htmlparser/node.js | 19 +++++++++ 2 files changed, 90 insertions(+), 20 deletions(-) diff --git a/core/filter.js b/core/filter.js index a9e7285e324..d9d4b6b8fe7 100644 --- a/core/filter.js +++ b/core/filter.js @@ -49,6 +49,16 @@ */ this.editor = null; + /** + * Enter mode used by filter when deciding how to strip disallowed elements. + * + * For editor's filter will be equal to {@link CKEDITOR.config#enterMode}. + * For standalone filter will be set to {@link CKEDITOR#ENTER_P} by default. + * + * @property {CKEDITOR.ENTER_P/CKEDITOR.ENTER_DIV/CKEDITOR.ENTER_BR} + */ + this.enterMode = CKEDITOR.ENTER_P; + this._ = { // Optimized allowed content rules. rules: {}, @@ -61,7 +71,8 @@ var editor = this.editor = editorOrRules; this.customConfig = true; - var allowedContent = editor.config.allowedContent; + var allowedContent = editor.config.allowedContent, + enterMode; // Disable filter completely by setting config.allowedContent = true. if ( allowedContent === true ) { @@ -72,8 +83,9 @@ if ( !allowedContent ) this.customConfig = false; - // Add editor's default rules. - this.allow( 'p br', 'default', 1 ); + this.enterMode = enterMode = editor.config.enterMode; + + this.allow( 'br ' + ( enterMode == CKEDITOR.ENTER_P ? 'p' : enterMode == CKEDITOR.ENTER_DIV ? 'div' : '' ), 'default', 1 ); this.allow( allowedContent, 'config', 1 ); this.allow( editor.config.extraAllowedContent, 'extra', 1 ); @@ -190,11 +202,12 @@ }, CKEDITOR.NODE_ELEMENT, true ); var element, - toBeChecked = []; + toBeChecked = [], + enterTag = [ 'p', 'br', 'div' ][ this.enterMode - 1 ]; // Remove elements in reverse order - from leaves to root, to avoid conflicts. while ( ( element = toBeRemoved.pop() ) ) - removeElement( element, toBeChecked ); + removeElement( element, enterTag, toBeChecked ); // Check elements that have been marked as invalid (e.g. li as child of body after ul has been removed). while ( ( element = toBeChecked.pop() ) ) { @@ -202,7 +215,7 @@ element.parent.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT && !DTD[ element.parent.name ][ element.name ] ) - removeElement( element, toBeChecked ); + removeElement( element, enterTag, toBeChecked ); } }, @@ -1086,38 +1099,55 @@ return true; } + function createBr() { + return new CKEDITOR.htmlParser.element( 'br' ); + } + // Whether this is an inline element or text. function inlineNode( node ) { return node.type == CKEDITOR.NODE_TEXT || node.type == CKEDITOR.NODE_ELEMENT && DTD.$inline[ node.name ]; } + function isBrOrBlock( node ) { + return node.type == CKEDITOR.NODE_ELEMENT && + ( node.name == 'br' || DTD.$block[ node.name ] ); + } + // Try to remove element in the best possible way. // // @param {Array} toBeChecked After executing this function // this array will contain elements that should be checked // because they were marked as potentially in wrong context (e.g. li in body). - function removeElement( element, toBeChecked ) { + function removeElement( element, enterTag, toBeChecked ) { var name = element.name; - if ( DTD.$empty[ name ] || !element.children.length ) - element.remove(); - else if ( DTD.$block[ name ] || name == 'tr' ) - stripElement( element, toBeChecked ); - else + if ( DTD.$empty[ name ] || !element.children.length ) { + // Special case - hr in br mode should be replaced with br, not removed. + if ( name == 'hr' && enterTag == 'br' ) + element.replaceWith( createBr() ); + else + element.remove(); + } else if ( DTD.$block[ name ] || name == 'tr' ) { + if ( enterTag == 'br' ) + stripBlockBr( element, toBeChecked ); + else + stripBlock( element, enterTag, toBeChecked ); + } else element.replaceWithChildren(); } - // Strip element, but leave its content. - function stripElement( element, toBeChecked ) { + // Strip element block, but leave its content. + // Works in 'div' and 'p' enter modes. + function stripBlock( element, enterTag, toBeChecked ) { var children = element.children; - // First, check if element's children may be wrapped with

. - // Ignore that

may not be allowed in element.parent. + // First, check if element's children may be wrapped with

. + // Ignore that

may not be allowed in element.parent. // This will be fixed when removing parent, because in all known cases - // parent will was also marked to be removed. - if ( checkChildren( children, 'p' ) ) { - element.name = 'p'; + // parent will be also marked to be removed. + if ( checkChildren( children, enterTag ) ) { + element.name = enterTag; element.attributes = {}; return; } @@ -1134,7 +1164,7 @@ // insert this child into newly created paragraph. if ( shouldAutoP && inlineNode( child ) ) { if ( !p ) { - p = new CKEDITOR.htmlParser.element( 'p' ); + p = new CKEDITOR.htmlParser.element( enterTag ); p.insertAfter( element ); } p.add( child, 0 ); @@ -1157,6 +1187,27 @@ element.remove(); } + // Prepend/append block with
if isn't + // already prepended/appended with
or block and + // isn't first/last child of its parent. + // Then replace element with its children. + //

a

b

=>

a


b => a
b + function stripBlockBr( element, toBeChecked ) { + var br; + + if ( element.previous && !isBrOrBlock( element.previous ) ) { + br = createBr(); + br.insertBefore( element ); + } + + if ( element.next && !isBrOrBlock( element.next ) ) { + br = createBr(); + br.insertAfter( element ); + } + + element.replaceWithChildren(); + } + // // TRANSFORMATIONS -------------------------------------------------------- // diff --git a/core/htmlparser/node.js b/core/htmlparser/node.js index bc4cbc1840b..0c0fa8edd8f 100644 --- a/core/htmlparser/node.js +++ b/core/htmlparser/node.js @@ -67,6 +67,25 @@ node.next = this; next && ( next.previous = this ); + this.parent = node.parent; + }, + + /** + * Insert this node before given one. + * + * @param {CKEDITOR.htmlParser.node} node The node that will follow this element. + */ + insertBefore: function( node ) { + var children = node.parent.children, + index = CKEDITOR.tools.indexOf( children, node ); + + children.splice( index, 0, this ); + + this.next = node; + this.previous = node.previous; + node.previous && ( node.previous.next = this ); + node.previous = this; + this.parent = node.parent; } }; From b1e3afdc10991649d00f3a396e05e9bd2d3cd923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Reinmar=20Koszuli=C5=84ski?= Date: Mon, 18 Feb 2013 14:50:40 +0100 Subject: [PATCH 2/2] Force filter.enterMode==BR for blockless editor. --- core/filter.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/filter.js b/core/filter.js index d9d4b6b8fe7..59df482a3cc 100644 --- a/core/filter.js +++ b/core/filter.js @@ -52,8 +52,11 @@ /** * Enter mode used by filter when deciding how to strip disallowed elements. * - * For editor's filter will be equal to {@link CKEDITOR.config#enterMode}. - * For standalone filter will be set to {@link CKEDITOR#ENTER_P} by default. + * For editor's filter it will be set to {@link CKEDITOR.config#enterMode} unless this + * is a blockless (see {@link CKEDITOR.editor#blockless}) editor - in this case + * {@link CKEDITOR#ENTER_BR} will be forced. + * + * For standalone filter it will be by default set to {@link CKEDITOR#ENTER_P}. * * @property {CKEDITOR.ENTER_P/CKEDITOR.ENTER_DIV/CKEDITOR.ENTER_BR} */ @@ -83,7 +86,8 @@ if ( !allowedContent ) this.customConfig = false; - this.enterMode = enterMode = editor.config.enterMode; + // Force ENTER_BR for blockless editable. + this.enterMode = enterMode = ( editor.blockless ? CKEDITOR.ENTER_BR : editor.config.enterMode ); this.allow( 'br ' + ( enterMode == CKEDITOR.ENTER_P ? 'p' : enterMode == CKEDITOR.ENTER_DIV ? 'div' : '' ), 'default', 1 ); this.allow( allowedContent, 'config', 1 );