mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-03-05 03:51:26 +01:00
Merge branch 'develop'
This commit is contained in:
21
.travis.yml
21
.travis.yml
@@ -1,30 +1,33 @@
|
||||
services:
|
||||
- mongodb
|
||||
- redis-server
|
||||
before_install:
|
||||
- "sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6"
|
||||
- "echo 'deb [ arch=amd64 ] http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.4 multiverse' | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list"
|
||||
- "sudo apt-get update"
|
||||
- "sudo apt-get install -y mongodb-org"
|
||||
- "sudo service mongod start"
|
||||
- cp install/package.json package.json
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
before_script:
|
||||
- sleep 15 # wait for mongodb to be ready
|
||||
- cp install/package.json package.json
|
||||
- npm install
|
||||
- sh -c "if [ '$DB' = 'mongodb' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"mongo\\\",\\\"mongo:host\\\":\\\"127.0.0.1\\\",\\\"mongo:port\\\":27017,\\\"mongo:username\\\":\\\"\\\",\\\"mongo:password\\\":\\\"\\\",\\\"mongo:database\\\":0,\\\"redis:host\\\":\\\"127.0.0.1\\\",\\\"redis:port\\\":6379,\\\"redis:password\\\":\\\"\\\",\\\"redis:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"abcdef\\\",\\\"admin:password:confirm\\\":\\\"abcdef\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":27017,\\\"database\\\":0}\"; fi"
|
||||
- sh -c "if [ '$DB' = 'redis' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"redis\\\",\\\"mongo:host\\\":\\\"127.0.0.1\\\",\\\"mongo:port\\\":27017,\\\"mongo:username\\\":\\\"\\\",\\\"mongo:password\\\":\\\"\\\",\\\"mongo:database\\\":0,\\\"redis:host\\\":\\\"127.0.0.1\\\",\\\"redis:port\\\":6379,\\\"redis:password\\\":\\\"\\\",\\\"redis:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"abcdef\\\",\\\"admin:password:confirm\\\":\\\"abcdef\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":6379,\\\"database\\\":0}\"; fi"
|
||||
after_success:
|
||||
- "npm run coveralls"
|
||||
language: node_js
|
||||
sudo: false
|
||||
dist: trusty
|
||||
env:
|
||||
- CXX=g++-4.8 DB=mongodb
|
||||
- CXX=g++-4.8 DB=redis
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- ubuntu-toolchain-r-test
|
||||
- mongodb-3.4-trusty
|
||||
packages:
|
||||
- g++-4.8
|
||||
- g++-4.8
|
||||
- mongodb-org-server
|
||||
node_js:
|
||||
- "9"
|
||||
- "8"
|
||||
- "7"
|
||||
- "6"
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"cropperjs": "^1.2.2",
|
||||
"csurf": "^1.9.0",
|
||||
"daemon": "^1.1.0",
|
||||
"diff": "^3.4.0",
|
||||
"express": "^4.16.2",
|
||||
"express-session": "^1.15.6",
|
||||
"express-useragent": "1.0.8",
|
||||
@@ -60,6 +61,7 @@
|
||||
"mongodb": "2.2.33",
|
||||
"morgan": "^1.9.0",
|
||||
"mousetrap": "^1.6.1",
|
||||
"mubsub": "^1.4.0",
|
||||
"nconf": "^0.9.1",
|
||||
"nodebb-plugin-composer-default": "6.0.16",
|
||||
"nodebb-plugin-dbsearch": "2.0.9",
|
||||
@@ -91,10 +93,12 @@
|
||||
"serve-favicon": "^2.4.5",
|
||||
"sitemap": "^1.13.0",
|
||||
"socket.io": "2.0.4",
|
||||
"socket.io-adapter-mongo": "^2.0.1",
|
||||
"socket.io-client": "2.0.4",
|
||||
"socket.io-redis": "5.2.0",
|
||||
"socketio-wildcard": "2.0.0",
|
||||
"spdx-license-list": "^3.0.1",
|
||||
"spider-detector": "1.0.18",
|
||||
"toobusy-js": "^0.5.1",
|
||||
"uglify-js": "^3.3.4",
|
||||
"validator": "9.2.0",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"cant-delete-chat-message": "You are not allowed to delete this message",
|
||||
"chat-edit-duration-expired": "You are only allowed to edit chat messages for %1 second(s) after posting",
|
||||
"chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting",
|
||||
"chat-deleted-already": "This chat message has already been deleted.",
|
||||
"chat-restored'already": "This chat message has already been restored.",
|
||||
|
||||
"already-voting-for-this-post": "You have already voted for this post.",
|
||||
"reputation-system-disabled": "Reputation system is disabled.",
|
||||
|
||||
@@ -134,5 +134,7 @@
|
||||
|
||||
"edited": "Edited",
|
||||
"disabled": "Disabled",
|
||||
"select": "Select"
|
||||
"select": "Select",
|
||||
|
||||
"user-search-prompt": "Type something here to find users..."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"chat.chatting_with": "Chat with <span id=\"chat-with-name\"></span>",
|
||||
"chat.chatting_with": "Chat with",
|
||||
"chat.placeholder": "Type chat message here, press enter to send",
|
||||
"chat.send": "Send",
|
||||
"chat.no_active": "You have no active chats.",
|
||||
@@ -12,6 +12,7 @@
|
||||
"chat.recent-chats": "Recent Chats",
|
||||
"chat.contacts": "Contacts",
|
||||
"chat.message-history": "Message History",
|
||||
"chat.options": "Chat options",
|
||||
"chat.pop-out": "Pop out chat",
|
||||
"chat.minimize": "Minimize",
|
||||
"chat.maximize": "Maximize",
|
||||
@@ -20,7 +21,17 @@
|
||||
"chat.three_months": "3 Months",
|
||||
"chat.delete_message_confirm": "Are you sure you wish to delete this message?",
|
||||
"chat.add-users-to-room": "Add users to room",
|
||||
"chat.retrieving-users": "Retrieving users...",
|
||||
"chat.manage-room": "Manage Chat Room",
|
||||
"chat.add-user-help": "Search for users here. When selected, the user will be added to the chat. The new user will not be able to see chat messages written before they were added to the conversation.",
|
||||
"chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?",
|
||||
"chat.rename-room": "Rename room",
|
||||
"chat.rename-placeholder": "Enter your room name here",
|
||||
"chat.rename-help": "The room name set here will be viewable by all participants in the room.",
|
||||
"chat.leave": "Leave Chat",
|
||||
"chat.leave-prompt": "Are you sure you wish to leave this chat?",
|
||||
"chat.leave-help": "Leaving this chat will remove you from future correspondence in this chat. If you are re-added in the future, you will not see any chat history from prior to your re-joining.",
|
||||
"chat.in-room": "In this room",
|
||||
|
||||
"composer.compose": "Compose",
|
||||
"composer.show_preview": "Show Preview",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"moved": "Moved",
|
||||
"copy-ip": "Copy IP",
|
||||
"ban-ip": "Ban IP",
|
||||
"view-history": "Edit History",
|
||||
|
||||
"bookmark_instructions" : "Click here to return to the last read post in this thread.",
|
||||
|
||||
@@ -143,5 +144,9 @@
|
||||
"stale.create": "Create a new topic",
|
||||
"stale.reply_anyway": "Reply to this topic anyway",
|
||||
|
||||
"link_back": "Re: [%1](%2)\n\n"
|
||||
"link_back": "Re: [%1](%2)\n\n",
|
||||
|
||||
"diffs.title": "Post Edit History",
|
||||
"diffs.description": "This post has <strong>%1</strong> revisions. Click one of the revisions below to see the post content at that point in time.",
|
||||
"diffs.no-revisions-description": "This post has <strong>%1</strong> revisions."
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"banned": "Banned",
|
||||
"offline": "Offline",
|
||||
"deleted": "Deleted",
|
||||
"username": "User Name",
|
||||
"joindate": "Join Date",
|
||||
"postcount": "Post Count",
|
||||
|
||||
@@ -27,21 +27,19 @@ define('forum/chats', [
|
||||
recentChats.init();
|
||||
|
||||
Chats.addEventListeners();
|
||||
Chats.createTagsInput($('[component="chat/messages"] .users-tag-input'), ajaxify.data);
|
||||
Chats.createAutoComplete($('[component="chat/input"]'));
|
||||
|
||||
components.get('expanded-chat/controlsToggle').on('click', function () {
|
||||
components.get('expanded-chat/controls').toggleClass('hide');
|
||||
});
|
||||
Chats.resizeMainWindow();
|
||||
|
||||
if (env === 'md' || env === 'lg') {
|
||||
Chats.resizeMainWindow();
|
||||
Chats.addHotkeys();
|
||||
}
|
||||
|
||||
messages.scrollToBottom($('.expanded-chat ul'));
|
||||
$(document).ready(function () {
|
||||
$(window).trigger('action:chat.loaded', $('.chats-full'));
|
||||
});
|
||||
|
||||
Chats.initialised = true;
|
||||
messages.scrollToBottom($('.expanded-chat ul.chat-content'));
|
||||
|
||||
search.init();
|
||||
|
||||
@@ -52,7 +50,16 @@ define('forum/chats', [
|
||||
|
||||
Chats.addEventListeners = function () {
|
||||
Chats.addSendHandlers(ajaxify.data.roomId, $('.chat-input'), $('.expanded-chat button[data-action="send"]'));
|
||||
Chats.addPopoutHandler();
|
||||
Chats.addActionHandlers(components.get('chat/messages'), ajaxify.data.roomId);
|
||||
Chats.addMemberHandler(ajaxify.data.roomId, components.get('chat/controls').find('[data-action="members"]'));
|
||||
Chats.addRenameHandler(ajaxify.data.roomId, components.get('chat/controls').find('[data-action="rename"]'));
|
||||
Chats.addLeaveHandler(ajaxify.data.roomId, components.get('chat/controls').find('[data-action="leave"]'));
|
||||
Chats.addScrollHandler(ajaxify.data.roomId, ajaxify.data.uid, $('.chat-content'));
|
||||
Chats.addCharactersLeftHandler($('[component="chat/main-wrapper"]'));
|
||||
};
|
||||
|
||||
Chats.addPopoutHandler = function () {
|
||||
$('[data-action="pop-out"]').on('click', function () {
|
||||
var text = components.get('chat/input').val();
|
||||
var roomId = ajaxify.data.roomId;
|
||||
@@ -70,12 +77,6 @@ define('forum/chats', [
|
||||
components.get('chat/input').val(text);
|
||||
});
|
||||
});
|
||||
|
||||
Chats.addEditDeleteHandler(components.get('chat/messages'), ajaxify.data.roomId);
|
||||
|
||||
Chats.addRenameHandler(ajaxify.data.roomId, $('[component="chat/room/name"]'));
|
||||
Chats.addScrollHandler(ajaxify.data.roomId, ajaxify.data.uid, $('.chat-content'));
|
||||
Chats.addCharactersLeftHandler($('[component="chat/main-wrapper"]'));
|
||||
};
|
||||
|
||||
Chats.addScrollHandler = function (roomId, uid, el) {
|
||||
@@ -120,23 +121,35 @@ define('forum/chats', [
|
||||
var element = parent.find('[component="chat/input"]');
|
||||
element.on('keyup', function () {
|
||||
parent.find('[component="chat/message/length"]').text(element.val().length);
|
||||
parent.find('[component="chat/message/remaining"]').text(config.maximumChatMessageLength - element.val().length);
|
||||
});
|
||||
};
|
||||
|
||||
Chats.addEditDeleteHandler = function (element, roomId) {
|
||||
element.on('click', '[data-action="edit"]', function () {
|
||||
Chats.addActionHandlers = function (element, roomId) {
|
||||
element.on('click', '[data-action]', function () {
|
||||
var messageId = $(this).parents('[data-mid]').attr('data-mid');
|
||||
var inputEl = $('[data-roomid="' + roomId + '"] [component="chat/input"]');
|
||||
messages.prepEdit(inputEl, messageId, roomId);
|
||||
}).on('click', '[data-action="delete"]', function () {
|
||||
var messageId = $(this).parents('[data-mid]').attr('data-mid');
|
||||
messages.delete(messageId, roomId);
|
||||
var action = this.getAttribute('data-action');
|
||||
|
||||
switch (action) {
|
||||
case 'edit':
|
||||
var inputEl = $('[data-roomid="' + roomId + '"] [component="chat/input"]');
|
||||
messages.prepEdit(inputEl, messageId, roomId);
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
messages.delete(messageId, roomId);
|
||||
break;
|
||||
|
||||
case 'restore':
|
||||
messages.restore(messageId, roomId);
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Chats.addHotkeys = function () {
|
||||
mousetrap.bind('ctrl+up', function () {
|
||||
var activeContact = $('.chats-list .bg-primary');
|
||||
var activeContact = $('.chats-list .bg-info');
|
||||
var prev = activeContact.prev();
|
||||
|
||||
if (prev.length) {
|
||||
@@ -144,7 +157,7 @@ define('forum/chats', [
|
||||
}
|
||||
});
|
||||
mousetrap.bind('ctrl+down', function () {
|
||||
var activeContact = $('.chats-list .bg-primary');
|
||||
var activeContact = $('.chats-list .bg-info');
|
||||
var next = activeContact.next();
|
||||
|
||||
if (next.length) {
|
||||
@@ -163,28 +176,126 @@ define('forum/chats', [
|
||||
});
|
||||
};
|
||||
|
||||
Chats.addRenameHandler = function (roomId, inputEl) {
|
||||
var oldName = inputEl.val();
|
||||
inputEl.on('blur keypress', function (ev) {
|
||||
if (ev.type === 'keypress' && ev.keyCode !== 13) {
|
||||
return;
|
||||
}
|
||||
var newName = inputEl.val();
|
||||
Chats.addMemberHandler = function (roomId, buttonEl) {
|
||||
var modal;
|
||||
|
||||
if (oldName === newName) {
|
||||
return;
|
||||
buttonEl.on('click', function () {
|
||||
Benchpress.parse('partials/modals/manage_room', {}, function (html) {
|
||||
translator.translate(html, function (html) {
|
||||
modal = bootbox.dialog({
|
||||
title: '[[modules:chat.manage-room]]',
|
||||
message: html,
|
||||
});
|
||||
|
||||
modal.attr('component', 'chat/manage-modal');
|
||||
|
||||
Chats.refreshParticipantsList(roomId, modal);
|
||||
|
||||
var searchInput = modal.find('input');
|
||||
var errorEl = modal.find('.text-danger');
|
||||
require(['autocomplete', 'translator'], function (autocomplete, translator) {
|
||||
autocomplete.user(searchInput, function (event, selected) {
|
||||
errorEl.text('');
|
||||
socket.emit('modules.chats.addUserToRoom', {
|
||||
roomId: roomId,
|
||||
username: selected.item.user.name,
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
translator.translate(err.message, function (translated) {
|
||||
errorEl.text(translated);
|
||||
});
|
||||
}
|
||||
|
||||
Chats.refreshParticipantsList(roomId, modal);
|
||||
searchInput.val('');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Chats.addLeaveHandler = function (roomId, buttonEl) {
|
||||
buttonEl.on('click', function () {
|
||||
bootbox.confirm({
|
||||
size: 'small',
|
||||
title: '[[modules:chat.leave]]',
|
||||
message: '<p>[[modules:chat.leave-prompt]]</p><p class="help-block">[[modules:chat.leave-help]]</p>',
|
||||
callback: function (ok) {
|
||||
if (ok) {
|
||||
socket.emit('modules.chats.leave', roomId, function (err) {
|
||||
if (err) {
|
||||
app.alertError(err.message);
|
||||
}
|
||||
|
||||
// Return user to chats page. If modal, close modal.
|
||||
var modal = buttonEl.parents('.chat-modal');
|
||||
if (modal.length) {
|
||||
require(['chat'], function (chatLib) {
|
||||
chatLib.close(modal);
|
||||
});
|
||||
} else {
|
||||
ajaxify.go('chats');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Chats.refreshParticipantsList = function (roomId, modal) {
|
||||
socket.emit('modules.chats.getUsersInRoom', { roomId: roomId }, function (err, users) {
|
||||
var listEl = modal.find('.list-group');
|
||||
|
||||
if (err) {
|
||||
return translator.translate('[[error:invalid-data]]', function (translated) {
|
||||
listEl.find('li').text(translated);
|
||||
});
|
||||
}
|
||||
|
||||
Benchpress.parse('partials/modals/manage_room_users', {
|
||||
users: users,
|
||||
}, function (html) {
|
||||
listEl.html(html);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Chats.addRenameHandler = function (roomId, buttonEl, roomName) {
|
||||
var modal;
|
||||
|
||||
buttonEl.on('click', function () {
|
||||
Benchpress.parse('partials/modals/rename_room', {
|
||||
name: roomName || ajaxify.data.roomName,
|
||||
}, function (html) {
|
||||
translator.translate(html, function (html) {
|
||||
modal = bootbox.dialog({
|
||||
title: '[[modules:chat.rename-room]]',
|
||||
message: html,
|
||||
buttons: {
|
||||
save: {
|
||||
label: '[[global:save]]',
|
||||
className: 'btn-primary',
|
||||
callback: submit,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function submit() {
|
||||
socket.emit('modules.chats.renameRoom', {
|
||||
roomId: roomId,
|
||||
newName: newName,
|
||||
newName: modal.find('#roomName').val(),
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
oldName = newName;
|
||||
inputEl.blur();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Chats.addSendHandlers = function (roomId, inputEl, sendEl) {
|
||||
@@ -222,75 +333,6 @@ define('forum/chats', [
|
||||
}
|
||||
};
|
||||
|
||||
Chats.createTagsInput = function (tagEl, data) {
|
||||
tagEl.tagsinput({
|
||||
confirmKeys: [13, 44],
|
||||
trimValue: true,
|
||||
});
|
||||
|
||||
if (data.users && data.users.length) {
|
||||
data.users.forEach(function (user) {
|
||||
tagEl.tagsinput('add', $('<div/>').html(user.username).text());
|
||||
});
|
||||
}
|
||||
|
||||
tagEl.on('beforeItemAdd', function (event) {
|
||||
event.cancel = event.item === app.user.username;
|
||||
});
|
||||
|
||||
tagEl.on('itemAdded', function (event) {
|
||||
if (event.item === app.user.username) {
|
||||
return;
|
||||
}
|
||||
socket.emit('modules.chats.addUserToRoom', {
|
||||
roomId: data.roomId,
|
||||
username: event.item,
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
app.alertError(err.message);
|
||||
tagEl.tagsinput('remove', event.item, {
|
||||
nouser: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
tagEl.on('beforeItemRemove', function (event) {
|
||||
if (event.options && event.options.nouser) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.cancel = !data.isOwner || tagEl.tagsinput('items').length < 2;
|
||||
if (!data.owner) {
|
||||
return app.alertError('[[error:not-allowed]]');
|
||||
}
|
||||
|
||||
if (tagEl.tagsinput('items').length < 2) {
|
||||
return app.alertError('[[error:cant-remove-last-user]]');
|
||||
}
|
||||
});
|
||||
|
||||
tagEl.on('itemRemoved', function (event) {
|
||||
if (event.options && event.options.nouser) {
|
||||
return;
|
||||
}
|
||||
socket.emit('modules.chats.removeUserFromRoom', {
|
||||
roomId: data.roomId,
|
||||
username: event.item,
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var input = $('.users-tag-container').find('.bootstrap-tagsinput input');
|
||||
|
||||
require(['autocomplete'], function (autocomplete) {
|
||||
autocomplete.user(input);
|
||||
});
|
||||
};
|
||||
|
||||
Chats.leave = function (el) {
|
||||
var roomId = el.attr('data-roomid');
|
||||
socket.emit('modules.chats.leave', roomId, function (err) {
|
||||
@@ -320,11 +362,13 @@ define('forum/chats', [
|
||||
response.json().then(function (payload) {
|
||||
app.parseAndTranslate('partials/chats/message-window', payload, function (html) {
|
||||
components.get('chat/main-wrapper').html(html);
|
||||
html.find('.timeago').timeago();
|
||||
Chats.resizeMainWindow();
|
||||
ajaxify.data = payload;
|
||||
Chats.setActive();
|
||||
Chats.addEventListeners();
|
||||
messages.scrollToBottom($('.expanded-chat ul'));
|
||||
$(window).trigger('action:chat.loaded', $('.chats-full'));
|
||||
messages.scrollToBottom($('.expanded-chat ul.chat-content'));
|
||||
if (history.pushState) {
|
||||
history.pushState({
|
||||
url: 'user/' + payload.userslug + '/chats/' + payload.roomId,
|
||||
@@ -361,7 +405,7 @@ define('forum/chats', [
|
||||
data.message.self = data.self;
|
||||
|
||||
messages.appendChatMessage($('.expanded-chat .chat-content'), data.message);
|
||||
} else if (ajaxify.currentPage.startsWith('chats')) {
|
||||
} else if (ajaxify.data.template.chats) {
|
||||
var roomEl = $('[data-roomid=' + data.roomId + ']');
|
||||
|
||||
if (roomEl.length > 0) {
|
||||
@@ -391,26 +435,18 @@ define('forum/chats', [
|
||||
messages.onChatMessageEdit();
|
||||
|
||||
socket.on('event:chats.roomRename', function (data) {
|
||||
$('[component="chat/room/name"]').val($('<div/>').html(data.newName).text());
|
||||
var roomEl = components.get('chat/recent/room', data.roomId);
|
||||
var titleEl = roomEl.find('[component="chat/title"]');
|
||||
ajaxify.data.roomName = data.newName;
|
||||
|
||||
titleEl.text(data.newName);
|
||||
});
|
||||
};
|
||||
|
||||
Chats.resizeMainWindow = function () {
|
||||
var messagesList = $('.expanded-chat .chat-content');
|
||||
var searchHeight = $('.chat-search').height();
|
||||
var searchListHeight = $('[component="chat/search/list"]').outerHeight(true) - $('[component="chat/search/list"]').height();
|
||||
var fromTop = components.get('chat/recent').offset().top;
|
||||
|
||||
if (messagesList.length) {
|
||||
var margin = $('.expanded-chat ul').outerHeight(true) - $('.expanded-chat ul').height();
|
||||
var inputHeight = $('.chat-input').outerHeight(true);
|
||||
|
||||
messagesList.height($(window).height() - (fromTop + inputHeight + (margin * 4)));
|
||||
components.get('chat/recent').height($('.expanded-chat').height() - (searchHeight + searchListHeight));
|
||||
$('[component="chat/search/list"]').css('max-height', (components.get('chat/recent').height() / 2) + 'px');
|
||||
} else {
|
||||
components.get('chat/recent').height($(window).height() - (fromTop + searchHeight + searchListHeight));
|
||||
}
|
||||
var viewportHeight = $(window).height();
|
||||
var fromTop = components.get('chat/main-wrapper').offset().top || components.get('chat/nav-wrapper').offset().top;
|
||||
$('.chats-full').height(viewportHeight - fromTop - 1);
|
||||
|
||||
Chats.setActive();
|
||||
};
|
||||
@@ -418,10 +454,13 @@ define('forum/chats', [
|
||||
Chats.setActive = function () {
|
||||
if (ajaxify.data.roomId) {
|
||||
socket.emit('modules.chats.markRead', ajaxify.data.roomId);
|
||||
$('[data-roomid="' + ajaxify.data.roomId + '"]').toggleClass('unread', false);
|
||||
$('.expanded-chat input').focus();
|
||||
}
|
||||
$('.chats-list li').removeClass('bg-primary');
|
||||
$('.chats-list li[data-roomid="' + ajaxify.data.roomId + '"]').addClass('bg-primary');
|
||||
$('.chats-list li').removeClass('bg-info');
|
||||
$('.chats-list li[data-roomid="' + ajaxify.data.roomId + '"]').addClass('bg-info');
|
||||
|
||||
components.get('chat/nav-wrapper').attr('data-loaded', ajaxify.data.roomId ? '1' : '0');
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -82,6 +82,10 @@ define('forum/chats/messages', ['components', 'sounds', 'translator', 'benchpres
|
||||
newMessage.find('.timeago').timeago();
|
||||
newMessage.find('img:not(.not-responsive)').addClass('img-responsive');
|
||||
messages.scrollToBottom(chatContentEl);
|
||||
|
||||
$(window).trigger('action:chat.received', {
|
||||
messageEl: newMessage,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -146,13 +150,24 @@ define('forum/chats/messages', ['components', 'sounds', 'translator', 'benchpres
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
components.get('chat/message', messageId).slideUp('slow', function () {
|
||||
$(this).remove();
|
||||
});
|
||||
components.get('chat/message', messageId).toggleClass('deleted', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
messages.restore = function (messageId, roomId) {
|
||||
socket.emit('modules.chats.restore', {
|
||||
messageId: messageId,
|
||||
roomId: roomId,
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
components.get('chat/message', messageId).toggleClass('deleted', false);
|
||||
});
|
||||
};
|
||||
|
||||
return messages;
|
||||
});
|
||||
|
||||
@@ -6,18 +6,8 @@ define('forum/chats/recent', function () {
|
||||
|
||||
recent.init = function () {
|
||||
require(['forum/chats'], function (Chats) {
|
||||
$('[component="chat/recent"]').on('click', '[component="chat/leave"]', function () {
|
||||
Chats.leave($(this).parents('[data-roomid]'));
|
||||
return false;
|
||||
});
|
||||
|
||||
$('[component="chat/recent"]').on('click', '[component="chat/recent/room"]', function () {
|
||||
var env = utils.findBootstrapEnvironment();
|
||||
if (env === 'xs' || env === 'sm') {
|
||||
app.openChat($(this).attr('data-roomid'));
|
||||
} else {
|
||||
Chats.switchChat($(this).attr('data-roomid'));
|
||||
}
|
||||
Chats.switchChat($(this).attr('data-roomid'));
|
||||
});
|
||||
|
||||
$('[component="chat/recent"]').on('scroll', function () {
|
||||
|
||||
@@ -52,6 +52,8 @@ define('forum/chats/search', ['components'], function (components) {
|
||||
var chatEl = displayUser(chatsListEl, userObj);
|
||||
onUserClick(chatEl, userObj);
|
||||
});
|
||||
|
||||
chatsListEl.parent().toggleClass('open', true);
|
||||
}
|
||||
|
||||
function displayUser(chatsListEl, userObj) {
|
||||
|
||||
65
public/src/client/topic/diffs.js
Normal file
65
public/src/client/topic/diffs.js
Normal file
@@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
|
||||
define('forum/topic/diffs', ['benchpress', 'translator'], function (Benchpress, translator) {
|
||||
var Diffs = {};
|
||||
|
||||
Diffs.open = function (pid) {
|
||||
var localeStringOpts = { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
||||
|
||||
socket.emit('posts.getDiffs', { pid: pid }, function (err, timestamps) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
Benchpress.parse('partials/modals/post_history', {
|
||||
diffs: timestamps.map(function (timestamp) {
|
||||
return {
|
||||
timestamp: timestamp,
|
||||
pretty: new Date(timestamp).toLocaleString(config.userLang.replace('_', '-'), localeStringOpts),
|
||||
};
|
||||
}),
|
||||
numDiffs: timestamps.length,
|
||||
}, function (html) {
|
||||
translator.translate(html, function (html) {
|
||||
var modal = bootbox.dialog({
|
||||
title: '[[topic:diffs.title]]',
|
||||
message: html,
|
||||
});
|
||||
|
||||
if (!timestamps.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selectEl = modal.find('select');
|
||||
var postContainer = modal.find('ul.posts-list');
|
||||
|
||||
selectEl.on('change', function () {
|
||||
Diffs.load(pid, this.value, postContainer);
|
||||
});
|
||||
|
||||
modal.on('shown.bs.modal', function () {
|
||||
Diffs.load(pid, selectEl.val(), postContainer);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Diffs.load = function (pid, since, postContainer) {
|
||||
socket.emit('posts.showPostAt', { pid: pid, since: since }, function (err, data) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
data.deleted = !!parseInt(data.deleted, 10);
|
||||
|
||||
app.parseAndTranslate('partials/posts_list', 'posts', {
|
||||
posts: [data],
|
||||
}, function (html) {
|
||||
postContainer.empty().append(html);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return Diffs;
|
||||
});
|
||||
@@ -8,7 +8,8 @@ define('forum/topic/postTools', [
|
||||
'translator',
|
||||
'forum/topic/votes',
|
||||
'forum/topic/move-post',
|
||||
], function (share, navigator, components, translator, votes, movePost) {
|
||||
'forum/topic/diffs',
|
||||
], function (share, navigator, components, translator, votes, movePost, diffs) {
|
||||
var PostTools = {};
|
||||
|
||||
var staleReplyAnyway = false;
|
||||
@@ -139,6 +140,11 @@ define('forum/topic/postTools', [
|
||||
}
|
||||
});
|
||||
|
||||
postContainer.on('click', '[component="post/view-history"], [component="post/edit-indicator"]', function () {
|
||||
var btn = $(this);
|
||||
diffs.open(getData(btn, 'data-pid'));
|
||||
});
|
||||
|
||||
postContainer.on('click', '[component="post/delete"]', function () {
|
||||
var btn = $(this);
|
||||
var timestamp = parseInt(getData(btn, 'data-timestamp'), 10);
|
||||
|
||||
@@ -10,7 +10,7 @@ define('autocomplete', function () {
|
||||
input.autocomplete({
|
||||
delay: 200,
|
||||
open: function () {
|
||||
$(this).autocomplete('widget').css('z-index', 20000);
|
||||
$(this).autocomplete('widget').css('z-index', 100005);
|
||||
},
|
||||
select: onselect,
|
||||
source: function (request, response) {
|
||||
|
||||
@@ -61,7 +61,7 @@ define('chat', [
|
||||
if (modal.is(':visible')) {
|
||||
taskbar.updateActive(modal.attr('UUID'));
|
||||
ChatsMessages.scrollToBottom(modal.find('.chat-content'));
|
||||
} else {
|
||||
} else if (!ajaxify.data.template.chats) {
|
||||
module.toggleNew(modal.attr('UUID'), true, true);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ define('chat', [
|
||||
roomId: data.roomId,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
} else if (!ajaxify.data.template.chats) {
|
||||
socket.emit('modules.chats.loadRoom', {
|
||||
roomId: data.roomId,
|
||||
}, function (err, roomData) {
|
||||
@@ -106,7 +106,7 @@ define('chat', [
|
||||
socket.on('event:chats.roomRename', function (data) {
|
||||
var newTitle = $('<div/>').html(data.newName).text();
|
||||
var modal = module.getModal(data.roomId);
|
||||
modal.find('[component="chat/room/name"]').val(newTitle);
|
||||
modal.find('[component="chat/room/name"]').text(newTitle);
|
||||
taskbar.updateTitle('chat', modal.attr('UUID'), newTitle);
|
||||
});
|
||||
|
||||
@@ -190,7 +190,7 @@ define('chat', [
|
||||
return;
|
||||
}
|
||||
|
||||
chatModal.find('.chat-content').css('height', module.calculateChatListHeight(chatModal));
|
||||
chatModal.find('.modal-body').css('height', module.calculateChatListHeight(chatModal));
|
||||
});
|
||||
|
||||
chatModal.draggable({
|
||||
@@ -249,20 +249,11 @@ define('chat', [
|
||||
}
|
||||
});
|
||||
|
||||
Chats.addEditDeleteHandler(chatModal.find('[component="chat/messages"]'), data.roomId);
|
||||
Chats.addActionHandlers(chatModal.find('[component="chat/messages"]'), data.roomId);
|
||||
Chats.addRenameHandler(chatModal.attr('data-roomid'), chatModal.find('[data-action="rename"]'), chatModal.attr('data-name'));
|
||||
Chats.addLeaveHandler(chatModal.attr('data-roomid'), chatModal.find('[data-action="leave"]'));
|
||||
Chats.addSendHandlers(chatModal.attr('data-roomid'), chatModal.find('.chat-input'), chatModal.find('[data-action="send"]'));
|
||||
|
||||
chatModal.find('[component="chat/controlsToggle"]').on('click', function () {
|
||||
var messagesEl = chatModal.find('[component="chat/messages"]');
|
||||
|
||||
chatModal.find('[component="chat/controls"]').toggle();
|
||||
messagesEl.css('height', module.calculateChatListHeight(chatModal));
|
||||
});
|
||||
|
||||
Chats.addRenameHandler(chatModal.attr('data-roomid'), chatModal.find('[component="chat/room/name"]'));
|
||||
|
||||
Chats.addSendHandlers(chatModal.attr('data-roomid'), chatModal.find('#chat-message-input'), chatModal.find('#chat-message-send-btn'));
|
||||
|
||||
Chats.createTagsInput(chatModal.find('.users-tag-input'), data);
|
||||
Chats.createAutoComplete(chatModal.find('[component="chat/input"]'));
|
||||
|
||||
Chats.addScrollHandler(chatModal.attr('data-roomid'), data.uid, chatModal.find('.chat-content'));
|
||||
@@ -333,7 +324,7 @@ define('chat', [
|
||||
module.enableMobileBehaviour = function (modalEl) {
|
||||
app.toggleNavbar(false);
|
||||
modalEl.attr('data-mobile', '1');
|
||||
var messagesEl = modalEl.find('.chat-content');
|
||||
var messagesEl = modalEl.find('.modal-body');
|
||||
messagesEl.css('height', module.calculateChatListHeight(modalEl));
|
||||
|
||||
$(window).on('resize', function () {
|
||||
@@ -346,12 +337,8 @@ define('chat', [
|
||||
};
|
||||
|
||||
module.calculateChatListHeight = function (modalEl) {
|
||||
var totalHeight = modalEl.find('.modal-content').outerHeight() - modalEl.find('.modal-header').outerHeight();
|
||||
var padding = parseInt(modalEl.find('.modal-body').css('padding-top'), 10) + parseInt(modalEl.find('.modal-body').css('padding-bottom'), 10);
|
||||
var contentMargin = parseInt(modalEl.find('.chat-content').css('margin-top'), 10) + parseInt(modalEl.find('.chat-content').css('margin-bottom'), 10);
|
||||
var inputGroupHeight = modalEl.find('.input-group').outerHeight();
|
||||
|
||||
return totalHeight - padding - contentMargin - inputGroupHeight;
|
||||
// Formula: modal height minus header height. Simple(tm).
|
||||
return modalEl.find('.modal-content').outerHeight() - modalEl.find('.modal-header').outerHeight();
|
||||
};
|
||||
|
||||
module.minimize = function (uuid) {
|
||||
|
||||
@@ -50,9 +50,14 @@ define('components', function () {
|
||||
'chat/message': function (messageId) {
|
||||
return $('[component="chat/message"][data-mid="' + messageId + '"]');
|
||||
},
|
||||
|
||||
'chat/message/body': function (messageId) {
|
||||
return $('[component="chat/message"][data-mid="' + messageId + '"] [component="chat/message/body"]');
|
||||
},
|
||||
|
||||
'chat/recent/room': function (roomid) {
|
||||
return $('[component="chat/recent/room"][data-roomid="' + roomid + '"]');
|
||||
},
|
||||
};
|
||||
|
||||
components.get = function () {
|
||||
|
||||
@@ -457,19 +457,6 @@ define('settings', function () {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Parse all values. If they are json, return json
|
||||
for (var key in values) {
|
||||
if (values.hasOwnProperty(key)) {
|
||||
try {
|
||||
if (!utils.isNumber(values[key])) {
|
||||
values[key] = JSON.parse(values[key]);
|
||||
}
|
||||
} catch (e) {
|
||||
// Leave the value as is
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save loaded settings into ajaxify.data for use client-side
|
||||
ajaxify.data.settings = values;
|
||||
|
||||
@@ -500,12 +487,6 @@ define('settings', function () {
|
||||
}
|
||||
});
|
||||
|
||||
// Normalizing value of multiple selects
|
||||
formEl.find('select[multiple]').each(function (idx, selectEl) {
|
||||
selectEl = $(selectEl);
|
||||
values[selectEl.attr('name')] = JSON.stringify(selectEl.val());
|
||||
});
|
||||
|
||||
socket.emit('admin.settings.set', {
|
||||
hash: hash,
|
||||
values: values,
|
||||
|
||||
@@ -97,7 +97,7 @@ chatsController.get = function (req, res, callback) {
|
||||
|
||||
chatsController.redirectToChat = function (req, res, next) {
|
||||
var roomid = parseInt(req.params.roomid, 10);
|
||||
if (!req.uid) {
|
||||
if (!req.loggedIn) {
|
||||
return next();
|
||||
}
|
||||
async.waterfall([
|
||||
|
||||
@@ -50,11 +50,26 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) {
|
||||
ips: function (next) {
|
||||
user.getIPs(uid, 4, next);
|
||||
},
|
||||
profile_links: function (next) {
|
||||
profile_links: function (next) { // DEPRECATED, do not use
|
||||
plugins.fireHook('filter:user.profileLinks', [], next);
|
||||
},
|
||||
profile_menu: function (next) {
|
||||
plugins.fireHook('filter:user.profileMenu', { uid: uid, callerUID: callerUID, links: [] }, next);
|
||||
plugins.fireHook('filter:user.profileMenu', {
|
||||
uid: uid,
|
||||
callerUID: callerUID,
|
||||
links: [{
|
||||
id: 'info',
|
||||
route: 'info',
|
||||
name: '[[user:account_info]]',
|
||||
visibility: {
|
||||
self: false,
|
||||
other: false,
|
||||
moderator: true,
|
||||
globalMod: true,
|
||||
admin: true,
|
||||
},
|
||||
}],
|
||||
}, next);
|
||||
},
|
||||
groups: function (next) {
|
||||
groups.getUserGroups([uid], next);
|
||||
|
||||
@@ -13,7 +13,7 @@ categoriesController.get = function (req, res, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
category: async.apply(categories.getCategories, [req.params.category_id], req.user.uid),
|
||||
category: async.apply(categories.getCategories, [req.params.category_id], req.uid),
|
||||
allCategories: async.apply(categories.buildForSelect, req.uid, 'read'),
|
||||
}, next);
|
||||
},
|
||||
|
||||
@@ -253,7 +253,7 @@ function uploadImage(filename, folder, uploadedFile, req, res, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
if (plugins.hasListeners('filter:uploadImage')) {
|
||||
plugins.fireHook('filter:uploadImage', { image: uploadedFile, uid: req.user.uid }, next);
|
||||
plugins.fireHook('filter:uploadImage', { image: uploadedFile, uid: req.uid }, next);
|
||||
} else {
|
||||
file.saveFileToLocal(filename, folder, uploadedFile.path, next);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ usersController.getCSV = function (req, res, next) {
|
||||
}
|
||||
events.log({
|
||||
type: 'getUsersCSV',
|
||||
uid: req.user.uid,
|
||||
uid: req.uid,
|
||||
ip: req.ip,
|
||||
});
|
||||
async.waterfall([
|
||||
|
||||
@@ -18,6 +18,7 @@ var apiController = module.exports;
|
||||
apiController.loadConfig = function (req, callback) {
|
||||
var config = {};
|
||||
config.relative_path = nconf.get('relative_path');
|
||||
config.upload_url = nconf.get('upload_url');
|
||||
config.siteTitle = validator.escape(String(meta.config.title || meta.config.browserTitle || 'NodeBB'));
|
||||
config.browserTitle = validator.escape(String(meta.config.browserTitle || meta.config.title || 'NodeBB'));
|
||||
config.titleLayout = (meta.config.titleLayout || '{pageTitle} | {browserTitle}').replace(/{/g, '{').replace(/}/g, '}');
|
||||
@@ -39,6 +40,7 @@ apiController.loadConfig = function (req, callback) {
|
||||
config.usePagination = parseInt(meta.config.usePagination, 10) === 1;
|
||||
config.disableChat = parseInt(meta.config.disableChat, 10) === 1;
|
||||
config.disableChatMessageEditing = parseInt(meta.config.disableChatMessageEditing, 10) === 1;
|
||||
config.maximumChatMessageLength = parseInt(meta.config.maximumChatMessageLength, 10) || 1000;
|
||||
config.socketioTransports = nconf.get('socket.io:transports') || ['polling', 'websocket'];
|
||||
config.websocketAddress = nconf.get('socket.io:address') || '';
|
||||
config.maxReconnectionAttempts = meta.config.maxReconnectionAttempts || 5;
|
||||
@@ -76,7 +78,7 @@ apiController.loadConfig = function (req, callback) {
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
if (!req.uid) {
|
||||
if (!req.loggedIn) {
|
||||
return next(null, config);
|
||||
}
|
||||
user.getSettings(req.uid, next);
|
||||
|
||||
@@ -417,7 +417,7 @@ authenticationController.localLogin = function (req, username, password, next) {
|
||||
};
|
||||
|
||||
authenticationController.logout = function (req, res, next) {
|
||||
if (!req.uid || !req.sessionID) {
|
||||
if (!req.loggedIn || !req.sessionID) {
|
||||
return res.status(200).send('not-logged-in');
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ categoryController.get = function (req, res, callback) {
|
||||
addTags(categoryData, res);
|
||||
|
||||
categoryData['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
|
||||
categoryData['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
|
||||
categoryData.title = translator.escape(categoryData.name);
|
||||
pageCount = Math.max(1, Math.ceil(categoryData.topic_count / settings.topicsPerPage));
|
||||
categoryData.pagination = pagination.create(currentPage, pageCount, req.query);
|
||||
|
||||
@@ -24,7 +24,7 @@ helpers.noScriptErrors = function (req, res, error, httpStatus) {
|
||||
middleware.buildHeader(req, res, function () {
|
||||
res.status(httpStatus).render(httpStatusString, {
|
||||
path: req.path,
|
||||
loggedIn: true,
|
||||
loggedIn: req.loggedIn,
|
||||
error: error,
|
||||
returnLink: true,
|
||||
title: '[[global:' + httpStatusString + '.title]]',
|
||||
@@ -67,11 +67,11 @@ helpers.notAllowed = function (req, res, error) {
|
||||
if (err) {
|
||||
return winston.error(err);
|
||||
}
|
||||
if (req.uid) {
|
||||
if (req.loggedIn) {
|
||||
if (res.locals.isAPI) {
|
||||
res.status(403).json({
|
||||
path: req.path.replace(/^\/api/, ''),
|
||||
loggedIn: !!req.uid,
|
||||
loggedIn: req.loggedIn,
|
||||
error: error,
|
||||
title: '[[global:403.title]]',
|
||||
});
|
||||
@@ -79,7 +79,7 @@ helpers.notAllowed = function (req, res, error) {
|
||||
middleware.buildHeader(req, res, function () {
|
||||
res.status(403).render('403', {
|
||||
path: req.path,
|
||||
loggedIn: !!req.uid,
|
||||
loggedIn: req.loggedIn,
|
||||
error: error,
|
||||
title: '[[global:403.title]]',
|
||||
});
|
||||
|
||||
@@ -113,7 +113,7 @@ Controllers.login = function (req, res, next) {
|
||||
}
|
||||
return res.redirect(nconf.get('relative_path') + data.authentication[0].url);
|
||||
}
|
||||
if (req.uid) {
|
||||
if (req.loggedIn) {
|
||||
user.getUserFields(req.uid, ['username', 'email'], function (err, user) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
|
||||
@@ -37,7 +37,7 @@ popularController.get = function (req, res, next) {
|
||||
alltime: '[[global:header.popular]]',
|
||||
};
|
||||
|
||||
if (!req.uid) {
|
||||
if (!req.loggedIn) {
|
||||
if (anonCache[term] && (Date.now() - lastUpdateTime) < 60 * 60 * 1000) {
|
||||
return res.render('popular', anonCache[term]);
|
||||
}
|
||||
@@ -73,7 +73,7 @@ popularController.get = function (req, res, next) {
|
||||
data.breadcrumbs = helpers.buildBreadcrumbs(breadcrumbs);
|
||||
}
|
||||
|
||||
if (!req.uid) {
|
||||
if (!req.loggedIn) {
|
||||
anonCache[term] = data;
|
||||
lastUpdateTime = Date.now();
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ recentController.get = function (req, res, next) {
|
||||
data.set = 'topics:recent';
|
||||
data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
|
||||
data.rssFeedUrl = nconf.get('relative_path') + '/recent.rss';
|
||||
if (req.uid) {
|
||||
if (req.loggedIn) {
|
||||
data.rssFeedUrl += '?uid=' + req.uid + '&token=' + rssToken;
|
||||
}
|
||||
data.title = meta.config.homePageTitle || '[[pages:home]]';
|
||||
|
||||
@@ -11,15 +11,14 @@ var categories = require('../categories');
|
||||
var pagination = require('../pagination');
|
||||
var helpers = require('./helpers');
|
||||
|
||||
|
||||
var searchController = {};
|
||||
var searchController = module.exports;
|
||||
|
||||
searchController.search = function (req, res, next) {
|
||||
if (!plugins.hasListeners('filter:search.query')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (!req.user && parseInt(meta.config.allowGuestSearching, 10) !== 1) {
|
||||
if (!req.loggedIn && parseInt(meta.config.allowGuestSearching, 10) !== 1) {
|
||||
return helpers.notAllowed(req, res);
|
||||
}
|
||||
|
||||
@@ -78,5 +77,3 @@ searchController.search = function (req, res, next) {
|
||||
res.render('search', searchData);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = searchController;
|
||||
|
||||
@@ -58,7 +58,7 @@ topController.get = function (req, res, next) {
|
||||
data.set = 'topics:votes';
|
||||
data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
|
||||
data.rssFeedUrl = nconf.get('relative_path') + '/top.rss';
|
||||
if (req.uid) {
|
||||
if (req.loggedIn) {
|
||||
data.rssFeedUrl += '?uid=' + req.uid + '&token=' + rssToken;
|
||||
}
|
||||
data.title = meta.config.homePageTitle || '[[pages:home]]';
|
||||
|
||||
@@ -146,7 +146,7 @@ topicsController.get = function (req, res, callback) {
|
||||
topicData.postDeleteDuration = parseInt(meta.config.postDeleteDuration, 10) || 0;
|
||||
topicData.scrollToMyPost = settings.scrollToMyPost;
|
||||
topicData.rssFeedUrl = nconf.get('relative_path') + '/topic/' + topicData.tid + '.rss';
|
||||
if (req.uid) {
|
||||
if (req.loggedIn) {
|
||||
topicData.rssFeedUrl += '?uid=' + req.uid + '&token=' + rssToken;
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ topicsController.get = function (req, res, callback) {
|
||||
req.session.tids_viewed[tid] = Date.now();
|
||||
}
|
||||
|
||||
if (req.uid) {
|
||||
if (req.loggedIn) {
|
||||
topics.markAsRead([tid], req.uid, function (err, markedRead) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
|
||||
@@ -9,7 +9,7 @@ var accountHelpers = require('./accounts/helpers');
|
||||
var userController = module.exports;
|
||||
|
||||
userController.getCurrentUser = function (req, res, next) {
|
||||
if (!req.uid) {
|
||||
if (!req.loggedIn) {
|
||||
return res.status(401).json('not-authorized');
|
||||
}
|
||||
async.waterfall([
|
||||
|
||||
@@ -61,11 +61,7 @@ mongoModule.questions = [
|
||||
mongoModule.helpers = mongoModule.helpers || {};
|
||||
mongoModule.helpers.mongo = require('./mongo/helpers');
|
||||
|
||||
mongoModule.init = function (callback) {
|
||||
callback = callback || function () { };
|
||||
|
||||
var mongoClient = require('mongodb').MongoClient;
|
||||
|
||||
function getConnectionString() {
|
||||
var usernamePassword = '';
|
||||
if (nconf.get('mongo:username') && nconf.get('mongo:password')) {
|
||||
usernamePassword = nconf.get('mongo:username') + ':' + encodeURIComponent(nconf.get('mongo:password')) + '@';
|
||||
@@ -92,7 +88,15 @@ mongoModule.init = function (callback) {
|
||||
servers.push(hosts[i] + ':' + ports[i]);
|
||||
}
|
||||
|
||||
var connString = nconf.get('mongo:uri') || 'mongodb://' + usernamePassword + servers.join() + '/' + nconf.get('mongo:database');
|
||||
return nconf.get('mongo:uri') || 'mongodb://' + usernamePassword + servers.join() + '/' + nconf.get('mongo:database');
|
||||
}
|
||||
|
||||
mongoModule.init = function (callback) {
|
||||
callback = callback || function () { };
|
||||
|
||||
var mongoClient = require('mongodb').MongoClient;
|
||||
|
||||
var connString = getConnectionString();
|
||||
|
||||
var connOptions = {
|
||||
poolSize: 10,
|
||||
@@ -262,3 +266,8 @@ mongoModule.close = function (callback) {
|
||||
callback = callback || function () {};
|
||||
db.close(callback);
|
||||
};
|
||||
|
||||
mongoModule.socketAdapter = function () {
|
||||
var mongoAdapter = require('socket.io-adapter-mongo');
|
||||
return mongoAdapter(getConnectionString());
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var pubsub = require('../../pubsub');
|
||||
|
||||
module.exports = function (db, module) {
|
||||
var helpers = module.helpers.mongo;
|
||||
|
||||
var LRU = require('lru-cache');
|
||||
var _ = require('lodash');
|
||||
var pubsub = require('../../pubsub');
|
||||
|
||||
var cache = LRU({
|
||||
max: 10000,
|
||||
@@ -35,6 +36,7 @@ module.exports = function (db, module) {
|
||||
cache.reset();
|
||||
};
|
||||
|
||||
|
||||
module.setObject = function (key, data, callback) {
|
||||
callback = callback || helpers.noop;
|
||||
if (!key || !data) {
|
||||
|
||||
8
src/database/mongo/pubsub.js
Normal file
8
src/database/mongo/pubsub.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
var mubsub = require('mubsub');
|
||||
|
||||
var db = require('../mongo');
|
||||
var client = mubsub(db.client);
|
||||
|
||||
module.exports = client.channel('pubsub');
|
||||
@@ -203,25 +203,36 @@ module.exports = function (db, module) {
|
||||
};
|
||||
|
||||
module.sortedSetRank = function (key, value, callback) {
|
||||
getSortedSetRank(module.getSortedSetRange, key, value, callback);
|
||||
getSortedSetRank(false, key, value, callback);
|
||||
};
|
||||
|
||||
module.sortedSetRevRank = function (key, value, callback) {
|
||||
getSortedSetRank(module.getSortedSetRevRange, key, value, callback);
|
||||
getSortedSetRank(true, key, value, callback);
|
||||
};
|
||||
|
||||
function getSortedSetRank(method, key, value, callback) {
|
||||
function getSortedSetRank(reverse, key, value, callback) {
|
||||
if (!key) {
|
||||
return callback();
|
||||
}
|
||||
value = helpers.valueToString(value);
|
||||
method(key, 0, -1, function (err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
module.sortedSetScore(key, value, function (err, score) {
|
||||
if (err || score === null) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
var rank = result.indexOf(value);
|
||||
callback(null, rank !== -1 ? rank : null);
|
||||
db.collection('objects').count({
|
||||
$or: [
|
||||
{
|
||||
_key: key,
|
||||
score: reverse ? { $gt: score } : { $lt: score },
|
||||
},
|
||||
{
|
||||
_key: key,
|
||||
score: score,
|
||||
value: reverse ? { $gt: value } : { $lt: value },
|
||||
},
|
||||
],
|
||||
}, function (err, rank) { callback(err, rank); });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -235,7 +246,7 @@ module.exports = function (db, module) {
|
||||
}
|
||||
|
||||
async.map(data, function (item, next) {
|
||||
getSortedSetRank(module.getSortedSetRange, item.key, item.value, next);
|
||||
getSortedSetRank(false, item.key, item.value, next);
|
||||
}, callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -169,5 +169,16 @@ redisModule.info = function (cxn, callback) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
redisModule.socketAdapter = function () {
|
||||
var redisAdapter = require('socket.io-redis');
|
||||
var pub = redisModule.connect();
|
||||
var sub = redisModule.connect();
|
||||
return redisAdapter({
|
||||
key: 'db:' + nconf.get('redis:database') + ':adapter_key',
|
||||
pubClient: pub,
|
||||
subClient: sub,
|
||||
});
|
||||
};
|
||||
|
||||
redisModule.helpers = redisModule.helpers || {};
|
||||
redisModule.helpers.redis = require('./redis/helpers');
|
||||
|
||||
39
src/database/redis/pubsub.js
Normal file
39
src/database/redis/pubsub.js
Normal file
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
var nconf = require('nconf');
|
||||
var util = require('util');
|
||||
var winston = require('winston');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
var channelName;
|
||||
|
||||
var PubSub = function () {
|
||||
var self = this;
|
||||
var db = require('../redis');
|
||||
var subClient = db.connect();
|
||||
this.pubClient = db.connect();
|
||||
|
||||
channelName = 'db:' + nconf.get('redis:database') + 'pubsub_channel';
|
||||
subClient.subscribe(channelName);
|
||||
|
||||
subClient.on('message', function (channel, message) {
|
||||
if (channel !== channelName) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var msg = JSON.parse(message);
|
||||
self.emit(msg.event, msg.data);
|
||||
} catch (err) {
|
||||
winston.error(err.stack);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
util.inherits(PubSub, EventEmitter);
|
||||
|
||||
PubSub.prototype.publish = function (event, data) {
|
||||
this.pubClient.publish(channelName, JSON.stringify({ event: event, data: data }));
|
||||
};
|
||||
|
||||
module.exports = new PubSub();
|
||||
@@ -22,7 +22,7 @@ require('./groups/posts')(Groups);
|
||||
require('./groups/user')(Groups);
|
||||
|
||||
|
||||
Groups.ephemeralGroups = ['guests'];
|
||||
Groups.ephemeralGroups = ['guests', 'spiders'];
|
||||
|
||||
Groups.getEphemeralGroup = function (groupName) {
|
||||
return {
|
||||
|
||||
@@ -56,6 +56,16 @@ Messaging.getMessages = function (params, callback) {
|
||||
messageData.forEach(function (messageData) {
|
||||
messageData.index = indices[messageData.messageId.toString()];
|
||||
});
|
||||
|
||||
// Filter out deleted messages unless you're the sender of said message
|
||||
messageData = messageData.filter(function (messageData) {
|
||||
if (messageData.deleted && parseInt(messageData.fromuid, 10) !== parseInt(params.uid, 10)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
next(null, messageData);
|
||||
},
|
||||
], callback);
|
||||
@@ -72,9 +82,6 @@ function canGet(hook, callerUid, uid, callback) {
|
||||
}
|
||||
|
||||
Messaging.parse = function (message, fromuid, uid, roomId, isNew, callback) {
|
||||
message = utils.decodeHTMLEntities(utils.stripHTMLTags(message));
|
||||
message = validator.escape(String(message));
|
||||
|
||||
plugins.fireHook('filter:parse.raw', message, function (err, parsed) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
|
||||
@@ -58,6 +58,7 @@ module.exports = function (Messaging) {
|
||||
timestamp: timestamp,
|
||||
fromuid: fromuid,
|
||||
roomId: roomId,
|
||||
deleted: 0,
|
||||
};
|
||||
|
||||
plugins.fireHook('filter:messaging.save', message, next);
|
||||
|
||||
@@ -51,11 +51,14 @@ module.exports = function (Messaging) {
|
||||
return msg && msg.fromuid;
|
||||
});
|
||||
|
||||
user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status'], next);
|
||||
user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'banned'], next);
|
||||
},
|
||||
function (users, next) {
|
||||
messages.forEach(function (message, index) {
|
||||
message.fromUser = users[index];
|
||||
message.fromUser.banned = !!parseInt(message.fromUser.banned, 10);
|
||||
message.fromUser.deleted = parseInt(message.fromuid, 10) !== message.fromUser.uid && message.fromUser.uid === 0;
|
||||
|
||||
var self = parseInt(message.fromuid, 10) === parseInt(uid, 10);
|
||||
message.self = self ? 1 : 0;
|
||||
message.timestampISO = utils.toISOString(message.timestamp);
|
||||
@@ -64,6 +67,8 @@ module.exports = function (Messaging) {
|
||||
if (message.hasOwnProperty('edited')) {
|
||||
message.editedISO = new Date(parseInt(message.edited, 10)).toISOString();
|
||||
}
|
||||
|
||||
message.deleted = !!parseInt(message.deleted, 10);
|
||||
});
|
||||
|
||||
async.map(messages, function (message, next) {
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var db = require('../database');
|
||||
|
||||
module.exports = function (Messaging) {
|
||||
Messaging.deleteMessage = function (mid, roomId, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Messaging.getUidsInRoom(roomId, 0, -1, next);
|
||||
},
|
||||
function (uids, next) {
|
||||
if (!uids.length) {
|
||||
return next();
|
||||
async.apply(Messaging.getMessageField, mid, 'deleted'),
|
||||
function (deleted, next) {
|
||||
if (parseInt(deleted, 10)) {
|
||||
return next(new Error('[[error:chat-deleted-already]]'));
|
||||
}
|
||||
var keys = uids.map(function (uid) {
|
||||
return 'uid:' + uid + ':chat:room:' + roomId + ':mids';
|
||||
});
|
||||
db.sortedSetsRemove(keys, mid, next);
|
||||
|
||||
Messaging.setMessageField(mid, 'deleted', 1, next);
|
||||
},
|
||||
function (next) {
|
||||
db.delete('message:' + mid, next);
|
||||
], callback);
|
||||
};
|
||||
|
||||
Messaging.restoreMessage = function (mid, roomId, callback) {
|
||||
async.waterfall([
|
||||
async.apply(Messaging.getMessageField, mid, 'deleted'),
|
||||
function (deleted, next) {
|
||||
if (!parseInt(deleted, 10)) {
|
||||
return next(new Error('[[error:chat-restored-already]]'));
|
||||
}
|
||||
|
||||
Messaging.setMessageField(mid, 'deleted', 0, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -83,7 +83,7 @@ module.exports = function (middleware) {
|
||||
},
|
||||
user: function (next) {
|
||||
var userData = {
|
||||
uid: 0,
|
||||
uid: req.uid,
|
||||
username: '[[global:guest]]',
|
||||
userslug: '',
|
||||
fullname: '[[global:guest]]',
|
||||
@@ -93,7 +93,7 @@ module.exports = function (middleware) {
|
||||
reputation: 0,
|
||||
'email:confirmed': 0,
|
||||
};
|
||||
if (req.uid) {
|
||||
if (req.loggedIn) {
|
||||
user.getUserFields(req.uid, Object.keys(userData), next);
|
||||
} else {
|
||||
next(null, userData);
|
||||
|
||||
@@ -59,12 +59,12 @@ middleware.pageView = function (req, res, next) {
|
||||
|
||||
plugins.fireHook('action:middleware.pageView', { req: req });
|
||||
|
||||
if (req.user) {
|
||||
user.updateLastOnlineTime(req.user.uid);
|
||||
if (req.loggedIn) {
|
||||
user.updateLastOnlineTime(req.uid);
|
||||
if (req.path.startsWith('/api/users') || req.path.startsWith('/users')) {
|
||||
user.updateOnlineUsers(req.user.uid, next);
|
||||
user.updateOnlineUsers(req.uid, next);
|
||||
} else {
|
||||
user.updateOnlineUsers(req.user.uid);
|
||||
user.updateOnlineUsers(req.uid);
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
@@ -112,7 +112,7 @@ middleware.routeTouchIcon = function (req, res) {
|
||||
};
|
||||
|
||||
middleware.privateTagListing = function (req, res, next) {
|
||||
if (!req.user && parseInt(meta.config.privateTagListing, 10) === 1) {
|
||||
if (!req.loggedIn && parseInt(meta.config.privateTagListing, 10) === 1) {
|
||||
controllers.helpers.notAllowed(req, res);
|
||||
} else {
|
||||
next();
|
||||
@@ -143,7 +143,7 @@ function expose(exposedField, method, field, req, res, next) {
|
||||
}
|
||||
|
||||
middleware.privateUploads = function (req, res, next) {
|
||||
if (req.user || parseInt(meta.config.privateUploads, 10) !== 1) {
|
||||
if (req.loggedIn || parseInt(meta.config.privateUploads, 10) !== 1) {
|
||||
return next();
|
||||
}
|
||||
if (req.path.startsWith(nconf.get('relative_path') + '/assets/uploads/files')) {
|
||||
|
||||
@@ -14,7 +14,7 @@ var controllers = {
|
||||
|
||||
module.exports = function (middleware) {
|
||||
middleware.authenticate = function (req, res, next) {
|
||||
if (req.uid) {
|
||||
if (req.loggedIn) {
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ module.exports = function (middleware) {
|
||||
*/
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
if (!req.uid) {
|
||||
if (!req.loggedIn) {
|
||||
return setImmediate(next, null, false);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ module.exports = function (middleware) {
|
||||
}
|
||||
|
||||
middleware.checkGlobalPrivacySettings = function (req, res, next) {
|
||||
if (!req.uid && !!parseInt(meta.config.privateUserInfo, 10)) {
|
||||
if (!req.loggedIn && !!parseInt(meta.config.privateUserInfo, 10)) {
|
||||
return middleware.authenticate(req, res, next);
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ module.exports = function (middleware) {
|
||||
};
|
||||
|
||||
middleware.requireUser = function (req, res, next) {
|
||||
if (req.uid) {
|
||||
if (req.loggedIn) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ require('./posts/tools')(Posts);
|
||||
require('./posts/votes')(Posts);
|
||||
require('./posts/bookmarks')(Posts);
|
||||
require('./posts/queue')(Posts);
|
||||
require('./posts/diffs')(Posts);
|
||||
|
||||
Posts.exists = function (pid, callback) {
|
||||
db.isSortedSetMember('posts:pid', pid, callback);
|
||||
|
||||
77
src/posts/diffs.js
Normal file
77
src/posts/diffs.js
Normal file
@@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var validator = require('validator');
|
||||
var diff = require('diff');
|
||||
|
||||
var db = require('../database');
|
||||
var plugins = require('../plugins');
|
||||
var translator = require('../translator');
|
||||
|
||||
module.exports = function (Posts) {
|
||||
Posts.diffs = {};
|
||||
|
||||
Posts.diffs.exists = function (pid, callback) {
|
||||
db.sortedSetCard('post:' + pid + ':diffs', function (err, numDiffs) {
|
||||
return callback(err, numDiffs > 0);
|
||||
});
|
||||
};
|
||||
|
||||
Posts.diffs.list = function (pid, callback) {
|
||||
db.getSortedSetRangeWithScores('post:' + pid + ':diffs', 0, -1, function (err, diffs) {
|
||||
callback(err, diffs ? diffs.map(function (diffObj) {
|
||||
return diffObj.score;
|
||||
}).reverse() : null);
|
||||
});
|
||||
};
|
||||
|
||||
Posts.diffs.save = function (pid, oldContent, newContent, callback) {
|
||||
db.sortedSetAdd('post:' + pid + ':diffs', Date.now(), diff.createPatch('', newContent, oldContent), callback);
|
||||
};
|
||||
|
||||
Posts.diffs.load = function (pid, since, uid, callback) {
|
||||
// Retrieves all diffs made since `since` and replays them to reconstruct what the post looked like at `since`
|
||||
since = parseInt(since, 10);
|
||||
|
||||
if (isNaN(since) || since > Date.now()) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
post: async.apply(Posts.getPostSummaryByPids, [pid], uid, {
|
||||
parse: false,
|
||||
}),
|
||||
diffs: async.apply(db.getSortedSetRangeByScore.bind(db), 'post:' + pid + ':diffs', 0, -1, since, Date.now()),
|
||||
}, function (err, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
data.post = data.post[0];
|
||||
data.post.content = validator.unescape(data.post.content);
|
||||
|
||||
// Replace content with re-constructed content from that point in time
|
||||
data.post.content = data.diffs.reverse().reduce(function (content, diffString) {
|
||||
return diff.applyPatch(content, diffString, {
|
||||
fuzzFactor: 1,
|
||||
});
|
||||
}, data.post.content);
|
||||
|
||||
// Clear editor data (as it is outdated for this content)
|
||||
delete data.post.edited;
|
||||
data.post.editor = null;
|
||||
|
||||
data.post.content = String(data.post.content || '');
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
plugins.fireHook('filter:parse.post', { postData: data.post }, next);
|
||||
},
|
||||
function (data, next) {
|
||||
data.postData.content = translator.escape(data.postData.content);
|
||||
next(null, data.postData);
|
||||
},
|
||||
], callback);
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -20,6 +20,7 @@ module.exports = function (Posts) {
|
||||
});
|
||||
|
||||
Posts.edit = function (data, callback) {
|
||||
var oldContent; // for diffing purposes
|
||||
var postData;
|
||||
var results;
|
||||
|
||||
@@ -39,6 +40,7 @@ module.exports = function (Posts) {
|
||||
}
|
||||
|
||||
postData = _postData;
|
||||
oldContent = postData.content;
|
||||
postData.content = data.content;
|
||||
postData.edited = Date.now();
|
||||
postData.editor = data.uid;
|
||||
@@ -63,6 +65,9 @@ module.exports = function (Posts) {
|
||||
results = _results;
|
||||
Posts.setPostFields(data.pid, postData, next);
|
||||
},
|
||||
function (next) {
|
||||
Posts.diffs.save(data.pid, oldContent, data.content, next);
|
||||
},
|
||||
function (next) {
|
||||
postData.cid = results.topic.cid;
|
||||
postData.topic = results.topic;
|
||||
|
||||
@@ -10,6 +10,11 @@ var plugins = require('../plugins');
|
||||
|
||||
var helpers = module.exports;
|
||||
|
||||
var uidToSystemGroup = {
|
||||
0: 'guests',
|
||||
'-1': 'spiders',
|
||||
};
|
||||
|
||||
helpers.some = function (tasks, callback) {
|
||||
async.some(tasks, function (task, next) {
|
||||
task(next);
|
||||
@@ -27,8 +32,8 @@ helpers.isUserAllowedTo = function (privilege, uid, cid, callback) {
|
||||
};
|
||||
|
||||
function isUserAllowedToCids(privilege, uid, cids, callback) {
|
||||
if (parseInt(uid, 10) === 0) {
|
||||
return isGuestAllowedToCids(privilege, cids, callback);
|
||||
if (parseInt(uid, 10) <= 0) {
|
||||
return isSystemGroupAllowedToCids(privilege, uid, cids, callback);
|
||||
}
|
||||
|
||||
var userKeys = [];
|
||||
@@ -42,8 +47,8 @@ function isUserAllowedToCids(privilege, uid, cids, callback) {
|
||||
}
|
||||
|
||||
function isUserAllowedToPrivileges(privileges, uid, cid, callback) {
|
||||
if (parseInt(uid, 10) === 0) {
|
||||
return isGuestAllowedToPrivileges(privileges, cid, callback);
|
||||
if (parseInt(uid, 10) <= 0) {
|
||||
return isSystemGroupAllowedToPrivileges(privileges, uid, cid, callback);
|
||||
}
|
||||
|
||||
var userKeys = [];
|
||||
@@ -100,20 +105,20 @@ helpers.isUsersAllowedTo = function (privilege, uids, cid, callback) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
function isGuestAllowedToCids(privilege, cids, callback) {
|
||||
function isSystemGroupAllowedToCids(privilege, uid, cids, callback) {
|
||||
var groupKeys = cids.map(function (cid) {
|
||||
return 'cid:' + cid + ':privileges:groups:' + privilege;
|
||||
});
|
||||
|
||||
groups.isMemberOfGroups('guests', groupKeys, callback);
|
||||
groups.isMemberOfGroups(uidToSystemGroup[uid], groupKeys, callback);
|
||||
}
|
||||
|
||||
function isGuestAllowedToPrivileges(privileges, cid, callback) {
|
||||
function isSystemGroupAllowedToPrivileges(privileges, uid, cid, callback) {
|
||||
var groupKeys = privileges.map(function (privilege) {
|
||||
return 'cid:' + cid + ':privileges:groups:' + privilege;
|
||||
});
|
||||
|
||||
groups.isMemberOfGroups('guests', groupKeys, callback);
|
||||
groups.isMemberOfGroups(uidToSystemGroup[uid], groupKeys, callback);
|
||||
}
|
||||
|
||||
helpers.getUserPrivileges = function (cid, hookName, userPrivilegeList, callback) {
|
||||
|
||||
@@ -1,48 +1,70 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var nconf = require('nconf');
|
||||
var util = require('util');
|
||||
var winston = require('winston');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
var channelName;
|
||||
var real;
|
||||
var fake = {
|
||||
publishQueue: [],
|
||||
publish: function (event, data) {
|
||||
fake.publishQueue.push({ event: event, data: data });
|
||||
},
|
||||
listenQueue: {},
|
||||
on: function (event, callback) {
|
||||
if (!Object.prototype.hasOwnProperty.call(fake.listenQueue, event)) {
|
||||
fake.listenQueue[event] = [];
|
||||
}
|
||||
fake.listenQueue[event].push(callback);
|
||||
},
|
||||
removeAllListeners: function (event) {
|
||||
delete fake.listenQueue[event];
|
||||
},
|
||||
};
|
||||
|
||||
var PubSub = function () {
|
||||
var self = this;
|
||||
if (nconf.get('redis')) {
|
||||
var redis = require('./database/redis');
|
||||
var subClient = redis.connect();
|
||||
this.pubClient = redis.connect();
|
||||
function get() {
|
||||
if (real) {
|
||||
return real;
|
||||
}
|
||||
|
||||
channelName = 'db:' + nconf.get('redis:database') + 'pubsub_channel';
|
||||
subClient.subscribe(channelName);
|
||||
var pubsub;
|
||||
|
||||
subClient.on('message', function (channel, message) {
|
||||
if (channel !== channelName) {
|
||||
return;
|
||||
}
|
||||
if (nconf.get('isCluster') === 'false') {
|
||||
var EventEmitter = require('events');
|
||||
pubsub = new EventEmitter();
|
||||
pubsub.publish = pubsub.emit.bind(pubsub);
|
||||
} else if (nconf.get('redis')) {
|
||||
pubsub = require('./database/redis/pubsub');
|
||||
} else if (nconf.get('mongo')) {
|
||||
pubsub = require('./database/mongo/pubsub');
|
||||
}
|
||||
|
||||
try {
|
||||
var msg = JSON.parse(message);
|
||||
self.emit(msg.event, msg.data);
|
||||
} catch (err) {
|
||||
winston.error(err.stack);
|
||||
}
|
||||
if (!pubsub) {
|
||||
return fake;
|
||||
}
|
||||
|
||||
Object.keys(fake.listenQueue).forEach(function (event) {
|
||||
fake.listenQueue[event].forEach(function (callback) {
|
||||
pubsub.on(event, callback);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
fake.publishQueue.forEach(function (msg) {
|
||||
pubsub.publish(msg.event, msg.data);
|
||||
});
|
||||
|
||||
real = pubsub;
|
||||
fake = null;
|
||||
|
||||
return pubsub;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
publish: function (event, data) {
|
||||
get().publish(event, data);
|
||||
},
|
||||
on: function (event, callback) {
|
||||
get().on(event, callback);
|
||||
},
|
||||
removeAllListeners: function (event) {
|
||||
get().removeAllListeners(event);
|
||||
},
|
||||
};
|
||||
|
||||
util.inherits(PubSub, EventEmitter);
|
||||
|
||||
PubSub.prototype.publish = function (event, data) {
|
||||
if (this.pubClient) {
|
||||
this.pubClient.publish(channelName, JSON.stringify({ event: event, data: data }));
|
||||
} else {
|
||||
this.emit(event, data);
|
||||
}
|
||||
};
|
||||
|
||||
var pubsub = new PubSub();
|
||||
|
||||
module.exports = pubsub;
|
||||
|
||||
@@ -20,7 +20,15 @@ Auth.initialize = function (app, middleware) {
|
||||
app.use(passport.session());
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
req.uid = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
var isSpider = req.isSpider();
|
||||
req.loggedIn = !isSpider && !!req.user;
|
||||
if (isSpider) {
|
||||
req.uid = -1;
|
||||
} else if (req.user) {
|
||||
req.uid = parseInt(req.user.uid, 10);
|
||||
} else {
|
||||
req.uid = 0;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Sockets.init = function (server) {
|
||||
path: nconf.get('relative_path') + '/socket.io',
|
||||
});
|
||||
|
||||
addRedisAdapter(io);
|
||||
io.adapter(nconf.get('redis') ? require('../database/redis').socketAdapter() : db.socketAdapter());
|
||||
|
||||
io.use(socketioWildcard);
|
||||
io.use(authorize);
|
||||
@@ -214,22 +214,6 @@ function authorize(socket, callback) {
|
||||
], callback);
|
||||
}
|
||||
|
||||
function addRedisAdapter(io) {
|
||||
if (nconf.get('redis')) {
|
||||
var redisAdapter = require('socket.io-redis');
|
||||
var redis = require('../database/redis');
|
||||
var pub = redis.connect();
|
||||
var sub = redis.connect();
|
||||
io.adapter(redisAdapter({
|
||||
key: 'db:' + nconf.get('redis:database') + ':adapter_key',
|
||||
pubClient: pub,
|
||||
subClient: sub,
|
||||
}));
|
||||
} else if (nconf.get('isCluster') === 'true') {
|
||||
winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.');
|
||||
}
|
||||
}
|
||||
|
||||
Sockets.in = function (room) {
|
||||
return io.in(room);
|
||||
};
|
||||
|
||||
@@ -188,6 +188,14 @@ SocketModules.chats.loadRoom = function (socket, data, callback) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
SocketModules.chats.getUsersInRoom = function (socket, data, callback) {
|
||||
if (!data || !data.roomId) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
Messaging.getUsersInRoom(data.roomId, 0, -1, callback);
|
||||
};
|
||||
|
||||
SocketModules.chats.addUserToRoom = function (socket, data, callback) {
|
||||
if (!data || !data.roomId || !data.username) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
@@ -220,7 +228,7 @@ SocketModules.chats.addUserToRoom = function (socket, data, callback) {
|
||||
return next(new Error('[[error:no-user]]'));
|
||||
}
|
||||
if (socket.uid === parseInt(uid, 10)) {
|
||||
return next(new Error('[[error:cant-add-self-to-chat-room]]'));
|
||||
return next(new Error('[[error:cant-chat-with-yourself]]'));
|
||||
}
|
||||
async.parallel({
|
||||
settings: async.apply(user.getSettings, uid),
|
||||
@@ -295,6 +303,21 @@ SocketModules.chats.delete = function (socket, data, callback) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
SocketModules.chats.restore = function (socket, data, callback) {
|
||||
if (!data || !data.roomId || !data.messageId) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Messaging.canDelete(data.messageId, socket.uid, next);
|
||||
},
|
||||
function (next) {
|
||||
Messaging.restoreMessage(data.messageId, data.roomId, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
SocketModules.chats.canMessage = function (socket, roomId, callback) {
|
||||
Messaging.canMessageRoom(socket.uid, roomId, callback);
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ require('./posts/move')(SocketPosts);
|
||||
require('./posts/votes')(SocketPosts);
|
||||
require('./posts/bookmarks')(SocketPosts);
|
||||
require('./posts/tools')(SocketPosts);
|
||||
require('./posts/diffs')(SocketPosts);
|
||||
|
||||
SocketPosts.reply = function (socket, data, callback) {
|
||||
if (!data || !data.tid || (parseInt(meta.config.minimumPostLength, 10) !== 0 && !data.content)) {
|
||||
|
||||
13
src/socket.io/posts/diffs.js
Normal file
13
src/socket.io/posts/diffs.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
var posts = require('../../posts');
|
||||
|
||||
module.exports = function (SocketPosts) {
|
||||
SocketPosts.getDiffs = function (socket, data, callback) {
|
||||
posts.diffs.list(data.pid, callback);
|
||||
};
|
||||
|
||||
SocketPosts.showPostAt = function (socket, data, callback) {
|
||||
posts.diffs.load(data.pid, data.since, socket.uid, callback);
|
||||
};
|
||||
};
|
||||
@@ -51,6 +51,7 @@ module.exports = function (SocketPosts) {
|
||||
postSharing: function (next) {
|
||||
social.getActivePostSharing(next);
|
||||
},
|
||||
history: async.apply(posts.diffs.exists, data.pid),
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
@@ -64,6 +65,7 @@ module.exports = function (SocketPosts) {
|
||||
results.posts.display_moderator_tools = results.posts.display_edit_tools || results.posts.display_delete_tools;
|
||||
results.posts.display_move_tools = results.isAdmin || results.isModerator;
|
||||
results.posts.display_ip_ban = (results.isAdmin || results.isGlobalMod) && !results.posts.selfPost;
|
||||
results.posts.display_history = results.history;
|
||||
|
||||
if (!results.isAdmin && !results.isGlobalMod && !results.isModerator) {
|
||||
results.posts.ip = undefined;
|
||||
|
||||
49
src/upgrades/1.8.0/give_spiders_privileges.js
Normal file
49
src/upgrades/1.8.0/give_spiders_privileges.js
Normal file
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
var async = require('async');
|
||||
var groups = require('../../groups');
|
||||
var privileges = require('../../privileges');
|
||||
var db = require('../../database');
|
||||
|
||||
module.exports = {
|
||||
name: 'Give category access privileges to spiders system group',
|
||||
timestamp: Date.UTC(2018, 0, 31),
|
||||
method: function (callback) {
|
||||
db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
async.eachSeries(cids, function (cid, next) {
|
||||
getGroupPrivileges(cid, function (err, groupPrivileges) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var privs = [];
|
||||
if (groupPrivileges['groups:find']) {
|
||||
privs.push('find');
|
||||
}
|
||||
if (groupPrivileges['groups:read']) {
|
||||
privs.push('read');
|
||||
}
|
||||
if (groupPrivileges['groups:topics:read']) {
|
||||
privs.push('topics:read');
|
||||
}
|
||||
|
||||
privileges.categories.give(privs, cid, 'spiders', next);
|
||||
});
|
||||
}, callback);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
function getGroupPrivileges(cid, callback) {
|
||||
var tasks = {};
|
||||
|
||||
['groups:find', 'groups:read', 'groups:topics:read'].forEach(function (privilege) {
|
||||
tasks[privilege] = async.apply(groups.isMember, 'guests', 'cid:' + cid + ':privileges:' + privilege);
|
||||
});
|
||||
|
||||
async.parallel(tasks, callback);
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
</td>
|
||||
<!-- ENDIF files.isFile -->
|
||||
|
||||
<td class="col-md-2 text-right"><!-- IF files.size -->{files.sizeHumanReadable}<!-- ELSE -->[[admin/manage/uploads:filecount, {files.fileCount}]]<!-- ENDIF files.size --></td>
|
||||
<td class="col-md-2 text-right"><!-- IF files.isFile -->{files.sizeHumanReadable}<!-- ELSE -->[[admin/manage/uploads:filecount, {files.fileCount}]]<!-- ENDIF files.isFile --></td>
|
||||
|
||||
<td role="button" class="col-md-1 text-right"><i class="delete fa fa-fw fa-trash-o <!-- IF !files.isFile --> hidden<!-- ENDIF !files.isFile -->"></i></td>
|
||||
</tr>
|
||||
|
||||
@@ -17,6 +17,7 @@ var cookieParser = require('cookie-parser');
|
||||
var session = require('express-session');
|
||||
var useragent = require('express-useragent');
|
||||
var favicon = require('serve-favicon');
|
||||
var detector = require('spider-detector');
|
||||
var helmet = require('helmet');
|
||||
|
||||
var db = require('./database');
|
||||
@@ -162,6 +163,7 @@ function setupExpressApp(app, callback) {
|
||||
app.use(bodyParser.json());
|
||||
app.use(cookieParser());
|
||||
app.use(useragent.express());
|
||||
app.use(detector.middleware());
|
||||
|
||||
app.use(session({
|
||||
store: db.sessionStore,
|
||||
|
||||
@@ -17,6 +17,9 @@ describe('Sorted Set methods', function () {
|
||||
function (next) {
|
||||
db.sortedSetAdd('sortedSetTest3', [2, 4], ['value2', 'value4'], next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('sortedSetTest4', [1, 1, 2, 3, 5], ['b', 'a', 'd', 'e', 'c'], next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('sortedSetLex', [0, 0, 0, 0], ['a', 'b', 'c', 'd'], next);
|
||||
},
|
||||
@@ -305,6 +308,33 @@ describe('Sorted Set methods', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the rank sorted by the score and then the value (a)', function (done) {
|
||||
db.sortedSetRank('sortedSetTest4', 'a', function (err, rank) {
|
||||
assert.equal(err, null);
|
||||
assert.equal(arguments.length, 2);
|
||||
assert.equal(rank, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the rank sorted by the score and then the value (b)', function (done) {
|
||||
db.sortedSetRank('sortedSetTest4', 'b', function (err, rank) {
|
||||
assert.equal(err, null);
|
||||
assert.equal(arguments.length, 2);
|
||||
assert.equal(rank, 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the rank sorted by the score and then the value (c)', function (done) {
|
||||
db.sortedSetRank('sortedSetTest4', 'c', function (err, rank) {
|
||||
assert.equal(err, null);
|
||||
assert.equal(arguments.length, 2);
|
||||
assert.equal(rank, 4);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortedSetRevRank()', function () {
|
||||
|
||||
@@ -14,8 +14,8 @@ var helpers = require('./helpers');
|
||||
var socketModules = require('../src/socket.io/modules');
|
||||
|
||||
describe('Messaging Library', function () {
|
||||
var fooUid;
|
||||
var bazUid;
|
||||
var fooUid; // the admin
|
||||
var bazUid; // the user with chat restriction enabled
|
||||
var herpUid;
|
||||
var roomId;
|
||||
|
||||
@@ -156,7 +156,7 @@ describe('Messaging Library', function () {
|
||||
|
||||
it('should fail to add self to room', function (done) {
|
||||
socketModules.chats.addUserToRoom({ uid: fooUid }, { roomId: roomId, username: 'foo' }, function (err) {
|
||||
assert.equal(err.message, '[[error:cant-add-self-to-chat-room]]');
|
||||
assert.equal(err.message, '[[error:cant-chat-with-yourself]]');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -593,21 +593,71 @@ describe('Messaging Library', function () {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should delete message', function (done) {
|
||||
it('should mark the message as deleted', function (done) {
|
||||
socketModules.chats.delete({ uid: fooUid }, { messageId: mid, roomId: roomId }, function (err) {
|
||||
assert.ifError(err);
|
||||
db.exists('message:' + mid, function (err, exists) {
|
||||
db.getObjectField('message:' + mid, 'deleted', function (err, value) {
|
||||
assert.ifError(err);
|
||||
assert(!exists);
|
||||
db.isSortedSetMember('uid:' + fooUid + ':chat:room:' + roomId + ':mids', mid, function (err, isMember) {
|
||||
assert.ifError(err);
|
||||
assert(!isMember);
|
||||
done();
|
||||
});
|
||||
assert.strictEqual(1, parseInt(value, 10));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show deleted message to original users', function (done) {
|
||||
socketModules.chats.getMessages({ uid: fooUid }, { uid: fooUid, roomId: roomId, start: 0 }, function (err, messages) {
|
||||
assert.ifError(err);
|
||||
|
||||
// Reduce messages to their mids
|
||||
var mids = messages.reduce(function (mids, cur) {
|
||||
mids.push(cur.messageId);
|
||||
return mids;
|
||||
}, []);
|
||||
|
||||
assert(mids.includes(mid));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show deleted message to other users', function (done) {
|
||||
socketModules.chats.getMessages({ uid: herpUid }, { uid: herpUid, roomId: roomId, start: 0 }, function (err, messages) {
|
||||
assert.ifError(err);
|
||||
|
||||
// Reduce messages to their mids
|
||||
var mids = messages.reduce(function (mids, cur) {
|
||||
mids.push(cur.messageId);
|
||||
return mids;
|
||||
}, []);
|
||||
|
||||
assert(!mids.includes(mid));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should error out if a message is deleted again', function (done) {
|
||||
socketModules.chats.delete({ uid: fooUid }, { messageId: mid, roomId: roomId }, function (err) {
|
||||
assert.strictEqual('[[error:chat-deleted-already]]', err.message);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should restore the message', function (done) {
|
||||
socketModules.chats.restore({ uid: fooUid }, { messageId: mid, roomId: roomId }, function (err) {
|
||||
assert.ifError(err);
|
||||
db.getObjectField('message:' + mid, 'deleted', function (err, value) {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(0, parseInt(value, 10));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should error out if a message is restored again', function (done) {
|
||||
socketModules.chats.restore({ uid: fooUid }, { messageId: mid, roomId: roomId }, function (err) {
|
||||
assert.strictEqual('[[error:chat-restored-already]]', err.message);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('controller', function () {
|
||||
|
||||
Reference in New Issue
Block a user