2
* Compiled inline version. (Library mode)
5
/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
8
(function(exports, undefined) {
13
function require(ids, callback) {
14
var module, defs = [];
16
for (var i = 0; i < ids.length; ++i) {
17
module = modules[ids[i]] || resolve(ids[i]);
19
throw 'module definition dependecy not found: ' + ids[i];
25
callback.apply(null, defs);
28
function define(id, dependencies, definition) {
29
if (typeof id !== 'string') {
30
throw 'invalid module definition, module id must be defined and be a string';
33
if (dependencies === undefined) {
34
throw 'invalid module definition, dependencies must be specified';
37
if (definition === undefined) {
38
throw 'invalid module definition, definition function must be specified';
41
require(dependencies, function() {
42
modules[id] = definition.apply(null, arguments);
46
function defined(id) {
50
function resolve(id) {
52
var fragments = id.split(/[.\/]/);
54
for (var fi = 0; fi < fragments.length; ++fi) {
55
if (!target[fragments[fi]]) {
59
target = target[fragments[fi]];
65
function expose(ids) {
66
for (var i = 0; i < ids.length; i++) {
69
var fragments = id.split(/[.\/]/);
71
for (var fi = 0; fi < fragments.length - 1; ++fi) {
72
if (target[fragments[fi]] === undefined) {
73
target[fragments[fi]] = {};
76
target = target[fragments[fi]];
79
target[fragments[fragments.length - 1]] = modules[id];
83
// Included from: js/tinymce/plugins/paste/classes/Utils.js
88
* Copyright, Moxiecode Systems AB
89
* Released under LGPL License.
91
* License: http://www.tinymce.com/license
92
* Contributing: http://www.tinymce.com/contributing
96
* This class contails various utility functions for the paste plugin.
98
* @class tinymce.pasteplugin.Clipboard
101
define("tinymce/pasteplugin/Utils", [
102
"tinymce/util/Tools",
103
"tinymce/html/DomParser",
104
"tinymce/html/Schema"
105
], function(Tools, DomParser, Schema) {
106
function filter(content, items) {
107
Tools.each(items, function(v) {
108
if (v.constructor == RegExp) {
109
content = content.replace(v, '');
111
content = content.replace(v[0], v[1]);
119
* Gets the innerText of the specified element. It will handle edge cases
120
* and works better than textContent on Gecko.
122
* @param {String} html HTML string to get text from.
123
* @return {String} String of text with line feeds.
125
function innerText(html) {
126
var schema = new Schema(), domParser = new DomParser({}, schema), text = '';
127
var shortEndedElements = schema.getShortEndedElements();
128
var ignoreElements = Tools.makeMap('script noscript style textarea video audio iframe object', ' ');
129
var blockElements = schema.getBlockElements();
131
function walk(node) {
132
var name = node.name, currentNode = node;
140
if (shortEndedElements[name]) {
144
// Ingore script, video contents
145
if (ignoreElements[name]) {
150
if (node.type == 3) {
155
if (!node.shortEnded) {
156
if ((node = node.firstChild)) {
159
} while ((node = node.next));
163
// Add \n or \n\n for blocks or P
164
if (blockElements[name] && currentNode.next) {
173
html = filter(html, [
174
/<!\[[^\]]+\]>/g // Conditional comments
177
walk(domParser.parse(html));
183
* Trims the specified HTML by removing all WebKit fragments, all elements wrapping the body trailing BR elements etc.
185
* @param {String} html Html string to trim contents on.
186
* @return {String} Html contents that got trimmed.
188
function trimHtml(html) {
189
function trimSpaces(all, s1, s2) {
190
// WebKit meant to preserve multiple spaces but instead inserted around all inline tags,
191
// including the spans with inline styles created on paste
199
html = filter(html, [
200
/^[\s\S]*<body[^>]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g, // Remove anything but the contents within the BODY element
201
/<!--StartFragment-->|<!--EndFragment-->/g, // Inner fragments (tables from excel on mac)
202
[/( ?)<span class="Apple-converted-space">\u00a0<\/span>( ?)/g, trimSpaces],
203
/<br>$/i // Trailing BR elements
211
innerText: innerText,
216
// Included from: js/tinymce/plugins/paste/classes/Clipboard.js
221
* Copyright, Moxiecode Systems AB
222
* Released under LGPL License.
224
* License: http://www.tinymce.com/license
225
* Contributing: http://www.tinymce.com/contributing
229
* This class contains logic for getting HTML contents out of the clipboard.
231
* We need to make a lot of ugly hacks to get the contents out of the clipboard since
232
* the W3C Clipboard API is broken in all browsers that have it: Gecko/WebKit/Blink.
233
* We might rewrite this the way those API:s stabilize. Browsers doesn't handle pasting
234
* from applications like Word the same way as it does when pasting into a contentEditable area
235
* so we need to do lots of extra work to try to get to this clipboard data.
237
* Current implementation steps:
238
* 1. On keydown with paste keys Ctrl+V or Shift+Insert create
239
* a paste bin element and move focus to that element.
240
* 2. Wait for the browser to fire a "paste" event and get the contents out of the paste bin.
241
* 3. Check if the paste was successful if true, process the HTML.
242
* (4). If the paste was unsuccessful use IE execCommand, Clipboard API, document.dataTransfer old WebKit API etc.
244
* @class tinymce.pasteplugin.Clipboard
247
define("tinymce/pasteplugin/Clipboard", [
250
"tinymce/pasteplugin/Utils"
251
], function(Env, VK, Utils) {
252
return function(editor) {
253
var self = this, pasteBinElm, lastRng, keyboardPasteTimeStamp = 0, draggingInternally = false;
254
var pasteBinDefaultContent = '%MCEPASTEBIN%', keyboardPastePlainTextState;
257
* Pastes the specified HTML. This means that the HTML is filtered and then
258
* inserted at the current selection in the editor. It will also fire paste events
259
* for custom user filtering.
261
* @param {String} html HTML code to paste into the current selection.
263
function pasteHtml(html) {
264
var args, dom = editor.dom;
266
args = editor.fire('BeforePastePreProcess', {content: html}); // Internal event used by Quirks
267
args = editor.fire('PastePreProcess', args);
270
if (!args.isDefaultPrevented()) {
271
// User has bound PastePostProcess events then we need to pass it through a DOM node
272
// This is not ideal but we don't want to let the browser mess up the HTML for example
273
// some browsers add to P tags etc
274
if (editor.hasEventListeners('PastePostProcess') && !args.isDefaultPrevented()) {
275
// We need to attach the element to the DOM so Sizzle selectors work on the contents
276
var tempBody = dom.add(editor.getBody(), 'div', {style: 'display:none'}, html);
277
args = editor.fire('PastePostProcess', {node: tempBody});
278
dom.remove(tempBody);
279
html = args.node.innerHTML;
282
if (!args.isDefaultPrevented()) {
283
editor.insertContent(html, {merge: editor.settings.paste_merge_formats !== false});
289
* Pastes the specified text. This means that the plain text is processed
290
* and converted into BR and P elements. It will fire paste events for custom filtering.
292
* @param {String} text Text to paste as the current selection location.
294
function pasteText(text) {
295
text = editor.dom.encode(text).replace(/\r\n/g, '\n');
297
var startBlock = editor.dom.getParent(editor.selection.getStart(), editor.dom.isBlock);
299
// Create start block html for example <p attr="value">
300
var forcedRootBlockName = editor.settings.forced_root_block;
301
var forcedRootBlockStartHtml;
302
if (forcedRootBlockName) {
303
forcedRootBlockStartHtml = editor.dom.createHTML(forcedRootBlockName, editor.settings.forced_root_block_attrs);
304
forcedRootBlockStartHtml = forcedRootBlockStartHtml.substr(0, forcedRootBlockStartHtml.length - 3) + '>';
307
if ((startBlock && /^(PRE|DIV)$/.test(startBlock.nodeName)) || !forcedRootBlockName) {
308
text = Utils.filter(text, [
312
text = Utils.filter(text, [
313
[/\n\n/g, "</p>" + forcedRootBlockStartHtml],
314
[/^(.*<\/p>)(<p>)$/, forcedRootBlockStartHtml + '$1'],
318
if (text.indexOf('<p>') != -1) {
319
text = forcedRootBlockStartHtml + text;
327
* Creates a paste bin element as close as possible to the current caret location and places the focus inside that element
328
* so that when the real paste event occurs the contents gets inserted into this element
329
* instead of the current editor selection element.
331
function createPasteBin() {
332
var dom = editor.dom, body = editor.getBody();
333
var viewport = editor.dom.getViewPort(editor.getWin()), scrollTop = viewport.y, top = 20;
336
lastRng = editor.selection.getRng();
339
scrollContainer = editor.selection.getScrollContainer();
341
// Can't always rely on scrollTop returning a useful value.
342
// It returns 0 if the browser doesn't support scrollTop for the element or is non-scrollable
343
if (scrollContainer && scrollContainer.scrollTop > 0) {
344
scrollTop = scrollContainer.scrollTop;
348
// Calculate top cordinate this is needed to avoid scrolling to top of document
349
// We want the paste bin to be as close to the caret as possible to avoid scrolling
350
if (lastRng.getClientRects) {
351
var rects = lastRng.getClientRects();
354
// Client rects gets us closes to the actual
355
// caret location in for example a wrapped paragraph block
356
top = scrollTop + (rects[0].top - dom.getPos(body).y);
360
// Check if we can find a closer location by checking the range element
361
var container = lastRng.startContainer;
363
if (container.nodeType == 3 && container.parentNode != body) {
364
container = container.parentNode;
367
if (container.nodeType == 1) {
368
top = dom.getPos(container, scrollContainer || body).y;
375
pasteBinElm = dom.add(editor.getBody(), 'div', {
377
contentEditable: true,
378
"data-mce-bogus": "all",
379
style: 'position: absolute; top: ' + top + 'px;' +
380
'width: 10px; height: 10px; overflow: hidden; opacity: 0'
381
}, pasteBinDefaultContent);
383
// Move paste bin out of sight since the controlSelection rect gets displayed otherwise on IE and Gecko
384
if (Env.ie || Env.gecko) {
385
dom.setStyle(pasteBinElm, 'left', dom.getStyle(body, 'direction', true) == 'rtl' ? 0xFFFF : -0xFFFF);
388
// Prevent focus events from bubbeling fixed FocusManager issues
389
dom.bind(pasteBinElm, 'beforedeactivate focusin focusout', function(e) {
394
editor.selection.select(pasteBinElm, true);
398
* Removes the paste bin if it exists.
400
function removePasteBin() {
404
// WebKit/Blink might clone the div so
405
// lets make sure we remove all clones
406
// TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
407
while ((pasteBinClone = editor.dom.get('mcepastebin'))) {
408
editor.dom.remove(pasteBinClone);
409
editor.dom.unbind(pasteBinClone);
413
editor.selection.setRng(lastRng);
417
pasteBinElm = lastRng = null;
421
* Returns the contents of the paste bin as a HTML string.
423
* @return {String} Get the contents of the paste bin.
425
function getPasteBinHtml() {
426
var html = '', pasteBinClones, i, clone, cloneHtml;
428
// Since WebKit/Chrome might clone the paste bin when pasting
429
// for example: <img style="float: right"> we need to check if any of them contains some useful html.
430
// TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
431
pasteBinClones = editor.dom.select('div[id=mcepastebin]');
432
for (i = 0; i < pasteBinClones.length; i++) {
433
clone = pasteBinClones[i];
435
// Pasting plain text produces pastebins in pastebinds makes sence right!?
436
if (clone.firstChild && clone.firstChild.id == 'mcepastebin') {
437
clone = clone.firstChild;
440
cloneHtml = clone.innerHTML;
441
if (html != pasteBinDefaultContent) {
450
* Gets various content types out of a datatransfer object.
452
* @param {DataTransfer} dataTransfer Event fired on paste.
453
* @return {Object} Object with mime types and data for those mime types.
455
function getDataTransferItems(dataTransfer) {
459
// Use old WebKit/IE API
460
if (dataTransfer.getData) {
461
var legacyText = dataTransfer.getData('Text');
462
if (legacyText && legacyText.length > 0) {
463
data['text/plain'] = legacyText;
467
if (dataTransfer.types) {
468
for (var i = 0; i < dataTransfer.types.length; i++) {
469
var contentType = dataTransfer.types[i];
470
data[contentType] = dataTransfer.getData(contentType);
479
* Gets various content types out of the Clipboard API. It will also get the
480
* plain text using older IE and WebKit API:s.
482
* @param {ClipboardEvent} clipboardEvent Event fired on paste.
483
* @return {Object} Object with mime types and data for those mime types.
485
function getClipboardContent(clipboardEvent) {
486
return getDataTransferItems(clipboardEvent.clipboardData || editor.getDoc().dataTransfer);
490
* Checks if the clipboard contains image data if it does it will take that data
491
* and convert it into a data url image and paste that image at the caret location.
493
* @param {ClipboardEvent} e Paste/drop event object.
494
* @param {DOMRange} rng Optional rng object to move selection to.
495
* @return {Boolean} true/false if the image data was found or not.
497
function pasteImageData(e, rng) {
498
var dataTransfer = e.clipboardData || e.dataTransfer;
500
function processItems(items) {
503
function pasteImage() {
505
editor.selection.setRng(rng);
509
pasteHtml('<img src="' + reader.result + '">');
513
for (i = 0; i < items.length; i++) {
516
if (/^image\/(jpeg|png|gif)$/.test(item.type)) {
517
reader = new FileReader();
518
reader.onload = pasteImage;
519
reader.readAsDataURL(item.getAsFile ? item.getAsFile() : item);
528
if (editor.settings.paste_data_images && dataTransfer) {
529
return processItems(dataTransfer.items) || processItems(dataTransfer.files);
534
* Chrome on Andoid doesn't support proper clipboard access so we have no choice but to allow the browser default behavior.
536
* @param {Event} e Paste event object to check if it contains any data.
537
* @return {Boolean} true/false if the clipboard is empty or not.
539
function isBrokenAndoidClipboardEvent(e) {
540
var clipboardData = e.clipboardData;
542
return navigator.userAgent.indexOf('Android') != -1 && clipboardData && clipboardData.items && clipboardData.items.length === 0;
545
function getCaretRangeFromEvent(e) {
546
var doc = editor.getDoc(), rng, point;
548
if (doc.caretPositionFromPoint) {
549
point = doc.caretPositionFromPoint(e.clientX, e.clientY);
550
rng = doc.createRange();
551
rng.setStart(point.offsetNode, point.offset);
553
} else if (doc.caretRangeFromPoint) {
554
rng = doc.caretRangeFromPoint(e.clientX, e.clientY);
555
} else if (doc.body.createTextRange) {
556
rng = doc.body.createTextRange();
559
rng.moveToPoint(e.clientX, e.clientY);
562
// Append to top or bottom depending on drop location
563
rng.collapse(e.clientY < doc.body.clientHeight);
570
function hasContentType(clipboardContent, mimeType) {
571
return mimeType in clipboardContent && clipboardContent[mimeType].length > 0;
574
function isKeyboardPasteEvent(e) {
575
return (VK.metaKeyPressed(e) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45);
578
function registerEventHandlers() {
579
editor.on('keydown', function(e) {
580
function removePasteBinOnKeyUp(e) {
581
// Ctrl+V or Shift+Insert
582
if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
587
// Ctrl+V or Shift+Insert
588
if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
589
keyboardPastePlainTextState = e.shiftKey && e.keyCode == 86;
591
// Edge case on Safari on Mac where it doesn't handle Cmd+Shift+V correctly
592
// it fires the keydown but no paste or keyup so we are left with a paste bin
593
if (keyboardPastePlainTextState && Env.webkit && navigator.userAgent.indexOf('Version/') != -1) {
597
// Prevent undoManager keydown handler from making an undo level with the pastebin in it
598
e.stopImmediatePropagation();
600
keyboardPasteTimeStamp = new Date().getTime();
602
// IE doesn't support Ctrl+Shift+V and it doesn't even produce a paste event
603
// so lets fake a paste event and let IE use the execCommand/dataTransfer methods
604
if (Env.ie && keyboardPastePlainTextState) {
606
editor.fire('paste', {ieFake: true});
613
// Remove pastebin if we get a keyup and no paste event
614
// For example pasting a file in IE 11 will not produce a paste event
615
editor.once('keyup', removePasteBinOnKeyUp);
616
editor.once('paste', function() {
617
editor.off('keyup', removePasteBinOnKeyUp);
622
editor.on('paste', function(e) {
623
// Getting content from the Clipboard can take some time
624
var clipboardTimer = new Date().getTime();
625
var clipboardContent = getClipboardContent(e);
626
var clipboardDelay = new Date().getTime() - clipboardTimer;
628
var isKeyBoardPaste = (new Date().getTime() - keyboardPasteTimeStamp - clipboardDelay) < 1000;
629
var plainTextMode = self.pasteFormat == "text" || keyboardPastePlainTextState;
631
keyboardPastePlainTextState = false;
633
if (e.isDefaultPrevented() || isBrokenAndoidClipboardEvent(e)) {
638
if (pasteImageData(e)) {
643
// Not a keyboard paste prevent default paste and try to grab the clipboard contents using different APIs
644
if (!isKeyBoardPaste) {
648
// Try IE only method if paste isn't a keyboard paste
649
if (Env.ie && (!isKeyBoardPaste || e.ieFake)) {
652
editor.dom.bind(pasteBinElm, 'paste', function(e) {
656
editor.getDoc().execCommand('Paste', false, null);
657
clipboardContent["text/html"] = getPasteBinHtml();
660
setTimeout(function() {
663
// Grab HTML from Clipboard API or paste bin as a fallback
664
if (hasContentType(clipboardContent, 'text/html')) {
665
content = clipboardContent['text/html'];
667
content = getPasteBinHtml();
669
// If paste bin is empty try using plain text mode
670
// since that is better than nothing right
671
if (content == pasteBinDefaultContent) {
672
plainTextMode = true;
676
content = Utils.trimHtml(content);
678
// WebKit has a nice bug where it clones the paste bin if you paste from for example notepad
679
// so we need to force plain text mode in this case
680
if (pasteBinElm && pasteBinElm.firstChild && pasteBinElm.firstChild.id === 'mcepastebin') {
681
plainTextMode = true;
686
// If we got nothing from clipboard API and pastebin then we could try the last resort: plain/text
687
if (!content.length) {
688
plainTextMode = true;
691
// Grab plain text from Clipboard API or convert existing HTML to plain text
693
// Use plain text contents from Clipboard API unless the HTML contains paragraphs then
694
// we should convert the HTML to plain text since works better when pasting HTML/Word contents as plain text
695
if (hasContentType(clipboardContent, 'text/plain') && content.indexOf('</p>') == -1) {
696
content = clipboardContent['text/plain'];
698
content = Utils.innerText(content);
702
// If the content is the paste bin default HTML then it was
703
// impossible to get the cliboard data out.
704
if (content == pasteBinDefaultContent) {
705
if (!isKeyBoardPaste) {
706
editor.windowManager.alert('Please use Ctrl+V/Cmd+V keyboard shortcuts to paste contents.');
720
editor.on('dragstart dragend', function(e) {
721
draggingInternally = e.type == 'dragstart';
724
editor.on('drop', function(e) {
725
var rng = getCaretRangeFromEvent(e);
727
if (e.isDefaultPrevented() || draggingInternally) {
731
if (pasteImageData(e, rng)) {
735
if (rng && editor.settings.paste_filter_drop !== false) {
736
var dropContent = getDataTransferItems(e.dataTransfer);
737
var content = dropContent['mce-internal'] || dropContent['text/html'] || dropContent['text/plain'];
742
editor.undoManager.transact(function() {
743
if (dropContent['mce-internal']) {
744
editor.execCommand('Delete');
747
editor.selection.setRng(rng);
749
content = Utils.trimHtml(content);
751
if (!dropContent['text/html']) {
761
editor.on('dragover dragend', function(e) {
762
var i, dataTransfer = e.dataTransfer;
764
if (editor.settings.paste_data_images && dataTransfer) {
765
for (i = 0; i < dataTransfer.types.length; i++) {
766
// Prevent default if we have files dragged into the editor since the pasteImageData handles that
767
if (dataTransfer.types[i] == "Files") {
776
self.pasteHtml = pasteHtml;
777
self.pasteText = pasteText;
779
editor.on('preInit', function() {
780
registerEventHandlers();
782
// Remove all data images from paste for example from Gecko
783
// except internal images like video elements
784
editor.parser.addNodeFilter('img', function(nodes) {
785
if (!editor.settings.paste_data_images) {
786
var i = nodes.length;
789
var src = nodes[i].attributes.map.src;
791
// Some browsers automatically produce data uris on paste
792
// Safari on Mac produces webkit-fake-url see: https://bugs.webkit.org/show_bug.cgi?id=49141
793
if (src && /^(data:image|webkit\-fake\-url)/.test(src)) {
794
if (!nodes[i].attr('data-mce-object') && src !== Env.transparentSrc) {
805
// Included from: js/tinymce/plugins/paste/classes/WordFilter.js
810
* Copyright, Moxiecode Systems AB
811
* Released under LGPL License.
813
* License: http://www.tinymce.com/license
814
* Contributing: http://www.tinymce.com/contributing
818
* This class parses word HTML into proper TinyMCE markup.
820
* @class tinymce.pasteplugin.Quirks
823
define("tinymce/pasteplugin/WordFilter", [
824
"tinymce/util/Tools",
825
"tinymce/html/DomParser",
826
"tinymce/html/Schema",
827
"tinymce/html/Serializer",
829
"tinymce/pasteplugin/Utils"
830
], function(Tools, DomParser, Schema, Serializer, Node, Utils) {
832
* Checks if the specified content is from any of the following sources: MS Word/Office 365/Google docs.
834
function isWordContent(content) {
836
(/<font face="Times New Roman"|class="?Mso|style="[^"]*\bmso-|style='[^'']*\bmso-|w:WordDocument/i).test(content) ||
837
(/class="OutlineElement/).test(content) ||
838
(/id="?docs\-internal\-guid\-/.test(content))
843
* Checks if the specified text starts with "1. " or "a. " etc.
845
function isNumericList(text) {
849
/^[IVXLMCD]{1,2}\.[ \u00a0]/, // Roman upper case
850
/^[ivxlmcd]{1,2}\.[ \u00a0]/, // Roman lower case
851
/^[a-z]{1,2}[\.\)][ \u00a0]/, // Alphabetical a-z
852
/^[A-Z]{1,2}[\.\)][ \u00a0]/, // Alphabetical A-Z
853
/^[0-9]+\.[ \u00a0]/, // Numeric lists
854
/^[\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d]+\.[ \u00a0]/, // Japanese
855
/^[\u58f1\u5f10\u53c2\u56db\u4f0d\u516d\u4e03\u516b\u4e5d\u62fe]+\.[ \u00a0]/ // Chinese
858
text = text.replace(/^[\u00a0 ]+/, '');
860
Tools.each(patterns, function(pattern) {
861
if (pattern.test(text)) {
870
function isBulletList(text) {
871
return /^[\s\u00a0]*[\u2022\u00b7\u00a7\u00d8\u25CF]\s*/.test(text);
874
function WordFilter(editor) {
875
var settings = editor.settings;
877
editor.on('BeforePastePreProcess', function(e) {
878
var content = e.content, retainStyleProperties, validStyles;
880
retainStyleProperties = settings.paste_retain_style_properties;
881
if (retainStyleProperties) {
882
validStyles = Tools.makeMap(retainStyleProperties.split(/[, ]/));
886
* Converts fake bullet and numbered lists to real semantic OL/UL.
888
* @param {tinymce.html.Node} node Root node to convert children of.
890
function convertFakeListsToProperLists(node) {
891
var currentListNode, prevListNode, lastLevel = 1;
893
function getText(node) {
896
if (node.type === 3) {
900
if ((node = node.firstChild)) {
902
txt += getText(node);
903
} while ((node = node.next));
909
function trimListStart(node, regExp) {
910
if (node.type === 3) {
911
if (regExp.test(node.value)) {
912
node.value = node.value.replace(regExp, '');
917
if ((node = node.firstChild)) {
919
if (!trimListStart(node, regExp)) {
922
} while ((node = node.next));
928
function removeIgnoredNodes(node) {
929
if (node._listIgnore) {
934
if ((node = node.firstChild)) {
936
removeIgnoredNodes(node);
937
} while ((node = node.next));
941
function convertParagraphToLi(paragraphNode, listName, start) {
942
var level = paragraphNode._listLevel || lastLevel;
944
// Handle list nesting
945
if (level != lastLevel) {
946
if (level < lastLevel) {
947
// Move to parent list
948
if (currentListNode) {
949
currentListNode = currentListNode.parent.parent;
953
prevListNode = currentListNode;
954
currentListNode = null;
958
if (!currentListNode || currentListNode.name != listName) {
959
prevListNode = prevListNode || currentListNode;
960
currentListNode = new Node(listName, 1);
963
currentListNode.attr('start', '' + start);
966
paragraphNode.wrap(currentListNode);
968
currentListNode.append(paragraphNode);
971
paragraphNode.name = 'li';
973
// Append list to previous list if it exists
974
if (level > lastLevel && prevListNode) {
975
prevListNode.lastChild.append(currentListNode);
980
// Remove start of list item "1. " or "· " etc
981
removeIgnoredNodes(paragraphNode);
982
trimListStart(paragraphNode, /^\u00a0+/);
983
trimListStart(paragraphNode, /^\s*([\u2022\u00b7\u00a7\u00d8\u25CF]|\w+\.)/);
984
trimListStart(paragraphNode, /^\u00a0+/);
987
var paragraphs = node.getAll('p');
989
for (var i = 0; i < paragraphs.length; i++) {
990
node = paragraphs[i];
992
if (node.name == 'p' && node.firstChild) {
993
// Find first text node in paragraph
994
var nodeText = getText(node);
996
// Detect unordered lists look for bullets
997
if (isBulletList(nodeText)) {
998
convertParagraphToLi(node, 'ul');
1002
// Detect ordered lists 1., a. or ixv.
1003
if (isNumericList(nodeText)) {
1004
// Parse OL start number
1005
var matches = /([0-9])\./.exec(nodeText);
1008
start = parseInt(matches[1], 10);
1011
convertParagraphToLi(node, 'ol', start);
1015
// Convert paragraphs marked as lists but doesn't look like anything
1016
if (node._listLevel) {
1017
convertParagraphToLi(node, 'ul', 1);
1021
currentListNode = null;
1026
function filterStyles(node, styleValue) {
1027
var outputStyles = {}, matches, styles = editor.dom.parseStyle(styleValue);
1029
Tools.each(styles, function(value, name) {
1030
// Convert various MS styles to W3C styles
1033
// Parse out list indent level for lists
1034
matches = /\w+ \w+([0-9]+)/i.exec(styleValue);
1036
node._listLevel = parseInt(matches[1], 10);
1039
// Remove these nodes <span style="mso-list:Ignore">o</span>
1040
// Since the span gets removed we mark the text node and the span
1041
if (/Ignore/i.test(value) && node.firstChild) {
1042
node._listIgnore = true;
1043
node.firstChild._listIgnore = true;
1049
name = "text-align";
1053
name = "vertical-align";
1057
case "mso-foreground":
1061
case "mso-background":
1062
case "mso-highlight":
1063
name = "background";
1068
if (value != "normal") {
1069
outputStyles[name] = value;
1074
// Remove track changes code
1075
if (/^(comment|comment-list)$/i.test(value)) {
1083
if (name.indexOf('mso-comment') === 0) {
1088
// Never allow mso- prefixed names
1089
if (name.indexOf('mso-') === 0) {
1093
// Output only valid styles
1094
if (retainStyleProperties == "all" || (validStyles && validStyles[name])) {
1095
outputStyles[name] = value;
1099
// Convert bold style to "b" element
1100
if (/(bold)/i.test(outputStyles["font-weight"])) {
1101
delete outputStyles["font-weight"];
1102
node.wrap(new Node("b", 1));
1105
// Convert italic style to "i" element
1106
if (/(italic)/i.test(outputStyles["font-style"])) {
1107
delete outputStyles["font-style"];
1108
node.wrap(new Node("i", 1));
1111
// Serialize the styles and see if there is something left to keep
1112
outputStyles = editor.dom.serializeStyle(outputStyles, node.name);
1114
return outputStyles;
1120
if (settings.paste_enable_default_filters === false) {
1124
// Detect is the contents is Word junk HTML
1125
if (isWordContent(e.content)) {
1126
e.wordContent = true; // Mark it for other processors
1128
// Remove basic Word junk
1129
content = Utils.filter(content, [
1130
// Word comments like conditional comments etc
1131
/<!--[\s\S]+?-->/gi,
1133
// Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
1134
// MS Office namespaced tags, and a few other tags
1135
/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,
1137
// Convert <s> into <strike> for line-though
1138
[/<(\/?)s>/gi, "<$1strike>"],
1140
// Replace nsbp entites to char since it's easier to handle
1141
[/ /gi, "\u00a0"],
1143
// Convert <span style="mso-spacerun:yes">___</span> to string of alternating
1144
// breaking/non-breaking spaces of same length
1145
[/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi,
1146
function(str, spaces) {
1147
return (spaces.length > 0) ?
1148
spaces.replace(/./, " ").slice(Math.floor(spaces.length / 2)).split("").join("\u00a0") : "";
1153
var validElements = settings.paste_word_valid_elements;
1154
if (!validElements) {
1155
validElements = '-strong/b,-em/i,-span,-p,-ol,-ul,-li,-h1,-h2,-h3,-h4,-h5,-h6,-p/div,' +
1156
'-table[width],-tr,-td[colspan|rowspan|width],-th,-thead,-tfoot,-tbody,-a[href|name],sub,sup,strike,br,del';
1159
// Setup strict schema
1160
var schema = new Schema({
1161
valid_elements: validElements,
1162
valid_children: '-li[p]'
1165
// Add style/class attribute to all element rules since the user might have removed them from
1166
// paste_word_valid_elements config option and we need to check them for properties
1167
Tools.each(schema.elements, function(rule) {
1168
if (!rule.attributes["class"]) {
1169
rule.attributes["class"] = {};
1170
rule.attributesOrder.push("class");
1173
if (!rule.attributes.style) {
1174
rule.attributes.style = {};
1175
rule.attributesOrder.push("style");
1179
// Parse HTML into DOM structure
1180
var domParser = new DomParser({}, schema);
1182
// Filter styles to remove "mso" specific styles and convert some of them
1183
domParser.addAttributeFilter('style', function(nodes) {
1184
var i = nodes.length, node;
1188
node.attr('style', filterStyles(node, node.attr('style')));
1190
// Remove pointess spans
1191
if (node.name == 'span' && node.parent && !node.attributes.length) {
1197
// Check the class attribute for comments or del items and remove those
1198
domParser.addAttributeFilter('class', function(nodes) {
1199
var i = nodes.length, node, className;
1204
className = node.attr('class');
1205
if (/^(MsoCommentReference|MsoCommentText|msoDel|MsoCaption)$/i.test(className)) {
1209
node.attr('class', null);
1213
// Remove all del elements since we don't want the track changes code in the editor
1214
domParser.addNodeFilter('del', function(nodes) {
1215
var i = nodes.length;
1222
// Keep some of the links and anchors
1223
domParser.addNodeFilter('a', function(nodes) {
1224
var i = nodes.length, node, href, name;
1228
href = node.attr('href');
1229
name = node.attr('name');
1231
if (href && href.indexOf('#_msocom_') != -1) {
1236
if (href && href.indexOf('file://') === 0) {
1237
href = href.split('#')[1];
1243
if (!href && !name) {
1246
// Remove all named anchors that aren't specific to TOC, Footnotes or Endnotes
1247
if (name && !/^_?(?:toc|edn|ftn)/i.test(name)) {
1260
// Parse into DOM structure
1261
var rootNode = domParser.parse(content);
1264
convertFakeListsToProperLists(rootNode);
1266
// Serialize DOM back to HTML
1267
e.content = new Serializer({}, schema).serialize(rootNode);
1272
WordFilter.isWordContent = isWordContent;
1277
// Included from: js/tinymce/plugins/paste/classes/Quirks.js
1282
* Copyright, Moxiecode Systems AB
1283
* Released under LGPL License.
1285
* License: http://www.tinymce.com/license
1286
* Contributing: http://www.tinymce.com/contributing
1290
* This class contains various fixes for browsers. These issues can not be feature
1291
* detected since we have no direct control over the clipboard. However we might be able
1292
* to remove some of these fixes once the browsers gets updated/fixed.
1294
* @class tinymce.pasteplugin.Quirks
1297
define("tinymce/pasteplugin/Quirks", [
1299
"tinymce/util/Tools",
1300
"tinymce/pasteplugin/WordFilter",
1301
"tinymce/pasteplugin/Utils"
1302
], function(Env, Tools, WordFilter, Utils) {
1305
return function(editor) {
1306
function addPreProcessFilter(filterFunc) {
1307
editor.on('BeforePastePreProcess', function(e) {
1308
e.content = filterFunc(e.content);
1313
* Removes BR elements after block elements. IE9 has a nasty bug where it puts a BR element after each
1314
* block element when pasting from word. This removes those elements.
1317
* <p>a</p><br><p>b</p>
1322
function removeExplorerBrElementsAfterBlocks(html) {
1323
// Only filter word specific content
1324
if (!WordFilter.isWordContent(html)) {
1328
// Produce block regexp based on the block elements in schema
1329
var blockElements = [];
1331
Tools.each(editor.schema.getBlockElements(), function(block, blockName) {
1332
blockElements.push(blockName);
1335
var explorerBlocksRegExp = new RegExp(
1336
'(?:<br> [\\s\\r\\n]+|<br>)*(<\\/?(' + blockElements.join('|') + ')[^>]*>)(?:<br> [\\s\\r\\n]+|<br>)*',
1340
// Remove BR:s from: <BLOCK>X</BLOCK><BR>
1341
html = Utils.filter(html, [
1342
[explorerBlocksRegExp, '$1']
1345
// IE9 also adds an extra BR element for each soft-linefeed and it also adds a BR for each word wrap break
1346
html = Utils.filter(html, [
1347
[/<br><br>/g, '<BR><BR>'], // Replace multiple BR elements with uppercase BR to keep them intact
1348
[/<br>/g, ' '], // Replace single br elements with space since they are word wrap BR:s
1349
[/<BR><BR>/g, '<br>'] // Replace back the double brs but into a single BR
1356
* WebKit has a nasty bug where the all computed styles gets added to style attributes when copy/pasting contents.
1357
* This fix solves that by simply removing the whole style attribute.
1359
* The paste_webkit_styles option can be set to specify what to keep:
1360
* paste_webkit_styles: "none" // Keep no styles
1361
* paste_webkit_styles: "all", // Keep all of them
1362
* paste_webkit_styles: "font-weight color" // Keep specific ones
1364
* @param {String} content Content that needs to be processed.
1365
* @return {String} Processed contents.
1367
function removeWebKitStyles(content) {
1368
// Passthrough all styles from Word and let the WordFilter handle that junk
1369
if (WordFilter.isWordContent(content)) {
1373
// Filter away styles that isn't matching the target node
1374
var webKitStyles = editor.settings.paste_webkit_styles;
1376
if (editor.settings.paste_remove_styles_if_webkit === false || webKitStyles == "all") {
1381
webKitStyles = webKitStyles.split(/[, ]/);
1384
// Keep specific styles that doesn't match the current node computed style
1386
var dom = editor.dom, node = editor.selection.getNode();
1388
content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, function(all, before, value, after) {
1389
var inputStyles = dom.parseStyle(value, 'span'), outputStyles = {};
1391
if (webKitStyles === "none") {
1392
return before + after;
1395
for (var i = 0; i < webKitStyles.length; i++) {
1396
var inputValue = inputStyles[webKitStyles[i]], currentValue = dom.getStyle(node, webKitStyles[i], true);
1398
if (/color/.test(webKitStyles[i])) {
1399
inputValue = dom.toHex(inputValue);
1400
currentValue = dom.toHex(currentValue);
1403
if (currentValue != inputValue) {
1404
outputStyles[webKitStyles[i]] = inputValue;
1408
outputStyles = dom.serializeStyle(outputStyles, 'span');
1410
return before + ' style="' + outputStyles + '"' + after;
1413
return before + after;
1416
// Remove all external styles
1417
content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, '$1$3');
1420
// Keep internal styles
1421
content = content.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi, function(all, before, value, after) {
1422
return before + ' style="' + value + '"' + after;
1428
// Sniff browsers and apply fixes since we can't feature detect
1430
addPreProcessFilter(removeWebKitStyles);
1434
addPreProcessFilter(removeExplorerBrElementsAfterBlocks);
1439
// Included from: js/tinymce/plugins/paste/classes/Plugin.js
1444
* Copyright, Moxiecode Systems AB
1445
* Released under LGPL License.
1447
* License: http://www.tinymce.com/license
1448
* Contributing: http://www.tinymce.com/contributing
1452
* This class contains the tinymce plugin logic for the paste plugin.
1454
* @class tinymce.pasteplugin.Plugin
1457
define("tinymce/pasteplugin/Plugin", [
1458
"tinymce/PluginManager",
1459
"tinymce/pasteplugin/Clipboard",
1460
"tinymce/pasteplugin/WordFilter",
1461
"tinymce/pasteplugin/Quirks"
1462
], function(PluginManager, Clipboard, WordFilter, Quirks) {
1465
PluginManager.add('paste', function(editor) {
1466
var self = this, clipboard, settings = editor.settings;
1468
function togglePlainTextPaste() {
1469
if (clipboard.pasteFormat == "text") {
1471
clipboard.pasteFormat = "html";
1473
clipboard.pasteFormat = "text";
1476
if (!userIsInformed) {
1477
editor.windowManager.alert(
1478
'Paste is now in plain text mode. Contents will now ' +
1479
'be pasted as plain text until you toggle this option off.'
1482
userIsInformed = true;
1487
self.clipboard = clipboard = new Clipboard(editor);
1488
self.quirks = new Quirks(editor);
1489
self.wordFilter = new WordFilter(editor);
1491
if (editor.settings.paste_as_text) {
1492
self.clipboard.pasteFormat = "text";
1495
if (settings.paste_preprocess) {
1496
editor.on('PastePreProcess', function(e) {
1497
settings.paste_preprocess.call(self, self, e);
1501
if (settings.paste_postprocess) {
1502
editor.on('PastePostProcess', function(e) {
1503
settings.paste_postprocess.call(self, self, e);
1507
editor.addCommand('mceInsertClipboardContent', function(ui, value) {
1508
if (value.content) {
1509
self.clipboard.pasteHtml(value.content);
1513
self.clipboard.pasteText(value.text);
1517
// Block all drag/drop events
1518
if (editor.paste_block_drop) {
1519
editor.on('dragend dragover draggesture dragdrop drop drag', function(e) {
1521
e.stopPropagation();
1525
// Prevent users from dropping data images on Gecko
1526
if (!editor.settings.paste_data_images) {
1527
editor.on('drop', function(e) {
1528
var dataTransfer = e.dataTransfer;
1530
if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
1536
editor.addButton('pastetext', {
1538
tooltip: 'Paste as text',
1539
onclick: togglePlainTextPaste,
1540
active: self.clipboard.pasteFormat == "text"
1543
editor.addMenuItem('pastetext', {
1544
text: 'Paste as text',
1546
active: clipboard.pasteFormat,
1547
onclick: togglePlainTextPaste
1552
expose(["tinymce/pasteplugin/Utils","tinymce/pasteplugin/WordFilter"]);
b'\\ No newline at end of file'