@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Restructure Hovercards to support more context information

Summary:
Ref T13602. Currently, Hovercards are functions only of the object they represent (and the viewer, etc).

Recent changes to how users who can't see an object are rendered motivate making them a function of both the object they represent //and// the context in which they are being viewed. In particular, this enables a hovecard for a user to explain "This user can't see the thing you're lookign at right now.", so visual "exiled" markers can have a path forward toward discovery.

Test Plan:
- This change isn't expected to affect any behavior.
- Viewed hovercards, moused over/out, resized windows, viewed standalone cards, viewed debug cards, saw no behavioral changes.

Maniphest Tasks: T13602

Differential Revision: https://secure.phabricator.com/D21553

+349 -242
+31 -19
resources/celerity/map.php
··· 10 10 'conpherence.pkg.css' => '0e3cf785', 11 11 'conpherence.pkg.js' => '020aebcf', 12 12 'core.pkg.css' => '970b3ceb', 13 - 'core.pkg.js' => 'adc34883', 13 + 'core.pkg.js' => '2fe70e3d', 14 14 'dark-console.pkg.js' => '187792c2', 15 15 'differential.pkg.css' => '5c459f92', 16 16 'differential.pkg.js' => '5080baf4', ··· 460 460 'rsrc/js/core/DraggableList.js' => '0169e425', 461 461 'rsrc/js/core/Favicon.js' => '7930776a', 462 462 'rsrc/js/core/FileUpload.js' => 'ab85e184', 463 - 'rsrc/js/core/Hovercard.js' => '074f0783', 463 + 'rsrc/js/core/Hovercard.js' => 'd9d29a5f', 464 + 'rsrc/js/core/HovercardList.js' => '10a5f4bf', 464 465 'rsrc/js/core/KeyboardShortcut.js' => '1a844c06', 465 466 'rsrc/js/core/KeyboardShortcutManager.js' => '81debc48', 466 467 'rsrc/js/core/MultirowRowManager.js' => '5b54c823', ··· 485 486 'rsrc/js/core/behavior-global-drag-and-drop.js' => '1cab0e9a', 486 487 'rsrc/js/core/behavior-high-security-warning.js' => 'dae2d55b', 487 488 'rsrc/js/core/behavior-history-install.js' => '6a1583a8', 488 - 'rsrc/js/core/behavior-hovercard.js' => '6c379000', 489 + 'rsrc/js/core/behavior-hovercard.js' => '3f446c72', 489 490 'rsrc/js/core/behavior-keyboard-pager.js' => '1325b731', 490 491 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '42c44e8b', 491 492 'rsrc/js/core/behavior-lightbox-attachments.js' => 'c7e748bf', ··· 670 671 'javelin-behavior-pholio-mock-view' => '5aa1544e', 671 672 'javelin-behavior-phui-dropdown-menu' => '5cf0501a', 672 673 'javelin-behavior-phui-file-upload' => 'e150bd50', 673 - 'javelin-behavior-phui-hovercards' => '6c379000', 674 + 'javelin-behavior-phui-hovercards' => '3f446c72', 674 675 'javelin-behavior-phui-selectable-list' => 'b26a41e4', 675 676 'javelin-behavior-phui-submenu' => 'b5e9bff9', 676 677 'javelin-behavior-phui-tab-group' => '242aa08b', ··· 858 859 'phui-formation-view-css' => 'd2dec8ed', 859 860 'phui-head-thing-view-css' => 'd7f293df', 860 861 'phui-header-view-css' => '36c86a58', 861 - 'phui-hovercard' => '074f0783', 862 + 'phui-hovercard' => 'd9d29a5f', 863 + 'phui-hovercard-list' => '10a5f4bf', 862 864 'phui-hovercard-view-css' => '6ca90fa0', 863 865 'phui-icon-set-selector-css' => '7aa5f3ec', 864 866 'phui-icon-view-css' => '4cbc684a', ··· 986 988 'javelin-uri', 987 989 'phabricator-notification', 988 990 ), 989 - '074f0783' => array( 990 - 'javelin-install', 991 - 'javelin-dom', 992 - 'javelin-vector', 993 - 'javelin-request', 994 - 'javelin-uri', 995 - ), 996 991 '0889b835' => array( 997 992 'javelin-install', 998 993 'javelin-event', ··· 1030 1025 'javelin-workflow', 1031 1026 'phuix-icon-view', 1032 1027 ), 1028 + '10a5f4bf' => array( 1029 + 'javelin-install', 1030 + 'javelin-dom', 1031 + 'javelin-vector', 1032 + 'javelin-request', 1033 + 'javelin-uri', 1034 + 'phui-hovercard', 1035 + ), 1033 1036 '111bfd2d' => array( 1034 1037 'javelin-install', 1035 1038 ), ··· 1265 1268 'phabricator-phtize', 1266 1269 'phabricator-drag-and-drop-file-upload', 1267 1270 'phabricator-draggable-list', 1271 + ), 1272 + '3f446c72' => array( 1273 + 'javelin-behavior', 1274 + 'javelin-behavior-device', 1275 + 'javelin-stratcom', 1276 + 'javelin-vector', 1277 + 'phui-hovercard', 1278 + 'phui-hovercard-list', 1268 1279 ), 1269 1280 '407ee861' => array( 1270 1281 'javelin-behavior', ··· 1557 1568 'javelin-workflow', 1558 1569 'javelin-magical-init', 1559 1570 ), 1560 - '6c379000' => array( 1561 - 'javelin-behavior', 1562 - 'javelin-behavior-device', 1563 - 'javelin-stratcom', 1564 - 'javelin-vector', 1565 - 'phui-hovercard', 1566 - ), 1567 1571 '6cfa0008' => array( 1568 1572 'javelin-dom', 1569 1573 'javelin-dynval', ··· 2132 2136 'javelin-util', 2133 2137 'phabricator-shaped-request', 2134 2138 ), 2139 + 'd9d29a5f' => array( 2140 + 'javelin-install', 2141 + 'javelin-dom', 2142 + 'javelin-vector', 2143 + 'javelin-request', 2144 + 'javelin-uri', 2145 + ), 2135 2146 'da15d3dc' => array( 2136 2147 'phui-oi-list-view-css', 2137 2148 ), ··· 2367 2378 'javelin-behavior-global-drag-and-drop', 2368 2379 'javelin-behavior-phabricator-reveal-content', 2369 2380 'phui-hovercard', 2381 + 'phui-hovercard-list', 2370 2382 'javelin-behavior-phui-hovercards', 2371 2383 'javelin-color', 2372 2384 'javelin-fx',
+1
resources/celerity/packages.php
··· 60 60 'javelin-behavior-global-drag-and-drop', 61 61 'javelin-behavior-phabricator-reveal-content', 62 62 'phui-hovercard', 63 + 'phui-hovercard-list', 63 64 'javelin-behavior-phui-hovercards', 64 65 'javelin-color', 65 66 'javelin-fx',
+37
src/aphront/AphrontRequest.php
··· 227 227 /** 228 228 * @task data 229 229 */ 230 + public function getJSONMap($name, $default = array()) { 231 + if (!isset($this->requestData[$name])) { 232 + return $default; 233 + } 234 + 235 + $raw_data = phutil_string_cast($this->requestData[$name]); 236 + $raw_data = trim($raw_data); 237 + if (!strlen($raw_data)) { 238 + return $default; 239 + } 240 + 241 + if ($raw_data[0] !== '{') { 242 + throw new Exception( 243 + pht( 244 + 'Request parameter "%s" is not formatted properly. Expected a '. 245 + 'JSON object, but value does not start with "{".', 246 + $name)); 247 + } 248 + 249 + try { 250 + $json_object = phutil_json_decode($raw_data); 251 + } catch (PhutilJSONParserException $ex) { 252 + throw new Exception( 253 + pht( 254 + 'Request parameter "%s" is not formatted properly. Expected a '. 255 + 'JSON object, but encountered a syntax error: %s.', 256 + $name, 257 + $ex->getMessage())); 258 + } 259 + 260 + return $json_object; 261 + } 262 + 263 + 264 + /** 265 + * @task data 266 + */ 230 267 public function getArr($name, $default = array()) { 231 268 if (isset($this->requestData[$name]) && 232 269 is_array($this->requestData[$name])) {
+27 -13
src/applications/search/controller/PhabricatorSearchHovercardController.php
··· 9 9 10 10 public function handleRequest(AphrontRequest $request) { 11 11 $viewer = $this->getViewer(); 12 - $phids = $request->getArr('phids'); 12 + 13 + $cards = $request->getJSONMap('cards'); 13 14 14 15 // If object names are provided, look them up and pretend they were 15 16 // passed as additional PHIDs. This is primarily useful for debugging, ··· 23 24 ->execute(); 24 25 25 26 foreach ($named_objects as $object) { 26 - $phids[] = $object->getPHID(); 27 + $cards[] = array( 28 + 'objectPHID' => $object->getPHID(), 29 + ); 27 30 } 28 31 } 29 32 33 + $object_phids = array(); 34 + $handle_phids = array(); 35 + foreach ($cards as $card) { 36 + $object_phid = idx($card, 'objectPHID'); 37 + 38 + $handle_phids[] = $object_phid; 39 + $object_phids[] = $object_phid; 40 + } 41 + 30 42 $handles = id(new PhabricatorHandleQuery()) 31 43 ->setViewer($viewer) 32 - ->withPHIDs($phids) 44 + ->withPHIDs($handle_phids) 33 45 ->execute(); 34 46 35 47 $objects = id(new PhabricatorObjectQuery()) 36 48 ->setViewer($viewer) 37 - ->withPHIDs($phids) 49 + ->withPHIDs($object_phids) 38 50 ->execute(); 39 51 $objects = mpull($objects, null, 'getPHID'); 40 52 ··· 67 79 array_select_keys($objects, $extension_phids)); 68 80 } 69 81 70 - $cards = array(); 71 - foreach ($phids as $phid) { 72 - $handle = $handles[$phid]; 73 - $object = idx($objects, $phid); 82 + $results = array(); 83 + foreach ($cards as $card_key => $card) { 84 + $object_phid = $card['objectPHID']; 85 + 86 + $handle = $handles[$object_phid]; 87 + $object = idx($objects, $object_phid); 74 88 75 89 $hovercard = id(new PHUIHovercardView()) 76 90 ->setUser($viewer) ··· 90 104 } 91 105 } 92 106 93 - $cards[$phid] = $hovercard; 107 + $results[$card_key] = $hovercard; 94 108 } 95 109 96 110 if ($request->isAjax()) { 97 111 return id(new AphrontAjaxResponse())->setContent( 98 112 array( 99 - 'cards' => $cards, 113 + 'cards' => $results, 100 114 )); 101 115 } 102 116 103 - foreach ($cards as $key => $hovercard) { 104 - $cards[$key] = phutil_tag('div', 117 + foreach ($results as $key => $hovercard) { 118 + $results[$key] = phutil_tag('div', 105 119 array( 106 120 'class' => 'ml', 107 121 ), ··· 109 123 } 110 124 111 125 return $this->newPage() 112 - ->appendChild($cards) 126 + ->appendChild($results) 113 127 ->setShowFooter(false); 114 128 } 115 129
+1 -1
src/applications/system/events/PhabricatorSystemDebugUIEventListener.php
··· 42 42 $submenu[] = id(new PhabricatorActionView()) 43 43 ->setIcon('fa-address-card-o') 44 44 ->setName(pht('View Hovercard')) 45 - ->setHref(urisprintf('/search/hovercard/?phids[]=%s', $phid)); 45 + ->setHref(urisprintf('/search/hovercard/?names=%s', $phid)); 46 46 47 47 $developer_action = id(new PhabricatorActionView()) 48 48 ->setName(pht('Advanced/Developer...'))
+11 -156
webroot/rsrc/js/core/Hovercard.js
··· 10 10 11 11 JX.install('Hovercard', { 12 12 13 - statics : { 14 - _node : null, 15 - _activeRoot : null, 16 - _visiblePHID : null, 17 - _alignment: null, 18 - 19 - fetchUrl : '/search/hovercard/', 20 - 21 - /** 22 - * Hovercard storage. {"PHID-XXXX-YYYY":"<...>", ...} 23 - */ 24 - _cards : {}, 25 - 26 - getAnchor : function() { 27 - return this._activeRoot; 28 - }, 29 - 30 - getCard : function() { 31 - var self = JX.Hovercard; 32 - return self._node; 33 - }, 34 - 35 - getAlignment: function() { 36 - var self = JX.Hovercard; 37 - return self._alignment; 38 - }, 39 - 40 - show : function(root, phid) { 41 - var self = JX.Hovercard; 42 - 43 - if (root === this._activeRoot) { 44 - return; 45 - } 46 - 47 - self.hide(); 48 - 49 - self._visiblePHID = phid; 50 - self._activeRoot = root; 51 - 52 - if (!(phid in self._cards)) { 53 - self._load([phid]); 54 - } else { 55 - self._drawCard(phid); 56 - } 57 - }, 58 - 59 - _drawCard : function(phid) { 60 - var self = JX.Hovercard; 61 - // card is loading... 62 - if (self._cards[phid] === true) { 63 - return; 64 - } 65 - // Not the current requested card 66 - if (phid != self._visiblePHID) { 67 - return; 68 - } 69 - // Not loaded 70 - if (!(phid in self._cards)) { 71 - return; 72 - } 73 - 74 - var root = self._activeRoot; 75 - var node = JX.$N('div', 76 - { className: 'jx-hovercard-container' }, 77 - JX.$H(self._cards[phid])); 13 + properties: { 14 + hovercardKey: null, 15 + objectPHID: null, 16 + isLoading: false, 17 + isLoaded: false, 18 + content: null 19 + }, 78 20 79 - self._node = node; 80 - 81 - // Append the card to the document, but offscreen, so we can measure it. 82 - node.style.left = '-10000px'; 83 - document.body.appendChild(node); 84 - 85 - // Retrieve size from child (wrapper), since node gives wrong dimensions? 86 - var child = node.firstChild; 87 - var p = JX.$V(root); 88 - var d = JX.Vector.getDim(root); 89 - var n = JX.Vector.getDim(child); 90 - var v = JX.Vector.getViewport(); 91 - var s = JX.Vector.getScroll(); 92 - 93 - // Move the tip so it's nicely aligned. 94 - var margin = 20; 95 - 96 - 97 - // Try to align the card directly above the link, with left borders 98 - // touching. 99 - var x = p.x; 100 - 101 - // If this would push us off the right side of the viewport, push things 102 - // back to the left. 103 - if ((x + n.x + margin) > (s.x + v.x)) { 104 - x = (s.x + v.x) - n.x - margin; 105 - } 106 - 107 - // Try to put the card above the link. 108 - var y = p.y - n.y - margin; 109 - self._alignment = 'north'; 110 - 111 - // If the card is near the top of the window, show it beneath the 112 - // link we're hovering over instead. 113 - if ((y - margin) < s.y) { 114 - y = p.y + d.y + margin; 115 - self._alignment = 'south'; 116 - } 117 - 118 - node.style.left = x + 'px'; 119 - node.style.top = y + 'px'; 120 - }, 121 - 122 - hide : function() { 123 - var self = JX.Hovercard; 124 - self._visiblePHID = null; 125 - self._activeRoot = null; 126 - if (self._node) { 127 - JX.DOM.remove(self._node); 128 - self._node = null; 129 - } 130 - }, 131 - 132 - /** 133 - * Pass it an array of phids to load them into storage 134 - * 135 - * @param list phids 136 - */ 137 - _load : function(phids) { 138 - var self = JX.Hovercard; 139 - var uri = JX.$U(self.fetchUrl); 140 - 141 - var send = false; 142 - for (var ii = 0; ii < phids.length; ii++) { 143 - var phid = phids[ii]; 144 - if (phid in self._cards) { 145 - continue; 146 - } 147 - self._cards[phid] = true; // means "loading" 148 - uri.setQueryParam('phids['+ii+']', phids[ii]); 149 - send = true; 150 - } 151 - 152 - if (!send) { 153 - // already loaded / loading everything! 154 - return; 155 - } 156 - 157 - new JX.Request(uri, function(r) { 158 - for (var phid in r.cards) { 159 - self._cards[phid] = r.cards[phid]; 160 - 161 - // Don't draw if the user is faster than the browser 162 - // Only draw if the user is still requesting the original card 163 - if (self.getCard() && phid != self._visiblePHID) { 164 - continue; 165 - } 166 - 167 - self._drawCard(phid); 168 - } 169 - }).send(); 21 + members: { 22 + newContentNode: function() { 23 + return JX.$H(this.getContent()); 170 24 } 171 25 } 26 + 172 27 });
+226
webroot/rsrc/js/core/HovercardList.js
··· 1 + /** 2 + * @requires javelin-install 3 + * javelin-dom 4 + * javelin-vector 5 + * javelin-request 6 + * javelin-uri 7 + * phui-hovercard 8 + * @provides phui-hovercard-list 9 + * @javelin 10 + */ 11 + 12 + JX.install('HovercardList', { 13 + 14 + construct: function() { 15 + this._cards = {}; 16 + this._drawRequest = {}; 17 + }, 18 + 19 + members: { 20 + _cardNode: null, 21 + _rootNode: null, 22 + _cards: null, 23 + _drawRequest: null, 24 + _visibleCard: null, 25 + 26 + _fetchURI : '/search/hovercard/', 27 + 28 + getCard: function(spec) { 29 + var hovercard_key = this._newHovercardKey(spec); 30 + 31 + if (!(hovercard_key in this._cards)) { 32 + var card = new JX.Hovercard() 33 + .setHovercardKey(hovercard_key) 34 + .setObjectPHID(spec.hoverPHID); 35 + 36 + this._cards[hovercard_key] = card; 37 + } 38 + 39 + return this._cards[hovercard_key]; 40 + }, 41 + 42 + drawCard: function(card, node) { 43 + this._drawRequest = { 44 + card: card, 45 + node: node 46 + }; 47 + 48 + if (card.getIsLoaded()) { 49 + return this._paintCard(card); 50 + } 51 + 52 + if (card.getIsLoading()) { 53 + return; 54 + } 55 + 56 + var hovercard_key = card.getHovercardKey(); 57 + 58 + var request = {}; 59 + request[hovercard_key] = this._newCardRequest(card); 60 + request = JX.JSON.stringify(request); 61 + 62 + var uri = JX.$U(this._fetchURI) 63 + .setQueryParam('cards', request); 64 + 65 + var onresponse = JX.bind(this, function(r) { 66 + var card = this._cards[hovercard_key]; 67 + 68 + this._fillCard(card, r.cards[hovercard_key]); 69 + this._paintCard(card); 70 + }); 71 + 72 + card.setIsLoading(true); 73 + 74 + new JX.Request(uri, onresponse) 75 + .send(); 76 + }, 77 + 78 + _newHovercardKey: function(spec) { 79 + return 'phid=' + spec.hoverPHID; 80 + }, 81 + 82 + _newCardRequest: function(card) { 83 + return { 84 + objectPHID: card.getObjectPHID() 85 + }; 86 + }, 87 + 88 + _getCardNode: function() { 89 + if (!this._cardNode) { 90 + var attributes = { 91 + className: 'jx-hovercard-container' 92 + }; 93 + 94 + this._cardNode = JX.$N('div', attributes); 95 + } 96 + 97 + return this._cardNode; 98 + }, 99 + 100 + _fillCard: function(card, response) { 101 + card.setContent(response); 102 + card.setIsLoaded(true); 103 + }, 104 + 105 + _paintCard: function(card) { 106 + var request = this._drawRequest; 107 + 108 + if (request.card !== card) { 109 + // This paint request is no longer the most recent paint request. 110 + return; 111 + } 112 + 113 + this.hideCard(); 114 + 115 + this._rootNode = request.node; 116 + var root = this._rootNode; 117 + var node = this._getCardNode(); 118 + 119 + JX.DOM.setContent(node, card.newContentNode()); 120 + 121 + // Append the card to the document, but offscreen, so we can measure it. 122 + node.style.left = '-10000px'; 123 + document.body.appendChild(node); 124 + 125 + // Retrieve size from child (wrapper), since node gives wrong dimensions? 126 + var child = node.firstChild; 127 + 128 + var p = JX.$V(root); 129 + var d = JX.Vector.getDim(root); 130 + var n = JX.Vector.getDim(child); 131 + var v = JX.Vector.getViewport(); 132 + var s = JX.Vector.getScroll(); 133 + 134 + // Move the tip so it's nicely aligned. 135 + var margin = 20; 136 + 137 + // Try to align the card directly above the link, with left borders 138 + // touching. 139 + var x = p.x; 140 + 141 + // If this would push us off the right side of the viewport, push things 142 + // back to the left. 143 + if ((x + n.x + margin) > (s.x + v.x)) { 144 + x = (s.x + v.x) - n.x - margin; 145 + } 146 + 147 + // Try to put the card above the link. 148 + var y = p.y - n.y - margin; 149 + 150 + var alignment = 'north'; 151 + 152 + // If the card is near the top of the window, show it beneath the 153 + // link we're hovering over instead. 154 + if ((y - margin) < s.y) { 155 + y = p.y + d.y + margin; 156 + alignment = 'south'; 157 + } 158 + 159 + this._alignment = alignment; 160 + node.style.left = x + 'px'; 161 + node.style.top = y + 'px'; 162 + 163 + this._visibleCard = card; 164 + }, 165 + 166 + hideCard: function() { 167 + var node = this._getCardNode(); 168 + JX.DOM.remove(node); 169 + 170 + this._rootNode = null; 171 + this._alignment = null; 172 + this._visibleCard = null; 173 + }, 174 + 175 + onMouseMove: function(e) { 176 + if (!this._visibleCard) { 177 + return; 178 + } 179 + 180 + var root = this._rootNode; 181 + var node = this._getCardNode(); 182 + var alignment = this._alignment; 183 + 184 + var mouse = JX.$V(e); 185 + var node_pos = JX.$V(node); 186 + var node_dim = JX.Vector.getDim(node); 187 + var root_pos = JX.$V(root); 188 + var root_dim = JX.Vector.getDim(root); 189 + 190 + var margin = 20; 191 + 192 + if (alignment === 'south') { 193 + // Cursor is below the node. 194 + if (mouse.y > node_pos.y + node_dim.y + margin) { 195 + this.hideCard(); 196 + } 197 + 198 + // Cursor is above the root. 199 + if (mouse.y < root_pos.y - margin) { 200 + this.hideCard(); 201 + } 202 + } else { 203 + // Cursor is above the node. 204 + if (mouse.y < node_pos.y - margin) { 205 + this.hideCard(); 206 + } 207 + 208 + // Cursor is below the root. 209 + if (mouse.y > root_pos.y + root_dim.y + margin) { 210 + this.hideCard(); 211 + } 212 + } 213 + 214 + // Cursor is too far to the left. 215 + if (mouse.x < Math.min(root_pos.x, node_pos.x) - margin) { 216 + this.hideCard(); 217 + } 218 + 219 + // Cursor is too far to the right. 220 + if (mouse.x > 221 + Math.max(root_pos.x + root_dim.x, node_pos.x + node_dim.x) + margin) { 222 + this.hideCard(); 223 + } 224 + } 225 + } 226 + });
+15 -53
webroot/rsrc/js/core/behavior-hovercard.js
··· 5 5 * javelin-stratcom 6 6 * javelin-vector 7 7 * phui-hovercard 8 + * phui-hovercard-list 8 9 * @javelin 9 10 */ 10 11 11 - JX.behavior('phui-hovercards', function() { 12 + JX.behavior('phui-hovercards', function(config, statics) { 13 + if (statics.hovercardList) { 14 + return; 15 + } 16 + 17 + var cards = new JX.HovercardList(); 18 + statics.hovercardList = cards; 19 + 12 20 13 21 // We listen for mousemove instead of mouseover to handle the case when user 14 22 // scrolls with keyboard. We don't want to display hovercard if node gets ··· 23 31 return; 24 32 } 25 33 34 + var node = e.getNode('hovercard'); 26 35 var data = e.getNodeData('hovercard'); 27 36 28 - JX.Hovercard.show( 29 - e.getNode('hovercard'), 30 - data.hoverPHID); 37 + var card = cards.getCard(data); 38 + 39 + cards.drawCard(card, node); 31 40 }); 32 41 33 42 JX.Stratcom.listen( 34 43 'mousemove', 35 44 null, 36 45 function (e) { 37 - if (!JX.Hovercard.getCard()) { 38 - return; 39 - } 40 - 41 - var root = JX.Hovercard.getAnchor(); 42 - var node = JX.Hovercard.getCard(); 43 - var align = JX.Hovercard.getAlignment(); 44 - 45 - var mouse = JX.$V(e); 46 - var node_pos = JX.$V(node); 47 - var node_dim = JX.Vector.getDim(node); 48 - var root_pos = JX.$V(root); 49 - var root_dim = JX.Vector.getDim(root); 50 - 51 - var margin = 20; 52 - 53 - if (align == 'south') { 54 - // Cursor is below the node. 55 - if (mouse.y > node_pos.y + node_dim.y + margin) { 56 - JX.Hovercard.hide(); 57 - } 58 - 59 - // Cursor is above the root. 60 - if (mouse.y < root_pos.y - margin) { 61 - JX.Hovercard.hide(); 62 - } 63 - } else { 64 - // Cursor is above the node. 65 - if (mouse.y < node_pos.y - margin) { 66 - JX.Hovercard.hide(); 67 - } 68 - 69 - // Cursor is below the root. 70 - if (mouse.y > root_pos.y + root_dim.y + margin) { 71 - JX.Hovercard.hide(); 72 - } 73 - } 74 - 75 - // Cursor is too far to the left. 76 - if (mouse.x < Math.min(root_pos.x, node_pos.x) - margin) { 77 - JX.Hovercard.hide(); 78 - } 79 - 80 - // Cursor is too far to the right. 81 - if (mouse.x > 82 - Math.max(root_pos.x + root_dim.x, node_pos.x + node_dim.x) + margin) { 83 - JX.Hovercard.hide(); 84 - } 46 + cards.onMouseMove(e); 85 47 }); 86 48 87 49 // When we leave the page, hide any visible hovercards. If we don't do this, ··· 91 53 ['unload', 'onresize'], 92 54 null, 93 55 function() { 94 - JX.Hovercard.hide(); 56 + cards.hideCard(); 95 57 }); 96 58 97 59 });