[Rt-commit] rt branch, 4.4/keyboard-shortcuts, created. rt-4.2.11-217-g1558c52
Dustin Graves
dustin at bestpractical.com
Wed Sep 30 11:28:16 EDT 2015
The branch, 4.4/keyboard-shortcuts has been created
at 1558c525dfbb55c6da5719613237672ea7a9829b (commit)
- Log -----------------------------------------------------------------
commit 1558c525dfbb55c6da5719613237672ea7a9829b
Author: Dustin Graves <dustin at bestpractical.com>
Date: Thu Sep 17 14:04:17 2015 +0000
add global / page-specific keyboard shortcuts
global shortcuts:
gh - click on home link
gb - browser back
gf - browser forward
/ - highlight quick search
search results shortcuts:
k/j - up/down through search results
o or <Enter> - view highlighted ticket
x - toggle highlighted ticket's checkbox (on bulk update)
Fixes: T#151846
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index b936325..eda827e 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -128,6 +128,8 @@ sub JSFiles {
forms.js
event-registration.js
late.js
+ mousetrap.min.js
+ keyboard-shortcuts/global.js
/static/RichText/ckeditor.js
dropzone.min.js
}, RT->Config->Get('JSFiles');
diff --git a/share/html/Elements/Footer b/share/html/Elements/Footer
index dc1ce6b..b10da49 100644
--- a/share/html/Elements/Footer
+++ b/share/html/Elements/Footer
@@ -45,6 +45,11 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
+%# Keyboard shortcuts info
+ <div class="clear"></div>
+ <div class="keyboard-shortcuts" style="font-size: 0.9em; margin: 10px 0 0 5px;">
+ <span><&|/l&>Press</&></span><span class="key"> ? </span><span><&|/l&>to view keyboard shortcuts.</&></span>
+ </div>
%# End of div#body from /Elements/PageLayout
<hr class="clear" />
</div>
diff --git a/share/html/Search/Bulk.html b/share/html/Search/Bulk.html
index 10799af..4208216 100644
--- a/share/html/Search/Bulk.html
+++ b/share/html/Search/Bulk.html
@@ -190,6 +190,7 @@ $cfs->SetContextObject( values %$seen_queues ) if keys %$seen_queues == 1;
</form>
+<script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/static/js/keyboard-shortcuts/search.js"></script>
<%INIT>
unless ( defined $Rows ) {
diff --git a/share/html/Search/Results.html b/share/html/Search/Results.html
index b6b3379..f8a5843 100644
--- a/share/html/Search/Results.html
+++ b/share/html/Search/Results.html
@@ -89,6 +89,9 @@
<input type="submit" class="button" value="<&|/l&>Change</&>" />
</form>
</div>
+
+<script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/static/js/keyboard-shortcuts/search.js"></script>
+
<%INIT>
$m->callback( ARGSRef => \%ARGS, CallbackName => 'Initial' );
diff --git a/share/html/Elements/Footer b/share/html/Shortcuts/Elements/index
similarity index 52%
copy from share/html/Elements/Footer
copy to share/html/Shortcuts/Elements/index
index dc1ce6b..bf08aab 100644
--- a/share/html/Elements/Footer
+++ b/share/html/Shortcuts/Elements/index
@@ -2,7 +2,7 @@
%#
%# COPYRIGHT:
%#
-%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+%# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
%# <sales at bestpractical.com>
%#
%# (Except where explicitly superseded by other copyright notices)
@@ -45,30 +45,55 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-%# End of div#body from /Elements/PageLayout
- <hr class="clear" />
+
+<h2 style="text-align: center"><&|/l&>Keyboard Shortcuts</&></h2>
+
+<div class="titlebox keyboard-shortcuts">
+ <div class="titlebox-title">
+ <span class="left"><&|/l&>Global</&></span>
+ <span class="right-empty"></span>
+ </div>
+ <div class="titlebox-content">
+ <hr class="clear">
+ <table>
+ <tr>
+ <td style="width:100px"><span class="key">/</span></td>
+ <td><% loc("Quick Search") %></td>
+ </tr>
+ <tr>
+ <td><span class="key">gh</span></td>
+ <td><% loc("Return Home") %></td>
+ </tr>
+ <tr>
+ <td><span class="key">gb</span> <span class="sm">/</span> <span class="key">gf</span></td>
+ <td><% loc("Go Back / Forward") %></td>
+ </tr>
+ </table>
+ </div>
</div>
-% $m->callback( %ARGS );
-<div id="footer">
-% if ($m->{'rt_base_time'}) {
- <p id="time"><span><&|/l&>Time to display</&>: <%Time::HiRes::tv_interval( $m->{'rt_base_time'} )%></span></p>
-%}
- <p id="bpscredits"><span><&|/l_unsafe, '»|«', $RT::VERSION, '2015', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>', &>[_1] RT [_2] Copyright 1996-[_3] [_4].</&>
-</span></p>
-% if (!$Menu) {
- <p id="legal"><&|/l_unsafe, '<a href="http://www.gnu.org/licenses/gpl-2.0.html">', '</a>' &>Distributed under [_1]version 2 of the GNU GPL[_2].</&><br /><&|/l_unsafe, '<a href="mailto:sales at bestpractical.com">sales at bestpractical.com</a>' &>To inquire about support, training, custom development or licensing, please contact [_1].</&><br /></p>
-% }
+
+<div class="titlebox keyboard-shortcuts">
+ <div class="titlebox-title">
+ <span class="left"><&|/l&>Search</&></span>
+ <span class="right-empty"></span>
+ </div>
+ <div class="titlebox-content">
+ <hr class="clear">
+ <table>
+ <tr>
+ <td style="width:100px"><span class="key">k</span><span class="sm"> / </span><span class="key">j</span></td>
+ <td><&|/l&>Move up/down the list of results</&></td>
+ </tr>
+ <tr>
+ <td><span class="key">o</span> <span class="sm">or</span> <span class="key"><<&|/l&>Enter</&>></span></td>
+ <td><&|/l&>View highlighted ticket</&></td>
+ </tr>
+ <tr>
+ <td><span class="key">x</span></td>
+ <td><&|/l&>Toggle highlighted ticket's checkbox</&></td>
+ </tr>
+ </table>
+ </div>
</div>
-% if ($Debug >= 2 ) {
-% require Data::Dumper;
-% my $d = Data::Dumper->new([\%ARGS], [qw(%ARGS)]);
-<pre>
-<%$d->Dump() %>
-</pre>
-% }
- </body>
-</html>
-<%ARGS>
-$Debug => 0
-$Menu => 1
-</%ARGS>
+
+
diff --git a/share/html/Elements/Footer b/share/html/Shortcuts/Helpers/index
similarity index 63%
copy from share/html/Elements/Footer
copy to share/html/Shortcuts/Helpers/index
index dc1ce6b..c7244d0 100644
--- a/share/html/Elements/Footer
+++ b/share/html/Shortcuts/Helpers/index
@@ -2,7 +2,7 @@
%#
%# COPYRIGHT:
%#
-%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+%# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
%# <sales at bestpractical.com>
%#
%# (Except where explicitly superseded by other copyright notices)
@@ -45,30 +45,5 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-%# End of div#body from /Elements/PageLayout
- <hr class="clear" />
-</div>
-% $m->callback( %ARGS );
-<div id="footer">
-% if ($m->{'rt_base_time'}) {
- <p id="time"><span><&|/l&>Time to display</&>: <%Time::HiRes::tv_interval( $m->{'rt_base_time'} )%></span></p>
-%}
- <p id="bpscredits"><span><&|/l_unsafe, '»|«', $RT::VERSION, '2015', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>', &>[_1] RT [_2] Copyright 1996-[_3] [_4].</&>
-</span></p>
-% if (!$Menu) {
- <p id="legal"><&|/l_unsafe, '<a href="http://www.gnu.org/licenses/gpl-2.0.html">', '</a>' &>Distributed under [_1]version 2 of the GNU GPL[_2].</&><br /><&|/l_unsafe, '<a href="mailto:sales at bestpractical.com">sales at bestpractical.com</a>' &>To inquire about support, training, custom development or licensing, please contact [_1].</&><br /></p>
-% }
-</div>
-% if ($Debug >= 2 ) {
-% require Data::Dumper;
-% my $d = Data::Dumper->new([\%ARGS], [qw(%ARGS)]);
-<pre>
-<%$d->Dump() %>
-</pre>
-% }
- </body>
-</html>
-<%ARGS>
-$Debug => 0
-$Menu => 1
-</%ARGS>
+<& /Shortcuts/Elements/index &>
+% $m->abort;
diff --git a/share/html/Elements/Footer b/share/html/Shortcuts/index.html
similarity index 63%
copy from share/html/Elements/Footer
copy to share/html/Shortcuts/index.html
index dc1ce6b..9bf0100 100644
--- a/share/html/Elements/Footer
+++ b/share/html/Shortcuts/index.html
@@ -2,7 +2,7 @@
%#
%# COPYRIGHT:
%#
-%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+%# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
%# <sales at bestpractical.com>
%#
%# (Except where explicitly superseded by other copyright notices)
@@ -45,30 +45,7 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-%# End of div#body from /Elements/PageLayout
- <hr class="clear" />
-</div>
-% $m->callback( %ARGS );
-<div id="footer">
-% if ($m->{'rt_base_time'}) {
- <p id="time"><span><&|/l&>Time to display</&>: <%Time::HiRes::tv_interval( $m->{'rt_base_time'} )%></span></p>
-%}
- <p id="bpscredits"><span><&|/l_unsafe, '»|«', $RT::VERSION, '2015', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>', &>[_1] RT [_2] Copyright 1996-[_3] [_4].</&>
-</span></p>
-% if (!$Menu) {
- <p id="legal"><&|/l_unsafe, '<a href="http://www.gnu.org/licenses/gpl-2.0.html">', '</a>' &>Distributed under [_1]version 2 of the GNU GPL[_2].</&><br /><&|/l_unsafe, '<a href="mailto:sales at bestpractical.com">sales at bestpractical.com</a>' &>To inquire about support, training, custom development or licensing, please contact [_1].</&><br /></p>
-% }
-</div>
-% if ($Debug >= 2 ) {
-% require Data::Dumper;
-% my $d = Data::Dumper->new([\%ARGS], [qw(%ARGS)]);
-<pre>
-<%$d->Dump() %>
-</pre>
-% }
- </body>
-</html>
-<%ARGS>
-$Debug => 0
-$Menu => 1
-</%ARGS>
+<& /Elements/Header,
+ Title => loc("Keyboard Shortcuts") &>
+<& /Elements/Tabs &>
+<& /Asset/Elements/Index &>
diff --git a/share/static/css/base/misc.css b/share/static/css/base/misc.css
index d42e01e..095ba68 100644
--- a/share/static/css/base/misc.css
+++ b/share/static/css/base/misc.css
@@ -89,3 +89,17 @@ textarea.messagebox, #cke_Content, #cke_UpdateContent {
.dashboard-subscription tr.frequency .value input {
margin-bottom: 0.75em;
}
+
+.keyboard-shortcuts td {
+ text-align: left;
+ padding-right: 5px;
+}
+
+.keyboard-shortcuts .key {
+ color: #3858a3;
+ font-family: 'Courier New';
+}
+
+.keyboard-shortcuts .sm {
+ font-size: 75%;
+}
diff --git a/share/static/js/keyboard-shortcuts/global.js b/share/static/js/keyboard-shortcuts/global.js
new file mode 100644
index 0000000..fdf1017
--- /dev/null
+++ b/share/static/js/keyboard-shortcuts/global.js
@@ -0,0 +1,52 @@
+jQuery(function() {
+ Mousetrap.bind('g b', function() {
+ window.history.back();
+ });
+
+ Mousetrap.bind('g f', function() {
+ window.history.forward();
+ });
+
+ function goHome() {
+ var homeLink = jQuery('a#home');
+ window.location.href = homeLink.attr('href');
+ }
+
+ Mousetrap.bind('g h', function() {
+ goHome();
+ });
+
+ function simpleSearch() {
+ var searchInput = jQuery('#simple-search').find('input');
+
+ if (!searchInput.length || searchInput.is(':focus')) return;
+
+ searchInput.focus();
+ searchInput.select();
+ }
+
+ Mousetrap.bind('/', function() {
+ simpleSearch();
+ return false;
+ });
+
+ var showModal = function(html) {
+ jQuery("<div class='modal'></div>")
+ .append(html).appendTo("body")
+ .bind('modal:close', function(ev,modal) { modal.elm.remove(); })
+ .modal();
+ };
+
+ Mousetrap.bind('?', function() {
+ var modal = jQuery('.modal');
+ if (modal.length) {
+ jQuery.modal.close();
+ return;
+ }
+
+ jQuery.get(
+ RT.Config.WebHomePath + "/Shortcuts/Helpers/index",
+ showModal
+ );
+ });
+});
diff --git a/share/static/js/keyboard-shortcuts/search.js b/share/static/js/keyboard-shortcuts/search.js
new file mode 100644
index 0000000..a364f93
--- /dev/null
+++ b/share/static/js/keyboard-shortcuts/search.js
@@ -0,0 +1,61 @@
+jQuery(function() {
+ var currentRow;
+
+ var navigateToCurrentTicket = function() {
+ if (!currentRow) return;
+ var ticketLink = currentRow.find('a');
+ if (!ticketLink.length) return;
+ window.location.href = ticketLink.attr('href');
+ return false;
+ }
+ Mousetrap.bind(['enter','o'], navigateToCurrentTicket);
+
+ function setNewRow(newRow) {
+ if (currentRow) currentRow.attr('style', '');
+ currentRow = newRow;
+ currentRow.attr('style', 'border-left-color: #3858a3; border-left-width: 3px;');
+ scrollToElement(currentRow);
+ }
+
+ function scrollToElement(element) {
+ if (!element.length) return;
+
+ var viewportHeight = jQuery(window).height(),
+ currentScrollPosition = jQuery(window).scrollTop(),
+ currentItemPosition = element.offset().top,
+ currentItemSize = element.height() + element.next().height();
+
+ if (currentScrollPosition + viewportHeight < currentItemPosition + currentItemSize) {
+ jQuery('html, body').scrollTop(currentItemPosition - viewportHeight + currentItemSize);
+ } else if (currentScrollPosition > currentItemPosition) {
+ jQuery('html, body').scrollTop(currentItemPosition);
+ }
+ }
+
+ var nextTicket = function() {
+ var nextRow;
+ var searchResultsTable = jQuery('.ticket-list.collection-as-table');
+ if (!currentRow || !(nextRow = currentRow.next().next(':not(.collection-as-table)')).length) {
+ nextRow = searchResultsTable.find('tr.oddline').first();
+ }
+ setNewRow(nextRow);
+ }
+ Mousetrap.bind('j', nextTicket);
+
+ var prevTicket = function() {
+ var prevRow, searchResultsTable = jQuery('.ticket-list.collection-as-table');
+ if (!currentRow || !(prevRow = currentRow.prev().prev(':not(.collection-as-table)')).length) {
+ prevRow = searchResultsTable.find('tr').last().prev();
+ }
+ setNewRow(prevRow);
+ }
+ Mousetrap.bind('k', prevTicket);
+
+ var toggleTicketCheckbox = function() {
+ if (!currentRow) return;
+ var ticketCheckBox = currentRow.find('input[type=checkbox]');
+ if (!ticketCheckBox.length) return;
+ ticketCheckBox.prop("checked", !ticketCheckBox.prop("checked"));
+ }
+ Mousetrap.bind('x', toggleTicketCheckbox);
+});
diff --git a/share/static/js/mousetrap.min.js b/share/static/js/mousetrap.min.js
new file mode 100644
index 0000000..291aff8
--- /dev/null
+++ b/share/static/js/mousetrap.min.js
@@ -0,0 +1,11 @@
+/* mousetrap v1.5.3 craig.is/killing/mice */
+(function(C,r,g){function t(a,b,h){a.addEventListener?a.addEventListener(b,h,!1):a.attachEvent("on"+b,h)}function x(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return l[a.which]?l[a.which]:p[a.which]?p[a.which]:String.fromCharCode(a.which).toLowerCase()}function D(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function u(a){return"shift"==a||"ctrl"==a||"alt"==a||
+"meta"==a}function y(a,b){var h,c,e,g=[];h=a;"+"===h?h=["+"]:(h=h.replace(/\+{2}/g,"+plus"),h=h.split("+"));for(e=0;e<h.length;++e)c=h[e],z[c]&&(c=z[c]),b&&"keypress"!=b&&A[c]&&(c=A[c],g.push("shift")),u(c)&&g.push(c);h=c;e=b;if(!e){if(!k){k={};for(var m in l)95<m&&112>m||l.hasOwnProperty(m)&&(k[l[m]]=m)}e=k[h]?"keydown":"keypress"}"keypress"==e&&g.length&&(e="keydown");return{key:c,modifiers:g,action:e}}function B(a,b){return null===a||a===r?!1:a===b?!0:B(a.parentNode,b)}function c(a){function b(a){a=
+a||{};var b=!1,n;for(n in q)a[n]?b=!0:q[n]=0;b||(v=!1)}function h(a,b,n,f,c,h){var g,e,l=[],m=n.type;if(!d._callbacks[a])return[];"keyup"==m&&u(a)&&(b=[a]);for(g=0;g<d._callbacks[a].length;++g)if(e=d._callbacks[a][g],(f||!e.seq||q[e.seq]==e.level)&&m==e.action){var k;(k="keypress"==m&&!n.metaKey&&!n.ctrlKey)||(k=e.modifiers,k=b.sort().join(",")===k.sort().join(","));k&&(k=f&&e.seq==f&&e.level==h,(!f&&e.combo==c||k)&&d._callbacks[a].splice(g,1),l.push(e))}return l}function g(a,b,n,f){d.stopCallback(b,
+b.target||b.srcElement,n,f)||!1!==a(b,n)||(b.preventDefault?b.preventDefault():b.returnValue=!1,b.stopPropagation?b.stopPropagation():b.cancelBubble=!0)}function e(a){"number"!==typeof a.which&&(a.which=a.keyCode);var b=x(a);b&&("keyup"==a.type&&w===b?w=!1:d.handleKey(b,D(a),a))}function l(a,c,n,f){function e(c){return function(){v=c;++q[a];clearTimeout(k);k=setTimeout(b,1E3)}}function h(c){g(n,c,a);"keyup"!==f&&(w=x(c));setTimeout(b,10)}for(var d=q[a]=0;d<c.length;++d){var p=d+1===c.length?h:e(f||
+y(c[d+1]).action);m(c[d],p,f,a,d)}}function m(a,b,c,f,e){d._directMap[a+":"+c]=b;a=a.replace(/\s+/g," ");var g=a.split(" ");1<g.length?l(a,g,b,c):(c=y(a,c),d._callbacks[c.key]=d._callbacks[c.key]||[],h(c.key,c.modifiers,{type:c.action},f,a,e),d._callbacks[c.key][f?"unshift":"push"]({callback:b,modifiers:c.modifiers,action:c.action,seq:f,level:e,combo:a}))}var d=this;a=a||r;if(!(d instanceof c))return new c(a);d.target=a;d._callbacks={};d._directMap={};var q={},k,w=!1,p=!1,v=!1;d._handleKey=function(a,
+c,e){var f=h(a,c,e),d;c={};var k=0,l=!1;for(d=0;d<f.length;++d)f[d].seq&&(k=Math.max(k,f[d].level));for(d=0;d<f.length;++d)f[d].seq?f[d].level==k&&(l=!0,c[f[d].seq]=1,g(f[d].callback,e,f[d].combo,f[d].seq)):l||g(f[d].callback,e,f[d].combo);f="keypress"==e.type&&p;e.type!=v||u(a)||f||b(c);p=l&&"keydown"==e.type};d._bindMultiple=function(a,b,c){for(var d=0;d<a.length;++d)m(a[d],b,c)};t(a,"keypress",e);t(a,"keydown",e);t(a,"keyup",e)}var l={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",
+20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},p={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},A={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},z={option:"alt",command:"meta","return":"enter",
+escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},k;for(g=1;20>g;++g)l[111+g]="f"+g;for(g=0;9>=g;++g)l[g+96]=g;c.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};c.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};c.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};c.prototype.reset=function(){this._callbacks={};this._directMap=
+{};return this};c.prototype.stopCallback=function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")||B(b,this.target)?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};c.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};c.init=function(){var a=c(r),b;for(b in a)"_"!==b.charAt(0)&&(c[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};c.init();C.Mousetrap=c;"undefined"!==typeof module&&module.exports&&(module.exports=
+c);"function"===typeof define&&define.amd&&define(function(){return c})})(window,document);
-----------------------------------------------------------------------
More information about the rt-commit
mailing list