Implemented mass tag

This commit is contained in:
Marcin Kurczewski 2014-10-09 23:46:32 +02:00
parent f2b5124d54
commit 74e6e008dc
11 changed files with 142 additions and 16 deletions

1
TODO
View file

@ -24,7 +24,6 @@ everything related to users:
everything related to tags: everything related to tags:
- tags.json refresh when editing post - tags.json refresh when editing post
- basic tags - basic tags
- mass tag
- merging - merging
- tag editing - tag editing
- name - name

View file

@ -56,6 +56,7 @@ changePostRelations = regularUser, powerUser, moderator, administrator
changePostFlags = regularUser, powerUser, moderator, administrator changePostFlags = regularUser, powerUser, moderator, administrator
listTags = anonymous, regularUser, powerUser, moderator, administrator listTags = anonymous, regularUser, powerUser, moderator, administrator
massTag = powerUser, moderator, administrator
listComments = anonymous, regularUser, powerUser, moderator, administrator listComments = anonymous, regularUser, powerUser, moderator, administrator
addComments = regularUser, powerUser, moderator, administrator addComments = regularUser, powerUser, moderator, administrator

View file

@ -23,6 +23,9 @@
justify-content: center; justify-content: center;
} }
.post-small {
position: relative;
}
.post-small a { .post-small a {
display: inline-block; display: inline-block;
margin: 0.4em; margin: 0.4em;
@ -112,3 +115,42 @@
.post-small.post-type-flash a:after { .post-small.post-type-flash a:after {
content: 'flash'; 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;
}

View file

@ -42,6 +42,7 @@ App.Auth = function(_, jQuery, util, api, appState, promise) {
deleteAllComments: 'deleteAllComments', deleteAllComments: 'deleteAllComments',
listTags: 'listTags', listTags: 'listTags',
massTag: 'massTag',
viewHistory: 'viewHistory', viewHistory: 'viewHistory',
}; };

View file

@ -57,7 +57,7 @@ App.Pager = function(
function retrieve() { function retrieve() {
return promise.make(function(resolve, reject) { 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) { .then(function(response) {
var pageSize = response.json.pageSize; var pageSize = response.json.pageSize;
var totalRecords = response.json.totalRecords; var totalRecords = response.json.totalRecords;

View file

@ -7,6 +7,7 @@ App.Presenters.PostListPresenter = function(
util, util,
promise, promise,
auth, auth,
api,
keyboard, keyboard,
pagerPresenter, pagerPresenter,
topNavigationPresenter) { topNavigationPresenter) {
@ -16,6 +17,7 @@ App.Presenters.PostListPresenter = function(
var templates = {}; var templates = {};
var $el = jQuery('#content'); var $el = jQuery('#content');
var $searchInput; var $searchInput;
var privileges = {};
var params; var params;
@ -25,6 +27,8 @@ App.Presenters.PostListPresenter = function(
params = _params; params = _params;
params.query = params.query || {}; params.query = params.query || {};
privileges.canMassTag = auth.hasPrivilege(auth.privileges.massTag);
promise.wait( promise.wait(
util.promiseTemplate('post-list'), util.promiseTemplate('post-list'),
util.promiseTemplate('post-list-item')) util.promiseTemplate('post-list-item'))
@ -52,6 +56,7 @@ App.Presenters.PostListPresenter = function(
function reinit(params, loaded) { function reinit(params, loaded) {
pagerPresenter.reinit({query: params.query}); pagerPresenter.reinit({query: params.query});
loaded(); loaded();
softRender();
} }
function deinit() { function deinit() {
@ -59,13 +64,14 @@ App.Presenters.PostListPresenter = function(
} }
function render() { function render() {
$el.html(templates.list()); $el.html(templates.list({massTag: params.query.massTag, privileges: privileges}));
$searchInput = $el.find('input[name=query]'); $searchInput = $el.find('input[name=query]');
App.Controls.AutoCompleteInput($searchInput); App.Controls.AutoCompleteInput($searchInput);
$searchInput.val(params.query.query); $searchInput.val(params.query.query);
$searchInput.keydown(searchInputKeyPressed); $searchInput.keydown(searchInputKeyPressed);
$el.find('form').submit(searchFormSubmitted); $el.find('form').submit(searchFormSubmitted);
$el.find('[name=mass-tag]').click(massTagButtonClicked);
keyboard.keyup('p', function() { keyboard.keyup('p', function() {
$el.find('.posts li a').eq(0).focus(); $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) { function renderPosts(posts, clear) {
var $target = $el.find('.posts'); var $target = $el.find('.posts');
@ -84,13 +103,59 @@ App.Presenters.PostListPresenter = function(
} }
_.each(posts, function(post) { _.each(posts, function(post) {
var $post = renderPost(post);
softRenderPost($post);
$target.append($post);
});
}
function renderPost(post) {
var $post = jQuery('<li>' + templates.listItem({ var $post = jQuery('<li>' + templates.listItem({
util: util, util: util,
query: params.query, query: params.query,
post: post, post: post,
}) + '</li>'); }) + '</li>');
$post.data('post', post);
util.loadImagesNicely($post.find('img')); util.loadImagesNicely($post.find('img'));
$target.append($post); 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);
}); });
} }
@ -101,6 +166,12 @@ App.Presenters.PostListPresenter = function(
updateSearch(); updateSearch();
} }
function massTagButtonClicked(e) {
e.preventDefault();
params.query.massTag = window.prompt('Enter tag to tag with:');
pagerPresenter.setQuery(params.query);
}
function searchFormSubmitted(e) { function searchFormSubmitted(e) {
e.preventDefault(); e.preventDefault();
updateSearch(); 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);

View file

@ -32,4 +32,8 @@
</div> </div>
<% } %> <% } %>
</a> </a>
<div class="action">
<button>Action</button>
</div>
</div> </div>

View file

@ -1,9 +1,15 @@
<div class="post-list"> <div class="post-list">
<form class="search"> <form class="search">
<input type="text" name="query" placeholder="Search query..."/> <input type="text" name="query" placeholder="Search query..."/>
<button type="submit">Search</button> <button type="submit" name="search">Search</button>
<% if (privileges.canMassTag) { %>
<button name="mass-tag">Mass tag</button>
<% } %>
</form> </form>
<p class="mass-tag-info">Tagging with <span class="mass-tag"><%= massTag %></span></p>
<div class="pagination-target"> <div class="pagination-target">
<div class="wrapper"> <div class="wrapper">
<ul class="posts"> <ul class="posts">

View file

@ -22,6 +22,7 @@ class PostEditFormData implements IValidatable
{ {
$this->content = $inputReader->decodeBase64($inputReader->content); $this->content = $inputReader->decodeBase64($inputReader->content);
$this->thumbnail = $inputReader->decodebase64($inputReader->thumbnail); $this->thumbnail = $inputReader->decodebase64($inputReader->thumbnail);
if ($inputReader->safety)
$this->safety = EnumHelper::postSafetyFromString($inputReader->safety); $this->safety = EnumHelper::postSafetyFromString($inputReader->safety);
$this->source = $inputReader->source; $this->source = $inputReader->source;
$this->tags = preg_split('/[\s+]/', $inputReader->tags); $this->tags = preg_split('/[\s+]/', $inputReader->tags);

View file

@ -35,6 +35,7 @@ class Privilege
const CHANGE_POST_FLAGS = 'changePostFlags'; const CHANGE_POST_FLAGS = 'changePostFlags';
const LIST_TAGS = 'listTags'; const LIST_TAGS = 'listTags';
const MASS_TAG = 'massTag';
const LIST_COMMENTS = 'listComments'; const LIST_COMMENTS = 'listComments';
const ADD_COMMENTS = 'addComments'; const ADD_COMMENTS = 'addComments';

View file

@ -61,7 +61,7 @@ class TagService
{ {
$tagNameGetter = function($tag) $tagNameGetter = function($tag)
{ {
return $tag->getName(); return strtolower($tag->getName());
}; };
$tagNames = array_map($tagNameGetter, $tags); $tagNames = array_map($tagNameGetter, $tags);
@ -86,10 +86,10 @@ class TagService
$result = []; $result = [];
foreach ($tags as $key => $tag) foreach ($tags as $key => $tag)
{ {
if (isset($tagsNotToCreate[$tag->getName()])) if (isset($tagsNotToCreate[$tagNameGetter($tag)]))
$tag = $tagsNotToCreate[$tag->getName()]; $tag = $tagsNotToCreate[$tagNameGetter($tag)];
else else
$tag = $createdTags[$tag->getName()]; $tag = $createdTags[$tagNameGetter($tag)];
$result[$key] = $tag; $result[$key] = $tag;
} }
return $result; return $result;