@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
at recaptime-dev/main 273 lines 6.3 kB view raw
1<?php 2 3final class PhabricatorKeyboardRemarkupRule extends PhutilRemarkupRule { 4 5 public function getPriority() { 6 return 200.0; 7 } 8 9 public function apply($text) { 10 return preg_replace_callback( 11 '@{key\b((?:[^}\\\\]+|\\\\.)*)}@m', 12 array($this, 'markupKeystrokes'), 13 $text); 14 } 15 16 public function markupKeystrokes(array $matches) { 17 if (!$this->isFlatText($matches[0])) { 18 return $matches[0]; 19 } 20 21 $keys = explode(' ', $matches[1]); 22 foreach ($keys as $k => $v) { 23 $v = trim($v, " \n"); 24 $v = preg_replace('/\\\\(.)/', '\\1', $v); 25 if ($v === '') { 26 unset($keys[$k]); 27 continue; 28 } 29 $keys[$k] = $v; 30 } 31 32 $special = array( 33 array( 34 'name' => pht('Command'), 35 'symbol' => "\xE2\x8C\x98", 36 'aliases' => array( 37 'cmd', 38 'command', 39 ), 40 ), 41 array( 42 'name' => pht('Option'), 43 'symbol' => "\xE2\x8C\xA5", 44 'aliases' => array( 45 'opt', 46 'option', 47 ), 48 ), 49 array( 50 'name' => pht('Shift'), 51 'symbol' => "\xE2\x87\xA7", 52 'aliases' => array( 53 'shift', 54 ), 55 ), 56 array( 57 'name' => pht('Escape'), 58 'symbol' => "\xE2\x8E\x8B", 59 'aliases' => array( 60 'esc', 61 'escape', 62 ), 63 ), 64 array( 65 'name' => pht('Enter'), 66 'symbol' => "\xE2\x8F\x8E", 67 'aliases' => array( 68 'enter', 69 'return', 70 ), 71 ), 72 array( 73 'name' => pht('Control'), 74 'symbol' => "\xE2\x8C\x83", 75 'aliases' => array( 76 'ctrl', 77 'control', 78 ), 79 ), 80 array( 81 'name' => pht('Up'), 82 'symbol' => "\xE2\x86\x91", 83 'heavy' => "\xE2\xAC\x86", 84 'aliases' => array( 85 'up', 86 'arrow-up', 87 'up-arrow', 88 'north', 89 ), 90 ), 91 array( 92 'name' => pht('Tab'), 93 'symbol' => "\xE2\x87\xA5", 94 'aliases' => array( 95 'tab', 96 ), 97 ), 98 array( 99 'name' => pht('Right'), 100 'symbol' => "\xE2\x86\x92", 101 'heavy' => "\xE2\x9E\xA1", 102 'aliases' => array( 103 'right', 104 'right-arrow', 105 'arrow-right', 106 'east', 107 ), 108 ), 109 array( 110 'name' => pht('Left'), 111 'symbol' => "\xE2\x86\x90", 112 'heavy' => "\xE2\xAC\x85", 113 'aliases' => array( 114 'left', 115 'left-arrow', 116 'arrow-left', 117 'west', 118 ), 119 ), 120 array( 121 'name' => pht('Down'), 122 'symbol' => "\xE2\x86\x93", 123 'heavy' => "\xE2\xAC\x87", 124 'aliases' => array( 125 'down', 126 'down-arrow', 127 'arrow-down', 128 'south', 129 ), 130 ), 131 array( 132 'name' => pht('Up Right'), 133 'symbol' => "\xE2\x86\x97", 134 'heavy' => "\xE2\xAC\x88", 135 'aliases' => array( 136 'up-right', 137 'upright', 138 'up-right-arrow', 139 'upright-arrow', 140 'arrow-up-right', 141 'arrow-upright', 142 'northeast', 143 'north-east', 144 ), 145 ), 146 array( 147 'name' => pht('Down Right'), 148 'symbol' => "\xE2\x86\x98", 149 'heavy' => "\xE2\xAC\x8A", 150 'aliases' => array( 151 'down-right', 152 'downright', 153 'down-right-arrow', 154 'downright-arrow', 155 'arrow-down-right', 156 'arrow-downright', 157 'southeast', 158 'south-east', 159 ), 160 ), 161 array( 162 'name' => pht('Down Left'), 163 'symbol' => "\xE2\x86\x99", 164 'heavy' => "\xE2\xAC\x8B", 165 'aliases' => array( 166 'down-left', 167 'downleft', 168 'down-left-arrow', 169 'downleft-arrow', 170 'arrow-down-left', 171 'arrow-downleft', 172 'southwest', 173 'south-west', 174 ), 175 ), 176 array( 177 'name' => pht('Up Left'), 178 'symbol' => "\xE2\x86\x96", 179 'heavy' => "\xE2\xAC\x89", 180 'aliases' => array( 181 'up-left', 182 'upleft', 183 'up-left-arrow', 184 'upleft-arrow', 185 'arrow-up-left', 186 'arrow-upleft', 187 'northwest', 188 'north-west', 189 ), 190 ), 191 ); 192 193 $map = array(); 194 foreach ($special as $spec) { 195 foreach ($spec['aliases'] as $alias) { 196 $map[$alias] = $spec; 197 } 198 } 199 200 $is_text = $this->getEngine()->isTextMode(); 201 $is_html_mail = $this->getEngine()->isHTMLMailMode(); 202 203 if ($is_html_mail) { 204 $key_style = array( 205 'display: inline-block;', 206 'min-width: 1em;', 207 'padding: 4px 5px 5px;', 208 'font-weight: normal;', 209 'font-size: 0.8rem;', 210 'text-align: center;', 211 'text-decoration: none;', 212 'line-height: 0.6rem;', 213 'border-radius: 3px;', 214 'box-shadow: inset 0 -1px 0 rgba(71, 87, 120, 0.08);', 215 '-webkit-user-select: none;', 216 'user-select: none;', 217 'background: #f7f7f7;', 218 'border: 1px solid #C7CCD9;', 219 ); 220 $key_style = implode(' ', $key_style); 221 222 $join_style = array( 223 'padding: 0 4px;', 224 'color: #92969D;', 225 ); 226 $join_style = implode(' ', $join_style); 227 } else { 228 $key_style = null; 229 $join_style = null; 230 } 231 232 $parts = array(); 233 foreach ($keys as $k => $v) { 234 $normal = phutil_utf8_strtolower($v); 235 if (isset($map[$normal])) { 236 $spec = $map[$normal]; 237 } else { 238 $spec = array( 239 'name' => null, 240 'symbol' => $v, 241 ); 242 } 243 244 if ($is_text) { 245 $parts[] = '['.$spec['symbol'].']'; 246 } else { 247 $parts[] = phutil_tag( 248 'kbd', 249 array( 250 'title' => $spec['name'], 251 'style' => $key_style, 252 ), 253 $spec['symbol']); 254 } 255 } 256 257 if ($is_text) { 258 $parts = implode(' + ', $parts); 259 } else { 260 $glue = phutil_tag( 261 'span', 262 array( 263 'class' => 'kbd-join', 264 'style' => $join_style, 265 ), 266 '+'); 267 $parts = phutil_implode_html($glue, $parts); 268 } 269 270 return $this->getEngine()->storeText($parts); 271 } 272 273}