diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index 88b1662e3b..1320e21923 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -90,6 +90,7 @@ "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "composer.title_placeholder": "Enter your topic title here...", + "composer.handle_placeholder": "Name", "composer.discard": "Discard", "composer.submit": "Submit", "composer.replying_to": "Replying to %1", diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js index e9ae8e8b5d..5a80616517 100644 --- a/public/src/modules/composer.js +++ b/public/src/modules/composer.js @@ -154,6 +154,8 @@ define('composer', [ push({ pid: pid, + uid: threadData.uid, + handle: threadData.handle, title: $('
').html(threadData.title).text(), body: threadData.body, modified: false, @@ -213,9 +215,11 @@ define('composer', [ } function createNewComposer(post_uuid) { - var allowTopicsThumbnail = config.allowTopicsThumbnail && composer.posts[post_uuid].isMain && (config.hasImageUploadPlugin || config.allowFileUploads); - var isTopic = composer.posts[post_uuid] ? !!composer.posts[post_uuid].cid : false; - var isMain = composer.posts[post_uuid] ? !!composer.posts[post_uuid].isMain : false; + var allowTopicsThumbnail = config.allowTopicsThumbnail && composer.posts[post_uuid].isMain && (config.hasImageUploadPlugin || config.allowFileUploads), + isTopic = composer.posts[post_uuid] ? !!composer.posts[post_uuid].cid : false, + isMain = composer.posts[post_uuid] ? !!composer.posts[post_uuid].isMain : false, + isEditing = composer.posts[post_uuid] ? !!composer.posts[post_uuid].pid : false, + isGuestPost = composer.posts[post_uuid] ? composer.posts[post_uuid].uid === '0' : null; composer.bsEnvironment = utils.findBootstrapEnvironment(); @@ -224,7 +228,9 @@ define('composer', [ var data = { allowTopicsThumbnail: allowTopicsThumbnail, showTags: isTopic || isMain, - isTopic: isTopic + isTopic: isTopic, + showHandleInput: (app.user.uid === 0 || (isEditing && isGuestPost && app.user.isAdmin)) && config.allowGuestHandles, + handle: composer.posts[post_uuid] ? composer.posts[post_uuid].handle || '' : undefined }; parseAndTranslate(template, data, function(composerTemplate) { @@ -377,6 +383,7 @@ define('composer', [ function post(post_uuid) { var postData = composer.posts[post_uuid], postContainer = $('#cmp-uuid-' + post_uuid), + handleEl = postContainer.find('.handle'), titleEl = postContainer.find('.title'), bodyEl = postContainer.find('textarea'), thumbEl = postContainer.find('input#topic-thumb-url'); @@ -405,6 +412,7 @@ define('composer', [ if (parseInt(postData.cid, 10) > 0) { composerData = { + handle: handleEl ? handleEl.val() : undefined, title: titleEl.val(), content: bodyEl.val(), topic_thumb: thumbEl.val() || '', @@ -423,6 +431,7 @@ define('composer', [ } else if (parseInt(postData.tid, 10) > 0) { composerData = { tid: postData.tid, + handle: handleEl ? handleEl.val() : undefined, content: bodyEl.val(), toPid: postData.toPid }; @@ -432,6 +441,7 @@ define('composer', [ } else if (parseInt(postData.pid, 10) > 0) { composerData = { pid: postData.pid, + handle: handleEl ? handleEl.val() : undefined, content: bodyEl.val(), title: titleEl.val(), topic_thumb: thumbEl.val() || '', diff --git a/src/controllers/api.js b/src/controllers/api.js index 2648f944b4..52bfd6233f 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -30,6 +30,7 @@ apiController.getConfig = function(req, res, next) { config.maximumSignatureLength = meta.config.maximumSignatureLength; config.useOutgoingLinksPage = parseInt(meta.config.useOutgoingLinksPage, 10) === 1; config.allowGuestSearching = parseInt(meta.config.allowGuestSearching, 10) === 1; + config.allowGuestHandles = parseInt(meta.config.allowGuestHandles, 10) === 1; config.allowFileUploads = parseInt(meta.config.allowFileUploads, 10) === 1; config.allowTopicsThumbnail = parseInt(meta.config.allowTopicsThumbnail, 10) === 1; config.allowAccountDelete = parseInt(meta.config.allowAccountDelete, 10) === 1; diff --git a/src/postTools.js b/src/postTools.js index 0e8d3934ce..b157ead650 100644 --- a/src/postTools.js +++ b/src/postTools.js @@ -18,21 +18,22 @@ var winston = require('winston'), (function(PostTools) { - PostTools.edit = function(uid, pid, title, content, options, callback) { - options = options || {}; + PostTools.edit = function(data, callback) { + var options = data.options || {}, + title = data.title.trim(); async.waterfall([ function (next) { - privileges.posts.canEdit(pid, uid, next); + privileges.posts.canEdit(data.pid, data.uid, next); }, function(canEdit, next) { if (!canEdit) { return next(new Error('[[error:no-privileges]]')); } - posts.getPostData(pid, next); + posts.getPostData(data.pid, next); }, function(postData, next) { - postData.content = content; + postData.content = data.content; plugins.fireHook('filter:post.save', postData, next); } ], function(err, postData) { @@ -42,15 +43,16 @@ var winston = require('winston'), async.parallel({ post: function(next) { - posts.setPostFields(pid, { + posts.setPostFields(data.pid, { edited: Date.now(), - editor: uid, + editor: data.uid, + handle: data.handle, content: postData.content }, next); }, topic: function(next) { var tid = postData.tid; - posts.isMain(pid, function(err, isMainPost) { + posts.isMain(data.pid, function(err, isMainPost) { if (err) { return next(err); } @@ -64,11 +66,9 @@ var winston = require('winston'), }); } - title = title.trim(); - var topicData = { tid: tid, - mainPid: pid, + mainPid: data.pid, title: title, slug: tid + '/' + utils.slugify(title) }; @@ -96,7 +96,7 @@ var winston = require('winston'), }); }, postData: function(next) { - PostTools.parsePost(postData, uid, next); + PostTools.parsePost(postData, data.uid, next); } }, function(err, results) { if (err) { diff --git a/src/posts/create.js b/src/posts/create.js index d9040e443e..198b9baae4 100644 --- a/src/posts/create.js +++ b/src/posts/create.js @@ -14,10 +14,10 @@ module.exports = function(Posts) { Posts.create = function(data, callback) { var uid = data.uid, tid = data.tid, + handle = data.uid ? null : data.handle, // Only guests have handles! content = data.content, timestamp = data.timestamp || Date.now(); - if (!uid && parseInt(uid, 10) !== 0) { return callback(new Error('[[error:invalid-uid]]')); } @@ -51,6 +51,10 @@ module.exports = function(Posts) { postData.ip = data.ip; } + if (handle) { + postData.handle = handle; + } + plugins.fireHook('filter:post.save', postData, next); }, function(postData, next) { diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index a922613ac3..71a44f36bd 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -35,7 +35,7 @@ SocketModules.composer.push = function(socket, pid, callback) { if (err || !canRead) { return callback(err || new Error('[[error:no-privileges]]')); } - posts.getPostFields(pid, ['content', 'tid'], function(err, postData) { + posts.getPostFields(pid, ['content', 'tid', 'uid', 'handle'], function(err, postData) { if(err || (!postData && !postData.content)) { return callback(err || new Error('[[error:invalid-pid]]')); } @@ -61,6 +61,8 @@ SocketModules.composer.push = function(socket, pid, callback) { callback(null, { pid: pid, + uid: postData.uid, + handle: parseInt(meta.config.allowGuestHandles, 10) ? postData.handle : undefined, body: postData.content, title: results.topic.title, topic_thumb: results.topic.thumb, diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 1b7193ccf0..97b8e7ea74 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -257,13 +257,25 @@ SocketPosts.edit = function(socket, data, callback) { return callback(new Error('[[error:content-too-short, ' + meta.config.minimumPostLength + ']]')); } - postTools.edit(socket.uid, data.pid, data.title, data.content, {topic_thumb: data.topic_thumb, tags: data.tags}, function(err, results) { + // uid, pid, title, content, options + postTools.edit({ + uid: socket.uid, + handle: data.handle, + pid: data.pid, + title: data.title, + content: data.content, + options: { + topic_thumb: data.topic_thumb, + tags: data.tags + } + }, function(err, results) { if (err) { return callback(err); } websockets.in('topic_' + results.topic.tid).emit('event:post_edited', { pid: data.pid, + handle: data.handle, title: results.topic.title, isMainPost: results.topic.isMainPost, tags: results.topic.tags, diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index e5222e04a5..fe2c313452 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -27,6 +27,7 @@ SocketTopics.post = function(socket, data, callback) { topics.post({ uid: socket.uid, + handle: data.handle, title: data.title, content: data.content, cid: data.category_id, diff --git a/src/topics/create.js b/src/topics/create.js index 71a1dddec4..9fd7354b17 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -93,6 +93,7 @@ module.exports = function(Topics) { Topics.post = function(data, callback) { var uid = data.uid, + handle = data.handle, title = data.title, content = data.content, cid = data.cid; @@ -134,7 +135,7 @@ module.exports = function(Topics) { Topics.create({uid: uid, title: title, cid: cid, thumb: data.thumb, tags: data.tags}, next); }, function(tid, next) { - Topics.reply({uid:uid, tid:tid, content:content, req: data.req}, next); + Topics.reply({uid:uid, tid:tid, handle: handle, content:content, req: data.req}, next); }, function(postData, next) { async.parallel({ @@ -184,6 +185,7 @@ module.exports = function(Topics) { var tid = data.tid, uid = data.uid, toPid = data.toPid, + handle = data.handle, content = data.content, postData; @@ -226,7 +228,7 @@ module.exports = function(Topics) { checkContentLength(content, next); }, function(next) { - posts.create({uid: uid, tid: tid, content: content, toPid: toPid, ip: data.req ? data.req.ip : null}, next); + posts.create({uid: uid, tid: tid, handle: handle, content: content, toPid: toPid, ip: data.req ? data.req.ip : null}, next); }, function(data, next) { postData = data; @@ -258,6 +260,11 @@ module.exports = function(Topics) { postData.user = results.userInfo[0]; postData.topic = results.topicInfo; + // Username override for guests, if enabled + if (parseInt(meta.config.allowGuestHandles, 10) === 1 && parseInt(postData.uid, 10) === 0 && data.handle) { + postData.user.username = data.handle; + } + if (results.settings.followTopicsOnReply) { threadTools.follow(postData.tid, uid); } diff --git a/src/topics/posts.js b/src/topics/posts.js index 0666303630..c6e7829e5d 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -4,12 +4,14 @@ var async = require('async'), winston = require('winston'), + _ = require('underscore'), db = require('../database'), user = require('../user'), favourites = require('../favourites'), posts = require('../posts'), - privileges = require('../privileges'); + privileges = require('../privileges'), + meta = require('../meta'); module.exports = function(Topics) { @@ -110,25 +112,32 @@ module.exports = function(Topics) { return callback(err); } - for (var i = 0; i < postData.length; ++i) { - if (postData[i]) { - postData[i].index = results.indices[i]; - postData[i].deleted = parseInt(postData[i].deleted, 10) === 1; - postData[i].user = results.userData[postData[i].uid]; - postData[i].editor = postData[i].editor ? results.editors[postData[i].editor] : null; - postData[i].favourited = results.favourites[i]; - postData[i].upvoted = results.voteData.upvotes[i]; - postData[i].downvoted = results.voteData.downvotes[i]; - postData[i].votes = postData[i].votes || 0; - postData[i].display_moderator_tools = results.privileges[i].editable; - postData[i].display_move_tools = results.privileges[i].move && postData[i].index !== 0; - postData[i].selfPost = parseInt(uid, 10) === parseInt(postData[i].uid, 10); + postData = postData.map(function(postObj, i) { + if (postObj) { + postObj.index = results.indices[i]; + postObj.deleted = parseInt(postObj.deleted, 10) === 1; + postObj.user = _.clone(results.userData[postObj.uid]); + postObj.editor = postObj.editor ? results.editors[postObj.editor] : null; + postObj.favourited = results.favourites[i]; + postObj.upvoted = results.voteData.upvotes[i]; + postObj.downvoted = results.voteData.downvotes[i]; + postObj.votes = postObj.votes || 0; + postObj.display_moderator_tools = results.privileges[i].editable; + postObj.display_move_tools = results.privileges[i].move && postObj.index !== 0; + postObj.selfPost = parseInt(uid, 10) === parseInt(postObj.uid, 10); - if(postData[i].deleted && !results.privileges[i].view_deleted) { - postData[i].content = '[[topic:post_is_deleted]]'; + if(postObj.deleted && !results.privileges[i].view_deleted) { + postObj.content = '[[topic:post_is_deleted]]'; + } + + // Username override for guests, if enabled + if (parseInt(meta.config.allowGuestHandles, 10) === 1 && parseInt(postObj.uid, 10) === 0 && postObj.handle) { + postObj.user.username = postObj.handle; } } - } + + return postObj; + }).filter(Boolean); callback(null, postData); }); diff --git a/src/views/admin/header.tpl b/src/views/admin/header.tpl index 2b14ff8e1f..dfa2dc9c89 100644 --- a/src/views/admin/header.tpl +++ b/src/views/admin/header.tpl @@ -101,95 +101,7 @@
\ No newline at end of file diff --git a/src/views/admin/partials/menu.tpl b/src/views/admin/partials/menu.tpl new file mode 100644 index 0000000000..7059ad6ba0 --- /dev/null +++ b/src/views/admin/partials/menu.tpl @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/views/admin/settings/guest.tpl b/src/views/admin/settings/guest.tpl new file mode 100644 index 0000000000..a212547164 --- /dev/null +++ b/src/views/admin/settings/guest.tpl @@ -0,0 +1,25 @@ + + +
+
Guests
+
+

+ These options affect guest users as a whole. Control over which categories a guest can see or post to is handled in + the categories themselves +

+ +
+
+ +
+
+
+
+ + \ No newline at end of file