Implemented mass tag
This commit is contained in:
parent
f2b5124d54
commit
74e6e008dc
11 changed files with 142 additions and 16 deletions
1
TODO
1
TODO
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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',
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,16 +103,62 @@ App.Presenters.PostListPresenter = function(
|
||||||
}
|
}
|
||||||
|
|
||||||
_.each(posts, function(post) {
|
_.each(posts, function(post) {
|
||||||
var $post = jQuery('<li>' + templates.listItem({
|
var $post = renderPost(post);
|
||||||
util: util,
|
softRenderPost($post);
|
||||||
query: params.query,
|
|
||||||
post: post,
|
|
||||||
}) + '</li>');
|
|
||||||
util.loadImagesNicely($post.find('img'));
|
|
||||||
$target.append($post);
|
$target.append($post);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderPost(post) {
|
||||||
|
var $post = jQuery('<li>' + templates.listItem({
|
||||||
|
util: util,
|
||||||
|
query: params.query,
|
||||||
|
post: post,
|
||||||
|
}) + '</li>');
|
||||||
|
$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) {
|
function searchInputKeyPressed(e) {
|
||||||
if (e.which !== KEY_RETURN) {
|
if (e.which !== KEY_RETURN) {
|
||||||
return;
|
return;
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -32,4 +32,8 @@
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<div class="action">
|
||||||
|
<button>Action</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -22,7 +22,8 @@ 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);
|
||||||
$this->safety = EnumHelper::postSafetyFromString($inputReader->safety);
|
if ($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);
|
||||||
$this->relations = array_filter(preg_split('/[\s+]/', $inputReader->relations));
|
$this->relations = array_filter(preg_split('/[\s+]/', $inputReader->relations));
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue