1
0
Fork 0
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:
epriestley 2012-02-23 12:26:14 -08:00
parent cdd55eda14
commit bf3dd8663c
6 changed files with 235 additions and 86 deletions

2
externals/javelin vendored

@ -1 +1 @@
Subproject commit f25470da2c98cd37fbac435a56128b1d465f7667
Subproject commit 45fa775fea5cc7fa1d834a04b9d25a08bcfa4812

View file

@ -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',
),
));

View file

@ -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,
),
'');
}
}

View file

@ -106,6 +106,8 @@ class DifferentialChangesetListView extends AphrontView {
array());
}
Javelin::initBehavior('buoyant', array());
$output = array();
$mapping = array();
$repository = $this->repository;

View file

@ -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;
}

View 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);
});