}
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",
/**
* 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) {
+], function(Env, RangeUtils, VK, Utils) {
return function(editor) {
var self = this, pasteBinElm, lastRng, keyboardPasteTimeStamp = 0, draggingInternally = false;
var pasteBinDefaultContent = '%MCEPASTEBIN%', keyboardPastePlainTextState;
+ var mceInternalUrlPrefix = 'data:text/mce-internal,';
/**
* 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});
+ editor.insertContent(html, {merge: editor.settings.paste_merge_formats !== false, data: {paste: true}});
}
}
}
}
}
+ /**
+ * 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 rects = lastRng.getClientRects();
+ var rect = getCaretRect(lastRng);
- if (rects.length) {
+ if (rect) {
// Client rects gets us closes to the actual
// caret location in for example a wrapped paragraph block
- top = scrollTop + (rects[0].top - dom.getPos(body).y);
+ top = scrollTop + (rect.top - dom.getPos(body).y);
} else {
top = scrollTop;
if (dataTransfer.getData) {
var legacyText = dataTransfer.getData('Text');
if (legacyText && legacyText.length > 0) {
- data['text/plain'] = legacyText;
+ if (legacyText.indexOf(mceInternalUrlPrefix) == -1) {
+ data['text/plain'] = legacyText;
+ }
}
}
var dataTransfer = e.clipboardData || e.dataTransfer;
function processItems(items) {
- var i, item, reader;
+ var i, item, reader, hadImage = false;
- function pasteImage() {
+ function pasteImage(reader) {
if (rng) {
editor.selection.setRng(rng);
rng = null;
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)) {
reader = new FileReader();
- reader.onload = pasteImage;
+ reader.onload = pasteImage.bind(null, reader);
reader.readAsDataURL(item.getAsFile ? item.getAsFile() : item);
e.preventDefault();
- return true;
+ hadImage = true;
}
}
}
+
+ return hadImage;
}
if (editor.settings.paste_data_images && dataTransfer) {
}
/**
- * Chrome on Andoid doesn't support proper clipboard access so we have no choice but to allow the browser default behavior.
+ * Chrome on Android doesn't support proper clipboard access so we have no choice but to allow the browser default behavior.
*
* @param {Event} e Paste event object to check if it contains any data.
* @return {Boolean} true/false if the clipboard is empty or not.
*/
- function isBrokenAndoidClipboardEvent(e) {
+ function isBrokenAndroidClipboardEvent(e) {
var clipboardData = e.clipboardData;
return navigator.userAgent.indexOf('Android') != -1 && clipboardData && clipboardData.items && clipboardData.items.length === 0;
}
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) {
keyboardPastePlainTextState = false;
- if (e.isDefaultPrevented() || isBrokenAndoidClipboardEvent(e)) {
+ if (e.isDefaultPrevented() || isBrokenAndroidClipboardEvent(e)) {
removePasteBin();
return;
}
});
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();
}
});
}
// 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+/);
}
- var paragraphs = node.getAll('p');
+ // Build a list of all root level elements before we start
+ // altering them in the loop below.
+ var elements = [], child = node.firstChild;
+ while (typeof child !== 'undefined' && child !== null) {
+ elements.push(child);
- for (var i = 0; i < paragraphs.length; i++) {
- node = paragraphs[i];
+ child = child.walk();
+ if (child !== null) {
+ while (typeof child !== 'undefined' && child.parent !== node) {
+ child = child.walk();
+ }
+ }
+ }
+
+ for (var i = 0; i < elements.length; i++) {
+ node = elements[i];
if (node.name == 'p' && node.firstChild) {
// Find first text node in paragraph
// Detect ordered lists 1., a. or ixv.
if (isNumericList(nodeText)) {
// Parse OL start number
- var matches = /([0-9])\./.exec(nodeText);
+ var matches = /([0-9]+)\./.exec(nodeText);
var start = 1;
if (matches) {
start = parseInt(matches[1], 10);
continue;
}
+ currentListNode = null;
+ } else {
+ // If the root level element isn't a p tag which can be
+ // processed by convertParagraphToLi, it interrupts the
+ // lists, causing a new list to start instead of having
+ // elements from the next list inserted above this tag.
+ prevListNode = currentListNode;
currentListNode = null;
}
}
var validElements = settings.paste_word_valid_elements;
if (!validElements) {
- validElements = '-strong/b,-em/i,-span,-p,-ol,-ul,-li,-h1,-h2,-h3,-h4,-h5,-h6,-p/div,' +
- '-table[width],-tr,-td[colspan|rowspan|width],-th,-thead,-tfoot,-tbody,-a[href|name],sub,sup,strike,br,del';
+ validElements = (
+ '-strong/b,-em/i,-u,-span,-p,-ol,-ul,-li,-h1,-h2,-h3,-h4,-h5,-h6,' +
+ '-p/div,-a[href|name],sub,sup,strike,br,del,table[width],tr,' +
+ 'td[colspan|rowspan|width],th[colspan|rowspan|width],thead,tfoot,tbody'
+ );
}
// Setup strict schema
// 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();
}
var rootNode = domParser.parse(content);
// Process DOM
- convertFakeListsToProperLists(rootNode);
+ if (settings.paste_convert_word_fake_lists !== false) {
+ convertFakeListsToProperLists(rootNode);
+ }
// Serialize DOM back to HTML
e.content = new Serializer({}, 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
});
});
-expose(["tinymce/pasteplugin/Utils","tinymce/pasteplugin/WordFilter"]);
+expose(["tinymce/pasteplugin/Utils"]);
})(this);
\ No newline at end of file