mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-25 16:22:43 +01:00
Add "buoyant" headers to Differential
Summary: As you scroll through a diff, add a fixed-position header to the top of the document to provide context. This is particularly useful with keyboard navigation. The technical implementation is that we seed the document with invisible markers. When the user scrolls past one, we show a header with that text until they scroll past another. Test Plan: Scrolled through a revision, was presented with context. https://secure.phabricator.com/file/data/5xhh2jmoon6ukr5qjkh3/PHID-FILE-463ituscyhyw7utnox7m/Screen_Shot_2012-02-22_at_2.48.19_PM.png Reviewers: btrahan Reviewed By: btrahan CC: aran, epriestley Maniphest Tasks: T696 Differential Revision: https://secure.phabricator.com/D1673
This commit is contained in:
parent
cdd55eda14
commit
bf3dd8663c
6 changed files with 235 additions and 86 deletions
2
externals/javelin
vendored
2
externals/javelin
vendored
|
@ -1 +1 @@
|
|||
Subproject commit f25470da2c98cd37fbac435a56128b1d465f7667
|
||||
Subproject commit 45fa775fea5cc7fa1d834a04b9d25a08bcfa4812
|
|
@ -330,30 +330,6 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/javelin/lib/behavior.js',
|
||||
),
|
||||
'javelin-behavior-aphront-drag-and-drop' =>
|
||||
array(
|
||||
'uri' => '/res/ac21045a/rsrc/js/application/core/behavior-drag-and-drop.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-dom',
|
||||
2 => 'javelin-util',
|
||||
3 => 'phabricator-drag-and-drop-file-upload',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-drag-and-drop.js',
|
||||
),
|
||||
0 =>
|
||||
array(
|
||||
'uri' => '/res/b6096fdd/rsrc/js/javelin/lib/__tests__/URI.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-uri',
|
||||
1 => 'javelin-php-serializer',
|
||||
),
|
||||
'disk' => '/rsrc/js/javelin/lib/__tests__/URI.js',
|
||||
),
|
||||
'javelin-behavior-aphront-basic-tokenizer' =>
|
||||
array(
|
||||
'uri' => '/res/9be30797/rsrc/js/application/core/behavior-tokenizer.js',
|
||||
|
@ -370,6 +346,19 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-tokenizer.js',
|
||||
),
|
||||
'javelin-behavior-aphront-drag-and-drop' =>
|
||||
array(
|
||||
'uri' => '/res/ac21045a/rsrc/js/application/core/behavior-drag-and-drop.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-dom',
|
||||
2 => 'javelin-util',
|
||||
3 => 'phabricator-drag-and-drop-file-upload',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-drag-and-drop.js',
|
||||
),
|
||||
'javelin-behavior-aphront-drag-and-drop-textarea' =>
|
||||
array(
|
||||
'uri' => '/res/fa7527f9/rsrc/js/application/core/behavior-drag-and-drop-textarea.js',
|
||||
|
@ -394,6 +383,19 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-form.js',
|
||||
),
|
||||
'javelin-behavior-buoyant' =>
|
||||
array(
|
||||
'uri' => '/res/e7581db1/rsrc/js/application/core/behavior-buoyant.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-stratcom',
|
||||
2 => 'javelin-vector',
|
||||
3 => 'javelin-dom',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-buoyant.js',
|
||||
),
|
||||
'javelin-behavior-countdown-timer' =>
|
||||
array(
|
||||
'uri' => '/res/5ee9cb13/rsrc/js/application/countdown/timer.js',
|
||||
|
@ -869,7 +871,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-dom' =>
|
||||
array(
|
||||
'uri' => '/res/b2e8a5b6/rsrc/js/javelin/lib/DOM.js',
|
||||
'uri' => '/res/4c86aaeb/rsrc/js/javelin/lib/DOM.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -952,7 +954,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-magical-init' =>
|
||||
array(
|
||||
'uri' => '/res/0e72d59b/rsrc/js/javelin/core/init.js',
|
||||
'uri' => '/res/d6832060/rsrc/js/javelin/core/init.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1023,7 +1025,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-request' =>
|
||||
array(
|
||||
'uri' => '/res/b3257b7d/rsrc/js/javelin/lib/Request.js',
|
||||
'uri' => '/res/6ccc1d5a/rsrc/js/javelin/lib/Request.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1051,7 +1053,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-stratcom' =>
|
||||
array(
|
||||
'uri' => '/res/d7a3d1e9/rsrc/js/javelin/core/Stratcom.js',
|
||||
'uri' => '/res/3afdac66/rsrc/js/javelin/core/Stratcom.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1064,7 +1066,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-tokenizer' =>
|
||||
array(
|
||||
'uri' => '/res/1b1c2148/rsrc/js/javelin/lib/control/tokenizer/Tokenizer.js',
|
||||
'uri' => '/res/2b91543e/rsrc/js/javelin/lib/control/tokenizer/Tokenizer.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1140,7 +1142,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-typeahead-source' =>
|
||||
array(
|
||||
'uri' => '/res/8606f519/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadSource.js',
|
||||
'uri' => '/res/e99c0c1d/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadSource.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1174,7 +1176,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-vector' =>
|
||||
array(
|
||||
'uri' => '/res/50535cb8/rsrc/js/javelin/lib/Vector.js',
|
||||
'uri' => '/res/f240bdb3/rsrc/js/javelin/lib/Vector.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1549,6 +1551,17 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/application/core/ShapedRequest.js',
|
||||
),
|
||||
0 =>
|
||||
array(
|
||||
'uri' => '/res/b6096fdd/rsrc/js/javelin/lib/__tests__/URI.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-uri',
|
||||
1 => 'javelin-php-serializer',
|
||||
),
|
||||
'disk' => '/rsrc/js/javelin/lib/__tests__/URI.js',
|
||||
),
|
||||
'phabricator-slowvote-css' =>
|
||||
array(
|
||||
'uri' => '/res/94d20443/rsrc/css/application/slowvote/slowvote.css',
|
||||
|
@ -1560,7 +1573,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'phabricator-standard-page-view' =>
|
||||
array(
|
||||
'uri' => '/res/ec5acaed/rsrc/css/application/base/standard-page-view.css',
|
||||
'uri' => '/res/7e09bbfc/rsrc/css/application/base/standard-page-view.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1785,6 +1798,22 @@ celerity_register_resource_map(array(
|
|||
'uri' => '/res/pkg/03ef179e/diffusion.pkg.css',
|
||||
'type' => 'css',
|
||||
),
|
||||
'080edee4' =>
|
||||
array(
|
||||
'name' => 'typeahead.pkg.js',
|
||||
'symbols' =>
|
||||
array(
|
||||
0 => 'javelin-typeahead',
|
||||
1 => 'javelin-typeahead-normalizer',
|
||||
2 => 'javelin-typeahead-source',
|
||||
3 => 'javelin-typeahead-preloaded-source',
|
||||
4 => 'javelin-typeahead-ondemand-source',
|
||||
5 => 'javelin-tokenizer',
|
||||
6 => 'javelin-behavior-aphront-basic-tokenizer',
|
||||
),
|
||||
'uri' => '/res/pkg/080edee4/typeahead.pkg.js',
|
||||
'type' => 'js',
|
||||
),
|
||||
'46547a92' =>
|
||||
array(
|
||||
'name' => 'core.pkg.js',
|
||||
|
@ -1824,23 +1853,7 @@ celerity_register_resource_map(array(
|
|||
'uri' => '/res/pkg/4c3b1b11/differential.pkg.css',
|
||||
'type' => 'css',
|
||||
),
|
||||
'540effd7' =>
|
||||
array(
|
||||
'name' => 'typeahead.pkg.js',
|
||||
'symbols' =>
|
||||
array(
|
||||
0 => 'javelin-typeahead',
|
||||
1 => 'javelin-typeahead-normalizer',
|
||||
2 => 'javelin-typeahead-source',
|
||||
3 => 'javelin-typeahead-preloaded-source',
|
||||
4 => 'javelin-typeahead-ondemand-source',
|
||||
5 => 'javelin-tokenizer',
|
||||
6 => 'javelin-behavior-aphront-basic-tokenizer',
|
||||
),
|
||||
'uri' => '/res/pkg/540effd7/typeahead.pkg.js',
|
||||
'type' => 'js',
|
||||
),
|
||||
'b164acea' =>
|
||||
'4fbae2af' =>
|
||||
array(
|
||||
'name' => 'javelin.pkg.js',
|
||||
'symbols' =>
|
||||
|
@ -1856,10 +1869,10 @@ celerity_register_resource_map(array(
|
|||
8 => 'javelin-json',
|
||||
9 => 'javelin-uri',
|
||||
),
|
||||
'uri' => '/res/pkg/b164acea/javelin.pkg.js',
|
||||
'uri' => '/res/pkg/4fbae2af/javelin.pkg.js',
|
||||
'type' => 'js',
|
||||
),
|
||||
'da9f8734' =>
|
||||
'6a6def05' =>
|
||||
array(
|
||||
'name' => 'core.pkg.css',
|
||||
'symbols' =>
|
||||
|
@ -1880,7 +1893,7 @@ celerity_register_resource_map(array(
|
|||
13 => 'phabricator-remarkup-css',
|
||||
14 => 'syntax-highlighting-css',
|
||||
),
|
||||
'uri' => '/res/pkg/da9f8734/core.pkg.css',
|
||||
'uri' => '/res/pkg/6a6def05/core.pkg.css',
|
||||
'type' => 'css',
|
||||
),
|
||||
'ef420ead' =>
|
||||
|
@ -1910,16 +1923,16 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'reverse' =>
|
||||
array(
|
||||
'aphront-crumbs-view-css' => 'da9f8734',
|
||||
'aphront-dialog-view-css' => 'da9f8734',
|
||||
'aphront-form-view-css' => 'da9f8734',
|
||||
'aphront-crumbs-view-css' => '6a6def05',
|
||||
'aphront-dialog-view-css' => '6a6def05',
|
||||
'aphront-form-view-css' => '6a6def05',
|
||||
'aphront-headsup-action-list-view-css' => '4c3b1b11',
|
||||
'aphront-list-filter-view-css' => 'da9f8734',
|
||||
'aphront-panel-view-css' => 'da9f8734',
|
||||
'aphront-side-nav-view-css' => 'da9f8734',
|
||||
'aphront-table-view-css' => 'da9f8734',
|
||||
'aphront-tokenizer-control-css' => 'da9f8734',
|
||||
'aphront-typeahead-control-css' => 'da9f8734',
|
||||
'aphront-list-filter-view-css' => '6a6def05',
|
||||
'aphront-panel-view-css' => '6a6def05',
|
||||
'aphront-side-nav-view-css' => '6a6def05',
|
||||
'aphront-table-view-css' => '6a6def05',
|
||||
'aphront-tokenizer-control-css' => '6a6def05',
|
||||
'aphront-typeahead-control-css' => '6a6def05',
|
||||
'differential-changeset-view-css' => '4c3b1b11',
|
||||
'differential-core-view-css' => '4c3b1b11',
|
||||
'differential-inline-comment-editor' => 'ef420ead',
|
||||
|
@ -1931,8 +1944,8 @@ celerity_register_resource_map(array(
|
|||
'differential-revision-history-css' => '4c3b1b11',
|
||||
'differential-table-of-contents-css' => '4c3b1b11',
|
||||
'diffusion-commit-view-css' => '03ef179e',
|
||||
'javelin-behavior' => 'b164acea',
|
||||
'javelin-behavior-aphront-basic-tokenizer' => '540effd7',
|
||||
'javelin-behavior' => '4fbae2af',
|
||||
'javelin-behavior-aphront-basic-tokenizer' => '080edee4',
|
||||
'javelin-behavior-aphront-drag-and-drop' => 'ef420ead',
|
||||
'javelin-behavior-aphront-drag-and-drop-textarea' => 'ef420ead',
|
||||
'javelin-behavior-aphront-form-disable-on-submit' => '46547a92',
|
||||
|
@ -1950,34 +1963,34 @@ celerity_register_resource_map(array(
|
|||
'javelin-behavior-phabricator-watch-anchor' => '46547a92',
|
||||
'javelin-behavior-refresh-csrf' => '46547a92',
|
||||
'javelin-behavior-workflow' => '46547a92',
|
||||
'javelin-dom' => 'b164acea',
|
||||
'javelin-event' => 'b164acea',
|
||||
'javelin-install' => 'b164acea',
|
||||
'javelin-json' => 'b164acea',
|
||||
'javelin-dom' => '4fbae2af',
|
||||
'javelin-event' => '4fbae2af',
|
||||
'javelin-install' => '4fbae2af',
|
||||
'javelin-json' => '4fbae2af',
|
||||
'javelin-mask' => '46547a92',
|
||||
'javelin-request' => 'b164acea',
|
||||
'javelin-stratcom' => 'b164acea',
|
||||
'javelin-tokenizer' => '540effd7',
|
||||
'javelin-typeahead' => '540effd7',
|
||||
'javelin-typeahead-normalizer' => '540effd7',
|
||||
'javelin-typeahead-ondemand-source' => '540effd7',
|
||||
'javelin-typeahead-preloaded-source' => '540effd7',
|
||||
'javelin-typeahead-source' => '540effd7',
|
||||
'javelin-uri' => 'b164acea',
|
||||
'javelin-util' => 'b164acea',
|
||||
'javelin-vector' => 'b164acea',
|
||||
'javelin-request' => '4fbae2af',
|
||||
'javelin-stratcom' => '4fbae2af',
|
||||
'javelin-tokenizer' => '080edee4',
|
||||
'javelin-typeahead' => '080edee4',
|
||||
'javelin-typeahead-normalizer' => '080edee4',
|
||||
'javelin-typeahead-ondemand-source' => '080edee4',
|
||||
'javelin-typeahead-preloaded-source' => '080edee4',
|
||||
'javelin-typeahead-source' => '080edee4',
|
||||
'javelin-uri' => '4fbae2af',
|
||||
'javelin-util' => '4fbae2af',
|
||||
'javelin-vector' => '4fbae2af',
|
||||
'javelin-workflow' => '46547a92',
|
||||
'phabricator-content-source-view-css' => '4c3b1b11',
|
||||
'phabricator-core-buttons-css' => 'da9f8734',
|
||||
'phabricator-core-css' => 'da9f8734',
|
||||
'phabricator-directory-css' => 'da9f8734',
|
||||
'phabricator-core-buttons-css' => '6a6def05',
|
||||
'phabricator-core-css' => '6a6def05',
|
||||
'phabricator-directory-css' => '6a6def05',
|
||||
'phabricator-drag-and-drop-file-upload' => 'ef420ead',
|
||||
'phabricator-keyboard-shortcut' => '46547a92',
|
||||
'phabricator-keyboard-shortcut-manager' => '46547a92',
|
||||
'phabricator-object-selector-css' => '4c3b1b11',
|
||||
'phabricator-remarkup-css' => 'da9f8734',
|
||||
'phabricator-remarkup-css' => '6a6def05',
|
||||
'phabricator-shaped-request' => 'ef420ead',
|
||||
'phabricator-standard-page-view' => 'da9f8734',
|
||||
'syntax-highlighting-css' => 'da9f8734',
|
||||
'phabricator-standard-page-view' => '6a6def05',
|
||||
'syntax-highlighting-css' => '6a6def05',
|
||||
),
|
||||
));
|
||||
|
|
|
@ -96,6 +96,10 @@ class DifferentialChangesetDetailView extends AphrontView {
|
|||
}
|
||||
|
||||
$display_filename = $changeset->getDisplayFilename();
|
||||
|
||||
$buoyant_begin = $this->renderBuoyant($display_filename);
|
||||
$buoyant_end = $this->renderBuoyant(null);
|
||||
|
||||
$output = javelin_render_tag(
|
||||
'div',
|
||||
array(
|
||||
|
@ -109,6 +113,7 @@ class DifferentialChangesetDetailView extends AphrontView {
|
|||
'class' => $class,
|
||||
'id' => $id,
|
||||
),
|
||||
$buoyant_begin.
|
||||
phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
|
@ -118,9 +123,29 @@ class DifferentialChangesetDetailView extends AphrontView {
|
|||
$buttons.
|
||||
'<h1>'.phutil_escape_html($display_filename).'</h1>'.
|
||||
'<div style="clear: both;"></div>'.
|
||||
$this->renderChildren());
|
||||
$this->renderChildren().
|
||||
$buoyant_end);
|
||||
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function renderBuoyant($text) {
|
||||
return javelin_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'sigil' => 'buoyant',
|
||||
'meta' => array(
|
||||
'text' => $text,
|
||||
),
|
||||
'style' => ($text === null)
|
||||
// Current CSS spacing rules cause the "end" anchor to appear too
|
||||
// late in the display document. Shift it up a bit so we drop the
|
||||
// buoyant header sooner. This reduces confusion when using keystroke
|
||||
// navigation.
|
||||
? 'bottom: 60px; position: absolute;'
|
||||
: null,
|
||||
),
|
||||
'');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,8 @@ class DifferentialChangesetListView extends AphrontView {
|
|||
array());
|
||||
}
|
||||
|
||||
Javelin::initBehavior('buoyant', array());
|
||||
|
||||
$output = array();
|
||||
$mapping = array();
|
||||
$repository = $this->repository;
|
||||
|
|
|
@ -190,3 +190,21 @@ a.handle-disabled {
|
|||
font-size: 11px;
|
||||
font-family: "Verdana";
|
||||
}
|
||||
|
||||
.buoyant {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 8;
|
||||
|
||||
padding: 6px;
|
||||
color: #dddddd;
|
||||
font-size: 11px;
|
||||
opacity: 0.90;
|
||||
width: 100%;
|
||||
|
||||
background: #222222;
|
||||
border-bottom: 1px solid #dfdfdf;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
|
91
webroot/rsrc/js/application/core/behavior-buoyant.js
Normal file
91
webroot/rsrc/js/application/core/behavior-buoyant.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* @provides javelin-behavior-buoyant
|
||||
* @requires javelin-behavior
|
||||
* javelin-stratcom
|
||||
* javelin-vector
|
||||
* javelin-dom
|
||||
*/
|
||||
|
||||
JX.behavior('buoyant', function() {
|
||||
|
||||
// The display element which shows the "buoyant" header to the user.
|
||||
var element = JX.$N('div', {className : 'buoyant'});
|
||||
|
||||
// Keeps track of whether we're currently showing anything or not.
|
||||
var visible = false;
|
||||
|
||||
// If we're showing something, the positional DOM element that triggered the
|
||||
// currently shown header.
|
||||
var active_marker = null;
|
||||
|
||||
// When the header is clicked, jump to the element that triggered it.
|
||||
JX.DOM.listen(element, 'click', null, function(e) {
|
||||
window.scrollTo(0, JX.$V(active_marker).y - 40);
|
||||
});
|
||||
|
||||
function hide() {
|
||||
if (visible) {
|
||||
JX.DOM.remove(element);
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
function show(text) {
|
||||
if (!visible) {
|
||||
document.body.appendChild(element);
|
||||
visible = true;
|
||||
}
|
||||
JX.DOM.setContent(element, text);
|
||||
}
|
||||
|
||||
var onviewportchange = function(e) {
|
||||
|
||||
// If we're currently showing a header but we've scrolled back up past its
|
||||
// marker, hide it.
|
||||
|
||||
var scroll_position = JX.Vector.getScroll().y;
|
||||
if (visible && (scroll_position < JX.$V(active_marker).y)) {
|
||||
hide();
|
||||
}
|
||||
|
||||
// Find all the markers in the document.
|
||||
|
||||
var markers = JX.DOM.scry(document.body, 'div', 'buoyant');
|
||||
|
||||
// Sort the markers by Y position, descending.
|
||||
|
||||
var markinfo = [];
|
||||
for (var ii = 0; ii < markers.length; ii++) {
|
||||
markinfo.push({
|
||||
marker: markers[ii],
|
||||
position: JX.$V(markers[ii]).y
|
||||
});
|
||||
}
|
||||
markinfo.sort(function(u, v) { return (v.position - u.position); });
|
||||
|
||||
// Find the first marker above the current scroll position.
|
||||
|
||||
for (var ii = 0; ii < markinfo.length; ii++) {
|
||||
if (markinfo[ii].position > scroll_position) {
|
||||
// This marker is below the current scroll position, so ignore it.
|
||||
continue;
|
||||
}
|
||||
|
||||
// We've found a marker. Display it as appropriate;
|
||||
|
||||
active_marker = markinfo[ii].marker;
|
||||
var text = JX.Stratcom.getData(active_marker).text;
|
||||
if (text) {
|
||||
show(text);
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
JX.Stratcom.listen('scroll', null, onviewportchange);
|
||||
JX.Stratcom.listen('resize', null, onviewportchange);
|
||||
});
|
Loading…
Reference in a new issue