/**
* PasteBin.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
*/
/**
* @class tinymce.pasteplugin.PasteBin
* @private
*/
define(
'tinymce.plugins.paste.core.PasteBin',
[
'tinymce.core.util.Tools',
'tinymce.core.Env'
],
function (Tools, Env) {
return function (editor) {
var lastRng;
var pasteBinDefaultContent = '%MCEPASTEBIN%';
/**
* Creates a paste bin element as close as possible to the current caret location and places the focus inside that element
* so that when the real paste event occurs the contents gets inserted into this element
* instead of the current editor selection element.
*/
var create = function () {
var dom = editor.dom, body = editor.getBody();
var viewport = editor.dom.getViewPort(editor.getWin()), scrollTop = viewport.y, top = 20;
var pasteBinElm;
var scrollContainer;
lastRng = editor.selection.getRng();
if (editor.inline) {
scrollContainer = editor.selection.getScrollContainer();
// Can't always rely on scrollTop returning a useful value.
// It returns 0 if the browser doesn't support scrollTop for the element or is non-scrollable
if (scrollContainer && scrollContainer.scrollTop > 0) {
scrollTop = scrollContainer.scrollTop;
}
}
/**
* Returns the rect of the current caret if the caret is in an empty block before a
* BR we insert a temporary invisible character that we get the rect this way we always get a proper rect.
*
* TODO: This might be useful in core.
*/
function getCaretRect(rng) {
var rects, textNode, node, container = rng.startContainer;
rects = rng.getClientRects();
if (rects.length) {
return rects[0];
}
if (!rng.collapsed || container.nodeType !== 1) {
return;
}
node = container.childNodes[lastRng.startOffset];
// Skip empty whitespace nodes
while (node && node.nodeType === 3 && !node.data.length) {
node = node.nextSibling;
}
if (!node) {
return;
}
// Check if the location is |<br>
// TODO: Might need to expand this to say |<table>
if (node.tagName === 'BR') {
textNode = dom.doc.createTextNode('\uFEFF');
node.parentNode.insertBefore(textNode, node);
rng = dom.createRng();
rng.setStartBefore(textNode);
rng.setEndAfter(textNode);
rects = rng.getClientRects();
dom.remove(textNode);
}
if (rects.length) {
return rects[0];
}
}
// Calculate top cordinate this is needed to avoid scrolling to top of document
// We want the paste bin to be as close to the caret as possible to avoid scrolling
if (lastRng.getClientRects) {
var rect = getCaretRect(lastRng);
if (rect) {
// Client rects gets us closes to the actual
// caret location in for example a wrapped paragraph block
top = scrollTop + (rect.top - dom.getPos(body).y);
} else {
top = scrollTop;
// Check if we can find a closer location by checking the range element
var container = lastRng.startContainer;
if (container) {
if (container.nodeType === 3 && container.parentNode !== body) {
container = container.parentNode;
}
if (container.nodeType === 1) {
top = dom.getPos(container, scrollContainer || body).y;
}
}
}
}
// Create a pastebin
pasteBinElm = editor.dom.add(editor.getBody(), 'div', {
id: "mcepastebin",
contentEditable: true,
"data-mce-bogus": "all",
style: 'position: absolute; top: ' + top + 'px; width: 10px; height: 10px; overflow: hidden; opacity: 0'
}, pasteBinDefaultContent);
// Move paste bin out of sight since the controlSelection rect gets displayed otherwise on IE and Gecko
if (Env.ie || Env.gecko) {
dom.setStyle(pasteBinElm, 'left', dom.getStyle(body, 'direction', true) === 'rtl' ? 0xFFFF : -0xFFFF);
}
// Prevent focus events from bubbeling fixed FocusManager issues
dom.bind(pasteBinElm, 'beforedeactivate focusin focusout', function (e) {
e.stopPropagation();
});
pasteBinElm.focus();
editor.selection.select(pasteBinElm, true);
};
/**
* Removes the paste bin if it exists.
*/
var remove = function () {
if (getEl()) {
var pasteBinClone;
// WebKit/Blink might clone the div so
// lets make sure we remove all clones
// TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
while ((pasteBinClone = editor.dom.get('mcepastebin'))) {
editor.dom.remove(pasteBinClone);
editor.dom.unbind(pasteBinClone);
}
if (lastRng) {
editor.selection.setRng(lastRng);
}
}
lastRng = null;
};
var getEl = function () {
return editor.dom.get('mcepastebin');
};
/**
* Returns the contents of the paste bin as a HTML string.
*
* @return {String} Get the contents of the paste bin.
*/
var getHtml = function () {
var pasteBinElm, pasteBinClones, i, dirtyWrappers, cleanWrapper;
// Since WebKit/Chrome might clone the paste bin when pasting
// for example: <img style="float: right"> we need to check if any of them contains some useful html.
// TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
var copyAndRemove = function (toElm, fromElm) {
toElm.appendChild(fromElm);
editor.dom.remove(fromElm, true); // remove, but keep children
};
// find only top level elements (there might be more nested inside them as well, see TINY-1162)
pasteBinClones = Tools.grep(editor.getBody().childNodes, function (elm) {
return elm.id === 'mcepastebin';
});
pasteBinElm = pasteBinClones.shift();
// if clones were found, move their content into the first bin
Tools.each(pasteBinClones, function (pasteBinClone) {
copyAndRemove(pasteBinElm, pasteBinClone);
});
// TINY-1162: when copying plain text (from notepad for example) WebKit clones
// paste bin (with styles and attributes) and uses it as a default wrapper for
// the chunks of the content, here we cycle over the whole paste bin and replace
// those wrappers with a basic div
dirtyWrappers = editor.dom.select('div[id=mcepastebin]', pasteBinElm);
for (i = dirtyWrappers.length - 1; i >= 0; i--) {
cleanWrapper = editor.dom.create('div');
pasteBinElm.insertBefore(cleanWrapper, dirtyWrappers[i]);
copyAndRemove(cleanWrapper, dirtyWrappers[i]);
}
return pasteBinElm ? pasteBinElm.innerHTML : '';
};
var getLastRng = function () {
return lastRng;
};
var isDefaultContent = function (content) {
return content === pasteBinDefaultContent;
};
var isPasteBin = function (elm) {
return elm && elm.id === 'mcepastebin';
};
var isDefault = function () {
var pasteBinElm = getEl();
return isPasteBin(pasteBinElm) && isDefaultContent(pasteBinElm.innerHTML);
};
return {
create: create,
remove: remove,
getEl: getEl,
getHtml: getHtml,
getLastRng: getLastRng,
isDefault: isDefault,
isDefaultContent: isDefaultContent
};
};
}
);
|