diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -72,7 +72,7 @@ 'rsrc/css/application/differential/table-of-contents.css' => 'bba788b9', 'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b', 'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4', - 'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c', + 'rsrc/css/application/diffusion/diffusion-repository.css' => 'c0f77930', 'rsrc/css/application/diffusion/diffusion.css' => 'e46232d6', 'rsrc/css/application/feed/feed.css' => 'd8b6e3f8', 'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4', @@ -471,6 +471,7 @@ 'rsrc/js/core/behavior-badge-view.js' => '92cdd7b6', 'rsrc/js/core/behavior-bulk-editor.js' => 'aa6d2308', 'rsrc/js/core/behavior-choose-control.js' => '04f8a1e3', + 'rsrc/js/core/behavior-collapsiblejx.js' => '7072263b', 'rsrc/js/core/behavior-copy.js' => 'cf32921f', 'rsrc/js/core/behavior-detect-timezone.js' => '78bc5d94', 'rsrc/js/core/behavior-device.js' => 'ac2b1e01', @@ -570,7 +571,7 @@ 'diffusion-css' => 'e46232d6', 'diffusion-icons-css' => '23b31a1b', 'diffusion-readme-css' => 'b68a76e4', - 'diffusion-repository-css' => 'b89e8c6c', + 'diffusion-repository-css' => 'c0f77930', 'diviner-shared-css' => '4bd263b0', 'font-fontawesome' => '3883938a', 'font-lato' => '23631304', @@ -600,6 +601,7 @@ 'javelin-behavior-bulk-job-reload' => '3829a3cf', 'javelin-behavior-calendar-month-view' => '158c64e0', 'javelin-behavior-choose-control' => '04f8a1e3', + 'javelin-behavior-collapsiblejx' => '7072263b', 'javelin-behavior-comment-actions' => '4dffaeb2', 'javelin-behavior-config-reorder-fields' => '2539f834', 'javelin-behavior-conpherence-menu' => '8c2ed2bf', @@ -1587,6 +1589,10 @@ 'javelin-workflow', 'javelin-dom', ), + '7072263b' => array( + 'javelin-behavior', + 'javelin-dom', + ), '727a5a61' => array( 'phuix-icon-view', ), diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -190,6 +190,10 @@ ->setRequest($request) ->setDiffusionRequest($drequest); + if ($engine instanceof PHUIRemarkupView) { + $engine->setGenerateTableOfContents(true); + } + $corpus = $engine->newDocumentView($ref); $this->corpusButtons[] = $this->renderFileButton(); diff --git a/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php b/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php --- a/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php +++ b/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php @@ -36,6 +36,7 @@ $content = phutil_utf8ize($content); $remarkup = new PHUIRemarkupView($viewer, $content); + $remarkup->setGenerateTableOfContents(true); $container = phutil_tag( 'div', diff --git a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php --- a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php +++ b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php @@ -208,12 +208,12 @@ $this->activeEngine = $engine; $encode_setting = $request->getStr('encode'); - if (strlen($encode_setting)) { + if (phutil_nonempty_string($encode_setting)) { $engine->setEncodingConfiguration($encode_setting); } $highlight_setting = $request->getStr('highlight'); - if (strlen($highlight_setting)) { + if (phutil_nonempty_string($highlight_setting)) { $engine->setHighlightingConfiguration($highlight_setting); } 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 @@ -459,6 +459,10 @@ $engine = self::newMarkupEngine(array()); $engine->setConfig('pygments.enabled', false); break; + case 'remarkupField': + $engine = self::newMarkupEngine(array()); + $engine->setConfig('header.generate-toc', true); + break; default: throw new Exception(pht('Unknown engine ruleset: %s!', $ruleset)); } diff --git a/src/infrastructure/markup/PhabricatorMarkupOneOff.php b/src/infrastructure/markup/PhabricatorMarkupOneOff.php --- a/src/infrastructure/markup/PhabricatorMarkupOneOff.php +++ b/src/infrastructure/markup/PhabricatorMarkupOneOff.php @@ -16,6 +16,7 @@ private $generateTableOfContents; private $tableOfContents; + private $collapsibleIndexInstance = 0; public function setEngineRuleset($engine_ruleset) { $this->engineRuleset = $engine_ruleset; @@ -97,7 +98,11 @@ if ($this->engineRuleset) { return PhabricatorMarkupEngine::getEngine($this->engineRuleset); } else if ($this->preserveLinebreaks) { - return PhabricatorMarkupEngine::getEngine(); + $engine = PhabricatorMarkupEngine::getEngine(); + if ($this->generateTableOfContents) { + $engine->setConfig('header.generate-toc', true); + } + return $engine; } else { return PhabricatorMarkupEngine::getEngine('nolinebreaks'); } @@ -115,10 +120,11 @@ if ($this->getGenerateTableOfContents()) { $toc = PhutilRemarkupHeaderBlockRule::renderTableOfContents($engine); $this->tableOfContents = $toc; + $collapsible_index = $this->buildCollapsibleIndex($toc); + $output = phutil_implode_html("\n", array($collapsible_index, $output)); } require_celerity_resource('phabricator-remarkup-css'); - return phutil_tag( 'div', array( @@ -127,6 +133,59 @@ $output); } + /** + * Build a collapsible table of contents display consisting of a + * screen-wide button to toggle the display, and the table of + * contents itself. + * @param PhutilSafeHTML $tocThe table of contents HTML. + * @return PhutilSafeHTML Div containing a button and the toc. + */ + private function buildCollapsibleIndex($toc) { + ++$this->collapsibleIndexInstance; + $instance = $this->collapsibleIndexInstance; + $config = array( + 'container_id' => "toc-index-div-$instance", + 'toggle_button_id' => "toc-index-button-$instance", + 'button_icon' => array( + 'id' => "toc-index-button-icon-$instance", + 'open_class' => 'fa-folder', + 'close_class' => 'fa-folder-open', + ), + 'button_i18n' => array( + 'id' => "toc-index-button-text-$instance", + 'open_text' => pht('Open Index'), + 'close_text' => pht('Close Index'), + ), + ); + + $button_icon = id(new PHUIIconView()) + ->setID($config['button_icon']['id']) + ->setIcon($config['button_icon']['open_class']); + + $button_text_span = phutil_tag( + 'span', + array('class' => 'collapsible', 'id' => $config['button_i18n']['id']), + $config['button_i18n']['open_text']); + + $button = phutil_tag( + 'button', + array( + 'type' => 'button', + 'class' => 'collapsible', + 'id' => $config['toggle_button_id'], + ), + phutil_implode_html("\n", array($button_icon, $button_text_span))); + + $toc_div = phutil_tag( + 'div', + array('class' => 'collapsible-content', 'id' => $config['container_id']), + $toc); + + require_celerity_resource('font-fontawesome'); + Javelin::initBehavior('collapsiblejx', $config); + return phutil_implode_html("\n", array($button, $toc_div)); + } + public function shouldUseMarkupCache($field) { if ($this->getDisableCache()) { return false; 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 @@ -134,7 +134,7 @@ list($level, $name) = $info; while ($depth < $level) { - $toc[] = hsprintf('
phutil_fake_test_block_interpreter {{{ content }}}
~~~~~~~~~~ Content: (content) Argv: (foo=bar) @@ -53,6 +51,4 @@ Content: (content) Argv: (x=y) - - -(No interpreter found: phutil_fake_test_block_interpreter) +phutil_fake_test_block_interpreter {{{ content }}} diff --git a/src/infrastructure/markup/view/PHUIRemarkupView.php b/src/infrastructure/markup/view/PHUIRemarkupView.php --- a/src/infrastructure/markup/view/PHUIRemarkupView.php +++ b/src/infrastructure/markup/view/PHUIRemarkupView.php @@ -85,7 +85,7 @@ $content = PhabricatorMarkupEngine::renderOneObject( $oneoff, - 'default', + 'remarkupField', $viewer, $context); diff --git a/webroot/rsrc/css/application/diffusion/diffusion-repository.css b/webroot/rsrc/css/application/diffusion/diffusion-repository.css --- a/webroot/rsrc/css/application/diffusion/diffusion-repository.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-repository.css @@ -11,3 +11,31 @@ .diffusion-panel-header-view.phui-header-shell { padding: 8px 4px 8px 16px; } + +button.collapsible { + background-color: {$blue.button.color}; + border: none; + color: #FFFFFF; + cursor: pointer; + font-size: {$biggerfontsize}; + outline: none; + padding: 18px; + text-align: left; + width: 100%; +} + +button.collapsible:hover { + background-color: {$blue.button.hover}; +} + +.collapsible-content { + background-color: {$lightgreybackground}; + display: none; + margin-top: 10px; + overflow: hidden; + padding: 0 18px; +} + +.collapsible-content.collapsible-content-open { + display: block; +} diff --git a/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner b/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner --- a/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner +++ b/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner @@ -38,7 +38,7 @@ lang=php $config = array('hogName' => 'Ethel'); - JavelinHelper::initBehaviors('win-a-hog', $config); + Javelin::initBehavior('win-a-hog', $config); Regardless, this will alert the user that they've won a hog (named Ethel, which is a good name for a hog) when they load the page. diff --git a/webroot/rsrc/js/core/behavior-collapsiblejx.js b/webroot/rsrc/js/core/behavior-collapsiblejx.js new file mode 100644 --- /dev/null +++ b/webroot/rsrc/js/core/behavior-collapsiblejx.js @@ -0,0 +1,54 @@ +/** + * @provides javelin-behavior-collapsiblejx + * @requires javelin-behavior + * javelin-dom + * + * Given a collapsible div and a button to open and close it, call with: + * + * Javelin::initBehavior( + * 'collapsiblejx', + * array( + * 'container_id' => $theCollapsibleDivId, + * 'toggle_button_id' => $theButtonId, + * 'button_icon' => array( + * 'id' => $theButtonIconId, + * 'open_class' => 'fa-folder', + * 'close_class' => 'fa-folder-open', + * ), + * 'button_i18n' => array( + * 'id' => $theButtonTextId, + * 'open_text' => pht('Open Whatever This Is'), + * 'close_text' => pht('Close Whatever This Is'), + * ), + * ) + * ); + * + * button_icon and button_i18n are both optional. If you want either or + * none, that's fine. + * + */ + +JX.behavior('collapsiblejx', function(config, statics) { + var open = false; + JX.Stratcom.listen( + 'click', + 'id:' + config.toggle_button_id, + function(e) { + var containerNode = JX.$(config.container_id); + open = !open; + JX.DOM.alterClass(containerNode, 'collapsible-content-open', open); + + // If element is open, we want a close button, and vice-versa. + if ('button_icon' in config) { + var button = JX.$(config.button_icon.id); + JX.DOM.alterClass(button, config.button_icon.open_class, !open); + JX.DOM.alterClass(button, config.button_icon.close_class, open); + } + + if ('button_i18n' in config) { + var btn_text = config.button_i18n[open ? 'close_text' : 'open_text']; + JX.$(config.button_i18n.id).innerText = btn_text; + } + } + ); +});