MDL-47002 atto: Improve paste behaviour to ensure propper cleanup
Old code failed to clean the editor div which resulted in undesirable code being being left in the div for editing. To properly handel incoming code, we should try to intercept and clean before the paste, or clean the entire editable div.
This commit is contained in:
+131
-3
@@ -298,7 +298,8 @@ Y.extend(Editor, Y.Base, {
|
||||
* @chainable
|
||||
*/
|
||||
setupAutomaticPolling: function() {
|
||||
this._registerEventHandle(this.editor.on(['keyup', 'paste', 'cut'], this.updateOriginal, this));
|
||||
this._registerEventHandle(this.editor.on(['keyup', 'cut'], this.updateOriginal, this));
|
||||
this._registerEventHandle(this.editor.on('paste', this.pasteCleanup, this));
|
||||
|
||||
// Call this.updateOriginal after dropped content has been processed.
|
||||
this._registerEventHandle(this.editor.on('drop', this.updateOriginalDelayed, this));
|
||||
@@ -1129,10 +1130,12 @@ EditorClean.prototype = {
|
||||
{regex: /<span[^>]*>( |\s)*<\/span>/gi, replace: ""},
|
||||
// Remove class="Msoblah"
|
||||
{regex: /class="Mso[^"]*"/gi, replace: ""},
|
||||
// Remove any open HTML comment opens that are not followed by a close. This can completely break page layout.
|
||||
{regex: /<!--(?![\s\S]*?-->)/gi, replace: ""},
|
||||
|
||||
// Source: "http://www.codinghorror.com/blog/2006/01/cleaning-words-nasty-html.html"
|
||||
// Remove forbidden tags for content, title, meta, style, st0-9, head, font, html, body.
|
||||
{regex: /<(\/?title|\/?meta|\/?style|\/?st\d|\/?head|\/?font|\/?html|\/?body|!\[)[^>]*?>/gi, replace: ""},
|
||||
// Remove forbidden tags for content, title, meta, style, st0-9, head, font, html, body, link.
|
||||
{regex: /<(\/?title|\/?meta|\/?style|\/?st\d|\/?head|\/?font|\/?html|\/?body|\/?link|!\[)[^>]*?>/gi, replace: ""},
|
||||
|
||||
// Source: "http://www.tim-jarrett.com/labs_javascript_scrub_word.php"
|
||||
// Replace extended chars with simple text.
|
||||
@@ -1155,6 +1158,131 @@ EditorClean.prototype = {
|
||||
}
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
/**
|
||||
* Intercept and clean html paste events.
|
||||
*
|
||||
* @method pasteCleanup
|
||||
* @param {Object} sourceEvent The YUI EventFacade object
|
||||
* @return {Boolean} True if the passed event should continue, false if not.
|
||||
*/
|
||||
pasteCleanup: function(sourceEvent) {
|
||||
// We only expect paste events, but we will check anyways.
|
||||
if (sourceEvent.type === 'paste') {
|
||||
// The YUI event wrapper doesn't provide paste event info, so we need the underlying event.
|
||||
var event = sourceEvent._event;
|
||||
// Check if we have a valid clipboardData object in the event.
|
||||
// IE has a clipboard object at window.clipboardData, but as of IE 11, it does not provide HTML content access.
|
||||
if (event && event.clipboardData && event.clipboardData.getData) {
|
||||
// Check if there is HTML type to be pasted, this is all we care about.
|
||||
var types = event.clipboardData.types;
|
||||
var isHTML = false;
|
||||
// Different browsers use different things to hold the types, so test various functions.
|
||||
if (!types) {
|
||||
isHTML = false;
|
||||
} else if (typeof types.contains === 'function') {
|
||||
isHTML = types.contains('text/html');
|
||||
} else if (typeof types.indexOf === 'function') {
|
||||
isHTML = (types.indexOf('text/html') > -1);
|
||||
if (!isHTML) {
|
||||
if ((types.indexOf('com.apple.webarchive') > -1) || (types.indexOf('com.apple.iWork.TSPNativeData') > -1)) {
|
||||
// This is going to be a specialized Apple paste paste. We cannot capture this, so clean everything.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We don't know how to handle the clipboard info, so wait for the clipboard event to finish then fallback.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isHTML) {
|
||||
// Get the clipboard content.
|
||||
var content;
|
||||
try {
|
||||
content = event.clipboardData.getData('text/html');
|
||||
} catch (error) {
|
||||
// Something went wrong. Fallback.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stop the original paste.
|
||||
sourceEvent.preventDefault();
|
||||
|
||||
// Scrub the paste content.
|
||||
content = this._cleanHTML(content);
|
||||
|
||||
// Save the current selection.
|
||||
// Using saveSelection as it produces a more consistent experience.
|
||||
var selection = window.rangy.saveSelection();
|
||||
|
||||
// Insert the content.
|
||||
this.insertContentAtFocusPoint(content);
|
||||
|
||||
// Restore the selection, and collapse to end.
|
||||
window.rangy.restoreSelection(selection);
|
||||
window.rangy.getSelection().collapseToEnd();
|
||||
|
||||
// Update the text area.
|
||||
this.updateOriginal();
|
||||
return false;
|
||||
} else {
|
||||
// This is a non-html paste event, we can just let this continue on and call updateOriginalDelayed.
|
||||
this.updateOriginalDelayed();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// If we reached a here, this probably means the browser has limited (or no) clipboard support.
|
||||
// Wait for the clipboard event to finish then fallback.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We should never get here - we must have received a non-paste event for some reason.
|
||||
// Um, just call updateOriginalDelayed() - it's safe.
|
||||
this.updateOriginalDelayed();
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup code after a paste event if we couldn't intercept the paste content.
|
||||
*
|
||||
* @method fallbackPasteCleanup
|
||||
* @chainable
|
||||
*/
|
||||
fallbackPasteCleanup: function() {
|
||||
Y.log('Using fallbackPasteCleanup for atto cleanup', 'debug', LOGNAME);
|
||||
|
||||
// Save the current selection (cursor position).
|
||||
var selection = window.rangy.saveSelection();
|
||||
|
||||
// Get, clean, and replace the content in the editable.
|
||||
var content = this.editor.get('innerHTML');
|
||||
this.editor.set('innerHTML', this._cleanHTML(content));
|
||||
|
||||
// Update the textarea.
|
||||
this.updateOriginal();
|
||||
|
||||
// Restore the selection (cursor position).
|
||||
window.rangy.restoreSelection(selection);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Calls fallbackPasteCleanup on a short timer to allow the paste event handlers to complete.
|
||||
*
|
||||
* @method fallbackPasteCleanupDelayed
|
||||
* @chainable
|
||||
*/
|
||||
fallbackPasteCleanupDelayed: function() {
|
||||
Y.soon(Y.bind(this.fallbackPasteCleanup, this));
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+4
-3
File diff suppressed because one or more lines are too long
+130
-3
@@ -296,7 +296,8 @@ Y.extend(Editor, Y.Base, {
|
||||
* @chainable
|
||||
*/
|
||||
setupAutomaticPolling: function() {
|
||||
this._registerEventHandle(this.editor.on(['keyup', 'paste', 'cut'], this.updateOriginal, this));
|
||||
this._registerEventHandle(this.editor.on(['keyup', 'cut'], this.updateOriginal, this));
|
||||
this._registerEventHandle(this.editor.on('paste', this.pasteCleanup, this));
|
||||
|
||||
// Call this.updateOriginal after dropped content has been processed.
|
||||
this._registerEventHandle(this.editor.on('drop', this.updateOriginalDelayed, this));
|
||||
@@ -1119,10 +1120,12 @@ EditorClean.prototype = {
|
||||
{regex: /<span[^>]*>( |\s)*<\/span>/gi, replace: ""},
|
||||
// Remove class="Msoblah"
|
||||
{regex: /class="Mso[^"]*"/gi, replace: ""},
|
||||
// Remove any open HTML comment opens that are not followed by a close. This can completely break page layout.
|
||||
{regex: /<!--(?![\s\S]*?-->)/gi, replace: ""},
|
||||
|
||||
// Source: "http://www.codinghorror.com/blog/2006/01/cleaning-words-nasty-html.html"
|
||||
// Remove forbidden tags for content, title, meta, style, st0-9, head, font, html, body.
|
||||
{regex: /<(\/?title|\/?meta|\/?style|\/?st\d|\/?head|\/?font|\/?html|\/?body|!\[)[^>]*?>/gi, replace: ""},
|
||||
// Remove forbidden tags for content, title, meta, style, st0-9, head, font, html, body, link.
|
||||
{regex: /<(\/?title|\/?meta|\/?style|\/?st\d|\/?head|\/?font|\/?html|\/?body|\/?link|!\[)[^>]*?>/gi, replace: ""},
|
||||
|
||||
// Source: "http://www.tim-jarrett.com/labs_javascript_scrub_word.php"
|
||||
// Replace extended chars with simple text.
|
||||
@@ -1145,6 +1148,130 @@ EditorClean.prototype = {
|
||||
}
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
/**
|
||||
* Intercept and clean html paste events.
|
||||
*
|
||||
* @method pasteCleanup
|
||||
* @param {Object} sourceEvent The YUI EventFacade object
|
||||
* @return {Boolean} True if the passed event should continue, false if not.
|
||||
*/
|
||||
pasteCleanup: function(sourceEvent) {
|
||||
// We only expect paste events, but we will check anyways.
|
||||
if (sourceEvent.type === 'paste') {
|
||||
// The YUI event wrapper doesn't provide paste event info, so we need the underlying event.
|
||||
var event = sourceEvent._event;
|
||||
// Check if we have a valid clipboardData object in the event.
|
||||
// IE has a clipboard object at window.clipboardData, but as of IE 11, it does not provide HTML content access.
|
||||
if (event && event.clipboardData && event.clipboardData.getData) {
|
||||
// Check if there is HTML type to be pasted, this is all we care about.
|
||||
var types = event.clipboardData.types;
|
||||
var isHTML = false;
|
||||
// Different browsers use different things to hold the types, so test various functions.
|
||||
if (!types) {
|
||||
isHTML = false;
|
||||
} else if (typeof types.contains === 'function') {
|
||||
isHTML = types.contains('text/html');
|
||||
} else if (typeof types.indexOf === 'function') {
|
||||
isHTML = (types.indexOf('text/html') > -1);
|
||||
if (!isHTML) {
|
||||
if ((types.indexOf('com.apple.webarchive') > -1) || (types.indexOf('com.apple.iWork.TSPNativeData') > -1)) {
|
||||
// This is going to be a specialized Apple paste paste. We cannot capture this, so clean everything.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We don't know how to handle the clipboard info, so wait for the clipboard event to finish then fallback.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isHTML) {
|
||||
// Get the clipboard content.
|
||||
var content;
|
||||
try {
|
||||
content = event.clipboardData.getData('text/html');
|
||||
} catch (error) {
|
||||
// Something went wrong. Fallback.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stop the original paste.
|
||||
sourceEvent.preventDefault();
|
||||
|
||||
// Scrub the paste content.
|
||||
content = this._cleanHTML(content);
|
||||
|
||||
// Save the current selection.
|
||||
// Using saveSelection as it produces a more consistent experience.
|
||||
var selection = window.rangy.saveSelection();
|
||||
|
||||
// Insert the content.
|
||||
this.insertContentAtFocusPoint(content);
|
||||
|
||||
// Restore the selection, and collapse to end.
|
||||
window.rangy.restoreSelection(selection);
|
||||
window.rangy.getSelection().collapseToEnd();
|
||||
|
||||
// Update the text area.
|
||||
this.updateOriginal();
|
||||
return false;
|
||||
} else {
|
||||
// This is a non-html paste event, we can just let this continue on and call updateOriginalDelayed.
|
||||
this.updateOriginalDelayed();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// If we reached a here, this probably means the browser has limited (or no) clipboard support.
|
||||
// Wait for the clipboard event to finish then fallback.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We should never get here - we must have received a non-paste event for some reason.
|
||||
// Um, just call updateOriginalDelayed() - it's safe.
|
||||
this.updateOriginalDelayed();
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup code after a paste event if we couldn't intercept the paste content.
|
||||
*
|
||||
* @method fallbackPasteCleanup
|
||||
* @chainable
|
||||
*/
|
||||
fallbackPasteCleanup: function() {
|
||||
|
||||
// Save the current selection (cursor position).
|
||||
var selection = window.rangy.saveSelection();
|
||||
|
||||
// Get, clean, and replace the content in the editable.
|
||||
var content = this.editor.get('innerHTML');
|
||||
this.editor.set('innerHTML', this._cleanHTML(content));
|
||||
|
||||
// Update the textarea.
|
||||
this.updateOriginal();
|
||||
|
||||
// Restore the selection (cursor position).
|
||||
window.rangy.restoreSelection(selection);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Calls fallbackPasteCleanup on a short timer to allow the paste event handlers to complete.
|
||||
*
|
||||
* @method fallbackPasteCleanupDelayed
|
||||
* @chainable
|
||||
*/
|
||||
fallbackPasteCleanupDelayed: function() {
|
||||
Y.soon(Y.bind(this.fallbackPasteCleanup, this));
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+129
-2
@@ -105,10 +105,12 @@ EditorClean.prototype = {
|
||||
{regex: /<span[^>]*>( |\s)*<\/span>/gi, replace: ""},
|
||||
// Remove class="Msoblah"
|
||||
{regex: /class="Mso[^"]*"/gi, replace: ""},
|
||||
// Remove any open HTML comment opens that are not followed by a close. This can completely break page layout.
|
||||
{regex: /<!--(?![\s\S]*?-->)/gi, replace: ""},
|
||||
|
||||
// Source: "http://www.codinghorror.com/blog/2006/01/cleaning-words-nasty-html.html"
|
||||
// Remove forbidden tags for content, title, meta, style, st0-9, head, font, html, body.
|
||||
{regex: /<(\/?title|\/?meta|\/?style|\/?st\d|\/?head|\/?font|\/?html|\/?body|!\[)[^>]*?>/gi, replace: ""},
|
||||
// Remove forbidden tags for content, title, meta, style, st0-9, head, font, html, body, link.
|
||||
{regex: /<(\/?title|\/?meta|\/?style|\/?st\d|\/?head|\/?font|\/?html|\/?body|\/?link|!\[)[^>]*?>/gi, replace: ""},
|
||||
|
||||
// Source: "http://www.tim-jarrett.com/labs_javascript_scrub_word.php"
|
||||
// Replace extended chars with simple text.
|
||||
@@ -131,6 +133,131 @@ EditorClean.prototype = {
|
||||
}
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
/**
|
||||
* Intercept and clean html paste events.
|
||||
*
|
||||
* @method pasteCleanup
|
||||
* @param {Object} sourceEvent The YUI EventFacade object
|
||||
* @return {Boolean} True if the passed event should continue, false if not.
|
||||
*/
|
||||
pasteCleanup: function(sourceEvent) {
|
||||
// We only expect paste events, but we will check anyways.
|
||||
if (sourceEvent.type === 'paste') {
|
||||
// The YUI event wrapper doesn't provide paste event info, so we need the underlying event.
|
||||
var event = sourceEvent._event;
|
||||
// Check if we have a valid clipboardData object in the event.
|
||||
// IE has a clipboard object at window.clipboardData, but as of IE 11, it does not provide HTML content access.
|
||||
if (event && event.clipboardData && event.clipboardData.getData) {
|
||||
// Check if there is HTML type to be pasted, this is all we care about.
|
||||
var types = event.clipboardData.types;
|
||||
var isHTML = false;
|
||||
// Different browsers use different things to hold the types, so test various functions.
|
||||
if (!types) {
|
||||
isHTML = false;
|
||||
} else if (typeof types.contains === 'function') {
|
||||
isHTML = types.contains('text/html');
|
||||
} else if (typeof types.indexOf === 'function') {
|
||||
isHTML = (types.indexOf('text/html') > -1);
|
||||
if (!isHTML) {
|
||||
if ((types.indexOf('com.apple.webarchive') > -1) || (types.indexOf('com.apple.iWork.TSPNativeData') > -1)) {
|
||||
// This is going to be a specialized Apple paste paste. We cannot capture this, so clean everything.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We don't know how to handle the clipboard info, so wait for the clipboard event to finish then fallback.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isHTML) {
|
||||
// Get the clipboard content.
|
||||
var content;
|
||||
try {
|
||||
content = event.clipboardData.getData('text/html');
|
||||
} catch (error) {
|
||||
// Something went wrong. Fallback.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stop the original paste.
|
||||
sourceEvent.preventDefault();
|
||||
|
||||
// Scrub the paste content.
|
||||
content = this._cleanHTML(content);
|
||||
|
||||
// Save the current selection.
|
||||
// Using saveSelection as it produces a more consistent experience.
|
||||
var selection = window.rangy.saveSelection();
|
||||
|
||||
// Insert the content.
|
||||
this.insertContentAtFocusPoint(content);
|
||||
|
||||
// Restore the selection, and collapse to end.
|
||||
window.rangy.restoreSelection(selection);
|
||||
window.rangy.getSelection().collapseToEnd();
|
||||
|
||||
// Update the text area.
|
||||
this.updateOriginal();
|
||||
return false;
|
||||
} else {
|
||||
// This is a non-html paste event, we can just let this continue on and call updateOriginalDelayed.
|
||||
this.updateOriginalDelayed();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// If we reached a here, this probably means the browser has limited (or no) clipboard support.
|
||||
// Wait for the clipboard event to finish then fallback.
|
||||
this.fallbackPasteCleanupDelayed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We should never get here - we must have received a non-paste event for some reason.
|
||||
// Um, just call updateOriginalDelayed() - it's safe.
|
||||
this.updateOriginalDelayed();
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup code after a paste event if we couldn't intercept the paste content.
|
||||
*
|
||||
* @method fallbackPasteCleanup
|
||||
* @chainable
|
||||
*/
|
||||
fallbackPasteCleanup: function() {
|
||||
Y.log('Using fallbackPasteCleanup for atto cleanup', 'debug', LOGNAME);
|
||||
|
||||
// Save the current selection (cursor position).
|
||||
var selection = window.rangy.saveSelection();
|
||||
|
||||
// Get, clean, and replace the content in the editable.
|
||||
var content = this.editor.get('innerHTML');
|
||||
this.editor.set('innerHTML', this._cleanHTML(content));
|
||||
|
||||
// Update the textarea.
|
||||
this.updateOriginal();
|
||||
|
||||
// Restore the selection (cursor position).
|
||||
window.rangy.restoreSelection(selection);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Calls fallbackPasteCleanup on a short timer to allow the paste event handlers to complete.
|
||||
*
|
||||
* @method fallbackPasteCleanupDelayed
|
||||
* @chainable
|
||||
*/
|
||||
fallbackPasteCleanupDelayed: function() {
|
||||
Y.soon(Y.bind(this.fallbackPasteCleanup, this));
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+2
-1
@@ -296,7 +296,8 @@ Y.extend(Editor, Y.Base, {
|
||||
* @chainable
|
||||
*/
|
||||
setupAutomaticPolling: function() {
|
||||
this._registerEventHandle(this.editor.on(['keyup', 'paste', 'cut'], this.updateOriginal, this));
|
||||
this._registerEventHandle(this.editor.on(['keyup', 'cut'], this.updateOriginal, this));
|
||||
this._registerEventHandle(this.editor.on('paste', this.pasteCleanup, this));
|
||||
|
||||
// Call this.updateOriginal after dropped content has been processed.
|
||||
this._registerEventHandle(this.editor.on('drop', this.updateOriginalDelayed, this));
|
||||
|
||||
Reference in New Issue
Block a user