Changed behavior of post list tabs

Before: each one linked to separate page that contained "static" search.
After: each one links to generic search that is aware of current search.

Example: if you searched for "snow" and clicked "upvoted", you would see
all upvoted posts ever regardless of what you just searched for. After
this change, you will see upvoted posts that have tag "snow". Now, if
you go back to "all posts", you will see again all posts tagged with
"snow" with or without the upvotes. Similarly if you click favorites,
favmin:1 will be appended to your search. In order to totally reset your
search, click "browse".

Additionally, typing favmin:1 and scoremin:1 manually now selects proper
tab.  Previously tabs were marked only if you clicked the tab.

Unfortunately, all of this had to happen at expense of URLs like
/upvoted and /random - now everything is represented with plain /posts/.
This commit is contained in:
Marcin Kurczewski 2014-08-04 22:15:10 +02:00
parent ea5a07b509
commit b97726f6ff
6 changed files with 128 additions and 88 deletions

View file

@ -70,21 +70,6 @@ class PostController extends AbstractController
$this->redirect($url);
}
public function favoritesView($page = 1)
{
$this->listView('favmin:1', $page, 'favorites');
}
public function upvotedView($page = 1)
{
$this->listView('scoremin:1', $page, 'upvoted');
}
public function randomView($page = 1)
{
$this->listView('order:random', $page, 'random');
}
public function toggleTagAction($identifier, $tag, $enable)
{
Access::assert(new Privilege(

View file

@ -5,11 +5,41 @@ abstract class AbstractSearchParser
{
protected $statement;
public function decorate($statement, $filterString)
public function disassembleTokens($searchString)
{
return preg_split('/\s+/', $searchString);
}
public function assembleTokens($tokens)
{
return implode(' ', $tokens);
}
public function addTokenToSearchString($searchString, $token)
{
$tokensToInclude = is_array($token)
? $token
: [$token];
$tokens = $this->disassembleTokens($searchString);
$newTokens = array_filter(array_unique(array_merge($tokens, $tokensToInclude)));
return $this->assembleTokens($newTokens);
}
public function removeTokenFromSearchString($searchString, $token)
{
$tokensToExclude = is_array($token)
? $token
: [$token];
$tokens = $this->disassembleTokens($searchString);
$newTokens = array_diff($tokens, $tokensToExclude);
return $this->assembleTokens($newTokens);
}
public function decorate($statement, $searchString)
{
$this->statement = $statement;
$tokens = preg_split('/\s+/', $filterString);
$tokens = $this->disassembleTokens($searchString);
$tokens = array_filter($tokens);
$tokens = array_unique($tokens);
$this->processSetup($tokens);

View file

@ -3,38 +3,7 @@ use \Chibi\Sql as Sql;
abstract class AbstractSearchService
{
protected static function getModelClassName()
{
$searchServiceClassName = get_called_class();
$modelClassName = str_replace('SearchService', 'Model', $searchServiceClassName);
return $modelClassName;
}
protected static function getParserClassName()
{
$searchServiceClassName = get_called_class();
$parserClassName = str_replace('SearchService', 'SearchParser', $searchServiceClassName);
return $parserClassName;
}
protected static function decorateParser($stmt, $searchQuery)
{
$parserClassName = self::getParserClassName();
(new $parserClassName)->decorate($stmt, $searchQuery);
}
protected static function decorateCustom($stmt)
{
}
protected static function decoratePager($stmt, $perPage, $page)
{
if ($perPage === null)
return;
$stmt->setLimit(
new Sql\Binding($perPage),
new Sql\Binding(($page - 1) * $perPage));
}
protected static $parser;
public static function getEntities($searchQuery, $perPage = null, $page = 1)
{
@ -44,7 +13,7 @@ abstract class AbstractSearchService
$stmt = Sql\Statements::select();
$stmt->setColumn($table . '.*');
$stmt->setTable($table);
static::decorateParser($stmt, $searchQuery);
static::decorateFromParser($stmt, $searchQuery);
static::decorateCustom($stmt);
static::decoratePager($stmt, $perPage, $page);
@ -60,7 +29,7 @@ abstract class AbstractSearchService
$innerStmt = Sql\Statements::select();
$innerStmt->setTable($table);
static::decorateParser($innerStmt, $searchQuery);
static::decorateFromParser($innerStmt, $searchQuery);
static::decorateCustom($innerStmt);
$innerStmt->resetOrderBy();
@ -70,4 +39,45 @@ abstract class AbstractSearchService
return Core::getDatabase()->fetchOne($stmt)['count'];
}
public static function getParser()
{
$parserClassName = self::getParserClassName();
if (self::$parser == null)
self::$parser = new $parserClassName();
return self::$parser;
}
protected static function getModelClassName()
{
$searchServiceClassName = get_called_class();
$modelClassName = str_replace('SearchService', 'Model', $searchServiceClassName);
return $modelClassName;
}
protected static function decorateFromParser($stmt, $searchQuery)
{
self::getParser()->decorate($stmt, $searchQuery);
}
protected static function decorateCustom($stmt)
{
}
protected static function decoratePager($stmt, $perPage, $page)
{
if ($perPage === null)
return;
$stmt->setLimit(
new Sql\Binding($perPage),
new Sql\Binding(($page - 1) * $perPage));
}
private static function getParserClassName()
{
$searchServiceClassName = get_called_class();
$parserClassName = str_replace('SearchService', 'SearchParser', $searchServiceClassName);
return $parserClassName;
}
}

View file

@ -20,7 +20,7 @@ class PostSearchService extends AbstractSearchService
$innerStmt = Sql\Statements::select();
$innerStmt->setColumn('post.id');
$innerStmt->setTable('post');
self::decorateParser($innerStmt, $searchQuery);
self::decorateFromParser($innerStmt, $searchQuery);
$stmt = Sql\Statements::insert();
$stmt->setTable('post_search');
$stmt->setSource(['post_id'], $innerStmt);

View file

@ -63,13 +63,6 @@ class Router extends \Chibi\Routing\Router
$this->register(['PostController', 'listView'], 'GET', '/{source}/{query}/{additionalInfo}/{page}', $postValidation);
$this->register(['PostController', 'listRedirectAction'], 'POST', '/{source}-redirect', $postValidation);
$this->register(['PostController', 'randomView'], 'GET', '/random', $postValidation);
$this->register(['PostController', 'randomView'], 'GET', '/random/{page}', $postValidation);
$this->register(['PostController', 'favoritesView'], 'GET', '/favorites', $postValidation);
$this->register(['PostController', 'favoritesView'], 'GET', '/favorites/{page}', $postValidation);
$this->register(['PostController', 'upvotedView'], 'GET', '/upvoted', $postValidation);
$this->register(['PostController', 'upvotedView'], 'GET', '/upvoted/{page}', $postValidation);
$this->register(['PostController', 'genericView'], 'GET', '/post/{identifier}', $postValidation);
$this->register(['PostController', 'fileView'], 'GET', '/post/{name}/retrieve', $postValidation);
$this->register(['PostController', 'thumbnailView'], 'GET', '/post/{name}/thumb', $postValidation);

View file

@ -1,59 +1,81 @@
<?php
$this->assets->setSubTitle('posts');
$searchQuery = isset($this->context->transport->searchQuery)
? htmlspecialchars($this->context->transport->searchQuery)
: '';
$parser = PostSearchService::getParser();
$newSearchQuery = PostSearchService::getParser()->removeTokenFromSearchString(
$searchQuery,
['favmin:1', 'scoremin:1', 'order:random']);
$tabs = [];
$activeTab = 0;
if (Access::check(new Privilege(Privilege::ListPosts)))
$tabs []= ['All posts', Core::getRouter()->linkTo(['PostController', 'listView'])];
if (Access::check(new Privilege(Privilege::ListPosts)))
{
$tabs []= ['Random', Core::getRouter()->linkTo(['PostController', 'randomView'])];
if ($this->context->source == 'random')
$activeTab = count($tabs) - 1;
$tabs []= [
'title' => 'All posts',
'active' => function() { return true; },
'link' => Core::getRouter()->linkTo(
['PostController', 'listView'],
!trim($newSearchQuery) ? [] : ['query' => $newSearchQuery])];
$tabs []= ['Favorites', Core::getRouter()->linkTo(['PostController', 'favoritesView'])];
if ($this->context->source == 'favorites')
$activeTab = count($tabs) - 1;
$tabs []= [
'title' => 'Random',
'active' => function() use ($searchQuery) { return strpos($searchQuery, 'order:random') !== false; },
'link' => Core::getRouter()->linkTo(
['PostController', 'listView'],
['query' => $parser->addTokenToSearchString($newSearchQuery, 'order:random')])];
$tabs []= ['Upvoted', Core::getRouter()->linkTo(['PostController', 'upvotedView'])];
if ($this->context->source == 'upvoted')
$activeTab = count($tabs) - 1;
$tabs []= [
'title' => 'Favorites',
'active' => function() use ($searchQuery) { return strpos($searchQuery, 'favmin:1') !== false; },
'link' => Core::getRouter()->linkTo(
['PostController', 'listView'],
['query' => $parser->addTokenToSearchString($newSearchQuery, 'favmin:1')])];
$tabs []= [
'title' => 'Upvoted',
'active' => function() use ($searchQuery) { return strpos($searchQuery, 'scoremin:1') !== false; },
'link' => Core::getRouter()->linkTo(
['PostController', 'listView'],
['query' => $parser->addTokenToSearchString($newSearchQuery, 'scoremin:1')])];
}
if (Access::check(new Privilege(Privilege::MassTag)))
{
$tabs []= ['Mass tag', Core::getRouter()->linkTo(['PostController', 'listView'], [
'source' => 'mass-tag',
'query' => isset($this->context->transport->searchQuery)
? htmlspecialchars($this->context->transport->searchQuery)
: '',
'page' => isset($this->context->transport->paginator)
? $this->context->transport->paginator->page
: 1])];
if ($this->context->source == 'mass-tag')
$activeTab = count($tabs) - 1;
$tabs []= [
'title' => 'Mass tag',
'active' => function() { return $this->context->source == 'mass-tag'; },
'link' => Core::getRouter()->linkTo(['PostController', 'listView'], [
'source' => 'mass-tag',
'query' => $searchQuery,
'page' => isset($this->context->transport->paginator)
? $this->context->transport->paginator->page
: 1])];
}
$activeTab = null;
foreach ($tabs as $key => $tab)
if ($tab['active']())
$activeTab = $key;
?>
<nav class="tabs">
<ul>
<?php foreach ($tabs as $i => $tab): ?>
<?php foreach ($tabs as $key => $tab): ?>
<?php
list($name, $url) = $tab;
$classes = [];
$classes []= TextCaseConverter::convert($name,
$classes []= TextCaseConverter::convert($tab['title'],
TextCaseConverter::BLANK_CASE,
TextCaseConverter::SPINAL_CASE);
if ($i == $activeTab)
if ($key == $activeTab)
$classes []= 'selected';
?>
<li class="<?= join(' ', $classes) ?>">
<a href="<?= $url ?>">
<?= $name ?>
<a href="<?= $tab['link'] ?>">
<?= $tab['title'] ?>
</a>
</li>
<?php endforeach ?>