src/site/controllers/GuidePageController.php (245 lines of code) (raw):

<?hh // strict /* * Copyright (c) 2004-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ use namespace Facebook\XHP\Core as x; use namespace HHVM\UserDocumentation\ui; use type Facebook\XHP\HTML\{div, span}; use type HHVM\UserDocumentation\{ Guides, GuidesIndex, GuidesNavData, GuidesProduct, HTMLFileRenderable, NavDataNode, PaginationDataNode, UIGlyphIcon, URLBuilder, }; use function HHVM\UserDocumentation\type_alias_structure; use namespace Facebook\{TypeAssert, TypeSpec}; use namespace HH\Lib\{C, Dict}; final class GuidePageController extends WebPageController { use GuidePageControllerParametersTrait; <<__Override>> public static function getUriPattern(): UriPattern { return (new UriPattern()) ->literal('/') ->guidesProduct('Product') ->literal('/') ->string('Guide') ->literal('/') ->string('Page'); } <<__Memoize>> private function getProduct(): GuidesProduct { return $this->getParameters()['Product']; } <<__Memoize>> private function getGuide(): string { return $this->getParameters()['Guide']; } <<__Memoize>> private function getPage(): string { return $this->getParameters()['Page']; } <<__Override>> protected async function beforeResponseAsync(): Awaitable<void> { $params = $this->getParameters(); $redirect_to = Guides::getGuidePageRedirects( $params['Product'], )[$params['Guide']][$params['Page']] ?? Guides::getGuideRedirects($params['Product'])[$params['Guide']] ?? null; if ($redirect_to === null) { return; } list($guide, $page) = $redirect_to; $page = $page ?? C\firstx(GuidesIndex::getPages($params['Product'], $guide)); throw new RedirectException(GuidePageControllerURIBuilder::getPath(shape( 'Product' => $params['Product'], 'Guide' => $guide, 'Page' => $page, ))); } <<__Override>> public async function getTitleAsync(): Awaitable<string> { list($product, $guide, $page) = tuple( $this->getProduct(), $this->getGuide(), $this->getPage(), ); return self::invariantTo404( () ==> Guides::normalizeName($product, $guide, $page), ); } <<__Override>> protected async function getBodyAsync(): Awaitable<x\node> { return <div class="guidePageWrapper"> {$this->getInnerContent()} {$this->getPaginationLinks()} {$this->getFeedbackForm()} </div>; } protected function getPaginationLinks(): x\node { list($product, $guide, $page) = tuple( $this->getProduct(), GuidesNavData::pathToName($this->getGuide()), GuidesNavData::pathToName($this->getPage()), ); $paging = <div class="paginationLinks" />; $nav_data = GuidesNavData::getNavData()[$product]['children'] |> TypeSpec\dict( TypeSpec\string(), TypeSpec\__Private\from_type_structure( type_alias_structure(NavDataNode::class), ), )->assertType($$); $paging->appendChild( $this->getPaginationLink($nav_data, $guide, $page, false), ); $paging->appendChild( $this->getPaginationLink($nav_data, $guide, $page, true), ); return $paging; } protected function getPaginationLink( dict<string, NavDataNode> $guides, string $guide, string $page, bool $next = false, ): x\node { $adjacent_page = $this->getAdjacentPage($guides, $guide, $page, $next); if ($adjacent_page === null) { return <x:frag />; } $page = $adjacent_page['page']; $guide = $adjacent_page['guide']; $guide_title = $guide[0]; $page_title = <x:frag />; if ($guide_title !== $page[0]) { $guide_title .= ':'; $page_title = <span class="paginationPageTitle"> {$page[0]} </span>; } $class = "paginationLink "; $class = $class.($next ? "next" : "previous"); if ($next) { $align = 'right'; $glyph = UIGlyphIcon::RIGHT; } else { $align = 'left'; $glyph = UIGlyphIcon::LEFT; } return <div class={$class}> <ui:button align={$align} href={$page[1]['urlPath']} glyph={$glyph} size="medium"> {$page_title} <span class="paginationGuideTitle"> {$guide_title} </span> </ui:button> </div>; } protected function getAdjacentPage( dict<string, NavDataNode> $guides, string $guide, string $page, bool $next = false, ): ?PaginationDataNode { $sibling_pages = $guides[$guide]['children']; $sibling_pages = $next ? array_reverse($sibling_pages) : $sibling_pages; $adjacent_page = null; // Gets last page if next = true, first page if previous (next = false) $head_page = C\first_key($sibling_pages); if ($page === $head_page) { $adj = $this->getAdjacentGuide($guides, $guide, $next); if ($adj !== null) { list($adjacent_guide, $adjacent_guide_data) = $adj; $guide_pages = $adjacent_guide_data['children']; $guide_pages = $next ? $guide_pages : array_reverse($guide_pages); $adjacent_page = shape( 'page' => tuple(C\first_keyx($guide_pages), C\firstx($guide_pages)), 'guide' => tuple($adjacent_guide, $adjacent_guide_data), ); } } else { foreach ($sibling_pages as $sibling => $sibling_data) { if ($sibling === $page) { break; } $adjacent_page = shape( 'page' => tuple($sibling, $sibling_data), 'guide' => tuple($guide, $guides[$guide]), ); } } return $adjacent_page; } protected function getAdjacentGuide( dict<string, NavDataNode> $guides, string $current_guide, bool $next = false, ): ?(string, NavDataNode) { $adj_guide = null; $guides = $next ? array_reverse($guides) : $guides; foreach ($guides as $guide => $data) { if ($guide === $current_guide) { break; } $adj_guide = tuple($guide, $data); } return $adj_guide; } protected function getFeedbackForm(): x\node { $feedback = <ui:feedback/>; return $feedback; } <<__Override>> protected function getBreadcrumbs(): vec<(string, ?string)> { $product = $this->getProduct(); $product_url = URLBuilder::getPathForProductGuides($product); $guide = $this->getGuide(); $page = Guides::normalizePart($this->getPage()); return vec[ tuple($product, $product_url), tuple('Learn', $product_url), tuple( Guides::normalizePart($guide), URLBuilder::getPathForGuide($product, $guide), ), tuple($page, null), ]; } <<__Override>> protected function getSideNav(): x\node { $ts = type_alias_structure(NavDataNode::class); $data = Dict\map( GuidesNavData::getNavData()[$this->getProduct()]['children'], $child ==> TypeAssert\matches_type_structure($ts, $child), ); return ( <ui:navbar data={$data} activePath={vec[ GuidesNavData::pathToName($this->getGuide()), GuidesNavData::pathToName($this->getPage()), ]} /> ); } protected function getInnerContent(): x\node { return self::invariantTo404(() ==> { $path = GuidesIndex::getFileForPage( $this->getProduct(), $this->getGuide(), $this->getPage(), ); return <div class="innerContent">{new HTMLFileRenderable($path)}</div>; }); } }