/**
* Quirks.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Env from 'tinymce/core/api/Env';
import Tools from 'tinymce/core/api/util/Tools';
import Settings from '../api/Settings';
import Utils from './Utils';
import WordFilter from './WordFilter';
import { Editor } from 'tinymce/core/api/Editor';
/**
* This class contains various fixes for browsers. These issues can not be feature
* detected since we have no direct control over the clipboard. However we might be able
* to remove some of these fixes once the browsers gets updated/fixed.
*
* @class tinymce.pasteplugin.Quirks
* @private
*/
function addPreProcessFilter(editor: Editor, filterFunc) {
editor.on('PastePreProcess', function (e) {
e.content = filterFunc(editor, e.content, e.internal, e.wordContent);
});
}
function addPostProcessFilter(editor: Editor, filterFunc) {
editor.on('PastePostProcess', function (e) {
filterFunc(editor, e.node);
});
}
/**
* Removes BR elements after block elements. IE9 has a nasty bug where it puts a BR element after each
* block element when pasting from word. This removes those elements.
*
* This:
*
a
b
*
* Becomes:
* a
b
*/
function removeExplorerBrElementsAfterBlocks(editor: Editor, html: string) {
// Only filter word specific content
if (!WordFilter.isWordContent(html)) {
return html;
}
// Produce block regexp based on the block elements in schema
const blockElements = [];
Tools.each(editor.schema.getBlockElements(), function (block: Element, blockName: string) {
blockElements.push(blockName);
});
const explorerBlocksRegExp = new RegExp(
'(?:
[\\s\\r\\n]+|
)*(<\\/?(' + blockElements.join('|') + ')[^>]*>)(?:
[\\s\\r\\n]+|
)*',
'g'
);
// Remove BR:s from: X
html = Utils.filter(html, [
[explorerBlocksRegExp, '$1']
]);
// IE9 also adds an extra BR element for each soft-linefeed and it also adds a BR for each word wrap break
html = Utils.filter(html, [
[/
/g, '
'], // Replace multiple BR elements with uppercase BR to keep them intact
[/
/g, ' '], // Replace single br elements with space since they are word wrap BR:s
[/
/g, '
'] // Replace back the double brs but into a single BR
]);
return html;
}
/**
* WebKit has a nasty bug where the all computed styles gets added to style attributes when copy/pasting contents.
* This fix solves that by simply removing the whole style attribute.
*
* The paste_webkit_styles option can be set to specify what to keep:
* paste_webkit_styles: "none" // Keep no styles
* paste_webkit_styles: "all", // Keep all of them
* paste_webkit_styles: "font-weight color" // Keep specific ones
*/
function removeWebKitStyles(editor: Editor, content: string, internal: boolean, isWordHtml: boolean) {
// WordFilter has already processed styles at this point and internal doesn't need any processing
if (isWordHtml || internal) {
return content;
}
// Filter away styles that isn't matching the target node
const webKitStylesSetting = Settings.getWebkitStyles(editor);
let webKitStyles: string[] | string;
if (Settings.shouldRemoveWebKitStyles(editor) === false || webKitStylesSetting === 'all') {
return content;
}
if (webKitStylesSetting) {
webKitStyles = webKitStylesSetting.split(/[, ]/);
}
// Keep specific styles that doesn't match the current node computed style
if (webKitStyles) {
const dom = editor.dom, node = editor.selection.getNode();
content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, function (all, before, value, after) {
const inputStyles = dom.parseStyle(dom.decode(value));
let outputStyles = {};
if (webKitStyles === 'none') {
return before + after;
}
for (let i = 0; i < webKitStyles.length; i++) {
let inputValue = inputStyles[webKitStyles[i]], currentValue = dom.getStyle(node, webKitStyles[i], true);
if (/color/.test(webKitStyles[i])) {
inputValue = dom.toHex(inputValue as string);
currentValue = dom.toHex(currentValue);
}
if (currentValue !== inputValue) {
outputStyles[webKitStyles[i]] = inputValue;
}
}
outputStyles = dom.serializeStyle(outputStyles, 'span');
if (outputStyles) {
return before + ' style="' + outputStyles + '"' + after;
}
return before + after;
});
} else {
// Remove all external styles
content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, '$1$3');
}
// Keep internal styles
content = content.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi, function (all, before, value, after) {
return before + ' style="' + value + '"' + after;
});
return content;
}
function removeUnderlineAndFontInAnchor(editor: Editor, root: Element) {
editor.$('a', root).find('font,u').each(function (i, node) {
editor.dom.remove(node, true);
});
}
const setup = function (editor: Editor) {
if (Env.webkit) {
addPreProcessFilter(editor, removeWebKitStyles);
}
if (Env.ie) {
addPreProcessFilter(editor, removeExplorerBrElementsAfterBlocks);
addPostProcessFilter(editor, removeUnderlineAndFontInAnchor);
}
};
export default {
setup
};