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 @@ -1724,6 +1724,7 @@ 'PHUIListItemView' => 'view/phui/PHUIListItemView.php', 'PHUIListView' => 'view/phui/PHUIListView.php', 'PHUIListViewTestCase' => 'view/layout/__tests__/PHUIListViewTestCase.php', + 'PHUINextPrevView' => 'view/phui/PHUINextPrevView.php', 'PHUIObjectBoxView' => 'view/phui/PHUIObjectBoxView.php', 'PHUIObjectItemListExample' => 'applications/uiexample/examples/PHUIObjectItemListExample.php', 'PHUIObjectItemListView' => 'view/phui/PHUIObjectItemListView.php', @@ -6730,6 +6731,7 @@ 'PHUIListItemView' => 'AphrontTagView', 'PHUIListView' => 'AphrontTagView', 'PHUIListViewTestCase' => 'PhabricatorTestCase', + 'PHUINextPrevView' => 'AphrontView', 'PHUIObjectBoxView' => 'AphrontTagView', 'PHUIObjectItemListExample' => 'PhabricatorUIExample', 'PHUIObjectItemListView' => 'AphrontTagView', diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -220,6 +220,26 @@ } $prop_list = phutil_tag_div('phui-document-view-pro-box', $prop_list); + $pager = new PHUINextPrevView(); + if ($request->getInt('v') != null) { + $pager->setOffset($request->getInt('v')); + } else { + // In Phriction, v=0 points to the last version + // We must handle this and set offset manually + // to prevent the NEXT button from appearing + $content = id(new PhrictionContent())->load($document->getContentID()); + $pager->setOffset($content->getVersion()); + } + $pager->setURI($request->getRequestURI(), 'v'); + + $next_prev_version = id(new PhrictionContent())->loadAllWhere( + 'documentID = %d ORDER BY version LIMIT %d,%d', + $document->getID(), + max(0, $pager->getOffset() - 1), + $pager->getPageSize() + 1); + + $next_prev_version = $pager->sliceResults($next_prev_version); + $page_content = id(new PHUIDocumentViewPro()) ->setHeader($header) ->setToc($toc) @@ -228,6 +248,7 @@ $version_note, $move_notice, $core_content, + $pager, )); return $this->newPage() diff --git a/src/view/phui/PHUINextPrevView.php b/src/view/phui/PHUINextPrevView.php new file mode 100644 --- /dev/null +++ b/src/view/phui/PHUINextPrevView.php @@ -0,0 +1,212 @@ +offset = max(0, $offset); + return $this; + } + + public function getOffset() { + return $this->offset; + } + + public function getPageSize() { + return $this->pageSize; + } + + public function setCount($count) { + $this->count = $count; + return $this; + } + + public function setHasMorePages($has_more) { + $this->hasMorePages = $has_more; + return $this; + } + + public function setURI(PhutilURI $uri, $paging_parameter) { + $this->uri = $uri; + $this->pagingParameter = $paging_parameter; + return $this; + } + + public function readFromRequest(AphrontRequest $request) { + $this->uri = $request->getRequestURI(); + $this->pagingParameter = 'offset'; + $this->offset = $request->getInt($this->pagingParameter); + return $this; + } + + public function willShowPagingControls() { + return $this->hasMorePages || $this->getOffset(); + } + + public function getHasMorePages() { + return $this->hasMorePages; + } + + private function computeCount() { + if ($this->count !== null) { + return $this->count; + } + return $this->getOffset() + + $this->getPageSize() + + ($this->hasMorePages ? 1 : 0); + } + + private function isExactCountKnown() { + return $this->count !== null; + } + + /** + * A common paging strategy is to select one extra record and use that to + * indicate that there's an additional page (this doesn't give you a + * complete page count but is often faster than counting the total number + * of items). This method will take a result array, slice it down to the + * page size if necessary, and call setHasMorePages() if there are more than + * one page of results. + * + * $results = queryfx_all( + * $conn, + * 'SELECT ... LIMIT %d, %d', + * $pager->getOffset(), + * $pager->getPageSize() + 1); + * $results = $pager->sliceResults($results); + * + * @param list Result array. + * @return list One page of results. + */ + public function sliceResults(array $results) { + if (count($results) > $this->getPageSize()) { + $results = array_slice($results, 0, $this->getPageSize(), true); + $this->setHasMorePages(true); + } + return $results; + } + + public function setEnableKeyboardShortcuts($enable) { + $this->enableKeyboardShortcuts = $enable; + return $this; + } + + public function render() { + if (!$this->uri) { + throw new PhutilInvalidStateException('setURI'); + } + + require_celerity_resource('phui-pager-css'); + + $page = (int)floor($this->getOffset() / $this->getPageSize()); + $last = ((int)ceil($this->computeCount() / $this->getPageSize())) - 1; + $near = $this->surroundingPages; + + $min = $page - $near; + $max = $page + $near; + + // Limit the window size to no larger than the number of available pages. + if ($max - $min > $last) { + $max = $min + $last; + if ($max == $min) { + return phutil_tag('div', array('class' => 'phui-pager-view'), ''); + } + } + + // Slide the window so it is entirely over displayable pages. + if ($min < 0) { + $max += 0 - $min; + $min += 0 - $min; + } + + if ($max > $last) { + $min -= $max - $last; + $max -= $max - $last; + } + + + // Build up a list of tuples which describe the + // links we'll display, then render them all at once. + + $links = array(); + + $prev_index = null; + $next_index = null; + + if ($page > 0) { + $links[] = array($page - 1, pht('Prev'), null); + $prev_index = $page - 1; + } + + if ($page < $last && $last > 0) { + $links[] = array($page + 1, pht('Next'), null); + $next_index = $page + 1; + } + + $base_uri = $this->uri; + $parameter = $this->pagingParameter; + + if ($this->enableKeyboardShortcuts) { + $pager_links = array(); + $pager_index = array( + 'prev' => $prev_index, + 'next' => $next_index, + ); + foreach ($pager_index as $key => $index) { + if ($index !== null) { + $display_index = $this->getDisplayIndex($index); + $pager_links[$key] = (string)$base_uri->alter( + $parameter, + $display_index); + } + } + Javelin::initBehavior('phabricator-keyboard-pager', $pager_links); + } + + // Convert tuples into rendered nodes. + $rendered_links = array(); + foreach ($links as $link) { + list($index, $label, $class) = $link; + $display_index = $this->getDisplayIndex($index); + $link = $base_uri->alter($parameter, $display_index); + $rendered_links[] = id(new PHUIButtonView()) + ->setTag('a') + ->setHref($link) + ->setColor(PHUIButtonView::GREY) + ->addClass('mml') + ->addClass($class) + ->setText($label); + } + + return phutil_tag( + 'div', + array( + 'class' => 'phui-pager-view', + ), + $rendered_links); + } + + private function getDisplayIndex($page_index) { + $page_size = $this->getPageSize(); + // Use a 1-based sequence for display so that the number in the URI is + // the same as the page number you're on. + if ($page_index == 0) { + // No need for the first page to say page=1. + $display_index = null; + } else { + $display_index = $page_index * $page_size; + } + return $display_index; + } + +}