Merge branch 'MDL-47002-master' of git://github.com/merrill-oakland/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 23 Mar 2015 06:59:05 +0000 (14:59 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 23 Mar 2015 06:59:05 +0000 (14:59 +0800)
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/src/editor/js/clean.js
lib/editor/atto/yui/src/editor/js/editor.js

index 52d04bf..4cf531c 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js differ
index 6a50ded..ee5d8be 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js differ
index f01c730..51da696 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js differ
index 89ccac7..e801fe1 100644 (file)
@@ -105,10 +105,12 @@ EditorClean.prototype = {
             {regex: /<span[^>]*>(&nbsp;|\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;
     }
 };
 
index 93e4ede..8089a11 100644 (file)
@@ -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));