From 9c1a42b85236d226db82a0d599a913d17588764f Mon Sep 17 00:00:00 2001 From: cahrens Date: Thu, 27 Mar 2014 13:03:28 -0400 Subject: [PATCH] Use postMessage to communicate between the iframe and TinyMCE. This gets around cross-origin issues on the CDN. Delete plugin.min.js as the Jake command will minimize into the full file. --- .../xmodule/xmodule/js/src/html/edit.coffee | 20 +- .../tiny_mce/plugins/codemirror/plugin.js | 120 +++++- .../tiny_mce/plugins/codemirror/plugin.min.js | 3 - .../tiny_mce/plugins/codemirror/source.html | 380 +++++++++--------- 4 files changed, 302 insertions(+), 221 deletions(-) delete mode 100644 common/static/js/vendor/tiny_mce/plugins/codemirror/plugin.min.js diff --git a/common/lib/xmodule/xmodule/js/src/html/edit.coffee b/common/lib/xmodule/xmodule/js/src/html/edit.coffee index 1b9e0c95db16..f0ca39f85283 100644 --- a/common/lib/xmodule/xmodule/js/src/html/edit.coffee +++ b/common/lib/xmodule/xmodule/js/src/html/edit.coffee @@ -63,8 +63,8 @@ class @HTMLEditingDescriptor ed.on('EditImage', @editImage) ed.on('SaveLink', @saveLink) ed.on('EditLink', @editLink) - ed.on('ShowCodeMirror', @showCodeEditor) - ed.on('SaveCodeMirror', @saveCodeEditor) + ed.on('ShowCodeEditor', @showCodeEditor) + ed.on('SaveCodeEditor', @saveCodeEditor) editImage: (data) => # Called when the image plugin will be shown. Input arg is the JSON version of the image data. @@ -86,17 +86,17 @@ class @HTMLEditingDescriptor if data['href'] data['href'] = rewriteStaticLinks(data['href'], '/static/', @base_asset_url) - showCodeEditor: (codeEditor) => + showCodeEditor: (source) => # Called when the CodeMirror Editor is displayed to convert links to show static prefix. - # The input argument is the CodeMirror instance. - content = rewriteStaticLinks(codeEditor.getValue(), @base_asset_url, '/static/') - codeEditor.setValue(content) + # The input argument is a dict with the text content. + content = rewriteStaticLinks(source.content, @base_asset_url, '/static/') + source.content = content - saveCodeEditor: (codeEditor) => + saveCodeEditor: (source) => # Called when the CodeMirror Editor is saved to convert links back to the full form. - # The input argument is the CodeMirror instance. - content = rewriteStaticLinks(codeEditor.getValue(), '/static/', @base_asset_url) - codeEditor.setValue(content) + # The input argument is a dict with the text content. + content = rewriteStaticLinks(source.content, '/static/', @base_asset_url) + source.content = content initInstanceCallback: (visualEditor) => visualEditor.setContent(rewriteStaticLinks(visualEditor.getContent({no_events: 1}), '/static/', @base_asset_url)) diff --git a/common/static/js/vendor/tiny_mce/plugins/codemirror/plugin.js b/common/static/js/vendor/tiny_mce/plugins/codemirror/plugin.js index 9c58127f2786..c6d8fc7ad459 100644 --- a/common/static/js/vendor/tiny_mce/plugins/codemirror/plugin.js +++ b/common/static/js/vendor/tiny_mce/plugins/codemirror/plugin.js @@ -18,24 +18,114 @@ tinymce.PluginManager.add('codemirror', function(editor, url) { editor.selection.collapse(true); editor.selection.setContent(''); + // Determine the origin of the window that will host the code editor. + // If tinyMCE's baseURL is relative, then static files are hosted in the + // same origin as the containing page. If it is not relative, then we know that + // the origin of the iframe hosting the code editor will match the origin + // of tinyMCE's baseURL, as they are both hosted on the CDN. + var codeEditorOrigin; + var index = tinyMCE.baseURL.indexOf("/static/"); + if (index > 0) { + codeEditorOrigin = tinyMCE.baseURL.substring(0, index); + } + else { + codeEditorOrigin = window.location.origin; + } + + // Send the path location for CodeMirror and the parent origin to use in postMessage. + var sourceHtmlParams = "?CodeMirrorPath=" + editor.settings.codemirror.path + + "&ParentOrigin=" + window.location.origin; + // Open editor window var win = editor.windowManager.open({ title: 'HTML source code', - url: url + '/source.html', - width: 800, - height: 550, - resizable : true, - maximizable : true, - buttons: [ - { text: 'Ok', subtype: 'primary', onclick: function(){ - var doc = document.querySelectorAll('.mce-container-body>iframe')[0]; - doc.contentWindow.submit(); - win.close(); - }}, - { text: 'Cancel', onclick: 'close' } - ] - }); - }; + url: url + '/source.html' + sourceHtmlParams, + width: 800, + height: 550, + resizable: true, + maximizable: true, + buttons: [ + { text: 'OK', subtype: 'primary', onclick: function () { + postToCodeEditor({type: "save"}); + }}, + { text: 'Cancel', onclick: function () { + postToCodeEditor({type: "cancel"}); + }} + ] + + }); + + // The master version of TinyMCE has a win.getContentWindow() method. This is its implementation. + var codeWindow = win.getEl().getElementsByTagName('iframe')[0].contentWindow; + + var postToCodeEditor = function (data) { + codeWindow.postMessage(data, codeEditorOrigin); + }; + + var messageListener = function (event) { + // Check that the message came from the code editor. + if (codeEditorOrigin !== event.origin) { + return; + } + + var source; + if (event.data.type === "init") { + source = {content: editor.getContent({source_view: true})}; + // Post an event to allow rewriting of static links on the content. + editor.fire("ShowCodeEditor", source); + + postToCodeEditor( + { + type: "init", + content: source.content + } + ); + editor.dom.remove(editor.dom.select('.CmCaReT')); + } + else if (event.data.type === "setText") { + source = {content: event.data.text}; + var isDirty = event.data.isDirty; + + // Post an event to allow rewriting of static links on the content. + editor.fire('SaveCodeEditor', source); + + editor.setContent(source.content); + + // Set cursor: + var el = editor.dom.select('span#CmCaReT')[0]; + if (el) { + editor.selection.scrollIntoView(el); + editor.selection.setCursorLocation(el,0); + editor.dom.remove(el); + } + // EDX: added because CmCaReT span was getting left in when caret was within a style tag. + // Make sure to strip it out (and accept that caret will not be in the correct place). + else { + var content = editor.getContent(); + var strippedContent = content.replace('', ''); + if (content !== strippedContent) { + editor.setContent(strippedContent); + } + } + + // EDX: moved block of code from original location since we may change content in bug fix code above. + editor.isNotDirty = !isDirty; + if (isDirty) { + editor.nodeChanged(); + } + } + else if (event.data.type === "closeWindow") { + win.close(); + } + }; + + win.on("close", function() { + window.removeEventListener("message", messageListener); + }); + + window.addEventListener("message", messageListener, false); + + } // Add a button to the button bar // EDX changed to show "HTML" on toolbar button diff --git a/common/static/js/vendor/tiny_mce/plugins/codemirror/plugin.min.js b/common/static/js/vendor/tiny_mce/plugins/codemirror/plugin.min.js deleted file mode 100644 index 05ad0a5597cb..000000000000 --- a/common/static/js/vendor/tiny_mce/plugins/codemirror/plugin.min.js +++ /dev/null @@ -1,3 +0,0 @@ -tinymce.PluginManager.requireLangPack("codemirror"); -tinymce.PluginManager.add("codemirror",function(a,c){function b(){a.focus();a.selection.collapse(!0);a.selection.setContent('');var b=a.windowManager.open({title:"HTML source code",url:c+"/source.html",width:800,height:550,resizable:!0,maximizable:!0,buttons:[{text:"Ok",subtype:"primary",onclick:function(){document.querySelectorAll(".mce-container-body>iframe")[0].contentWindow.submit();b.close()}},{text:"Cancel",onclick:"close"}]})}a.addButton("code", -{title:"Edit HTML",text:"HTML",icon:false,onclick:b});a.addMenuItem("code",{icon:"code",text:"Edit HTML",context:"tools",onclick:b})}); diff --git a/common/static/js/vendor/tiny_mce/plugins/codemirror/source.html b/common/static/js/vendor/tiny_mce/plugins/codemirror/source.html index 15de3748ab3f..627d1051e4a7 100644 --- a/common/static/js/vendor/tiny_mce/plugins/codemirror/source.html +++ b/common/static/js/vendor/tiny_mce/plugins/codemirror/source.html @@ -1,6 +1,8 @@ + +