Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15207535
D21553.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
D21553.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D21553: Restructure Hovercards to support more context information
Attached
Detach File
Event Timeline
Log In to Comment