PHP Classes

File: core/assets/plugins/ckeditor/plugins/pastefromword/filter/default.js

Recommend this page to a friend!
  Classes of No name   RT Adminlte   core/assets/plugins/ckeditor/plugins/pastefromword/filter/default.js   Download  
File: core/assets/plugins/ckeditor/plugins/pastefromword/filter/default.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: RT Adminlte
Generate layout and menus for Adminlte
Author: By
Last change: Update v0.3
Date: 7 years ago
Size: 59,620 bytes
 

Contents

Class file image Download
/** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or http://ckeditor.com/license */ /* globals CKEDITOR */ ( function() { var List, Style, Heuristics, filter, tools = CKEDITOR.tools, invalidTags = [ 'o:p', 'xml', 'script', 'meta', 'link' ], links = {}, inComment = 0; /** * Set of Paste from Word plugin helpers. * * @since 4.6.0 * @private * @member CKEDITOR.plugins */ CKEDITOR.plugins.pastefromword = {}; CKEDITOR.cleanWord = function( mswordHtml, editor ) { var msoListsDetected = Boolean( mswordHtml.match( /mso-list:\s*l\d+\s+level\d+\s+lfo\d+/ ) ); // Sometimes Word malforms the comments. mswordHtml = mswordHtml.replace( /<!\[/g, '<!--[' ).replace( /\]>/g, ']-->' ); var fragment = CKEDITOR.htmlParser.fragment.fromHtml( mswordHtml ); filter = new CKEDITOR.htmlParser.filter( { root: function( element ) { element.filterChildren( filter ); CKEDITOR.plugins.pastefromword.lists.cleanup( List.createLists( element ) ); }, elementNames: [ [ ( /^\?xml:namespace$/ ), '' ], [ /^v:shapetype/, '' ], [ new RegExp( invalidTags.join( '|' ) ), '' ] // Remove invalid tags. ], elements: { 'a': function( element ) { // Redundant anchor created by IE8. if ( element.attributes.name ) { if ( element.attributes.name == '_GoBack' ) { delete element.name; return; } // Garbage links that go nowhere. if ( element.attributes.name.match( /^OLE_LINK\d+$/ ) ) { delete element.name; return; } } if ( element.attributes.href && element.attributes.href.match( /#.+$/ ) ) { var name = element.attributes.href.match( /#(.+)$/ )[ 1 ]; links[ name ] = element; } if ( element.attributes.name && links[ element.attributes.name ] ) { var link = links[ element.attributes.name ]; link.attributes.href = link.attributes.href.replace( /.*#(.*)$/, '#$1' ); } }, 'div': function( element ) { Style.createStyleStack( element, filter, editor ); }, 'img': function( element ) { var attributeStyleMap = { width: function( value ) { Style.setStyle( element, 'width', value + 'px' ); }, height: function( value ) { Style.setStyle( element, 'height', value + 'px' ); } }; if ( element.parent ) { var attrs = element.parent.attributes, style = attrs.style || attrs.STYLE; if ( style && style.match( /mso\-list:\s?Ignore/ ) ) { element.attributes[ 'cke-ignored' ] = true; } } Style.mapStyles( element, attributeStyleMap ); if ( element.attributes.src && element.attributes.src.match( /^file:\/\// ) && element.attributes.alt && element.attributes.alt.match( /^https?:\/\// ) ) { element.attributes.src = element.attributes.alt; } }, 'p': function( element ) { element.filterChildren( filter ); if ( element.attributes.style && element.attributes.style.match( /display:\s*none/i ) ) { return false; } if ( List.thisIsAListItem( editor, element ) ) { List.convertToFakeListItem( editor, element ); } else { // In IE list level information is stored in <p> elements inside <li> elements. var container = element.getAscendant( function( element ) { return element.name == 'ul' || element.name == 'ol'; } ), style = tools.parseCssText( element.attributes.style ); if ( container && !container.attributes[ 'cke-list-level' ] && style[ 'mso-list' ] && style[ 'mso-list' ].match( /level/ ) ) { container.attributes[ 'cke-list-level' ] = style[ 'mso-list' ].match( /level(\d+)/ )[1]; } } Style.createStyleStack( element, filter, editor ); }, 'pre': function( element ) { if ( List.thisIsAListItem( editor, element ) ) List.convertToFakeListItem( editor, element ); Style.createStyleStack( element, filter, editor ); }, 'h1': function( element ) { if ( List.thisIsAListItem( editor, element ) ) List.convertToFakeListItem( editor, element ); Style.createStyleStack( element, filter, editor ); }, 'font': function( element ) { if ( element.getHtml().match( /^\s*$/ ) ) { new CKEDITOR.htmlParser.text( ' ' ).insertAfter( element ); return false; } if ( editor && editor.config.pasteFromWordRemoveFontStyles === true && element.attributes.size ) { // font[size] are still used by old IEs for font size. delete element.attributes.size; } createAttributeStack( element, filter ); }, 'ul': function( element ) { if ( !msoListsDetected ) { // List should only be processed if we're sure we're working with Word. (#16593) return; } // Edge case from 11683 - an unusual way to create a level 2 list. if ( element.parent.name == 'li' && tools.indexOf( element.parent.children, element ) === 0 ) { Style.setStyle( element.parent, 'list-style-type', 'none' ); } List.dissolveList( element ); return false; }, 'li': function( element ) { if ( !msoListsDetected ) { return; } element.attributes.style = Style.normalizedStyles( element, editor ); Style.pushStylesLower( element ); }, 'ol': function( element ) { if ( !msoListsDetected ) { // List should only be processed if we're sure we're working with Word. (#16593) return; } // Fix edge-case where when a list skips a level in IE11, the <ol> element // is implicitly surrounded by a <li>. if ( element.parent.name == 'li' && tools.indexOf( element.parent.children, element ) === 0 ) { Style.setStyle( element.parent, 'list-style-type', 'none' ); } List.dissolveList( element ); return false; }, 'span': function( element ) { element.filterChildren( filter ); element.attributes.style = Style.normalizedStyles( element, editor ); if ( !element.attributes.style || // Remove garbage bookmarks that disrupt the content structure. element.attributes.style.match( /^mso\-bookmark:OLE_LINK\d+$/ ) || element.getHtml().match( /^(\s|&nbsp;)+$/ ) ) { // replaceWithChildren doesn't work in filters. for ( var i = element.children.length - 1; i >= 0; i-- ) { element.children[ i ].insertAfter( element ); } return false; } Style.createStyleStack( element, filter, editor ); }, 'table': function( element ) { element._tdBorders = {}; element.filterChildren( filter ); var borderStyle, occurences = 0; for ( var border in element._tdBorders ) { if ( element._tdBorders[ border ] > occurences ) { occurences = element._tdBorders[ border ]; borderStyle = border; } } Style.setStyle( element, 'border', borderStyle ); }, 'td': function( element ) { var ascendant = element.getAscendant( 'table' ), tdBorders = ascendant._tdBorders, borderStyles = [ 'border', 'border-top', 'border-right', 'border-bottom', 'border-left' ], ascendantStyle = tools.parseCssText( ascendant.attributes.style ); // Sometimes the background is set for the whole table - move it to individual cells. var background = ascendantStyle.background || ascendantStyle.BACKGROUND; if ( background ) { Style.setStyle( element, 'background', background, true ); } var backgroundColor = ascendantStyle[ 'background-color' ] || ascendantStyle[ 'BACKGROUND-COLOR' ]; if ( backgroundColor ) { Style.setStyle( element, 'background-color', backgroundColor, true ); } var styles = tools.parseCssText( element.attributes.style ); for ( var style in styles ) { var temp = styles[ style ]; delete styles[ style ]; styles[ style.toLowerCase() ] = temp; } // Count all border styles that occur in the table. for ( var i = 0; i < borderStyles.length; i++ ) { if ( styles[ borderStyles[ i ] ] ) { var key = styles[ borderStyles[ i ] ]; tdBorders[ key ] = tdBorders[ key ] ? tdBorders[ key ] + 1 : 1; } } Style.pushStylesLower( element, { 'background': true } ); }, 'v:imagedata': remove, // This is how IE8 presents images. 'v:shape': function( element ) { // In chrome a <v:shape> element may be followed by an <img> element with the same content. var duplicate = false; element.parent.getFirst( function( child ) { if ( child.name == 'img' && child.attributes && child.attributes[ 'v:shapes' ] == element.attributes.id ) { duplicate = true; } } ); if ( duplicate ) return false; var src = ''; element.forEach( function( child ) { if ( child.attributes && child.attributes.src ) { src = child.attributes.src; } }, CKEDITOR.NODE_ELEMENT, true ); element.filterChildren( filter ); element.name = 'img'; element.attributes.src = element.attributes.src || src; delete element.attributes.type; }, 'style': function() { // We don't want to let any styles in. Firefox tends to add some. return false; } }, attributes: { 'style': function( styles, element ) { // Returning false deletes the attribute. return Style.normalizedStyles( element, editor ) || false; }, 'class': function( classes ) { return falseIfEmpty( classes.replace( /msonormal|msolistparagraph\w*/ig, '' ) ); }, 'cellspacing': remove, 'cellpadding': remove, 'border': remove, 'valign': remove, 'v:shapes': remove, 'o:spid': remove }, comment: function( element ) { if ( element.match( /\[if.* supportFields.*\]/ ) ) { inComment++; } if ( element == '[endif]' ) { inComment = inComment > 0 ? inComment - 1 : 0; } return false; }, text: function( content ) { if ( inComment ) { return ''; } return content.replace( /&nbsp;/g, ' ' ); } } ); var writer = new CKEDITOR.htmlParser.basicWriter(); filter.applyTo( fragment ); fragment.writeHtml( writer ); return writer.getHtml(); }; /** * Namespace containing all the helper functions to work with styles. * * @private * @since 4.6.0 * @member CKEDITOR.plugins.pastefromword */ CKEDITOR.plugins.pastefromword.styles = { setStyle: function( element, key, value, dontOverwrite ) { var styles = tools.parseCssText( element.attributes.style ); if ( dontOverwrite && styles[ key ] ) { return; } if ( value === '' ) { delete styles[ key ]; } else { styles[ key ] = value; } element.attributes.style = CKEDITOR.tools.writeCssText( styles ); }, // Map attributes to styles. mapStyles: function( element, attributeStyleMap ) { for ( var attribute in attributeStyleMap ) { if ( element.attributes[ attribute ] ) { if ( typeof attributeStyleMap[ attribute ] === 'function' ) { attributeStyleMap[ attribute ]( element.attributes[ attribute ] ); } else { Style.setStyle( element, attributeStyleMap[ attribute ], element.attributes[ attribute ] ); } delete element.attributes[ attribute ]; } } }, /** * Filters Word-specific styles for a given element. Also might filter additional styles * based on the `editor` configuration. * * @private * @param {CKEDITOR.htmlParser.element} element * @param {CKEDITOR.editor} editor * @member CKEDITOR.plugins.pastefromword.styles */ normalizedStyles: function( element, editor ) { // Some styles and style values are redundant, so delete them. var resetStyles = [ 'background-color:transparent', 'border-image:none', 'color:windowtext', 'direction:ltr', 'mso-', 'text-indent', 'visibility:visible', 'div:border:none' // This one stays because #6241 ], textStyles = [ 'font-family', 'font', 'font-size', 'color', 'background-color', 'line-height', 'text-decoration' ], matchStyle = function() { var keys = []; for ( var i = 0; i < arguments.length; i++ ) { if ( arguments[ i ] ) { keys.push( arguments[ i ] ); } } return tools.indexOf( resetStyles, keys.join( ':' ) ) !== -1; }, removeFontStyles = editor && editor.config.pasteFromWordRemoveFontStyles === true; var styles = tools.parseCssText( element.attributes.style ); if ( element.name == 'cke:li' ) { // IE8 tries to emulate list indentation with a combination of // text-indent and left margin. Normalize this. Note that IE8 styles are uppercase. if ( styles[ 'TEXT-INDENT' ] && styles.MARGIN ) { element.attributes[ 'cke-indentation' ] = List.getElementIndentation( element ); styles.MARGIN = styles.MARGIN.replace( /(([\w\.]+ ){3,3})[\d\.]+(\w+$)/, '$10$3' ); } } var keys = tools.objectKeys( styles ); for ( var i = 0; i < keys.length; i++ ) { var styleName = keys[ i ].toLowerCase(), styleValue = styles[ keys[ i ] ], indexOf = CKEDITOR.tools.indexOf, toBeRemoved = removeFontStyles && indexOf( textStyles, styleName.toLowerCase() ) !== -1; if ( toBeRemoved || matchStyle( null, styleName, styleValue ) || matchStyle( null, styleName.replace( /\-.*$/, '-' ) ) || matchStyle( null, styleName ) || matchStyle( element.name, styleName, styleValue ) || matchStyle( element.name, styleName.replace( /\-.*$/, '-' ) ) || matchStyle( element.name, styleName ) || matchStyle( styleValue ) ) { delete styles[ keys[ i ] ]; } } return CKEDITOR.tools.writeCssText( styles ); }, /** * Surrounds the element's children with a stack of spans, each one having one style * originally belonging to the element. * * @private * @param {CKEDITOR.htmlParser.element} element * @param {CKEDITOR.htmlParser.filter} filter * @param {CKEDITOR.editor} editor * @member CKEDITOR.plugins.pastefromword.styles */ createStyleStack: function( element, filter, editor ) { var i, children = []; element.filterChildren( filter ); // Store element's children somewhere else. for ( i = element.children.length - 1; i >= 0; i-- ) { children.unshift( element.children[ i ] ); element.children[ i ].remove(); } Style.sortStyles( element ); // Create a stack of spans with each containing one style. var styles = tools.parseCssText( Style.normalizedStyles( element, editor ) ), innermostElement = element, styleTopmost = element.name === 'span'; // Ensure that the root element retains at least one style. for ( var style in styles ) { if ( style.match( /margin|text\-align|width|border|padding/i ) ) { continue; } if ( styleTopmost ) { styleTopmost = false; continue; } var newElement = new CKEDITOR.htmlParser.element( 'span' ); newElement.attributes.style = style + ':' + styles[ style ]; innermostElement.add( newElement ); innermostElement = newElement; delete styles[ style ]; } if ( JSON.stringify( styles ) !== '{}' ) { element.attributes.style = CKEDITOR.tools.writeCssText( styles ); } else { delete element.attributes.style; } // Add the stored children to the innermost span. for ( i = 0; i < children.length; i++ ) { innermostElement.add( children[ i ] ); } }, // Some styles need to be stacked in a particular order to work properly. sortStyles: function( element ) { var orderedStyles = [ 'border', 'border-bottom', 'font-size', 'background' ], style = tools.parseCssText( element.attributes.style ), keys = tools.objectKeys( style ), sortedKeys = [], nonSortedKeys = []; // Divide styles into sorted and non-sorted, because Array.prototype.sort() // requires a transitive relation. for ( var i = 0; i < keys.length; i++ ) { if ( tools.indexOf( orderedStyles, keys[ i ].toLowerCase() ) !== -1 ) { sortedKeys.push( keys[ i ] ); } else { nonSortedKeys.push( keys[ i ] ); } } // For styles in orderedStyles[] enforce the same order as in orderedStyles[]. sortedKeys.sort( function( a, b ) { var aIndex = tools.indexOf( orderedStyles, a.toLowerCase() ); var bIndex = tools.indexOf( orderedStyles, b.toLowerCase() ); return aIndex - bIndex; } ); keys = [].concat( sortedKeys, nonSortedKeys ); var sortedStyles = {}; for ( i = 0; i < keys.length; i++ ) { sortedStyles[ keys[ i ] ] = style[ keys[ i ] ]; } element.attributes.style = CKEDITOR.tools.writeCssText( sortedStyles ); }, // Moves the element's styles lower in the DOM hierarchy. // Returns true on success. pushStylesLower: function( element, exceptions ) { if ( !element.attributes.style || element.children.length === 0 ) { return false; } exceptions = exceptions || {}; // Entries ending with a dash match styles that start with // the entry name, e.g. 'border-' matches 'border-style', 'border-color' etc. var retainedStyles = { 'list-style-type': true, 'width': true, 'border': true, 'border-': true }; var styles = tools.parseCssText( element.attributes.style ); for ( var style in styles ) { if ( style.toLowerCase() in retainedStyles || retainedStyles [ style.toLowerCase().replace( /\-.*$/, '-' ) ] || style.toLowerCase() in exceptions ) { continue; } var pushed = false; for ( var i = 0; i < element.children.length; i++ ) { var child = element.children[ i ]; if ( child.type !== CKEDITOR.NODE_ELEMENT ) { continue; } pushed = true; Style.setStyle( child, style, styles[ style ] ); } if ( pushed ) { delete styles[ style ]; } } element.attributes.style = CKEDITOR.tools.writeCssText( styles ); return true; } }; Style = CKEDITOR.plugins.pastefromword.styles; /** * Namespace containing any list-oriented helper methods. * * @private * @since 4.6.0 * @member CKEDITOR.plugins.pastefromword */ CKEDITOR.plugins.pastefromword.lists = { /** * Checks if a given element is a list item-alike. * * @private * @param {CKEDITOR.editor} editor * @param {CKEDITOR.htmlParser.element} element * @returns {Boolean} * @member CKEDITOR.plugins.pastefromword.lists */ thisIsAListItem: function( editor, element ) { if ( Heuristics.isEdgeListItem( editor, element ) ) { return true; } /*jshint -W024 */ // Normally a style of the sort that looks like "mso-list: l0 level1 lfo1" // indicates a list element, but the same style may appear in a <p> that's within a <li>. if ( ( ( element.attributes.style && element.attributes.style.match( /mso\-list:\s?l\d/ ) ) && element.parent.name !== 'li' ) || element.attributes[ 'cke-dissolved' ] || element.getHtml().match( /<!\-\-\[if !supportLists]\-\->/ ) || // Flat, ordered lists are represented by paragraphs // who's text content roughly matches /(&nbsp;)*(.*?)(&nbsp;)+/ // where the middle parentheses contain the symbol. element.getHtml().match( /^( )*.*?[\.\)] ( ){2,700}/ ) ) { return true; } return false; /*jshint +W024 */ }, /** * Converts an element to an element with the `cke:li` tag name. * * @private * @param {CKEDITOR.editor} editor * @param {CKEDITOR.htmlParser.element} element * @member CKEDITOR.plugins.pastefromword.lists */ convertToFakeListItem: function( editor, element ) { if ( Heuristics.isEdgeListItem( editor, element ) ) { Heuristics.assignListLevels( editor, element ); } // A dummy call to cache parsed list info inside of cke-list-* attributes. this.getListItemInfo( element ); if ( !element.attributes[ 'cke-dissolved' ] ) { // The symbol is usually the first text node descendant // of the element that doesn't start with a whitespace character; var symbol; element.forEach( function( element ) { // Sometimes there are custom markers represented as images. // They can be recognized by the distinctive alt attribute value. if ( !symbol && element.name == 'img' && element.attributes[ 'cke-ignored' ] && element.attributes.alt == '*' ) { symbol = '·'; // Remove the "symbol" now, since it's the best opportunity to do so. element.remove(); } }, CKEDITOR.NODE_ELEMENT ); element.forEach( function( element ) { if ( !symbol && !element.value.match( /^ / ) ) { symbol = element.value; } }, CKEDITOR.NODE_TEXT ); // Without a symbol this isn't really a list item. if ( typeof symbol == 'undefined' ) { return; } element.attributes[ 'cke-symbol' ] = symbol.replace( / .*$/, '' ); List.removeSymbolText( element ); } if ( element.attributes.style ) { // Hacky way to get rid of margin left. // @todo: we should gather all css cleanup here, and consider bidi. Eventually we might put a config variable to // to enable it. var styles = tools.parseCssText( element.attributes.style ); if ( styles[ 'margin-left' ] ) { delete styles[ 'margin-left' ]; element.attributes.style = CKEDITOR.tools.writeCssText( styles ); } } // Converting to a normal list item would implicitly wrap the element around an <ul>. element.name = 'cke:li'; }, /** * Converts any fake list items contained within `root` into real `li` elements. * * @private * @param {CKEDITOR.htmlParser.element} root * @returns {CKEDITOR.htmlParser.element[]} An array of converted elements. * @member CKEDITOR.plugins.pastefromword.lists */ convertToRealListItems: function( root ) { var listElements = []; // Select and clean up list elements. root.forEach( function( element ) { if ( element.name == 'cke:li' ) { element.name = 'li'; //List.removeSymbolText( element ); listElements.push( element ); } }, CKEDITOR.NODE_ELEMENT, false ); return listElements; }, removeSymbolText: function( element ) { // ...from a list element. var removed, symbol = element.attributes[ 'cke-symbol' ]; element.forEach( function( node ) { if ( !removed && node.value.match( symbol.replace( ')', '\\)' ).replace( '(', '' ) ) ) { node.value = node.value.replace( symbol, '' ); if ( node.parent.getHtml().match( /^(\s|&nbsp;)*$/ ) ) { removed = node.parent !== element ? node.parent : null; } } }, CKEDITOR.NODE_TEXT ); removed && removed.remove(); }, setListSymbol: function( list, symbol, level ) { level = level || 1; var style = tools.parseCssText( list.attributes.style ); if ( list.name == 'ol' ) { if ( list.attributes.type || style[ 'list-style-type' ] ) return; var typeMap = { '[ivx]': 'lower-roman', '[IVX]': 'upper-roman', '[a-z]': 'lower-alpha', '[A-Z]': 'upper-alpha', '\\d': 'decimal' }; for ( var type in typeMap ) { if ( List.getSubsectionSymbol( symbol ).match( new RegExp( type ) ) ) { style[ 'list-style-type' ] = typeMap[ type ]; break; } } list.attributes[ 'cke-list-style-type' ] = style[ 'list-style-type' ]; } else { var symbolMap = { '·': 'disc', 'o': 'circle', '§': 'square' // In Word this is a square. }; if ( !style[ 'list-style-type' ] && symbolMap[ symbol ] ) { style[ 'list-style-type' ] = symbolMap[ symbol ]; } } List.setListSymbol.removeRedundancies( style, level ); ( list.attributes.style = CKEDITOR.tools.writeCssText( style ) ) || delete list.attributes.style; }, setListStart: function( list ) { var symbols = [], offset = 0; for ( var i = 0; i < list.children.length; i++ ) { symbols.push( list.children[ i ].attributes[ 'cke-symbol' ] || '' ); } // When a list starts with a sublist, use the next element as a start indicator. if ( !symbols[ 0 ] ) { offset++; } // Attribute set in setListSymbol() switch ( list.attributes[ 'cke-list-style-type' ] ) { case 'lower-roman': case 'upper-roman': list.attributes.start = List.toArabic( List.getSubsectionSymbol( symbols[ offset ] ) ) - offset; break; case 'lower-alpha': case 'upper-alpha': list.attributes.start = List.getSubsectionSymbol( symbols[ offset ] ).replace( /\W/g, '' ).toLowerCase().charCodeAt( 0 ) - 96 - offset; break; case 'decimal': list.attributes.start = ( parseInt( List.getSubsectionSymbol( symbols[ offset ] ), 10 ) - offset ) || 1; break; } if ( list.attributes.start == '1' ) { delete list.attributes.start; } delete list.attributes[ 'cke-list-style-type' ]; }, numbering: { /** * Converts the list marker value into a decimal number. * * var toNumber = CKEDITOR.plugins.pastefromword.lists.numbering.toNumber; * * console.log( toNumber( 'XIV', 'upper-roman' ) ); // Logs 14. * console.log( toNumber( 'd', 'lower-alpha' ) ); // Logs 4. * console.log( toNumber( '35', 'decimal' ) ); // Logs 35. * console.log( toNumber( '404', 'foo' ) ); // Logs 1. * * @param {String} marker * @param {String} markerType Marker type according to CSS `list-style-type` values. * @returns {Number} * @member CKEDITOR.plugins.pastefromword.lists.numbering */ toNumber: function( marker, markerType ) { // Functions copied straight from old PFW implementation, no need to reinvent the wheel. function fromAlphabet( str ) { var alpahbets = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; str = str.toUpperCase(); var l = alpahbets.length, retVal = 1; for ( var x = 1; str.length > 0; x *= l ) { retVal += alpahbets.indexOf( str.charAt( str.length - 1 ) ) * x; str = str.substr( 0, str.length - 1 ); } return retVal; } function fromRoman( str ) { var romans = [ [ 1000, 'M' ], [ 900, 'CM' ], [ 500, 'D' ], [ 400, 'CD' ], [ 100, 'C' ], [ 90, 'XC' ], [ 50, 'L' ], [ 40, 'XL' ], [ 10, 'X' ], [ 9, 'IX' ], [ 5, 'V' ], [ 4, 'IV' ], [ 1, 'I' ] ]; str = str.toUpperCase(); var l = romans.length, retVal = 0; for ( var i = 0; i < l; ++i ) { for ( var j = romans[ i ], k = j[ 1 ].length; str.substr( 0, k ) == j[ 1 ]; str = str.substr( k ) ) retVal += j[ 0 ]; } return retVal; } if ( markerType == 'decimal' ) { return Number( marker ); } else if ( markerType == 'upper-roman' || markerType == 'lower-roman' ) { return fromRoman( marker.toUpperCase() ); } else if ( markerType == 'lower-alpha' || markerType == 'upper-alpha' ) { return fromAlphabet( marker ); } else { return 1; } }, /** * Returns a list style based on the Word marker content. * * var getStyle = CKEDITOR.plugins.pastefromword.lists.numbering.getStyle; * * console.log( getStyle( '4' ) ); // Logs: "decimal" * console.log( getStyle( 'b' ) ); // Logs: "lower-alpha" * console.log( getStyle( 'P' ) ); // Logs: "upper-alpha" * console.log( getStyle( 'i' ) ); // Logs: "lower-roman" * console.log( getStyle( 'X' ) ); // Logs: "upper-roman" * * * **Implementation note:** Characters `c` and `d` are not converted to roman on purpose. It is 100 and 500 respectively, so * you rarely go with a list up until this point, while it is common to start with `c` and `d` in alpha. * * @param {String} marker Marker content retained from Word, e.g. `1`, `7`, `XI`, `b`. * @returns {String} Resolved marker type. * @member CKEDITOR.plugins.pastefromword.lists.numbering */ getStyle: function( marker ) { var typeMap = { 'i': 'lower-roman', 'v': 'lower-roman', 'x': 'lower-roman', 'l': 'lower-roman', 'm': 'lower-roman', 'I': 'upper-roman', 'V': 'upper-roman', 'X': 'upper-roman', 'L': 'upper-roman', 'M': 'upper-roman' }, firstCharacter = marker.slice( 0, 1 ), type = typeMap[ firstCharacter ]; if ( !type ) { type = 'decimal'; if ( firstCharacter.match( /[a-z]/ ) ) { type = 'lower-alpha'; } if ( firstCharacter.match( /[A-Z]/ ) ) { type = 'upper-alpha'; } } return type; } }, // Taking into account cases like "1.1.2." etc. - get the last element. getSubsectionSymbol: function( symbol ) { return ( symbol.match( /([\da-zA-Z]+).?$/ ) || [ 'placeholder', 1 ] )[ 1 ]; }, setListDir: function( list ) { var dirs = { ltr: 0, rtl: 0 }; list.forEach( function( child ) { if ( child.name == 'li' ) { var dir = child.attributes.dir || child.attributes.DIR || ''; if ( dir.toLowerCase() == 'rtl' ) { dirs.rtl++; } else { dirs.ltr++; } } }, CKEDITOR.ELEMENT_NODE ); if ( dirs.rtl > dirs.ltr ) { list.attributes.dir = 'rtl'; } }, createList: function( element ) { // "o" symbolizes a circle in unordered lists. if ( ( element.attributes[ 'cke-symbol' ].match( /([\da-np-zA-NP-Z]).?/ ) || [] )[ 1 ] ) { return new CKEDITOR.htmlParser.element( 'ol' ); } return new CKEDITOR.htmlParser.element( 'ul' ); }, /** * @private * @param {CKEDITOR.htmlParser.element} root An element to be looked through for lists. * @returns {CKEDITOR.htmlParser.element[]} An array of created list items. * @member CKEDITOR.plugins.pastefromword.lists */ createLists: function( root ) { var element, level, i, j, listElements = List.convertToRealListItems( root ); if ( listElements.length === 0 ) { return []; } // Chop data into continuous lists. var lists = List.groupLists( listElements ); // Create nested list structures. for ( i = 0; i < lists.length; i++ ) { var list = lists[ i ], firstLevel1Element = list[ 0 ]; // To determine the type of the top-level list a level 1 element is needed. for ( j = 0; j < list.length; j++ ) { if ( list[ j ].attributes[ 'cke-list-level' ] == 1 ) { firstLevel1Element = list[ j ]; break; } } var containerStack = [ List.createList( firstLevel1Element ) ], // List wrapper (ol/ul). innermostContainer = containerStack[ 0 ], allContainers = [ containerStack[ 0 ] ]; // Insert first known list item before the list wrapper. innermostContainer.insertBefore( list[ 0 ] ); for ( j = 0; j < list.length; j++ ) { element = list[ j ]; level = element.attributes[ 'cke-list-level' ]; while ( level > containerStack.length ) { var content = List.createList( element ); var children = innermostContainer.children; if ( children.length > 0 ) { children[ children.length - 1 ].add( content ); } else { var container = new CKEDITOR.htmlParser.element( 'li', { style: 'list-style-type:none' } ); container.add( content ); innermostContainer.add( container ); } containerStack.push( content ); allContainers.push( content ); innermostContainer = content; if ( level == containerStack.length ) { List.setListSymbol( content, element.attributes[ 'cke-symbol' ], level ); } } while ( level < containerStack.length ) { containerStack.pop(); innermostContainer = containerStack[ containerStack.length - 1 ]; if ( level == containerStack.length ) { List.setListSymbol( innermostContainer, element.attributes[ 'cke-symbol' ], level ); } } // For future reference this is where the list elements are actually put into the lists. element.remove(); innermostContainer.add( element ); } // Try to set the symbol for the root (level 1) list. var level1Symbol; if ( containerStack[ 0 ].children.length ) { level1Symbol = containerStack[ 0 ].children[ 0 ].attributes[ 'cke-symbol' ]; if ( !level1Symbol && containerStack[ 0 ].children.length > 1 ) { level1Symbol = containerStack[0].children[1].attributes[ 'cke-symbol' ]; } if ( level1Symbol ) { List.setListSymbol( containerStack[ 0 ], level1Symbol ); } } // This can be done only after all the list elements are where they should be. for ( j = 0; j < allContainers.length; j++ ) { List.setListStart( allContainers[ j ] ); } // Last but not least apply li[start] if needed, also this needs to be done once ols are final. for ( j = 0; j < list.length; j++ ) { this.determineListItemValue( list[ j ] ); } } return listElements; }, /** * Final cleanup &mdash; removes all `cke-*` helper attributes. * * @private * @param {CKEDITOR.htmlParser.element[]} listElements * @member CKEDITOR.plugins.pastefromword.lists */ cleanup: function( listElements ) { var tempAttributes = [ 'cke-list-level', 'cke-symbol', 'cke-list-id', 'cke-indentation', 'cke-dissolved' ], i, j; for ( i = 0; i < listElements.length; i++ ) { for ( j = 0; j < tempAttributes.length; j++ ) { delete listElements[ i ].attributes[ tempAttributes[ j ] ]; } } }, /** * Tries to determine the `li[value]` attribute for a given list item. The `element` given must * have a parent in order for this function to work properly. * * @private * @param {CKEDITOR.htmlParser.element} element * @member CKEDITOR.plugins.pastefromword.lists */ determineListItemValue: function( element ) { if ( element.parent.name !== 'ol' ) { // li[value] make sense only for list items in ordered list. return; } var assumedValue = this.calculateValue( element ), cleanSymbol = element.attributes[ 'cke-symbol' ].match( /[a-z0-9]+/gi ), computedValue, listType; if ( cleanSymbol ) { // Note that we always want to use last match, just because of markers like "1.1.4" "1.A.a.IV" etc. cleanSymbol = cleanSymbol[ cleanSymbol.length - 1 ]; // We can determine proper value only if we know what type of list is it. // So we need to check list wrapper if it has this information. listType = element.parent.attributes[ 'cke-list-style-type' ] || this.numbering.getStyle( cleanSymbol ); computedValue = this.numbering.toNumber( cleanSymbol, listType ); if ( computedValue !== assumedValue ) { element.attributes.value = computedValue; } } }, /** * Calculates the value for a given `<li>` element based on its precedent list items (e.g. the `value` * attribute). It could also look at the list parent (`<ol>`) at its start attribute. * * @private * @param {CKEDITOR.htmlParser.element} element The `<li>` element. * @returns {Number} * @member CKEDITOR.plugins.pastefromword.lists */ calculateValue: function( element ) { if ( !element.parent ) { return 1; } var list = element.parent, elementIndex = element.getIndex(), valueFound = null, // Index of the element with value attribute. valueElementIndex, curElement, i; // Look for any preceding li[value]. for ( i = elementIndex; i >= 0 && valueFound === null; i-- ) { curElement = list.children[ i ]; if ( curElement.attributes && curElement.attributes.value !== undefined ) { valueElementIndex = i; valueFound = parseInt( curElement.attributes.value, 10 ); } } // Still if no li[value] was found, we'll check the list. if ( valueFound === null ) { valueFound = list.attributes.start !== undefined ? parseInt( list.attributes.start, 10 ) : 1; valueElementIndex = 0; } return valueFound + ( elementIndex - valueElementIndex ); }, /** * @private * @param {CKEDITOR.htmlParser.element} element * @member CKEDITOR.plugins.pastefromword.lists */ dissolveList: function( element ) { var nameIs = function( name ) { return function( element ) { return element.name == name; }; }, isList = function( element ) { return nameIs( 'ul' )( element ) || nameIs( 'ol' )( element ); }, arrayTools = CKEDITOR.tools.array, elements = [], children, i; element.forEach( function( child ) { elements.push( child ); }, CKEDITOR.NODE_ELEMENT, false ); var items = arrayTools.filter( elements, nameIs( 'li' ) ), lists = arrayTools.filter( elements, isList ); arrayTools.forEach( lists, function( list ) { var type = list.attributes.type, start = parseInt( list.attributes.start, 10 ) || 1, level = countParents( isList, list ) + 1; if ( !type ) { var style = tools.parseCssText( list.attributes.style ); type = style[ 'list-style-type' ]; } arrayTools.forEach( arrayTools.filter( list.children, nameIs( 'li' ) ), function( child, index ) { var symbol; switch ( type ) { case 'disc': symbol = '·'; break; case 'circle': symbol = 'o'; break; case 'square': symbol = '§'; break; case '1': case 'decimal': symbol = ( start + index ) + '.'; break; case 'a': case 'lower-alpha': symbol = String.fromCharCode( 'a'.charCodeAt( 0 ) + start - 1 + index ) + '.'; break; case 'A': case 'upper-alpha': symbol = String.fromCharCode( 'A'.charCodeAt( 0 ) + start - 1 + index ) + '.'; break; case 'i': case 'lower-roman': symbol = toRoman( start + index ) + '.'; break; case 'I': case 'upper-roman': symbol = toRoman( start + index ).toUpperCase() + '.'; break; default: symbol = list.name == 'ul' ? '·' : ( start + index ) + '.'; } child.attributes[ 'cke-symbol' ] = symbol; child.attributes[ 'cke-list-level' ] = level; } ); } ); children = arrayTools.reduce( items, function( acc, listElement ) { var child = listElement.children[ 0 ]; if ( child && child.name && child.attributes.style && child.attributes.style.match( /mso-list:/i ) ) { Style.pushStylesLower( listElement, { 'list-style-type': true, 'display': true } ); var childStyle = tools.parseCssText( child.attributes.style, true ); Style.setStyle( listElement, 'mso-list', childStyle[ 'mso-list' ], true ); Style.setStyle( child, 'mso-list', '' ); // mso-list takes precedence in determining the level. delete listElement[ 'cke-list-level' ]; // If this style has a value it's usually "none". This marks such list elements for deletion. var styleName = childStyle.display ? 'display' : childStyle.DISPLAY ? 'DISPLAY' : ''; if ( styleName ) { Style.setStyle( listElement, 'display', childStyle[ styleName ], true ); } } // Don't include elements put there only to contain another list. if ( listElement.children.length === 1 && isList( listElement.children[ 0 ] ) ) { return acc; } listElement.name = 'p'; listElement.attributes[ 'cke-dissolved' ] = true; acc.push( listElement ); return acc; }, [] ); for ( i = children.length - 1; i >= 0; i-- ) { children[ i ].insertAfter( element ); } for ( i = lists.length - 1; i >= 0; i-- ) { delete lists[ i ].name; } function toRoman( number ) { if ( number >= 50 ) return 'l' + toRoman( number - 50 ); if ( number >= 40 ) return 'xl' + toRoman( number - 40 ); if ( number >= 10 ) return 'x' + toRoman( number - 10 ); if ( number == 9 ) return 'ix'; if ( number >= 5 ) return 'v' + toRoman( number - 5 ); if ( number == 4 ) return 'iv'; if ( number >= 1 ) return 'i' + toRoman( number - 1 ); return ''; } function countParents( condition, element ) { return count( element, 0 ); function count( parent, number ) { if ( !parent || !parent.parent ) { return number; } if ( condition( parent.parent ) ) { return count( parent.parent, number + 1 ); } else { return count( parent.parent, number ); } } } }, groupLists: function( listElements ) { // Chop data into continuous lists. var i, element, lists = [ [ listElements[ 0 ] ] ], lastList = lists[ 0 ]; element = listElements[ 0 ]; element.attributes[ 'cke-indentation' ] = element.attributes[ 'cke-indentation' ] || List.getElementIndentation( element ); for ( i = 1; i < listElements.length; i++ ) { element = listElements[ i ]; var previous = listElements[ i - 1 ]; element.attributes[ 'cke-indentation' ] = element.attributes[ 'cke-indentation' ] || List.getElementIndentation( element ); if ( element.previous !== previous ) { List.chopDiscontinuousLists( lastList, lists ); lists.push( lastList = [] ); } lastList.push( element ); } List.chopDiscontinuousLists( lastList, lists ); return lists; }, /** * Converts a single, flat list items array into an array with a hierarchy of items. * * As the list gets chopped, it will be forced to render as a separate list, even if it has a deeper nesting level. * For example, for level 3 it will create a structure like `ol > li > ol > li > ol > li`. * * Note that list items within a single list but with different levels that did not get chopped * will still be rendered as a list tree later. * * @private * @param {CKEDITOR.htmlParser.element[]} list An array containing list items. * @param {CKEDITOR.htmlParser.element[]} lists All the lists in the pasted content represented by an array of arrays * of list items. Modified by this method. * @member CKEDITOR.plugins.pastefromword.lists */ chopDiscontinuousLists: function( list, lists ) { var levelSymbols = {}; var choppedLists = [ [] ], lastListInfo; for ( var i = 0; i < list.length; i++ ) { var lastSymbol = levelSymbols[ list[ i ].attributes[ 'cke-list-level' ] ], currentListInfo = this.getListItemInfo( list[ i ] ), currentSymbol, forceType; if ( lastSymbol ) { // An "h" before an "i". forceType = lastSymbol.type.match( /alpha/ ) && lastSymbol.index == 7 ? 'alpha' : forceType; // An "n" before an "o". forceType = list[ i ].attributes[ 'cke-symbol' ] == 'o' && lastSymbol.index == 14 ? 'alpha' : forceType; currentSymbol = List.getSymbolInfo( list[ i ].attributes[ 'cke-symbol' ], forceType ); currentListInfo = this.getListItemInfo( list[ i ] ); // Based on current and last index we'll decide if we want to chop list. if ( // If the last list was a different list type then chop it! lastSymbol.type != currentSymbol.type || // If those are logically different lists, and current list is not a continuation (#7918): ( lastListInfo && currentListInfo.id != lastListInfo.id && !this.isAListContinuation( list[ i ] ) ) ) { choppedLists.push( [] ); } } else { currentSymbol = List.getSymbolInfo( list[ i ].attributes[ 'cke-symbol' ] ); } // Reset all higher levels for ( var j = parseInt( list[ i ].attributes[ 'cke-list-level' ], 10 ) + 1; j < 20; j++ ) { if ( levelSymbols[ j ] ) { delete levelSymbols[ j ]; } } levelSymbols[ list[ i ].attributes[ 'cke-list-level' ] ] = currentSymbol; choppedLists[ choppedLists.length - 1 ].push( list[ i ] ); lastListInfo = currentListInfo; } [].splice.apply( lists, [].concat( [ tools.indexOf( lists, list ), 1 ], choppedLists ) ); }, /** * Checks if this list is a direct continuation of a list interrupted by a list with a different ID, * with a different level. So if you look at a following list: * * * list1 level1 * * list1 level1 * * list2 level2 * * list2 level2 * * list1 level1 * * It would return `true` &mdash; meaning it is a continuation, and should not be chopped. However, if any paragraph or * anything else appears in between, it should be broken into different lists. * * You can see fixtures from issue #7918 as an example. * * @private * @param {CKEDITOR.htmlParser.element} listElement The list to be checked. * @returns {Boolean} * @member CKEDITOR.plugins.pastefromword.lists */ isAListContinuation: function( listElement ) { var prev = listElement; do { prev = prev.previous; if ( prev && prev.type === CKEDITOR.NODE_ELEMENT ) { if ( prev.attributes[ 'cke-list-level' ] === undefined ) { // Not a list, so looks like an interrupted list. return false; } if ( prev.attributes[ 'cke-list-level' ] === listElement.attributes[ 'cke-list-level' ] ) { // Same level, so we want to check if this is a continuation. return prev.attributes[ 'cke-list-id' ] === listElement.attributes[ 'cke-list-id' ]; } } } while ( prev ); return false; }, getElementIndentation: function( element ) { var style = tools.parseCssText( element.attributes.style ); if ( style.margin || style.MARGIN ) { style.margin = style.margin || style.MARGIN; var fakeElement = { styles: { margin: style.margin } }; CKEDITOR.filter.transformationsTools.splitMarginShorthand( fakeElement ); style[ 'margin-left' ] = fakeElement.styles[ 'margin-left' ]; } return parseInt( tools.convertToPx( style[ 'margin-left' ] || '0px' ), 10 ); }, // Source: http://stackoverflow.com/a/17534350/3698944 toArabic: function( symbol ) { if ( !symbol.match( /[ivxl]/i ) ) return 0; if ( symbol.match( /^l/i ) ) return 50 + List.toArabic( symbol.slice( 1 ) ); if ( symbol.match( /^lx/i ) ) return 40 + List.toArabic( symbol.slice( 1 ) ); if ( symbol.match( /^x/i ) ) return 10 + List.toArabic( symbol.slice( 1 ) ); if ( symbol.match( /^ix/i ) ) return 9 + List.toArabic( symbol.slice( 2 ) ); if ( symbol.match( /^v/i ) ) return 5 + List.toArabic( symbol.slice( 1 ) ); if ( symbol.match( /^iv/i ) ) return 4 + List.toArabic( symbol.slice( 2 ) ); if ( symbol.match( /^i/i ) ) return 1 + List.toArabic( symbol.slice( 1 ) ); // Ignore other characters. return List.toArabic( symbol.slice( 1 ) ); }, /** * Returns an object describing the given `symbol`. * * @private * @param {String} symbol * @param {String} type * @returns {Object} ret * @returns {Number} ret.index Identified numbering value * @returns {String} ret.type One of `decimal`, `disc`, `circle`, `square`, `roman`, `alpha`. * @member CKEDITOR.plugins.pastefromword.lists */ getSymbolInfo: function( symbol, type ) { var symbolCase = symbol.toUpperCase() == symbol ? 'upper-' : 'lower-', symbolMap = { '·': [ 'disc', -1 ], 'o': [ 'circle', -2 ], '§': [ 'square', -3 ] }; if ( symbol in symbolMap || ( type && type.match( /(disc|circle|square)/ ) ) ) { return { index: symbolMap[ symbol ][ 1 ], type: symbolMap[ symbol ][ 0 ] }; } if ( symbol.match( /\d/ ) ) { return { index: symbol ? parseInt( List.getSubsectionSymbol( symbol ) , 10 ) : 0, type: 'decimal' }; } symbol = symbol.replace( /\W/g, '' ).toLowerCase(); if ( ( !type && symbol.match( /[ivxl]+/i ) ) || ( type && type != 'alpha' ) || type == 'roman' ) { return { index: List.toArabic( symbol ), type: symbolCase + 'roman' }; } if ( symbol.match( /[a-z]/i ) ) { return { index: symbol.charCodeAt( 0 ) - 97, type: symbolCase + 'alpha' }; } return { index: -1, type: 'disc' }; }, /** * Returns Word-generated information about the given list item, mainly by parsing the `mso-list` * CSS property. * * Note: Paragraphs with `mso-list` are also counted as list items because Word serves * list items as paragraphs. * * @private * @param {CKEDITOR.htmlParser.element} list * @returns ret * @returns {String} ret.id List ID. Usually it is a decimal string. * @returns {String} ret.level List nesting level, `0` means it is the outermost list. Usually it is * a decimal string. * @member CKEDITOR.plugins.pastefromword.lists */ getListItemInfo: function( list ) { if ( list.attributes[ 'cke-list-id' ] !== undefined ) { // List was already resolved. return { id: list.attributes[ 'cke-list-id' ], level: list.attributes[ 'cke-list-level' ] }; } var propValue = tools.parseCssText( list.attributes.style )[ 'mso-list' ], ret = { id: '0', level: '1' }; if ( propValue ) { // Add one whitespace so it's easier to match values assuming that all of these are separated with \s. propValue += ' '; ret.level = propValue.match( /level(.+?)\s+/ )[ 1 ]; ret.id = propValue.match( /l(\d+?)\s+/ )[ 1 ]; } // Store values. List level will be reused if present to prevent regressions. list.attributes[ 'cke-list-level' ] = list.attributes[ 'cke-list-level' ] !== undefined ? list.attributes[ 'cke-list-level' ] : ret.level; list.attributes[ 'cke-list-id' ] = ret.id; return ret; } }; List = CKEDITOR.plugins.pastefromword.lists; /** * Namespace containing methods used to process the pasted content using heuristics. * * @private * @since 4.6.2 * @member CKEDITOR.plugins.pastefromword */ CKEDITOR.plugins.pastefromword.heuristics = { /** * Decides if an `item` looks like a list item in Microsoft Edge. * * Note: It will return `false` when run in a browser other than Microsoft Edge, despite the configuration. * * @param {CKEDITOR.editor} item * @param {CKEDITOR.htmlParser.element} item * @return {Boolean} * @member CKEDITOR.plugins.pastefromword.heuristics * @private */ isEdgeListItem: function( editor, item ) { if ( !CKEDITOR.env.edge || !editor.config.pasteFromWord_heuristicsEdgeList ) { return false; } return item.attributes.style && !item.attributes.style.match( /mso\-list/ ) && !!item.find( function( child ) { var css = tools.parseCssText( child.attributes && child.attributes.style ); if ( !css ) { return false; } var fontSize = css.font || css['font-size'] || '', fontFamily = css[ 'font-family' ] || ''; return ( fontSize.match( /7pt/i ) && !!child.previous ) || fontFamily.match( /symbol/i ); }, true ).length; }, /** * Assigns list levels to the `item` and all directly subsequent nodes for which {@link #isEdgeListItem} returns `true`. * * The algorithm determines list item level based on the lowest common non-zero difference in indentation * of two or more subsequent list-like elements. * * @param {CKEDITOR.editor} editor * @param {CKEDITOR.htmlParser.element} item The first item of the list. * @returns {Object/null} `null` if list levels were already applied, or an object used to verify results in tests. * @returns {Number[]} return.indents * @returns {Number[]} return.levels * @returns {Number[]} return.diffs * @member CKEDITOR.plugins.pastefromword.heuristics * @private */ assignListLevels: function( editor, item ) { // If levels were already calculated, it means that this function was called for preceeding element. There's // no need to do this heavy work. if ( item.attributes && item.attributes[ 'cke-list-level' ] !== undefined ) { return; } var indents = [ List.getElementIndentation( item ) ], items = [ item ], levels = [], array = CKEDITOR.tools.array, map = array.map; while ( item.next && item.next.attributes && !item.next.attributes[ 'cke-list-level' ] && Heuristics.isEdgeListItem( editor, item.next ) ) { item = item.next; indents.push( List.getElementIndentation( item ) ); items.push( item ); } // An array with indentation difference between n and n-1 list item. It's 0 for the first one. var indentationDiffs = map( indents, function( curIndent, i ) { return i === 0 ? 0 : curIndent - indents[ i - 1 ]; } ), // Guess indentation step, but it must not be equal to 0. indentationPerLevel = this.guessIndentationStep( array.filter( indents, function( val ) { return val !== 0; } ) ); // Here's the tricky part, we need to magically figure out what is the indentation difference between list level. levels = map( indents, function( val ) { // Make sure that the level is a full number. return Math.round( val / indentationPerLevel ); } ); // Level can not be equal to 0, in case if it happens bump all the levels by 1, if ( array.indexOf( levels, 0 ) !== -1 ) { levels = map( levels, function( val ) { return val + 1; } ); } // Assign levels to a proper place. array.forEach( items, function( curItem, index ) { curItem.attributes[ 'cke-list-level' ] = levels[ index ]; } ); return { indents: indents, levels: levels, diffs: indentationDiffs }; }, /** * Given an array of list indentations, this method tries to guess what the indentation difference per list level is. * E.g. assuming that you have something like: * * * foo (indentation 30px) * * bar (indentation 90px) * * baz (indentation 90px) * * baz (indentation 115px) * * baz (indentation 60px) * * The method will return `30`. * * @param {Number[]} indentations An array of indentation sizes. * @returns {Number/null} A number or `null` if empty `indentations` was given. * @member CKEDITOR.plugins.pastefromword.heuristics * @private */ guessIndentationStep: function( indentations ) { return indentations.length ? Math.min.apply( null, indentations ) : null; } }; Heuristics = CKEDITOR.plugins.pastefromword.heuristics; // Expose this function since it's useful in other places. List.setListSymbol.removeRedundancies = function( style, level ) { // 'disc' and 'decimal' are the default styles in some cases - remove redundancy. if ( ( level === 1 && style[ 'list-style-type' ] === 'disc' ) || style[ 'list-style-type' ] === 'decimal' ) { delete style[ 'list-style-type' ]; } }; function falseIfEmpty( value ) { if ( value === '' ) { return false; } return value; } // Used when filtering attributes - returning false deletes the attribute. function remove() { return false; } // Same as createStyleStack, but instead of styles - stack attributes. function createAttributeStack( element, filter ) { var i, children = []; element.filterChildren( filter ); // Store element's children somewhere else. for ( i = element.children.length - 1; i >= 0; i-- ) { children.unshift( element.children[ i ] ); element.children[ i ].remove(); } // Create a stack of spans with each containing one style. var attributes = element.attributes, innermostElement = element, topmost = true; for ( var attribute in attributes ) { if ( topmost ) { topmost = false; continue; } var newElement = new CKEDITOR.htmlParser.element( element.name ); newElement.attributes[ attribute ] = attributes[ attribute ]; innermostElement.add( newElement ); innermostElement = newElement; delete attributes[ attribute ]; } // Add the stored children to the innermost span. for ( i = 0; i < children.length; i++ ) { innermostElement.add( children[ i ] ); } } CKEDITOR.plugins.pastefromword.createAttributeStack = createAttributeStack; /** * Numbering helper. * * @property {CKEDITOR.plugins.pastefromword.lists.numbering} numbering * @member CKEDITOR.plugins.pastefromword.lists */ /** * Whether to ignore all font-related formatting styles, including: * * * font size; * * font family; * * font foreground and background color. * * config.pasteFromWordRemoveFontStyles = true; * * **Important note:** Prior to version 4.6.0 this configuration option defaulted to `true`. * * @deprecated 4.6.0 Either configure proper [Advanced Content Filter](#!/guide/dev_advanced_content_filter) for the editor * or use the {@link CKEDITOR.editor#afterPasteFromWord} event. * @since 3.1 * @cfg {Boolean} [pasteFromWordRemoveFontStyles=false] * @member CKEDITOR.config */ /** * Whether to transform Microsoft Word outline numbered headings into lists. * * config.pasteFromWordNumberedHeadingToList = true; * * @removed 4.6.0 * @since 3.1 * @cfg {Boolean} [pasteFromWordNumberedHeadingToList=false] * @member CKEDITOR.config */ /** * Whether to remove element styles that cannot be managed with the editor. Note * that this option does not handle font-specific styles, which depend on the * {@link #pasteFromWordRemoveFontStyles} setting instead. * * config.pasteFromWordRemoveStyles = false; * * @removed 4.6.0 * @since 3.1 * @cfg {Boolean} [pasteFromWordRemoveStyles=true] * @member CKEDITOR.config */ /** * Activates a heuristic that helps detect lists pasted into the editor in Microsoft Edge. * * The reason why this heuristic is needed is that on pasting Microsoft Edge removes any Word-specific * metadata allowing to identify lists. * * // Disables list heuristics for Edge. * config.pasteFromWord_heuristicsEdgeList = false; * * @since 4.6.2 * @cfg {Boolean} [pasteFromWord_heuristicsEdgeList=true] * @member CKEDITOR.config */ CKEDITOR.config.pasteFromWord_heuristicsEdgeList = true; } )();