@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.

Allow a user to target "#anchor" by navigating to any prefix

Summary:
Ref T13410. We currently generate some less-than-ideal anchors in remarkup, but it's hard to change the algorithm without breaking stuff.

To mitigate this, allow `#xyz` to match any target on the page which begins with `xyz`. This means we can make anchors longer with no damage, and savvy users are free to shorten anchors to produce more presentation-friendly links.

Test Plan: Browsed to `#header-th`, was scrolled to `#header-three`, etc.

Maniphest Tasks: T13410

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

+95 -45
+9 -9
resources/celerity/map.php
··· 10 10 'conpherence.pkg.css' => '3c8a0668', 11 11 'conpherence.pkg.js' => '020aebcf', 12 12 'core.pkg.css' => 'c69171e6', 13 - 'core.pkg.js' => '73a06a9f', 13 + 'core.pkg.js' => '6e5c894f', 14 14 'differential.pkg.css' => '8d8360fb', 15 15 'differential.pkg.js' => '0b037a4f', 16 16 'diffusion.pkg.css' => '42c75c37', ··· 506 506 'rsrc/js/core/behavior-tokenizer.js' => '3b4899b0', 507 507 'rsrc/js/core/behavior-tooltip.js' => '73ecc1f8', 508 508 'rsrc/js/core/behavior-user-menu.js' => '60cd9241', 509 - 'rsrc/js/core/behavior-watch-anchor.js' => '0e6d261f', 509 + 'rsrc/js/core/behavior-watch-anchor.js' => '3972dadb', 510 510 'rsrc/js/core/behavior-workflow.js' => '9623adc1', 511 511 'rsrc/js/core/darkconsole/DarkLog.js' => '3b869402', 512 512 'rsrc/js/core/darkconsole/DarkMessage.js' => '26cd4b73', ··· 655 655 'javelin-behavior-phabricator-tooltips' => '73ecc1f8', 656 656 'javelin-behavior-phabricator-transaction-comment-form' => '2bdadf1a', 657 657 'javelin-behavior-phabricator-transaction-list' => '9cec214e', 658 - 'javelin-behavior-phabricator-watch-anchor' => '0e6d261f', 658 + 'javelin-behavior-phabricator-watch-anchor' => '3972dadb', 659 659 'javelin-behavior-pholio-mock-edit' => '3eed1f2b', 660 660 'javelin-behavior-pholio-mock-view' => '5aa1544e', 661 661 'javelin-behavior-phui-dropdown-menu' => '5cf0501a', ··· 999 999 '0d2490ce' => array( 1000 1000 'javelin-install', 1001 1001 ), 1002 - '0e6d261f' => array( 1003 - 'javelin-behavior', 1004 - 'javelin-stratcom', 1005 - 'javelin-dom', 1006 - 'javelin-vector', 1007 - ), 1008 1002 '0eaa33a9' => array( 1009 1003 'javelin-behavior', 1010 1004 'javelin-dom', ··· 1226 1220 '38c1f3fb' => array( 1227 1221 'javelin-install', 1228 1222 'javelin-dom', 1223 + ), 1224 + '3972dadb' => array( 1225 + 'javelin-behavior', 1226 + 'javelin-stratcom', 1227 + 'javelin-dom', 1228 + 'javelin-vector', 1229 1229 ), 1230 1230 '398fdf13' => array( 1231 1231 'javelin-behavior',
+86 -36
webroot/rsrc/js/core/behavior-watch-anchor.js
··· 8 8 9 9 JX.behavior('phabricator-watch-anchor', function() { 10 10 11 - var highlighted; 11 + // When the user loads a page with an "#anchor" or changes the "#anchor" on 12 + // an existing page, we try to scroll the page to the relevant location. 13 + 14 + // Browsers do this on their own, but we have some additional rules to try 15 + // to match anchors more flexibly and handle cases where an anchor is not 16 + // yet present in the document because something is still loading or 17 + // rendering it, often via Ajax. 12 18 13 - function highlight() { 14 - highlighted && JX.DOM.alterClass(highlighted, 'anchor-target', false); 15 - try { 16 - highlighted = JX.$('anchor-' + window.location.hash.replace('#', '')); 17 - } catch (ex) { 18 - highlighted = null; 19 - } 20 - highlighted && JX.DOM.alterClass(highlighted, 'anchor-target', true); 21 - } 19 + // Number of milliseconds we'll keep trying to find an anchor for. 20 + var wait_max = 5000; 21 + 22 + // Wait between retries. 23 + var wait_ms = 100; 24 + 25 + var target; 26 + var retry_ms; 22 27 23 - // Defer invocation so other listeners can update the document. 24 - function defer_highlight() { 25 - setTimeout(highlight, 0); 28 + function try_anchor() { 29 + retry_ms = wait_max; 30 + seek_anchor(); 26 31 } 27 32 28 - // In some cases, we link to an anchor but the anchor target ajaxes in 29 - // later. If it pops in within the first few seconds, jump to it. 30 - function try_anchor() { 33 + function seek_anchor() { 31 34 var anchor = window.location.hash.replace('#', ''); 32 - try { 33 - // If the anchor exists, assume the browser handled the jump. 34 - if (anchor) { 35 - JX.$(anchor); 35 + 36 + if (!anchor.length) { 37 + return; 38 + } 39 + 40 + var ii; 41 + var node = null; 42 + 43 + // When the user navigates to "#abc", we'll try to find a node with 44 + // either ID "abc" or ID "anchor-abc". 45 + var ids = [anchor, 'anchor-' + anchor]; 46 + 47 + for (ii = 0; ii < ids.length; ii++) { 48 + try { 49 + node = JX.$(ids[ii]); 50 + break; 51 + } catch (e) { 52 + // Continue. 36 53 } 37 - defer_highlight(); 38 - } catch (e) { 39 - var n = 50; 40 - var try_anchor_again = function () { 41 - try { 42 - var node = JX.$(anchor); 43 - var pos = JX.Vector.getPosWithScroll(node); 44 - JX.DOM.scrollToPosition(0, pos.y - 60); 45 - defer_highlight(); 46 - } catch (e) { 47 - if (n--) { 48 - setTimeout(try_anchor_again, 100); 49 - } 54 + } 55 + 56 + // If we haven't found a matching node yet, look for an "<a />" tag with 57 + // a "name" attribute that has our anchor as a prefix. For example, you 58 + // can navigate to "#cat" and we'll match "#cat-and-mouse". 59 + 60 + if (!node) { 61 + var anchor_nodes = JX.DOM.scry(document.body, 'a'); 62 + for (ii = 0; ii < anchor_nodes.length; ii++) { 63 + if (!anchor_nodes[ii].name) { 64 + continue; 65 + } 66 + 67 + if (anchor_nodes[ii].name.substring(0, anchor.length) === anchor) { 68 + node = anchor_nodes[ii]; 69 + break; 50 70 } 51 - }; 52 - try_anchor_again(); 71 + } 72 + } 73 + 74 + // If we already have an anchor highlighted, unhighlight it and throw 75 + // it away if it doesn't match the new target. 76 + if (target && (target !== node)) { 77 + JX.DOM.alterClass(target, 'anchor-target', false); 78 + target = null; 79 + } 80 + 81 + // If we didn't find a matching anchor, try again soon. This allows 82 + // rendering logic some time to complete Ajax requests and draw elements 83 + // onto the page. 84 + if (!node) { 85 + if (retry_ms > 0) { 86 + retry_ms -= wait_ms; 87 + setTimeout(try_anchor, wait_ms); 88 + return; 89 + } 90 + } 91 + 92 + // If we've found a new target, highlight it. 93 + if (target !== node) { 94 + target = node; 95 + JX.DOM.alterClass(target, 'anchor-target', true); 96 + } 97 + 98 + // Try to scroll to the new target. 99 + try { 100 + var pos = JX.Vector.getPosWithScroll(node); 101 + JX.DOM.scrollToPosition(0, pos.y - 60); 102 + } catch (e) { 103 + // Ignore issues with scrolling the document. 53 104 } 54 105 } 55 106 56 107 JX.Stratcom.listen('hashchange', null, try_anchor); 57 108 try_anchor(); 58 - 59 109 });