Fossil SCM
Shortcut "." (period) to set focus to the entry closest to the center of the viewport. (The keys need to be reassigned later, since non-letter keys don't produce the same characters with or without SHIFT pressed on all keyboard layouts.) Thanks @rouilj for the suggestion and the hint!
Commit
e1796f2df2e55e1357d9b67a6028b1afa89fcba46d90cbcafbeea43601c61501
Parent
cf38f5abb8475e2…
2 files changed
+43
-1
+1
+43
-1
| --- src/graph.js | ||
| +++ src/graph.js | ||
| @@ -879,10 +879,41 @@ | ||
| 879 | 879 | if( dx<-1 ) return focusFirstId(id); |
| 880 | 880 | if( dx>+1 ) return focusLastId(id); |
| 881 | 881 | var m = /^m(\d+)$/.exec(id); |
| 882 | 882 | return m!==null ? 'm' + (parseInt(m[1]) + dx) : null; |
| 883 | 883 | } |
| 884 | + // NOTE: This code relies on `getElementsByClassName()' returning elements | |
| 885 | + // in document order; however, attempts to use binary searching (using the | |
| 886 | + // signed distance to the vertical center) or 2-pass searching (to get the | |
| 887 | + // 1000-block first) didn't yield any significant speedups -- probably the | |
| 888 | + // calls to `getBoundingClientRect()' are cached because `initGraph()' has | |
| 889 | + // already done so, or because they depend on their previous siblings, and | |
| 890 | + // so `e(N).getBoundingClientRect()' ≈ `e(0..N).getBoundingClientRect()'? | |
| 891 | + function focusViewportCenterId(){ | |
| 892 | + var | |
| 893 | + fe = null, | |
| 894 | + fd = 0, | |
| 895 | + wt = 0, | |
| 896 | + wb = wt + window.innerHeight, | |
| 897 | + wc = wt + window.innerHeight/2, | |
| 898 | + el = document.getElementsByClassName('timelineGraph'); | |
| 899 | + for( var i=0; i<el.length; i++ ){ | |
| 900 | + var | |
| 901 | + rc = el[i].getBoundingClientRect(), | |
| 902 | + et = rc.top, | |
| 903 | + eb = rc.bottom; | |
| 904 | + if( (et>=wt && et<=wb) || (eb>=wt && eb<=wb) ){ | |
| 905 | + var ed = Math.min(Math.abs(et-wc),Math.abs(eb-wc)); | |
| 906 | + if( !fe || ed<fd ){ | |
| 907 | + fe = el[i]; | |
| 908 | + fd = ed; | |
| 909 | + } | |
| 910 | + else break; | |
| 911 | + } | |
| 912 | + } | |
| 913 | + return fe ? fe.querySelector('.tl-nodemark').id : null; | |
| 914 | + } | |
| 884 | 915 | function timelineGetDataBlock(i){ |
| 885 | 916 | var tb = document.getElementById('timeline-data-' + i); |
| 886 | 917 | return tb ? JSON.parse(tb.textContent || tb.innerText) : null; |
| 887 | 918 | } |
| 888 | 919 | function timelineGetRowInfo(id){ |
| @@ -959,10 +990,11 @@ | ||
| 959 | 990 | kFRST = mSHIFT | 78 /* SHIFT+N */, |
| 960 | 991 | kNEXT = 78 /* N */, |
| 961 | 992 | kPREV = 77 /* M */, |
| 962 | 993 | kLAST = mSHIFT | 77 /* SHIFT+M */, |
| 963 | 994 | kCYCL = 72 /* H */, |
| 995 | + kCNTR = 190 /* . */, | |
| 964 | 996 | kSCRL = mSHIFT | 72 /* H */, |
| 965 | 997 | kTICK = 188 /* , */, |
| 966 | 998 | kUNTK = mSHIFT | 188 /* , */, |
| 967 | 999 | kCPYH = 66 /* B */, |
| 968 | 1000 | kCPYB = mSHIFT | 66 /* SHIFT+B */, |
| @@ -977,10 +1009,11 @@ | ||
| 977 | 1009 | case kFRST: dx = -2; break; |
| 978 | 1010 | case kNEXT: dx = -1; break; |
| 979 | 1011 | case kPREV: dx = +1; break; |
| 980 | 1012 | case kLAST: dx = +2; break; |
| 981 | 1013 | case kCYCL: |
| 1014 | + case kCNTR: | |
| 982 | 1015 | case kSCRL: |
| 983 | 1016 | case kTICK: |
| 984 | 1017 | case kUNTK: |
| 985 | 1018 | case kCPYH: |
| 986 | 1019 | case kCPYB: |
| @@ -988,11 +1021,20 @@ | ||
| 988 | 1021 | case kTMLB: |
| 989 | 1022 | case kVIEW: |
| 990 | 1023 | case kDONE: break; |
| 991 | 1024 | default: return; |
| 992 | 1025 | } |
| 993 | - if( key==kSCRL ){ | |
| 1026 | + if( key==kCNTR ){ | |
| 1027 | + var cid = focusViewportCenterId(); | |
| 1028 | + if( cid ){ | |
| 1029 | + focusCacheSet(cid); | |
| 1030 | + focusVisualize(cid,false); | |
| 1031 | + focusCookieInit(); | |
| 1032 | + } | |
| 1033 | + return; | |
| 1034 | + } | |
| 1035 | + else if( key==kSCRL ){ | |
| 994 | 1036 | var td = document.querySelector('.timelineFocused'); |
| 995 | 1037 | if( td ) fossilScrollIntoView(td); |
| 996 | 1038 | return; |
| 997 | 1039 | } |
| 998 | 1040 | else if( key==kUNTK ){ |
| 999 | 1041 |
| --- src/graph.js | |
| +++ src/graph.js | |
| @@ -879,10 +879,41 @@ | |
| 879 | if( dx<-1 ) return focusFirstId(id); |
| 880 | if( dx>+1 ) return focusLastId(id); |
| 881 | var m = /^m(\d+)$/.exec(id); |
| 882 | return m!==null ? 'm' + (parseInt(m[1]) + dx) : null; |
| 883 | } |
| 884 | function timelineGetDataBlock(i){ |
| 885 | var tb = document.getElementById('timeline-data-' + i); |
| 886 | return tb ? JSON.parse(tb.textContent || tb.innerText) : null; |
| 887 | } |
| 888 | function timelineGetRowInfo(id){ |
| @@ -959,10 +990,11 @@ | |
| 959 | kFRST = mSHIFT | 78 /* SHIFT+N */, |
| 960 | kNEXT = 78 /* N */, |
| 961 | kPREV = 77 /* M */, |
| 962 | kLAST = mSHIFT | 77 /* SHIFT+M */, |
| 963 | kCYCL = 72 /* H */, |
| 964 | kSCRL = mSHIFT | 72 /* H */, |
| 965 | kTICK = 188 /* , */, |
| 966 | kUNTK = mSHIFT | 188 /* , */, |
| 967 | kCPYH = 66 /* B */, |
| 968 | kCPYB = mSHIFT | 66 /* SHIFT+B */, |
| @@ -977,10 +1009,11 @@ | |
| 977 | case kFRST: dx = -2; break; |
| 978 | case kNEXT: dx = -1; break; |
| 979 | case kPREV: dx = +1; break; |
| 980 | case kLAST: dx = +2; break; |
| 981 | case kCYCL: |
| 982 | case kSCRL: |
| 983 | case kTICK: |
| 984 | case kUNTK: |
| 985 | case kCPYH: |
| 986 | case kCPYB: |
| @@ -988,11 +1021,20 @@ | |
| 988 | case kTMLB: |
| 989 | case kVIEW: |
| 990 | case kDONE: break; |
| 991 | default: return; |
| 992 | } |
| 993 | if( key==kSCRL ){ |
| 994 | var td = document.querySelector('.timelineFocused'); |
| 995 | if( td ) fossilScrollIntoView(td); |
| 996 | return; |
| 997 | } |
| 998 | else if( key==kUNTK ){ |
| 999 |
| --- src/graph.js | |
| +++ src/graph.js | |
| @@ -879,10 +879,41 @@ | |
| 879 | if( dx<-1 ) return focusFirstId(id); |
| 880 | if( dx>+1 ) return focusLastId(id); |
| 881 | var m = /^m(\d+)$/.exec(id); |
| 882 | return m!==null ? 'm' + (parseInt(m[1]) + dx) : null; |
| 883 | } |
| 884 | // NOTE: This code relies on `getElementsByClassName()' returning elements |
| 885 | // in document order; however, attempts to use binary searching (using the |
| 886 | // signed distance to the vertical center) or 2-pass searching (to get the |
| 887 | // 1000-block first) didn't yield any significant speedups -- probably the |
| 888 | // calls to `getBoundingClientRect()' are cached because `initGraph()' has |
| 889 | // already done so, or because they depend on their previous siblings, and |
| 890 | // so `e(N).getBoundingClientRect()' ≈ `e(0..N).getBoundingClientRect()'? |
| 891 | function focusViewportCenterId(){ |
| 892 | var |
| 893 | fe = null, |
| 894 | fd = 0, |
| 895 | wt = 0, |
| 896 | wb = wt + window.innerHeight, |
| 897 | wc = wt + window.innerHeight/2, |
| 898 | el = document.getElementsByClassName('timelineGraph'); |
| 899 | for( var i=0; i<el.length; i++ ){ |
| 900 | var |
| 901 | rc = el[i].getBoundingClientRect(), |
| 902 | et = rc.top, |
| 903 | eb = rc.bottom; |
| 904 | if( (et>=wt && et<=wb) || (eb>=wt && eb<=wb) ){ |
| 905 | var ed = Math.min(Math.abs(et-wc),Math.abs(eb-wc)); |
| 906 | if( !fe || ed<fd ){ |
| 907 | fe = el[i]; |
| 908 | fd = ed; |
| 909 | } |
| 910 | else break; |
| 911 | } |
| 912 | } |
| 913 | return fe ? fe.querySelector('.tl-nodemark').id : null; |
| 914 | } |
| 915 | function timelineGetDataBlock(i){ |
| 916 | var tb = document.getElementById('timeline-data-' + i); |
| 917 | return tb ? JSON.parse(tb.textContent || tb.innerText) : null; |
| 918 | } |
| 919 | function timelineGetRowInfo(id){ |
| @@ -959,10 +990,11 @@ | |
| 990 | kFRST = mSHIFT | 78 /* SHIFT+N */, |
| 991 | kNEXT = 78 /* N */, |
| 992 | kPREV = 77 /* M */, |
| 993 | kLAST = mSHIFT | 77 /* SHIFT+M */, |
| 994 | kCYCL = 72 /* H */, |
| 995 | kCNTR = 190 /* . */, |
| 996 | kSCRL = mSHIFT | 72 /* H */, |
| 997 | kTICK = 188 /* , */, |
| 998 | kUNTK = mSHIFT | 188 /* , */, |
| 999 | kCPYH = 66 /* B */, |
| 1000 | kCPYB = mSHIFT | 66 /* SHIFT+B */, |
| @@ -977,10 +1009,11 @@ | |
| 1009 | case kFRST: dx = -2; break; |
| 1010 | case kNEXT: dx = -1; break; |
| 1011 | case kPREV: dx = +1; break; |
| 1012 | case kLAST: dx = +2; break; |
| 1013 | case kCYCL: |
| 1014 | case kCNTR: |
| 1015 | case kSCRL: |
| 1016 | case kTICK: |
| 1017 | case kUNTK: |
| 1018 | case kCPYH: |
| 1019 | case kCPYB: |
| @@ -988,11 +1021,20 @@ | |
| 1021 | case kTMLB: |
| 1022 | case kVIEW: |
| 1023 | case kDONE: break; |
| 1024 | default: return; |
| 1025 | } |
| 1026 | if( key==kCNTR ){ |
| 1027 | var cid = focusViewportCenterId(); |
| 1028 | if( cid ){ |
| 1029 | focusCacheSet(cid); |
| 1030 | focusVisualize(cid,false); |
| 1031 | focusCookieInit(); |
| 1032 | } |
| 1033 | return; |
| 1034 | } |
| 1035 | else if( key==kSCRL ){ |
| 1036 | var td = document.querySelector('.timelineFocused'); |
| 1037 | if( td ) fossilScrollIntoView(td); |
| 1038 | return; |
| 1039 | } |
| 1040 | else if( key==kUNTK ){ |
| 1041 |
+1
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -1635,10 +1635,11 @@ | ||
| 1635 | 1635 | ** N Focus first (newest) entry. |
| 1636 | 1636 | ** n Focus next (newer) entry, or open next page. |
| 1637 | 1637 | ** m Focus previous (older) entry, or open previous page. |
| 1638 | 1638 | ** M Focus last (oldest) entry. |
| 1639 | 1639 | ** h Move focus between selected, current (check-out) and ticked entries. |
| 1640 | +** . Focus entry closest to center of viewport. | |
| 1640 | 1641 | ** H Scroll to focused entry. |
| 1641 | 1642 | ** , Tick/untick the node of the focused entry. |
| 1642 | 1643 | ** ; Untick the nodes of all entries. |
| 1643 | 1644 | ** b Copy the commit hash of the focused entry to clipboard. |
| 1644 | 1645 | ** B Copy the branch name of the focused entry to clipboard. |
| 1645 | 1646 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -1635,10 +1635,11 @@ | |
| 1635 | ** N Focus first (newest) entry. |
| 1636 | ** n Focus next (newer) entry, or open next page. |
| 1637 | ** m Focus previous (older) entry, or open previous page. |
| 1638 | ** M Focus last (oldest) entry. |
| 1639 | ** h Move focus between selected, current (check-out) and ticked entries. |
| 1640 | ** H Scroll to focused entry. |
| 1641 | ** , Tick/untick the node of the focused entry. |
| 1642 | ** ; Untick the nodes of all entries. |
| 1643 | ** b Copy the commit hash of the focused entry to clipboard. |
| 1644 | ** B Copy the branch name of the focused entry to clipboard. |
| 1645 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -1635,10 +1635,11 @@ | |
| 1635 | ** N Focus first (newest) entry. |
| 1636 | ** n Focus next (newer) entry, or open next page. |
| 1637 | ** m Focus previous (older) entry, or open previous page. |
| 1638 | ** M Focus last (oldest) entry. |
| 1639 | ** h Move focus between selected, current (check-out) and ticked entries. |
| 1640 | ** . Focus entry closest to center of viewport. |
| 1641 | ** H Scroll to focused entry. |
| 1642 | ** , Tick/untick the node of the focused entry. |
| 1643 | ** ; Untick the nodes of all entries. |
| 1644 | ** b Copy the commit hash of the focused entry to clipboard. |
| 1645 | ** B Copy the branch name of the focused entry to clipboard. |
| 1646 |