diff --git a/public_html/media/css/core.css b/public_html/media/css/core.css index 0e062fbd..1a53b11b 100644 --- a/public_html/media/css/core.css +++ b/public_html/media/css/core.css @@ -320,6 +320,28 @@ ul.tagit input { height: auto !important; margin: -4px 0 !important; } +.related-tags { + padding: 0.5em; + background: rgba(255,255,255,0.7); + border-radius: 3px; + margin: 0.4em 0 0.2em 0; + font-size: 95%; +} +.related-tags ul { + list-style-type: none; + margin: 0; + padding: 0; + display: block; + overflow: hidden; +} +.related-tags p { + float: left; + margin: 0 1em 0 0; +} +.related-tags li { + display: inline-block; + margin: 0 1em 0 0; +} @@ -427,7 +449,6 @@ blockquote>*:last-child { } .ui-state-default, -.ui-widget-content .ui-state-default, -.ui-widget-header .ui-state-default { +.ui-state-default a { color: hsla(0,70%,45%,0.8) !important; } diff --git a/public_html/media/js/core.js b/public_html/media/js/core.js index b389dc88..ca0d5c90 100644 --- a/public_html/media/js/core.js +++ b/public_html/media/js/core.js @@ -268,11 +268,52 @@ $(function() }); }); -function attachTagIt(element) +function attachTagIt(target) { var tagItOptions = { caseSensitive: false, + onTagClicked: function(e, ui) + { + var targetTagit = ui.tag.parents('.tagit'); + options = { tag: ui.tagLabel }; + if (targetTagit.siblings('.related-tags:eq(0)').data('for') == options.tag) + { + targetTagit.siblings('.related-tags').slideUp(function() + { + $(this).remove(); + }); + return; + } + + $.getJSON('/tags-related?json', options, function(data) + { + var list = $('
Related tags:
'); + div.append(list); + div.insertAfter(targetTagit).hide().slideDown(); + }); + }, + autocomplete: { source: @@ -295,8 +336,8 @@ function attachTagIt(element) } }; - tagItOptions.placeholderText = element.attr('placeholder'); - element.tagit(tagItOptions); + tagItOptions.placeholderText = target.attr('placeholder'); + target.tagit(tagItOptions); } diff --git a/src/Controllers/TagController.php b/src/Controllers/TagController.php index 7f54629b..8e9f4895 100644 --- a/src/Controllers/TagController.php +++ b/src/Controllers/TagController.php @@ -66,6 +66,28 @@ class TagController }, $tags)); } + /** + * @route /tags-related + */ + public function relatedAction() + { + PrivilegesHelper::confirmWithException(Privilege::ListTags); + + $suppliedTag = InputHelper::get('tag'); + + $tags = TagSearchService::getRelatedTagRows($suppliedTag, 10, 1); + + $this->context->transport->tags = + array_values(array_map( + function($tag) + { + return [ + 'name' => $tag['name'], + 'count' => $tag['post_count'] + ]; + }, $tags)); + } + /** * @route /tag/merge */ diff --git a/src/Models/SearchServices/TagSearchService.php b/src/Models/SearchServices/TagSearchService.php index 05b10510..ca35e887 100644 --- a/src/Models/SearchServices/TagSearchService.php +++ b/src/Models/SearchServices/TagSearchService.php @@ -9,6 +9,63 @@ class TagSearchService extends AbstractSearchService $stmt->addColumn(new Sql\AliasFunctor(new Sql\CountFunctor('post_tag.post_id'), 'post_count')); } + public static function getRelatedTagRows($parentTagName, $limit) + { + $parentTagEntity = TagModel::findByName($parentTagName, false); + if (empty($parentTagEntity)) + return []; + $parentTagId = $parentTagEntity->id; + + //get tags that appear with selected tag along with their occurence frequency + $stmt = (new Sql\SelectStatement) + ->setTable('tag') + ->addColumn('tag.*') + ->addColumn(new Sql\AliasFunctor(new Sql\CountFunctor('post_tag.post_id'), 'post_count')) + ->addInnerJoin('post_tag', new Sql\EqualsFunctor('post_tag.tag_id', 'tag.id')) + ->setGroupBy('tag.id') + ->setOrderBy('post_count', Sql\SelectStatement::ORDER_DESC) + ->setLimit($limit + 1, 0) + ->setCriterion(new Sql\ExistsFunctor((new Sql\SelectStatement) + ->setTable('post_tag pt2') + ->setCriterion((new Sql\ConjunctionFunctor) + ->add(new Sql\EqualsFunctor('pt2.post_id', 'post_tag.post_id')) + ->add(new Sql\EqualsFunctor('pt2.tag_id', new Sql\Binding($parentTagId))) + ))); + + $rows1 = []; + foreach (Database::fetchAll($stmt) as $row) + $rows1[$row['id']] = $row; + + //get the same tags, but this time - get global occurence frequency + $stmt = (new Sql\SelectStatement) + ->setTable('tag') + ->addColumn('tag.*') + ->addColumn(new Sql\AliasFunctor(new Sql\CountFunctor('post_tag.post_id'), 'post_count')) + ->addInnerJoin('post_tag', new Sql\EqualsFunctor('post_tag.tag_id', 'tag.id')) + ->setCriterion(Sql\InFunctor::fromArray('tag.id', Sql\Binding::fromArray(array_keys($rows1)))) + ->setGroupBy('tag.id'); + + $rows2 = []; + foreach (Database::fetchAll($stmt) as $row) + $rows2[$row['id']] = $row; + + $rows = []; + foreach ($rows1 as $i => $row) + { + //multiply own occurences by two because we are going to subtract them + $row['sort'] = $row['post_count'] * 2; + //subtract global occurencecount + $row['sort'] -= isset($rows2[$i]) ? $rows2[$i]['post_count'] : 0; + + if ($row['id'] != $parentTagId) + $rows []= $row; + } + + usort($rows, function($a, $b) { return intval($b['sort']) - intval($a['sort']); }); + + return $rows; + } + public static function getMostUsedTag() { $stmt = (new Sql\SelectStatement)