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

Support text encoding and syntax highlighting options in document rendering

Summary: Depends on D19273. Ref T13105. Adds "Change Text Encoding..." and "Highlight As..." options when rendering documents, and makes an effort to automatically detect and handle text encoding.

Test Plan:
- Uploaded a Shift-JIS file, saw it auto-detect as Shift-JIS.
- Converted files between encodings.
- Highlighted various things as "Rainbow", etc.

Maniphest Tasks: T13105

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

+243 -29
+15 -15
resources/celerity/map.php
··· 10 10 'conpherence.pkg.css' => 'e68cf1fa', 11 11 'conpherence.pkg.js' => '15191c65', 12 12 'core.pkg.css' => '1dd5fa4b', 13 - 'core.pkg.js' => 'b9b4a943', 13 + 'core.pkg.js' => '1ea38af8', 14 14 'differential.pkg.css' => '113e692c', 15 15 'differential.pkg.js' => 'f6d809c0', 16 16 'diffusion.pkg.css' => 'a2d17c7d', ··· 392 392 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc', 393 393 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70', 394 394 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', 395 - 'rsrc/js/application/files/behavior-document-engine.js' => '194cbe53', 395 + 'rsrc/js/application/files/behavior-document-engine.js' => '9108ee1a', 396 396 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 397 397 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 398 398 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909', ··· 508 508 'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b', 509 509 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 510 510 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 511 - 'rsrc/js/phuix/PHUIXActionView.js' => 'ed18356a', 511 + 'rsrc/js/phuix/PHUIXActionView.js' => '8d4a8c72', 512 512 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'df1bbd34', 513 513 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', 514 514 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', ··· 607 607 'javelin-behavior-diffusion-jump-to' => '73d09eef', 608 608 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 609 609 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 610 - 'javelin-behavior-document-engine' => '194cbe53', 610 + 'javelin-behavior-document-engine' => '9108ee1a', 611 611 'javelin-behavior-doorkeeper-tag' => '1db13e70', 612 612 'javelin-behavior-drydock-live-operation-status' => '901935ef', 613 613 'javelin-behavior-durable-column' => '2ae077e1', ··· 864 864 'phui-workcard-view-css' => 'cca5fa92', 865 865 'phui-workpanel-view-css' => 'a3a63478', 866 866 'phuix-action-list-view' => 'b5c256b8', 867 - 'phuix-action-view' => 'ed18356a', 867 + 'phuix-action-view' => '8d4a8c72', 868 868 'phuix-autocomplete' => 'df1bbd34', 869 869 'phuix-button-view' => '8a91e1ac', 870 870 'phuix-dropdown-menu' => '04b2ae03', ··· 982 982 ), 983 983 '191b4909' => array( 984 984 'javelin-behavior', 985 - ), 986 - '194cbe53' => array( 987 - 'javelin-behavior', 988 - 'javelin-dom', 989 - 'javelin-stratcom', 990 985 ), 991 986 '1ad0a787' => array( 992 987 'javelin-install', ··· 1619 1614 'javelin-stratcom', 1620 1615 'javelin-install', 1621 1616 ), 1617 + '8d4a8c72' => array( 1618 + 'javelin-install', 1619 + 'javelin-dom', 1620 + 'javelin-util', 1621 + ), 1622 1622 '8e1baf68' => array( 1623 1623 'phui-button-css', 1624 1624 ), ··· 1643 1643 'javelin-dom', 1644 1644 'javelin-stratcom', 1645 1645 'javelin-vector', 1646 + ), 1647 + '9108ee1a' => array( 1648 + 'javelin-behavior', 1649 + 'javelin-dom', 1650 + 'javelin-stratcom', 1646 1651 ), 1647 1652 '92b9ec77' => array( 1648 1653 'javelin-behavior', ··· 2124 2129 'javelin-dom', 2125 2130 'javelin-stratcom', 2126 2131 'javelin-vector', 2127 - ), 2128 - 'ed18356a' => array( 2129 - 'javelin-install', 2130 - 'javelin-dom', 2131 - 'javelin-util', 2132 2132 ), 2133 2133 'edf8a145' => array( 2134 2134 'javelin-behavior',
+10
src/applications/files/controller/PhabricatorFileDocumentController.php
··· 43 43 $engine = $engines[$engine_key]; 44 44 $this->engine = $engine; 45 45 46 + $encode_setting = $request->getStr('encode'); 47 + if (strlen($encode_setting)) { 48 + $engine->setEncodingConfiguration($encode_setting); 49 + } 50 + 51 + $highlight_setting = $request->getStr('highlight'); 52 + if (strlen($highlight_setting)) { 53 + $engine->setHighlightingConfiguration($highlight_setting); 54 + } 55 + 46 56 try { 47 57 $content = $engine->newDocument($ref); 48 58 } catch (Exception $ex) {
+24
src/applications/files/controller/PhabricatorFileViewController.php
··· 422 422 $engine->setHighlightedLines(range($lines[0], $lines[1])); 423 423 } 424 424 425 + $encode_setting = $request->getStr('encode'); 426 + if (strlen($encode_setting)) { 427 + $engine->setEncodingConfiguration($encode_setting); 428 + } 429 + 430 + $highlight_setting = $request->getStr('highlight'); 431 + if (strlen($highlight_setting)) { 432 + $engine->setHighlightingConfiguration($highlight_setting); 433 + } 434 + 425 435 $views = array(); 426 436 foreach ($engines as $candidate_key => $candidate_engine) { 427 437 $label = $candidate_engine->getViewAsLabel($ref); ··· 443 453 'engineURI' => $candidate_engine->getRenderURI($ref), 444 454 'viewURI' => $view_uri, 445 455 'loadingMarkup' => hsprintf('%s', $loading), 456 + 'canEncode' => $candidate_engine->canConfigureEncoding($ref), 457 + 'canHighlight' => $candidate_engine->CanConfigureHighlighting($ref), 446 458 ); 447 459 } 448 460 ··· 474 486 'viewKey' => $engine->getDocumentEngineKey(), 475 487 'views' => $views, 476 488 'standaloneURI' => $engine->getRenderURI($ref), 489 + 'encode' => array( 490 + 'icon' => 'fa-font', 491 + 'name' => pht('Change Text Encoding...'), 492 + 'uri' => '/services/encoding/', 493 + 'value' => $encode_setting, 494 + ), 495 + 'highlight' => array( 496 + 'icon' => 'fa-lightbulb-o', 497 + 'name' => pht('Highlight As...'), 498 + 'uri' => '/services/highlight/', 499 + 'value' => $highlight_setting, 500 + ), 477 501 ); 478 502 479 503 $view_button = id(new PHUIButtonView())
+28
src/applications/files/document/PhabricatorDocumentEngine.php
··· 5 5 6 6 private $viewer; 7 7 private $highlightedLines = array(); 8 + private $encodingConfiguration; 9 + private $highlightingConfiguration; 8 10 9 11 final public function setViewer(PhabricatorUser $viewer) { 10 12 $this->viewer = $viewer; ··· 26 28 27 29 final public function canRenderDocument(PhabricatorDocumentRef $ref) { 28 30 return $this->canRenderDocumentType($ref); 31 + } 32 + 33 + public function canConfigureEncoding(PhabricatorDocumentRef $ref) { 34 + return false; 35 + } 36 + 37 + public function canConfigureHighlighting(PhabricatorDocumentRef $ref) { 38 + return false; 39 + } 40 + 41 + final public function setEncodingConfiguration($config) { 42 + $this->encodingConfiguration = $config; 43 + return $this; 44 + } 45 + 46 + final public function getEncodingConfiguration() { 47 + return $this->encodingConfiguration; 48 + } 49 + 50 + final public function setHighlightingConfiguration($config) { 51 + $this->highlightingConfiguration = $config; 52 + return $this; 53 + } 54 + 55 + final public function getHighlightingConfiguration() { 56 + return $this->highlightingConfiguration; 29 57 } 30 58 31 59 public function shouldRenderAsync(PhabricatorDocumentRef $ref) {
+14 -3
src/applications/files/document/PhabricatorSourceDocumentEngine.php
··· 9 9 return pht('View as Source'); 10 10 } 11 11 12 + public function canConfigureHighlighting(PhabricatorDocumentRef $ref) { 13 + return true; 14 + } 15 + 12 16 protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) { 13 17 return 'fa-code'; 14 18 } ··· 20 24 protected function newDocumentContent(PhabricatorDocumentRef $ref) { 21 25 $content = $this->loadTextData($ref); 22 26 23 - $content = PhabricatorSyntaxHighlighter::highlightWithFilename( 24 - $ref->getName(), 25 - $content); 27 + $highlighting = $this->getHighlightingConfiguration(); 28 + if ($highlighting !== null) { 29 + $content = PhabricatorSyntaxHighlighter::highlightWithLanguage( 30 + $highlighting, 31 + $content); 32 + } else { 33 + $content = PhabricatorSyntaxHighlighter::highlightWithFilename( 34 + $ref->getName(), 35 + $content); 36 + } 26 37 27 38 return $this->newTextDocumentContent($content); 28 39 }
+58 -2
src/applications/files/document/PhabricatorTextDocumentEngine.php
··· 3 3 abstract class PhabricatorTextDocumentEngine 4 4 extends PhabricatorDocumentEngine { 5 5 6 + private $encodingMessage = null; 7 + 6 8 protected function canRenderDocumentType(PhabricatorDocumentRef $ref) { 7 9 return $ref->isProbablyText(); 8 10 } 9 11 12 + public function canConfigureEncoding(PhabricatorDocumentRef $ref) { 13 + return true; 14 + } 15 + 10 16 protected function newTextDocumentContent($content) { 11 17 $lines = phutil_split_lines($content); 12 18 ··· 14 20 ->setHighlights($this->getHighlightedLines()) 15 21 ->setLines($lines); 16 22 23 + $message = null; 24 + if ($this->encodingMessage !== null) { 25 + $message = $this->newMessage($this->encodingMessage); 26 + } 27 + 17 28 $container = phutil_tag( 18 29 'div', 19 30 array( 20 31 'class' => 'document-engine-text', 21 32 ), 22 - $view); 33 + array( 34 + $message, 35 + $view, 36 + )); 23 37 24 38 return $container; 25 39 } 26 40 27 41 protected function loadTextData(PhabricatorDocumentRef $ref) { 28 42 $content = $ref->loadData(); 29 - $content = phutil_utf8ize($content); 43 + 44 + $encoding = $this->getEncodingConfiguration(); 45 + if ($encoding !== null) { 46 + if (function_exists('mb_convert_encoding')) { 47 + $content = mb_convert_encoding($content, 'UTF-8', $encoding); 48 + $this->encodingMessage = pht( 49 + 'This document was converted from %s to UTF8 for display.', 50 + $encoding); 51 + } else { 52 + $this->encodingMessage = pht( 53 + 'Unable to perform text encoding conversion: mbstring extension '. 54 + 'is not available.'); 55 + } 56 + } else { 57 + if (!phutil_is_utf8($content)) { 58 + if (function_exists('mb_detect_encoding')) { 59 + $try_encodings = array( 60 + 'JIS' => pht('JIS'), 61 + 'EUC-JP' => pht('EUC-JP'), 62 + 'SJIS' => pht('Shift JIS'), 63 + 'ISO-8859-1' => pht('ISO-8859-1 (Latin 1)'), 64 + ); 65 + 66 + $guess = mb_detect_encoding($content, array_keys($try_encodings)); 67 + if ($guess) { 68 + $content = mb_convert_encoding($content, 'UTF-8', $guess); 69 + $this->encodingMessage = pht( 70 + 'This document is not UTF8. It was detected as %s and '. 71 + 'converted to UTF8 for display.', 72 + idx($try_encodings, $guess, $guess)); 73 + } 74 + } 75 + } 76 + } 77 + 78 + if (!phutil_is_utf8($content)) { 79 + $content = phutil_utf8ize($content); 80 + $this->encodingMessage = pht( 81 + 'This document is not UTF8 and its text encoding could not be '. 82 + 'detected automatically. Use "Change Text Encoding..." to choose '. 83 + 'an encoding.'); 84 + } 85 + 30 86 return $content; 31 87 } 32 88
+90 -9
webroot/rsrc/js/application/files/behavior-document-engine.js
··· 52 52 }); 53 53 } 54 54 55 + list.addItem( 56 + new JX.PHUIXActionView() 57 + .setDivider(true)); 58 + 59 + var encode_item = new JX.PHUIXActionView() 60 + .setName(data.encode.name) 61 + .setIcon(data.encode.icon); 62 + 63 + var onencode = JX.bind(null, function(data, e) { 64 + e.prevent(); 65 + 66 + if (encode_item.getDisabled()) { 67 + return; 68 + } 69 + 70 + new JX.Workflow(data.encode.uri, {encoding: data.encode.value}) 71 + .setHandler(function(r) { 72 + data.encode.value = r.encoding; 73 + onview(data); 74 + }) 75 + .start(); 76 + 77 + menu.close(); 78 + 79 + }, data); 80 + 81 + encode_item.setHandler(onencode); 82 + 83 + list.addItem(encode_item); 84 + 85 + var highlight_item = new JX.PHUIXActionView() 86 + .setName(data.highlight.name) 87 + .setIcon(data.highlight.icon); 88 + 89 + var onhighlight = JX.bind(null, function(data, e) { 90 + e.prevent(); 91 + 92 + if (highlight_item.getDisabled()) { 93 + return; 94 + } 95 + 96 + new JX.Workflow(data.highlight.uri, {highlight: data.highlight.value}) 97 + .setHandler(function(r) { 98 + data.highlight.value = r.highlight; 99 + onview(data); 100 + }) 101 + .start(); 102 + 103 + menu.close(); 104 + }, data); 105 + 106 + highlight_item.setHandler(onhighlight); 107 + 108 + list.addItem(highlight_item); 109 + 55 110 menu.setContent(list.getNode()); 56 111 57 112 menu.listen('open', function() { ··· 61 116 // Highlight the current rendering engine. 62 117 var is_selected = (engine.spec.viewKey == data.viewKey); 63 118 engine.view.setSelected(is_selected); 119 + 120 + if (is_selected) { 121 + encode_item.setDisabled(!engine.spec.canEncode); 122 + highlight_item.setDisabled(!engine.spec.canHighlight); 123 + } 64 124 } 65 125 }); 66 126 ··· 68 128 menu.open(); 69 129 } 70 130 131 + function add_params(uri, data) { 132 + uri = JX.$U(uri); 133 + 134 + if (data.highlight.value) { 135 + uri.setQueryParam('highlight', data.highlight.value); 136 + } 137 + 138 + if (data.encode.value) { 139 + uri.setQueryParam('encode', data.encode.value); 140 + } 141 + 142 + return uri.toString(); 143 + } 144 + 71 145 function onview(data, spec, immediate) { 146 + if (!spec) { 147 + for (var ii = 0; ii < data.views.length; ii++) { 148 + if (data.views[ii].viewKey == data.viewKey) { 149 + spec = data.views[ii]; 150 + break; 151 + } 152 + } 153 + } 154 + 72 155 data.sequence = (data.sequence || 0) + 1; 73 156 var handler = JX.bind(null, onrender, data, data.sequence); 74 157 75 158 data.viewKey = spec.viewKey; 76 159 77 - new JX.Request(spec.engineURI, handler) 160 + var uri = add_params(spec.engineURI, data); 161 + 162 + new JX.Request(uri, handler) 78 163 .send(); 79 164 80 165 if (data.loadingView) { ··· 93 178 94 179 // Replace the URI with the URI for the specific rendering the user 95 180 // has selected. 96 - JX.History.replace(spec.viewURI); 181 + 182 + var view_uri = add_params(spec.viewURI, data); 183 + JX.History.replace(view_uri); 97 184 } 98 185 } 99 186 ··· 134 221 if (config && config.renderControlID) { 135 222 var control = JX.$(config.renderControlID); 136 223 var data = JX.Stratcom.getData(control); 137 - 138 - for (var ii = 0; ii < data.views.length; ii++) { 139 - if (data.views[ii].viewKey == data.viewKey) { 140 - onview(data, data.views[ii], true); 141 - break; 142 - } 143 - } 224 + onview(data, null, true); 144 225 } 145 226 146 227 });
+4
webroot/rsrc/js/phuix/PHUIXActionView.js
··· 34 34 return this; 35 35 }, 36 36 37 + getDisabled: function() { 38 + return this._disabled; 39 + }, 40 + 37 41 setLabel: function(label) { 38 42 this._label = label; 39 43 JX.DOM.alterClass(