Hi, PFA patch to add keyboard navigation in Query tool module via Tab/Shift-Tab key. RM#2896
Please review. *Note:* 1) Once the keyboard shortcut infrastructure is ready we will add generic shortcut to focus out from CodeMirror editor and set foucs to next element, Right now there is no way of doing this, For testing purpose you can manually focus out from CodeMirror and click on data output panel to continue navigate using Tab key. 2) As of now inner panel's are not getting focused on Tab/Shift-Tab keys but once RM#2895 <https://redmine.postgresql.org/issues/2895> patch gets committed it will start working automatically as it's inherited code which will add tabindex tag automatically on each newly created wcDocker panel. -- Regards, Murtuza Zabuawala EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
diff --git a/docs/en_US/keyboard_shortcuts.rst b/docs/en_US/keyboard_shortcuts.rst index c82287a..76b1084 100644 --- a/docs/en_US/keyboard_shortcuts.rst +++ b/docs/en_US/keyboard_shortcuts.rst @@ -61,6 +61,8 @@ When using the syntax-highlighting SQL editors, the following shortcuts are avai +--------------------------+------------------+-------------------------------------+ | Shift+Tab | Shift+Tab | Un-indent selected text | +--------------------------+------------------+-------------------------------------+ +| <accesskey> + y | <accesskey> + y | Copy SQL on history panel | ++--------------------------+------------------+-------------------------------------+ **Query Tool** @@ -89,32 +91,42 @@ When using the Query Tool, the following shortcuts are available: +--------------------------+--------------------+-----------------------------------+ | Ctrl+Shift+F | Cmd+Shift+F | Replace | +--------------------------+--------------------+-----------------------------------+ +| <accesskey> + y | <accesskey> + y | Copy SQL on history panel | ++--------------------------+--------------------+-----------------------------------+ **Debugger** When using the Debugger, the following shortcuts are available: ++--------------------------+--------------------+-----------------------------------+ +| Shortcut (Windows/Linux) | Shortcut (Mac) | Function | ++==========================+====================+===================================+ +| <accesskey> + i | <accesskey> + i | Step in | ++--------------------------+--------------------+-----------------------------------+ +| <accesskey> + o | <accesskey> + o | Step over | ++--------------------------+--------------------+-----------------------------------+ +| <accesskey> + c | <accesskey> + c | Continue/Restart | ++--------------------------+--------------------+-----------------------------------+ +| <accesskey> + t | <accesskey> + t | Toggle breakpoint | ++--------------------------+--------------------+-----------------------------------+ +| <accesskey> + x | <accesskey> + x | Clear all breakpoints | ++--------------------------+--------------------+-----------------------------------+ +| <accesskey> + s | <accesskey> + s | Stop | ++--------------------------+--------------------+-----------------------------------+ +| Alt + Shift + g | Alt + Shift + g | Enter or Edit values in Grid | ++--------------------------+--------------------+-----------------------------------+ + +**Inner panel navigation** + +When using the SQL Editors, Query Tool and Debugger, the following shortcuts are available for inner panel navigation: + +--------------------------+---------------------------+------------------------------+ | Shortcut (Windows/Linux) | Shortcut (Mac) | Function | +==========================+===========================+==============================+ -| <accesskey> + i | <accesskey> + i | Step in | -+--------------------------+---------------------------+------------------------------+ -| <accesskey> + o | <accesskey> + o | Step over | -+--------------------------+---------------------------+------------------------------+ -| <accesskey> + c | <accesskey> + c | Continue/Restart | -+--------------------------+---------------------------+------------------------------+ -| <accesskey> + t | <accesskey> + t | Toggle breakpoint | -+--------------------------+---------------------------+------------------------------+ -| <accesskey> + x | <accesskey> + x | Clear all breakpoints | -+--------------------------+---------------------------+------------------------------+ -| <accesskey> + s | <accesskey> + s | Stop | -+--------------------------+---------------------------+------------------------------+ | Alt + Shift + Right Arrow| Alt + Shift + Right Arrow | Move to next inner panel | +--------------------------+---------------------------+------------------------------+ | Alt + Shift + Left Arrow | Alt + Shift + Left Arrow | Move to previous inner panel | +--------------------------+---------------------------+------------------------------+ -| Alt + Shift + g | Alt + Shift + g | Enter or Edit values in Grid | -+--------------------------+---------------------------+------------------------------+ .. note:: <accesskey> is browser and platform dependant. The following table lists the default access keys for supported browsers. diff --git a/web/pgadmin/misc/static/explain/js/explain.js b/web/pgadmin/misc/static/explain/js/explain.js index 7e25b2e..f406c3c 100644 --- a/web/pgadmin/misc/static/explain/js/explain.js +++ b/web/pgadmin/misc/static/explain/js/explain.js @@ -762,6 +762,7 @@ define('pgadmin.misc.explain', [ zoomInBtn = $('<button></button>', { class: 'btn pg-explain-zoom-btn badge', title: 'Zoom in', + tabindex: 0, }).appendTo(zoomArea).append( $('<i></i>', { class: 'fa fa-search-plus', @@ -769,6 +770,7 @@ define('pgadmin.misc.explain', [ zoomToNormal = $('<button></button>', { class: 'btn pg-explain-zoom-btn badge', title: 'Zoom to original', + tabindex: 0, }).appendTo(zoomArea).append( $('<i></i>', { class: 'fa fa-arrows-alt', @@ -776,6 +778,7 @@ define('pgadmin.misc.explain', [ zoomOutBtn = $('<button></button>', { class: 'btn pg-explain-zoom-btn badge', title: 'Zoom out', + tabindex: 0, }).appendTo(zoomArea).append( $('<i></i>', { class: 'fa fa-search-minus', diff --git a/web/pgadmin/static/js/keyboard_shortcuts.js b/web/pgadmin/static/js/keyboard_shortcuts.js index 780e089..48229a1 100644 --- a/web/pgadmin/static/js/keyboard_shortcuts.js +++ b/web/pgadmin/static/js/keyboard_shortcuts.js @@ -1,8 +1,14 @@ import $ from 'jquery'; +// Debugger const EDIT_KEY = 71, // Key: G -> Grid values LEFT_ARROW_KEY = 37, - RIGHT_ARROW_KEY = 39; + RIGHT_ARROW_KEY = 39, + F5_KEY = 116, + F7_KEY = 118, + F8_KEY = 119, + PERIOD_KEY = 190, + FWD_SLASH_KEY = 191; function isMac() { return window.navigator.platform.search('Mac') != -1; @@ -103,8 +109,62 @@ function getInnerPanel($el, direction) { return id; } +/* Query tool: Keyboard Shortcuts handling */ +function keyboardShortcutsQueryTool(sqlEditorController, queryToolActions, event) { + if (sqlEditorController.isQueryRunning()) { + return; + } + let keyCode = event.which || event.keyCode, panel_id; + + if (keyCode === F5_KEY) { + event.preventDefault(); + queryToolActions.executeQuery(sqlEditorController); + } else if (event.shiftKey && keyCode === F7_KEY) { + this._stopEventPropagation(event); + queryToolActions.explainAnalyze(sqlEditorController); + } else if (keyCode === F7_KEY) { + this._stopEventPropagation(event); + queryToolActions.explain(sqlEditorController); + } else if (keyCode === F8_KEY) { + event.preventDefault(); + queryToolActions.download(sqlEditorController); + } else if (( + (this.isMac() && event.metaKey) || + (!this.isMac() && event.ctrlKey) + ) && !event.altKey && event.shiftKey && keyCode === FWD_SLASH_KEY) { + this._stopEventPropagation(event); + queryToolActions.commentBlockCode(sqlEditorController); + } else if (( + (this.isMac() && !this.isKeyCtrlAltShift(event) && event.metaKey) || + (!this.isMac() && !this.isKeyAltShift(event) && event.ctrlKey) + ) && keyCode === FWD_SLASH_KEY) { + this._stopEventPropagation(event); + queryToolActions.commentLineCode(sqlEditorController); + } else if (( + (this.isMac() && !this.isKeyCtrlAltShift(event) && event.metaKey) || + (!this.isMac() && !this.isKeyAltShift(event) && event.ctrlKey) + ) && keyCode === PERIOD_KEY) { + this._stopEventPropagation(event); + queryToolActions.uncommentLineCode(sqlEditorController); + } else if (this.isAltShiftBoth(event) && keyCode === LEFT_ARROW_KEY) { + // Goto previous side panel + this._stopEventPropagation(event); + panel_id = this.getInnerPanel( + sqlEditorController.container, 'left' + ); + } else if (this.isAltShiftBoth(event) && keyCode === RIGHT_ARROW_KEY) { + // Goto next side panel + this._stopEventPropagation(event); + panel_id = this.getInnerPanel( + sqlEditorController.container, 'right' + ); + } + return panel_id; +} + module.exports = { processEventDebugger: keyboardShortcutsDebugger, + processEventQueryTool: keyboardShortcutsQueryTool, getInnerPanel: getInnerPanel, // misc functions _stopEventPropagation: _stopEventPropagation, diff --git a/web/pgadmin/static/js/sqleditor/keyboard_shortcuts.js b/web/pgadmin/static/js/sqleditor/keyboard_shortcuts.js deleted file mode 100644 index 2ad4755..0000000 --- a/web/pgadmin/static/js/sqleditor/keyboard_shortcuts.js +++ /dev/null @@ -1,81 +0,0 @@ -const F5_KEY = 116, - F7_KEY = 118, - F8_KEY = 119, - PERIOD_KEY = 190, - FWD_SLASH_KEY = 191; - -function keyboardShortcuts(sqlEditorController, queryToolActions, event) { - if (sqlEditorController.isQueryRunning()) { - return; - } - - let keyCode = event.which || event.keyCode; - - if (keyCode === F5_KEY) { - event.preventDefault(); - queryToolActions.executeQuery(sqlEditorController); - } else if (event.shiftKey && keyCode === F7_KEY) { - _stopEventPropagation(); - queryToolActions.explainAnalyze(sqlEditorController); - } else if (keyCode === F7_KEY) { - _stopEventPropagation(); - queryToolActions.explain(sqlEditorController); - } else if (keyCode === F8_KEY) { - event.preventDefault(); - queryToolActions.download(sqlEditorController); - } else if (( - (this.isMac() && event.metaKey) || - (!this.isMac() && event.ctrlKey) - ) && !event.altKey && event.shiftKey && keyCode === FWD_SLASH_KEY) { - _stopEventPropagation(); - queryToolActions.commentBlockCode(sqlEditorController); - } else if (( - (this.isMac() && !this.isKeyCtrlAltShift(event) && event.metaKey) || - (!this.isMac() && !this.isKeyAltShift(event) && event.ctrlKey) - ) && keyCode === FWD_SLASH_KEY) { - _stopEventPropagation(); - queryToolActions.commentLineCode(sqlEditorController); - } else if (( - (this.isMac() && !this.isKeyCtrlAltShift(event) && event.metaKey) || - (!this.isMac() && !this.isKeyAltShift(event) && event.ctrlKey) - ) && keyCode === PERIOD_KEY) { - _stopEventPropagation(); - queryToolActions.uncommentLineCode(sqlEditorController); - } - - function _stopEventPropagation() { - event.cancelBubble = true; - event.preventDefault(); - event.stopPropagation(); - event.stopImmediatePropagation(); - } -} - -function isMac() { - return window.navigator.platform.search('Mac') != -1; -} - -function isKeyCtrlAlt(event) { - return event.ctrlKey || event.altKey; -} - -function isKeyAltShift(event) { - return event.altKey || event.shiftKey; -} - -function isKeyCtrlShift(event) { - return event.ctrlKey || event.shiftKey; -} - -function isKeyCtrlAltShift(event) { - return event.ctrlKey || event.altKey || event.shiftKey; -} - -module.exports = { - processEvent: keyboardShortcuts, - isMac: isMac, - isKeyCtrlAlt: isKeyCtrlAlt, - isKeyAltShift: isKeyAltShift, - isKeyCtrlShift: isKeyCtrlShift, - isKeyCtrlAltShift: isKeyCtrlAltShift, -}; diff --git a/web/pgadmin/static/jsx/history/detail/history_detail_query.jsx b/web/pgadmin/static/jsx/history/detail/history_detail_query.jsx index 22bd498..dce3460 100644 --- a/web/pgadmin/static/jsx/history/detail/history_detail_query.jsx +++ b/web/pgadmin/static/jsx/history/detail/history_detail_query.jsx @@ -54,10 +54,13 @@ export default class HistoryDetailQuery extends React.Component { return ( <div id="history-detail-query"> <button className={this.copyButtonClass()} + tabIndex={0} + accessKey={'y'} onClick={this.copyAllHandler}>{this.copyButtonText()}</button> <CodeMirror value={this.props.historyEntry.query} options={{ + tabindex: -1, mode: 'text/x-pgsql', readOnly: true, }} diff --git a/web/pgadmin/tools/datagrid/static/js/datagrid.js b/web/pgadmin/tools/datagrid/static/js/datagrid.js index 24a49ae..e7329c3 100644 --- a/web/pgadmin/tools/datagrid/static/js/datagrid.js +++ b/web/pgadmin/tools/datagrid/static/js/datagrid.js @@ -307,6 +307,7 @@ define('pgadmin.datagrid', [ // Apply CodeMirror to filter text area. this.filter_obj = CodeMirror.fromTextArea($sql_filter.get(0), { + tabindex: 0, lineNumbers: true, mode: 'text/x-pgsql', extraKeys: pgBrowser.editor_shortcut_keys, @@ -594,4 +595,4 @@ define('pgadmin.datagrid', [ }; return pgAdmin.DataGrid; -}); \ No newline at end of file +}); diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/filter.html b/web/pgadmin/tools/datagrid/templates/datagrid/filter.html index 0275731..8cc9a9f 100644 --- a/web/pgadmin/tools/datagrid/templates/datagrid/filter.html +++ b/web/pgadmin/tools/datagrid/templates/datagrid/filter.html @@ -1,5 +1,5 @@ <div class="filter-textarea"> - <textarea id="sql_filter" rows="5"></textarea> + <textarea id="sql_filter" rows="5" tabindex="0"></textarea> <style> .filter-textarea .CodeMirror-scroll { min-height: 120px; diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html index c0f7d99..ee62ed1 100644 --- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html +++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html @@ -17,7 +17,6 @@ width:{% if display_connection_status -%} calc(100% - 43px) {% else %} 100% {%- endif %}; } - </style> <div id="main-editor_panel"> <div id="fetching_data" class="wcLoadingIconContainer sql-editor-busy-fetching hide"> @@ -29,24 +28,25 @@ <div id="btn-toolbar" class="pg-prop-btn-group bg-gray-2 border-gray-3" role="toolbar" aria-label=""> <div class="btn-group" role="group" aria-label=""> <button id="btn-load-file" type="button" class="btn btn-default btn-load-file" - title="{{ _('Open File') }}" accesskey="o"> + title="{{ _('Open File') }}" accesskey="o" tabindex="0"> <i class="fa fa-folder-open-o" aria-hidden="true"></i> </button> <button id="btn-save" type="button" class="btn btn-default" title="{{ _('Save') }}" disabled> - <i class="fa fa-floppy-o" aria-hidden="true"></i> + <i class="fa fa-floppy-o" aria-hidden="true" tabindex="0"></i> </button> <button id="btn-file-menu-dropdown" type="button" class="btn btn-default dropdown-toggle" - data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" disabled accesskey="s"> - <span class="caret"></span> <span class="sr-only">Toggle Dropdown</span> + data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" disabled accesskey="s" + tabindex="0"> + <span class="caret"></span> <span class="sr-only">{{ _('Toggle Dropdown') }}</span> </button> <ul class="dropdown-menu"> <li> - <a id="btn-file-menu-save" href="#"> + <a id="btn-file-menu-save" href="#" tabindex="0"> <span>{{ _('Save') }}</span> </a> </li> <li> - <a id="btn-file-menu-save-as" href="#"> + <a id="btn-file-menu-save-as" href="#" tabindex="0"> <span>{{ _('Save As') }}</span> </a> </li> @@ -54,15 +54,15 @@ </div> <div class="btn-group" role="group" aria-label=""> <button id="btn-find" type="button" class="btn btn-default" title="{{ _('Find (Ctrl/Cmd+F)') }}"> - <i class="fa fa-search" aria-hidden="true"></i> + <i class="fa fa-search" aria-hidden="true" tabindex="0"></i> </button> <button id="btn-find-menu-dropdown" type="button" class="btn btn-default dropdown-toggle" - data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" accesskey="f"> + data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" accesskey="f" tabindex="0"> <span class="caret"></span> <span class="sr-only">Toggle Dropdown</span> </button> <ul class="dropdown-menu"> <li> - <a id="btn-find-menu-find" href="#"> + <a id="btn-find-menu-find" href="#" tabindex="0"> <span> {{ _('Find') }}{% if client_platform == 'macos' -%} {{ _(' (Cmd+F)') }} {% else %} @@ -70,7 +70,7 @@ </a> </li> <li> - <a id="btn-find-menu-find-next" href="#"> + <a id="btn-find-menu-find-next" href="#" tabindex="0"> <span> {{ _('Find next') }}{% if client_platform == 'macos' -%} {{ _(' (Cmd+G)') }} {% else %} @@ -78,7 +78,7 @@ </a> </li> <li> - <a id="btn-find-menu-find-previous" href="#"> + <a id="btn-find-menu-find-previous" href="#" tabindex="0"> <span> {{ _('Find previous') }}{% if client_platform == 'macos' -%} {{ _(' (Cmd+Shift+G)') }} {% else %} @@ -86,13 +86,13 @@ </a> </li> <li> - <a id="btn-find-menu-find-persistent" href="#"> + <a id="btn-find-menu-find-persistent" href="#" tabindex="0"> <span>{{ _('Persistent find') }}</span> </a> </li> <li class="divider"></li> <li> - <a id="btn-find-menu-replace" href="#"> + <a id="btn-find-menu-replace" href="#" tabindex="0"> <span> {{ _('Replace') }}{% if client_platform == 'macos' -%} {{ _(' (Cmd+Shift+F)') }} {% else %} @@ -100,13 +100,13 @@ </a> </li> <li> - <a id="btn-find-menu-replace-all" href="#"> + <a id="btn-find-menu-replace-all" href="#" tabindex="0"> <span>{{ _('Replace all') }}</span> </a> </li> <li class="divider"></li> <li> - <a id="btn-find-menu-jump" href="#"> + <a id="btn-find-menu-jump" href="#" tabindex="0"> <span>{{ _('Jump (Alt+G)') }}</span> </a> </li> @@ -114,48 +114,48 @@ </div> <div class="btn-group" role="group" aria-label=""> <button id="btn-copy-row" type="button" class="btn btn-default" - title="{{ _('Copy') }}" disabled accesskey="c"> + title="{{ _('Copy') }}" disabled accesskey="c" tabindex="0"> <i class="fa fa-files-o" aria-hidden="true"></i> </button> <button id="btn-paste-row" type="button" class="btn btn-default" - title="{{ _('Paste Row') }}" disabled accesskey="p"> + title="{{ _('Paste Row') }}" disabled accesskey="p" tabindex="0"> <i class="fa fa-clipboard" aria-hidden="true"></i> </button> </div> <div class="btn-group" role="group" aria-label=""> <button id="btn-delete-row" type="button" class="btn btn-default" - title="{{ _('Delete Row(s)') }}" disabled accesskey="d"> + title="{{ _('Delete Row(s)') }}" disabled accesskey="d" tabindex="0"> <i class="fa fa-trash" aria-hidden="true"></i> </button> </div> <div class="btn-group" role="group" aria-label=""> <button id="btn-edit-dropdown" type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" - title="{{ _('Edit') }}" accesskey="e"> + title="{{ _('Edit') }}" accesskey="e" tabindex="0"> <i class="fa fa-pencil-square-o" aria-hidden="true"></i> - <span class="caret"></span> <span class="sr-only">Toggle Dropdown</span> + <span class="caret"></span> <span class="sr-only">{{ _('Toggle Dropdown') }}</span> </button> - <ul class="dropdown-menu dropdown-menu"> + <ul class="dropdown-menu"> <li> - <a id="btn-indent-code" href="#"> + <a id="btn-indent-code" href="#" tabindex="0"> <span> {{ _('Indent Selection (Tab)') }} </span> </a> - <a id="btn-unindent-code" href="#"> + <a id="btn-unindent-code" href="#" tabindex="0"> <span> {{ _('Unindent Selection (Shift+Tab)') }} </span> </a> - <a id="btn-comment-line" href="#"> + <a id="btn-comment-line" href="#" tabindex="0"> <span> {{ _('Inline Comment Selection') }}{% if client_platform == 'macos' -%} {{ _(' (Cmd+/)') }} {% else %} {{ _(' (Ctrl+/)') }}{%- endif %}</span> </a> - <a id="btn-uncomment-line" href="#"> + <a id="btn-uncomment-line" href="#" tabindex="0"> <span> {{ _('Inline Uncomment Selection') }}{% if client_platform == 'macos' -%} {{ _(' (Cmd+.)') }} {% else %} {{ _(' (Ctrl+.)') }}{%- endif %}</span> </a> - <a id="btn-toggle-comment-block" href="#"> + <a id="btn-toggle-comment-block" href="#" tabindex="0"> <span> {{ _('Block Comment/Uncomment Selection') }}{% if client_platform == 'macos' -%} {{ _(' (Shift+Cmd+/)') }} {% else %} @@ -166,79 +166,79 @@ </div> <div class="btn-group" role="group" aria-label=""> <button id="btn-filter" type="button" class="btn btn-default" - title="{{ _('Filter') }}" disabled accesskey="i"> + title="{{ _('Filter') }}" disabled accesskey="i" tabindex="0"> <i class="fa fa-filter" aria-hidden="true"></i> </button> <button id="btn-filter-dropdown" type="button" class="btn btn-default dropdown-toggle" - data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" disabled> - <span class="caret"></span> <span class="sr-only">Toggle Dropdown</span> + data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" disabled tabindex="0"> + <span class="caret"></span> <span class="sr-only">{{ _('Toggle Dropdown') }}</span> </button> <ul class="dropdown-menu dropdown-menu-right"> <li> - <a id="btn-filter-menu" href="#">{{ _('Filter') }}</a> - <a id="btn-remove-filter" href="#">{{ _('Remove Filter') }}</a> - <a id="btn-include-filter" href="#">{{ _('By Selection') }}</a> - <a id="btn-exclude-filter" href="#">{{ _('Exclude Selection') }}</a> + <a id="btn-filter-menu" href="#" tabindex="0">{{ _('Filter') }}</a> + <a id="btn-remove-filter" href="#" tabindex="0">{{ _('Remove Filter') }}</a> + <a id="btn-include-filter" href="#" tabindex="0">{{ _('By Selection') }}</a> + <a id="btn-exclude-filter" href="#" tabindex="0">{{ _('Exclude Selection') }}</a> </li> </ul> </div> <div class="btn-group" role="group" aria-label=""> - <select class="limit" style="height: 30px; width: 90px;" disabled accesskey="r"> - <option value="-1">No limit</option> - <option value="1000">1000 rows</option> - <option value="500">500 rows</option> - <option value="100">100 rows</option> + <select class="limit" style="height: 30px; width: 90px;" disabled accesskey="r" tabindex="0"> + <option value="-1">{{ _('No limit') }}</option> + <option value="1000">{{ _('1000 rows') }}</option> + <option value="500">{{ _('500 rows') }}</option> + <option value="100">{{ _('100 rows') }}</option> </select> </div> <div class="btn-group" role="group" aria-label=""> <button id="btn-flash" type="button" class="btn btn-default" style="width: 40px;" - title="{{ _('Execute/Refresh (F5)') }}"> + title="{{ _('Execute/Refresh (F5)') }}" tabindex="0"> <i class="fa fa-bolt" aria-hidden="true"></i> </button> <button id="btn-query-dropdown" type="button" class="btn btn-default dropdown-toggle" - data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" accesskey="x"> - <span class="caret"></span> <span class="sr-only">Toggle Dropdown</span> + data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" accesskey="x" tabindex="0"> + <span class="caret"></span> <span class="sr-only">{{ _('Toggle Dropdown') }}</span> </button> - <ul class="dropdown-menu dropdown-menu"> + <ul class="dropdown-menu" role="menu"> <li> - <a id="btn-flash-menu" href="#"> + <a id="btn-flash-menu" href="#" tabindex="0"> <span>{{ _('Execute/Refresh (F5)') }}</span> </a> </li> <li> - <a id="btn-explain" href="#"> + <a id="btn-explain" href="#" tabindex="0"> <span>{{ _('Explain (F7)') }}</span> </a> </li> <li> - <a id="btn-explain-analyze" href="#"> + <a id="btn-explain-analyze" href="#" tabindex="0"> <span>{{ _('Explain Analyze (Shift+F7)') }}</span> </a> </li> <li class="divider"></li> - <li class="dropdown-submenu dropdown-submenu"> - <a href="#">{{ _('Explain Options') }}</a> + <li class="dropdown-submenu"> + <a href="#" tabindex="0">{{ _('Explain Options') }}</a> <ul class="dropdown-menu"> <li> - <a id="btn-explain-verbose" href="#" class="noclose"> + <a id="btn-explain-verbose" href="#" class="noclose" tabindex="0"> <i class="explain-verbose fa fa-check visibility-hidden" aria-hidden="true"></i> <span> {{ _('Verbose') }} </span> </a> </li> <li> - <a id="btn-explain-costs" href="#" class="noclose"> + <a id="btn-explain-costs" href="#" class="noclose" tabindex="0"> <i class="explain-costs fa fa-check visibility-hidden" aria-hidden="true"></i> <span> {{ _('Costs') }} </span> </a> </li> <li> - <a id="btn-explain-buffers" href="#" class="noclose"> + <a id="btn-explain-buffers" href="#" class="noclose" tabindex="0"> <i class="explain-buffers fa fa-check visibility-hidden" aria-hidden="true"></i> <span> {{ _('Buffers') }} </span> </a> </li> <li> - <a id="btn-explain-timing" href="#" class="noclose"> + <a id="btn-explain-timing" href="#" class="noclose" tabindex="0"> <i class="explain-timing fa fa-check visibility-hidden" aria-hidden="true"></i> <span> {{ _('Timing') }} </span> </a> @@ -247,36 +247,36 @@ </li> <li class="divider"></li> <li> - <a id="btn-auto-commit" href="#"> + <a id="btn-auto-commit" href="#" tabindex="0"> <i class="auto-commit fa fa-check visibility-hidden" aria-hidden="true"></i> <span> {{ _('Auto commit?') }} </span> </a> </li> <li> - <a id="btn-auto-rollback" href="#"> + <a id="btn-auto-rollback" href="#" tabindex="0"> <i class="auto-rollback fa fa-check visibility-hidden" aria-hidden="true"></i> <span> {{ _('Auto rollback?') }} </span> </a> </li> </ul> <button id="btn-cancel-query" type="button" class="btn btn-default" title="{{ _('Cancel query') }}" - disabled accesskey="q"> + disabled accesskey="q" tabindex="0"> <i class="fa fa-stop" aria-hidden="true"></i> </button> </div> <div class="btn-group" role="group" aria-label=""> <button id="btn-clear-dropdown" type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" - title="{{ _('Clear') }}" accesskey="l"> + title="{{ _('Clear') }}" accesskey="l" tabindex="0"> <i class="fa fa-eraser" aria-hidden="true"></i> - <span class="caret"></span> <span class="sr-only">Toggle Dropdown</span> + <span class="caret"></span> <span class="sr-only">{{ _('Toggle Dropdown') }}</span> </button> - <ul class="dropdown-menu dropdown-menu"> + <ul class="dropdown-menu"> <li> - <a id="btn-clear" href="#"> + <a id="btn-clear" href="#" tabindex="0"> <span> {{ _('Clear Query Window') }} </span> </a> - <a id="btn-clear-history" href="#"> + <a id="btn-clear-history" href="#" tabindex="0"> <span> {{ _('Clear History') }} </span> </a> </li> @@ -284,7 +284,7 @@ </div> <div class="btn-group" role="group" aria-label=""> <button id="btn-download" type="button" class="btn btn-default" - title="{{ _('Download as CSV (F8)') }}" accesskey="v"> + title="{{ _('Download as CSV (F8)') }}" accesskey="v" tabindex="0"> <i class="fa fa-download" aria-hidden="true"></i> </button> </div> @@ -298,7 +298,7 @@ data-toggle="popover" data-placement="bottom" data-content="" data-panel-visible="visible" - accesskey="t"> + accesskey="t" tabindex="0"> <i class="fa-custom fa-query-tool-disconnected" aria-hidden="true" title="{{ _('Connection status (click for details) (<accesskey>+T)') }}"> </i> @@ -315,17 +315,17 @@ <textarea id="sql_filter" rows="5"></textarea> </div> <div class="btn-group"> - <button id="btn-cancel" type="button" class="btn btn-danger" title="{{ _('Cancel') }}"> + <button id="btn-cancel" type="button" class="btn btn-danger" title="{{ _('Cancel') }}" tabindex="0"> <i class="fa fa-times" aria-hidden="true"></i> {{ _('Cancel') }} </button> </div> <div class="btn-group"> - <button id="btn-apply" type="button" class="btn btn-primary" title="{{ _('Apply') }}"> + <button id="btn-apply" type="button" class="btn btn-primary" title="{{ _('Apply') }}" tabindex="0"> <i class="fa fa-check" aria-hidden="true"></i> {{ _('Apply') }} </button> </div> </div> - <div id="editor-panel"></div> + <div id="editor-panel" tabindex="0"></div> <iframe id="download-csv" style="display:none"></iframe> </div> </div> diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css index 7088f8e..e6e8e3a 100644 --- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css +++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css @@ -572,3 +572,8 @@ input.editor-checkbox:focus { .connection_status .fa-query-tool-disconnected { content: url('../img/disconnect.svg'); } + +.sql-editor .dropdown-toggle:focus, +.sql-editor .copy-all:focus { + outline: 5px auto -webkit-focus-ring-color; +} diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index 3df9605..94151e7 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -15,7 +15,7 @@ define('tools.querytool', [ 'sources/history/index.js', 'sources/../jsx/history/query_history', 'react', 'react-dom', - 'sources/sqleditor/keyboard_shortcuts', + 'sources/keyboard_shortcuts', 'sources/sqleditor/query_tool_actions', 'sources/../bundle/slickgrid', 'pgadmin.file_manager', @@ -110,6 +110,7 @@ define('tools.querytool', [ self.checkConnectionStatus(); self.filter_obj = CodeMirror.fromTextArea(filter.get(0), { + tabindex: '0', lineNumbers: true, mode: self.handler.server_type === 'gpdb' ? 'text/x-gpsql' : 'text/x-pgsql', foldOptions: { @@ -158,6 +159,8 @@ define('tools.querytool', [ theme: 'webcabin.overrides.css', }); + self.docker = main_docker; + var sql_panel = new pgAdmin.Browser.Panel({ name: 'sql_panel', title: false, @@ -170,11 +173,12 @@ define('tools.querytool', [ sql_panel.load(main_docker); var sql_panel_obj = main_docker.addPanel('sql_panel', wcDocker.DOCK.TOP); - var text_container = $('<textarea id="sql_query_tool"></textarea>'); - var output_container = $('<div id="output-panel"></div>').append(text_container); + var text_container = $('<textarea id="sql_query_tool" tabindex: "-1"></textarea>'); + var output_container = $('<div id="output-panel" tabindex: "0"></div>').append(text_container); sql_panel_obj.$container.find('.pg-panel-content').append(output_container); self.query_tool_obj = CodeMirror.fromTextArea(text_container.get(0), { + tabindex: '0', lineNumbers: true, styleSelectedText: true, mode: self.handler.server_type === 'gpdb' ? 'text/x-gpsql' : 'text/x-pgsql', @@ -218,7 +222,7 @@ define('tools.querytool', [ height: '100%', isCloseable: false, isPrivate: true, - content: '<div id ="datagrid" class="sql-editor-grid-container text-12"></div>', + content: '<div id ="datagrid" class="sql-editor-grid-container text-12" tabindex: "0"></div>', }); var explain = new pgAdmin.Browser.Panel({ @@ -228,7 +232,7 @@ define('tools.querytool', [ height: '100%', isCloseable: false, isPrivate: true, - content: '<div class="sql-editor-explain"></div>', + content: '<div class="sql-editor-explain" tabindex: "0"></div>', }); var messages = new pgAdmin.Browser.Panel({ @@ -238,7 +242,7 @@ define('tools.querytool', [ height: '100%', isCloseable: false, isPrivate: true, - content: '<div class="sql-editor-message"></div>', + content: '<div class="sql-editor-message" tabindex: "0"></div>', }); var history = new pgAdmin.Browser.Panel({ @@ -248,7 +252,7 @@ define('tools.querytool', [ height: '100%', isCloseable: false, isPrivate: true, - content: '<div id ="history_grid" class="sql-editor-history-container"></div>', + content: '<div id ="history_grid" class="sql-editor-history-container" tabindex: "0"></div>', }); // Load all the created panels @@ -1540,8 +1544,8 @@ define('tools.querytool', [ return true; } ).set('labels', { - ok: 'Yes', - cancel: 'No', + ok: gettext('Yes'), + cancel: gettext('No'), }); } else { self.query_tool_obj.setValue(''); @@ -1569,8 +1573,8 @@ define('tools.querytool', [ return true; } ).set('labels', { - ok: 'Yes', - cancel: 'No', + ok: gettext('Yes'), + cancel: gettext('No'), }); }, @@ -1698,7 +1702,22 @@ define('tools.querytool', [ }, keyAction: function(event) { - keyboardShortcuts.processEvent(this.handler, queryToolActions, event); + var panel_id, self = this; + panel_id = keyboardShortcuts.processEventQueryTool( + this.handler, queryToolActions, event + ); + + // If it return panel id then focus it + if(!_.isNull(panel_id) && !_.isUndefined(panel_id)) { + // Returned panel index, by incrementing it by 1 we will get actual panel + panel_id++; + this.docker.findPanels()[panel_id].focus(); + // We set focus on history tab so we need to set the focus on + // editor explicitly + if(panel_id == 3) { + setTimeout(function() { self.query_tool_obj.focus(); }, 100); + } + } }, }); @@ -1827,8 +1846,8 @@ define('tools.querytool', [ return true; } ).set('labels', { - ok: 'Yes', - cancel: 'No', + ok: gettext('Yes'), + cancel: gettext('No'), }); } else { self._run_query(); diff --git a/web/regression/javascript/sqleditor/keyboard_shortcuts_spec.js b/web/regression/javascript/sqleditor/keyboard_shortcuts_spec.js index 3dafbce..792eb81 100644 --- a/web/regression/javascript/sqleditor/keyboard_shortcuts_spec.js +++ b/web/regression/javascript/sqleditor/keyboard_shortcuts_spec.js @@ -7,7 +7,7 @@ // ////////////////////////////////////////////////////////////////////////// -import keyboardShortcuts from 'sources/sqleditor/keyboard_shortcuts'; +import keyboardShortcuts from 'sources/keyboard_shortcuts'; import {queryToolActions} from 'sources/sqleditor/query_tool_actions'; describe('the keyboard shortcuts', () => { @@ -57,7 +57,7 @@ describe('the keyboard shortcuts', () => { beforeEach(() => { event.which = F1_KEY; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('should allow event to propagate', () => { @@ -69,7 +69,7 @@ describe('the keyboard shortcuts', () => { describe('when there is no query already running', () => { beforeEach(() => { event.keyCode = F5_KEY; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('should execute the query', () => { @@ -86,7 +86,7 @@ describe('the keyboard shortcuts', () => { event.keyCode = F5_KEY; sqlEditorControllerSpy.isQueryRunning.and.returnValue(true); - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); expect(queryToolActionsSpy.executeQuery).not.toHaveBeenCalled(); }); @@ -97,7 +97,7 @@ describe('the keyboard shortcuts', () => { describe('when there is not a query already running', () => { beforeEach(() => { event.which = F7_KEY; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('should explain the query plan', () => { @@ -112,7 +112,7 @@ describe('the keyboard shortcuts', () => { event.keyCode = F7_KEY; sqlEditorControllerSpy.isQueryRunning.and.returnValue(true); - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); expect(queryToolActionsSpy.explain).not.toHaveBeenCalled(); }); @@ -124,7 +124,7 @@ describe('the keyboard shortcuts', () => { beforeEach(() => { event.shiftKey = true; event.which = F7_KEY; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('should analyze explain the query plan', () => { @@ -140,7 +140,7 @@ describe('the keyboard shortcuts', () => { event.which = F7_KEY; sqlEditorControllerSpy.isQueryRunning.and.returnValue(true); - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); expect(queryToolActionsSpy.explainAnalyze).not.toHaveBeenCalled(); }); @@ -151,7 +151,7 @@ describe('the keyboard shortcuts', () => { describe('when there is not a query already running', () => { beforeEach(() => { event.which = F8_KEY; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('should download the query results as a CSV', () => { @@ -168,7 +168,7 @@ describe('the keyboard shortcuts', () => { event.keyCode = F8_KEY; sqlEditorControllerSpy.isQueryRunning.and.returnValue(true); - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); expect(queryToolActionsSpy.download).not.toHaveBeenCalled(); }); @@ -181,7 +181,7 @@ describe('the keyboard shortcuts', () => { beforeEach(() => { macKeysSetup(); event.which = FWD_SLASH_KEY; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('should comment the line', () => { @@ -195,7 +195,7 @@ describe('the keyboard shortcuts', () => { beforeEach(() => { windowsKeysSetup(); event.which = FWD_SLASH_KEY; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('should comment the line', () => { @@ -217,7 +217,7 @@ describe('the keyboard shortcuts', () => { }); it('does nothing', () => { - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); expect(queryToolActionsSpy.commentLineCode).not.toHaveBeenCalled(); }); @@ -230,7 +230,7 @@ describe('the keyboard shortcuts', () => { }); it('does nothing', () => { - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); expect(queryToolActionsSpy.commentLineCode).not.toHaveBeenCalled(); }); @@ -244,7 +244,7 @@ describe('the keyboard shortcuts', () => { beforeEach(() => { macKeysSetup(); event.which = PERIOD_KEY; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('should uncomment the line', () => { @@ -257,7 +257,7 @@ describe('the keyboard shortcuts', () => { beforeEach(() => { windowsKeysSetup(); event.which = PERIOD_KEY; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('should uncomment the line', () => { @@ -279,7 +279,7 @@ describe('the keyboard shortcuts', () => { }); it('does nothing', () => { - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); expect(queryToolActionsSpy.uncommentLineCode).not.toHaveBeenCalled(); }); }); @@ -290,7 +290,7 @@ describe('the keyboard shortcuts', () => { }); it('does nothing', () => { - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); expect(queryToolActionsSpy.uncommentLineCode).not.toHaveBeenCalled(); }); @@ -305,7 +305,7 @@ describe('the keyboard shortcuts', () => { macKeysSetup(); event.which = FWD_SLASH_KEY; event.shiftKey = true; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('should comment out the block selection', () => { @@ -322,7 +322,7 @@ describe('the keyboard shortcuts', () => { windowsKeysSetup(); event.which = FWD_SLASH_KEY; event.shiftKey = true; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('should comment out the block selection', () => { @@ -342,7 +342,7 @@ describe('the keyboard shortcuts', () => { macKeysSetup(); event.which = FWD_SLASH_KEY; event.shiftKey = true; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('does nothing', () => { expect(queryToolActionsSpy.commentBlockCode).not.toHaveBeenCalled(); @@ -353,7 +353,7 @@ describe('the keyboard shortcuts', () => { windowsKeysSetup(); event.which = FWD_SLASH_KEY; event.shiftKey = true; - keyboardShortcuts.processEvent(sqlEditorControllerSpy, queryToolActionsSpy, event); + keyboardShortcuts.processEventQueryTool(sqlEditorControllerSpy, queryToolActionsSpy, event); }); it('does nothing', () => { expect(queryToolActionsSpy.commentBlockCode).not.toHaveBeenCalled();