diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5598,6 +5598,7 @@ 'PhutilQsprintfInterface' => 'infrastructure/storage/xsprintf/PhutilQsprintfInterface.php', 'PhutilQueryString' => 'infrastructure/storage/xsprintf/PhutilQueryString.php', 'PhutilRealNameContextFreeGrammar' => 'infrastructure/lipsum/PhutilRealNameContextFreeGrammar.php', + 'PhutilRemarkupAnchorRule' => 'infrastructure/markup/markuprule/PhutilRemarkupAnchorRule.php', 'PhutilRemarkupBlockInterpreter' => 'infrastructure/markup/blockrule/PhutilRemarkupBlockInterpreter.php', 'PhutilRemarkupBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupBlockRule.php', 'PhutilRemarkupBlockStorage' => 'infrastructure/markup/PhutilRemarkupBlockStorage.php', @@ -12391,6 +12392,7 @@ 'PhutilPhabricatorAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilQueryString' => 'Phobject', 'PhutilRealNameContextFreeGrammar' => 'PhutilContextFreeGrammar', + 'PhutilRemarkupAnchorRule' => 'PhutilRemarkupRule', 'PhutilRemarkupBlockInterpreter' => 'Phobject', 'PhutilRemarkupBlockRule' => 'Phobject', 'PhutilRemarkupBlockStorage' => 'Phobject', diff --git a/src/docs/user/userguide/remarkup.diviner b/src/docs/user/userguide/remarkup.diviner --- a/src/docs/user/userguide/remarkup.diviner +++ b/src/docs/user/userguide/remarkup.diviner @@ -715,6 +715,18 @@ > Press {key down down-right right LP} to activate the hadoken technique. +Anchors +======== + +You can use `{anchor #xyz}` to create a document anchor and later link to +it directly with `#xyz` in the URI. + +Headers also automatically create named anchors. + +If you navigate to `#xyz` in your browser location bar, the page will scroll +to the first anchor with "xyz" as a prefix of the anchor name. + + = Fullscreen Mode = Remarkup editors provide a fullscreen composition mode. This can make it easier diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -539,6 +539,7 @@ $rules[] = new PhutilRemarkupDelRule(); $rules[] = new PhutilRemarkupUnderlineRule(); $rules[] = new PhutilRemarkupHighlightRule(); + $rules[] = new PhutilRemarkupAnchorRule(); foreach (self::loadCustomInlineRules() as $rule) { $rules[] = clone $rule; diff --git a/src/infrastructure/markup/blockrule/PhutilRemarkupHeaderBlockRule.php b/src/infrastructure/markup/blockrule/PhutilRemarkupHeaderBlockRule.php --- a/src/infrastructure/markup/blockrule/PhutilRemarkupHeaderBlockRule.php +++ b/src/infrastructure/markup/blockrule/PhutilRemarkupHeaderBlockRule.php @@ -162,12 +162,7 @@ public static function getAnchorNameFromHeaderText($text) { $anchor = phutil_utf8_strtolower($text); - - // Replace all latin characters which are not "a-z" or "0-9" with "-". - // Preserve other characters, since non-latin letters and emoji work - // fine in anchors. - $anchor = preg_replace('/[\x00-\x2F\x3A-\x60\x7B-\x7F]+/', '-', $anchor); - $anchor = trim($anchor, '-'); + $anchor = PhutilRemarkupAnchorRule::normalizeAnchor($anchor); // Truncate the fragment to something reasonable. $anchor = id(new PhutilUTF8StringTruncator()) diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupAnchorRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupAnchorRule.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/markup/markuprule/PhutilRemarkupAnchorRule.php @@ -0,0 +1,69 @@ +getEngine(); + + if ($engine->isTextMode()) { + return null; + } + + if ($engine->isHTMLMailMode()) { + return null; + } + + if ($engine->isAnchorMode()) { + return null; + } + + if (!$this->isFlatText($matches[0])) { + return $matches[0]; + } + + if (!self::isValidAnchorName($matches[1])) { + return $matches[0]; + } + + $tag_view = phutil_tag( + 'a', + array( + 'name' => $matches[1], + ), + ''); + + return $this->getEngine()->storeText($tag_view); + } + + public static function isValidAnchorName($anchor_name) { + $normal_anchor = self::normalizeAnchor($anchor_name); + + if ($normal_anchor === $anchor_name) { + return true; + } + + return false; + } + + public static function normalizeAnchor($anchor) { + // Replace all latin characters which are not "a-z" or "0-9" with "-". + // Preserve other characters, since non-latin letters and emoji work + // fine in anchors. + $anchor = preg_replace('/[\x00-\x2F\x3A-\x60\x7B-\x7F]+/', '-', $anchor); + $anchor = trim($anchor, '-'); + + return $anchor; + } + +}