From 74e6e008dc1c8ba69cf0d9487bc8f4d08a8f7f2b Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Thu, 9 Oct 2014 23:46:32 +0200 Subject: [PATCH] Implemented mass tag --- TODO | 1 - data/config.ini | 1 + public_html/css/post-list.css | 42 +++++++++ public_html/js/Auth.js | 1 + public_html/js/Pager.js | 2 +- .../js/Presenters/PostListPresenter.js | 87 +++++++++++++++++-- public_html/templates/post-list-item.tpl | 4 + public_html/templates/post-list.tpl | 8 +- src/FormData/PostEditFormData.php | 3 +- src/Privilege.php | 1 + src/Services/TagService.php | 8 +- 11 files changed, 142 insertions(+), 16 deletions(-) diff --git a/TODO b/TODO index 1747b543..9a36ba57 100644 --- a/TODO +++ b/TODO @@ -24,7 +24,6 @@ everything related to users: everything related to tags: - tags.json refresh when editing post - basic tags - - mass tag - merging - tag editing - name diff --git a/data/config.ini b/data/config.ini index ba628486..be888d38 100644 --- a/data/config.ini +++ b/data/config.ini @@ -56,6 +56,7 @@ changePostRelations = regularUser, powerUser, moderator, administrator changePostFlags = regularUser, powerUser, moderator, administrator listTags = anonymous, regularUser, powerUser, moderator, administrator +massTag = powerUser, moderator, administrator listComments = anonymous, regularUser, powerUser, moderator, administrator addComments = regularUser, powerUser, moderator, administrator diff --git a/public_html/css/post-list.css b/public_html/css/post-list.css index cfa84c04..af9fe988 100644 --- a/public_html/css/post-list.css +++ b/public_html/css/post-list.css @@ -23,6 +23,9 @@ justify-content: center; } +.post-small { + position: relative; +} .post-small a { display: inline-block; margin: 0.4em; @@ -112,3 +115,42 @@ .post-small.post-type-flash a:after { content: 'flash'; } + +.post-small .action { + display: none; + position: absolute; + z-index: 3; + left: 0; + right: 0; + top: 50%; + bottom: 0; +} + +.post-small .action button { + padding: 0.5em 1em; + height: 1em; + line-height: 1em; + display: block; + margin: -1em auto 0 auto; + box-sizing: content-box; + opacity: .7; +} + +.tagged .action button, +.untagged .action button { + border: 1px solid black; + font-weight: bold; + box-shadow: none; +} +.untagged .action button { + background: red; + color: white; +} +.tagged .action button { + background: lime; + color: black; +} +.untagged .post-small img, +.tagged .post-small img { + opacity: .85; +} diff --git a/public_html/js/Auth.js b/public_html/js/Auth.js index 993cd6cc..152de6b8 100644 --- a/public_html/js/Auth.js +++ b/public_html/js/Auth.js @@ -42,6 +42,7 @@ App.Auth = function(_, jQuery, util, api, appState, promise) { deleteAllComments: 'deleteAllComments', listTags: 'listTags', + massTag: 'massTag', viewHistory: 'viewHistory', }; diff --git a/public_html/js/Pager.js b/public_html/js/Pager.js index a9803f93..bdc6ef9d 100644 --- a/public_html/js/Pager.js +++ b/public_html/js/Pager.js @@ -57,7 +57,7 @@ App.Pager = function( function retrieve() { return promise.make(function(resolve, reject) { - promise.wait(api.get(url, _.extend({page: pageNumber}, searchParams))) + promise.wait(api.get(url, _.extend({}, searchParams, {page: pageNumber}))) .then(function(response) { var pageSize = response.json.pageSize; var totalRecords = response.json.totalRecords; diff --git a/public_html/js/Presenters/PostListPresenter.js b/public_html/js/Presenters/PostListPresenter.js index f454add7..6b8db18f 100644 --- a/public_html/js/Presenters/PostListPresenter.js +++ b/public_html/js/Presenters/PostListPresenter.js @@ -7,6 +7,7 @@ App.Presenters.PostListPresenter = function( util, promise, auth, + api, keyboard, pagerPresenter, topNavigationPresenter) { @@ -16,6 +17,7 @@ App.Presenters.PostListPresenter = function( var templates = {}; var $el = jQuery('#content'); var $searchInput; + var privileges = {}; var params; @@ -25,6 +27,8 @@ App.Presenters.PostListPresenter = function( params = _params; params.query = params.query || {}; + privileges.canMassTag = auth.hasPrivilege(auth.privileges.massTag); + promise.wait( util.promiseTemplate('post-list'), util.promiseTemplate('post-list-item')) @@ -52,6 +56,7 @@ App.Presenters.PostListPresenter = function( function reinit(params, loaded) { pagerPresenter.reinit({query: params.query}); loaded(); + softRender(); } function deinit() { @@ -59,13 +64,14 @@ App.Presenters.PostListPresenter = function( } function render() { - $el.html(templates.list()); + $el.html(templates.list({massTag: params.query.massTag, privileges: privileges})); $searchInput = $el.find('input[name=query]'); App.Controls.AutoCompleteInput($searchInput); $searchInput.val(params.query.query); $searchInput.keydown(searchInputKeyPressed); $el.find('form').submit(searchFormSubmitted); + $el.find('[name=mass-tag]').click(massTagButtonClicked); keyboard.keyup('p', function() { $el.find('.posts li a').eq(0).focus(); @@ -76,6 +82,19 @@ App.Presenters.PostListPresenter = function( }); } + function softRender() { + $searchInput.val(params.query.query); + + var $massTagInfo = $el.find('.mass-tag-info'); + if (params.query.massTag) { + $massTagInfo.show(); + $massTagInfo.find('span').text(params.query.massTag); + } else { + $massTagInfo.hide(); + } + _.map($el.find('.posts .post-small'), function(postNode) { softRenderPost(jQuery(postNode).parents('li')); }); + } + function renderPosts(posts, clear) { var $target = $el.find('.posts'); @@ -84,16 +103,62 @@ App.Presenters.PostListPresenter = function( } _.each(posts, function(post) { - var $post = jQuery('
  • ' + templates.listItem({ - util: util, - query: params.query, - post: post, - }) + '
  • '); - util.loadImagesNicely($post.find('img')); + var $post = renderPost(post); + softRenderPost($post); $target.append($post); }); } + function renderPost(post) { + var $post = jQuery('
  • ' + templates.listItem({ + util: util, + query: params.query, + post: post, + }) + '
  • '); + $post.data('post', post); + util.loadImagesNicely($post.find('img')); + return $post; + } + + function softRenderPost($post) { + var classes = []; + if (params.query.massTag) { + var post = $post.data('post'); + if (_.contains(_.map(post.tags, function(tag) { return tag.name.toLowerCase(); }), params.query.massTag.toLowerCase())) { + classes.push('tagged'); + } else { + classes.push('untagged'); + } + } + $post.toggleClass('tagged', _.contains(classes, 'tagged')); + $post.toggleClass('untagged', _.contains(classes, 'untagged')); + $post.find('.action').toggle(_.any(classes)); + $post.find('.action button').text(_.contains(classes, 'tagged') ? 'Tagged' : 'Untagged').unbind('click').click(postTagButtonClicked); + } + + function postTagButtonClicked(e) { + e.preventDefault(); + var $post = jQuery(e.target).parents('li'); + var post = $post.data('post'); + var tags = _.pluck(post.tags, 'name'); + if (_.contains(_.map(tags, function(tag) { return tag.toLowerCase(); }), params.query.massTag.toLowerCase())) { + tags = _.filter(tags, function(tag) { return tag.toLowerCase() !== params.query.massTag.toLowerCase(); }); + } else { + tags.push(params.query.massTag); + } + var formData = {}; + formData.seenEditTime = post.lastEditTime; + formData.tags = tags.join(' '); + promise.wait(api.put('/posts/' + post.id, formData)) + .then(function(response) { + post = response.json; + $post.data('post', post); + softRenderPost($post); + }).fail(function(response) { + console.log(response); + }); + } + function searchInputKeyPressed(e) { if (e.which !== KEY_RETURN) { return; @@ -101,6 +166,12 @@ App.Presenters.PostListPresenter = function( updateSearch(); } + function massTagButtonClicked(e) { + e.preventDefault(); + params.query.massTag = window.prompt('Enter tag to tag with:'); + pagerPresenter.setQuery(params.query); + } + function searchFormSubmitted(e) { e.preventDefault(); updateSearch(); @@ -121,4 +192,4 @@ App.Presenters.PostListPresenter = function( }; -App.DI.register('postListPresenter', ['_', 'jQuery', 'util', 'promise', 'auth', 'keyboard', 'pagerPresenter', 'topNavigationPresenter'], App.Presenters.PostListPresenter); +App.DI.register('postListPresenter', ['_', 'jQuery', 'util', 'promise', 'auth', 'api', 'keyboard', 'pagerPresenter', 'topNavigationPresenter'], App.Presenters.PostListPresenter); diff --git a/public_html/templates/post-list-item.tpl b/public_html/templates/post-list-item.tpl index a430d22c..1499d090 100644 --- a/public_html/templates/post-list-item.tpl +++ b/public_html/templates/post-list-item.tpl @@ -32,4 +32,8 @@ <% } %> + +
    + +
    diff --git a/public_html/templates/post-list.tpl b/public_html/templates/post-list.tpl index fca6325c..6a6464fb 100644 --- a/public_html/templates/post-list.tpl +++ b/public_html/templates/post-list.tpl @@ -1,9 +1,15 @@
    +

    Tagging with <%= massTag %>

    +
      diff --git a/src/FormData/PostEditFormData.php b/src/FormData/PostEditFormData.php index 63d84de0..faa551d2 100644 --- a/src/FormData/PostEditFormData.php +++ b/src/FormData/PostEditFormData.php @@ -22,7 +22,8 @@ class PostEditFormData implements IValidatable { $this->content = $inputReader->decodeBase64($inputReader->content); $this->thumbnail = $inputReader->decodebase64($inputReader->thumbnail); - $this->safety = EnumHelper::postSafetyFromString($inputReader->safety); + if ($inputReader->safety) + $this->safety = EnumHelper::postSafetyFromString($inputReader->safety); $this->source = $inputReader->source; $this->tags = preg_split('/[\s+]/', $inputReader->tags); $this->relations = array_filter(preg_split('/[\s+]/', $inputReader->relations)); diff --git a/src/Privilege.php b/src/Privilege.php index 487dc2f7..1d8308cd 100644 --- a/src/Privilege.php +++ b/src/Privilege.php @@ -35,6 +35,7 @@ class Privilege const CHANGE_POST_FLAGS = 'changePostFlags'; const LIST_TAGS = 'listTags'; + const MASS_TAG = 'massTag'; const LIST_COMMENTS = 'listComments'; const ADD_COMMENTS = 'addComments'; diff --git a/src/Services/TagService.php b/src/Services/TagService.php index 1d857e81..72009443 100644 --- a/src/Services/TagService.php +++ b/src/Services/TagService.php @@ -61,7 +61,7 @@ class TagService { $tagNameGetter = function($tag) { - return $tag->getName(); + return strtolower($tag->getName()); }; $tagNames = array_map($tagNameGetter, $tags); @@ -86,10 +86,10 @@ class TagService $result = []; foreach ($tags as $key => $tag) { - if (isset($tagsNotToCreate[$tag->getName()])) - $tag = $tagsNotToCreate[$tag->getName()]; + if (isset($tagsNotToCreate[$tagNameGetter($tag)])) + $tag = $tagsNotToCreate[$tagNameGetter($tag)]; else - $tag = $createdTags[$tag->getName()]; + $tag = $createdTags[$tagNameGetter($tag)]; $result[$key] = $tag; } return $result;