Added tag searching
This commit is contained in:
parent
bf58207950
commit
b7f077df9b
6 changed files with 154 additions and 63 deletions
|
@ -1,12 +1,25 @@
|
||||||
#tag-list {
|
#tag-list {
|
||||||
|
min-width: 15em;
|
||||||
|
max-width: 40em;
|
||||||
|
margin: 0 auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tag-list form {
|
||||||
|
float: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
#tag-list ul {
|
#tag-list ul {
|
||||||
|
float: right;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
#tag-list .search:after {
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
#ta-list ul.order {
|
#ta-list ul.order {
|
||||||
margin: -0.5em -0.5em 0.5em -0.5em;
|
margin: -0.5em -0.5em 0.5em -0.5em;
|
||||||
|
@ -21,13 +34,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#tag-list table {
|
#tag-list table {
|
||||||
min-width: 20em;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tag-list th,
|
#tag-list th:not(:last-child),
|
||||||
#tag-list td {
|
#tag-list td:not(:last-child) {
|
||||||
padding-right: 1.5em;
|
padding-right: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,3 +56,10 @@
|
||||||
#tag-list .usages {
|
#tag-list .usages {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 40em) {
|
||||||
|
#tag-list .implications,
|
||||||
|
#tag-list .suggestions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,11 +11,16 @@ App.Presenters.TagListPresenter = function(
|
||||||
topNavigationPresenter) {
|
topNavigationPresenter) {
|
||||||
|
|
||||||
var $el = jQuery('#content');
|
var $el = jQuery('#content');
|
||||||
|
var $searchInput;
|
||||||
var templates = {};
|
var templates = {};
|
||||||
|
|
||||||
function init(params, loaded) {
|
var params;
|
||||||
|
|
||||||
|
function init(_params, loaded) {
|
||||||
topNavigationPresenter.select('tags');
|
topNavigationPresenter.select('tags');
|
||||||
topNavigationPresenter.changeTitle('Tags');
|
topNavigationPresenter.changeTitle('Tags');
|
||||||
|
params = _params;
|
||||||
|
params.query = params.query || {};
|
||||||
|
|
||||||
promise.wait(
|
promise.wait(
|
||||||
util.promiseTemplate('tag-list'),
|
util.promiseTemplate('tag-list'),
|
||||||
|
@ -44,7 +49,8 @@ App.Presenters.TagListPresenter = function(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function reinit(params, loaded) {
|
function reinit(_params, loaded) {
|
||||||
|
params = _params;
|
||||||
params.query = params.query || {};
|
params.query = params.query || {};
|
||||||
params.query.order = params.query.order || 'name,asc';
|
params.query.order = params.query.order || 'name,asc';
|
||||||
updateActiveOrder(params.query.order);
|
updateActiveOrder(params.query.order);
|
||||||
|
@ -55,7 +61,12 @@ App.Presenters.TagListPresenter = function(
|
||||||
$el.find('table a').eq(0).focus();
|
$el.find('table a').eq(0).focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
keyboard.keyup('q', function() {
|
||||||
|
$searchInput.eq(0).focus().select();
|
||||||
|
});
|
||||||
|
|
||||||
loaded();
|
loaded();
|
||||||
|
softRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
function deinit() {
|
function deinit() {
|
||||||
|
@ -64,6 +75,33 @@ App.Presenters.TagListPresenter = function(
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
$el.html(templates.list());
|
$el.html(templates.list());
|
||||||
|
$searchInput = $el.find('input[name=query]');
|
||||||
|
$searchInput.keydown(searchInputKeyPressed);
|
||||||
|
$el.find('form').submit(searchFormSubmitted);
|
||||||
|
softRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
function softRender() {
|
||||||
|
$searchInput.val(params.query.query);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function searchInputKeyPressed(e) {
|
||||||
|
if (e.which !== KEY_RETURN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchFormSubmitted(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
updateSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSearch() {
|
||||||
|
$searchInput.blur();
|
||||||
|
params.query.query = $searchInput.val().trim();
|
||||||
|
pagerPresenter.setQuery(params.query);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateActiveOrder(activeOrder) {
|
function updateActiveOrder(activeOrder) {
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
<div id="tag-list">
|
<div id="tag-list">
|
||||||
<ul class="order">
|
<div class="search">
|
||||||
<li>
|
<form>
|
||||||
<a class="big-button" href="#/tags/order=name,asc">Tags</a>
|
<input type="text" name="query" placeholder="Search tags..."/>
|
||||||
</li>
|
<button type="submit" name="search">Search</button>
|
||||||
<li>
|
</form>
|
||||||
<a class="big-button" href="#/tags/order=creation_time,desc">Recent</a>
|
|
||||||
</li>
|
<ul class="order">
|
||||||
<li>
|
<li>
|
||||||
<a class="big-button" href="#/tags/order=usage_count,desc">Popular</a>
|
<a class="big-button" href="#/tags/order=name,asc">Tags</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<li>
|
||||||
|
<a class="big-button" href="#/tags/order=creation_time,desc">Recent</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="big-button" href="#/tags/order=usage_count,desc">Popular</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pagination-target">
|
<div class="pagination-target">
|
||||||
<table class="tags">
|
<table class="tags">
|
||||||
|
|
|
@ -5,6 +5,8 @@ use Szurubooru\Dao\EntityConverters\TagEntityConverter;
|
||||||
use Szurubooru\DatabaseConnection;
|
use Szurubooru\DatabaseConnection;
|
||||||
use Szurubooru\Entities\Entity;
|
use Szurubooru\Entities\Entity;
|
||||||
use Szurubooru\Entities\Tag;
|
use Szurubooru\Entities\Tag;
|
||||||
|
use Szurubooru\SearchServices\Filters\TagFilter;
|
||||||
|
use Szurubooru\SearchServices\Requirements\Requirement;
|
||||||
|
|
||||||
class TagDao extends AbstractDao implements ICrudDao
|
class TagDao extends AbstractDao implements ICrudDao
|
||||||
{
|
{
|
||||||
|
@ -67,6 +69,52 @@ class TagDao extends AbstractDao implements ICrudDao
|
||||||
$this->deleteBy('usages', 0);
|
$this->deleteBy('usages', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function export()
|
||||||
|
{
|
||||||
|
$exported = [];
|
||||||
|
foreach ($this->fpdo->from('tags') as $arrayEntity)
|
||||||
|
{
|
||||||
|
$exported[$arrayEntity['id']] = [
|
||||||
|
'name' => $arrayEntity['name'],
|
||||||
|
'usages' => intval($arrayEntity['usages']),
|
||||||
|
'banned' => boolval($arrayEntity['banned'])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
//upgrades on old databases
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$relations = iterator_to_array($this->fpdo->from('tagRelations'));
|
||||||
|
}
|
||||||
|
catch (\Exception $e)
|
||||||
|
{
|
||||||
|
$relations = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($relations as $arrayEntity)
|
||||||
|
{
|
||||||
|
$key1 = $arrayEntity['tag1id'];
|
||||||
|
$key2 = $arrayEntity['tag2id'];
|
||||||
|
$type = intval($arrayEntity['type']);
|
||||||
|
if ($type === self::TAG_RELATION_IMPLICATION)
|
||||||
|
$target = 'implications';
|
||||||
|
elseif ($type === self::TAG_RELATION_SUGGESTION)
|
||||||
|
$target = 'suggestions';
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!isset($exported[$key1]) or !isset($exported[$key2]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!isset($exported[$key1][$target]))
|
||||||
|
$exported[$key1][$target] = [];
|
||||||
|
|
||||||
|
$exported[$key1][$target][] = $exported[$key2]['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values($exported);
|
||||||
|
}
|
||||||
|
|
||||||
protected function afterLoad(Entity $tag)
|
protected function afterLoad(Entity $tag)
|
||||||
{
|
{
|
||||||
$tag->setLazyLoader(
|
$tag->setLazyLoader(
|
||||||
|
@ -90,6 +138,22 @@ class TagDao extends AbstractDao implements ICrudDao
|
||||||
$this->syncSuggestedTags($tag);
|
$this->syncSuggestedTags($tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function decorateQueryFromRequirement($query, Requirement $requirement)
|
||||||
|
{
|
||||||
|
if ($requirement->getType() === TagFilter::REQUIREMENT_PARTIAL_TAG_NAME)
|
||||||
|
{
|
||||||
|
$sql = 'INSTR(LOWER(tags.name), LOWER(?)) > 0';
|
||||||
|
|
||||||
|
if ($requirement->isNegated())
|
||||||
|
$sql = 'NOT ' . $sql;
|
||||||
|
|
||||||
|
$query->where($sql, $requirement->getValue()->getValue());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::decorateQueryFromRequirement($query, $requirement);
|
||||||
|
}
|
||||||
|
|
||||||
private function findImpliedTags(Tag $tag)
|
private function findImpliedTags(Tag $tag)
|
||||||
{
|
{
|
||||||
return $this->findRelatedTagsByType($tag, self::TAG_RELATION_IMPLICATION);
|
return $this->findRelatedTagsByType($tag, self::TAG_RELATION_IMPLICATION);
|
||||||
|
@ -138,52 +202,6 @@ class TagDao extends AbstractDao implements ICrudDao
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function export()
|
|
||||||
{
|
|
||||||
$exported = [];
|
|
||||||
foreach ($this->fpdo->from('tags') as $arrayEntity)
|
|
||||||
{
|
|
||||||
$exported[$arrayEntity['id']] = [
|
|
||||||
'name' => $arrayEntity['name'],
|
|
||||||
'usages' => intval($arrayEntity['usages']),
|
|
||||||
'banned' => boolval($arrayEntity['banned'])
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
//upgrades on old databases
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$relations = iterator_to_array($this->fpdo->from('tagRelations'));
|
|
||||||
}
|
|
||||||
catch (\Exception $e)
|
|
||||||
{
|
|
||||||
$relations = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($relations as $arrayEntity)
|
|
||||||
{
|
|
||||||
$key1 = $arrayEntity['tag1id'];
|
|
||||||
$key2 = $arrayEntity['tag2id'];
|
|
||||||
$type = intval($arrayEntity['type']);
|
|
||||||
if ($type === self::TAG_RELATION_IMPLICATION)
|
|
||||||
$target = 'implications';
|
|
||||||
elseif ($type === self::TAG_RELATION_SUGGESTION)
|
|
||||||
$target = 'suggestions';
|
|
||||||
else
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!isset($exported[$key1]) or !isset($exported[$key2]))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!isset($exported[$key1][$target]))
|
|
||||||
$exported[$key1][$target] = [];
|
|
||||||
|
|
||||||
$exported[$key1][$target][] = $exported[$key2]['name'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_values($exported);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function findRelatedTagsByType(Tag $tag, $type)
|
private function findRelatedTagsByType(Tag $tag, $type)
|
||||||
{
|
{
|
||||||
$tagId = $tag->getId();
|
$tagId = $tag->getId();
|
||||||
|
|
|
@ -8,6 +8,8 @@ class TagFilter extends BasicFilter implements IFilter
|
||||||
const ORDER_CREATION_TIME = 'creationTime';
|
const ORDER_CREATION_TIME = 'creationTime';
|
||||||
const ORDER_USAGE_COUNT = 'usages';
|
const ORDER_USAGE_COUNT = 'usages';
|
||||||
|
|
||||||
|
const REQUIREMENT_PARTIAL_TAG_NAME = 'partialTagName';
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->setOrder([self::ORDER_ID => self::ORDER_DESC]);
|
$this->setOrder([self::ORDER_ID => self::ORDER_DESC]);
|
||||||
|
|
|
@ -3,6 +3,8 @@ namespace Szurubooru\SearchServices\Parsers;
|
||||||
use Szurubooru\NotSupportedException;
|
use Szurubooru\NotSupportedException;
|
||||||
use Szurubooru\SearchServices\Filters\IFilter;
|
use Szurubooru\SearchServices\Filters\IFilter;
|
||||||
use Szurubooru\SearchServices\Filters\TagFilter;
|
use Szurubooru\SearchServices\Filters\TagFilter;
|
||||||
|
use Szurubooru\SearchServices\Requirements\Requirement;
|
||||||
|
use Szurubooru\SearchServices\Requirements\RequirementSingleValue;
|
||||||
use Szurubooru\SearchServices\Tokens\NamedSearchToken;
|
use Szurubooru\SearchServices\Tokens\NamedSearchToken;
|
||||||
use Szurubooru\SearchServices\Tokens\SearchToken;
|
use Szurubooru\SearchServices\Tokens\SearchToken;
|
||||||
|
|
||||||
|
@ -15,7 +17,11 @@ class TagSearchParser extends AbstractSearchParser
|
||||||
|
|
||||||
protected function decorateFilterFromToken(IFilter $filter, SearchToken $token)
|
protected function decorateFilterFromToken(IFilter $filter, SearchToken $token)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
$requirement = new Requirement();
|
||||||
|
$requirement->setType(TagFilter::REQUIREMENT_PARTIAL_TAG_NAME);
|
||||||
|
$requirement->setValue(new RequirementSingleValue($token->getValue()));
|
||||||
|
$requirement->setNegated($token->isNegated());
|
||||||
|
$filter->addRequirement($requirement);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function decorateFilterFromNamedToken(IFilter $filter, NamedSearchToken $namedToken)
|
protected function decorateFilterFromNamedToken(IFilter $filter, NamedSearchToken $namedToken)
|
||||||
|
|
Loading…
Reference in a new issue