From ef11a0dc7891a63d9e09c0ab09a89239837a98db Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Wed, 20 Jun 2012 15:27:56 +1200 Subject: [PATCH 1/2] BUGFIX: Fix trac ticket 7081 IE contentEditable carets and selection boxes show on top of any elements, interfering with HtmlEditorFields dialogs. This fixes that by storing the selection prior to opening the dialog, then unselecting everything. On dialog close the selection is restored --- javascript/HtmlEditorField.js | 178 ++++++++++++++++++++-------------- 1 file changed, 103 insertions(+), 75 deletions(-) diff --git a/javascript/HtmlEditorField.js b/javascript/HtmlEditorField.js index e2f4fbc90..6c3b59e14 100644 --- a/javascript/HtmlEditorField.js +++ b/javascript/HtmlEditorField.js @@ -171,6 +171,13 @@ */ moveToBookmark: function(bookmark) { this.getInstance().selection.moveToBookmark(bookmark); + this.getInstance().focus(); + }, + /** + * Removes any selection & de-focuses this editor + */ + blur: function() { + this.getInstance().selection.collapse(); }, /** * Add new undo point with the current DOM content. @@ -318,6 +325,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; url: url, success: function(html) { dialog.html(html); + dialog.trigger('dialogopen'); } }); } @@ -325,19 +333,17 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; }); $('.htmleditorfield-dialog').entwine({ - onmatch: function() { + onadd: function() { // Create jQuery dialog var height = $(window).height() * 0.8; - var width = $(window).width() * 0.8; + var width = $(window).width() * 0.8; - this.dialog({autoOpen: true, bgiframe: true, modal: true, height: height, width: width, ghost: true}); + if (!this.is('.ui-dialog-content')) this.dialog({autoOpen: true, bgiframe: true, modal: true, height: height, width: width, ghost: true}); this._super(); }, - onunmatch: function() { - this._super(); - }, + getForm: function() { return this.find('form'); }, @@ -350,13 +356,6 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; toggle: function(bool) { if(this.is(':visible')) this.close(); else this.open(); - }, - ondialogopen: function(e) { - this.getForm().updateFromEditor(); - this.getForm().redraw(); - }, - ondialogclose: function(e) { - this.getForm().resetFields(); } }); @@ -365,44 +364,69 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; * mostly geared towards modification and insertion of content. */ $('form.htmleditorfield-form').entwine({ + Selection: null, + Bookmark: null, + + setSelection: function(node) { + return this._super($(node)); + }, // Wrapper for various HTML editors 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() { + onadd: function() { // Move title from headline to (jQuery compatible) title attribute var titleEl = this.find(':header:first'); this.getDialog().attr('title', titleEl.text()); this._super(); - - this.redraw(); }, - onunmatch: function() { + onremove: function() { + this.setSelection(null); + this.setBookmark(null); + this.setEditor(null); + this._super(); }, - redraw: function() { - }, - resetFields: function() { - if(typeof window._ss_htmleditorfield_bookmark != 'undefined') window._ss_htmleditorfield_bookmark = null; - this.getEditor().onclose(); - }, + getDialog: function() { // TODO Refactor to listen to form events to remove two-way coupling return this.closest('.htmleditorfield-dialog'); }, - /** - * Update the view state based on the current editor selection. - */ - updateFromEditor: function() { - this.getEditor().onopen(); - window._ss_htmleditorfield_bookmark = this.getEditor().createBookmark(); + + fromDialog: { + ondialogopen: function(){ + var ed = this.getEditor(); + ed.onopen(); + + this.setSelection(ed.getSelectedNode()); + this.setBookmark(ed.createBookmark()); + + ed.blur(); + + this.find(':input:not(:submit)[data-skip-autofocus!="true"]').filter(':visible:enabled').eq(0).focus(); + + this.updateFromEditor(); + this.redraw(); + }, + + ondialogclose: function(){ + var ed = this.getEditor(); + ed.onclose(); + + ed.moveToBookmark(this.getBookmark()); + + this.setSelection(null); + this.setBookmark(null); + + this.resetFields(); + } }, + createEditor: function(){ return ss.editorWrappers['default'](); }, + /** * Get the tinyMCE editor */ @@ -412,6 +436,28 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; this.setEditor(val = this.createEditor()); } return val; + }, + + modifySelection: function(callback) { + var ed = this.getEditor(); + + ed.moveToBookmark(this.getBookmark()); + callback.call(this, ed); + + this.setSelection(ed.getSelectedNode()); + this.setBookmark(ed.createBookmark()); + + ed.blur(); + }, + + updateFromEditor: function() { + /* NOP */ + }, + redraw: function() { + /* NOP */ + }, + resetFields: function() { + /* NOP */ } }); @@ -463,7 +509,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; } }, insertLink: function() { - var href, target = null, anchor = this.find(':input[name=Anchor]').val(), ed = this.getEditor(); + var href, target = null, anchor = this.find(':input[name=Anchor]').val(); // Determine target if(this.find(':input[name=TargetBlank]').is(':checked')) target = '_blank'; @@ -503,17 +549,16 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; 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; + this.modifySelection(function(ed){ + ed.insertLink(attributes); + }) - // Add the new link - ed.insertLink(attributes); - this.trigger('onafterinsert', attributes); this.updateFromEditor(); }, removeLink: function() { - this.getEditor().removeLink(); + this.modifySelection(function(ed){ + ed.removeLink(); + }) this.close(); }, addAnchorSelector: function() { @@ -590,7 +635,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; * form. */ getCurrentLink: function() { - var ed = this.getEditor(), selectedEl = $(ed.getSelectedNode()), + var selectedEl = this.getSelection(), href = "", target = "", title = "", action = "insert", style_class = ""; // We use a separate field for linkDataSource from tinyMCE.linkElement. @@ -611,7 +656,9 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; linkDataSource = selectedEl = selectedEl.parents('a:first'); } } - if(linkDataSource && linkDataSource.length) ed.selectNode(linkDataSource[0]); + if(linkDataSource && linkDataSource.length) this.modifySelection(function(ed){ + ed.selectNode(linkDataSource[0]); + }); // Is anchor not a link if (!linkDataSource.attr('href')) linkDataSource = null; @@ -621,7 +668,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; target = linkDataSource.attr('target'); title = linkDataSource.attr('title'); style_class = linkDataSource.attr('class'); - href = ed.cleanLink(href, linkDataSource); + href = this.getEditor().cleanLink(href, linkDataSource); action = "update"; } @@ -699,21 +746,20 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; this.find('.overview .action-delete')[updateExisting ? 'hide' : 'show'](); }, onsubmit: function() { - var self = this, ed = this.getEditor(); + this.modifySelection(function(ed){ + this.find('.ss-htmleditorfield-file').each(function() { + $(this).insertHTML(ed); + }); + + ed.repaint(); + }) - // HACK: See ondialogopen() - // jQuery(ed.getContainer()).show(); - - this.find('.ss-htmleditorfield-file').each(function() { - $(this).insertHTML(); - }); - ed.repaint(); this.getDialog().close(); - return false; }, updateFromEditor: function() { - var self = this, ed = this.getEditor(), node = $(ed.getSelectedNode()); + var self = this, node = this.getSelection(); + // TODO Depends on managed mime type if(node.is('img')) { this.showFileView(node.data('url') || node.attr('src'), function() { @@ -727,7 +773,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; redraw: function() { this._super(); - var ed = this.getEditor(), node = $(ed.getSelectedNode()), + var node = this.getSelection(), hasItems = Boolean(this.find('.ss-htmleditorfield-file').length), editingSelected = node.is('img'), header = this.find('.header-edit'); @@ -750,11 +796,6 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; this.find('.Actions .media-update')[updateExisting ? 'show' : 'hide'](); }, resetFields: function() { - var ed = this.getEditor(), node = $(ed.getSelectedNode()); - - // HACK: See ondialogopen() - // jQuery(ed.getContainer()).show(); - this.find('.ss-htmleditorfield-file').remove(); // Remove any existing views this.find('.ss-gridfield-items .ui-selected').removeClass('ui-selected'); // Unselect all items this.redraw(); @@ -869,13 +910,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; /** * 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; - + insertHTML: function(ed) { // Insert content ed.replaceContent(this.getHTML()); }, @@ -959,16 +994,9 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; /** * 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; + insertHTML: function(ed) { + var form = this.closest('form'), + node = form.getSelection(), captionNode = node.closest('.captionImage'); if(node && node.is('img')) { // If the image exists, update it to avoid complications with inserting TinyMCE HTML content From c6039ae8d15bc4ef15928f2035a721fe2e518b72 Mon Sep 17 00:00:00 2001 From: Sean Harvey Date: Wed, 20 Jun 2012 21:58:23 +1200 Subject: [PATCH 2/2] BUGFIX When updating the tree from EditForm, ensure we only change the text of the tree node for the first .text element, instead of the nested ones. --- admin/javascript/LeftAndMain.Tree.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/admin/javascript/LeftAndMain.Tree.js b/admin/javascript/LeftAndMain.Tree.js index 7a3c8c5eb..bf23fd084 100644 --- a/admin/javascript/LeftAndMain.Tree.js +++ b/admin/javascript/LeftAndMain.Tree.js @@ -239,7 +239,8 @@ // set title (either from TreeTitle or from Title fields) // Treetitle has special HTML formatting to denote the status changes. - if(title) node.find('.text').html(title); + // only update immediate text element, we don't want to update all the nested ones + if(title) node.find('.text:first').html(title); // Collect flag classes and also apply to parent var statusFlags = [];