Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 8102de3

Browse files
authored
Merge pull request #62 from ckeditor/t/61
Feature: Prevent of bolding entire content pasted from Google Docs. Closes #61.
2 parents 70fe6fd + ee7e3a0 commit 8102de3

31 files changed

+770
-394
lines changed

src/filters/removeboldwrapper.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4+
*/
5+
6+
/**
7+
* @module paste-from-office/filters/removeboldwrapper
8+
*/
9+
10+
/**
11+
* Removes `<b>` tag wrapper added by Google Docs to a copied content.
12+
*
13+
* @param {module:engine/view/documentfragment~DocumentFragment} documentFragment element `data.content` obtained from clipboard
14+
* @param {module:engine/view/upcastwriter~UpcastWriter} writer
15+
*/
16+
export default function removeBoldWrapper( documentFragment, writer ) {
17+
for ( const child of documentFragment.getChildren() ) {
18+
if ( child.is( 'b' ) && child.getStyle( 'font-weight' ) === 'normal' ) {
19+
const childIndex = documentFragment.getChildIndex( child );
20+
21+
writer.remove( child );
22+
writer.insertChild( childIndex, child.getChildren(), documentFragment );
23+
}
24+
}
25+
}

src/normalizer.jsdoc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4+
*/
5+
6+
/**
7+
* @module paste-from-office/normalizer
8+
*/
9+
10+
/**
11+
* Interface defining a content transformation pasted from an external editor.
12+
*
13+
* Normalizers are registered by the {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin and run on
14+
* {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. They detect environment-specific
15+
* quirks and transform it into a form compatible with other CKEditor features.
16+
*
17+
* @interface Normalizer
18+
*/
19+
20+
/**
21+
* Must return `true` if the `htmlString` contains content which this normalizer can transform.
22+
*
23+
* @method #isActive
24+
* @param {String} htmlString full content of `dataTransfer.getData( 'text/html' )`
25+
* @returns {Boolean}
26+
*/
27+
28+
/**
29+
* Executes the normalization of a given data.
30+
*
31+
* @method #execute
32+
* @param {Object} data object obtained from
33+
* {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}.
34+
*/
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4+
*/
5+
6+
/**
7+
* @module paste-from-office/normalizers/googledocsnormalizer
8+
*/
9+
10+
import removeBoldWrapper from '../filters/removeboldwrapper';
11+
import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';
12+
13+
const googleDocsMatch = /id=("|')docs-internal-guid-[-0-9a-f]+("|')/i;
14+
15+
/**
16+
* Normalizer for the content pasted from Google Docs.
17+
*
18+
* @implements module:paste-from-office/normalizer~Normalizer
19+
*/
20+
export default class GoogleDocsNormalizer {
21+
/**
22+
* @inheritDoc
23+
*/
24+
isActive( htmlString ) {
25+
return googleDocsMatch.test( htmlString );
26+
}
27+
28+
/**
29+
* @inheritDoc
30+
*/
31+
execute( data ) {
32+
const writer = new UpcastWriter();
33+
34+
removeBoldWrapper( data.content, writer );
35+
}
36+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4+
*/
5+
6+
/**
7+
* @module paste-from-office/normalizers/mswordnormalizer
8+
*/
9+
10+
import { parseHtml } from '../filters/parse';
11+
import { transformListItemLikeElementsIntoLists } from '../filters/list';
12+
import { replaceImagesSourceWithBase64 } from '../filters/image';
13+
14+
const msWordMatch1 = /<meta\s*name="?generator"?\s*content="?microsoft\s*word\s*\d+"?\/?>/i;
15+
const msWordMatch2 = /xmlns:o="urn:schemas-microsoft-com/i;
16+
17+
/**
18+
* Normalizer for the content pasted from Microsoft Word.
19+
*
20+
* @implements module:paste-from-office/normalizer~Normalizer
21+
*/
22+
export default class MSWordNormalizer {
23+
/**
24+
* @inheritDoc
25+
*/
26+
isActive( htmlString ) {
27+
return msWordMatch1.test( htmlString ) || msWordMatch2.test( htmlString );
28+
}
29+
30+
/**
31+
* @inheritDoc
32+
*/
33+
execute( data ) {
34+
const { body, stylesString } = parseHtml( data.dataTransfer.getData( 'text/html' ) );
35+
36+
transformListItemLikeElementsIntoLists( body, stylesString );
37+
replaceImagesSourceWithBase64( body, data.dataTransfer.getData( 'text/rtf' ) );
38+
39+
data.content = body;
40+
}
41+
}

src/pastefromoffice.js

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,21 @@
99

1010
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
1111

12-
import { parseHtml } from './filters/parse';
13-
import { transformListItemLikeElementsIntoLists } from './filters/list';
14-
import { replaceImagesSourceWithBase64 } from './filters/image';
12+
import GoogleDocsNormalizer from './normalizers/googledocsnormalizer';
13+
import MSWordNormalizer from './normalizers/mswordnormalizer';
14+
import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
1515

1616
/**
1717
* The Paste from Office plugin.
1818
*
19-
* This plugin handles content pasted from Office apps (for now only Word) and transforms it (if necessary)
19+
* This plugin handles content pasted from Office apps and transforms it (if necessary)
2020
* to a valid structure which can then be understood by the editor features.
2121
*
22+
* Transformation is made by a set of predefined {@link module:paste-from-office/normalizer~Normalizer normalizers}.
23+
* This plugin includes following normalizers:
24+
* * {@link module:paste-from-office/normalizer/mswordnormalizer~MSWordNormalizer Microsoft Word normalizer}
25+
* * {@link module:paste-from-office/normalizer/googledocsnormalizer~GoogleDocsNormalizer Google Docs normalizer}
26+
*
2227
* For more information about this feature check the {@glink api/paste-from-office package page}.
2328
*
2429
* @extends module:core/plugin~Plugin
@@ -31,49 +36,40 @@ export default class PasteFromOffice extends Plugin {
3136
return 'PasteFromOffice';
3237
}
3338

39+
/**
40+
* @inheritDoc
41+
*/
42+
static get requires() {
43+
return [ Clipboard ];
44+
}
45+
3446
/**
3547
* @inheritDoc
3648
*/
3749
init() {
3850
const editor = this.editor;
51+
const normalizers = [];
3952

40-
this.listenTo( editor.plugins.get( 'Clipboard' ), 'inputTransformation', ( evt, data ) => {
41-
const html = data.dataTransfer.getData( 'text/html' );
53+
normalizers.push( new MSWordNormalizer() );
54+
normalizers.push( new GoogleDocsNormalizer() );
4255

43-
if ( data.pasteFromOfficeProcessed !== true && isWordInput( html ) ) {
44-
data.content = this._normalizeWordInput( html, data.dataTransfer );
56+
editor.plugins.get( 'Clipboard' ).on(
57+
'inputTransformation',
58+
( evt, data ) => {
59+
if ( data.isTransformedWithPasteFromOffice ) {
60+
return;
61+
}
4562

46-
// Set the flag so if `inputTransformation` is re-fired, PFO will not process it again (#44).
47-
data.pasteFromOfficeProcessed = true;
48-
}
49-
}, { priority: 'high' } );
50-
}
51-
52-
/**
53-
* Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}.
54-
*
55-
* **Note**: this function was exposed mainly for testing purposes and should not be called directly.
56-
*
57-
* @protected
58-
* @param {String} input Word input.
59-
* @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance.
60-
* @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input.
61-
*/
62-
_normalizeWordInput( input, dataTransfer ) {
63-
const { body, stylesString } = parseHtml( input );
63+
const htmlString = data.dataTransfer.getData( 'text/html' );
64+
const activeNormalizer = normalizers.find( normalizer => normalizer.isActive( htmlString ) );
6465

65-
transformListItemLikeElementsIntoLists( body, stylesString );
66-
replaceImagesSourceWithBase64( body, dataTransfer.getData( 'text/rtf' ) );
66+
if ( activeNormalizer ) {
67+
activeNormalizer.execute( data );
6768

68-
return body;
69+
data.isTransformedWithPasteFromOffice = true;
70+
}
71+
},
72+
{ priority: 'high' }
73+
);
6974
}
7075
}
71-
72-
// Checks if given HTML string is a result of pasting content from Word.
73-
//
74-
// @param {String} html HTML string to test.
75-
// @returns {Boolean} True if given HTML string is a Word HTML.
76-
function isWordInput( html ) {
77-
return !!( html && ( html.match( /<meta\s*name="?generator"?\s*content="?microsoft\s*word\s*\d+"?\/?>/gi ) ||
78-
html.match( /xmlns:o="urn:schemas-microsoft-com/gi ) ) );
79-
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4+
*/
5+
6+
import simpleText from './simple-text/input.html';
7+
import simpleTextWindows from './simple-text-windows/input.html';
8+
9+
import simpleTextNormalized from './simple-text/normalized.html';
10+
import simpleTextWindowsNormalized from './simple-text-windows/normalized.html';
11+
12+
import simpleTextModel from './simple-text/model.html';
13+
import simpleTextWindowsModel from './simple-text-windows/model.html';
14+
15+
export const fixtures = {
16+
input: {
17+
simpleText,
18+
simpleTextWindows
19+
},
20+
normalized: {
21+
simpleText: simpleTextNormalized,
22+
simpleTextWindows: simpleTextWindowsNormalized
23+
},
24+
model: {
25+
simpleText: simpleTextModel,
26+
simpleTextWindows: simpleTextWindowsModel
27+
}
28+
};
29+
30+
import simpleTextFirefox from './simple-text/input.firefox.html';
31+
import simpleTextWindowsFirefox from './simple-text-windows/input.firefox.html';
32+
33+
import simpleTextNormalizedFirefox from './simple-text/normalized.firefox.html';
34+
import simpleTextWindowsNormalizedFirefox from './simple-text-windows/normalized.firefox.html';
35+
36+
export const browserFixtures = {
37+
firefox: {
38+
input: {
39+
simpleText: simpleTextFirefox,
40+
simpleTextWindows: simpleTextWindowsFirefox
41+
},
42+
normalized: {
43+
simpleText: simpleTextNormalizedFirefox,
44+
simpleTextWindows: simpleTextWindowsNormalizedFirefox
45+
},
46+
model: {
47+
simpleText: simpleTextModel,
48+
simpleTextWindows: simpleTextWindowsModel
49+
}
50+
}
51+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<html><body>
2+
<!--StartFragment--><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;" id="docs-internal-guid-f8b26bf1-7fff-40c0-af18-1234567890ab"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Hello world</span></p><!--EndFragment-->
3+
</body>
4+
</html>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<html>
2+
<body>
3+
<!--StartFragment--><b style="font-weight:normal;" id="docs-internal-guid-0954d9f2-7fff-2b3c-4978-1234567890ab"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Hello world</span></p></b><br class="Apple-interchange-newline"><!--EndFragment-->
4+
</body>
5+
</html>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<paragraph>Hello world</paragraph>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p dir="ltr" id="docs-internal-guid-f8b26bf1-7fff-40c0-af18-1234567890ab" style="line-height:1.38;margin-bottom:0pt;margin-top:0pt"><span style="background-color:transparent;color:#000000;font-family:Arial;font-size:11pt;font-style:normal;font-variant:normal;font-weight:400;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">Hello world</span></p>

0 commit comments

Comments
 (0)