mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-01-05 23:30:36 +01:00
closes #2267
This commit is contained in:
@@ -1,12 +1,21 @@
|
||||
"use strict";
|
||||
/*global define, socket, app, admin, utils, bootbox, RELATIVE_PATH*/
|
||||
|
||||
define('admin/manage/flags', ['forum/infinitescroll', 'admin/modules/selectable'], function(infinitescroll, selectable) {
|
||||
define('admin/manage/flags', [
|
||||
'forum/infinitescroll',
|
||||
'admin/modules/selectable',
|
||||
'autocomplete'
|
||||
], function(infinitescroll, selectable, autocomplete) {
|
||||
|
||||
var Flags = {};
|
||||
|
||||
Flags.init = function() {
|
||||
$('.post-container .content img').addClass('img-responsive');
|
||||
|
||||
var params = utils.params();
|
||||
$('#flag-sort-by').val(params.sortBy);
|
||||
autocomplete.user($('#byUsername'));
|
||||
|
||||
handleDismiss();
|
||||
handleDismissAll();
|
||||
handleDelete();
|
||||
@@ -69,8 +78,15 @@ define('admin/manage/flags', ['forum/infinitescroll', 'admin/modules/selectable'
|
||||
if (direction < 0 && !$('.flags').length) {
|
||||
return;
|
||||
}
|
||||
var params = utils.params();
|
||||
var sortBy = params.sortBy || 'count';
|
||||
var byUsername = params.byUsername || '';
|
||||
|
||||
infinitescroll.loadMore('admin.getMoreFlags', $('[data-next]').attr('data-next'), function(data, done) {
|
||||
infinitescroll.loadMore('admin.getMoreFlags', {
|
||||
byUsername: byUsername,
|
||||
sortBy: sortBy,
|
||||
after: $('[data-next]').attr('data-next')
|
||||
}, function(data, done) {
|
||||
if (data.posts && data.posts.length) {
|
||||
infinitescroll.parseAndTranslate('admin/manage/flags', 'posts', {posts: data.posts}, function(html) {
|
||||
$('[data-next]').attr('data-next', data.next);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/* globals app, define, utils, socket*/
|
||||
|
||||
define('forum/search', ['search'], function(searchModule) {
|
||||
define('forum/search', ['search', 'autocomplete'], function(searchModule, autocomplete) {
|
||||
var Search = {};
|
||||
|
||||
Search.init = function() {
|
||||
@@ -156,25 +156,7 @@ define('forum/search', ['search'], function(searchModule) {
|
||||
}
|
||||
|
||||
function enableAutoComplete() {
|
||||
var input = $('#posted-by-user');
|
||||
input.autocomplete({
|
||||
delay: 100,
|
||||
source: function(request, response) {
|
||||
socket.emit('user.search', {query: request.term}, function(err, result) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
if (result && result.users) {
|
||||
var names = result.users.map(function(user) {
|
||||
return user && user.username;
|
||||
});
|
||||
response(names);
|
||||
}
|
||||
$('.ui-autocomplete a').attr('data-ajaxify', 'false');
|
||||
});
|
||||
}
|
||||
});
|
||||
autocomplete.user($('#posted-by-user'));
|
||||
}
|
||||
|
||||
return Search;
|
||||
|
||||
31
public/src/modules/autocomplete.js
Normal file
31
public/src/modules/autocomplete.js
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
/* globals define, socket, app */
|
||||
|
||||
define('autocomplete', function() {
|
||||
var module = {};
|
||||
|
||||
module.user = function (input) {
|
||||
input.autocomplete({
|
||||
delay: 100,
|
||||
source: function(request, response) {
|
||||
socket.emit('user.search', {query: request.term}, function(err, result) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
if (result && result.users) {
|
||||
var names = result.users.map(function(user) {
|
||||
return user && user.username;
|
||||
});
|
||||
response(names);
|
||||
}
|
||||
$('.ui-autocomplete a').attr('data-ajaxify', 'false');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return module;
|
||||
});
|
||||
@@ -163,14 +163,24 @@ adminController.tags.get = function(req, res, next) {
|
||||
};
|
||||
|
||||
adminController.flags.get = function(req, res, next) {
|
||||
var uid = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
posts.getFlags(uid, 0, 19, function(err, posts) {
|
||||
function done(err, posts) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.render('admin/manage/flags', {posts: posts, next: end + 1, byUsername: byUsername});
|
||||
}
|
||||
var uid = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
var sortBy = req.query.sortBy || 'count';
|
||||
var byUsername = req.query.byUsername || '';
|
||||
var start = 0;
|
||||
var end = 19;
|
||||
|
||||
res.render('admin/manage/flags', {posts: posts, next: 20});
|
||||
});
|
||||
if (byUsername) {
|
||||
posts.getUserFlags(byUsername, sortBy, uid, start, end, done);
|
||||
} else {
|
||||
var set = sortBy === 'count' ? 'posts:flags:count' : 'posts:flagged';
|
||||
posts.getFlags(set, uid, start, end, done);
|
||||
}
|
||||
};
|
||||
|
||||
adminController.database.get = function(req, res, next) {
|
||||
|
||||
@@ -29,7 +29,7 @@ module.exports = function(Posts) {
|
||||
removeFromCategoryRecentPosts(pid, postData.tid, next);
|
||||
},
|
||||
function(next) {
|
||||
db.sortedSetRemove('posts:flagged', pid, next);
|
||||
Posts.dismissFlags(pid, next);
|
||||
}
|
||||
], function(err) {
|
||||
callback(err, postData);
|
||||
@@ -125,6 +125,9 @@ module.exports = function(Posts) {
|
||||
},
|
||||
function(next) {
|
||||
db.sortedSetsRemove(['posts:pid', 'posts:flagged'], pid, next);
|
||||
},
|
||||
function(next) {
|
||||
Posts.dismissFlags(pid, next);
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
|
||||
@@ -3,22 +3,52 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
db = require('../database');
|
||||
db = require('../database'),
|
||||
user = require('../user');
|
||||
|
||||
|
||||
module.exports = function(Posts) {
|
||||
Posts.flag = function(pid, callback) {
|
||||
Posts.exists(pid, function(err, exists) {
|
||||
if (err || !exists) {
|
||||
|
||||
Posts.flag = function(post, uid, callback) {
|
||||
async.parallel({
|
||||
hasFlagged: async.apply(hasFlagged, post.pid, uid),
|
||||
exists: async.apply(Posts.exists, post.pid)
|
||||
}, function(err, results) {
|
||||
if (err || !results.exists) {
|
||||
return callback(err || new Error('[[error:no-post]]'));
|
||||
}
|
||||
|
||||
if (results.hasFlagged) {
|
||||
return callback(new Error('[[error:already-flagged]]'));
|
||||
}
|
||||
var now = Date.now();
|
||||
|
||||
async.parallel([
|
||||
function(next) {
|
||||
db.sortedSetAdd('posts:flagged', Date.now(), pid, next);
|
||||
db.sortedSetAdd('posts:flagged', now, post.pid, next);
|
||||
},
|
||||
function(next) {
|
||||
db.incrObjectField('post:' + pid, 'flags', next);
|
||||
db.sortedSetIncrBy('posts:flags:count', 1, post.pid, next);
|
||||
},
|
||||
function(next) {
|
||||
db.incrObjectField('post:' + post.pid, 'flags', next);
|
||||
},
|
||||
function(next) {
|
||||
db.sortedSetAdd('pid:' + post.pid + ':flag:uids', now, uid, next);
|
||||
},
|
||||
function(next) {
|
||||
if (parseInt(post.uid, 10)) {
|
||||
db.sortedSetAdd('uid:' + post.uid + ':flag:pids', now, post.pid, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
function(next) {
|
||||
if (parseInt(post.uid, 10)) {
|
||||
db.setAdd('uid:' + post.uid + ':flagged_by', uid, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
], function(err, results) {
|
||||
callback(err);
|
||||
@@ -26,14 +56,31 @@ module.exports = function(Posts) {
|
||||
});
|
||||
};
|
||||
|
||||
function hasFlagged(pid, uid, callback) {
|
||||
db.isSortedSetMember('pid:' + pid + ':flag:uids', uid, callback);
|
||||
}
|
||||
|
||||
Posts.dismissFlag = function(pid, callback) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
db.sortedSetRemove('posts:flagged', pid, next);
|
||||
db.getObjectField('post:' + pid, 'uid', function(err, uid) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
db.sortedSetsRemove([
|
||||
'posts:flagged',
|
||||
'posts:flags:count',
|
||||
'uid:' + uid + ':flag:pids'
|
||||
], pid, next);
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
db.deleteObjectField('post:' + pid, 'flags', next);
|
||||
}
|
||||
},
|
||||
function(next) {
|
||||
db.delete('pid:' + pid + ':flag:uids', next);
|
||||
}
|
||||
], function(err, results) {
|
||||
callback(err);
|
||||
});
|
||||
@@ -43,8 +90,8 @@ module.exports = function(Posts) {
|
||||
db.delete('posts:flagged', callback);
|
||||
};
|
||||
|
||||
Posts.getFlags = function(uid, start, end, callback) {
|
||||
db.getSortedSetRevRange('posts:flagged', start, end, function(err, pids) {
|
||||
Posts.getFlags = function(set, uid, start, end, callback) {
|
||||
db.getSortedSetRevRange(set, start, end, function(err, pids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -52,4 +99,29 @@ module.exports = function(Posts) {
|
||||
Posts.getPostSummaryByPids(pids, uid, {stripTags: false, extraFields: ['flags']}, callback);
|
||||
});
|
||||
};
|
||||
|
||||
Posts.getUserFlags = function(byUsername, sortBy, callerUID, start, end, callback) {
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
user.getUidByUsername(byUsername, next);
|
||||
},
|
||||
function(uid, next) {
|
||||
if (!uid) {
|
||||
return next(null, []);
|
||||
}
|
||||
db.getSortedSetRevRange('uid:' + uid + ':flag:pids', 0, -1, next);
|
||||
},
|
||||
function(pids, next) {
|
||||
Posts.getPostSummaryByPids(pids, callerUID, {stripTags: false, extraFields: ['flags']}, next);
|
||||
},
|
||||
function(posts, next) {
|
||||
if (sortBy === 'count') {
|
||||
posts.sort(function(a, b) {
|
||||
return b.flags - a.flags;
|
||||
});
|
||||
}
|
||||
next(null, posts.slice(start, end));
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -311,14 +311,24 @@ SocketAdmin.dismissAllFlags = function(socket, data, callback) {
|
||||
posts.dismissAllFlags(callback);
|
||||
};
|
||||
|
||||
SocketAdmin.getMoreFlags = function(socket, after, callback) {
|
||||
if (!parseInt(after, 10)) {
|
||||
SocketAdmin.getMoreFlags = function(socket, data, callback) {
|
||||
if (!data || !parseInt(data.after, 10)) {
|
||||
return callback('[[error:invalid-data]]');
|
||||
}
|
||||
after = parseInt(after, 10);
|
||||
posts.getFlags(socket.uid, after, after + 19, function(err, posts) {
|
||||
callback(err, {posts: posts, next: after + 20});
|
||||
});
|
||||
var sortBy = data.sortBy || 'count';
|
||||
var byUsername = data.byUsername || '';
|
||||
var start = parseInt(data.after, 10);
|
||||
var end = start + 19;
|
||||
if (byUsername) {
|
||||
posts.getUserFlags(byUsername, sortBy, socket.uid, start, end, function(err, posts) {
|
||||
callback(err, {posts: posts, next: end + 1});
|
||||
});
|
||||
} else {
|
||||
var set = sortBy === 'count' ? 'posts:flags:count' : 'posts:flagged';
|
||||
posts.getFlags(set, socket.uid, start, end, function(err, posts) {
|
||||
callback(err, {posts: posts, next: end + 1});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
SocketAdmin.takeHeapSnapshot = function(socket, data, callback) {
|
||||
|
||||
@@ -422,14 +422,14 @@ SocketPosts.flag = function(socket, pid, callback) {
|
||||
return next(new Error('[[error:not-enough-reputation-to-flag]]'));
|
||||
}
|
||||
userName = userData.username;
|
||||
posts.getPostFields(pid, ['tid', 'uid', 'content', 'deleted'], next);
|
||||
posts.getPostFields(pid, ['pid', 'tid', 'uid', 'content', 'deleted'], next);
|
||||
},
|
||||
function(postData, next) {
|
||||
if (parseInt(postData.deleted, 10) === 1) {
|
||||
return next(new Error('[[error:post-deleted]]'));
|
||||
}
|
||||
post = postData;
|
||||
posts.flag(pid, next);
|
||||
posts.flag(post, socket.uid, next);
|
||||
},
|
||||
function(next) {
|
||||
topics.getTopicFields(post.tid, ['title', 'cid'], next);
|
||||
@@ -462,14 +462,7 @@ SocketPosts.flag = function(socket, pid, callback) {
|
||||
}
|
||||
notifications.push(notification, results.admins.concat(results.moderators), next);
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
if (!parseInt(post.uid, 10)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
db.setAdd('uid:' + post.uid + ':flagged_by', socket.uid, next);
|
||||
}
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ module.exports = function(User) {
|
||||
'uid:' + uid + ':topics', 'uid:' + uid + ':posts',
|
||||
'uid:' + uid + ':chats', 'uid:' + uid + ':chats:unread',
|
||||
'uid:' + uid + ':upvote', 'uid:' + uid + ':downvote',
|
||||
'uid:' + uid + ':ignored:cids'
|
||||
'uid:' + uid + ':ignored:cids', 'uid:' + uid + ':flag:pids'
|
||||
];
|
||||
db.deleteAll(keys, next);
|
||||
},
|
||||
|
||||
@@ -2,42 +2,69 @@
|
||||
<div class="col-lg-9">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><i class="fa fa-flag"></i> Flags</div>
|
||||
<div class="panel-body post-container" data-next="{next}">
|
||||
|
||||
<!-- IF !posts.length -->
|
||||
No flagged posts!
|
||||
<!-- ENDIF !posts.length-->
|
||||
|
||||
<!-- BEGIN posts -->
|
||||
<div>
|
||||
<div class="panel panel-default" data-pid="{posts.pid}" data-tid="{posts.topic.tid}">
|
||||
<div class="panel-body">
|
||||
<a href="{relative_path}/user/{posts.user.userslug}">
|
||||
<img title="{posts.user.username}" class="img-rounded user-img" src="{posts.user.picture}">
|
||||
</a>
|
||||
|
||||
<a href="{relative_path}/user/{posts.user.userslug}">
|
||||
<strong><span>{posts.user.username}</span></strong>
|
||||
</a>
|
||||
<div class="content">
|
||||
<p>{posts.content}</p>
|
||||
<p class="fade-out"></p>
|
||||
<div class="panel-body" data-next="{next}">
|
||||
<form id="flag-search" method="GET" action="flags">
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label>Flags by user</label>
|
||||
<input type="text" class="form-control" id="byUsername" placeholder="Search flagged posts by username" name="byUsername" value="{byUsername}">
|
||||
</div>
|
||||
<small>
|
||||
<span class="pull-right footer">
|
||||
Posted in <a href="{relative_path}/category/{posts.category.slug}" target="_blank"><i class="fa {posts.category.icon}"></i> {posts.category.name}</a>, <span class="timeago" title="{posts.relativeTime}"></span> •
|
||||
<a href="{relative_path}/topic/{posts.topic.slug}/{posts.index}" target="_blank">Read More</a>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="badge badge-warning"><i class="fa fa-flag"></i> {posts.flags}</span>
|
||||
<button class="btn btn-warning dismiss">Dismiss</button>
|
||||
<button class="btn btn-danger delete">Delete</button>
|
||||
<br/><br/>
|
||||
<div class="form-group">
|
||||
<label>Sort By</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<select id="flag-sort-by" class="form-control" name="sortBy">
|
||||
<option value="count">Most Flags</option>
|
||||
<option value="time">Most Recent</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-default">[[global:search]]</button>
|
||||
</form>
|
||||
<hr/>
|
||||
|
||||
<div class="post-container" data-next="{next}">
|
||||
<!-- IF !posts.length -->
|
||||
No flagged posts!
|
||||
<!-- ENDIF !posts.length-->
|
||||
|
||||
<!-- BEGIN posts -->
|
||||
<div>
|
||||
<div class="panel panel-default" data-pid="{posts.pid}" data-tid="{posts.topic.tid}">
|
||||
<div class="panel-body">
|
||||
<a href="{relative_path}/user/{posts.user.userslug}">
|
||||
<img title="{posts.user.username}" class="img-rounded user-img" src="{posts.user.picture}">
|
||||
</a>
|
||||
|
||||
<a href="{relative_path}/user/{posts.user.userslug}">
|
||||
<strong><span>{posts.user.username}</span></strong>
|
||||
</a>
|
||||
<div class="content">
|
||||
<p>{posts.content}</p>
|
||||
<p class="fade-out"></p>
|
||||
</div>
|
||||
<small>
|
||||
<span class="pull-right footer">
|
||||
Posted in <a href="{relative_path}/category/{posts.category.slug}" target="_blank"><i class="fa {posts.category.icon}"></i> {posts.category.name}</a>, <span class="timeago" title="{posts.relativeTime}"></span> •
|
||||
<a href="{relative_path}/topic/{posts.topic.slug}/{posts.index}" target="_blank">Read More</a>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="badge badge-warning"><i class="fa fa-flag"></i> {posts.flags}</span>
|
||||
<button class="btn btn-warning dismiss">Dismiss</button>
|
||||
<button class="btn btn-danger delete">Delete</button>
|
||||
<br/><br/>
|
||||
</div>
|
||||
<!-- END posts -->
|
||||
</div>
|
||||
<!-- END posts -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user