}
function expose(ids) {
- for (var i = 0; i < ids.length; i++) {
- var target = exports;
- var id = ids[i];
- var fragments = id.split(/[.\/]/);
+ var i, target, id, fragments, privateModules;
+
+ for (i = 0; i < ids.length; i++) {
+ target = exports;
+ id = ids[i];
+ fragments = id.split(/[.\/]/);
for (var fi = 0; fi < fragments.length - 1; ++fi) {
if (target[fragments[fi]] === undefined) {
target[fragments[fragments.length - 1]] = modules[id];
}
+
+ // Expose private modules for unit tests
+ if (exports.AMDLC_TESTS) {
+ privateModules = exports.privateModules || {};
+
+ for (id in modules) {
+ privateModules[id] = modules[id];
+ }
+
+ for (i = 0; i < ids.length; i++) {
+ delete privateModules[ids[i]];
+ }
+
+ exports.privateModules = privateModules;
+ }
}
// Included from: js/tinymce/plugins/paste/classes/Utils.js
/**
* Utils.js
*
- * Copyright, Moxiecode Systems AB
* Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
/**
* This class contails various utility functions for the paste plugin.
*
- * @class tinymce.pasteplugin.Clipboard
- * @private
+ * @class tinymce.pasteplugin.Utils
*/
define("tinymce/pasteplugin/Utils", [
"tinymce/util/Tools",
/^[\s\S]*<body[^>]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g, // Remove anything but the contents within the BODY element
/<!--StartFragment-->|<!--EndFragment-->/g, // Inner fragments (tables from excel on mac)
[/( ?)<span class="Apple-converted-space">\u00a0<\/span>( ?)/g, trimSpaces],
+ /<br class="Apple-interchange-newline">/g,
/<br>$/i // Trailing BR elements
]);
return html;
}
+ // TODO: Should be in some global class
+ function createIdGenerator(prefix) {
+ var count = 0;
+
+ return function() {
+ return prefix + (count++);
+ };
+ }
+
return {
filter: filter,
innerText: innerText,
- trimHtml: trimHtml
+ trimHtml: trimHtml,
+ createIdGenerator: createIdGenerator
+ };
+});
+
+// Included from: js/tinymce/plugins/paste/classes/SmartPaste.js
+
+/**
+ * SmartPaste.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+/**
+ * Tries to be smart depending on what the user pastes if it looks like an url
+ * it will make a link out of the current selection. If it's an image url that looks
+ * like an image it will check if it's an image and insert it as an image.
+ *
+ * @class tinymce.pasteplugin.SmartPaste
+ * @private
+ */
+define("tinymce/pasteplugin/SmartPaste", [
+ "tinymce/util/Tools"
+], function (Tools) {
+ var isAbsoluteUrl = function (url) {
+ return /^https?:\/\/[\w\?\-\/+=.&%]+$/i.test(url);
+ };
+
+ var isImageUrl = function (url) {
+ return isAbsoluteUrl(url) && /.(gif|jpe?g|png)$/.test(url);
+ };
+
+ var createImage = function (editor, url, pasteHtml) {
+ editor.undoManager.extra(function () {
+ pasteHtml(editor, url);
+ }, function () {
+ editor.insertContent('<img src="' + url + '">');
+ });
+
+ return true;
+ };
+
+ var createLink = function (editor, url, pasteHtml) {
+ editor.undoManager.extra(function () {
+ pasteHtml(editor, url);
+ }, function () {
+ editor.execCommand('mceInsertLink', false, url);
+ });
+
+ return true;
+ };
+
+ var linkSelection = function (editor, html, pasteHtml) {
+ return editor.selection.isCollapsed() === false && isAbsoluteUrl(html) ? createLink(editor, html, pasteHtml) : false;
+ };
+
+ var insertImage = function (editor, html, pasteHtml) {
+ return isImageUrl(html) ? createImage(editor, html, pasteHtml) : false;
+ };
+
+ var pasteHtml = function (editor, html) {
+ editor.insertContent(html, {
+ merge: editor.settings.paste_merge_formats !== false,
+ paste: true
+ });
+
+ return true;
+ };
+
+ var smartInsertContent = function (editor, html) {
+ Tools.each([
+ linkSelection,
+ insertImage,
+ pasteHtml
+ ], function (action) {
+ return action(editor, html, pasteHtml) !== true;
+ });
+ };
+
+ var insertContent = function (editor, html) {
+ if (editor.settings.smart_paste === false) {
+ pasteHtml(editor, html);
+ } else {
+ smartInsertContent(editor, html);
+ }
+ };
+
+ return {
+ isImageUrl: isImageUrl,
+ isAbsoluteUrl: isAbsoluteUrl,
+ insertContent: insertContent
};
});
/**
* Clipboard.js
*
- * Copyright, Moxiecode Systems AB
* Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
define("tinymce/pasteplugin/Clipboard", [
"tinymce/Env",
+ "tinymce/dom/RangeUtils",
"tinymce/util/VK",
- "tinymce/pasteplugin/Utils"
-], function(Env, VK, Utils) {
+ "tinymce/pasteplugin/Utils",
+ "tinymce/pasteplugin/SmartPaste",
+ "tinymce/util/Delay"
+], function(Env, RangeUtils, VK, Utils, SmartPaste, Delay) {
return function(editor) {
var self = this, pasteBinElm, lastRng, keyboardPasteTimeStamp = 0, draggingInternally = false;
var pasteBinDefaultContent = '%MCEPASTEBIN%', keyboardPastePlainTextState;
+ var mceInternalUrlPrefix = 'data:text/mce-internal,';
+ var uniqueId = Utils.createIdGenerator("mceclip");
/**
* Pastes the specified HTML. This means that the HTML is filtered and then
}
if (!args.isDefaultPrevented()) {
- editor.insertContent(html, {merge: editor.settings.paste_merge_formats !== false});
+ SmartPaste.insertContent(editor, html);
}
}
}
* @return {Object} Object with mime types and data for those mime types.
*/
function getDataTransferItems(dataTransfer) {
- var data = {};
+ var items = {};
if (dataTransfer) {
// Use old WebKit/IE API
if (dataTransfer.getData) {
var legacyText = dataTransfer.getData('Text');
if (legacyText && legacyText.length > 0) {
- data['text/plain'] = legacyText;
+ if (legacyText.indexOf(mceInternalUrlPrefix) == -1) {
+ items['text/plain'] = legacyText;
+ }
}
}
if (dataTransfer.types) {
for (var i = 0; i < dataTransfer.types.length; i++) {
var contentType = dataTransfer.types[i];
- data[contentType] = dataTransfer.getData(contentType);
+ items[contentType] = dataTransfer.getData(contentType);
}
}
}
- return data;
+ return items;
}
/**
return getDataTransferItems(clipboardEvent.clipboardData || editor.getDoc().dataTransfer);
}
+ function hasHtmlOrText(content) {
+ return hasContentType(content, 'text/html') || hasContentType(content, 'text/plain');
+ }
+
+ function getBase64FromUri(uri) {
+ var idx;
+
+ idx = uri.indexOf(',');
+ if (idx !== -1) {
+ return uri.substr(idx + 1);
+ }
+
+ return null;
+ }
+
+ function isValidDataUriImage(settings, imgElm) {
+ return settings.images_dataimg_filter ? settings.images_dataimg_filter(imgElm) : true;
+ }
+
+ function pasteImage(rng, reader, blob) {
+ if (rng) {
+ editor.selection.setRng(rng);
+ rng = null;
+ }
+
+ var dataUri = reader.result;
+ var base64 = getBase64FromUri(dataUri);
+
+ var img = new Image();
+ img.src = dataUri;
+
+ // TODO: Move the bulk of the cache logic to EditorUpload
+ if (isValidDataUriImage(editor.settings, img)) {
+ var blobCache = editor.editorUpload.blobCache;
+ var blobInfo, existingBlobInfo;
+
+ existingBlobInfo = blobCache.findFirst(function(cachedBlobInfo) {
+ return cachedBlobInfo.base64() === base64;
+ });
+
+ if (!existingBlobInfo) {
+ blobInfo = blobCache.create(uniqueId(), blob, base64);
+ blobCache.add(blobInfo);
+ } else {
+ blobInfo = existingBlobInfo;
+ }
+
+ pasteHtml('<img src="' + blobInfo.blobUri() + '">');
+ } else {
+ pasteHtml('<img src="' + dataUri + '">');
+ }
+ }
+
/**
* Checks if the clipboard contains image data if it does it will take that data
* and convert it into a data url image and paste that image at the caret location.
*
* @param {ClipboardEvent} e Paste/drop event object.
- * @param {DOMRange} rng Optional rng object to move selection to.
+ * @param {DOMRange} rng Rng object to move selection to.
* @return {Boolean} true/false if the image data was found or not.
*/
function pasteImageData(e, rng) {
var dataTransfer = e.clipboardData || e.dataTransfer;
function processItems(items) {
- var i, item, reader;
-
- function pasteImage() {
- if (rng) {
- editor.selection.setRng(rng);
- rng = null;
- }
-
- pasteHtml('<img src="' + reader.result + '">');
- }
+ var i, item, reader, hadImage = false;
if (items) {
for (i = 0; i < items.length; i++) {
item = items[i];
- if (/^image\/(jpeg|png|gif)$/.test(item.type)) {
+ if (/^image\/(jpeg|png|gif|bmp)$/.test(item.type)) {
+ var blob = item.getAsFile ? item.getAsFile() : item;
+
reader = new FileReader();
- reader.onload = pasteImage;
- reader.readAsDataURL(item.getAsFile ? item.getAsFile() : item);
+ reader.onload = pasteImage.bind(null, rng, reader, blob);
+ reader.readAsDataURL(blob);
e.preventDefault();
- return true;
+ hadImage = true;
}
}
}
+
+ return hadImage;
}
if (editor.settings.paste_data_images && dataTransfer) {
}
function getCaretRangeFromEvent(e) {
- var doc = editor.getDoc(), rng, point;
-
- if (doc.caretPositionFromPoint) {
- point = doc.caretPositionFromPoint(e.clientX, e.clientY);
- rng = doc.createRange();
- rng.setStart(point.offsetNode, point.offset);
- rng.collapse(true);
- } else if (doc.caretRangeFromPoint) {
- rng = doc.caretRangeFromPoint(e.clientX, e.clientY);
- } else if (doc.body.createTextRange) {
- rng = doc.body.createTextRange();
-
- try {
- rng.moveToPoint(e.clientX, e.clientY);
- rng.collapse(true);
- } catch (ex) {
- // Append to top or bottom depending on drop location
- rng.collapse(e.clientY < doc.body.clientHeight);
- }
- }
-
- return rng;
+ return RangeUtils.getCaretRangeFromPoint(e.clientX, e.clientY, editor.getDoc());
}
function hasContentType(clipboardContent, mimeType) {
}
});
+ function insertClipboardContent(clipboardContent, isKeyBoardPaste, plainTextMode) {
+ var content;
+
+ // Grab HTML from Clipboard API or paste bin as a fallback
+ if (hasContentType(clipboardContent, 'text/html')) {
+ content = clipboardContent['text/html'];
+ } else {
+ content = getPasteBinHtml();
+
+ // If paste bin is empty try using plain text mode
+ // since that is better than nothing right
+ if (content == pasteBinDefaultContent) {
+ plainTextMode = true;
+ }
+ }
+
+ content = Utils.trimHtml(content);
+
+ // WebKit has a nice bug where it clones the paste bin if you paste from for example notepad
+ // so we need to force plain text mode in this case
+ if (pasteBinElm && pasteBinElm.firstChild && pasteBinElm.firstChild.id === 'mcepastebin') {
+ plainTextMode = true;
+ }
+
+ removePasteBin();
+
+ // If we got nothing from clipboard API and pastebin then we could try the last resort: plain/text
+ if (!content.length) {
+ plainTextMode = true;
+ }
+
+ // Grab plain text from Clipboard API or convert existing HTML to plain text
+ if (plainTextMode) {
+ // Use plain text contents from Clipboard API unless the HTML contains paragraphs then
+ // we should convert the HTML to plain text since works better when pasting HTML/Word contents as plain text
+ if (hasContentType(clipboardContent, 'text/plain') && content.indexOf('</p>') == -1) {
+ content = clipboardContent['text/plain'];
+ } else {
+ content = Utils.innerText(content);
+ }
+ }
+
+ // If the content is the paste bin default HTML then it was
+ // impossible to get the cliboard data out.
+ if (content == pasteBinDefaultContent) {
+ if (!isKeyBoardPaste) {
+ editor.windowManager.alert('Please use Ctrl+V/Cmd+V keyboard shortcuts to paste contents.');
+ }
+
+ return;
+ }
+
+ if (plainTextMode) {
+ pasteText(content);
+ } else {
+ pasteHtml(content);
+ }
+ }
+
+ var getLastRng = function() {
+ return lastRng || editor.selection.getRng();
+ };
+
editor.on('paste', function(e) {
// Getting content from the Clipboard can take some time
var clipboardTimer = new Date().getTime();
return;
}
- if (pasteImageData(e)) {
+ if (!hasHtmlOrText(clipboardContent) && pasteImageData(e, getLastRng())) {
removePasteBin();
return;
}
clipboardContent["text/html"] = getPasteBinHtml();
}
- setTimeout(function() {
- var content;
-
- // Grab HTML from Clipboard API or paste bin as a fallback
- if (hasContentType(clipboardContent, 'text/html')) {
- content = clipboardContent['text/html'];
- } else {
- content = getPasteBinHtml();
-
- // If paste bin is empty try using plain text mode
- // since that is better than nothing right
- if (content == pasteBinDefaultContent) {
- plainTextMode = true;
- }
- }
-
- content = Utils.trimHtml(content);
-
- // WebKit has a nice bug where it clones the paste bin if you paste from for example notepad
- // so we need to force plain text mode in this case
- if (pasteBinElm && pasteBinElm.firstChild && pasteBinElm.firstChild.id === 'mcepastebin') {
- plainTextMode = true;
- }
-
- removePasteBin();
-
- // If we got nothing from clipboard API and pastebin then we could try the last resort: plain/text
- if (!content.length) {
- plainTextMode = true;
- }
-
- // Grab plain text from Clipboard API or convert existing HTML to plain text
- if (plainTextMode) {
- // Use plain text contents from Clipboard API unless the HTML contains paragraphs then
- // we should convert the HTML to plain text since works better when pasting HTML/Word contents as plain text
- if (hasContentType(clipboardContent, 'text/plain') && content.indexOf('</p>') == -1) {
- content = clipboardContent['text/plain'];
- } else {
- content = Utils.innerText(content);
- }
- }
-
- // If the content is the paste bin default HTML then it was
- // impossible to get the cliboard data out.
- if (content == pasteBinDefaultContent) {
- if (!isKeyBoardPaste) {
- editor.windowManager.alert('Please use Ctrl+V/Cmd+V keyboard shortcuts to paste contents.');
- }
-
- return;
- }
-
- if (plainTextMode) {
- pasteText(content);
- } else {
- pasteHtml(content);
- }
- }, 0);
+ // If clipboard API has HTML then use that directly
+ if (hasContentType(clipboardContent, 'text/html')) {
+ e.preventDefault();
+ insertClipboardContent(clipboardContent, isKeyBoardPaste, plainTextMode);
+ } else {
+ Delay.setEditorTimeout(editor, function() {
+ insertClipboardContent(clipboardContent, isKeyBoardPaste, plainTextMode);
+ }, 0);
+ }
});
editor.on('dragstart dragend', function(e) {
draggingInternally = e.type == 'dragstart';
});
+ function isPlainTextFileUrl(content) {
+ return content['text/plain'].indexOf('file://') === 0;
+ }
+
editor.on('drop', function(e) {
- var rng = getCaretRangeFromEvent(e);
+ var dropContent, rng;
+
+ rng = getCaretRangeFromEvent(e);
if (e.isDefaultPrevented() || draggingInternally) {
return;
}
- if (pasteImageData(e, rng)) {
+ dropContent = getDataTransferItems(e.dataTransfer);
+
+ if ((!hasHtmlOrText(dropContent) || isPlainTextFileUrl(dropContent)) && pasteImageData(e, rng)) {
return;
}
if (rng && editor.settings.paste_filter_drop !== false) {
- var dropContent = getDataTransferItems(e.dataTransfer);
var content = dropContent['mce-internal'] || dropContent['text/html'] || dropContent['text/plain'];
if (content) {
e.preventDefault();
- editor.undoManager.transact(function() {
- if (dropContent['mce-internal']) {
- editor.execCommand('Delete');
- }
+ // FF 45 doesn't paint a caret when dragging in text in due to focus call by execCommand
+ Delay.setEditorTimeout(editor, function() {
+ editor.undoManager.transact(function() {
+ if (dropContent['mce-internal']) {
+ editor.execCommand('Delete');
+ }
- editor.selection.setRng(rng);
+ editor.selection.setRng(rng);
- content = Utils.trimHtml(content);
+ content = Utils.trimHtml(content);
- if (!dropContent['text/html']) {
- pasteText(content);
- } else {
- pasteHtml(content);
- }
+ if (!dropContent['text/html']) {
+ pasteText(content);
+ } else {
+ pasteHtml(content);
+ }
+ });
});
}
}
});
editor.on('dragover dragend', function(e) {
- var i, dataTransfer = e.dataTransfer;
-
- if (editor.settings.paste_data_images && dataTransfer) {
- for (i = 0; i < dataTransfer.types.length; i++) {
- // Prevent default if we have files dragged into the editor since the pasteImageData handles that
- if (dataTransfer.types[i] == "Files") {
- e.preventDefault();
- return false;
- }
- }
+ if (editor.settings.paste_data_images) {
+ e.preventDefault();
}
});
}
self.pasteHtml = pasteHtml;
self.pasteText = pasteText;
+ self.pasteImageData = pasteImageData;
editor.on('preInit', function() {
registerEventHandlers();
// Remove all data images from paste for example from Gecko
// except internal images like video elements
- editor.parser.addNodeFilter('img', function(nodes) {
- if (!editor.settings.paste_data_images) {
+ editor.parser.addNodeFilter('img', function(nodes, name, args) {
+ function isPasteInsert(args) {
+ return args.data && args.data.paste === true;
+ }
+
+ function remove(node) {
+ if (!node.attr('data-mce-object') && src !== Env.transparentSrc) {
+ node.remove();
+ }
+ }
+
+ function isWebKitFakeUrl(src) {
+ return src.indexOf("webkit-fake-url") === 0;
+ }
+
+ function isDataUri(src) {
+ return src.indexOf("data:") === 0;
+ }
+
+ if (!editor.settings.paste_data_images && isPasteInsert(args)) {
var i = nodes.length;
while (i--) {
var src = nodes[i].attributes.map.src;
- // Some browsers automatically produce data uris on paste
+ if (!src) {
+ continue;
+ }
+
// Safari on Mac produces webkit-fake-url see: https://bugs.webkit.org/show_bug.cgi?id=49141
- if (src && /^(data:image|webkit\-fake\-url)/.test(src)) {
- if (!nodes[i].attr('data-mce-object') && src !== Env.transparentSrc) {
- nodes[i].remove();
- }
+ if (isWebKitFakeUrl(src)) {
+ remove(nodes[i]);
+ } else if (!editor.settings.allow_html_data_urls && isDataUri(src)) {
+ remove(nodes[i]);
}
}
}
/**
* WordFilter.js
*
- * Copyright, Moxiecode Systems AB
* Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
/**
* This class parses word HTML into proper TinyMCE markup.
*
- * @class tinymce.pasteplugin.Quirks
+ * @class tinymce.pasteplugin.WordFilter
* @private
*/
define("tinymce/pasteplugin/WordFilter", [
}
function isBulletList(text) {
- return /^[\s\u00a0]*[\u2022\u00b7\u00a7\u00d8\u25CF]\s*/.test(text);
+ return /^[\s\u00a0]*[\u2022\u00b7\u00a7\u25CF]\s*/.test(text);
}
function WordFilter(editor) {
editor.on('BeforePastePreProcess', function(e) {
var content = e.content, retainStyleProperties, validStyles;
+ // Remove google docs internal guid markers
+ content = content.replace(/<b[^>]+id="?docs-internal-[^>]*>/gi, '');
+ content = content.replace(/<br class="?Apple-interchange-newline"?>/gi, '');
+
retainStyleProperties = settings.paste_retain_style_properties;
if (retainStyleProperties) {
validStyles = Tools.makeMap(retainStyleProperties.split(/[, ]/));
// Remove start of list item "1. " or "· " etc
removeIgnoredNodes(paragraphNode);
trimListStart(paragraphNode, /^\u00a0+/);
- trimListStart(paragraphNode, /^\s*([\u2022\u00b7\u00a7\u00d8\u25CF]|\w+\.)/);
+ trimListStart(paragraphNode, /^\s*([\u2022\u00b7\u00a7\u25CF]|\w+\.)/);
trimListStart(paragraphNode, /^\u00a0+/);
}
// Add style/class attribute to all element rules since the user might have removed them from
// paste_word_valid_elements config option and we need to check them for properties
Tools.each(schema.elements, function(rule) {
+ /*eslint dot-notation:0*/
if (!rule.attributes["class"]) {
rule.attributes["class"] = {};
rule.attributesOrder.push("class");
node = nodes[i];
className = node.attr('class');
- if (/^(MsoCommentReference|MsoCommentText|msoDel|MsoCaption)$/i.test(className)) {
+ if (/^(MsoCommentReference|MsoCommentText|msoDel)$/i.test(className)) {
node.remove();
}
}
// Serialize DOM back to HTML
- e.content = new Serializer({}, schema).serialize(rootNode);
+ e.content = new Serializer({
+ validate: settings.validate
+ }, schema).serialize(rootNode);
}
});
}
/**
* Quirks.js
*
- * Copyright, Moxiecode Systems AB
* Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
/**
* Plugin.js
*
- * Copyright, Moxiecode Systems AB
* Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
PluginManager.add('paste', function(editor) {
var self = this, clipboard, settings = editor.settings;
+ function isUserInformedAboutPlainText() {
+ return userIsInformed || editor.settings.paste_plaintext_inform === false;
+ }
+
function togglePlainTextPaste() {
if (clipboard.pasteFormat == "text") {
this.active(false);
clipboard.pasteFormat = "html";
+ editor.fire('PastePlainTextToggle', {state: false});
} else {
clipboard.pasteFormat = "text";
this.active(true);
- if (!userIsInformed) {
- editor.windowManager.alert(
- 'Paste is now in plain text mode. Contents will now ' +
- 'be pasted as plain text until you toggle this option off.'
- );
+ if (!isUserInformedAboutPlainText()) {
+ var message = editor.translate('Paste is now in plain text mode. Contents will now ' +
+ 'be pasted as plain text until you toggle this option off.');
+
+ editor.notificationManager.open({
+ text: message,
+ type: 'info'
+ });
userIsInformed = true;
+ editor.fire('PastePlainTextToggle', {state: true});
}
}
+
+ editor.focus();
}
self.clipboard = clipboard = new Clipboard(editor);
});
// Block all drag/drop events
- if (editor.paste_block_drop) {
+ if (editor.settings.paste_block_drop) {
editor.on('dragend dragover draggesture dragdrop drop drag', function(e) {
e.preventDefault();
e.stopPropagation();
});
});
-expose(["tinymce/pasteplugin/Utils","tinymce/pasteplugin/WordFilter"]);
+expose(["tinymce/pasteplugin/Utils"]);
})(this);
\ No newline at end of file