diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '6791587e', + 'core.pkg.css' => 'd28c0515', 'core.pkg.js' => '1d376fa9', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '3fb7f532', @@ -105,7 +105,7 @@ 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'd0801452', - 'rsrc/css/core/remarkup.css' => '9905d6c4', + 'rsrc/css/core/remarkup.css' => 'cd912f2c', 'rsrc/css/core/syntax.css' => '769d3498', 'rsrc/css/core/z-index.css' => '2b01a823', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', @@ -792,7 +792,7 @@ 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'cfd23f37', - 'phabricator-remarkup-css' => '9905d6c4', + 'phabricator-remarkup-css' => 'cd912f2c', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 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 @@ -2707,6 +2707,7 @@ 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', 'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php', + 'PhabricatorKeyboardRemarkupRule' => 'infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php', 'PhabricatorKeyring' => 'applications/files/keyring/PhabricatorKeyring.php', 'PhabricatorKeyringConfigOptionType' => 'applications/files/keyring/PhabricatorKeyringConfigOptionType.php', 'PhabricatorLDAPAuthProvider' => 'applications/auth/provider/PhabricatorLDAPAuthProvider.php', @@ -7532,6 +7533,7 @@ 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorJumpNavHandler' => 'Phobject', 'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache', + 'PhabricatorKeyboardRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorKeyring' => 'Phobject', 'PhabricatorKeyringConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorLDAPAuthProvider' => 'PhabricatorAuthProvider', 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 @@ -688,6 +688,32 @@ - The `type` option can be set to `instructions` to indicate that an element is asking the user to make a choice or follow specific instructions. +Keystrokes +========== + +You can use `{key ...}` to render a stylized keystroke. For example, this: + +``` +Press {key M} to view the starmap. +``` + +...renders this: + +> Press {key M} to view the starmap. + +You can also render sequences with modifier keys. This: + +``` +Use {key command option shift 3} to take a screenshot. +Press {key down down-right right LP} to activate the hadoken technique. +``` + +...renders this: + +> Use {key command option shift 3} to take a screenshot. +> Press {key down down-right right LP} to activate the hadoken technique. + + = 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 @@ -505,6 +505,7 @@ $rules[] = new PhutilRemarkupDocumentLinkRule(); $rules[] = new PhabricatorNavigationRemarkupRule(); + $rules[] = new PhabricatorKeyboardRemarkupRule(); if ($options['youtube']) { $rules[] = new PhabricatorYoutubeRemarkupRule(); diff --git a/src/infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php @@ -0,0 +1,225 @@ +isFlatText($matches[0])) { + return $matches[0]; + } + + $keys = explode(' ', $matches[1]); + foreach ($keys as $k => $v) { + $v = trim($v, " \n"); + $v = preg_replace('/\\\\(.)/', '\\1', $v); + if (!strlen($v)) { + unset($keys[$k]); + continue; + } + $keys[$k] = $v; + } + + $special = array( + array( + 'name' => pht('Command'), + 'symbol' => "\xE2\x8C\x98", + 'aliases' => array( + 'cmd', + 'command', + ), + ), + array( + 'name' => pht('Option'), + 'symbol' => "\xE2\x8C\xA5", + 'aliases' => array( + 'opt', + 'option', + ), + ), + array( + 'name' => pht('Shift'), + 'symbol' => "\xE2\x87\xA7", + 'aliases' => array( + 'shift', + ), + ), + array( + 'name' => pht('Escape'), + 'symbol' => "\xE2\x8E\x8B", + 'aliases' => array( + 'esc', + 'escape', + ), + ), + array( + 'name' => pht('Up'), + 'symbol' => "\xE2\x86\x91", + 'heavy' => "\xE2\xAC\x86", + 'aliases' => array( + 'up', + 'arrow-up', + 'up-arrow', + 'north', + ), + ), + array( + 'name' => pht('Tab'), + 'symbol' => "\xE2\x87\xA5", + 'aliases' => array( + 'tab', + ), + ), + array( + 'name' => pht('Right'), + 'symbol' => "\xE2\x86\x92", + 'heavy' => "\xE2\x9E\xA1", + 'aliases' => array( + 'right', + 'right-arrow', + 'arrow-right', + 'east', + ), + ), + array( + 'name' => pht('Left'), + 'symbol' => "\xE2\x86\x90", + 'heavy' => "\xE2\xAC\x85", + 'aliases' => array( + 'left', + 'left-arrow', + 'arrow-left', + 'west', + ), + ), + array( + 'name' => pht('Down'), + 'symbol' => "\xE2\x86\x93", + 'heavy' => "\xE2\xAC\x87", + 'aliases' => array( + 'down', + 'down-arrow', + 'arrow-down', + 'south', + ), + ), + array( + 'name' => pht('Up Right'), + 'symbol' => "\xE2\x86\x97", + 'heavy' => "\xE2\xAC\x88", + 'aliases' => array( + 'up-right', + 'upright', + 'up-right-arrow', + 'upright-arrow', + 'arrow-up-right', + 'arrow-upright', + 'northeast', + 'north-east', + ), + ), + array( + 'name' => pht('Down Right'), + 'symbol' => "\xE2\x86\x98", + 'heavy' => "\xE2\xAC\x8A", + 'aliases' => array( + 'down-right', + 'downright', + 'down-right-arrow', + 'downright-arrow', + 'arrow-down-right', + 'arrow-downright', + 'southeast', + 'south-east', + ), + ), + array( + 'name' => pht('Down Left'), + 'symbol' => "\xE2\x86\x99", + 'heavy' => "\xE2\xAC\x8B", + 'aliases' => array( + 'down-left', + 'downleft', + 'down-left-arrow', + 'downleft-arrow', + 'arrow-down-left', + 'arrow-downleft', + 'southwest', + 'south-west', + ), + ), + array( + 'name' => pht('Up Left'), + 'symbol' => "\xE2\x86\x96", + 'heavy' => "\xE2\xAC\x89", + 'aliases' => array( + 'up-left', + 'upleft', + 'up-left-arrow', + 'upleft-arrow', + 'arrow-up-left', + 'arrow-upleft', + 'northwest', + 'north-west', + ), + ), + ); + + $map = array(); + foreach ($special as $spec) { + foreach ($spec['aliases'] as $alias) { + $map[$alias] = $spec; + } + } + + $is_text = $this->getEngine()->isTextMode(); + + $parts = array(); + foreach ($keys as $k => $v) { + $normal = phutil_utf8_strtolower($v); + if (isset($map[$normal])) { + $spec = $map[$normal]; + } else { + $spec = array( + 'name' => null, + 'symbol' => $v, + ); + } + + if ($is_text) { + $parts[] = '['.$spec['symbol'].']'; + } else { + $parts[] = phutil_tag( + 'kbd', + array( + 'title' => $spec['name'], + ), + $spec['symbol']); + } + } + + if ($is_text) { + $parts = implode(' + ', $parts); + } else { + $glue = phutil_tag( + 'span', + array( + 'class' => 'kbd-join', + ), + '+'); + $parts = phutil_implode_html($glue, $parts); + } + + return $this->getEngine()->storeText($parts); + } + +} diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -70,6 +70,11 @@ border: 1px solid {$lightgreyborder}; } +.phabricator-remarkup .kbd-join { + padding: 0 4px; + color: {$lightgreytext}; +} + .phabricator-remarkup pre.remarkup-counterexample { background-color: {$sh-redbackground}; }