mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-03 14:48:38 +02:00
BUGFIX Retain editor focus when insert images, update attributes on existing image to avoid IE bugs through TinyMCE
This commit is contained in:
parent
5f635d8315
commit
8790ba3c4f
@ -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();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user