mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-06-17 00:21:30 +02:00
merge
This commit is contained in:
@@ -36,6 +36,22 @@
|
||||
|
||||
"flag_title": "Flag this post for moderation",
|
||||
"flag_success": "This post has been flagged for moderation.",
|
||||
"flag_manage_title": "Flagged post in %1",
|
||||
"flag_manage_history": "Action History",
|
||||
"flag_manage_no_history": "No event history to report",
|
||||
"flag_manage_assignee": "Assignee",
|
||||
"flag_manage_state": "State",
|
||||
"flag_manage_state_open": "New/Open",
|
||||
"flag_manage_state_wip": "Work in Progress",
|
||||
"flag_manage_state_resolved": "Resolved",
|
||||
"flag_manage_state_rejected": "Rejected",
|
||||
"flag_manage_notes": "Shared Notes",
|
||||
"flag_manage_update": "Update Flag Status",
|
||||
"flag_manage_history_assignee": "Assigned to %1",
|
||||
"flag_manage_history_state": "Updated state to %1",
|
||||
"flag_manage_history_notes": "Updated flag notes",
|
||||
"flag_manage_saved": "Flag Details Updated",
|
||||
|
||||
"deleted_message": "This topic has been deleted. Only users with topic management privileges can see it.",
|
||||
|
||||
"following_topic.message": "You will now be receiving notifications when somebody posts to this topic.",
|
||||
|
||||
@@ -31,4 +31,8 @@
|
||||
.user-icon-style(24px, 1.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
[component="posts/flag/history"] .avatar {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,9 @@
|
||||
define('admin/manage/flags', [
|
||||
'forum/infinitescroll',
|
||||
'autocomplete',
|
||||
'Chart'
|
||||
], function(infinitescroll, autocomplete, Chart) {
|
||||
'Chart',
|
||||
'components'
|
||||
], function(infinitescroll, autocomplete, Chart, components) {
|
||||
|
||||
var Flags = {};
|
||||
|
||||
@@ -21,6 +22,10 @@ define('admin/manage/flags', [
|
||||
handleDelete();
|
||||
handleInfiniteScroll();
|
||||
handleGraphs();
|
||||
|
||||
updateFlagDetails(ajaxify.data.posts);
|
||||
|
||||
components.get('posts/flags').on('click', '[component="posts/flag/update"]', updateFlag);
|
||||
};
|
||||
|
||||
function handleDismiss() {
|
||||
@@ -89,10 +94,14 @@ define('admin/manage/flags', [
|
||||
after: $('[data-next]').attr('data-next')
|
||||
}, function(data, done) {
|
||||
if (data.posts && data.posts.length) {
|
||||
app.parseAndTranslate('admin/manage/flags', 'posts', {posts: data.posts}, function(html) {
|
||||
app.parseAndTranslate('admin/manage/flags', 'posts', {
|
||||
posts: data.posts,
|
||||
assignees: ajaxify.data.assignees
|
||||
}, function(html) {
|
||||
$('[data-next]').attr('data-next', data.next);
|
||||
$('.post-container').append(html);
|
||||
html.find('img:not(.not-responsive)').addClass('img-responsive');
|
||||
updateFlagDetails(data.posts);
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
@@ -150,5 +159,44 @@ define('admin/manage/flags', [
|
||||
});
|
||||
}
|
||||
|
||||
function updateFlagDetails(source) {
|
||||
// As the flag details are returned in the API, update the form controls to show the correct data
|
||||
|
||||
// Create reference hash for use in this method
|
||||
source = source.reduce(function(memo, cur) {
|
||||
memo[cur.pid] = cur.flagData;
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
components.get('posts/flag').each(function(idx, el) {
|
||||
var pid = el.getAttribute('data-pid');
|
||||
var el = $(el);
|
||||
|
||||
if (source[pid]) {
|
||||
for(var prop in source[pid]) {
|
||||
if (source[pid].hasOwnProperty(prop)) {
|
||||
el.find('[name="' + prop + '"]').val(source[pid][prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateFlag() {
|
||||
var pid = $(this).parents('[component="posts/flag"]').attr('data-pid');
|
||||
var formData = $($(this).parents('form').get(0)).serializeArray();
|
||||
|
||||
socket.emit('posts.updateFlag', {
|
||||
pid: pid,
|
||||
data: formData
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
} else {
|
||||
app.alertSuccess('[[topic:flag_manage_saved]]');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Flags;
|
||||
});
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
var async = require('async');
|
||||
var posts = require('../../posts');
|
||||
var user = require('../../user');
|
||||
var analytics = require('../../analytics');
|
||||
|
||||
var flagsController = {};
|
||||
@@ -25,15 +26,32 @@ flagsController.get = function(req, res, next) {
|
||||
},
|
||||
analytics: function(next) {
|
||||
analytics.getDailyStatsForSet('analytics:flags', Date.now(), 30, next);
|
||||
}
|
||||
},
|
||||
assignees: async.apply(user.getAdminsandGlobalMods)
|
||||
}, next);
|
||||
}
|
||||
], function (err, results) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// Minimise data set for assignees so tjs does less work
|
||||
results.assignees = results.assignees.map(function(userObj) {
|
||||
var keep = ['uid', 'username'];
|
||||
for(var prop in userObj) {
|
||||
if (userObj.hasOwnProperty(prop)) {
|
||||
if (keep.indexOf(prop) === -1) {
|
||||
delete userObj[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return userObj;
|
||||
});
|
||||
|
||||
var data = {
|
||||
posts: results.posts,
|
||||
assignees: results.assignees,
|
||||
analytics: results.analytics,
|
||||
next: stop + 1,
|
||||
byUsername: byUsername,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
var db = require('../database');
|
||||
var user = require('../user');
|
||||
var analytics = require('../analytics');
|
||||
@@ -171,7 +172,7 @@ module.exports = function(Posts) {
|
||||
}, next);
|
||||
},
|
||||
posts: function(next) {
|
||||
Posts.getPostSummaryByPids(pids, uid, {stripTags: false, extraFields: ['flags']}, next);
|
||||
Posts.getPostSummaryByPids(pids, uid, {stripTags: false, extraFields: ['flags', 'flag:assignee', 'flag:state', 'flag:notes', 'flag:history']}, next);
|
||||
}
|
||||
}, next);
|
||||
},
|
||||
@@ -190,6 +191,8 @@ module.exports = function(Posts) {
|
||||
}
|
||||
|
||||
results.posts.forEach(function(post, index) {
|
||||
var history;
|
||||
|
||||
if (post) {
|
||||
post.flagReasons = reasons[index];
|
||||
}
|
||||
@@ -197,6 +200,42 @@ module.exports = function(Posts) {
|
||||
|
||||
next(null, results.posts);
|
||||
});
|
||||
},
|
||||
async.apply(Posts.expandFlagHistory),
|
||||
function(posts, next) {
|
||||
// Parse out flag data into its own object inside each post hash
|
||||
posts = posts.map(function(postObj) {
|
||||
for(var prop in postObj) {
|
||||
postObj.flagData = postObj.flagData || {};
|
||||
|
||||
if (postObj.hasOwnProperty(prop) && prop.startsWith('flag:')) {
|
||||
postObj.flagData[prop.slice(5)] = postObj[prop];
|
||||
|
||||
if (prop === 'flag:state') {
|
||||
switch(postObj[prop]) {
|
||||
case 'open':
|
||||
postObj.flagData.labelClass = 'info';
|
||||
break;
|
||||
case 'wip':
|
||||
postObj.flagData.labelClass = 'warning';
|
||||
break;
|
||||
case 'resolved':
|
||||
postObj.flagData.labelClass = 'success';
|
||||
break;
|
||||
case 'rejected':
|
||||
postObj.flagData.labelClass = 'danger';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete postObj[prop];
|
||||
}
|
||||
}
|
||||
|
||||
return postObj;
|
||||
});
|
||||
|
||||
setImmediate(next.bind(null, null, posts));
|
||||
}
|
||||
], callback);
|
||||
}
|
||||
@@ -226,4 +265,130 @@ module.exports = function(Posts) {
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
Posts.updateFlagData = function(uid, pid, flagObj, callback) {
|
||||
// Retrieve existing flag data to compare for history-saving purposes
|
||||
var changes = [];
|
||||
var changeset = {};
|
||||
var prop;
|
||||
|
||||
Posts.getPostData(pid, function(err, postData) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Track new additions
|
||||
for(prop in flagObj) {
|
||||
if (flagObj.hasOwnProperty(prop) && !postData.hasOwnProperty('flag:' + prop)) {
|
||||
changes.push(prop);
|
||||
}
|
||||
|
||||
// Generate changeset for object modification
|
||||
if (flagObj.hasOwnProperty(prop)) {
|
||||
changeset['flag:' + prop] = flagObj[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// Track changed items
|
||||
for(prop in postData) {
|
||||
if (
|
||||
postData.hasOwnProperty(prop) && prop.startsWith('flag:') &&
|
||||
flagObj.hasOwnProperty(prop.slice(5)) &&
|
||||
postData[prop] !== flagObj[prop.slice(5)]
|
||||
) {
|
||||
changes.push(prop.slice(5));
|
||||
}
|
||||
}
|
||||
|
||||
// Append changes to history string
|
||||
if (changes.length) {
|
||||
try {
|
||||
var history = JSON.parse(postData['flag:history'] || '[]');
|
||||
|
||||
changes.forEach(function(property) {
|
||||
switch(property) {
|
||||
case 'assignee': // intentional fall-through
|
||||
case 'state':
|
||||
history.unshift({
|
||||
uid: uid,
|
||||
type: property,
|
||||
value: flagObj[property],
|
||||
timestamp: Date.now()
|
||||
});
|
||||
break;
|
||||
|
||||
case 'notes':
|
||||
history.unshift({
|
||||
uid: uid,
|
||||
type: property,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
changeset['flag:history'] = JSON.stringify(history);
|
||||
} catch (e) {
|
||||
winston.warn('[posts/updateFlagData] Unable to deserialise post flag history, likely malformed data');
|
||||
}
|
||||
}
|
||||
|
||||
// Save flag data into post hash
|
||||
Posts.setPostFields(pid, changeset, callback);
|
||||
});
|
||||
};
|
||||
|
||||
Posts.expandFlagHistory = function(posts, callback) {
|
||||
// Expand flag history
|
||||
async.map(posts, function(post, next) {
|
||||
try {
|
||||
var history = JSON.parse(post['flag:history'] || '[]');
|
||||
} catch (e) {
|
||||
winston.warn('[posts/getFlags] Unable to deserialise post flag history, likely malformed data');
|
||||
callback(e);
|
||||
}
|
||||
|
||||
async.map(history, function(event, next) {
|
||||
event.timestampISO = new Date(event.timestamp).toISOString();
|
||||
|
||||
async.parallel([
|
||||
function(next) {
|
||||
user.getUserFields(event.uid, ['username', 'picture'], function(err, userData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
event.user = userData;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
if (event.type === 'assignee') {
|
||||
user.getUserField(parseInt(event.value, 10), 'username', function(err, username) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
event.label = username || 'Unknown user';
|
||||
next(null);
|
||||
});
|
||||
} else if (event.type === 'state') {
|
||||
event.label = '[[topic:flag_manage_state_' + event.value + ']]';
|
||||
setImmediate(next);
|
||||
} else {
|
||||
setImmediate(next);
|
||||
}
|
||||
}
|
||||
], function(err) {
|
||||
next(err, event);
|
||||
})
|
||||
}, function(err, history) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
post['flag:history'] = history;
|
||||
next(null, post);
|
||||
});
|
||||
}, callback);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -163,7 +163,35 @@ module.exports = function(SocketPosts) {
|
||||
},
|
||||
function (posts, next) {
|
||||
next(null, {posts: posts, next: stop + 1});
|
||||
},
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
SocketPosts.updateFlag = function(socket, data, callback) {
|
||||
if (!data || !(data.pid && data.data)) {
|
||||
return callback('[[error:invalid-data]]');
|
||||
}
|
||||
|
||||
var payload = {};
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.isAdminOrGlobalMod(socket.uid, next);
|
||||
},
|
||||
function (isAdminOrGlobalModerator, next) {
|
||||
if (!isAdminOrGlobalModerator) {
|
||||
return next(new Error('[[no-privileges]]'));
|
||||
}
|
||||
|
||||
// Translate form data into object
|
||||
payload = data.data.reduce(function(memo, cur) {
|
||||
memo[cur.name] = cur.value;
|
||||
return memo;
|
||||
}, payload);
|
||||
|
||||
next(null, socket.uid, data.pid, payload);
|
||||
},
|
||||
async.apply(posts.updateFlagData)
|
||||
], callback);
|
||||
}
|
||||
};
|
||||
|
||||
14
src/user.js
14
src/user.js
@@ -2,6 +2,7 @@
|
||||
|
||||
var async = require('async');
|
||||
|
||||
var groups = require('./groups');
|
||||
var plugins = require('./plugins');
|
||||
var db = require('./database');
|
||||
var topics = require('./topics');
|
||||
@@ -260,6 +261,19 @@ var utils = require('../public/src/utils');
|
||||
});
|
||||
};
|
||||
|
||||
User.getAdminsandGlobalMods = function(callback) {
|
||||
async.parallel({
|
||||
admins: async.apply(groups.getMembers, 'administrators', 0, -1),
|
||||
mods: async.apply(groups.getMembers, 'Global Moderators', 0, -1)
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
User.getUsersData(results.admins.concat(results.mods), callback);
|
||||
});
|
||||
};
|
||||
|
||||
User.addInterstitials = function(callback) {
|
||||
plugins.registerHook('core', {
|
||||
hook: 'filter:register.interstitial',
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<div data-next="{next}">
|
||||
|
||||
<div class="post-container" data-next="{next}">
|
||||
<div component="posts/flags" class="panel-group post-container" id="accordion" role="tablist" aria-multiselectable="true" data-next="{next}">
|
||||
<!-- IF !posts.length -->
|
||||
<div class="alert alert-success">
|
||||
No flagged posts!
|
||||
@@ -52,54 +52,117 @@
|
||||
<!-- ENDIF !posts.length -->
|
||||
|
||||
<!-- BEGIN posts -->
|
||||
<div class="row" data-pid="{posts.pid}" data-tid="{posts.topic.tid}">
|
||||
<div class="col-sm-8">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body flag-post-body">
|
||||
<a href="{config.relative_path}/user/{../user.userslug}">
|
||||
<!-- IF ../user.picture -->
|
||||
<img title="{posts.user.username}" src="{../user.picture}">
|
||||
<!-- ELSE -->
|
||||
<div class="user-icon" style="background-color: {../user.icon:bgColor};">{../user.icon:text}</div>
|
||||
<!-- ENDIF ../user.picture -->
|
||||
</a>
|
||||
|
||||
<a href="{config.relative_path}/user/{../user.userslug}">
|
||||
<strong><span>{../user.username}</span></strong>
|
||||
</a>
|
||||
<div class="content">
|
||||
<p>{posts.content}</p>
|
||||
</div>
|
||||
<small>
|
||||
<span class="pull-right">
|
||||
Posted in <a href="{config.relative_path}/category/{posts.category.slug}" target="_blank"><i class="fa {posts.category.icon}"></i> {posts.category.name}</a>, <span class="timeago" title="{posts.timestampISO}"></span> •
|
||||
<a href="{config.relative_path}/topic/{posts.topic.slug}/{posts.index}" target="_blank">Read More</a>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default" component="posts/flag" data-pid="{../pid}">
|
||||
<div class="panel-heading" role="tab">
|
||||
<h4 class="panel-title">
|
||||
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#flag-pid-{posts.pid}" aria-expanded="true" aria-controls="flag-pid-{posts.pid}">
|
||||
<span class="label <!-- IF ../flagData.labelClass -->label-{../flagData.labelClass}<!-- ELSE -->label-info<!-- ENDIF ../flagData.labelClass -->">[[topic:flag_manage_state_<!-- IF ../flagData.state -->{../flagData.state}<!-- ELSE -->open<!-- ENDIF ../flagData.state -->]]</span>
|
||||
[[topic:flag_manage_title, {posts.category.name}]]
|
||||
<small><span class="timeago" title="{posts.timestampISO}"></span></small>
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<i class="fa fa-flag"></i> This post has been flagged {posts.flags} time(s):
|
||||
<blockquote class="flag-reporters">
|
||||
<ul>
|
||||
<!-- BEGIN posts.flagReasons -->
|
||||
<li>
|
||||
<a target="_blank" href="{config.relative_path}/user/{../user.userslug}">
|
||||
<!-- IF ../user.picture -->
|
||||
<img src="{../user.picture}" />
|
||||
<!-- ELSE -->
|
||||
<div class="user-icon" style="background-color: {../user.icon:bgColor};">{../user.icon:text}</div>
|
||||
<!-- ENDIF ../user.picture -->
|
||||
{../user.username}
|
||||
</a>: "{../reason}"
|
||||
</li>
|
||||
<!-- END posts.flagReasons -->
|
||||
</ul>
|
||||
</blockquote>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-success dismiss">Dismiss this Flag</button>
|
||||
<button class="btn btn-sm btn-danger delete">Delete the Post</button>
|
||||
<div id="flag-pid-{posts.pid}" class="panel-collapse collapse" role="tabpanel">
|
||||
<div class="panel-body">
|
||||
<div class="row" data-pid="{posts.pid}" data-tid="{posts.topic.tid}">
|
||||
<div class="col-sm-8">
|
||||
<div class="well flag-post-body">
|
||||
<a href="{config.relative_path}/user/{../user.userslug}">
|
||||
<!-- IF ../user.picture -->
|
||||
<img title="{posts.user.username}" src="{../user.picture}">
|
||||
<!-- ELSE -->
|
||||
<div class="user-icon" style="background-color: {../user.icon:bgColor};">{../user.icon:text}</div>
|
||||
<!-- ENDIF ../user.picture -->
|
||||
</a>
|
||||
|
||||
<a href="{config.relative_path}/user/{../user.userslug}">
|
||||
<strong><span>{../user.username}</span></strong>
|
||||
</a>
|
||||
<div class="content">
|
||||
<p>{posts.content}</p>
|
||||
</div>
|
||||
<small>
|
||||
<span class="pull-right">
|
||||
Posted in <a href="{config.relative_path}/category/{posts.category.slug}" target="_blank"><i class="fa {posts.category.icon}"></i> {posts.category.name}</a>, <span class="timeago" title="{posts.timestampISO}"></span> •
|
||||
<a href="{config.relative_path}/topic/{posts.topic.slug}/{posts.index}" target="_blank">Read More</a>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<i class="fa fa-flag"></i> This post has been flagged {posts.flags} time(s):
|
||||
<blockquote class="flag-reporters">
|
||||
<ul>
|
||||
<!-- BEGIN posts.flagReasons -->
|
||||
<li>
|
||||
<a target="_blank" href="{config.relative_path}/user/{../user.userslug}">
|
||||
<!-- IF ../user.picture -->
|
||||
<img src="{../user.picture}" />
|
||||
<!-- ELSE -->
|
||||
<div class="user-icon" style="background-color: {../user.icon:bgColor};">{../user.icon:text}</div>
|
||||
<!-- ENDIF ../user.picture -->
|
||||
{../user.username}
|
||||
</a>: "{posts.flagReasons.reason}"
|
||||
</li>
|
||||
<!-- END posts.flagReasons -->
|
||||
</ul>
|
||||
</blockquote>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-success dismiss">Dismiss this Flag</button>
|
||||
<button class="btn btn-sm btn-danger delete">Delete the Post</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<form role="form">
|
||||
<div class="form-group">
|
||||
<label for="{posts.pid}-assignee">[[topic:flag_manage_assignee]]</label>
|
||||
<select class="form-control" id="{posts.pid}-assignee" name="assignee">
|
||||
<option value="">No Assignee</option>
|
||||
<!-- BEGIN assignees -->
|
||||
<option value="{assignees.uid}">{assignees.username}</option>
|
||||
<!-- END assignees -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{posts.pid}-state">[[topic:flag_manage_state]]</label>
|
||||
<select class="form-control" id="{posts.pid}-state" name="state">
|
||||
<option value="open">[[topic:flag_manage_state_open]]</option>
|
||||
<option value="wip">[[topic:flag_manage_state_wip]]</option>
|
||||
<option value="resolved">[[topic:flag_manage_state_resolved]]</option>
|
||||
<option value="rejected">[[topic:flag_manage_state_rejected]]</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{posts.pid}-notes">[[topic:flag_manage_notes]]</label>
|
||||
<textarea class="form-control" id="{posts.pid}-notes" name="notes"></textarea>
|
||||
</div>
|
||||
<button type="button" component="posts/flag/update" class="btn btn-sm btn-primary btn-block">[[topic:flag_manage_update]]</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<h5>[[topic:flag_manage_history]]</h5>
|
||||
<!-- IF !posts.flagData.history.length -->
|
||||
<div class="alert alert-info">[[topic:flag_manage_no_history]]</div>
|
||||
<!-- ELSE -->
|
||||
<ul class="list-group" component="posts/flag/history">
|
||||
<!-- BEGIN posts.flagData.history -->
|
||||
<li class="list-group-item">
|
||||
<div class="pull-right"><small><span class="timeago" title="{posts.flagData.history.timestampISO}"></span></small></div>
|
||||
<!-- IF ../user.picture -->
|
||||
<img class="avatar avatar-sm avatar-rounded" src="{../user.picture}" title="{../user.username}" />
|
||||
<!-- ELSE -->
|
||||
<div class="avatar avatar-sm avatar-rounded" style="background-color: {../user.icon:bgColor};" title="{../user.username}">{../user.icon:text}</div>
|
||||
<!-- ENDIF ../user.picture -->
|
||||
[[topic:flag_manage_history_{posts.flagData.history.type}, {posts.flagData.history.label}]]
|
||||
</li>
|
||||
<!-- END posts.flagData.history -->
|
||||
</ul>
|
||||
<!-- ENDIF !posts.flagData.history.length -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user