|
49 | 49 | */
|
50 | 50 | this.editor = null;
|
51 | 51 |
|
| 52 | + /** |
| 53 | + * Enter mode used by filter when deciding how to strip disallowed elements. |
| 54 | + * |
| 55 | + * For editor's filter it will be set to {@link CKEDITOR.config#enterMode} unless this |
| 56 | + * is a blockless (see {@link CKEDITOR.editor#blockless}) editor - in this case |
| 57 | + * {@link CKEDITOR#ENTER_BR} will be forced. |
| 58 | + * |
| 59 | + * For standalone filter it will be by default set to {@link CKEDITOR#ENTER_P}. |
| 60 | + * |
| 61 | + * @property {CKEDITOR.ENTER_P/CKEDITOR.ENTER_DIV/CKEDITOR.ENTER_BR} |
| 62 | + */ |
| 63 | + this.enterMode = CKEDITOR.ENTER_P; |
| 64 | + |
52 | 65 | this._ = {
|
53 | 66 | // Optimized allowed content rules.
|
54 | 67 | rules: {},
|
|
61 | 74 | var editor = this.editor = editorOrRules;
|
62 | 75 | this.customConfig = true;
|
63 | 76 |
|
64 |
| - var allowedContent = editor.config.allowedContent; |
| 77 | + var allowedContent = editor.config.allowedContent, |
| 78 | + enterMode; |
65 | 79 |
|
66 | 80 | // Disable filter completely by setting config.allowedContent = true.
|
67 | 81 | if ( allowedContent === true ) {
|
|
72 | 86 | if ( !allowedContent )
|
73 | 87 | this.customConfig = false;
|
74 | 88 |
|
75 |
| - // Add editor's default rules. |
76 |
| - this.allow( 'p br', 'default', 1 ); |
| 89 | + // Force ENTER_BR for blockless editable. |
| 90 | + this.enterMode = enterMode = ( editor.blockless ? CKEDITOR.ENTER_BR : editor.config.enterMode ); |
| 91 | + |
| 92 | + this.allow( 'br ' + ( enterMode == CKEDITOR.ENTER_P ? 'p' : enterMode == CKEDITOR.ENTER_DIV ? 'div' : '' ), 'default', 1 ); |
77 | 93 | this.allow( allowedContent, 'config', 1 );
|
78 | 94 | this.allow( editor.config.extraAllowedContent, 'extra', 1 );
|
79 | 95 |
|
|
190 | 206 | }, CKEDITOR.NODE_ELEMENT, true );
|
191 | 207 |
|
192 | 208 | var element,
|
193 |
| - toBeChecked = []; |
| 209 | + toBeChecked = [], |
| 210 | + enterTag = [ 'p', 'br', 'div' ][ this.enterMode - 1 ]; |
194 | 211 |
|
195 | 212 | // Remove elements in reverse order - from leaves to root, to avoid conflicts.
|
196 | 213 | while ( ( element = toBeRemoved.pop() ) )
|
197 |
| - removeElement( element, toBeChecked ); |
| 214 | + removeElement( element, enterTag, toBeChecked ); |
198 | 215 |
|
199 | 216 | // Check elements that have been marked as invalid (e.g. li as child of body after ul has been removed).
|
200 | 217 | while ( ( element = toBeChecked.pop() ) ) {
|
201 | 218 | if ( element.parent &&
|
202 | 219 | element.parent.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT &&
|
203 | 220 | !DTD[ element.parent.name ][ element.name ]
|
204 | 221 | )
|
205 |
| - removeElement( element, toBeChecked ); |
| 222 | + removeElement( element, enterTag, toBeChecked ); |
206 | 223 | }
|
207 | 224 | },
|
208 | 225 |
|
|
1086 | 1103 | return true;
|
1087 | 1104 | }
|
1088 | 1105 |
|
| 1106 | + function createBr() { |
| 1107 | + return new CKEDITOR.htmlParser.element( 'br' ); |
| 1108 | + } |
| 1109 | + |
1089 | 1110 | // Whether this is an inline element or text.
|
1090 | 1111 | function inlineNode( node ) {
|
1091 | 1112 | return node.type == CKEDITOR.NODE_TEXT ||
|
1092 | 1113 | node.type == CKEDITOR.NODE_ELEMENT && DTD.$inline[ node.name ];
|
1093 | 1114 | }
|
1094 | 1115 |
|
| 1116 | + function isBrOrBlock( node ) { |
| 1117 | + return node.type == CKEDITOR.NODE_ELEMENT && |
| 1118 | + ( node.name == 'br' || DTD.$block[ node.name ] ); |
| 1119 | + } |
| 1120 | + |
1095 | 1121 | // Try to remove element in the best possible way.
|
1096 | 1122 | //
|
1097 | 1123 | // @param {Array} toBeChecked After executing this function
|
1098 | 1124 | // this array will contain elements that should be checked
|
1099 | 1125 | // because they were marked as potentially in wrong context (e.g. li in body).
|
1100 |
| - function removeElement( element, toBeChecked ) { |
| 1126 | + function removeElement( element, enterTag, toBeChecked ) { |
1101 | 1127 | var name = element.name;
|
1102 | 1128 |
|
1103 |
| - if ( DTD.$empty[ name ] || !element.children.length ) |
1104 |
| - element.remove(); |
1105 |
| - else if ( DTD.$block[ name ] || name == 'tr' ) |
1106 |
| - stripElement( element, toBeChecked ); |
1107 |
| - else |
| 1129 | + if ( DTD.$empty[ name ] || !element.children.length ) { |
| 1130 | + // Special case - hr in br mode should be replaced with br, not removed. |
| 1131 | + if ( name == 'hr' && enterTag == 'br' ) |
| 1132 | + element.replaceWith( createBr() ); |
| 1133 | + else |
| 1134 | + element.remove(); |
| 1135 | + } else if ( DTD.$block[ name ] || name == 'tr' ) { |
| 1136 | + if ( enterTag == 'br' ) |
| 1137 | + stripBlockBr( element, toBeChecked ); |
| 1138 | + else |
| 1139 | + stripBlock( element, enterTag, toBeChecked ); |
| 1140 | + } else |
1108 | 1141 | element.replaceWithChildren();
|
1109 | 1142 | }
|
1110 | 1143 |
|
1111 |
| - // Strip element, but leave its content. |
1112 |
| - function stripElement( element, toBeChecked ) { |
| 1144 | + // Strip element block, but leave its content. |
| 1145 | + // Works in 'div' and 'p' enter modes. |
| 1146 | + function stripBlock( element, enterTag, toBeChecked ) { |
1113 | 1147 | var children = element.children;
|
1114 | 1148 |
|
1115 |
| - // First, check if element's children may be wrapped with <p>. |
1116 |
| - // Ignore that <p> may not be allowed in element.parent. |
| 1149 | + // First, check if element's children may be wrapped with <p/div>. |
| 1150 | + // Ignore that <p/div> may not be allowed in element.parent. |
1117 | 1151 | // This will be fixed when removing parent, because in all known cases
|
1118 |
| - // parent will was also marked to be removed. |
1119 |
| - if ( checkChildren( children, 'p' ) ) { |
1120 |
| - element.name = 'p'; |
| 1152 | + // parent will be also marked to be removed. |
| 1153 | + if ( checkChildren( children, enterTag ) ) { |
| 1154 | + element.name = enterTag; |
1121 | 1155 | element.attributes = {};
|
1122 | 1156 | return;
|
1123 | 1157 | }
|
|
1134 | 1168 | // insert this child into newly created paragraph.
|
1135 | 1169 | if ( shouldAutoP && inlineNode( child ) ) {
|
1136 | 1170 | if ( !p ) {
|
1137 |
| - p = new CKEDITOR.htmlParser.element( 'p' ); |
| 1171 | + p = new CKEDITOR.htmlParser.element( enterTag ); |
1138 | 1172 | p.insertAfter( element );
|
1139 | 1173 | }
|
1140 | 1174 | p.add( child, 0 );
|
|
1157 | 1191 | element.remove();
|
1158 | 1192 | }
|
1159 | 1193 |
|
| 1194 | + // Prepend/append block with <br> if isn't |
| 1195 | + // already prepended/appended with <br> or block and |
| 1196 | + // isn't first/last child of its parent. |
| 1197 | + // Then replace element with its children. |
| 1198 | + // <p>a</p><p>b</p> => <p>a</p><br>b => a<br>b |
| 1199 | + function stripBlockBr( element, toBeChecked ) { |
| 1200 | + var br; |
| 1201 | + |
| 1202 | + if ( element.previous && !isBrOrBlock( element.previous ) ) { |
| 1203 | + br = createBr(); |
| 1204 | + br.insertBefore( element ); |
| 1205 | + } |
| 1206 | + |
| 1207 | + if ( element.next && !isBrOrBlock( element.next ) ) { |
| 1208 | + br = createBr(); |
| 1209 | + br.insertAfter( element ); |
| 1210 | + } |
| 1211 | + |
| 1212 | + element.replaceWithChildren(); |
| 1213 | + } |
| 1214 | + |
1160 | 1215 | //
|
1161 | 1216 | // TRANSFORMATIONS --------------------------------------------------------
|
1162 | 1217 | //
|
|
0 commit comments