Skip to content
Permalink
Browse files

Merge branch 't/16682'

  • Loading branch information...
mlewand committed Dec 26, 2016
2 parents cebdb12 + 176daf8 commit 7f3050f0a8584e48edfd7ccded6a769c1f06d7e4
Showing with 2,629 additions and 13 deletions.
  1. +1 −0 CHANGES.md
  2. +39 −0 core/tools.js
  3. +166 −12 plugins/pastefromword/filter/default.js
  4. +48 −1 tests/core/tools/array.js
  5. +16 −0 tests/plugins/pastefromword/generated/_fixtures/No_heuristics/Ordered_list/word2016/edge.html
  6. +51 −0 .../plugins/pastefromword/generated/_fixtures/No_heuristics/Ordered_list/word2016/expected_edge.html
  7. +24 −0 .../plugins/pastefromword/generated/_fixtures/No_heuristics/Ordered_list_multiple/word2016/edge.html
  8. +161 −0 ...pastefromword/generated/_fixtures/No_heuristics/Ordered_list_multiple/word2016/expected_edge.html
  9. +120 −0 ...astefromword/generated/_fixtures/No_heuristics/Tickets/16745MixedListsAndParagraphs/expected.html
  10. +87 −0 ...mword/generated/_fixtures/No_heuristics/Tickets/16745MixedListsAndParagraphs/word2016/chrome.html
  11. +39 −0 ...romword/generated/_fixtures/No_heuristics/Tickets/16745MixedListsAndParagraphs/word2016/edge.html
  12. +50 −0 ...enerated/_fixtures/No_heuristics/Tickets/16745MixedListsAndParagraphs/word2016/expected_edge.html
  13. +23 −0 tests/plugins/pastefromword/generated/_fixtures/No_heuristics/Unordered_list/word2016/edge.html
  14. +147 −0 ...lugins/pastefromword/generated/_fixtures/No_heuristics/Unordered_list/word2016/expected_edge.html
  15. +23 −0 ...lugins/pastefromword/generated/_fixtures/No_heuristics/Unordered_list_multiple/word2016/edge.html
  16. +147 −0 ...stefromword/generated/_fixtures/No_heuristics/Unordered_list_multiple/word2016/expected_edge.html
  17. +16 −0 tests/plugins/pastefromword/generated/_fixtures/Ordered_list/word2016/edge.html
  18. +51 −0 tests/plugins/pastefromword/generated/_fixtures/Ordered_list/word2016/expected_edge.html
  19. +24 −0 tests/plugins/pastefromword/generated/_fixtures/Ordered_list_multiple/word2016/edge.html
  20. +55 −0 tests/plugins/pastefromword/generated/_fixtures/Ordered_list_multiple/word2016/expected_edge.html
  21. BIN tests/plugins/pastefromword/generated/_fixtures/Tickets/16682listWithMargin/16682listWithMargin.docx
  22. +16 −0 tests/plugins/pastefromword/generated/_fixtures/Tickets/16682listWithMargin/expected.html
  23. +48 −0 tests/plugins/pastefromword/generated/_fixtures/Tickets/16682listWithMargin/word2016/chrome.html
  24. +25 −0 tests/plugins/pastefromword/generated/_fixtures/Tickets/16682listWithMargin/word2016/edge.html
  25. +56 −0 ...plugins/pastefromword/generated/_fixtures/Tickets/16682listWithMargin/word2016/expected_edge.html
  26. BIN tests/plugins/pastefromword/generated/_fixtures/Tickets/16682noIndentation/16682noIndentation.docx
  27. +14 −0 tests/plugins/pastefromword/generated/_fixtures/Tickets/16682noIndentation/expected.html
  28. +44 −0 tests/plugins/pastefromword/generated/_fixtures/Tickets/16682noIndentation/word2016/chrome.html
  29. +24 −0 tests/plugins/pastefromword/generated/_fixtures/Tickets/16682noIndentation/word2016/edge.html
  30. +62 −0 .../plugins/pastefromword/generated/_fixtures/Tickets/16682noIndentation/word2016/expected_edge.html
  31. BIN ...stefromword/generated/_fixtures/Tickets/16745MixedListsAndParagraphs/MixedListsAndParagraphs.docx
  32. +120 −0 tests/plugins/pastefromword/generated/_fixtures/Tickets/16745MixedListsAndParagraphs/expected.html
  33. +87 −0 ...ugins/pastefromword/generated/_fixtures/Tickets/16745MixedListsAndParagraphs/word2016/chrome.html
  34. +39 −0 ...plugins/pastefromword/generated/_fixtures/Tickets/16745MixedListsAndParagraphs/word2016/edge.html
  35. +47 −0 ...astefromword/generated/_fixtures/Tickets/16745MixedListsAndParagraphs/word2016/expected_edge.html
  36. +23 −0 tests/plugins/pastefromword/generated/_fixtures/Unordered_list/word2016/edge.html
  37. +51 −0 tests/plugins/pastefromword/generated/_fixtures/Unordered_list/word2016/expected_edge.html
  38. BIN ...tefromword/generated/_fixtures/Unordered_list_adjusted_margin/Unordered_list_adjusted_margin.docx
  39. +25 −0 tests/plugins/pastefromword/generated/_fixtures/Unordered_list_adjusted_margin/expected.html
  40. +51 −0 tests/plugins/pastefromword/generated/_fixtures/Unordered_list_adjusted_margin/word2016/chrome.html
  41. +29 −0 tests/plugins/pastefromword/generated/_fixtures/Unordered_list_adjusted_margin/word2016/edge.html
  42. +76 −0 ...gins/pastefromword/generated/_fixtures/Unordered_list_adjusted_margin/word2016/expected_edge.html
  43. +51 −0 tests/plugins/pastefromword/generated/_fixtures/Unordered_list_adjusted_margin/word2016/firefox.html
  44. +23 −0 tests/plugins/pastefromword/generated/_fixtures/Unordered_list_multiple/word2016/edge.html
  45. +55 −0 tests/plugins/pastefromword/generated/_fixtures/Unordered_list_multiple/word2016/expected_edge.html
  46. +120 −0 tests/plugins/pastefromword/generated/heuristics.html
  47. +81 −0 tests/plugins/pastefromword/generated/heuristics.js
  48. +63 −0 tests/plugins/pastefromword/generated/tickets6.js
  49. +61 −0 tests/plugins/pastefromword/generated/tickets7.js
  50. +14 −0 tests/plugins/pastefromword/manual/configheuristicsedgelist.html
  51. +13 −0 tests/plugins/pastefromword/manual/configheuristicsedgelist.md
  52. +12 −0 tests/plugins/pastefromword/manual/edgelists.html
  53. +11 −0 tests/plugins/pastefromword/manual/edgelists.md
@@ -9,6 +9,7 @@ New Features:

Fixed Issues:

* [#16682](http://dev.ckeditor.com/ticket/16682): [Edge] Fixed: List gets pasted as a set of paragraphs.
* [#10373](http://dev.ckeditor.com/ticket/10373): Fixed: Context menu items can be dragged into the editor.
* [#16728](http://dev.ckeditor.com/ticket/16728): [IE] Fixed: [Copy Formatting](http://ckeditor.com/addon/copyformatting) breaks editor in Quirks Mode.
* [#16753](http://dev.ckeditor.com/ticket/16753): Fixed: `element.setSize` sets wrong editor's dimensions if border's width is in fraction of pixels.
@@ -1807,6 +1807,45 @@
for ( i = 0; i < len; i++ ) {
fn.call( thisArg, array[ i ], i, array );
}
},

/**
* Applies a function to each element of an array and returns the array of results in the same order.
* Note the order of the parameters.
*
* @param {Array} array Array over elements of which `fn` is applied.
* @param {Function} fn A function with the signature `a -> b`.
* @param {Mixed} [thisArg=undefined] The context object for `fn`.
* @returns {Array} Array of mapped elements.
* @member CKEDITOR.tools.array
* @since 4.6.2
*/
map: function( array, fn, thisArg ) {
var result = [];
for ( var i = 0; i < array.length; i++ ) {
result.push( fn.call( thisArg, array[ i ], i, array ) );
}
return result;
},

/**
* Applies a function against each value in an array storing the result in an accumulator passed to the next iteration.
* Note the order of the parameters.
*
* @param {Array} array Array over elements of which `fn` is applied.
* @param {Function} fn A function with the signature `(accumulator, a, index, array) -> b`.
* @param {Mixed} initial Initial value of the accumulator.
* @param {Mixed} [thisArg=undefined] The context object for `fn`.
* @returns {Mixed} Final value of the accumulator.
* @member CKEDITOR.tools.array
* @since 4.6.2
*/
reduce: function( array, fn, initial, thisArg ) {
var acc = initial;
for ( var i = 0; i < array.length; i++ ) {
acc = fn.call( thisArg, acc, array[ i ], i, array );
}
return acc;
}
}
};
@@ -6,7 +6,10 @@
/* globals CKEDITOR */

( function() {
var List, Style, filter,
var List,
Style,
Heuristics,
filter,
tools = CKEDITOR.tools,
invalidTags = [
'o:p',
@@ -109,8 +112,8 @@
return false;
}

if ( List.thisIsAListItem( element ) ) {
List.convertToFakeListItem( element );
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 ) {
@@ -128,12 +131,12 @@
Style.createStyleStack( element, filter, editor );
},
'pre': function( element ) {
if ( List.thisIsAListItem( element ) ) List.convertToFakeListItem( element );
if ( List.thisIsAListItem( editor, element ) ) List.convertToFakeListItem( editor, element );

Style.createStyleStack( element, filter, editor );
},
'h1': function( element ) {
if ( List.thisIsAListItem( element ) ) List.convertToFakeListItem( element );
if ( List.thisIsAListItem( editor, element ) ) List.convertToFakeListItem( editor, element );

Style.createStyleStack( element, filter, editor );
},
@@ -619,11 +622,16 @@
* 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( element ) {
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>.
@@ -634,9 +642,7 @@
// 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,666}/ )
element.getHtml().match( /^( )*.*?[\.\)] ( ){2,700}/ )
) {
return true;
}
@@ -649,10 +655,15 @@
* 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( element ) {
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 );

@@ -689,7 +700,6 @@
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
@@ -990,7 +1000,6 @@
return [];
}


// Chop data into continuous lists.
var lists = List.groupLists( listElements );

@@ -1566,6 +1575,136 @@
};
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 = {
/**
* Tells if `item` looks like list item in Microsoft Edge.
*
* Note: It will return `false` when run on 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 `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 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 ( levels.indexOf( 0 ) !== -1 ) {
levels = map( levels, function( val ) {
return val + 1;
} );
}

// Assign levels to a proper place.
items.forEach( function( curItem, index ) {
curItem.attributes[ 'cke-list-level' ] = levels[ index ];
} );

return {
indents: indents,
levels: levels,
diffs: indentationDiffs
};
},

/**
* Given array of list indentations, tries tu guess what is the indentation difference per list level. E.g. assuming that we
* 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 Array of indentation sizes.
* @returns {Number/null} 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.
@@ -1677,4 +1816,19 @@
* @cfg {Boolean} [pasteFromWordRemoveStyles=true]
* @member CKEDITOR.config
*/

/**
* Activates a heuristic that helps detecting lists pasted to editor in Microsoft Edge.
*
* The reason why this heuristic is needed is that when pasting Microsoft Edge will remove 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;
} )();
@@ -80,7 +80,54 @@
assert.isFalse( isArray( { length: 0 } ), 'Case 3' );
assert.isFalse( isArray( 'asd' ), 'Case 4' );
assert.isTrue( isArray( [ 1, 2 ] ), 'Case 5' );
},

'test array.map': function() {
arrayAssert.itemsAreSame( [ 2, 4, 6 ], this.array.map( [ 1, 2, 3 ], function( a ) {
return a * 2;
} ) );

arrayAssert.itemsAreSame( [], this.array.map( [], function( a ) {
return a * 2;
} ) );

arrayAssert.itemsAreSame( [ 12, 10, 6 ], this.array.map( [ 3, 2, 1 ], function( a, i ) {
return a * this[ i ];
}, [ 4, 5, 6 ] ) );
},

'test array.map does not modify input array': function() {
var arr = [ 8, 4 ],
ret = this.array.map( arr, function() {
return 'a';
} );

// Make sure it returned a different array.
assert.areNotSame( arr, ret, 'Input arr was not modified' );
},

'test array.reduce': function() {
assert.areSame( 6, this.array.reduce( [ 1, 2, 3 ], function( acc, a ) {
return acc + a;
}, 0 ) );

assert.areSame( 9, this.array.reduce( [], function( acc, a ) {
return acc + a;
}, 9 ) );

arrayAssert.itemsAreSame( [ 4, 5 ], this.array.reduce( [ 4, 5, 6, 7 ], function( acc, a, i ) {
if ( this[ i ] ) {
acc.push( a );
}
return acc;
}, [], [ true, true, false, false ] ) );

arrayAssert.itemsAreSame( [ 1, 0, 2, 1, 3, 2 ], this.array.reduce( [ 1, 2, 3, 4, 5 ], function( acc, a ) {
acc.push( a - acc[ acc.length - 1 ] );

return acc;
}, [ 1 ] ) );
}
} );

} )();
} )();
@@ -0,0 +1,16 @@


<p style="margin: 0px 0px 11px;"><font color="#000000" face="Calibri" size="3">1.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;This</font></p>

<p style="margin: 0px 0px 11px;"><font color="#000000" face="Calibri" size="3">2.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Is</font></p>

<p style="margin: 0px 0px 11px;"><font color="#000000" face="Calibri" size="3">3.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A</font></p>

<p style="margin: 0px 0px 11px;"><font color="#000000" face="Calibri" size="3">4.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;One</font></p>

<p style="margin: 0px 0px 11px;"><font color="#000000" face="Calibri" size="3">5.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Level</font></p>

<p style="margin: 0px 0px 11px;"><font color="#000000" face="Calibri" size="3">6.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ordered</font></p>

<p style="margin: 0px 0px 11px;"><font color="#000000" face="Calibri" size="3">7.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List</font></p>

0 comments on commit 7f3050f

Please sign in to comment.
You can’t perform that action at this time.