From ee616cff450b5ce80e62d0f7e126000d1771a718 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 27 Mar 2014 12:40:25 +0800 Subject: [PATCH] MDL-44760 editor_atto: Make menus consistent with Clean theme --- lib/editor/atto/plugins/backcolor/styles.css | 3 + lib/editor/atto/plugins/fontcolor/styles.css | 3 + .../moodle-atto_table-button-debug.js | 82 ++++-- .../moodle-atto_table-button-min.js | 4 +- .../moodle-atto_table-button.js | 82 ++++-- .../plugins/table/yui/src/button/js/button.js | 82 ++++-- lib/editor/atto/styles.css | 74 +----- .../moodle-editor_atto-menu-debug.js | 242 ++++++++++++++++-- .../moodle-editor_atto-menu-min.js | 2 +- .../moodle-editor_atto-menu.js | 241 +++++++++++++++-- .../moodle-editor_atto-plugin-debug.js | 158 ++---------- .../moodle-editor_atto-plugin-min.js | 4 +- .../moodle-editor_atto-plugin.js | 157 ++---------- .../src/editor/js/editor-plugin-buttons.js | 158 ++---------- lib/editor/atto/yui/src/editor/js/menu.js | 242 ++++++++++++++++-- theme/base/style/core.css | 67 +++++ 16 files changed, 1005 insertions(+), 596 deletions(-) create mode 100644 lib/editor/atto/plugins/backcolor/styles.css create mode 100644 lib/editor/atto/plugins/fontcolor/styles.css diff --git a/lib/editor/atto/plugins/backcolor/styles.css b/lib/editor/atto/plugins/backcolor/styles.css new file mode 100644 index 00000000000..2d0a90c3137 --- /dev/null +++ b/lib/editor/atto/plugins/backcolor/styles.css @@ -0,0 +1,3 @@ +.atto_backcolor_button .dropdown-menu { + min-width: inherit; +} diff --git a/lib/editor/atto/plugins/fontcolor/styles.css b/lib/editor/atto/plugins/fontcolor/styles.css new file mode 100644 index 00000000000..7333969bcaf --- /dev/null +++ b/lib/editor/atto/plugins/fontcolor/styles.css @@ -0,0 +1,3 @@ +.atto_fontcolor_button .dropdown-menu { + min-width: inherit; +} 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 8e9c9a5cc07..d339ccf67a1 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 @@ -57,20 +57,8 @@ var COMPONENT = 'atto_table', '' + '' + '', - CONTEXTMENUTEMPLATE = '' + - ''; - - CSS = { - }; + CSS = { + }; Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { @@ -102,6 +90,15 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi */ _lastTarget: null, + /** + * The list of menu items. + * + * @property _menuOptions + * @type Object + * @private + */ + _menuOptions: null, + initializer: function() { this.addButton({ icon: 'e/table', @@ -273,22 +270,59 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi var boundingBox; if (!this._contextMenu) { - var template = Y.Handlebars.compile(CONTEXTMENUTEMPLATE), - content = Y.Node.create(template({ - elementid: this.get('host').get('elementid'), - component: COMPONENT - })); + this._menuOptions = [ + { + text: M.util.get_string("addcolumnafter", COMPONENT), + data: { + change: "addcolumnafter" + } + }, { + text: M.util.get_string("addrowafter", COMPONENT), + data: { + change: "addrowafter" + } + }, { + text: M.util.get_string("moverowup", COMPONENT), + data: { + change: "moverowup" + } + }, { + text: M.util.get_string("moverowdown", COMPONENT), + data: { + change: "moverowdown" + } + }, { + text: M.util.get_string("movecolumnleft", COMPONENT), + data: { + change: "movecolumnleft" + } + }, { + text: M.util.get_string("movecolumnright", COMPONENT), + data: { + change: "movecolumnright" + } + }, { + text: M.util.get_string("deleterow", COMPONENT), + data: { + change: "deleterow" + } + }, { + text: M.util.get_string("deletecolumn", COMPONENT), + data: { + change: "deletecolumn" + } + } + ]; this._contextMenu = new Y.M.editor_atto.Menu({ - headerText: M.util.get_string('edittable', 'atto_table'), - bodyContent: content + items: this._menuOptions }); // Add event handlers for table control menus. boundingBox = this._contextMenu.get('boundingBox'); boundingBox.delegate('click', this._handleTableChange, 'a', this); - boundingBox.delegate('key', this._handleTableChange, 'down:enter,space', 'a', this); } + boundingBox = this._contextMenu.get('boundingBox'); // We store the cell of the last click (the control node is transient). @@ -297,6 +331,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi // Show the context menu, and align to the current position. this._contextMenu.show(); this._contextMenu.align(this.buttons.table, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]); + this._contextMenu.set('focusAfterHide', this.buttons[this.name]); // If there are any anchors in the bounding box, focus on the first. if (boundingBox.one('a')) { @@ -314,8 +349,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi _handleTableChange: function(e) { e.preventDefault(); + this._contextMenu.set('focusAfterHide', this.get('host').editor); // Hide the context menu. - this._contextMenu.hide(); + this._contextMenu.hide(e); // Make our changes. switch (e.target.getData('change')) { 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 5fd06f0e25d..47dea18d314 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='





',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(this.buttons.table,[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='





',i={};e.namespace("M.atto_table").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_contextMenu:null,_lastTarget:null,_menuOptions: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:i,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;this._contextMenu||(this._menuOptions=[{text:M.util.get_string("addcolumnafter",n),data:{change:"addcolumnafter"}},{text:M.util.get_string("addrowafter",n),data:{change:"addrowafter"}},{text:M.util.get_string("moverowup",n),data:{change:"moverowup"}},{text:M.util.get_string("moverowdown",n),data:{change:"moverowdown"}},{text:M.util.get_string("movecolumnleft",n),data:{change:"movecolumnleft"}},{text:M.util.get_string("movecolumnright",n),data:{change:"movecolumnright"}},{text:M.util.get_string("deleterow",n),data:{change:"deleterow"}},{text:M.util.get_string("deletecolumn",n),data:{change:"deletecolumn"}}],this._contextMenu=new e.M.editor_atto.Menu({items:this._menuOptions}),r=this._contextMenu.get("boundingBox"),r.delegate("click",this._handleTableChange,"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(this.buttons.table,[e.WidgetPositionAlign.TL,e.WidgetPositionAlign.BL]),this._contextMenu.set("focusAfterHide",this.buttons[this.name]),r.one("a")&&r.one("a").focus()},_handleTableChange:function(e){e.preventDefault(),this._contextMenu.set("focusAfterHide",this.get("host").editor),this._contextMenu.hide(e);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 8e9c9a5cc07..d339ccf67a1 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 @@ -57,20 +57,8 @@ var COMPONENT = 'atto_table', '' + '' + '', - CONTEXTMENUTEMPLATE = '' + - ''; - - CSS = { - }; + CSS = { + }; Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { @@ -102,6 +90,15 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi */ _lastTarget: null, + /** + * The list of menu items. + * + * @property _menuOptions + * @type Object + * @private + */ + _menuOptions: null, + initializer: function() { this.addButton({ icon: 'e/table', @@ -273,22 +270,59 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi var boundingBox; if (!this._contextMenu) { - var template = Y.Handlebars.compile(CONTEXTMENUTEMPLATE), - content = Y.Node.create(template({ - elementid: this.get('host').get('elementid'), - component: COMPONENT - })); + this._menuOptions = [ + { + text: M.util.get_string("addcolumnafter", COMPONENT), + data: { + change: "addcolumnafter" + } + }, { + text: M.util.get_string("addrowafter", COMPONENT), + data: { + change: "addrowafter" + } + }, { + text: M.util.get_string("moverowup", COMPONENT), + data: { + change: "moverowup" + } + }, { + text: M.util.get_string("moverowdown", COMPONENT), + data: { + change: "moverowdown" + } + }, { + text: M.util.get_string("movecolumnleft", COMPONENT), + data: { + change: "movecolumnleft" + } + }, { + text: M.util.get_string("movecolumnright", COMPONENT), + data: { + change: "movecolumnright" + } + }, { + text: M.util.get_string("deleterow", COMPONENT), + data: { + change: "deleterow" + } + }, { + text: M.util.get_string("deletecolumn", COMPONENT), + data: { + change: "deletecolumn" + } + } + ]; this._contextMenu = new Y.M.editor_atto.Menu({ - headerText: M.util.get_string('edittable', 'atto_table'), - bodyContent: content + items: this._menuOptions }); // Add event handlers for table control menus. boundingBox = this._contextMenu.get('boundingBox'); boundingBox.delegate('click', this._handleTableChange, 'a', this); - boundingBox.delegate('key', this._handleTableChange, 'down:enter,space', 'a', this); } + boundingBox = this._contextMenu.get('boundingBox'); // We store the cell of the last click (the control node is transient). @@ -297,6 +331,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi // Show the context menu, and align to the current position. this._contextMenu.show(); this._contextMenu.align(this.buttons.table, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]); + this._contextMenu.set('focusAfterHide', this.buttons[this.name]); // If there are any anchors in the bounding box, focus on the first. if (boundingBox.one('a')) { @@ -314,8 +349,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi _handleTableChange: function(e) { e.preventDefault(); + this._contextMenu.set('focusAfterHide', this.get('host').editor); // Hide the context menu. - this._contextMenu.hide(); + this._contextMenu.hide(e); // Make our changes. switch (e.target.getData('change')) { 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 5cc18aaa720..35c9ad7030b 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 @@ -55,20 +55,8 @@ var COMPONENT = 'atto_table', '' + '' + '', - CONTEXTMENUTEMPLATE = '' + - ''; - - CSS = { - }; + CSS = { + }; Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { @@ -100,6 +88,15 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi */ _lastTarget: null, + /** + * The list of menu items. + * + * @property _menuOptions + * @type Object + * @private + */ + _menuOptions: null, + initializer: function() { this.addButton({ icon: 'e/table', @@ -271,22 +268,59 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi var boundingBox; if (!this._contextMenu) { - var template = Y.Handlebars.compile(CONTEXTMENUTEMPLATE), - content = Y.Node.create(template({ - elementid: this.get('host').get('elementid'), - component: COMPONENT - })); + this._menuOptions = [ + { + text: M.util.get_string("addcolumnafter", COMPONENT), + data: { + change: "addcolumnafter" + } + }, { + text: M.util.get_string("addrowafter", COMPONENT), + data: { + change: "addrowafter" + } + }, { + text: M.util.get_string("moverowup", COMPONENT), + data: { + change: "moverowup" + } + }, { + text: M.util.get_string("moverowdown", COMPONENT), + data: { + change: "moverowdown" + } + }, { + text: M.util.get_string("movecolumnleft", COMPONENT), + data: { + change: "movecolumnleft" + } + }, { + text: M.util.get_string("movecolumnright", COMPONENT), + data: { + change: "movecolumnright" + } + }, { + text: M.util.get_string("deleterow", COMPONENT), + data: { + change: "deleterow" + } + }, { + text: M.util.get_string("deletecolumn", COMPONENT), + data: { + change: "deletecolumn" + } + } + ]; this._contextMenu = new Y.M.editor_atto.Menu({ - headerText: M.util.get_string('edittable', 'atto_table'), - bodyContent: content + items: this._menuOptions }); // Add event handlers for table control menus. boundingBox = this._contextMenu.get('boundingBox'); boundingBox.delegate('click', this._handleTableChange, 'a', this); - boundingBox.delegate('key', this._handleTableChange, 'down:enter,space', 'a', this); } + boundingBox = this._contextMenu.get('boundingBox'); // We store the cell of the last click (the control node is transient). @@ -295,6 +329,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi // Show the context menu, and align to the current position. this._contextMenu.show(); this._contextMenu.align(this.buttons.table, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]); + this._contextMenu.set('focusAfterHide', this.buttons[this.name]); // If there are any anchors in the bounding box, focus on the first. if (boundingBox.one('a')) { @@ -312,8 +347,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi _handleTableChange: function(e) { e.preventDefault(); + this._contextMenu.set('focusAfterHide', this.get('host').editor); // Hide the context menu. - this._contextMenu.hide(); + this._contextMenu.hide(e); // Make our changes. switch (e.target.getData('change')) { diff --git a/lib/editor/atto/styles.css b/lib/editor/atto/styles.css index 008dd7c1e0b..c416a6f91c6 100644 --- a/lib/editor/atto/styles.css +++ b/lib/editor/atto/styles.css @@ -98,15 +98,6 @@ div.editor_atto_toolbar div.atto_group { height: 16px; } -.atto_menu { - background: white; - padding: 1px; - white-space: inherit; -} - -.atto_menu a { - color: black; -} .atto_menuentry { clear: left; } @@ -167,33 +158,6 @@ div.editor_atto_content:hover .atto_control { .yui3-menu-hidden { display: none; } -.editor_atto_controlmenu li { - list-style-type: none; - padding: 0; - margin: 0; -} -.editor_atto_controlmenu ul { - padding: 0; - margin: 0; -} -.editor_atto_controlmenu .moodle-dialogue-hd, -.editor_atto_controlmenu .moodle-dialogue-ft { - display: none; -} - -.editor_atto_controlmenu ul li a:active, -.editor_atto_controlmenu ul li a:hover { - background-color: #0088cc; - color: white; -} -.editor_atto_controlmenu ul li a { - color: #333; - padding: 2px; - padding-left: 6px; - padding-right: 6px; - border-radius: 2px; - display: block; -} /* Get broken images back in firefox */ .editor_atto_content img:-moz-broken { @@ -202,41 +166,17 @@ div.editor_atto_content:hover .atto_control { min-height:24px; } +/* Atto menu styling */ .moodle-dialogue-base .editor_atto_menu .moodle-dialogue-content .moodle-dialogue-bd { - position: absolute; - text-align:left; + padding: 0; z-index: 1000; - display: block; - background-color: #fff; - border: 1px solid #ccc; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; - -webkit-box-shadow: 5px 5px 20px 0 #666666; - -moz-box-shadow: 5px 5px 20px 0 #666666; - box-shadow: 5px 5px 20px 0 #666666; - padding: 0px; } -.editor_atto_menu .menu { - margin: 0px; +.editor_atto_menu .dropdown-menu > li > a { + padding: 3px 14px; } -.editor_atto_menu .menu a { - display: block; - padding: 4px 1em 4px 1em; - color: #333333; -} - -.editor_atto_menu .menu a:hover { - color: #ffffff;background-color: #0088cc; -} -.editor_atto_menu .menu a.hidden { - display: none; -} -.editor_atto_menu .menu img { - vertical-align: middle; -} -.editor_atto_menu .menu > li { - display: block; +.editor_atto_menu .open ul.dropdown-menu { + padding-top: 5px; + padding-bottom: 5px; } diff --git a/lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu-debug.js b/lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu-debug.js index b2014f1bf0c..89f6576d2eb 100644 --- a/lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu-debug.js +++ b/lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu-debug.js @@ -25,6 +25,20 @@ YUI.add('moodle-editor_atto-menu', function (Y, NAME) { * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +var MENUDIALOGUE = '' + + '
' + + '' + + '
'; + /** * A Menu for the Atto editor used in Moodle. * @@ -43,24 +57,169 @@ Menu = function() { Y.extend(Menu, M.core.dialogue, { + /** + * A list of the menu handlers which have been attached here. + * + * @property _menuHandlers + * @type Array + * @private + */ + _menuHandlers: null, + initializer: function(config) { - var body, headertext, bb; - Menu.superclass.initializer.call(this, config); + var headertext, + bb; + + this._menuHandlers = []; + + // Create the actual button. + var template = Y.Handlebars.compile(MENUDIALOGUE), + menu = Y.Node.create(template({ + config: config + })); + this.set('bodyContent', menu); bb = this.get('boundingBox'); bb.addClass('editor_atto_controlmenu'); - - // Close the menu when clicked outside (excluding the button that opened the menu). - body = this.bodyNode; + bb.addClass('editor_atto_menu'); + bb.one('.moodle-dialogue-wrap') + .removeClass('moodle-dialogue-wrap') + .addClass('moodle-dialogue-content'); headertext = Y.Node.create('

') .addClass('accesshide') .setHTML(this.get('headerText')); - body.prepend(headertext); + this.get('bodyContent').prepend(headertext); - this.get('hideOn'); + // Hide the header and footer node entirely. + this.headerNode.hide(); + this.footerNode.hide(); + + this._setupHandlers(); + }, + + /** + * Setup the Event handlers. + * + * @method _setupHandlers + * @private + */ + _setupHandlers: function() { + var contentBox = this.get('contentBox'); + // Handle menu item selection. + this._menuHandlers.push( + // Select the menu item on space, and enter. + contentBox.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this), + + // Move up and down the menu on up/down. + contentBox.delegate('key', this._handleKeyboardEvent, 'down:38,40', '.dropdown-menu', this), + + // Hide the menu when clicking outside of it. + contentBox.on('focusoutside', this.hide, this), + + // Hide the menu on left/right, and escape keys. + contentBox.delegate('key', this.hide, 'down:37,39,esc', '.dropdown-menu', this) + ); + }, + + /** + * Simulate other types of menu selection. + * + * @method _chooseMenuItem + * @param {EventFacade} e + */ + _chooseMenuItem: function(e) { + e.target.simulate('click'); + e.preventDefault(); + }, + + /** + * Hide a menu, removing all of the event handlers which trigger the hide. + * + * @method hide + * @param {EventFacade} e + */ + hide: function(e) { + if (this.get('preventHideMenu') === true) { + return; + } + + // We must prevent the default action (left/right/escape) because + // there are other listeners on the toolbar which will focus on the + // editor. + if (e) { + e.preventDefault(); + } + + return Menu.superclass.hide.call(this, arguments); + }, + + /** + * Implement arrow-key navigation for the items in a toolbar menu. + * + * @method _handleKeyboardEvent + * @param {EventFacade} e The keyboard event. + * @static + */ + _handleKeyboardEvent: function(e) { + // Prevent the default browser behaviour. + e.preventDefault(); + + // Get a list of all buttons in the menu. + var buttons = e.currentTarget.all('a[role="menuitem"]'); + + // On cursor moves we loops through the buttons. + var found = false, + index = 0, + direction = 1, + checkCount = 0, + current = e.target.ancestor('a[role="menuitem"]', true); + + // Determine which button is currently selected. + while (!found && index < buttons.size()) { + if (buttons.item(index) === current) { + found = true; + } else { + index++; + } + } + + if (!found) { + Y.log("Unable to find this menu item in the menu", 'debug', LOGNAME); + return; + } + + if (e.keyCode === 38) { + // Moving up so reverse the direction. + direction = -1; + } + + // Try to find the next + do { + index += direction; + if (index < 0) { + index = buttons.size() - 1; + } else if (index >= buttons.size()) { + // Handle wrapping. + index = 0; + } + next = buttons.item(index); + + // Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item. + checkCount++; + // Loop while: + // * we are not in a loop and have not already checked every button; and + // * we are on a different button; and + // * the next menu item is not hidden. + } while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden')); + + if (next) { + next.focus(); + } + + e.preventDefault(); + e.stopImmediatePropagation(); } - }, { NAME: "menu", ATTRS: { @@ -89,17 +248,6 @@ Y.Base.modifyAttrs(Menu, { value: 'auto' }, - /** - * TODO - chekc that this is needed - * The footer content. - * - * @attribute footerContent - * @default '' - */ - footerContent: { - value: '' - }, - /** * When to hide this menu. * @@ -116,6 +264,62 @@ Y.Base.modifyAttrs(Menu, { eventName: 'clickoutside' } ] + }, + + /** + * The default list of extra classes for this menu. + * + * @attribute extraClasses + * @type Array + * @default editor_atto_menu + */ + extraClasses: { + value: [ + 'editor_atto_menu' + ] + }, + + /** + * Override the responsive nature of the core dialogues. + * + * @attribute responsive + * @type boolean + * @default false + */ + responsive: { + value: false + }, + + /** + * The default visibility of the menu. + * + * @attribute visible + * @type boolean + * @default false + */ + visible: { + value: false + }, + + /** + * Whether to centre the menu. + * + * @attribute center + * @type boolean + * @default false + */ + center: { + value: false + }, + + /** + * Hide the close button. + * @attribute closeButton + * @type boolean + * @default false + */ + closeButton: { + value: false } }); diff --git a/lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu-min.js b/lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu-min.js index 77fb95dce4d..d1b89010bc6 100644 --- a/lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu-min.js +++ b/lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu-min.js @@ -1 +1 @@ -YUI.add("moodle-editor_atto-menu",function(e,t){Menu=function(){Menu.superclass.constructor.apply(this,arguments)},e.extend(Menu,M.core.dialogue,{initializer:function(t){var n,r,i;Menu.superclass.initializer.call(this,t),i=this.get("boundingBox"),i.addClass("editor_atto_controlmenu"),n=this.bodyNode,r=e.Node.create("

").addClass("accesshide").setHTML(this.get("headerText")),n.prepend(r),this.get("hideOn")}},{NAME:"menu",ATTRS:{headerText:{value:""}}}),e.Base.modifyAttrs(Menu,{width:{value:"auto"},footerContent:{value:""},hideOn:{value:[{eventName:"clickoutside"}]}}),e.namespace("M.editor_atto").Menu=Menu},"@VERSION@",{requires:["moodle-core-notification-dialogue","node","event","event-custom"]}); +YUI.add("moodle-editor_atto-menu",function(e,t){var n='
';Menu=function(){Menu.superclass.constructor.apply(this,arguments)},e.extend(Menu,M.core.dialogue,{_menuHandlers:null,initializer:function(t){var r,i;this._menuHandlers=[];var s=e.Handlebars.compile(n),o=e.Node.create(s({config:t}));this.set("bodyContent",o),i=this.get("boundingBox"),i.addClass("editor_atto_controlmenu"),i.addClass("editor_atto_menu"),i.one(".moodle-dialogue-wrap").removeClass("moodle-dialogue-wrap").addClass("moodle-dialogue-content"),r=e.Node.create("

").addClass("accesshide").setHTML(this.get("headerText")),this.get("bodyContent").prepend(r),this.headerNode.hide(),this.footerNode.hide(),this._setupHandlers()},_setupHandlers:function(){var e=this.get("contentBox");this._menuHandlers.push(e.delegate("key",this._chooseMenuItem,"32, enter",".atto_menuentry",this),e.delegate("key",this._handleKeyboardEvent,"down:38,40",".dropdown-menu",this),e.on("focusoutside",this.hide,this),e.delegate("key",this.hide,"down:37,39,esc",".dropdown-menu",this))},_chooseMenuItem:function(e){e.target.simulate("click"),e.preventDefault()},hide:function(e){if(this.get("preventHideMenu")===!0)return;return e&&e.preventDefault(),Menu.superclass.hide.call(this,arguments)},_handleKeyboardEvent:function(e){e.preventDefault();var t=e.currentTarget.all('a[role="menuitem"]'),n=!1,r=0,i=1,s=0,o=e.target.ancestor('a[role="menuitem"]',!0);while(!n&&r=t.size()&&(r=0),next=t.item(r),s++;while(s' + + '' + + ''; + /** * A Menu for the Atto editor used in Moodle. * @@ -43,24 +57,168 @@ Menu = function() { Y.extend(Menu, M.core.dialogue, { + /** + * A list of the menu handlers which have been attached here. + * + * @property _menuHandlers + * @type Array + * @private + */ + _menuHandlers: null, + initializer: function(config) { - var body, headertext, bb; - Menu.superclass.initializer.call(this, config); + var headertext, + bb; + + this._menuHandlers = []; + + // Create the actual button. + var template = Y.Handlebars.compile(MENUDIALOGUE), + menu = Y.Node.create(template({ + config: config + })); + this.set('bodyContent', menu); bb = this.get('boundingBox'); bb.addClass('editor_atto_controlmenu'); - - // Close the menu when clicked outside (excluding the button that opened the menu). - body = this.bodyNode; + bb.addClass('editor_atto_menu'); + bb.one('.moodle-dialogue-wrap') + .removeClass('moodle-dialogue-wrap') + .addClass('moodle-dialogue-content'); headertext = Y.Node.create('

') .addClass('accesshide') .setHTML(this.get('headerText')); - body.prepend(headertext); + this.get('bodyContent').prepend(headertext); - this.get('hideOn'); + // Hide the header and footer node entirely. + this.headerNode.hide(); + this.footerNode.hide(); + + this._setupHandlers(); + }, + + /** + * Setup the Event handlers. + * + * @method _setupHandlers + * @private + */ + _setupHandlers: function() { + var contentBox = this.get('contentBox'); + // Handle menu item selection. + this._menuHandlers.push( + // Select the menu item on space, and enter. + contentBox.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this), + + // Move up and down the menu on up/down. + contentBox.delegate('key', this._handleKeyboardEvent, 'down:38,40', '.dropdown-menu', this), + + // Hide the menu when clicking outside of it. + contentBox.on('focusoutside', this.hide, this), + + // Hide the menu on left/right, and escape keys. + contentBox.delegate('key', this.hide, 'down:37,39,esc', '.dropdown-menu', this) + ); + }, + + /** + * Simulate other types of menu selection. + * + * @method _chooseMenuItem + * @param {EventFacade} e + */ + _chooseMenuItem: function(e) { + e.target.simulate('click'); + e.preventDefault(); + }, + + /** + * Hide a menu, removing all of the event handlers which trigger the hide. + * + * @method hide + * @param {EventFacade} e + */ + hide: function(e) { + if (this.get('preventHideMenu') === true) { + return; + } + + // We must prevent the default action (left/right/escape) because + // there are other listeners on the toolbar which will focus on the + // editor. + if (e) { + e.preventDefault(); + } + + return Menu.superclass.hide.call(this, arguments); + }, + + /** + * Implement arrow-key navigation for the items in a toolbar menu. + * + * @method _handleKeyboardEvent + * @param {EventFacade} e The keyboard event. + * @static + */ + _handleKeyboardEvent: function(e) { + // Prevent the default browser behaviour. + e.preventDefault(); + + // Get a list of all buttons in the menu. + var buttons = e.currentTarget.all('a[role="menuitem"]'); + + // On cursor moves we loops through the buttons. + var found = false, + index = 0, + direction = 1, + checkCount = 0, + current = e.target.ancestor('a[role="menuitem"]', true); + + // Determine which button is currently selected. + while (!found && index < buttons.size()) { + if (buttons.item(index) === current) { + found = true; + } else { + index++; + } + } + + if (!found) { + return; + } + + if (e.keyCode === 38) { + // Moving up so reverse the direction. + direction = -1; + } + + // Try to find the next + do { + index += direction; + if (index < 0) { + index = buttons.size() - 1; + } else if (index >= buttons.size()) { + // Handle wrapping. + index = 0; + } + next = buttons.item(index); + + // Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item. + checkCount++; + // Loop while: + // * we are not in a loop and have not already checked every button; and + // * we are on a different button; and + // * the next menu item is not hidden. + } while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden')); + + if (next) { + next.focus(); + } + + e.preventDefault(); + e.stopImmediatePropagation(); } - }, { NAME: "menu", ATTRS: { @@ -89,17 +247,6 @@ Y.Base.modifyAttrs(Menu, { value: 'auto' }, - /** - * TODO - chekc that this is needed - * The footer content. - * - * @attribute footerContent - * @default '' - */ - footerContent: { - value: '' - }, - /** * When to hide this menu. * @@ -116,6 +263,62 @@ Y.Base.modifyAttrs(Menu, { eventName: 'clickoutside' } ] + }, + + /** + * The default list of extra classes for this menu. + * + * @attribute extraClasses + * @type Array + * @default editor_atto_menu + */ + extraClasses: { + value: [ + 'editor_atto_menu' + ] + }, + + /** + * Override the responsive nature of the core dialogues. + * + * @attribute responsive + * @type boolean + * @default false + */ + responsive: { + value: false + }, + + /** + * The default visibility of the menu. + * + * @attribute visible + * @type boolean + * @default false + */ + visible: { + value: false + }, + + /** + * Whether to centre the menu. + * + * @attribute center + * @type boolean + * @default false + */ + center: { + value: false + }, + + /** + * Hide the close button. + * @attribute closeButton + * @type boolean + * @default false + */ + closeButton: { + value: false } }); diff --git a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js index 28b4239df79..f98fafc14fb 100644 --- a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js +++ b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js @@ -177,20 +177,7 @@ var MENUTEMPLATE = '' + '' + '' + - '', - MENUDIALOGUE = '' + - '
' + - '' + - '
'; + ''; var DISABLED = 'disabled', HIGHLIGHT = 'highlight', @@ -322,6 +309,7 @@ EditorPluginButtons.prototype = { } else { buttonClass = buttonClass + '_' + config.buttonName; } + config.buttonClass = buttonClass; // Normalize icon configuration. config = this._normalizeIcon(config); @@ -465,6 +453,7 @@ EditorPluginButtons.prototype = { } else { buttonClass = buttonClass + '_' + config.buttonName; } + config.buttonClass = buttonClass; // Normalize icon configuration. config = this._normalizeIcon(config); @@ -499,7 +488,7 @@ EditorPluginButtons.prototype = { // Add the standard click handler to the menu. this._buttonHandlers.push( this.toolbar.delegate('click', this._showToolbarMenu, '.' + buttonClass, this, config), - this.toolbar.delegate('key', this._showToolbarMenu, '40, 32, enter', '.' + buttonClass, this, config) + this.toolbar.delegate('key', this._showToolbarMenuAndFocus, '40, 32, enter', '.' + buttonClass, this, config) ); // Add the button reference to the buttons array for later reference. @@ -545,45 +534,15 @@ EditorPluginButtons.prototype = { } config.overlayWidth = parseInt(config.overlayWidth, 10) + 'em'; - // Create the actual button. - var template = Y.Handlebars.compile(MENUDIALOGUE), - menu = Y.Node.create(template({ - config: config - })); + this.menus[config.buttonClass] = new Y.M.editor_atto.Menu(config); - // Create the dialogue. - this.menus[config.buttonClass] = new M.core.dialogue({ - bodyContent: menu, - width: null, - visible: false, - center: false, - closeButton: false, - responsive: false, - extraClasses: ['editor_atto_menu'] - }); - - menuDialogue = this.menus[config.buttonClass]; - - // Hide the header node entirely. - menuDialogue.headerNode.hide(); - - // Handle menu item selection. - this._buttonHandlers.push( - menu.delegate('click', this._chooseMenuItem, '.atto_menuentry a', this, config, menuDialogue), - - // Select the menu item on space, and enter. - menu.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this, config, menuDialogue), - - // Move up and down the menu on up/down. - menu.delegate('key', this._menuKeyboardNavigation, 'down:38,40', '.menu', this), - - // Hide the menu on left/right. - menu.delegate('key', this._hideMenu, 'down:37,39', '.menu', this, menuDialogue) - ); + this.menus[config.buttonClass].get('contentBox').delegate('click', + this._chooseMenuItem, '.atto_menuentry a', this, config); } // Ensure that we focus on this button next time. var creatorButton = this.buttons[config.buttonName]; + creatorButton.focus(); this.get('host')._setTabFocus(creatorButton); // Get a reference to the menu dialogue. @@ -591,36 +550,31 @@ EditorPluginButtons.prototype = { // Focus on the button by default after hiding this menu. menuDialogue.set('focusAfterHide', creatorButton); + menuDialogue.on('focusAfterHide', function(e) { + console.log(e); + }); // Display the menu. menuDialogue.show(); // Position it next to the button which opened it. menuDialogue.align(this.buttons[config.buttonName], [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]); - - // And focus on the first element in the menu. - menuDialogue.get('boundingBox').one('a').focus(); - - // Hide the menu when clicking outside of it. - this._menuHideHandlers.push( - menuDialogue.get('boundingBox').on('focusoutside', this._hideMenu, this, menuDialogue) - ); }, /** - * Hide a menu, removing all of the event handlers which trigger the hide. + * Display a toolbar menu and focus upon the first item. * - * @method _hideMenu + * @method _showToolbarMenuAndFocus * @param {EventFacade} e - * @param {M.core.dialogue} menuDialogue The Dialogue to hide. + * @param {object} config The configuration for the whole toolbar. + * @param {Number} [config.overlayWidth=14] The width of the menu * @private */ - _hideMenu: function(e, menuDialogue) { - if (menuDialogue.get('preventHideMenu') === true) { - return; - } - menuDialogue.hide(); - new Y.EventHandle(this._menuHideHandlers).detach(); + _showToolbarMenuAndFocus: function(e, config) { + this._showToolbarMenu(e, config); + + // Focus on the first element in the menu. + this.menus[config.buttonClass].get('boundingBox').one('a').focus(); }, /** @@ -633,12 +587,14 @@ EditorPluginButtons.prototype = { * @private */ _chooseMenuItem: function(e, config, menuDialogue) { - // Get the index from the clicked anchor. + // Get the index from the clicked anchor. var index = e.target.ancestor('a', true).getData('index'), // And the normalized callback configuration. buttonConfig = this._normalizeCallback(config.items[index], config.globalItemConfig); + menuDialogue = this.menus[config.buttonClass]; + // Prevent the dialogue to be closed because of some browser weirdness. menuDialogue.set('preventHideMenu', true); @@ -648,9 +604,10 @@ EditorPluginButtons.prototype = { // Cancel the hide menu prevention. menuDialogue.set('preventHideMenu', false); + console.log('Menu item chosen'); // Set the focus after hide so that focus is returned to the editor and changes are made correctly. menuDialogue.set('focusAfterHide', this.get('host').editor); - this._hideMenu(e, menuDialogue); + menuDialogue.hide(e); }, /** @@ -949,71 +906,6 @@ EditorPluginButtons.prototype = { return this; }, - /** - * Implement arrow-key navigation for the items in a toolbar menu. - * - * @method _menuKeyboardNavigation - * @param {EventFacade} e The keyboard event. - * @private - */ - _menuKeyboardNavigation: function(e) { - // Prevent the default browser behaviour. - e.preventDefault(); - - // Get a list of all buttons in the menu. - var buttons = e.currentTarget.all('a[role="menuitem"]'); - - // On cursor moves we loops through the buttons. - var found = false, - index = 0, - direction = 1, - checkCount = 0, - current = e.target.ancestor('a[role="menuitem"]', true); - - // Determine which button is currently selected. - while (!found && index < buttons.size()) { - if (buttons.item(index) === current) { - found = true; - } else { - index++; - } - } - - if (!found) { - Y.log("Unable to find this menu item in the menu", 'debug', LOGNAME); - return; - } - - if (e.keyCode === 38) { - // Moving up so reverse the direction. - direction = -1; - } - - // Try to find the next - do { - index += direction; - if (index < 0) { - index = buttons.size() - 1; - } else if (index >= buttons.size()) { - // Handle wrapping. - index = 0; - } - next = buttons.item(index); - - // Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item. - checkCount++; - // Loop while: - // * we are not in a loop and have not already checked every button; and - // * we are on a different button; and - // * the next menu item is not hidden. - } while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden')); - - if (next) { - next.focus(); - } - }, - - /** * Get the default meta key to use with keyboard events. * diff --git a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js index f8d557ee7b1..87d10599dfb 100644 --- a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js +++ b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js @@ -1,2 +1,2 @@ -YUI.add("moodle-editor_atto-plugin",function(e,t){function n(){n.superclass.constructor.apply(this,arguments)}function c(){}function h(){}var r=".atto_group.",i="_group";e.extend(n,e.Base,{name:null,editor:null,toolbar:null,initializer:function(e){this.name=e.name,this.toolbar=e.toolbar,this.editor=e.editor,this.buttons={},this.buttonNames=[],this.buttonStates={},this.menus={},this._buttonHandlers=[],this._menuHideHandlers=[]},markUpdated:function(){return this.get("host").updateOriginal()}},{NAME:"editorPlugin",ATTRS:{host:{writeOnce:!0},group:{writeOnce:!0,getter:function(t){var n=this.toolbar.one(r+t+i);return n||(n=e.Node.create('
'),this.toolbar.append(n)),n}}}}),e.namespace("M.editor_atto").EditorPlugin=n;var s='',o='
',u="disabled",a="highlight",f="moodle-editor_atto-editor-plugin",l={EDITORWRAPPER:".editor_atto_content"};c.ATTRS={},c.prototype={buttons:null,buttonNames:null,buttonStates:null,menus:null,DISABLED:0,ENABLED:1,_buttonHandlers:null,_menuHideHandlers:null,addButton:function(t){var n=this.get("group"),r=this.name,i="atto_"+r+"_button",s,o=this.get("host");t.exec&&(i=i+"_"+t.exec),t.buttonName?i=i+"_"+t.buttonName:t.buttonName=t.exec||r,t=this._normalizeIcon(t),t.title||(t.title="pluginname");var u=M.util.get_string(t.title,"atto_"+r);s=e.Node.create('"),s.setAttribute("title",u),n.append(s);var a=this.toolbar.getAttribute("aria-activedescendant");a||(s.setAttribute("tabindex","0"),this.toolbar.setAttribute("aria-activedescendant",s.generateID()),this.get("host")._tabFocus=s),t=this._normalizeCallback(t),this._buttonHandlers.push(this.toolbar.delegate("click",t.callback,"."+i,this)),t.keys&&this._addKeyboardListener(t.callback,t.keys,i);if(t.tags){var f=null;typeof t.tagMatchRequiresAll=="boolean"&&(f=t.tagMatchRequiresAll),this._buttonHandlers.push(o.on("atto:selectionchanged",function(e){o.selectionFilterMatches(t.tags,e.selectedNodes,f)?this.highlightButtons(t.buttonName):this.unHighlightButtons(t.buttonName)},this))}return this.buttonNames.push(t.buttonName),this.buttons[t.buttonName]=s,this.buttonStates[t.buttonName]=this.ENABLED,s},addBasicButton:function(e){return e.exec?(e.icon||(e.icon="e/"+e.exec),e.callback=function(){document.execCommand(e.exec,!1,null),this.markUpdated()},this.addButton(e)):null},addToolbarMenu:function(t){var n=this.get("group"),r=this.name,i="atto_"+r+"_button",o,u;t.buttonName?i=i+"_"+t.buttonName:t.buttonName=r,t=this._normalizeIcon(t),t.title||(t.title="pluginname");var a=M.util.get_string(t.title,"atto_"+r);t.menuColor||(t.menuColor="transparent");var f=e.Handlebars.compile(s);return o=e.Node.create(f({buttonClass:i,config:t,title:a})),n.append(o),u=this.toolbar.getAttribute("aria-activedescendant"),u||(o.setAttribute("tabindex","0"),this.toolbar.setAttribute("aria-activedescendant",o.generateID())),this._buttonHandlers.push(this.toolbar.delegate("click",this._showToolbarMenu,"."+i,this,t),this.toolbar.delegate("key",this._showToolbarMenu,"40, 32, enter","."+i,this,t)),this.buttonNames.push(t.buttonName),this.buttons[t.buttonName]=o,this.buttonStates[t.buttonName]=this.ENABLED,o},_showToolbarMenu:function(t,n){t.preventDefault();if(!this.isEnabled())return;if(t.currentTarget.ancestor("button",!0).hasAttribute(u))return;var r;if(!this.menus[n.buttonClass]){n.overlayWidth||(n.overlayWidth="14"),n.innerOverlayWidth||(n.innerOverlayWidth=parseInt(n.overlayWidth,10)-2+"em"),n.overlayWidth=parseInt(n.overlayWidth,10)+"em";var i=e.Handlebars.compile(o),s=e.Node.create(i({config:n}));this.menus[n.buttonClass]=new M.core.dialogue({bodyContent:s,width:null,visible:!1,center:!1,closeButton:!1,responsive:!1,extraClasses:["editor_atto_menu"]}),r=this.menus[n.buttonClass],r.headerNode.hide(),this._buttonHandlers.push(s.delegate("click",this._chooseMenuItem,".atto_menuentry a",this,n,r),s.delegate("key",this._chooseMenuItem,"32, enter",".atto_menuentry",this,n,r),s.delegate("key",this._menuKeyboardNavigation,"down:38,40",".menu",this),s.delegate("key",this._hideMenu,"down:37,39",".menu",this,r))}var a=this.buttons[n.buttonName];this.get("host")._setTabFocus(a),r=this.menus[n.buttonClass],r.set("focusAfterHide",a),r.show(),r.align(this.buttons[n.buttonName],[e.WidgetPositionAlign.TL,e.WidgetPositionAlign.BL]),r.get("boundingBox").one("a").focus(),this._menuHideHandlers.push(r.get("boundingBox").on("focusoutside",this._hideMenu,this,r))},_hideMenu:function(t,n){if(n.get("preventHideMenu")===!0)return;n.hide(),(new e.EventHandle(this._menuHideHandlers)).detach()},_chooseMenuItem:function(e,t,n){var r=e.target.ancestor("a",!0).getData("index"),i=this._normalizeCallback(t.items[r],t.globalItemConfig);n.set("preventHideMenu",!0),i.callback(e,i._callback,i.callbackArgs),n.set("preventHideMenu",!1),n.set("focusAfterHide",this.get("host").editor),this._hideMenu(e,n)},_normalizeCallback:function(t,n){return t._callbackNormalized?t:(n||(n={}),typeof t.wrapCallback=="undefined"&&(t.wrapCallback=n.wrapCallback||!0),t.wrapCallback?(t._callback=t.callback||n.callback,t.callback=e.rbind(this._callbackWrapper,this,t._callback,t.callbackArgs)):t.callback=t.callback||n.callback,t._callbackNormalized=!0,t)},_normalizeIcon:function(e){return e.iconurl|| -(e.iconComponent||(e.iconComponent="core"),e.iconurl=M.util.image_url(e.icon,e.iconComponent)),e},_callbackWrapper:function(e,t,n){e.preventDefault();if(!this.isEnabled())return;var r=e.currentTarget.ancestor("button",!0);if(r&&r.hasAttribute(u))return;this.get("host").isActive()||this.get("host").focus(),r&&this.get("host")._setTabFocus(r);var i=[e,n];return t.apply(this,i)},_addKeyboardListener:function(t,n,r){var i="key",s=l.EDITORWRAPPER,o;if(e.Lang.isArray(n))return e.Array.each(n,function(e){this._addKeyboardListener(t,e)},this),this;typeof n=="object"?(n.eventtype&&(i=n.eventtype),n.container&&(s=n.container),o=n.keyCodes):o=this._getKeyEvent()+n+this._getDefaultMetaKey(),this._buttonHandlers.push(this.editor.delegate(i,t,o,s,this))},isEnabled:function(){var t=e.Object.some(this.buttonStates,function(e){return e===this.ENABLED},this);return t},disableButtons:function(e){return this._setButtonState(!1,e)},enableButtons:function(e){return this._setButtonState(!0,e)},_setButtonState:function(t,n){var r="setAttribute";return t&&(r="removeAttribute"),n?this.buttons[n]&&(this.buttons[n][r](u,u),this.buttonStates[n]=t?this.ENABLED:this.DISABLED):e.Array.each(this.buttonNames,function(e){this.buttons[e][r](u,u),this.buttonStates[e]=t?this.ENABLED:this.DISABLED},this),this},highlightButtons:function(e){return this._changeButtonHighlight(!0,e)},unHighlightButtons:function(e){return this._changeButtonHighlight(!1,e)},_changeButtonHighlight:function(t,n){var r="addClass";return t||(r="removeClass"),n?this.buttons[n]&&this.buttons[n][r](a):e.Object.each(this.buttons,function(e){e[r](a)},this),this},_menuKeyboardNavigation:function(e){e.preventDefault();var t=e.currentTarget.all('a[role="menuitem"]'),n=!1,r=0,i=1,s=0,o=e.target.ancestor('a[role="menuitem"]',!0);while(!n&&r=t.size()&&(r=0),next=t.item(r),s++;while(s'),this.toolbar.append(n)),n}}}}),e.namespace("M.editor_atto").EditorPlugin=n;var s='',o="disabled",u="highlight",a="moodle-editor_atto-editor-plugin",f={EDITORWRAPPER:".editor_atto_content"};l.ATTRS={},l.prototype={buttons:null,buttonNames:null,buttonStates:null,menus:null,DISABLED:0,ENABLED:1,_buttonHandlers:null,_menuHideHandlers:null,addButton:function(t){var n=this.get("group"),r=this.name,i="atto_"+r+"_button",s,o=this.get("host");t.exec&&(i=i+"_"+t.exec),t.buttonName?i=i+"_"+t.buttonName:t.buttonName=t.exec||r,t.buttonClass=i,t=this._normalizeIcon(t),t.title||(t.title="pluginname");var u=M.util.get_string(t.title,"atto_"+r);s=e.Node.create('"),s.setAttribute("title",u),n.append(s);var a=this.toolbar.getAttribute("aria-activedescendant");a||(s.setAttribute("tabindex","0"),this.toolbar.setAttribute("aria-activedescendant",s.generateID()),this.get("host")._tabFocus=s),t=this._normalizeCallback(t),this._buttonHandlers.push(this.toolbar.delegate("click",t.callback,"."+i,this)),t.keys&&this._addKeyboardListener(t.callback,t.keys,i);if(t.tags){var f=null;typeof t.tagMatchRequiresAll=="boolean"&&(f=t.tagMatchRequiresAll),this._buttonHandlers.push(o.on("atto:selectionchanged",function(e){o.selectionFilterMatches(t.tags,e.selectedNodes,f)?this.highlightButtons(t.buttonName):this.unHighlightButtons(t.buttonName)},this))}return this.buttonNames.push(t.buttonName),this.buttons[t.buttonName]=s,this.buttonStates[t.buttonName]=this.ENABLED,s},addBasicButton:function(e){return e.exec?(e.icon||(e.icon="e/"+e.exec),e.callback=function(){document.execCommand(e.exec,!1,null),this.markUpdated()},this.addButton(e)):null},addToolbarMenu:function(t){var n=this.get("group"),r=this.name,i="atto_"+r+"_button",o,u;t.buttonName?i=i+"_"+t.buttonName:t.buttonName=r,t.buttonClass=i,t=this._normalizeIcon(t),t.title||(t.title="pluginname");var a=M.util.get_string(t.title,"atto_"+r);t.menuColor||(t.menuColor="transparent");var f=e.Handlebars.compile(s);return o=e.Node.create(f({buttonClass:i,config:t,title:a})),n.append(o),u=this.toolbar.getAttribute("aria-activedescendant"),u||(o.setAttribute("tabindex","0"),this.toolbar.setAttribute("aria-activedescendant",o.generateID())),this._buttonHandlers.push(this.toolbar.delegate("click",this._showToolbarMenu,"."+i,this,t),this.toolbar.delegate("key",this._showToolbarMenuAndFocus,"40, 32, enter","."+i,this,t)),this.buttonNames.push(t.buttonName),this.buttons[t.buttonName]=o,this.buttonStates[t.buttonName]=this.ENABLED,o},_showToolbarMenu:function(t,n){t.preventDefault();if(!this.isEnabled())return;if(t.currentTarget.ancestor("button",!0).hasAttribute(o))return;var r;this.menus[n.buttonClass]||(n.overlayWidth||(n.overlayWidth="14"),n.innerOverlayWidth||(n.innerOverlayWidth=parseInt(n.overlayWidth,10)-2+"em"),n.overlayWidth=parseInt(n.overlayWidth,10)+"em",this.menus[n.buttonClass]=new e.M.editor_atto.Menu(n),this.menus[n.buttonClass].get("contentBox").delegate("click",this._chooseMenuItem,".atto_menuentry a",this,n));var i=this.buttons[n.buttonName];i.focus(),this.get("host")._setTabFocus(i),r=this.menus[n.buttonClass],r.set("focusAfterHide",i),r.on("focusAfterHide",function(e){console.log(e)}),r.show(),r.align(this.buttons[n.buttonName],[e.WidgetPositionAlign.TL,e.WidgetPositionAlign.BL])},_showToolbarMenuAndFocus:function(e,t){this._showToolbarMenu(e,t),this.menus[t.buttonClass].get("boundingBox").one("a").focus()},_chooseMenuItem:function(e,t,n){var r=e.target.ancestor("a",!0).getData("index"),i=this._normalizeCallback(t.items[r],t.globalItemConfig);n=this.menus[t.buttonClass],n.set("preventHideMenu",!0),i.callback(e,i._callback,i.callbackArgs),n.set("preventHideMenu",!1),console.log("Menu item chosen"),n.set("focusAfterHide",this.get("host").editor),n.hide(e)},_normalizeCallback:function(t,n){return t._callbackNormalized?t:(n||(n={}),typeof t.wrapCallback=="undefined"&&(t.wrapCallback=n.wrapCallback||!0),t.wrapCallback?(t._callback=t.callback||n.callback,t.callback=e.rbind(this._callbackWrapper,this,t._callback,t.callbackArgs)):t.callback=t.callback||n.callback,t._callbackNormalized=!0,t)},_normalizeIcon:function(e){return e.iconurl||(e.iconComponent||(e.iconComponent="core"),e.iconurl=M.util.image_url(e.icon,e.iconComponent)),e},_callbackWrapper:function(e,t,n){e.preventDefault();if(!this.isEnabled())return;var r=e.currentTarget.ancestor("button",!0);if(r&&r.hasAttribute(o))return;this.get("host").isActive()||this.get("host").focus(),r&&this.get("host")._setTabFocus(r);var i=[e,n];return t.apply(this,i)},_addKeyboardListener:function(t,n,r){var i="key",s=f.EDITORWRAPPER,o;if(e.Lang.isArray(n))return e.Array.each(n,function(e){this._addKeyboardListener(t,e)},this),this;typeof n=="object"?(n.eventtype&&(i=n.eventtype),n.container&&(s=n.container),o=n.keyCodes):o=this._getKeyEvent()+n+this +._getDefaultMetaKey(),this._buttonHandlers.push(this.editor.delegate(i,t,o,s,this))},isEnabled:function(){var t=e.Object.some(this.buttonStates,function(e){return e===this.ENABLED},this);return t},disableButtons:function(e){return this._setButtonState(!1,e)},enableButtons:function(e){return this._setButtonState(!0,e)},_setButtonState:function(t,n){var r="setAttribute";return t&&(r="removeAttribute"),n?this.buttons[n]&&(this.buttons[n][r](o,o),this.buttonStates[n]=t?this.ENABLED:this.DISABLED):e.Array.each(this.buttonNames,function(e){this.buttons[e][r](o,o),this.buttonStates[e]=t?this.ENABLED:this.DISABLED},this),this},highlightButtons:function(e){return this._changeButtonHighlight(!0,e)},unHighlightButtons:function(e){return this._changeButtonHighlight(!1,e)},_changeButtonHighlight:function(t,n){var r="addClass";return t||(r="removeClass"),n?this.buttons[n]&&this.buttons[n][r](u):e.Object.each(this.buttons,function(e){e[r](u)},this),this},_getDefaultMetaKey:function(){return e.UA.os==="macintosh"?"+meta":"+ctrl"},_getKeyEvent:function(){return"down:"}},e.Base.mix(e.M.editor_atto.EditorPlugin,[l]),c.ATTRS={},c.prototype={_dialogue:null,getDialogue:function(t){t=t||{};var n=!1;t.focusAfterHide&&(n=t.focusAfterHide,delete t.focusAfterHide);if(!this._dialogue){var r=e.merge({visible:!1,modal:!0,close:!0,draggable:!0},t);this._dialogue=new M.core.dialogue(r)}return n!==!1&&(n===!0?this._dialogue.set("focusAfterHide",this.buttons[this.buttonNames[0]]):typeof n=="string"?this._dialogue.set("focusAfterHide",this.buttons[n]):this._dialogue.set("focusAfterHide",n)),this._dialogue}},e.Base.mix(e.M.editor_atto.EditorPlugin,[c])},"@VERSION@",{requires:["node","base","escape","event","event-outside","handlebars","event-custom"]}); diff --git a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js index 4a51f518e06..29098e3e1f9 100644 --- a/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js +++ b/lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js @@ -177,20 +177,7 @@ var MENUTEMPLATE = '' + '' + '' + - '', - MENUDIALOGUE = '' + - '
' + - '' + - '
'; + ''; var DISABLED = 'disabled', HIGHLIGHT = 'highlight', @@ -322,6 +309,7 @@ EditorPluginButtons.prototype = { } else { buttonClass = buttonClass + '_' + config.buttonName; } + config.buttonClass = buttonClass; // Normalize icon configuration. config = this._normalizeIcon(config); @@ -463,6 +451,7 @@ EditorPluginButtons.prototype = { } else { buttonClass = buttonClass + '_' + config.buttonName; } + config.buttonClass = buttonClass; // Normalize icon configuration. config = this._normalizeIcon(config); @@ -497,7 +486,7 @@ EditorPluginButtons.prototype = { // Add the standard click handler to the menu. this._buttonHandlers.push( this.toolbar.delegate('click', this._showToolbarMenu, '.' + buttonClass, this, config), - this.toolbar.delegate('key', this._showToolbarMenu, '40, 32, enter', '.' + buttonClass, this, config) + this.toolbar.delegate('key', this._showToolbarMenuAndFocus, '40, 32, enter', '.' + buttonClass, this, config) ); // Add the button reference to the buttons array for later reference. @@ -543,45 +532,15 @@ EditorPluginButtons.prototype = { } config.overlayWidth = parseInt(config.overlayWidth, 10) + 'em'; - // Create the actual button. - var template = Y.Handlebars.compile(MENUDIALOGUE), - menu = Y.Node.create(template({ - config: config - })); + this.menus[config.buttonClass] = new Y.M.editor_atto.Menu(config); - // Create the dialogue. - this.menus[config.buttonClass] = new M.core.dialogue({ - bodyContent: menu, - width: null, - visible: false, - center: false, - closeButton: false, - responsive: false, - extraClasses: ['editor_atto_menu'] - }); - - menuDialogue = this.menus[config.buttonClass]; - - // Hide the header node entirely. - menuDialogue.headerNode.hide(); - - // Handle menu item selection. - this._buttonHandlers.push( - menu.delegate('click', this._chooseMenuItem, '.atto_menuentry a', this, config, menuDialogue), - - // Select the menu item on space, and enter. - menu.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this, config, menuDialogue), - - // Move up and down the menu on up/down. - menu.delegate('key', this._menuKeyboardNavigation, 'down:38,40', '.menu', this), - - // Hide the menu on left/right. - menu.delegate('key', this._hideMenu, 'down:37,39', '.menu', this, menuDialogue) - ); + this.menus[config.buttonClass].get('contentBox').delegate('click', + this._chooseMenuItem, '.atto_menuentry a', this, config); } // Ensure that we focus on this button next time. var creatorButton = this.buttons[config.buttonName]; + creatorButton.focus(); this.get('host')._setTabFocus(creatorButton); // Get a reference to the menu dialogue. @@ -589,36 +548,31 @@ EditorPluginButtons.prototype = { // Focus on the button by default after hiding this menu. menuDialogue.set('focusAfterHide', creatorButton); + menuDialogue.on('focusAfterHide', function(e) { + console.log(e); + }); // Display the menu. menuDialogue.show(); // Position it next to the button which opened it. menuDialogue.align(this.buttons[config.buttonName], [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]); - - // And focus on the first element in the menu. - menuDialogue.get('boundingBox').one('a').focus(); - - // Hide the menu when clicking outside of it. - this._menuHideHandlers.push( - menuDialogue.get('boundingBox').on('focusoutside', this._hideMenu, this, menuDialogue) - ); }, /** - * Hide a menu, removing all of the event handlers which trigger the hide. + * Display a toolbar menu and focus upon the first item. * - * @method _hideMenu + * @method _showToolbarMenuAndFocus * @param {EventFacade} e - * @param {M.core.dialogue} menuDialogue The Dialogue to hide. + * @param {object} config The configuration for the whole toolbar. + * @param {Number} [config.overlayWidth=14] The width of the menu * @private */ - _hideMenu: function(e, menuDialogue) { - if (menuDialogue.get('preventHideMenu') === true) { - return; - } - menuDialogue.hide(); - new Y.EventHandle(this._menuHideHandlers).detach(); + _showToolbarMenuAndFocus: function(e, config) { + this._showToolbarMenu(e, config); + + // Focus on the first element in the menu. + this.menus[config.buttonClass].get('boundingBox').one('a').focus(); }, /** @@ -631,12 +585,14 @@ EditorPluginButtons.prototype = { * @private */ _chooseMenuItem: function(e, config, menuDialogue) { - // Get the index from the clicked anchor. + // Get the index from the clicked anchor. var index = e.target.ancestor('a', true).getData('index'), // And the normalized callback configuration. buttonConfig = this._normalizeCallback(config.items[index], config.globalItemConfig); + menuDialogue = this.menus[config.buttonClass]; + // Prevent the dialogue to be closed because of some browser weirdness. menuDialogue.set('preventHideMenu', true); @@ -646,9 +602,10 @@ EditorPluginButtons.prototype = { // Cancel the hide menu prevention. menuDialogue.set('preventHideMenu', false); + console.log('Menu item chosen'); // Set the focus after hide so that focus is returned to the editor and changes are made correctly. menuDialogue.set('focusAfterHide', this.get('host').editor); - this._hideMenu(e, menuDialogue); + menuDialogue.hide(e); }, /** @@ -945,70 +902,6 @@ EditorPluginButtons.prototype = { return this; }, - /** - * Implement arrow-key navigation for the items in a toolbar menu. - * - * @method _menuKeyboardNavigation - * @param {EventFacade} e The keyboard event. - * @private - */ - _menuKeyboardNavigation: function(e) { - // Prevent the default browser behaviour. - e.preventDefault(); - - // Get a list of all buttons in the menu. - var buttons = e.currentTarget.all('a[role="menuitem"]'); - - // On cursor moves we loops through the buttons. - var found = false, - index = 0, - direction = 1, - checkCount = 0, - current = e.target.ancestor('a[role="menuitem"]', true); - - // Determine which button is currently selected. - while (!found && index < buttons.size()) { - if (buttons.item(index) === current) { - found = true; - } else { - index++; - } - } - - if (!found) { - return; - } - - if (e.keyCode === 38) { - // Moving up so reverse the direction. - direction = -1; - } - - // Try to find the next - do { - index += direction; - if (index < 0) { - index = buttons.size() - 1; - } else if (index >= buttons.size()) { - // Handle wrapping. - index = 0; - } - next = buttons.item(index); - - // Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item. - checkCount++; - // Loop while: - // * we are not in a loop and have not already checked every button; and - // * we are on a different button; and - // * the next menu item is not hidden. - } while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden')); - - if (next) { - next.focus(); - } - }, - - /** * Get the default meta key to use with keyboard events. * diff --git a/lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js b/lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js index 923a8ad0f4d..a92317b9d33 100644 --- a/lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js +++ b/lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js @@ -35,20 +35,7 @@ var MENUTEMPLATE = '' + '' + '' + - '', - MENUDIALOGUE = '' + - '
' + - '' + - '
'; + ''; var DISABLED = 'disabled', HIGHLIGHT = 'highlight', @@ -180,6 +167,7 @@ EditorPluginButtons.prototype = { } else { buttonClass = buttonClass + '_' + config.buttonName; } + config.buttonClass = buttonClass; // Normalize icon configuration. config = this._normalizeIcon(config); @@ -323,6 +311,7 @@ EditorPluginButtons.prototype = { } else { buttonClass = buttonClass + '_' + config.buttonName; } + config.buttonClass = buttonClass; // Normalize icon configuration. config = this._normalizeIcon(config); @@ -357,7 +346,7 @@ EditorPluginButtons.prototype = { // Add the standard click handler to the menu. this._buttonHandlers.push( this.toolbar.delegate('click', this._showToolbarMenu, '.' + buttonClass, this, config), - this.toolbar.delegate('key', this._showToolbarMenu, '40, 32, enter', '.' + buttonClass, this, config) + this.toolbar.delegate('key', this._showToolbarMenuAndFocus, '40, 32, enter', '.' + buttonClass, this, config) ); // Add the button reference to the buttons array for later reference. @@ -403,45 +392,15 @@ EditorPluginButtons.prototype = { } config.overlayWidth = parseInt(config.overlayWidth, 10) + 'em'; - // Create the actual button. - var template = Y.Handlebars.compile(MENUDIALOGUE), - menu = Y.Node.create(template({ - config: config - })); + this.menus[config.buttonClass] = new Y.M.editor_atto.Menu(config); - // Create the dialogue. - this.menus[config.buttonClass] = new M.core.dialogue({ - bodyContent: menu, - width: null, - visible: false, - center: false, - closeButton: false, - responsive: false, - extraClasses: ['editor_atto_menu'] - }); - - menuDialogue = this.menus[config.buttonClass]; - - // Hide the header node entirely. - menuDialogue.headerNode.hide(); - - // Handle menu item selection. - this._buttonHandlers.push( - menu.delegate('click', this._chooseMenuItem, '.atto_menuentry a', this, config, menuDialogue), - - // Select the menu item on space, and enter. - menu.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this, config, menuDialogue), - - // Move up and down the menu on up/down. - menu.delegate('key', this._menuKeyboardNavigation, 'down:38,40', '.menu', this), - - // Hide the menu on left/right. - menu.delegate('key', this._hideMenu, 'down:37,39', '.menu', this, menuDialogue) - ); + this.menus[config.buttonClass].get('contentBox').delegate('click', + this._chooseMenuItem, '.atto_menuentry a', this, config); } // Ensure that we focus on this button next time. var creatorButton = this.buttons[config.buttonName]; + creatorButton.focus(); this.get('host')._setTabFocus(creatorButton); // Get a reference to the menu dialogue. @@ -449,36 +408,31 @@ EditorPluginButtons.prototype = { // Focus on the button by default after hiding this menu. menuDialogue.set('focusAfterHide', creatorButton); + menuDialogue.on('focusAfterHide', function(e) { + console.log(e); + }); // Display the menu. menuDialogue.show(); // Position it next to the button which opened it. menuDialogue.align(this.buttons[config.buttonName], [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]); - - // And focus on the first element in the menu. - menuDialogue.get('boundingBox').one('a').focus(); - - // Hide the menu when clicking outside of it. - this._menuHideHandlers.push( - menuDialogue.get('boundingBox').on('focusoutside', this._hideMenu, this, menuDialogue) - ); }, /** - * Hide a menu, removing all of the event handlers which trigger the hide. + * Display a toolbar menu and focus upon the first item. * - * @method _hideMenu + * @method _showToolbarMenuAndFocus * @param {EventFacade} e - * @param {M.core.dialogue} menuDialogue The Dialogue to hide. + * @param {object} config The configuration for the whole toolbar. + * @param {Number} [config.overlayWidth=14] The width of the menu * @private */ - _hideMenu: function(e, menuDialogue) { - if (menuDialogue.get('preventHideMenu') === true) { - return; - } - menuDialogue.hide(); - new Y.EventHandle(this._menuHideHandlers).detach(); + _showToolbarMenuAndFocus: function(e, config) { + this._showToolbarMenu(e, config); + + // Focus on the first element in the menu. + this.menus[config.buttonClass].get('boundingBox').one('a').focus(); }, /** @@ -491,12 +445,14 @@ EditorPluginButtons.prototype = { * @private */ _chooseMenuItem: function(e, config, menuDialogue) { - // Get the index from the clicked anchor. + // Get the index from the clicked anchor. var index = e.target.ancestor('a', true).getData('index'), // And the normalized callback configuration. buttonConfig = this._normalizeCallback(config.items[index], config.globalItemConfig); + menuDialogue = this.menus[config.buttonClass]; + // Prevent the dialogue to be closed because of some browser weirdness. menuDialogue.set('preventHideMenu', true); @@ -506,9 +462,10 @@ EditorPluginButtons.prototype = { // Cancel the hide menu prevention. menuDialogue.set('preventHideMenu', false); + console.log('Menu item chosen'); // Set the focus after hide so that focus is returned to the editor and changes are made correctly. menuDialogue.set('focusAfterHide', this.get('host').editor); - this._hideMenu(e, menuDialogue); + menuDialogue.hide(e); }, /** @@ -807,71 +764,6 @@ EditorPluginButtons.prototype = { return this; }, - /** - * Implement arrow-key navigation for the items in a toolbar menu. - * - * @method _menuKeyboardNavigation - * @param {EventFacade} e The keyboard event. - * @private - */ - _menuKeyboardNavigation: function(e) { - // Prevent the default browser behaviour. - e.preventDefault(); - - // Get a list of all buttons in the menu. - var buttons = e.currentTarget.all('a[role="menuitem"]'); - - // On cursor moves we loops through the buttons. - var found = false, - index = 0, - direction = 1, - checkCount = 0, - current = e.target.ancestor('a[role="menuitem"]', true); - - // Determine which button is currently selected. - while (!found && index < buttons.size()) { - if (buttons.item(index) === current) { - found = true; - } else { - index++; - } - } - - if (!found) { - Y.log("Unable to find this menu item in the menu", 'debug', LOGNAME); - return; - } - - if (e.keyCode === 38) { - // Moving up so reverse the direction. - direction = -1; - } - - // Try to find the next - do { - index += direction; - if (index < 0) { - index = buttons.size() - 1; - } else if (index >= buttons.size()) { - // Handle wrapping. - index = 0; - } - next = buttons.item(index); - - // Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item. - checkCount++; - // Loop while: - // * we are not in a loop and have not already checked every button; and - // * we are on a different button; and - // * the next menu item is not hidden. - } while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden')); - - if (next) { - next.focus(); - } - }, - - /** * Get the default meta key to use with keyboard events. * diff --git a/lib/editor/atto/yui/src/editor/js/menu.js b/lib/editor/atto/yui/src/editor/js/menu.js index 5c907d90903..d0558683ace 100644 --- a/lib/editor/atto/yui/src/editor/js/menu.js +++ b/lib/editor/atto/yui/src/editor/js/menu.js @@ -23,6 +23,20 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +var MENUDIALOGUE = '' + + '
' + + '' + + '
'; + /** * A Menu for the Atto editor used in Moodle. * @@ -41,24 +55,169 @@ Menu = function() { Y.extend(Menu, M.core.dialogue, { + /** + * A list of the menu handlers which have been attached here. + * + * @property _menuHandlers + * @type Array + * @private + */ + _menuHandlers: null, + initializer: function(config) { - var body, headertext, bb; - Menu.superclass.initializer.call(this, config); + var headertext, + bb; + + this._menuHandlers = []; + + // Create the actual button. + var template = Y.Handlebars.compile(MENUDIALOGUE), + menu = Y.Node.create(template({ + config: config + })); + this.set('bodyContent', menu); bb = this.get('boundingBox'); bb.addClass('editor_atto_controlmenu'); - - // Close the menu when clicked outside (excluding the button that opened the menu). - body = this.bodyNode; + bb.addClass('editor_atto_menu'); + bb.one('.moodle-dialogue-wrap') + .removeClass('moodle-dialogue-wrap') + .addClass('moodle-dialogue-content'); headertext = Y.Node.create('

') .addClass('accesshide') .setHTML(this.get('headerText')); - body.prepend(headertext); + this.get('bodyContent').prepend(headertext); - this.get('hideOn'); + // Hide the header and footer node entirely. + this.headerNode.hide(); + this.footerNode.hide(); + + this._setupHandlers(); + }, + + /** + * Setup the Event handlers. + * + * @method _setupHandlers + * @private + */ + _setupHandlers: function() { + var contentBox = this.get('contentBox'); + // Handle menu item selection. + this._menuHandlers.push( + // Select the menu item on space, and enter. + contentBox.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this), + + // Move up and down the menu on up/down. + contentBox.delegate('key', this._handleKeyboardEvent, 'down:38,40', '.dropdown-menu', this), + + // Hide the menu when clicking outside of it. + contentBox.on('focusoutside', this.hide, this), + + // Hide the menu on left/right, and escape keys. + contentBox.delegate('key', this.hide, 'down:37,39,esc', '.dropdown-menu', this) + ); + }, + + /** + * Simulate other types of menu selection. + * + * @method _chooseMenuItem + * @param {EventFacade} e + */ + _chooseMenuItem: function(e) { + e.target.simulate('click'); + e.preventDefault(); + }, + + /** + * Hide a menu, removing all of the event handlers which trigger the hide. + * + * @method hide + * @param {EventFacade} e + */ + hide: function(e) { + if (this.get('preventHideMenu') === true) { + return; + } + + // We must prevent the default action (left/right/escape) because + // there are other listeners on the toolbar which will focus on the + // editor. + if (e) { + e.preventDefault(); + } + + return Menu.superclass.hide.call(this, arguments); + }, + + /** + * Implement arrow-key navigation for the items in a toolbar menu. + * + * @method _handleKeyboardEvent + * @param {EventFacade} e The keyboard event. + * @static + */ + _handleKeyboardEvent: function(e) { + // Prevent the default browser behaviour. + e.preventDefault(); + + // Get a list of all buttons in the menu. + var buttons = e.currentTarget.all('a[role="menuitem"]'); + + // On cursor moves we loops through the buttons. + var found = false, + index = 0, + direction = 1, + checkCount = 0, + current = e.target.ancestor('a[role="menuitem"]', true); + + // Determine which button is currently selected. + while (!found && index < buttons.size()) { + if (buttons.item(index) === current) { + found = true; + } else { + index++; + } + } + + if (!found) { + Y.log("Unable to find this menu item in the menu", 'debug', LOGNAME); + return; + } + + if (e.keyCode === 38) { + // Moving up so reverse the direction. + direction = -1; + } + + // Try to find the next + do { + index += direction; + if (index < 0) { + index = buttons.size() - 1; + } else if (index >= buttons.size()) { + // Handle wrapping. + index = 0; + } + next = buttons.item(index); + + // Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item. + checkCount++; + // Loop while: + // * we are not in a loop and have not already checked every button; and + // * we are on a different button; and + // * the next menu item is not hidden. + } while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden')); + + if (next) { + next.focus(); + } + + e.preventDefault(); + e.stopImmediatePropagation(); } - }, { NAME: "menu", ATTRS: { @@ -87,17 +246,6 @@ Y.Base.modifyAttrs(Menu, { value: 'auto' }, - /** - * TODO - chekc that this is needed - * The footer content. - * - * @attribute footerContent - * @default '' - */ - footerContent: { - value: '' - }, - /** * When to hide this menu. * @@ -114,6 +262,62 @@ Y.Base.modifyAttrs(Menu, { eventName: 'clickoutside' } ] + }, + + /** + * The default list of extra classes for this menu. + * + * @attribute extraClasses + * @type Array + * @default editor_atto_menu + */ + extraClasses: { + value: [ + 'editor_atto_menu' + ] + }, + + /** + * Override the responsive nature of the core dialogues. + * + * @attribute responsive + * @type boolean + * @default false + */ + responsive: { + value: false + }, + + /** + * The default visibility of the menu. + * + * @attribute visible + * @type boolean + * @default false + */ + visible: { + value: false + }, + + /** + * Whether to centre the menu. + * + * @attribute center + * @type boolean + * @default false + */ + center: { + value: false + }, + + /** + * Hide the close button. + * @attribute closeButton + * @type boolean + * @default false + */ + closeButton: { + value: false } }); diff --git a/theme/base/style/core.css b/theme/base/style/core.css index 64b5aa309cd..d6bf942acf7 100644 --- a/theme/base/style/core.css +++ b/theme/base/style/core.css @@ -1805,3 +1805,70 @@ body.lockscroll { height: 100%; overflow: hidden; } + +/* Dropdown Menu styling */ +.dropdown-menu { + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + *border-right-width: 2px; + *border-bottom-width: 2px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; +} + +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 20px; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + text-decoration: none; + background-repeat: repeat-x; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + text-decoration: none; + outline: 0; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + cursor: default; +} +.open { + *z-index: 1000; +} +.open > .dropdown-menu { + display: block; +} + +.editor_atto_menu .moodle-dialogue-content { + padding: 0px; + border: inherit; +}