EBernhardson (WMF) has submitted this change and it was merged.

Change subject: Paging for topics on TopicList objects.
......................................................................


Paging for topics on TopicList objects.

Change-Id: Id94af9cf71c4ca0fd89bce7fc58c3bb756742777
---
M Flow.i18n.php
M Flow.php
M includes/Block/Block.php
M includes/Block/Summary.php
M includes/Block/Topic.php
M includes/Block/TopicList.php
M includes/Data/ObjectManager.php
A includes/Data/Pager.php
M includes/Templating.php
M includes/api/ApiQueryFlow.php
M modules/base/ext.flow.base.js
M modules/discussion/base.css
A modules/discussion/paging.js
M templates/topiclist.html.php
14 files changed, 612 insertions(+), 62 deletions(-)

Approvals:
  EBernhardson (WMF): Verified; Looks good to me, approved



diff --git a/Flow.i18n.php b/Flow.i18n.php
index d162c47..00ec9e6 100644
--- a/Flow.i18n.php
+++ b/Flow.i18n.php
@@ -67,6 +67,9 @@
 
        'flow-comment-restored' => 'Restored comment',
        'flow-comment-deleted' => 'Deleted comment',
+
+       'flow-paging-rev' => 'More recent topics',
+       'flow-paging-fwd' => 'Older topics',
 );
 
 /** Message documentation (Message documentation)
@@ -165,6 +168,8 @@
 
 See also:
 * {{msg-mw|Flow-comment-restored}}',
+       'flow-paging-rev' => 'Label for paging link that shows more recently 
modified topics',
+       'flow-paging-fwd' => 'Label for paging link that shows less recently 
modified topics',
 );
 
 /** Asturian (asturianu)
diff --git a/Flow.php b/Flow.php
index 6eb6f22..3279e35 100755
--- a/Flow.php
+++ b/Flow.php
@@ -88,6 +88,8 @@
 $wgAutoloadClasses['Flow\Data\RootPostLoader'] = $dir . 
'includes/Data/RootPostLoader.php';
 $wgAutoloadClasses['Flow\Data\MultiDimArray'] = $dir . 
'includes/Data/MultiDimArray.php';
 $wgAutoloadClasses['Flow\Data\ResultDuplicator'] = $dir . 
'includes/Data/MultiDimArray.php';
+$wgAutoloadClasses['Flow\Data\Pager'] = $dir . 'includes/Data/Pager.php';
+$wgAutoloadClasses['Flow\Data\PagerPage'] = $dir . 
'includes/Data/PagerPage.php';
 
 // database interaction for singular models
 $wgAutoloadClasses['Flow\Data\PostRevisionStorage'] = $dir . 
'includes/Data/RevisionStorage.php';
@@ -150,6 +152,7 @@
                        'discussion/ui-functions.js',
                        'discussion/ui.js',
                        'discussion/forms.js',
+                       'discussion/paging.js',
                        'discussion/init.js',
                ),
                'dependencies' => array(
@@ -166,6 +169,8 @@
                        'flow-error-external-multi',
                        'flow-edit-title-submit',
                        'flow-edit-post-submit',
+                       'flow-paging-fwd',
+                       'flow-paging-rev',
                ),
        ),
 );
@@ -195,6 +200,8 @@
 // When visiting the flow for an article but not specifying what type of 
workflow should be viewed,
 // use this workflow
 $wgFlowDefaultWorkflow = 'discussion';
+$wgFlowDefaultLimit = 5;
+$wgFlowMaxLimit = 50;
 
 $wgFlowUseParsoid = false;
 $wgFlowParsoidURL = 'http://localhost:8000';
diff --git a/includes/Block/Block.php b/includes/Block/Block.php
index 2053f4e..168ee92 100644
--- a/includes/Block/Block.php
+++ b/includes/Block/Block.php
@@ -28,6 +28,12 @@
        function render( Templating $templating, array $options );
 
        /**
+        * Render the API output of this Block.
+        * Templating is provided for convenience
+        */
+       function renderAPI( Templating $templating, array $options );
+
+       /**
         * @return string Unique name among all blocks on an object
         */
        function getName();
@@ -51,7 +57,7 @@
 
        abstract protected function validate();
        abstract public function render( Templating $templating, array $options 
);
-       abstract public function renderAPI( array $options );
+       abstract public function renderAPI( Templating $templating, array 
$options );
        abstract public function commit();
 
        public function init( $action, $user ) {
diff --git a/includes/Block/Summary.php b/includes/Block/Summary.php
index 41fb3de..4f68647 100644
--- a/includes/Block/Summary.php
+++ b/includes/Block/Summary.php
@@ -92,7 +92,7 @@
                ) );
        }
 
-       public function renderAPI( array $options ) {
+       public function renderAPI( Templating $templating, array $options ) {
                $output = array();
                $output['type'] = 'summary';
 
diff --git a/includes/Block/Topic.php b/includes/Block/Topic.php
index 8420c8b..82edc2f 100644
--- a/includes/Block/Topic.php
+++ b/includes/Block/Topic.php
@@ -298,7 +298,7 @@
                ), $return );
        }
 
-       public function renderAPI( array $options ) {
+       public function renderAPI( Templating $templating, array $options ) {
                if ( isset( $options['postId'] ) ) {
                        $rootPost = $this->loadRootPost();
                        $post = $rootPost->findDescendant( $options['postId'] );
@@ -307,13 +307,13 @@
                                throw new MWException( "Requested post could 
not be found" );
                        }
 
-                       return array( $this->renderPostAPI( $post, $options ) );
+                       return array( $this->renderPostAPI( $templating, $post, 
$options ) );
                } else {
-                       return $this->renderTopicAPI( $options );
+                       return $this->renderTopicAPI( $templating, $options );
                }
        }
 
-       public function renderTopicAPI ( array $options ) {
+       public function renderTopicAPI ( Templating $templating, array $options 
) {
                $output = array();
                $rootPost = $this->loadRootPost();
                $topic = $this->workflow;
@@ -341,14 +341,18 @@
                        }
                }
 
+               if ( isset( $options['render'] ) ) {
+                       $output['rendered'] = $templating->renderTopic( 
$rootPost, $this, true );
+               }
+
                foreach( $rootPost->getChildren() as $child ) {
-                       $output[] = $this->renderPostAPI( $child, $options );
+                       $output[] = $this->renderPostAPI( $templating, $child, 
$options );
                }
 
                return $output;
        }
 
-       protected function renderPostAPI( PostRevision $post, array $options ) {
+       protected function renderPostAPI( Templating $templating, PostRevision 
$post, array $options ) {
                $output = array();
 
                $output['post-id'] = $post->getPostId()->getHex();
@@ -366,7 +370,7 @@
                        $children = array( '_element' => 'post' );
 
                        foreach( $post->getChildren() as $child ) {
-                               $children[] = $this->renderPostAPI( $child, 
$options );
+                               $children[] = $this->renderPostAPI( 
$templating, $child, $options );
                        }
 
                        if ( count($children) > 1 ) {
diff --git a/includes/Block/TopicList.php b/includes/Block/TopicList.php
index 142493f..aa2c563 100644
--- a/includes/Block/TopicList.php
+++ b/includes/Block/TopicList.php
@@ -2,8 +2,10 @@
 
 namespace Flow\Block;
 
+use Flow\Data\Pager;
 use Flow\Model\PostRevision;
 use Flow\Model\TopicListEntry;
+use Flow\Model\UUID;
 use Flow\Model\Workflow;
 use Flow\Data\ManagerGroup;
 use Flow\Data\ObjectManager;
@@ -64,9 +66,9 @@
                $output = array(
                        'created-topic-id' => $topicWorkflow->getId(),
                        'created-post-id' => $firstPost->getRevisionId(),
-                       'render-function' => function($templating) use 
($topicWorkflow, $firstPost, $topicPost, $storage, $user) {
+                       'render-function' => function( $templating ) use ( 
$topicWorkflow, $firstPost, $topicPost, $storage, $user ) {
                                $block = new TopicBlock( $topicWorkflow, 
$storage, $topicPost );
-                               return $templating->renderTopic( $topicPost, 
$block, $user );
+                               return $templating->renderTopic( $topicPost, 
$block, true );
                        },
                );
 
@@ -75,42 +77,102 @@
 
        public function render( Templating $templating, array $options ) {
                $templating->getOutput()->addModules( array( 
'ext.flow.discussion' ) );
-               $templating->render( "flow:topiclist.html.php", array(
-                       'topicList' => $this,
-                       'topics' => $this->getTopics(),
-                       'user' => $this->user,
-               ) );
+
+               if ( $this->workflow->isNew() ) {
+                       $templating->render( "flow:topiclist.html.php", array(
+                               'block' => $this,
+                               'topics' => array(),
+                               'user' => $this->user,
+                               'page' => false,
+                       ) );
+               } else {
+                       $findOptions = $this->getFindOptions( $options );
+                       $page = $this->getPage( $findOptions );
+                       $topics = $this->getTopics( $page );
+
+                       $templating->render( "flow:topiclist.html.php", array(
+                               'block' => $this,
+                               'topics' => $topics,
+                               'user' => $this->user,
+                               'page' => $page,
+                       ) );
+               }
        }
 
-       public function renderAPI( array $options ) {
+       public function renderAPI( Templating $templating, array $options ) {
                $output = array( '_element' => 'topic' );
-               $topics = $this->getTopics();
+               if ( ! $this->workflow->isNew() ) {
+                       $findOptions = $this->getFindOptions( $options + array( 
'api' => true ) );
+                       $page = $this->getPage( $findOptions );
+                       $topics = $this->getTopics( $page );
 
-               foreach( $topics as $topic ) {
-                       $output[] = $topic->renderAPI( $options );
+                       foreach( $topics as $topic ) {
+                               $output[] = $topic->renderAPI( $templating, 
$options );
+                       }
+
+                       $output['paging'] = $page->getPagingLinks();
                }
 
                return $output;
-       }
-
-       protected function getTopics() {
-               // New workflows cant have content yet
-               if ( $this->workflow->isNew() ) {
-                       return array();
-               } else {
-                       return $this->loadAllRelatedTopics();
-               }
        }
 
        public function getName() {
                return 'topic_list';
        }
 
-       protected function loadAllRelatedTopics() {
-               $found = $this->storage->find( 'TopicListEntry', array(
-                       'topic_list_id' => $this->workflow->getId(),
-               ) );
-               if ( !$found ) {
+       protected function getLimit( $options ) {
+               global $wgFlowDefaultLimit, $wgFlowMaxLimit;
+               $limit = $wgFlowDefaultLimit;
+               if ( isset( $options['limit'] ) ) {
+                       $requestedLimit = intval( $options['limit'] );
+                       if ( $requestedLimit > 0 && $requestedLimit < 
$wgFlowMaxLimit ) {
+                               $limit = $requestedLimit;
+                       }
+               }
+
+               return $limit;
+       }
+
+       protected function getFindOptions( $requestOptions ) {
+               global $wgFlowDefaultLimit, $wgFlowMaxLimit;
+               $findOptions = array();
+
+               // Compute offset/limit
+               $limit = $this->getLimit( $requestOptions );
+
+               if ( isset( $requestOptions['offset-id'] ) ) {
+                       $findOptions['pager-offset'] = UUID::create( 
$requestOptions['offset-id'] );
+               } elseif ( isset( $requestOptions['offset'] ) ) {
+                       $findOptions['pager-offset'] = intval( 
$requestOptions['offset'] );
+               }
+
+               if ( isset( $requestOptions['offset-dir'] ) ) {
+                       $findOptions['pager-dir'] = 
$requestOptions['offset-dir'];
+               }
+
+               if ( isset( $requestOptions['api'] ) ) {
+                       $findOptions['offset-elastic'] = false;
+               }
+
+               $findOptions['pager-limit'] = $limit;
+
+               return $findOptions;
+       }
+
+       protected function getPage( $findOptions ) {
+               $pager = new Pager(
+                       $this->storage->getStorage( 'TopicListEntry' ),
+                       array( 'topic_list_id' => $this->workflow->getId() ),
+                       $findOptions
+               );
+
+               return $pager->getPage();
+       }
+
+       protected function getTopics( $page ) {
+               $found = $page->getResults();
+
+               if ( ! count( $found ) ) {
                        return array();
                }
 
diff --git a/includes/Data/ObjectManager.php b/includes/Data/ObjectManager.php
index b460160..50fc9a4 100644
--- a/includes/Data/ObjectManager.php
+++ b/includes/Data/ObjectManager.php
@@ -79,10 +79,10 @@
 // An Index is just a store that receives updates via handler.
 // backing store's can be passed via constructor
 interface Index extends LifecycleHandler {
-       // Indexes accept no query options
+       /// Indexes accept no query options
        function find( array $keys );
 
-       // Indexes accept no query options
+       /// Indexes accept no query options
        function findMulti( array $queries );
 
        // Maximum number of items in a single index value
@@ -193,24 +193,25 @@
                if ( isset( $options['sort'] ) && !is_array( $options['sort'] ) 
) {
                        $options['sort'] = ObjectManager::makeArray( 
$options['sort'] );
                }
-               if ( isset( $options['limit'] ) ) {
-                       $limit = $options['limit'];
-                       $offset = isset( $options['offset'] ) ? 
$options['offset'] : 0;
-               } else {
-                       $limit = false;
-               }
 
-               $res = $this->getIndexFor( $keys, $options )->findMulti( 
$queries );
+               $index = $this->getIndexFor( $keys, $options );
+               $res = $index->findMulti( $queries );
+
                if ( $res === null ) {
                        return null;
                }
+
                $retval = array();
                foreach ( $res as $i => $rows ) {
-                       if ( $limit ) {
-                               $rows = array_slice( $rows, $offset, $limit, 
true );
-                       }
-                       foreach ( $rows as $j => $row ) {
-                               $retval[$i][$j] = $this->load( $row );
+                       list( $startPos, $limit ) = $this->getOffsetLimit( 
$rows, $index, $options );
+                       $keys = array_keys( $rows );
+                       for(
+                               $k = $startPos;
+                               $k < $startPos + $limit && $k < count( $keys );
+                               ++$k
+                       ) {
+                               $j = $keys[$k];
+                               $retval[$i][$j] = $this->load( $rows[$j] );
                        }
                }
                return $retval;
@@ -251,6 +252,67 @@
                return $retval;
        }
 
+       protected function getOffsetLimit( $rows, $index, $options ) {
+               $limit = isset( $options['limit'] ) ? $options['limit'] : 
$index->getLimit();
+
+               if ( ! isset( $options['offset-key'] ) ) {
+                       $offset = isset( $options['offset'] ) ? 
$options['offset'] : 0;
+                       return array( $offset, $limit );
+               }
+
+               $offsetKey = $options['offset-key'];
+               if ( $offsetKey instanceof UUID ) {
+                       $offsetKey = $offsetKey->getBinary();
+               }
+
+               $dir = 'fwd';
+               if (
+                       isset( $options['offset-dir'] ) &&
+                       $options['offset-dir'] === 'rev'
+               ) {
+                       $dir = 'rev';
+               }
+
+               $offset = $this->getOffsetFromKey( $rows, $offsetKey, $index );
+
+               if ( $dir === 'fwd' ) {
+                       $startPos = $offset + 1;
+               } elseif ( $dir === 'rev' ) {
+                       $startPos = $offset - $limit;
+
+                       if ( $startPos < 0 ) {
+                               if (
+                                       isset( $options['offset-elastic'] ) &&
+                                       $options['offset-elastic'] === false
+                               ) {
+                                       // If non-elastic, then reduce the 
number of items shown commeasurately
+                                       $limit += $startPos;
+                               }
+                               $startPos = 0;
+                       }
+               }
+
+               return array( $startPos, $limit );
+       }
+
+       protected function getOffsetFromKey( $rows, $offsetKey, $index ) {
+               $offset = false;
+               for( $rowIndex = 0; $rowIndex < count( $rows ); ++$rowIndex ) {
+                       $row = $rows[$rowIndex];
+                       $comparisonValue = $index->compareRowToOffset( $row, 
$offsetKey );
+                       if ( $comparisonValue <= 0 ) {
+                               $offset = $rowIndex;
+                               break;
+                       }
+               }
+
+               if ( $offset === false ) {
+                       throw new \MWException( "Unable to find specified 
offset in query results" );
+               }
+
+               return $offset;
+       }
+
        public function clear() {
                // nop, we dont store anything
        }
@@ -270,7 +332,7 @@
                }
        }
 
-       protected function getIndexFor( array $keys, array $options = array() ) 
{
+       public function getIndexFor( array $keys, array $options = array() ) {
                sort( $keys );
                $current = null;
                foreach ( $this->indexes as $index ) {
@@ -431,6 +493,21 @@
                }
 
                return $split;
+       }
+
+       public function serializeOffset( $object, $sortFields ) {
+               $offsetFields = array();
+               $row = $this->mapper->toStorageRow( $object );
+               foreach( $sortFields as $field ) {
+                       $value = $row[$field];
+
+                       if ( strlen($value) == 16 && preg_match( '/_id$/', 
$field ) ) {
+                               $value = UUID::create( $value )->getHex();
+                       }
+                       $offsetFields[] = $value;
+               }
+
+               return implode( '|', $offsetFields );
        }
 
        public function multiPut( array $objects ) {
@@ -671,6 +748,34 @@
                return true;
        }
 
+       public function getSort() {
+               return isset( $this->options['sort'] ) ? $this->options['sort'] 
: false;
+       }
+
+       public function compareRowToOffset( $row, $offset ) {
+               $sortFields = $this->getSort();
+               $splitOffset = explode( '|', $offset );
+               $fieldIndex = 0;
+
+               if ( $sortFields === false ) {
+                       throw new MWException( "This Index implementation does 
not support key offsets" );
+               }
+
+               foreach( $sortFields as $field ) {
+                       $valueInRow = $row[$field];
+                       $valueInOffset = $splitOffset[$fieldIndex];
+
+                       if ( $valueInRow > $valueInOffset ) {
+                               return 1;
+                       } elseif ( $valueInRow < $valueInOffset ) {
+                               return -1;
+                       }
+                       ++$fieldIndex;
+               }
+
+               return 0;
+       }
+
        public function onAfterInsert( $object, array $new ) {
                $indexed = ObjectManager::splitFromRow( $new , $this->indexed );
                // is un-indexable a bail-worthy occasion? Probably not but 
makes debugging easier
diff --git a/includes/Data/Pager.php b/includes/Data/Pager.php
new file mode 100644
index 0000000..09730fb
--- /dev/null
+++ b/includes/Data/Pager.php
@@ -0,0 +1,175 @@
+<?php
+
+namespace Flow\Data;
+
+class Pager {
+       protected $storage, $index;
+       protected $defaultLimit = 5;
+
+       public function __construct( ObjectManager $storage, array $query, 
array $options ) {
+               // not sure i like this
+               $this->storage = $storage;
+               $this->sort = $storage->getIndexFor(
+                       array_keys( $query ),
+                       array( 'limit' => $options['pager-limit'] )
+               )->getSort();
+               $this->query = $query;
+               $this->options = $options;
+       }
+
+       public function getPage() {
+               $direction = $this->getDirection( $this->options );
+               $offset = $this->getOffset( $this->options );
+               $pageLimit = $this->getLimit( $this->options );
+
+               // We need one item of leeway to determine if there are more 
items
+               $queryLimit = $pageLimit + 1;
+
+               $options = $this->options + array(
+                       'limit' => $queryLimit,
+                       'offset-dir' => $direction,
+                       'offset-key' => $offset,
+                       'offset-elastic' => true,
+               );
+
+               // Retrieve results
+               $results = $this->storage->find( $this->query, $options );
+
+               return $this->processPage( $direction, $offset, $pageLimit, 
$results );
+       }
+
+       public function getDefaultLimit() {
+               return $this->defaultLimit;
+       }
+
+       public function setDefaultLimit( $newLimit ) {
+               $this->defaultLimit = $newLimit;
+       }
+
+       protected function getDefaultDirection() {
+               return 'fwd';
+       }
+
+       protected function validateDirection( $dir ) {
+               return in_array( $dir, array( 'fwd', 'rev' ), true );
+       }
+
+       protected function processPage( $direction, $offset, $pageLimit, 
$results ) {
+               $pagingLinks = array();
+
+               // Retrieve paging links
+               if ( $direction === 'fwd' ) {
+                       if ( count( $results ) == $pageLimit + 1 ) {
+                               // We got one extra, another page exists
+                               array_pop( $results ); // Discard last item
+                               $pagingLinks['fwd'] = $this->makePagingLink(
+                                       'fwd',
+                                       end( $results ),
+                                       $pageLimit
+                               );
+                       }
+
+                       if ( $offset !== null ) {
+                               $pagingLinks['rev'] = $this->makePagingLink(
+                                       'rev',
+                                       reset( $results ),
+                                       $pageLimit
+                               );
+                       }
+               } elseif ( $direction === 'rev' ) {
+                       if ( count( $results ) == $pageLimit + 1 ) {
+                               array_shift( $results );
+
+                               $pagingLinks['rev'] = $this->makePagingLink(
+                                       'rev',
+                                       reset( $results ),
+                                       $pageLimit
+                               );
+                       }
+
+                       if ( $offset !== null ) {
+                               $pagingLinks['fwd'] = $this->makePagingLink(
+                                       'fwd',
+                                       end( $results ),
+                                       $pageLimit
+                               );
+                       }
+               } else {
+                       throw new MWException( "Unrecognised direction 
$direction" );
+               }
+
+               return new PagerPage( $results, $pagingLinks, $this );
+       }
+
+       protected function makePagingLink( $direction, $object, $pageLimit ) {
+               $offset = $this->storage->serializeOffset( $object, $this->sort 
);
+               return array(
+                       'direction' => $direction,
+                       'offset' => $offset,
+                       'limit' => $pageLimit,
+               );
+       }
+
+       protected function getDirection( $options ) {
+               $direction = 'fwd';
+               if ( isset( $options['pager-dir'] ) ) {
+                       if ( $this->validateDirection( $options['pager-dir'] ) 
) {
+                               $direction = $options['pager-dir'];
+                       }
+               }
+
+               return $direction;
+       }
+
+       protected function getMaxLimit() {
+               return 500;
+       }
+
+       protected function getLimit( $options ) {
+               if ( isset( $options['pager-limit'] ) ) {
+                       $requestedLimit = intval( $options['pager-limit'] );
+
+                       if ( $requestedLimit > 0 && $requestedLimit < 
$this->getMaxLimit() ) {
+                               return $requestedLimit;
+                       }
+               }
+
+               return $this->getDefaultLimit();
+       }
+
+       protected function getOffset( $options ) {
+               if ( isset( $options['pager-offset'] ) ) {
+                       return $options['pager-offset'];
+               }
+
+               return null;
+       }
+}
+
+class PagerPage {
+       function __construct( $results, $pagingLinks, $pager ) {
+               $this->results = $results;
+               $this->pagingLinks = $pagingLinks;
+               $this->pager = $pager;
+       }
+
+       public function getPager() {
+               return $this->pager;
+       }
+
+       public function getResults() {
+               return $this->results;
+       }
+
+       public function getPagingLink( $direction ) {
+               if ( isset( $this->pagingLinks[$direction] ) ) {
+                       return $this->pagingLinks[$direction];
+               }
+
+               return null;
+       }
+
+       public function getPagingLinks() {
+               return $this->pagingLinks;
+       }
+}
diff --git a/includes/Templating.php b/includes/Templating.php
index 9be7e69..6da848c 100644
--- a/includes/Templating.php
+++ b/includes/Templating.php
@@ -90,5 +90,40 @@
                        'root' => $root,
                ), $return );
        }
+
+       public function getPagingLink( $block, $direction, $offset, $limit ) {
+               $output = '';
+
+               // Use the message/class flow-paging-fwd or flow-paging-rev
+               //  depending on direction
+               $output .= \Html::element(
+                       'a',
+                       array(
+                               'href' => $this->generateUrl(
+                                       $block->getWorkflowId(),
+                                       'view',
+                                       array(
+                                               $block->getName().'[offset-id]' 
=> $offset,
+                                               
$block->getName().'[offset-dir]' => $direction,
+                                               $block->getName().'[limit]' => 
$limit,
+                                       )
+                               ),
+                       ),
+                       wfMessage( 'flow-paging-'.$direction )->parse()
+               );
+
+               $output = \Html::rawElement(
+                       'div',
+                       array(
+                               'class' => 'flow-paging 
flow-paging-'.$direction,
+                               'data-offset' => $offset,
+                               'data-direction' => $direction,
+                               'data-limit' => $limit,
+                       ),
+                       $output
+               );
+
+               return $output;
+       }
 }
 
diff --git a/includes/api/ApiQueryFlow.php b/includes/api/ApiQueryFlow.php
index faef3e3..e34779f 100644
--- a/includes/api/ApiQueryFlow.php
+++ b/includes/api/ApiQueryFlow.php
@@ -32,8 +32,15 @@
                                $blockParams = $passedParams[$block->getName()];
                        }
 
-                       $blockOutput[] = $block->renderAPI( $blockParams ) +
-                               array( 'block-name' => $block->getName() );
+                       $templating = $this->container['templating'];
+                       $thisBlock = $block->renderAPI( $templating, 
$blockParams ) +
+                               array(
+                                       'block-name' => $block->getName()
+                               );
+
+                       $templating = $this->container['templating'];
+
+                       $blockOutput[] = $thisBlock;
                }
 
                $result = array(
@@ -63,7 +70,9 @@
        public function getParamDescription() {
                return array(
                        'workflow' => 'Hex-encoded ID of the workflow to query',
-                       'page' => 'Title of the page to query'
+                       'page' => 'Title of the page to query',
+                       'action' => 'The view-type action to take',
+                       'params' => 'View parameters to pass to each block, 
indexed by block name',
                );
        }
 
diff --git a/modules/base/ext.flow.base.js b/modules/base/ext.flow.base.js
index 0ed82ef..b14542f 100644
--- a/modules/base/ext.flow.base.js
+++ b/modules/base/ext.flow.base.js
@@ -59,7 +59,7 @@
                        );
                },
 
-               'readTopic' : function( pageName, topicId, options ) {
+               'readBlock' : function( pageName, topicId, blockName, options ) 
{
                        var deferredObject = $.Deferred();
 
                        mw.flow.api.read( pageName, topicId, options )
@@ -76,13 +76,14 @@
 
                                        $.each( output.query.flow, function( 
index, block ) {
                                                // Looping through each block
-                                               if ( block['block-name'] === 
'topic' ) {
+                                               if ( block['block-name'] === 
blockName ) {
                                                        // Return this block
                                                        deferredObject.resolve( 
block );
                                                }
                                        } );
 
-                                       deferredObject.fail( 'invalid-result', 
'Unable to find the topic block in the API result' );
+                                       deferredObject.fail( 'invalid-result', 
'Unable to find the '+
+                                               blockName+' block in the API 
result' );
                                } )
                                .fail( function() {
                                        deferredObject.fail( arguments );
@@ -91,6 +92,14 @@
                        return deferredObject.promise();
                },
 
+               'readTopicList' : function( pageName, workflowId, options ) {
+                       return mw.flow.api.readBlock( pageName, workflowId, 
'topic_list', options );
+               },
+
+               'readTopic' : function( pageName, topicId, options ) {
+                       return mw.flow.api.readBlock( pageName, topicId, 
'topic', options );
+               },
+
                'generateTopicAction' : function( actionName, parameterList, 
promiseFilterCallback ) {
                        return function( workflowId ) {
                                var deferredObject = $.Deferred();
diff --git a/modules/discussion/base.css b/modules/discussion/base.css
index 08de39f..3c91a42 100644
--- a/modules/discussion/base.css
+++ b/modules/discussion/base.css
@@ -917,4 +917,18 @@
 
 .flow-edit-post-form {
        padding: 4px;
+}
+
+.flow-paging {
+       padding: 4px;
+       font-size: large;
+       border: 1px solid #ddddff;
+       text-align: center;
+}
+
+.flow-paging-loading a {
+       padding-left: 20px;
+       background-image: url(images/ajax-loader.gif);
+       background-position: center left;
+       background-repeat: no-repeat;
 }
\ No newline at end of file
diff --git a/modules/discussion/paging.js b/modules/discussion/paging.js
new file mode 100644
index 0000000..eba7bd4
--- /dev/null
+++ b/modules/discussion/paging.js
@@ -0,0 +1,109 @@
+( function( $, mw ) {
+       var getPagingLink = function( data ) {
+               var direction = data.direction,
+                       offset = data.offset,
+                       limit = data.limit;
+               
+               return $( '<div/>' )
+                       .addClass( 'flow-paging' )
+                       .addClass( 'flow-paging-'+direction )
+                       .data( 'direction', direction )
+                       .data( 'offset', offset )
+                       .data( 'limit', limit )
+                       .append(
+                               $('<a/>')
+                                       .attr( 'href', '#' )
+                                       .text( mw.msg( 'flow-paging-'+direction 
) )
+                       );
+       };
+
+       $( document ).on( 'flow_init', function( e ) {
+               $(this).find( '.flow-paging a' ).click( function(e) {
+                       e.preventDefault();
+                       var $pagingLinkDiv = $(this).closest('.flow-paging')
+                               .addClass( 'flow-paging-loading' );
+
+                       var offset = $pagingLinkDiv.data( 'offset' );
+                       var direction = $pagingLinkDiv.data( 'direction' );
+                       var limit = $pagingLinkDiv.data( 'limit' );
+                       var workflowId = $(this).flow( 'getTopicWorkflowId' );
+                       var pageName = $(this).closest( '.flow-container' 
).data( 'page-title' );
+                       var requestLimit;
+
+                       // One more for paging forward.
+                       requestLimit = limit + 1;
+
+                       var request = {
+                               'topic_list' : {
+                                       'offset-dir' : direction,
+                                       'offset-id' : offset,
+                                       'limit' : requestLimit,
+                                       'render' : true
+                               }
+                       };
+
+                       mw.flow.api.readTopicList( pageName, workflowId, 
request )
+                               .done( function( data ) {
+                                       var nextPage, prevPage;
+                                       var topics = [];
+                                       $.each( data, function( k, v ) {
+                                               if ( k - 0 == k ) {
+                                                       topics.push( v );
+                                               }
+                                       } );
+
+                                       var $output = $('<div/>');
+
+                                       if ( direction == 'rev' && 
data.paging.rev ) {
+                                               $output.append(
+                                                       getPagingLink(
+                                                               data.paging.rev
+                                                       )
+                                               );
+                                       }
+                                       $.each( topics, function( k, topic ) {
+                                               $output.append( topic.rendered 
);
+                                       } );
+                                       if ( direction == 'fwd' && 
data.paging.fwd ) {
+                                               $output.append(
+                                                       getPagingLink(
+                                                               data.paging.fwd
+                                                       )
+                                               );
+                                       }
+
+                                       var $replaceContent = 
$output.children();
+                                       $pagingLinkDiv.next( '.flow-error' 
).remove();
+                                       $pagingLinkDiv.replaceWith( 
$replaceContent );
+                                       $replaceContent.trigger( 'flow_init' );
+                               } )
+                               .fail( function() {
+                                       $( '<div/>' )
+                                               .flow( 'showError', arguments )
+                                               .insertAfter( $pagingLinkDiv );
+                               } );
+               } );
+       } );
+
+       $( function() {
+               var $window = $(window);
+               $window
+                       .scroll( function(e) {
+                               $( '.flow-paging-fwd' ).each( function() {
+                                       var $pagingLinkDiv = $( this );
+                                       if ( $pagingLinkDiv.hasClass( 
'flow-paging-loading' ) ) {
+                                               // Already loading
+                                               return;
+                                       }
+
+                                       // Trigger infinite scroll when the 
user is half a screenlength
+                                       //  away from the end.
+                                       var windowEnd = $window.scrollTop() + 
(1.5 * $window.height() );
+
+                                       if ( $pagingLinkDiv.position().top < 
windowEnd ) {
+                                               
$pagingLinkDiv.find('a').click();
+                                       }
+                               } );
+                       } );
+       } );
+} )( jQuery, mediaWiki );
\ No newline at end of file
diff --git a/templates/topiclist.html.php b/templates/topiclist.html.php
index b704f9b..1600e67 100644
--- a/templates/topiclist.html.php
+++ b/templates/topiclist.html.php
@@ -3,24 +3,24 @@
 $editToken = $user->getEditToken( 'flow' );
 echo Html::openElement( 'form', array(
        'method' => 'POST',
-       'action' => $this->generateUrl( $topicList->getWorkflow(), 'new-topic' 
),
+       'action' => $this->generateUrl( $block->getWorkflow(), 'new-topic' ),
        'class' => 'flow-newtopic-form',
 ) );
 echo Html::element( 'input', array( 'type' => 'hidden', 'name' => 
'wpEditToken', 'value' => $editToken) );
 
-if ( $topicList->hasErrors( 'topic' ) ) {
-       echo '<p>' . $topicList->getError( 'topic' )->escaped() . '</p>';
+if ( $block->hasErrors( 'topic' ) ) {
+       echo '<p>' . $block->getError( 'topic' )->escaped() . '</p>';
 }
-echo Html::textarea( $topicList->getName() . '[topic]', '', array(
+echo Html::textarea( $block->getName() . '[topic]', '', array(
        'placeholder' => wfMessage( 'flow-newtopic-title-placeholder' )->text(),
        'class' => 'flow-newtopic-title flow-input',
        'rows' => 1,
 ) );
 
-if ( $topicList->hasErrors( 'content' ) ) {
-       echo '<p>' . $topicList->getError( 'content' )->escaped() . '</p>';
+if ( $block->hasErrors( 'content' ) ) {
+       echo '<p>' . $block->getError( 'content' )->escaped() . '</p>';
 }
-echo Html::textarea( $topicList->getName() . '[content]', '', array(
+echo Html::textarea( $block->getName() . '[content]', '', array(
        'placeholder' => wfMessage( 'flow-newtopic-content-placeholder' 
)->text(),
        'class' => 'flow-newtopic-step2 flow-newtopic-content flow-input',
        'rows' => '10',
@@ -37,6 +37,16 @@
 ), wfMessage( 'flow-disclaimer' )->parse() );
 echo '</form>';
 
+if ( $page && $page->getPagingLink( 'rev' ) ) {
+       $linkData = $page->getPagingLink( 'rev' );
+       echo $this->getPagingLink( $block, 'rev', $linkData['offset'], 
$linkData['limit'] );
+}
+
 foreach ( $topics as $topic ) {
        echo $topic->render( $this, array(), true );
 }
+
+if ( $page && $page->getPagingLink( 'fwd' ) ) {
+       $linkData = $page->getPagingLink( 'fwd' );
+       echo $this->getPagingLink( $block, 'fwd', $linkData['offset'], 
$linkData['limit'] );
+}
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/79444
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Id94af9cf71c4ca0fd89bce7fc58c3bb756742777
Gerrit-PatchSet: 6
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Werdna <[email protected]>
Gerrit-Reviewer: EBernhardson (WMF) <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to