BUGFIX Retain editor focus when insert images, update attributes on existing image to avoid IE bugs through TinyMCE

This commit is contained in:
Ingo Schommer 2012-02-22 17:53:37 +01:00
parent 5f635d8315
commit 8790ba3c4f

View File

@ -14,8 +14,6 @@
*/ */
ss.editorWrappers = {}; ss.editorWrappers = {};
ss.editorWrappers.tinyMCE = (function() { ss.editorWrappers.tinyMCE = (function() {
var bookmark;
return { return {
/** /**
* @return Mixed Implementation specific object * @return Mixed Implementation specific object
@ -27,13 +25,11 @@
* Invoked when a content-modifying UI is opened. * Invoked when a content-modifying UI is opened.
*/ */
onopen: function() { onopen: function() {
bookmark = this.getInstance().selection.getBookmark();
}, },
/** /**
* Invoked when a content-modifying UI is closed. * Invoked when a content-modifying UI is closed.
*/ */
onclose: function() { onclose: function() {
bookmark = null;
}, },
/** /**
* Write the HTML back to the original text area field. * Write the HTML back to the original text area field.
@ -104,22 +100,28 @@
this.getInstance().selection.select(node); this.getInstance().selection.select(node);
}, },
/** /**
* Insert content at the current caret position
*
* @param String HTML * @param String HTML
*/ */
insertContent: function(html) { insertContent: function(html, opts) {
// Workaround for IE losing focus this.getInstance().execCommand('mceInsertContent', false, html, opts);
this.getInstance().selection.moveToBookmark(bookmark); },
this.getInstance().execCommand('mceInsertContent', false, html); /**
* Replace currently selected content
*
* @param {String} html
*/
replaceContent: function(html, opts) {
this.getInstance().execCommand('mceReplaceContent', false, html, opts);
}, },
/** /**
* Insert or update a link in the content area (based on current editor selection) * Insert or update a link in the content area (based on current editor selection)
* *
* Parameters: {Object} attrs * Parameters: {Object} attrs
*/ */
insertLink: function(attrs) { insertLink: function(attrs, opts) {
// Workaround for IE losing focus this.getInstance().execCommand("mceInsertLink", false, attrs, opts);
this.getInstance().selection.moveToBookmark(bookmark);
this.getInstance().execCommand("mceInsertLink", false, attrs);
}, },
/** /**
* Remove the link from the currently selected node (if any). * Remove the link from the currently selected node (if any).
@ -147,6 +149,27 @@
if(href.match(/^javascript:\s*mctmp/)) href = ''; if(href.match(/^javascript:\s*mctmp/)) href = '';
return href; return href;
},
/**
* Creates a bookmark for the currently selected range,
* which can be used to reselect this range at a later point.
* @return {mixed}
*/
createBookmark: function() {
return this.getInstance().selection.getBookmark();
},
/**
* Selects a bookmarked range previously saved through createBookmark().
* @param {mixed} bookmark
*/
moveToBookmark: function(bookmark) {
this.getInstance().selection.moveToBookmark(bookmark);
},
/**
* Add new undo point with the current DOM content.
*/
addUndo: function() {
this.getInstance().undoManager.add();
} }
}; };
}); });
@ -220,7 +243,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
isChanged: function() { isChanged: function() {
var ed = this.getEditor(); var ed = this.getEditor();
return (ed && ed.isDirty()); return (ed && ed.getInstance() && ed.isDirty());
}, },
resetChanged: function() { resetChanged: function() {
@ -250,6 +273,9 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
// Wrapper for various HTML editors // Wrapper for various HTML editors
Editor: null, Editor: null,
// TODO Figure out how to keep bookmark reference in entwine, and still be allowed to delete the JS object
// Bookmark: null,
onmatch: function() { onmatch: function() {
// Move title from headline to (jQuery compatible) title attribute // Move title from headline to (jQuery compatible) title attribute
var titleEl = this.find(':header:first'); var titleEl = this.find(':header:first');
@ -270,12 +296,14 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
close: function() { close: function() {
this.dialog('close'); this.dialog('close');
this.getEditor().onclose(); this.getEditor().onclose();
if(typeof window._ss_htmleditorfield_bookmark != 'undefined') window._ss_htmleditorfield_bookmark = null;
}, },
open: function() { open: function() {
this.updateFromEditor(); this.updateFromEditor();
this.dialog('open'); this.dialog('open');
this.redraw(); this.redraw();
this.getEditor().onopen(); this.getEditor().onopen();
window._ss_htmleditorfield_bookmark = this.getEditor().createBookmark();
}, },
/** /**
* Update the view state based on the current editor selection. * Update the view state based on the current editor selection.
@ -374,6 +402,10 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
title : this.find(':input[name=Description]').val() title : this.find(':input[name=Description]').val()
}; };
// Workaround for browsers losing focus, similar to tinyMCEPopup.restoreSelection
ed.moveToBookmark(window._ss_htmleditorfield_bookmark);
window._ss_htmleditorfield_bookmark = null;
// Add the new link // Add the new link
ed.insertLink(attributes); ed.insertLink(attributes);
this.trigger('onafterinsert', attributes); this.trigger('onafterinsert', attributes);
@ -567,11 +599,11 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
var self = this, ed = this.getEditor(); var self = this, ed = this.getEditor();
// HACK: See ondialogopen() // HACK: See ondialogopen()
// if($.browser.msie) jQuery(ed.getContainer()).show(); // jQuery(ed.getContainer()).show();
this.find('.ss-htmleditorfield-file').each(function(el) { this.find('.ss-htmleditorfield-file').each(function(el) {
ed.insertContent($(this).getHTML()); $(this).insertHTML();
}); });
ed.repaint(); ed.repaint();
this.close(); this.close();
@ -594,13 +626,13 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
// HACK: Hide selected node in IE because its drag handles on potentially selected elements // HACK: Hide selected node in IE because its drag handles on potentially selected elements
// don't respect the z-index of the dialog overlay. // don't respect the z-index of the dialog overlay.
// if($.browser.msie) jQuery(ed.getContainer()).hide(); // jQuery(ed.getContainer()).hide();
}, },
ondialogclose: function() { ondialogclose: function() {
var ed = this.getEditor(), node = $(ed.getSelectedNode()); var ed = this.getEditor(), node = $(ed.getSelectedNode());
// HACK: See ondialogopen() // HACK: See ondialogopen()
// if($.browser.msie) jQuery(ed.getContainer()).show(); // jQuery(ed.getContainer()).show();
this.find('.ss-htmleditorfield-file').remove(); // Remove any existing views this.find('.ss-htmleditorfield-file').remove(); // Remove any existing views
this.find('.ss-gridfield-items .ui-selected').removeClass('ui-selected'); // Unselect all items this.find('.ss-gridfield-items .ui-selected').removeClass('ui-selected'); // Unselect all items
@ -685,6 +717,19 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
*/ */
getHTML: function() { getHTML: function() {
}, },
/**
* Insert updated HTML content into the rich text editor
*/
insertHTML: function() {
var form = this.closest('form'), ed = form.getEditor();
// Workaround for browsers losing focus, similar to tinyMCEPopup.restoreSelection
ed.moveToBookmark(window._ss_htmleditorfield_bookmark);
window._ss_htmleditorfield_bookmark = null;
// Insert content
ed.replaceContent(this.getHTML());
},
/** /**
* Updates the form values from an existing node in the editor. * Updates the form values from an existing node in the editor.
* *
@ -737,8 +782,8 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
return { return {
'src' : this.find(':input[name=URL]').val(), 'src' : this.find(':input[name=URL]').val(),
'alt' : this.find(':input[name=AltText]').val(), 'alt' : this.find(':input[name=AltText]').val(),
'width' : width ? parseInt(width, 10) : null, 'width' : width ? parseInt(width, 10) + "px" : null,
'height' : height ? parseInt(height, 10) : null, 'height' : height ? parseInt(height, 10) + "px" : null,
'title' : this.find(':input[name=Title]').val(), 'title' : this.find(':input[name=Title]').val(),
'class' : this.find(':input[name=CSSClass]').val() 'class' : this.find(':input[name=CSSClass]').val()
}; };
@ -752,7 +797,8 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
var el, var el,
attrs = this.getAttributes(), attrs = this.getAttributes(),
extraData = this.getExtraData(), extraData = this.getExtraData(),
imgEl = $('<img id="__mce_tmp" />').attr(attrs); // imgEl = $('<img id="_ss_tmp_img" />');
imgEl = $('<img />').attr(attrs);
if(extraData.CaptionText) { if(extraData.CaptionText) {
el = $('<div style="width: ' + attrs['width'] + 'px;" class="captionImage ' + attrs['class'] + '"><p class="caption">' + extraData.CaptionText + '</p></div>').prepend(imgEl); el = $('<div style="width: ' + attrs['width'] + 'px;" class="captionImage ' + attrs['class'] + '"><p class="caption">' + extraData.CaptionText + '</p></div>').prepend(imgEl);
@ -761,6 +807,40 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
} }
return $('<div />').append(el).html(); // Little hack to get outerHTML string return $('<div />').append(el).html(); // Little hack to get outerHTML string
}, },
/**
* Logic similar to TinyMCE 'advimage' plugin, insertAndClose() method.
*/
insertHTML: function() {
var form = this.closest('form'), ed = form.getEditor(),
node = $(ed.getSelectedNode()), captionNode = node.closest('.captionImage');
// Workaround for browsers losing focus, similar to tinyMCEPopup.restoreSelection.
// TODO In TinyMCE core this is restricted to IE, but leaving it our also
// breaks Firefox: It doesn't save the selection because it inserts into a temporary TinyMCE
// marker element rather than the content DOM nodes
ed.moveToBookmark(window._ss_htmleditorfield_bookmark);
window._ss_htmleditorfield_bookmark = null;
if(node && node.is('img')) {
// If the image exists, update it to avoid complications with inserting TinyMCE HTML content
var attrs = this.getAttributes(), extraData = this.getExtraData();
node.attr(attrs);
// TODO Doesn't allow adding a caption to image after it was first added
if(captionNode.length) {
captionNode.find('.caption').text(extraData.CaptionText);
captionNode.css({width: attrs.width, height: attrs.height}).attr('class', attrs['class']);
}
// Undo needs to be added manually as we're doing direct DOM changes
ed.addUndo();
} else {
// Otherwise insert the whole HTML content
ed.repaint();
ed.insertContent(this.getHTML(), {skip_undo : 1});
ed.addUndo(); // Not sure why undo is separate here, replicating TinyMCE logic
}
ed.repaint();
},
updateFromNode: function(node) { updateFromNode: function(node) {
this.find(':input[name=AltText]').val(node.attr('alt')); this.find(':input[name=AltText]').val(node.attr('alt'));
this.find(':input[name=Title]').val(node.attr('title')); this.find(':input[name=Title]').val(node.attr('title'));
@ -786,11 +866,6 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
'height' : height ? parseInt(height, 10) : null 'height' : height ? parseInt(height, 10) : null
}; };
}, },
getExtraData: function() {
return {
'CaptionText': this.find(':input[name=CaptionText]').val()
};
},
getHTML: function() { getHTML: function() {
var attrs = this.getAttributes(); var attrs = this.getAttributes();