diff --git a/public_html/css/tag-list.css b/public_html/css/tag-list.css index 8543befa..facebc6d 100644 --- a/public_html/css/tag-list.css +++ b/public_html/css/tag-list.css @@ -1,12 +1,25 @@ #tag-list { + min-width: 15em; + max-width: 40em; + margin: 0 auto; text-align: center; } +#tag-list form { + float: left; + white-space: nowrap; +} #tag-list ul { + float: right; list-style-type: none; padding: 0; margin: 0; } +#tag-list .search:after { + display: block; + content: ''; + clear: both; +} #ta-list ul.order { margin: -0.5em -0.5em 0.5em -0.5em; @@ -21,13 +34,13 @@ } #tag-list table { - min-width: 20em; + width: 100%; text-align: left; margin: 1em auto; } -#tag-list th, -#tag-list td { +#tag-list th:not(:last-child), +#tag-list td:not(:last-child) { padding-right: 1.5em; } @@ -43,3 +56,10 @@ #tag-list .usages { text-align: center; } + +@media all and (max-width: 40em) { + #tag-list .implications, + #tag-list .suggestions { + display: none; + } +} diff --git a/public_html/js/Presenters/TagListPresenter.js b/public_html/js/Presenters/TagListPresenter.js index 8f8ffbcc..d8bea4f0 100644 --- a/public_html/js/Presenters/TagListPresenter.js +++ b/public_html/js/Presenters/TagListPresenter.js @@ -11,11 +11,16 @@ App.Presenters.TagListPresenter = function( topNavigationPresenter) { var $el = jQuery('#content'); + var $searchInput; var templates = {}; - function init(params, loaded) { + var params; + + function init(_params, loaded) { topNavigationPresenter.select('tags'); topNavigationPresenter.changeTitle('Tags'); + params = _params; + params.query = params.query || {}; promise.wait( 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.order = params.query.order || 'name,asc'; updateActiveOrder(params.query.order); @@ -55,7 +61,12 @@ App.Presenters.TagListPresenter = function( $el.find('table a').eq(0).focus(); }); + keyboard.keyup('q', function() { + $searchInput.eq(0).focus().select(); + }); + loaded(); + softRender(); } function deinit() { @@ -64,6 +75,33 @@ App.Presenters.TagListPresenter = function( function render() { $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) { diff --git a/public_html/templates/tag-list.tpl b/public_html/templates/tag-list.tpl index 2a866d3a..e012a85a 100644 --- a/public_html/templates/tag-list.tpl +++ b/public_html/templates/tag-list.tpl @@ -1,15 +1,22 @@
- +
diff --git a/src/Dao/TagDao.php b/src/Dao/TagDao.php index bb172e7b..8f739192 100644 --- a/src/Dao/TagDao.php +++ b/src/Dao/TagDao.php @@ -5,6 +5,8 @@ use Szurubooru\Dao\EntityConverters\TagEntityConverter; use Szurubooru\DatabaseConnection; use Szurubooru\Entities\Entity; use Szurubooru\Entities\Tag; +use Szurubooru\SearchServices\Filters\TagFilter; +use Szurubooru\SearchServices\Requirements\Requirement; class TagDao extends AbstractDao implements ICrudDao { @@ -67,6 +69,52 @@ class TagDao extends AbstractDao implements ICrudDao $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) { $tag->setLazyLoader( @@ -90,6 +138,22 @@ class TagDao extends AbstractDao implements ICrudDao $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) { 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) { $tagId = $tag->getId(); diff --git a/src/SearchServices/Filters/TagFilter.php b/src/SearchServices/Filters/TagFilter.php index e05402cf..cb04d136 100644 --- a/src/SearchServices/Filters/TagFilter.php +++ b/src/SearchServices/Filters/TagFilter.php @@ -8,6 +8,8 @@ class TagFilter extends BasicFilter implements IFilter const ORDER_CREATION_TIME = 'creationTime'; const ORDER_USAGE_COUNT = 'usages'; + const REQUIREMENT_PARTIAL_TAG_NAME = 'partialTagName'; + public function __construct() { $this->setOrder([self::ORDER_ID => self::ORDER_DESC]); diff --git a/src/SearchServices/Parsers/TagSearchParser.php b/src/SearchServices/Parsers/TagSearchParser.php index 69f065b0..ad56d234 100644 --- a/src/SearchServices/Parsers/TagSearchParser.php +++ b/src/SearchServices/Parsers/TagSearchParser.php @@ -3,6 +3,8 @@ namespace Szurubooru\SearchServices\Parsers; use Szurubooru\NotSupportedException; use Szurubooru\SearchServices\Filters\IFilter; use Szurubooru\SearchServices\Filters\TagFilter; +use Szurubooru\SearchServices\Requirements\Requirement; +use Szurubooru\SearchServices\Requirements\RequirementSingleValue; use Szurubooru\SearchServices\Tokens\NamedSearchToken; use Szurubooru\SearchServices\Tokens\SearchToken; @@ -15,7 +17,11 @@ class TagSearchParser extends AbstractSearchParser 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)