From 3a6511a530015813f7bfda5547f8fc9de750efcf Mon Sep 17 00:00:00 2001 From: Jetha Chan Date: Tue, 1 Apr 2014 12:40:33 +0800 Subject: [PATCH] MDL-44758 editor-atto: prevent table-in-table creation Prevent new table creation, displaying table context menu instead when cursor is within caption, when multiple cells are selected, when selection contains both nodes within and outside of the table, and when selection contains nodes from multiple tables. --- .../moodle-atto_table-button-debug.js | 67 ++++++++++++++----- .../moodle-atto_table-button-min.js | 4 +- .../moodle-atto_table-button.js | 67 ++++++++++++++----- .../plugins/table/yui/src/button/js/button.js | 67 ++++++++++++++----- 4 files changed, 155 insertions(+), 50 deletions(-) diff --git a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js index 9cf781fbf86..fb51c1a83cc 100644 --- a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js +++ b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js @@ -150,30 +150,27 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi * @private */ _displayTableEditor: function(e) { - var selection = this.get('host').getSelectionParentNode(), - cell; - - if (!selection) { - // We don't have a current selection at all, so show the standard dialogue. - return this._displayDialogue(e); - } - - // Check all of the table cells found in the selection. - Y.one(selection).ancestors('th, td', true).each(function(node) { - if (this.editor.contains(node)) { - cell = node; - } - }, this); - + var cell = this._getSuitableTableCell(); if (cell) { // Add the cell to the EventFacade to save duplication in when showing the menu. e.tableCell = cell; return this._showTableMenu(e); } - return this._displayDialogue(e); }, + /** + * Returns whether or not the parameter node exists within the editor. + * + * @method _stopAtContentEditableFilter + * @param {Node} node + * @private + * @return {boolean} whether or not the parameter node exists within the editor. + */ + _stopAtContentEditableFilter: function(node) { + this.editor.contains(node); + }, + /** * Return the dialogue content for the tool, attaching any required * events. @@ -196,6 +193,44 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi return this._content; }, + /** + * Given the current selection, return a table cell suitable for table editing + * purposes, i.e. the first table cell selected, or the first cell in the table + * that the selection exists in, or null if not within a table. + * + * @method _getSuitableTableCell + * @private + * @return {Node} suitable target cell, or null if not within a table + */ + _getSuitableTableCell: function() { + var targetcell = null, + host = this.get('host'); + + host.getSelectedNodes().some(function (node) { + if (node.ancestor('td, th, caption', true, this._stopAtContentEditableFilter)) { + targetcell = node; + + var caption = node.ancestor('caption', true, this._stopAtContentEditableFilter); + if (caption) { + var table = caption.get('parentNode'); + if (table) { + targetcell = table.one('td, th'); + } + } + + // Once we've found a cell to target, we shouldn't need to keep looking. + return true; + } + }); + + if (targetcell) { + var selection = host.getSelectionFromNode(targetcell); + host.setSelection(selection); + } + + return targetcell; + }, + /** * Handle creation of a new table. * diff --git a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js index da3625dce3d..a8aee067d87 100644 --- a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js +++ b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js @@ -1,2 +1,2 @@ -YUI.add("moodle-atto_table-button",function(e,t){var n="atto_table",r='






{{{get_string "accessibilityhint" component}}}',i='';CSS={},e.namespace("M.atto_table").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_contextMenu:null,_lastTarget:null,initializer:function(){this.addButton({icon:"e/table",callback:this._displayTableEditor,tags:"table"}),e.UA.gecko&&(document.execCommand("enableInlineTableEditing",!1,!1),document.execCommand("enableObjectResizing",!1,!1))},_displayDialogue:function(){this._currentSelection=this.get("host").getSelection();if(this._currentSelection!==!1&&!this._currentSelection.collapsed){var e=this.getDialogue({headerContent:M.util.get_string("createtable",n),focusAfterHide:!0});e.set("bodyContent",this._getDialogueContent()).show()}},_displayTableEditor:function(t){var n=this.get("host").getSelectionParentNode(),r;return n?(e.one(n).ancestors("th, td",!0).each(function(e){this.editor.contains(e)&&(r=e)},this),r?(t.tableCell=r,this._showTableMenu(t)):this._displayDialogue(t)):this._displayDialogue(t)},_getDialogueContent:function(){var t=e.Handlebars.compile(r);return this._content=e.Node.create(t({CSS:CSS,elementid:this.get("host").get("elementid"),component:n})),this._content.one(".submit").on("click",this._setTable,this),this._content},_setTable:function(t){var n,r,i,s,o,u,a;t.preventDefault(),this.getDialogue({focusAfterHide:null}).hide(),n=t.currentTarget.ancestor(".atto_form").one(".caption"),r=t.currentTarget.ancestor(".atto_form").one(".rows"),i=t.currentTarget.ancestor(".atto_form").one(".columns"),s=t.currentTarget.ancestor(".atto_form").one(".headers"),this.get("host").setSelection(this._currentSelection);var f="\n";o="
"+f+""+f,o+=""+f,u=0;if(s.get("value")==="columns"||s.get("value")==="both"){u=1,o+=""+f+""+f;for(a=0;a'+f;o+=""+f+""+f}o+=""+f;for(;u"+f;for(a=0;a"+f:o+=''+f;o+=""+f}o+=""+f,o+="
"+e.Escape.html(n.get("value"))+"
"+f+"
",this.get("host").insertContentAtFocusPoint(o),this.markUpdated()},_showTableMenu:function(t){t.preventDefault();var r;if(!this._contextMenu){var s=e.Handlebars.compile(i),o=e.Node.create(s({elementid:this.get("host").get("elementid"),component:n}));this._contextMenu=new e.M.editor_atto.Menu({headerText:M.util.get_string("edittable","atto_table"),bodyContent:o}),r=this._contextMenu.get("boundingBox"),r.delegate("click",this._handleTableChange,"a",this),r.delegate("key",this._handleTableChange,"down:enter,space","a",this)}r=this._contextMenu.get("boundingBox"),this._lastTarget=t.tableCell.ancestor(".editor_atto_content td, .editor_atto_content th",!0),this._contextMenu.show(),this._contextMenu.align(t.tableCell,[e.WidgetPositionAlign.TL,e.WidgetPositionAlign.BL]),r.one("a")&&r.one("a").focus()},_handleTableChange:function(e){e.preventDefault(),this._contextMenu.hide();switch(e.target.getData("change")){case"addcolumnafter":this._addColumnAfter();break;case"addrowafter":this._addRowAfter();break;case"deleterow":this._deleteRow();break;case"deletecolumn":this._deleteColumn();break;case"moverowdown":this._moveRowDown();break;case"moverowup":this._moveRowUp();break;case"movecolumnleft":this._moveColumnLeft();break;case"movecolumnright":this._moveColumnRight()}},_getRowIndex:function(e){var t=e.ancestor("table"),n=e.ancestor("tr");if(!t||!n)return;var r=t.all("tr");return r.indexOf(n)},_getColumnIndex:function(e){var t=e.ancestor("tr");if(!t)return;var n=t.all("td, th");return n.indexOf(e)},_deleteRow:function(){var e=this._lastTarget.ancestor("tr");e&&e.one("td")&&e.remove(!0),this.markUpdated()},_moveRowUp:function(){var e=this._lastTarget.ancestor("tr"),t=e.previous("tr");if(!e||!t)return;e.swap(t),this.markUpdated()},_moveColumnLeft:function(){var t=this._getColumnIndex(this._lastTarget),n=this._lastTarget.ancestor -("table").all("tr"),r=new e.NodeList,i=new e.NodeList,s=!1;n.each(function(e){var n=e.all("td, th"),o=n.item(t),u=n.item(t-1);r.push(o),u&&(u.get("tagName")==="TD"&&(s=!0),i.push(u))});if(s&&i.size()>0){var o=0;for(o=0;o0){var o=0;for(o=0;o");t.replace(n),t=n}t.setHTML(" ")}),n.insert(newrow,t),this.markUpdated()},_addColumnAfter:function(){var t=this._getColumnIndex(this._lastTarget),n=this._lastTarget.ancestor("table"),r=n.all("tr");e.each(r,function(e){var n=e.one("td, th").cloneNode(!0);n.setHTML(" "),e.insert(n,t+1)},this),this.markUpdated()}})},"@VERSION@",{requires:["moodle-editor_atto-plugin","moodle-editor_atto-menu","event","event-valuechange"]}); +YUI.add("moodle-atto_table-button",function(e,t){var n="atto_table",r='






{{{get_string "accessibilityhint" component}}}',i='';CSS={},e.namespace("M.atto_table").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_contextMenu:null,_lastTarget:null,initializer:function(){this.addButton({icon:"e/table",callback:this._displayTableEditor,tags:"table"}),e.UA.gecko&&(document.execCommand("enableInlineTableEditing",!1,!1),document.execCommand("enableObjectResizing",!1,!1))},_displayDialogue:function(){this._currentSelection=this.get("host").getSelection();if(this._currentSelection!==!1&&!this._currentSelection.collapsed){var e=this.getDialogue({headerContent:M.util.get_string("createtable",n),focusAfterHide:!0});e.set("bodyContent",this._getDialogueContent()).show()}},_displayTableEditor:function(e){var t=this._getSuitableTableCell();return t?(e.tableCell=t,this._showTableMenu(e)):this._displayDialogue(e)},_stopAtContentEditableFilter:function(e){this.editor.contains(e)},_getDialogueContent:function(){var t=e.Handlebars.compile(r);return this._content=e.Node.create(t({CSS:CSS,elementid:this.get("host").get("elementid"),component:n})),this._content.one(".submit").on("click",this._setTable,this),this._content},_getSuitableTableCell:function(){var e=null,t=this.get("host");t.getSelectedNodes().some(function(t){if(t.ancestor("td, th, caption",!0,this._stopAtContentEditableFilter)){e=t;var n=t.ancestor("caption",!0,this._stopAtContentEditableFilter);if(n){var r=n.get("parentNode");r&&(e=r.one("td, th"))}return!0}});if(e){var n=t.getSelectionFromNode(e);t.setSelection(n)}return e},_setTable:function(t){var n,r,i,s,o,u,a;t.preventDefault(),this.getDialogue({focusAfterHide:null}).hide(),n=t.currentTarget.ancestor(".atto_form").one(".caption"),r=t.currentTarget.ancestor(".atto_form").one(".rows"),i=t.currentTarget.ancestor(".atto_form").one(".columns"),s=t.currentTarget.ancestor(".atto_form").one(".headers"),this.get("host").setSelection(this._currentSelection);var f="\n";o="
"+f+""+f,o+=""+f,u=0;if(s.get("value")==="columns"||s.get("value")==="both"){u=1,o+=""+f+""+f;for(a=0;a'+f;o+=""+f+""+f}o+=""+f;for(;u"+f;for(a=0;a"+f:o+=''+f;o+=""+f}o+=""+f,o+="
"+e.Escape.html(n.get("value"))+"
"+f+"
",this.get("host").insertContentAtFocusPoint(o),this.markUpdated()},_showTableMenu:function(t){t.preventDefault();var r;if(!this._contextMenu){var s=e.Handlebars.compile(i),o=e.Node.create(s({elementid:this.get("host").get("elementid"),component:n}));this._contextMenu=new e.M.editor_atto.Menu({headerText:M.util.get_string("edittable","atto_table"),bodyContent:o}),r=this._contextMenu.get("boundingBox"),r.delegate("click",this._handleTableChange,"a",this),r.delegate("key",this._handleTableChange,"down:enter,space","a",this)}r=this._contextMenu.get("boundingBox"),this._lastTarget=t.tableCell.ancestor(".editor_atto_content td, .editor_atto_content th",!0),this._contextMenu.show(),this._contextMenu.align(t.tableCell,[e.WidgetPositionAlign.TL,e.WidgetPositionAlign.BL]),r.one("a")&&r.one("a").focus()},_handleTableChange:function(e){e.preventDefault(),this._contextMenu.hide();switch(e.target.getData("change")){case"addcolumnafter":this._addColumnAfter();break;case"addrowafter":this._addRowAfter();break;case"deleterow":this._deleteRow();break;case"deletecolumn":this._deleteColumn();break;case"moverowdown":this._moveRowDown();break;case"moverowup":this._moveRowUp();break;case"movecolumnleft":this._moveColumnLeft();break;case"movecolumnright":this._moveColumnRight()}},_getRowIndex:function(e){var t=e.ancestor("table"),n=e.ancestor("tr");if(!t||!n)return;var r=t.all("tr");return r.indexOf(n)},_getColumnIndex:function(e){var t=e.ancestor("tr");if(!t)return;var n=t.all("td, th");return n.indexOf(e)},_deleteRow:function(){var e= +this._lastTarget.ancestor("tr");e&&e.one("td")&&e.remove(!0),this.markUpdated()},_moveRowUp:function(){var e=this._lastTarget.ancestor("tr"),t=e.previous("tr");if(!e||!t)return;e.swap(t),this.markUpdated()},_moveColumnLeft:function(){var t=this._getColumnIndex(this._lastTarget),n=this._lastTarget.ancestor("table").all("tr"),r=new e.NodeList,i=new e.NodeList,s=!1;n.each(function(e){var n=e.all("td, th"),o=n.item(t),u=n.item(t-1);r.push(o),u&&(u.get("tagName")==="TD"&&(s=!0),i.push(u))});if(s&&i.size()>0){var o=0;for(o=0;o0){var o=0;for(o=0;o");t.replace(n),t=n}t.setHTML(" ")}),n.insert(newrow,t),this.markUpdated()},_addColumnAfter:function(){var t=this._getColumnIndex(this._lastTarget),n=this._lastTarget.ancestor("table"),r=n.all("tr");e.each(r,function(e){var n=e.one("td, th").cloneNode(!0);n.setHTML(" "),e.insert(n,t+1)},this),this.markUpdated()}})},"@VERSION@",{requires:["moodle-editor_atto-plugin","moodle-editor_atto-menu","event","event-valuechange"]}); diff --git a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js index 9cf781fbf86..fb51c1a83cc 100644 --- a/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js +++ b/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js @@ -150,30 +150,27 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi * @private */ _displayTableEditor: function(e) { - var selection = this.get('host').getSelectionParentNode(), - cell; - - if (!selection) { - // We don't have a current selection at all, so show the standard dialogue. - return this._displayDialogue(e); - } - - // Check all of the table cells found in the selection. - Y.one(selection).ancestors('th, td', true).each(function(node) { - if (this.editor.contains(node)) { - cell = node; - } - }, this); - + var cell = this._getSuitableTableCell(); if (cell) { // Add the cell to the EventFacade to save duplication in when showing the menu. e.tableCell = cell; return this._showTableMenu(e); } - return this._displayDialogue(e); }, + /** + * Returns whether or not the parameter node exists within the editor. + * + * @method _stopAtContentEditableFilter + * @param {Node} node + * @private + * @return {boolean} whether or not the parameter node exists within the editor. + */ + _stopAtContentEditableFilter: function(node) { + this.editor.contains(node); + }, + /** * Return the dialogue content for the tool, attaching any required * events. @@ -196,6 +193,44 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi return this._content; }, + /** + * Given the current selection, return a table cell suitable for table editing + * purposes, i.e. the first table cell selected, or the first cell in the table + * that the selection exists in, or null if not within a table. + * + * @method _getSuitableTableCell + * @private + * @return {Node} suitable target cell, or null if not within a table + */ + _getSuitableTableCell: function() { + var targetcell = null, + host = this.get('host'); + + host.getSelectedNodes().some(function (node) { + if (node.ancestor('td, th, caption', true, this._stopAtContentEditableFilter)) { + targetcell = node; + + var caption = node.ancestor('caption', true, this._stopAtContentEditableFilter); + if (caption) { + var table = caption.get('parentNode'); + if (table) { + targetcell = table.one('td, th'); + } + } + + // Once we've found a cell to target, we shouldn't need to keep looking. + return true; + } + }); + + if (targetcell) { + var selection = host.getSelectionFromNode(targetcell); + host.setSelection(selection); + } + + return targetcell; + }, + /** * Handle creation of a new table. * diff --git a/lib/editor/atto/plugins/table/yui/src/button/js/button.js b/lib/editor/atto/plugins/table/yui/src/button/js/button.js index f0e52b76d44..ee9f31a5c80 100644 --- a/lib/editor/atto/plugins/table/yui/src/button/js/button.js +++ b/lib/editor/atto/plugins/table/yui/src/button/js/button.js @@ -148,30 +148,27 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi * @private */ _displayTableEditor: function(e) { - var selection = this.get('host').getSelectionParentNode(), - cell; - - if (!selection) { - // We don't have a current selection at all, so show the standard dialogue. - return this._displayDialogue(e); - } - - // Check all of the table cells found in the selection. - Y.one(selection).ancestors('th, td', true).each(function(node) { - if (this.editor.contains(node)) { - cell = node; - } - }, this); - + var cell = this._getSuitableTableCell(); if (cell) { // Add the cell to the EventFacade to save duplication in when showing the menu. e.tableCell = cell; return this._showTableMenu(e); } - return this._displayDialogue(e); }, + /** + * Returns whether or not the parameter node exists within the editor. + * + * @method _stopAtContentEditableFilter + * @param {Node} node + * @private + * @return {boolean} whether or not the parameter node exists within the editor. + */ + _stopAtContentEditableFilter: function(node) { + this.editor.contains(node); + }, + /** * Return the dialogue content for the tool, attaching any required * events. @@ -194,6 +191,44 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi return this._content; }, + /** + * Given the current selection, return a table cell suitable for table editing + * purposes, i.e. the first table cell selected, or the first cell in the table + * that the selection exists in, or null if not within a table. + * + * @method _getSuitableTableCell + * @private + * @return {Node} suitable target cell, or null if not within a table + */ + _getSuitableTableCell: function() { + var targetcell = null, + host = this.get('host'); + + host.getSelectedNodes().some(function (node) { + if (node.ancestor('td, th, caption', true, this._stopAtContentEditableFilter)) { + targetcell = node; + + var caption = node.ancestor('caption', true, this._stopAtContentEditableFilter); + if (caption) { + var table = caption.get('parentNode'); + if (table) { + targetcell = table.one('td, th'); + } + } + + // Once we've found a cell to target, we shouldn't need to keep looking. + return true; + } + }); + + if (targetcell) { + var selection = host.getSelectionFromNode(targetcell); + host.setSelection(selection); + } + + return targetcell; + }, + /** * Handle creation of a new table. *