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