Page MenuHomePhabricator

D21553.diff
No OneTemporary

D21553.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -10,7 +10,7 @@
'conpherence.pkg.css' => '0e3cf785',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => '970b3ceb',
- 'core.pkg.js' => 'adc34883',
+ 'core.pkg.js' => '2fe70e3d',
'dark-console.pkg.js' => '187792c2',
'differential.pkg.css' => '5c459f92',
'differential.pkg.js' => '5080baf4',
@@ -460,7 +460,8 @@
'rsrc/js/core/DraggableList.js' => '0169e425',
'rsrc/js/core/Favicon.js' => '7930776a',
'rsrc/js/core/FileUpload.js' => 'ab85e184',
- 'rsrc/js/core/Hovercard.js' => '074f0783',
+ 'rsrc/js/core/Hovercard.js' => 'd9d29a5f',
+ 'rsrc/js/core/HovercardList.js' => '10a5f4bf',
'rsrc/js/core/KeyboardShortcut.js' => '1a844c06',
'rsrc/js/core/KeyboardShortcutManager.js' => '81debc48',
'rsrc/js/core/MultirowRowManager.js' => '5b54c823',
@@ -485,7 +486,7 @@
'rsrc/js/core/behavior-global-drag-and-drop.js' => '1cab0e9a',
'rsrc/js/core/behavior-high-security-warning.js' => 'dae2d55b',
'rsrc/js/core/behavior-history-install.js' => '6a1583a8',
- 'rsrc/js/core/behavior-hovercard.js' => '6c379000',
+ 'rsrc/js/core/behavior-hovercard.js' => '3f446c72',
'rsrc/js/core/behavior-keyboard-pager.js' => '1325b731',
'rsrc/js/core/behavior-keyboard-shortcuts.js' => '42c44e8b',
'rsrc/js/core/behavior-lightbox-attachments.js' => 'c7e748bf',
@@ -670,7 +671,7 @@
'javelin-behavior-pholio-mock-view' => '5aa1544e',
'javelin-behavior-phui-dropdown-menu' => '5cf0501a',
'javelin-behavior-phui-file-upload' => 'e150bd50',
- 'javelin-behavior-phui-hovercards' => '6c379000',
+ 'javelin-behavior-phui-hovercards' => '3f446c72',
'javelin-behavior-phui-selectable-list' => 'b26a41e4',
'javelin-behavior-phui-submenu' => 'b5e9bff9',
'javelin-behavior-phui-tab-group' => '242aa08b',
@@ -858,7 +859,8 @@
'phui-formation-view-css' => 'd2dec8ed',
'phui-head-thing-view-css' => 'd7f293df',
'phui-header-view-css' => '36c86a58',
- 'phui-hovercard' => '074f0783',
+ 'phui-hovercard' => 'd9d29a5f',
+ 'phui-hovercard-list' => '10a5f4bf',
'phui-hovercard-view-css' => '6ca90fa0',
'phui-icon-set-selector-css' => '7aa5f3ec',
'phui-icon-view-css' => '4cbc684a',
@@ -986,13 +988,6 @@
'javelin-uri',
'phabricator-notification',
),
- '074f0783' => array(
- 'javelin-install',
- 'javelin-dom',
- 'javelin-vector',
- 'javelin-request',
- 'javelin-uri',
- ),
'0889b835' => array(
'javelin-install',
'javelin-event',
@@ -1030,6 +1025,14 @@
'javelin-workflow',
'phuix-icon-view',
),
+ '10a5f4bf' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ 'javelin-vector',
+ 'javelin-request',
+ 'javelin-uri',
+ 'phui-hovercard',
+ ),
'111bfd2d' => array(
'javelin-install',
),
@@ -1266,6 +1269,14 @@
'phabricator-drag-and-drop-file-upload',
'phabricator-draggable-list',
),
+ '3f446c72' => array(
+ 'javelin-behavior',
+ 'javelin-behavior-device',
+ 'javelin-stratcom',
+ 'javelin-vector',
+ 'phui-hovercard',
+ 'phui-hovercard-list',
+ ),
'407ee861' => array(
'javelin-behavior',
'javelin-uri',
@@ -1557,13 +1568,6 @@
'javelin-workflow',
'javelin-magical-init',
),
- '6c379000' => array(
- 'javelin-behavior',
- 'javelin-behavior-device',
- 'javelin-stratcom',
- 'javelin-vector',
- 'phui-hovercard',
- ),
'6cfa0008' => array(
'javelin-dom',
'javelin-dynval',
@@ -2132,6 +2136,13 @@
'javelin-util',
'phabricator-shaped-request',
),
+ 'd9d29a5f' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ 'javelin-vector',
+ 'javelin-request',
+ 'javelin-uri',
+ ),
'da15d3dc' => array(
'phui-oi-list-view-css',
),
@@ -2367,6 +2378,7 @@
'javelin-behavior-global-drag-and-drop',
'javelin-behavior-phabricator-reveal-content',
'phui-hovercard',
+ 'phui-hovercard-list',
'javelin-behavior-phui-hovercards',
'javelin-color',
'javelin-fx',
diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php
--- a/resources/celerity/packages.php
+++ b/resources/celerity/packages.php
@@ -60,6 +60,7 @@
'javelin-behavior-global-drag-and-drop',
'javelin-behavior-phabricator-reveal-content',
'phui-hovercard',
+ 'phui-hovercard-list',
'javelin-behavior-phui-hovercards',
'javelin-color',
'javelin-fx',
diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php
--- a/src/aphront/AphrontRequest.php
+++ b/src/aphront/AphrontRequest.php
@@ -224,6 +224,43 @@
}
+ /**
+ * @task data
+ */
+ public function getJSONMap($name, $default = array()) {
+ if (!isset($this->requestData[$name])) {
+ return $default;
+ }
+
+ $raw_data = phutil_string_cast($this->requestData[$name]);
+ $raw_data = trim($raw_data);
+ if (!strlen($raw_data)) {
+ return $default;
+ }
+
+ if ($raw_data[0] !== '{') {
+ throw new Exception(
+ pht(
+ 'Request parameter "%s" is not formatted properly. Expected a '.
+ 'JSON object, but value does not start with "{".',
+ $name));
+ }
+
+ try {
+ $json_object = phutil_json_decode($raw_data);
+ } catch (PhutilJSONParserException $ex) {
+ throw new Exception(
+ pht(
+ 'Request parameter "%s" is not formatted properly. Expected a '.
+ 'JSON object, but encountered a syntax error: %s.',
+ $name,
+ $ex->getMessage()));
+ }
+
+ return $json_object;
+ }
+
+
/**
* @task data
*/
diff --git a/src/applications/search/controller/PhabricatorSearchHovercardController.php b/src/applications/search/controller/PhabricatorSearchHovercardController.php
--- a/src/applications/search/controller/PhabricatorSearchHovercardController.php
+++ b/src/applications/search/controller/PhabricatorSearchHovercardController.php
@@ -9,7 +9,8 @@
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
- $phids = $request->getArr('phids');
+
+ $cards = $request->getJSONMap('cards');
// If object names are provided, look them up and pretend they were
// passed as additional PHIDs. This is primarily useful for debugging,
@@ -23,18 +24,29 @@
->execute();
foreach ($named_objects as $object) {
- $phids[] = $object->getPHID();
+ $cards[] = array(
+ 'objectPHID' => $object->getPHID(),
+ );
}
}
+ $object_phids = array();
+ $handle_phids = array();
+ foreach ($cards as $card) {
+ $object_phid = idx($card, 'objectPHID');
+
+ $handle_phids[] = $object_phid;
+ $object_phids[] = $object_phid;
+ }
+
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
- ->withPHIDs($phids)
+ ->withPHIDs($handle_phids)
->execute();
$objects = id(new PhabricatorObjectQuery())
->setViewer($viewer)
- ->withPHIDs($phids)
+ ->withPHIDs($object_phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
@@ -67,10 +79,12 @@
array_select_keys($objects, $extension_phids));
}
- $cards = array();
- foreach ($phids as $phid) {
- $handle = $handles[$phid];
- $object = idx($objects, $phid);
+ $results = array();
+ foreach ($cards as $card_key => $card) {
+ $object_phid = $card['objectPHID'];
+
+ $handle = $handles[$object_phid];
+ $object = idx($objects, $object_phid);
$hovercard = id(new PHUIHovercardView())
->setUser($viewer)
@@ -90,18 +104,18 @@
}
}
- $cards[$phid] = $hovercard;
+ $results[$card_key] = $hovercard;
}
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())->setContent(
array(
- 'cards' => $cards,
+ 'cards' => $results,
));
}
- foreach ($cards as $key => $hovercard) {
- $cards[$key] = phutil_tag('div',
+ foreach ($results as $key => $hovercard) {
+ $results[$key] = phutil_tag('div',
array(
'class' => 'ml',
),
@@ -109,7 +123,7 @@
}
return $this->newPage()
- ->appendChild($cards)
+ ->appendChild($results)
->setShowFooter(false);
}
diff --git a/src/applications/system/events/PhabricatorSystemDebugUIEventListener.php b/src/applications/system/events/PhabricatorSystemDebugUIEventListener.php
--- a/src/applications/system/events/PhabricatorSystemDebugUIEventListener.php
+++ b/src/applications/system/events/PhabricatorSystemDebugUIEventListener.php
@@ -42,7 +42,7 @@
$submenu[] = id(new PhabricatorActionView())
->setIcon('fa-address-card-o')
->setName(pht('View Hovercard'))
- ->setHref(urisprintf('/search/hovercard/?phids[]=%s', $phid));
+ ->setHref(urisprintf('/search/hovercard/?names=%s', $phid));
$developer_action = id(new PhabricatorActionView())
->setName(pht('Advanced/Developer...'))
diff --git a/webroot/rsrc/js/core/Hovercard.js b/webroot/rsrc/js/core/Hovercard.js
--- a/webroot/rsrc/js/core/Hovercard.js
+++ b/webroot/rsrc/js/core/Hovercard.js
@@ -10,163 +10,18 @@
JX.install('Hovercard', {
- statics : {
- _node : null,
- _activeRoot : null,
- _visiblePHID : null,
- _alignment: null,
-
- fetchUrl : '/search/hovercard/',
-
- /**
- * Hovercard storage. {"PHID-XXXX-YYYY":"<...>", ...}
- */
- _cards : {},
-
- getAnchor : function() {
- return this._activeRoot;
- },
-
- getCard : function() {
- var self = JX.Hovercard;
- return self._node;
- },
-
- getAlignment: function() {
- var self = JX.Hovercard;
- return self._alignment;
- },
-
- show : function(root, phid) {
- var self = JX.Hovercard;
-
- if (root === this._activeRoot) {
- return;
- }
-
- self.hide();
-
- self._visiblePHID = phid;
- self._activeRoot = root;
-
- if (!(phid in self._cards)) {
- self._load([phid]);
- } else {
- self._drawCard(phid);
- }
- },
-
- _drawCard : function(phid) {
- var self = JX.Hovercard;
- // card is loading...
- if (self._cards[phid] === true) {
- return;
- }
- // Not the current requested card
- if (phid != self._visiblePHID) {
- return;
- }
- // Not loaded
- if (!(phid in self._cards)) {
- return;
- }
-
- var root = self._activeRoot;
- var node = JX.$N('div',
- { className: 'jx-hovercard-container' },
- JX.$H(self._cards[phid]));
-
- self._node = node;
-
- // Append the card to the document, but offscreen, so we can measure it.
- node.style.left = '-10000px';
- document.body.appendChild(node);
-
- // Retrieve size from child (wrapper), since node gives wrong dimensions?
- var child = node.firstChild;
- var p = JX.$V(root);
- var d = JX.Vector.getDim(root);
- var n = JX.Vector.getDim(child);
- var v = JX.Vector.getViewport();
- var s = JX.Vector.getScroll();
-
- // Move the tip so it's nicely aligned.
- var margin = 20;
-
-
- // Try to align the card directly above the link, with left borders
- // touching.
- var x = p.x;
-
- // If this would push us off the right side of the viewport, push things
- // back to the left.
- if ((x + n.x + margin) > (s.x + v.x)) {
- x = (s.x + v.x) - n.x - margin;
- }
-
- // Try to put the card above the link.
- var y = p.y - n.y - margin;
- self._alignment = 'north';
-
- // If the card is near the top of the window, show it beneath the
- // link we're hovering over instead.
- if ((y - margin) < s.y) {
- y = p.y + d.y + margin;
- self._alignment = 'south';
- }
-
- node.style.left = x + 'px';
- node.style.top = y + 'px';
- },
-
- hide : function() {
- var self = JX.Hovercard;
- self._visiblePHID = null;
- self._activeRoot = null;
- if (self._node) {
- JX.DOM.remove(self._node);
- self._node = null;
- }
- },
-
- /**
- * Pass it an array of phids to load them into storage
- *
- * @param list phids
- */
- _load : function(phids) {
- var self = JX.Hovercard;
- var uri = JX.$U(self.fetchUrl);
-
- var send = false;
- for (var ii = 0; ii < phids.length; ii++) {
- var phid = phids[ii];
- if (phid in self._cards) {
- continue;
- }
- self._cards[phid] = true; // means "loading"
- uri.setQueryParam('phids['+ii+']', phids[ii]);
- send = true;
- }
-
- if (!send) {
- // already loaded / loading everything!
- return;
- }
-
- new JX.Request(uri, function(r) {
- for (var phid in r.cards) {
- self._cards[phid] = r.cards[phid];
-
- // Don't draw if the user is faster than the browser
- // Only draw if the user is still requesting the original card
- if (self.getCard() && phid != self._visiblePHID) {
- continue;
- }
-
- self._drawCard(phid);
- }
- }).send();
+ properties: {
+ hovercardKey: null,
+ objectPHID: null,
+ isLoading: false,
+ isLoaded: false,
+ content: null
+ },
+
+ members: {
+ newContentNode: function() {
+ return JX.$H(this.getContent());
}
}
+
});
diff --git a/webroot/rsrc/js/core/HovercardList.js b/webroot/rsrc/js/core/HovercardList.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/core/HovercardList.js
@@ -0,0 +1,226 @@
+/**
+ * @requires javelin-install
+ * javelin-dom
+ * javelin-vector
+ * javelin-request
+ * javelin-uri
+ * phui-hovercard
+ * @provides phui-hovercard-list
+ * @javelin
+ */
+
+JX.install('HovercardList', {
+
+ construct: function() {
+ this._cards = {};
+ this._drawRequest = {};
+ },
+
+ members: {
+ _cardNode: null,
+ _rootNode: null,
+ _cards: null,
+ _drawRequest: null,
+ _visibleCard: null,
+
+ _fetchURI : '/search/hovercard/',
+
+ getCard: function(spec) {
+ var hovercard_key = this._newHovercardKey(spec);
+
+ if (!(hovercard_key in this._cards)) {
+ var card = new JX.Hovercard()
+ .setHovercardKey(hovercard_key)
+ .setObjectPHID(spec.hoverPHID);
+
+ this._cards[hovercard_key] = card;
+ }
+
+ return this._cards[hovercard_key];
+ },
+
+ drawCard: function(card, node) {
+ this._drawRequest = {
+ card: card,
+ node: node
+ };
+
+ if (card.getIsLoaded()) {
+ return this._paintCard(card);
+ }
+
+ if (card.getIsLoading()) {
+ return;
+ }
+
+ var hovercard_key = card.getHovercardKey();
+
+ var request = {};
+ request[hovercard_key] = this._newCardRequest(card);
+ request = JX.JSON.stringify(request);
+
+ var uri = JX.$U(this._fetchURI)
+ .setQueryParam('cards', request);
+
+ var onresponse = JX.bind(this, function(r) {
+ var card = this._cards[hovercard_key];
+
+ this._fillCard(card, r.cards[hovercard_key]);
+ this._paintCard(card);
+ });
+
+ card.setIsLoading(true);
+
+ new JX.Request(uri, onresponse)
+ .send();
+ },
+
+ _newHovercardKey: function(spec) {
+ return 'phid=' + spec.hoverPHID;
+ },
+
+ _newCardRequest: function(card) {
+ return {
+ objectPHID: card.getObjectPHID()
+ };
+ },
+
+ _getCardNode: function() {
+ if (!this._cardNode) {
+ var attributes = {
+ className: 'jx-hovercard-container'
+ };
+
+ this._cardNode = JX.$N('div', attributes);
+ }
+
+ return this._cardNode;
+ },
+
+ _fillCard: function(card, response) {
+ card.setContent(response);
+ card.setIsLoaded(true);
+ },
+
+ _paintCard: function(card) {
+ var request = this._drawRequest;
+
+ if (request.card !== card) {
+ // This paint request is no longer the most recent paint request.
+ return;
+ }
+
+ this.hideCard();
+
+ this._rootNode = request.node;
+ var root = this._rootNode;
+ var node = this._getCardNode();
+
+ JX.DOM.setContent(node, card.newContentNode());
+
+ // Append the card to the document, but offscreen, so we can measure it.
+ node.style.left = '-10000px';
+ document.body.appendChild(node);
+
+ // Retrieve size from child (wrapper), since node gives wrong dimensions?
+ var child = node.firstChild;
+
+ var p = JX.$V(root);
+ var d = JX.Vector.getDim(root);
+ var n = JX.Vector.getDim(child);
+ var v = JX.Vector.getViewport();
+ var s = JX.Vector.getScroll();
+
+ // Move the tip so it's nicely aligned.
+ var margin = 20;
+
+ // Try to align the card directly above the link, with left borders
+ // touching.
+ var x = p.x;
+
+ // If this would push us off the right side of the viewport, push things
+ // back to the left.
+ if ((x + n.x + margin) > (s.x + v.x)) {
+ x = (s.x + v.x) - n.x - margin;
+ }
+
+ // Try to put the card above the link.
+ var y = p.y - n.y - margin;
+
+ var alignment = 'north';
+
+ // If the card is near the top of the window, show it beneath the
+ // link we're hovering over instead.
+ if ((y - margin) < s.y) {
+ y = p.y + d.y + margin;
+ alignment = 'south';
+ }
+
+ this._alignment = alignment;
+ node.style.left = x + 'px';
+ node.style.top = y + 'px';
+
+ this._visibleCard = card;
+ },
+
+ hideCard: function() {
+ var node = this._getCardNode();
+ JX.DOM.remove(node);
+
+ this._rootNode = null;
+ this._alignment = null;
+ this._visibleCard = null;
+ },
+
+ onMouseMove: function(e) {
+ if (!this._visibleCard) {
+ return;
+ }
+
+ var root = this._rootNode;
+ var node = this._getCardNode();
+ var alignment = this._alignment;
+
+ var mouse = JX.$V(e);
+ var node_pos = JX.$V(node);
+ var node_dim = JX.Vector.getDim(node);
+ var root_pos = JX.$V(root);
+ var root_dim = JX.Vector.getDim(root);
+
+ var margin = 20;
+
+ if (alignment === 'south') {
+ // Cursor is below the node.
+ if (mouse.y > node_pos.y + node_dim.y + margin) {
+ this.hideCard();
+ }
+
+ // Cursor is above the root.
+ if (mouse.y < root_pos.y - margin) {
+ this.hideCard();
+ }
+ } else {
+ // Cursor is above the node.
+ if (mouse.y < node_pos.y - margin) {
+ this.hideCard();
+ }
+
+ // Cursor is below the root.
+ if (mouse.y > root_pos.y + root_dim.y + margin) {
+ this.hideCard();
+ }
+ }
+
+ // Cursor is too far to the left.
+ if (mouse.x < Math.min(root_pos.x, node_pos.x) - margin) {
+ this.hideCard();
+ }
+
+ // Cursor is too far to the right.
+ if (mouse.x >
+ Math.max(root_pos.x + root_dim.x, node_pos.x + node_dim.x) + margin) {
+ this.hideCard();
+ }
+ }
+ }
+});
diff --git a/webroot/rsrc/js/core/behavior-hovercard.js b/webroot/rsrc/js/core/behavior-hovercard.js
--- a/webroot/rsrc/js/core/behavior-hovercard.js
+++ b/webroot/rsrc/js/core/behavior-hovercard.js
@@ -5,10 +5,18 @@
* javelin-stratcom
* javelin-vector
* phui-hovercard
+ * phui-hovercard-list
* @javelin
*/
-JX.behavior('phui-hovercards', function() {
+JX.behavior('phui-hovercards', function(config, statics) {
+ if (statics.hovercardList) {
+ return;
+ }
+
+ var cards = new JX.HovercardList();
+ statics.hovercardList = cards;
+
// We listen for mousemove instead of mouseover to handle the case when user
// scrolls with keyboard. We don't want to display hovercard if node gets
@@ -23,65 +31,19 @@
return;
}
+ var node = e.getNode('hovercard');
var data = e.getNodeData('hovercard');
- JX.Hovercard.show(
- e.getNode('hovercard'),
- data.hoverPHID);
+ var card = cards.getCard(data);
+
+ cards.drawCard(card, node);
});
JX.Stratcom.listen(
'mousemove',
null,
function (e) {
- if (!JX.Hovercard.getCard()) {
- return;
- }
-
- var root = JX.Hovercard.getAnchor();
- var node = JX.Hovercard.getCard();
- var align = JX.Hovercard.getAlignment();
-
- var mouse = JX.$V(e);
- var node_pos = JX.$V(node);
- var node_dim = JX.Vector.getDim(node);
- var root_pos = JX.$V(root);
- var root_dim = JX.Vector.getDim(root);
-
- var margin = 20;
-
- if (align == 'south') {
- // Cursor is below the node.
- if (mouse.y > node_pos.y + node_dim.y + margin) {
- JX.Hovercard.hide();
- }
-
- // Cursor is above the root.
- if (mouse.y < root_pos.y - margin) {
- JX.Hovercard.hide();
- }
- } else {
- // Cursor is above the node.
- if (mouse.y < node_pos.y - margin) {
- JX.Hovercard.hide();
- }
-
- // Cursor is below the root.
- if (mouse.y > root_pos.y + root_dim.y + margin) {
- JX.Hovercard.hide();
- }
- }
-
- // Cursor is too far to the left.
- if (mouse.x < Math.min(root_pos.x, node_pos.x) - margin) {
- JX.Hovercard.hide();
- }
-
- // Cursor is too far to the right.
- if (mouse.x >
- Math.max(root_pos.x + root_dim.x, node_pos.x + node_dim.x) + margin) {
- JX.Hovercard.hide();
- }
+ cards.onMouseMove(e);
});
// When we leave the page, hide any visible hovercards. If we don't do this,
@@ -91,7 +53,7 @@
['unload', 'onresize'],
null,
function() {
- JX.Hovercard.hide();
+ cards.hideCard();
});
});

File Metadata

Mime Type
text/plain
Expires
Tue, Feb 25, 5:42 AM (16 h, 28 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7194560
Default Alt Text
D21553.diff (21 KB)

Event Timeline