Merge branch 'master' into install_script

v1.18.x
Julian Lam 12 years ago
commit 5d7ec10aae

@ -59,7 +59,8 @@ var config = {
},
"show_motd": true,
"motd": undefined
"motd": undefined,
"post_delay" : 10000
}
config.url = config.base_url + (config.use_port ? ':' + config.port : '') + '/';

@ -0,0 +1 @@
@import "style";

@ -278,7 +278,56 @@ footer.footer {
width: 70%;
margin-left: 10px;
overflow: hidden;
height: 50px;
height: 32px;
}
span {
display: block;
float: left;
width: 70%;
margin-left: 10px;
overflow: hidden;
height: 16px;
margin-top: -10px;
color: #666;
}
}
}
.recent-replies {
overflow-y: auto;
overflow-x: hidden;
ul {
width: 100%;
height: 50px;
line-height: 16px;
margin-left: 1px;
padding: 5px;
li {
line-height: 16px;
img {
display: block;
float: left;
}
p {
display: block;
float: left;
width: 70%;
margin-left: 10px;
overflow: hidden;
height: 32px;
}
span {
display: block;
float: left;
width: 70%;
margin-left: 10px;
overflow: hidden;
height: 16px;
margin-top: -10px;
color: #666;
}
}
}
}

@ -194,11 +194,17 @@ var socket,
app.post_topic(id);
}
} else if (post_mode === 'edit') {
reply_title.innerHTML = 'You are editing "' + title + '"';
if(title === "") {
post_title.style.display = "none";
}
else {
post_title.style.display = "block";
post_title.value = title;
}
reply_title.style.display = "none";
socket.emit('api:posts.getRawPost', { pid: pid });
post_title.style.display = "none";
reply_title.style.display = "block";
post_content.focus();
submit_post_btn.onclick = function() {
app.edit_post(pid);
@ -307,9 +313,10 @@ var socket,
};
app.edit_post = function(pid) {
var content = $('#post_content');
var title = $('#post_title'),
content = $('#post_content');
if (content.val().length < 5) {
if (title.val().length < 5 || content.val().length < 5) {
app.alert({
title: 'Topic Post Failure',
message: 'You need to write more dude.',
@ -320,7 +327,7 @@ var socket,
return;
}
socket.emit('api:posts.edit', { pid: pid, content: content.val() });
socket.emit('api:posts.edit', { pid: pid, content: content.val(), title: title.val() });
app.close_post_window();
content.val('');

@ -5,7 +5,8 @@
var config = {},
templates,
fs = null,
available_templates = [];
available_templates = [],
parsed_variables = {};
module.exports = templates = {};
@ -153,8 +154,8 @@
(function() {
jQuery.get(API_URL + api_url, function(data) {
if(!data) {
window.location.href = '/404';
if(data === false) {
ajaxify.go('404');
return;
}
@ -168,12 +169,25 @@
if (!templates[tpl_url] || !template_data) return;
document.getElementById('content').innerHTML = templates[tpl_url].parse(JSON.parse(template_data));
if (callback) callback(true);
jQuery('#content [template-variable]').each(function(index, element) {
templates.set(element.getAttribute('template-variable'), element.value);
});
if (callback) {
callback(true);
}
}
}
templates.get = function(key) {
return parsed_variables[key];
}
templates.set = function(key, value) {
parsed_variables[key] = value;
}
//modified from https://github.com/psychobunny/dcp.templates
var parse = function(data) {

@ -69,12 +69,16 @@
</div>
<br/>
<div id="user-action-alert" class="alert alert-success hide"></div>
</div>
<input type="hidden" template-variable="yourid" value="{yourid}" />
<input type="hidden" template-variable="theirid" value="{theirid}" />
<script type="text/javascript">
var yourid = '{yourid}';
var theirid = '{theirid}';
var yourid = templates.get('yourid'),
theirid = templates.get('theirid');
(function() {

@ -137,11 +137,14 @@
</div>
</div>
<input type="hidden" template-variable="gravatarpicture" value="{gravatarpicture}" />
<input type="hidden" template-variable="uploadedpicture" value="{uploadedpicture}" />
<script type="text/javascript">
var gravatarPicture = '{gravatarpicture}';
var uploadedPicture = '{uploadedpicture}';
var gravatarPicture = templates.get('gravatarpicture');
var uploadedPicture = templates.get('uploadedpicture');
$(document).ready(function() {

@ -27,6 +27,7 @@
<div class="pull-right">
<img style="width: 48px; height: 48px; /*temporary*/" src="/graph/users/{topics.teaser_username}/picture" />
<p><strong>{topics.teaser_username}</strong>: {topics.teaser_text}</p>
<span>posted {topics.teaser_timestamp} ago</span>
</div>
</div>
<div>
@ -42,13 +43,15 @@
</li></a>
<!-- END topics -->
</ul>
<hr />
<button id="new_post" class="btn btn-primary btn-large {show_category_features}">New Topic</button>
</div>
<div class="span3 {show_category_features}">
<div class="sidebar-block img-polaroid">
<div class="block-header">
Recent Replies
</div>
<div class="block-content">
<div class="block-content recent-replies" id="category_recent_replies">
</div>
</div>
@ -76,13 +79,15 @@
</div>
<hr />
<button id="new_post" class="btn btn-primary btn-large {show_category_features}">New Topic</button>
<input type="hidden" template-variable="category_id" value="{category_id}" />
<script type="text/javascript">
(function() {
var room = 'category_' + '{category_id}';
var cid = templates.get('category_id'),
room = 'category_' + cid;
app.enter_room(room);
var new_post = document.getElementById('new_post');
@ -129,9 +134,31 @@
//socket.emit('api:topics.getRecentReplies', tid);
socket.on('api:topics.getRecentReplies', function(replies) {
console.log(replies);
socket.emit('api:categories.getRecentReplies', cid);
socket.on('api:categories.getRecentReplies', function(replies) {
if (replies === false) {
return;
}
var users = replies.users,
posts = replies.posts,
recent_replies = document.getElementById('category_recent_replies');
recent_replies.innerHTML = '';
for (var i=0, ii=posts.pids.length; i<ii; i++) {
var a = document.createElement('a'),
ul = document.createElement('ul'),
username = users[posts.uid[i]].username,
picture = users[posts.uid[i]].picture;
//temp until design finalized
ul.innerHTML = '<li><img title="' + username + '" style="width: 48px; height: 48px; /*temporary*/" src="' + picture + '" class="" />'
+ '<p><strong>' + username + '</strong>: ' + posts.content[i] + '</p><span>posted ' + utils.relativeTime(posts.timestamp[i]) + ' ago</span></li>';
a.appendChild(ul);
recent_replies.appendChild(a);
}
});
})();

@ -40,12 +40,15 @@
<div id="no-friend-notice" class="alert alert-warning hide">This user doesn't have any friends :(</div>
</div>
<script>
<input type="hidden" template-variable="yourid" value="{yourid}" />
<input type="hidden" template-variable="theirid" value="{theirid}" />
<input type="hidden" template-variable="friendCount" value="{friendCount}" />
var yourid = '{yourid}';
var theirid = '{theirid}';
<script type="text/javascript">
var friendCount = '{friendCount}';
var yourid = templates.get('yourid'),
theirid = templates.get('theirid'),
friendCount = templates.get('friendCount');
(function() {

@ -18,7 +18,7 @@
<script type="text/javascript" src="/src/jquery.form.js"></script>
<script type="text/javascript" src="/src/utils.js"></script>
<link rel="stylesheet" type="text/css" href="/css/style.css" />
<link rel="stylesheet" type="text/css" href="/css/nodebb.css" />
</head>
<body>

@ -19,8 +19,13 @@
<button class="btn btn-primary" id="reset" type="submit" disabled>Reset Password</button>
</div>
</div>
<input type="hidden" template-variable="reset_code" value="{reset_code}" />
<script type="text/javascript">
(function() {
var reset_code = templates.get('reset_code');
var resetEl = document.getElementById('reset'),
password = document.getElementById('password'),
repeat = document.getElementById('repeat'),
@ -33,12 +38,12 @@
noticeEl.querySelector('p').innerHTML = 'The password entered it too short, please pick a different password!';
noticeEl.style.display = 'block';
} else if (password.value === repeat.value) {
socket.emit('user:reset.commit', { code: '{reset_code}', password: password.value });
socket.emit('user:reset.commit', { code: reset_code, password: password.value });
}
}, false);
// Enable the form if the code is valid
socket.emit('user:reset.valid', { code: '{reset_code}' });
socket.emit('user:reset.valid', { code: reset_code });
ajaxify.register_events(['user:reset.valid', 'user:reset.commit']);

@ -23,7 +23,7 @@
<i class="icon-pencil"></i><span class="user_posts_{main_posts.uid}">8</span>
</div>
</a>
<h3><p class="topic-title">{topic_name}</p>
<h3><p id="topic_title_{main_posts.pid}" class="topic-title">{topic_name}</p>
<div class="pull-right hidden-phone" style="margin-right: 10px;">
<button id="ids_{main_posts.pid}_{main_posts.uid}" class="btn edit {main_posts.display_moderator_tools}" type="button"><i class="icon-pencil"></i></button>
<button id="ids_{main_posts.pid}_{main_posts.uid}" class="btn delete {main_posts.display_moderator_tools}" type="button"><i class="icon-trash"></i></button>
@ -114,18 +114,27 @@
</div>
</div>
<input type="hidden" template-variable="expose_tools" value="{expose_tools}" />
<input type="hidden" template-variable="topic_id" value="{topic_id}" />
<input type="hidden" template-variable="locked" value="{locked}" />
<input type="hidden" template-variable="deleted" value="{deleted}" />
<input type="hidden" template-variable="pinned" value="{pinned}" />
<input type="hidden" template-variable="topic_name" value="{topic_name}" />
<script type="text/javascript">
(function() {
var expose_tools = '{expose_tools}',
tid = '{topic_id}',
var expose_tools = templates.get('expose_tools'),
tid = templates.get('topic_id'),
postListEl = document.getElementById('post-container'),
editBtns = document.querySelectorAll('#post-container .post-buttons .edit, #post-container .post-buttons .edit i'),
thread_state = {
locked: '{locked}',
deleted: '{deleted}',
pinned: '{pinned}'
};
locked: templates.get('locked'),
deleted: templates.get('deleted'),
pinned: templates.get('pinned')
},
topic_name = templates.get('topic_name');
function addCommasToNumbers() {
$('.formatted-number').each(function(index, element) {
@ -137,7 +146,7 @@
addCommasToNumbers();
var room = 'topic_' + '{topic_id}',
var room = 'topic_' + tid,
adminTools = document.getElementById('thread-tools');
app.enter_room(room);
@ -277,7 +286,13 @@
$('.post-container').delegate('.edit', 'click', function(e) {
var pid = ($(this).attr('id') || $(this.parentNode).attr('id')).split('_')[1];
app.open_post_window('edit', "{topic_id}", "{topic_name}", pid);
var main = $(this).parents('.main-post');
if(main.length > 0)
app.open_post_window('edit', tid, topic_name, pid);
else
app.open_post_window('edit', tid, "", pid);
});
$('.post-container').delegate('.delete', 'click', function(e) {
@ -490,6 +505,16 @@
socket.on('event:post_edited', function(data) {
var editedPostEl = document.getElementById('content_' + data.pid);
var editedPostTitle = $('#topic_title_'+data.pid);
if(editedPostTitle.length > 0) {
editedPostTitle.fadeOut(250, function() {
editedPostTitle.html(data.title);
editedPostTitle.fadeIn(250);
});
}
$(editedPostEl).fadeOut(250, function() {
this.innerHTML = data.content;
$(this).fadeIn(250);
@ -531,11 +556,11 @@
else div = '#' + div;
jQuery(div + ' .post_reply').click(function() {
if (thread_state.locked !== '1') app.open_post_window('reply', "{topic_id}", "{topic_name}");
if (thread_state.locked !== '1') app.open_post_window('reply', tid, topic_name);
});
jQuery(div + ' .quote').click(function() {
if (thread_state.locked !== '1') app.open_post_window('quote', "{topic_id}", "{topic_name}");
if (thread_state.locked !== '1') app.open_post_window('quote', tid, topic_name);
var pid = $(this).parents('li').attr('data-pid');

@ -1,8 +1,6 @@
<h1>Users</h1>
<div>
<!-- BEGIN users -->
<div class="users-box well">
<a href="/users/{users.username}">
<img src="{users.picture}" class="user-8080-picture"/>
@ -18,9 +16,7 @@
<span class='postcount'>{users.postcount}</span>
<i class='icon-pencil'></i>
</div>
</div>
<!-- END users -->
</div>

@ -180,7 +180,8 @@ var RDB = require('./redis.js'),
'pin-icon': pinned[i] === '1' ? 'icon-pushpin' : 'none',
'badgeclass' : (hasReadTopics[i] && current_user !=0) ? '' : 'badge-important',
'teaser_text': teaserInfo[i].text,
'teaser_username': teaserInfo[i].username
'teaser_username': teaserInfo[i].username,
'teaser_timestamp': utils.relativeTime(teaserInfo[i].timestamp)
});
}
}
@ -259,7 +260,17 @@ var RDB = require('./redis.js'),
});
}
Categories.getRecentReplies = function(cid, callback) {
RDB.zrange('categories:recent_posts:cid:' + cid, 0, -1, function(err, pids) {
if (pids.length == 0) {
callback(false);
return;
}
posts.getPostSummaryByPids(pids, function(posts) {
callback(posts);
});
});
}
Categories.getCategories = function(cids, callback, current_user) {
if (cids.length === 0) {

@ -47,15 +47,18 @@ marked.setOptions({
});
}
PostTools.edit = function(uid, pid, content) {
PostTools.edit = function(uid, pid, title, content) {
var success = function() {
RDB.set('pid:' + pid + ':content', content);
RDB.set('pid:' + pid + ':edited', new Date().getTime());
RDB.set('pid:' + pid + ':editor', uid);
posts.get_tid_by_pid(pid, function(tid) {
RDB.set('tid:' + tid + ':title', title);
io.sockets.in('topic_' + tid).emit('event:post_edited', {
pid: pid,
title: title,
content: marked(content || '')
});
});

@ -23,74 +23,113 @@ marked.setOptions({
}
topics.markAsRead(tid, current_user);
Posts.getPostsByPids(pids, current_user, function(posts) {
callback(posts);
})
var content = [], uid = [], timestamp = [], pid = [], post_rep = [], editor = [], editTime = [], deleted = [];
for (var i=0, ii=pids.length; i<ii; i++) {
content.push('pid:' + pids[i] + ':content');
uid.push('pid:' + pids[i] + ':uid');
timestamp.push('pid:' + pids[i] + ':timestamp');
post_rep.push('pid:' + pids[i] + ':rep');
editor.push('pid:' + pids[i] + ':editor');
editTime.push('pid:' + pids[i] + ':edited');
deleted.push('pid:' + pids[i] + ':deleted');
pid.push(pids[i]);
}
});
}
// todo, getPostsByPids has duplicated stuff, have that call this fn - after userinfo calls are pulled out.
Posts.getPostSummaryByPids = function(pids, callback) {
var content = [], uid = [], timestamp = [];
for (var i=0, ii=pids.length; i<ii; i++) {
content.push('pid:' + pids[i] + ':content');
uid.push('pid:' + pids[i] + ':uid');
timestamp.push('pid:' + pids[i] + ':timestamp');
}
function getFavouritesData(next) {
favourites.getFavouritesByPostIDs(pids, current_user, function(fav_data) {
next(null, fav_data);
}); // to be moved
}
RDB.multi()
.mget(content)
.mget(uid)
.mget(timestamp)
.exec(function(err, replies) {
post_data = {
pids: pids,
content: replies[0],
uid: replies[1],
timestamp: replies[2]
}
// below, to be deprecated
user.getMultipleUserFields(post_data.uid, ['username','reputation','picture'], function(user_details) {
callback({
users: user_details,
posts: post_data
});
});
// above, to be deprecated
});
};
Posts.getPostsByPids = function(pids, current_user, callback) {
var content = [], uid = [], timestamp = [], post_rep = [], editor = [], editTime = [], deleted = [];
function getPostData(next) {
RDB.multi()
.mget(content)
.mget(uid)
.mget(timestamp)
.mget(post_rep)
.mget(editor)
.mget(editTime)
.mget(deleted)
.exec(function(err, replies) {
post_data = {
pid: pids,
content: replies[0],
uid: replies[1],
timestamp: replies[2],
reputation: replies[3],
editor: replies[4],
editTime: replies[5],
deleted: replies[6]
};
// below, to be deprecated
// Add any editors to the user_data object
for(var x = 0, numPosts = post_data.editor.length; x < numPosts; x++) {
if (post_data.editor[x] !== null && post_data.uid.indexOf(post_data.editor[x]) === -1) {
post_data.uid.push(post_data.editor[x]);
}
for (var i=0, ii=pids.length; i<ii; i++) {
content.push('pid:' + pids[i] + ':content');
uid.push('pid:' + pids[i] + ':uid');
timestamp.push('pid:' + pids[i] + ':timestamp');
post_rep.push('pid:' + pids[i] + ':rep');
editor.push('pid:' + pids[i] + ':editor');
editTime.push('pid:' + pids[i] + ':edited');
deleted.push('pid:' + pids[i] + ':deleted');
}
function getFavouritesData(next) {
favourites.getFavouritesByPostIDs(pids, current_user, function(fav_data) {
next(null, fav_data);
}); // to be moved
}
function getPostData(next) {
RDB.multi()
.mget(content)
.mget(uid)
.mget(timestamp)
.mget(post_rep)
.mget(editor)
.mget(editTime)
.mget(deleted)
.exec(function(err, replies) {
post_data = {
pid: pids,
content: replies[0],
uid: replies[1],
timestamp: replies[2],
reputation: replies[3],
editor: replies[4],
editTime: replies[5],
deleted: replies[6]
};
// below, to be deprecated
// Add any editors to the user_data object
for(var x = 0, numPosts = post_data.editor.length; x < numPosts; x++) {
if (post_data.editor[x] !== null && post_data.uid.indexOf(post_data.editor[x]) === -1) {
post_data.uid.push(post_data.editor[x]);
}
}
user.getMultipleUserFields(post_data.uid, ['username','reputation','picture', 'signature'], function(user_details) {
next(null, {
users: user_details,
posts: post_data
});
user.getMultipleUserFields(post_data.uid, ['username','reputation','picture', 'signature'], function(user_details) {
next(null, {
users: user_details,
posts: post_data
});
// above, to be deprecated
});
}
async.parallel([getFavouritesData, getPostData], function(err, results) {
callback({
'voteData' : results[0], // to be moved
'userData' : results[1].users, // to be moved
'postData' : results[1].posts
// above, to be deprecated
});
});
}
async.parallel([getFavouritesData, getPostData], function(err, results) {
callback({
'voteData' : results[0], // to be moved
'userData' : results[1].users, // to be moved
'postData' : results[1].posts
});
});
}
@ -127,60 +166,73 @@ marked.setOptions({
return;
}
Posts.create(uid, tid, content, function(pid) {
if (pid > 0) {
RDB.rpush('tid:' + tid + ':posts', pid);
RDB.del('tid:' + tid + ':read_by_uid'); // let everybody know there is an unread post
Posts.get_cid_by_pid(pid, function(cid) {
RDB.del('cid:' + cid + ':read_by_uid');
});
RDB.zadd('topics:recent_posts:tid:' + tid, (new Date()).getTime(), pid);
// Re-add the poster, so he/she does not get an "unread" flag on this topic
topics.markAsRead(tid, uid);
// this will duplicate once we enter the thread, which is where we should be going
user.getUserField(uid, 'lastposttime', function(lastposttime) {
if(new Date().getTime() - lastposttime < config.post_delay) {
socket.emit('event:alert', {
title: 'Reply Successful',
message: 'You have successfully replied. Click here to view your reply.',
type: 'notify',
title: 'Too many posts!',
message: 'You can only post every '+ (config.post_delay / 1000) + ' seconds.',
type: 'error',
timeout: 2000
});
return;
}
user.getUserFields(uid, ['username','reputation','picture','signature'], function(data) {
var timestamp = new Date().getTime();
io.sockets.in('topic_' + tid).emit('event:new_post', {
'posts' : [
{
'pid' : pid,
'content' : marked(content || ''),
'uid' : uid,
'username' : data.username || 'anonymous',
'user_rep' : data.reputation || 0,
'post_rep' : 0,
'gravatar' : data.picture,
'signature' : marked(data.signature || ''),
'timestamp' : timestamp,
'relativeTime': utils.relativeTime(timestamp),
'fav_star_class' :'icon-star-empty',
'edited-class': 'none',
'editor': '',
}
]
Posts.create(uid, tid, content, function(pid) {
if (pid > 0) {
RDB.rpush('tid:' + tid + ':posts', pid);
RDB.del('tid:' + tid + ':read_by_uid'); // let everybody know there is an unread post
Posts.get_cid_by_pid(pid, function(cid) {
RDB.del('cid:' + cid + ':read_by_uid');
RDB.zadd('categories:recent_posts:cid:' + cid, (new Date()).getTime(), pid);
});
});
} else {
socket.emit('event:alert', {
title: 'Reply Unsuccessful',
message: 'Your reply could not be posted at this time. Please try again later.',
type: 'notify',
timeout: 2000
});
}
// Re-add the poster, so he/she does not get an "unread" flag on this topic
topics.markAsRead(tid, uid);
// this will duplicate once we enter the thread, which is where we should be going
socket.emit('event:alert', {
title: 'Reply Successful',
message: 'You have successfully replied. Click here to view your reply.',
type: 'notify',
timeout: 2000
});
user.getUserFields(uid, ['username','reputation','picture','signature'], function(data) {
var timestamp = new Date().getTime();
io.sockets.in('topic_' + tid).emit('event:new_post', {
'posts' : [
{
'pid' : pid,
'content' : marked(content || ''),
'uid' : uid,
'username' : data.username || 'anonymous',
'user_rep' : data.reputation || 0,
'post_rep' : 0,
'gravatar' : data.picture,
'signature' : marked(data.signature || ''),
'timestamp' : timestamp,
'relativeTime': utils.relativeTime(timestamp),
'fav_star_class' :'icon-star-empty',
'edited-class': 'none',
'editor': '',
}
]
});
});
} else {
socket.emit('event:alert', {
title: 'Reply Unsuccessful',
message: 'Your reply could not be posted at this time. Please try again later.',
type: 'notify',
timeout: 2000
});
}
});
});
};
@ -193,11 +245,12 @@ marked.setOptions({
if (!locked || locked === '0') {
RDB.incr('global:next_post_id', function(err, pid) {
RDB.handle(err);
var timestamp = new Date().getTime();
// Posts Info
RDB.set('pid:' + pid + ':content', content);
RDB.set('pid:' + pid + ':uid', uid);
RDB.set('pid:' + pid + ':timestamp', new Date().getTime());
RDB.set('pid:' + pid + ':timestamp', timestamp);
RDB.set('pid:' + pid + ':rep', 0);
RDB.set('pid:' + pid + ':tid', tid);
@ -225,6 +278,7 @@ marked.setOptions({
RDB.lpush('uid:' + uid + ':posts', pid);
user.incrementUserFieldBy(uid, 'postcount', 1);
user.setUserField(uid, 'lastposttime', timestamp);
if (callback)
callback(pid);

@ -37,4 +37,29 @@
});
};
/*
* A lot of redis calls come back like this:
* [key, value, key, value, key, value]
* this is a simple utility fn to turn this into an object.
*/
RedisDB.exports.objectify = function(arr) {
var obj = {};
for (var i = 0; i < arr.length; i += 2) {
obj[arr[i]] = arr[i+1];
}
return obj;
};
/*
* Similar to .objectify, this utility function splits the data array into two arrays
*/
RedisDB.exports.splitify = function(arr) {
var arr1 = [], arr2 = [];
for (var i = 0; i < arr.length; i += 2) {
arr1.push(arr[i]);
arr2.push(arr[i+1]);
}
return [arr1,arr2];
};
}(module));

@ -0,0 +1,63 @@
(function(Schema) {
Schema.global = function() {
return {
/* strings */
next_topic_id: 'next_topic_id'
}
};
Schema.topics = function(tid) {
return {
/* strings */
title: 'tid:' + tid + ':title',
locked: 'tid:' + tid + ':locked',
category_name: 'tid:' + tid + ':category_name',
category_slug: 'tid:' + tid + ':category_slug',
deleted: 'tid:' + tid + ':deleted',
pinned: 'tid:' + tid + ':pinned',
uid: 'tid:' + tid + ':uid',
timestamp: 'tid:' + tid + ':timestamp',
slug: 'tid:' + tid + ':slug',
postcount: 'tid:' + tid + ':postcount',
cid: 'tid:' + tid + ':cid',
/* sets */
tid: 'topics:tid',
read_by_uid: 'tid:' + tid + ':read_by_uid',
/* sorted sets */
recent: 'topics:recent',
/* lists */
posts: 'tid:' + tid + ':posts',
queued_tids: 'topics:queued:tid',
slug: function(slug) {
return {
tid: 'topic:slug:' + slug + ':tid'
}
}
}
};
Schema.categories = function(cid) {
};
Schema.users = function(uid) {
};
Schema.posts = function(pid) {
};
}(module.exports));

@ -1,4 +1,5 @@
var RDB = require('./redis.js'),
var RDB = require('./redis.js')
schema = require('./schema.js'),
posts = require('./posts.js'),
utils = require('./../public/src/utils.js'),
user = require('./user.js'),
@ -17,12 +18,12 @@ marked.setOptions({
Topics.getTopicById = function(tid, current_user, callback) {
function getTopicData(next) {
RDB.multi()
.get('tid:' + tid + ':title')
.get('tid:' + tid + ':locked')
.get('tid:' + tid + ':category_name')
.get('tid:' + tid + ':category_slug')
.get('tid:' + tid + ':deleted')
.get('tid:' + tid + ':pinned')
.get(schema.topics(tid).title)
.get(schema.topics(tid).locked)
.get(schema.topics(tid).category_name)
.get(schema.topics(tid).category_slug)
.get(schema.topics(tid).deleted)
.get(schema.topics(tid).pinned)
.exec(function(err, replies) {
next(null, {
topic_name: replies[0],
@ -118,14 +119,14 @@ marked.setOptions({
function get_topic_data(next) {
RDB.mget([
'tid:' + tid + ':title',
'tid:' + tid + ':uid',
'tid:' + tid + ':timestamp',
'tid:' + tid + ':slug',
'tid:' + tid + ':postcount',
'tid:' + tid + ':locked',
'tid:' + tid + ':pinned',
'tid:' + tid + ':deleted'
schema.topics(tid).title,
schema.topics(tid).uid,
schema.topics(tid).timestamp,
schema.topics(tid).slug,
schema.topics(tid).postcount,
schema.topics(tid).locked,
schema.topics(tid).pinned,
schema.topics(tid).deleted
], function(err, topic) {
if (err) {
throw new Error(err);
@ -152,7 +153,7 @@ marked.setOptions({
function get_read_status(next) {
// posts.create calls this function - should be an option to skip this because its always true
if (uid && parseInt(uid) > 0) {
RDB.sismember('tid:' + tid + ':read_by_uid', uid, function(err, read) {
RDB.sismember(schema.topics(tid).read_by_uid, uid, function(err, read) {
topicData.badgeclass = read ? '' : 'badge-important';
next();
@ -181,7 +182,7 @@ marked.setOptions({
}
Topics.get_cid_by_tid = function(tid, callback) {
RDB.get('tid:' + tid + ':cid', function(err, cid) {
RDB.get(schema.topics(tid).cid, function(err, cid) {
if (cid && parseInt(cid) > 0) {
callback(cid);
} else {
@ -192,7 +193,7 @@ marked.setOptions({
Topics.markAsRead = function(tid, uid) {
// there is an issue with this fn. if you read a topic that is previously read you will mark the category as read anyways - there is no check
RDB.sadd('tid:' + tid + ':read_by_uid', uid);
RDB.sadd(schema.topics(tid).read_by_uid, uid);
Topics.get_cid_by_tid(tid, function(cid) {
RDB.sadd('cid:' + cid + ':read_by_uid', uid);
});
@ -202,7 +203,7 @@ marked.setOptions({
var batch = RDB.multi();
for (var i=0, ii=tids.length; i<ii; i++) {
batch.sismember('tid:' + tids[i] + ':read_by_uid', uid);
batch.sismember(schema.topics(tids[i]).read_by_uid, uid);
}
batch.exec(function(err, hasRead) {
@ -230,8 +231,9 @@ marked.setOptions({
}
}
// start: probably should be moved into posts
Topics.get_latest_undeleted_pid = function(tid, callback) {
RDB.lrange('tid:' + tid + ':posts', 0, -1, function(err, pids) {
RDB.lrange(schema.topics(tid).posts, 0, -1, function(err, pids) {
var pidKeys = [],
numPids = pids.length;
@ -257,27 +259,25 @@ marked.setOptions({
if (pid !== null) {
RDB.mget([
'pid:' + pid + ':content',
'pid:' + pid + ':uid'
'pid:' + pid + ':uid',
'pid:' + pid + ':timestamp'
], function(err, content) {
user.getUserField(content[1], 'username', function(username) {
var stripped = content[0];
var stripped = content[0],
timestamp = content[2];
if(content[0])
stripped = utils.strip_tags(marked(content[0]));
callback({
"text": stripped,
"username": username
"username": username,
"timestamp" : timestamp
});
});
});
}
});
}
Topics.getRecentReplies = function(tid, callback) {
RDB.zrange('topics:recent_posts:tid:' + tid, 0, -1, 'WITHSCORES', function(err, replies) {
callback(replies);
});
}
// end: probably should be moved into posts
Topics.post = function(socket, uid, title, content, category_id) {
if (!category_id) throw new Error('Attempted to post without a category_id');
@ -295,68 +295,81 @@ marked.setOptions({
return; // for now, until anon code is written.
}
RDB.incr('global:next_topic_id', function(err, tid) {
RDB.handle(err);
// Global Topics
if (uid == null) uid = 0;
if (uid !== null) {
RDB.sadd('topics:tid', tid);
} else {
// need to add some unique key sent by client so we can update this with the real uid later
RDB.lpush('topics:queued:tid', tid);
user.getUserField(uid, 'lastposttime', function(lastposttime) {
if(new Date().getTime() - lastposttime < config.post_delay) {
socket.emit('event:alert', {
title: 'Too many posts!',
message: 'You can only post every '+ (config.post_delay / 1000) + ' seconds.',
type: 'error',
timeout: 2000
});
return;
}
var slug = tid + '/' + utils.slugify(title);
RDB.incr(schema.global().next_topic_id, function(err, tid) {
RDB.handle(err);
// Topic Info
RDB.set('tid:' + tid + ':title', title);
RDB.set('tid:' + tid + ':uid', uid);
RDB.set('tid:' + tid + ':slug', slug);
RDB.set('tid:' + tid + ':timestamp', new Date().getTime());
// Global Topics
if (uid == null) uid = 0;
if (uid !== null) {
RDB.sadd(schema.topics().tid, tid);
} else {
// need to add some unique key sent by client so we can update this with the real uid later
RDB.lpush(schema.topics().queued_tids, tid);
}
var slug = tid + '/' + utils.slugify(title);
// Topic Info
RDB.set(schema.topic(tid).title, title);
RDB.set(schema.topic(tid).uid, uid);
RDB.set(schema.topic(tid).slug, slug);
RDB.set(schema.topic(tid).timestamp, new Date().getTime());
RDB.set('topic:slug:' + slug + ':tid', tid);
RDB.set(schema.topic().slug(slug).tid, tid);
// Posts
posts.create(uid, tid, content, function(pid) {
if (pid > 0) {
RDB.lpush('tid:' + tid + ':posts', pid);
// Posts
posts.create(uid, tid, content, function(pid) {
if (pid > 0) {
RDB.lpush(schema.topic(tid).posts, pid);
// Notify any users looking at the category that a new topic has arrived
Topics.get_topic(tid, uid, function(topicData) {
io.sockets.in('category_' + category_id).emit('event:new_topic', topicData);
});
}
});
// Notify any users looking at the category that a new topic has arrived
Topics.get_topic(tid, uid, function(topicData) {
io.sockets.in('category_' + category_id).emit('event:new_topic', topicData);
});
}
});
Topics.markAsRead(tid, uid);
Topics.markAsRead(tid, uid);
// User Details - move this out later
RDB.lpush('uid:' + uid + ':topics', tid);
// User Details - move this out later
RDB.lpush('uid:' + uid + ':topics', tid);
socket.emit('event:alert', {
title: 'Thank you for posting',
message: 'You have successfully posted. Click here to view your post.',
type: 'notify',
timeout: 2000
});
socket.emit('event:alert', {
title: 'Thank you for posting',
message: 'You have successfully posted. Click here to view your post.',
type: 'notify',
timeout: 2000
});
// let everyone know that there is an unread topic in this category
RDB.del('cid:' + category_id + ':read_by_uid');
// let everyone know that there is an unread topic in this category
RDB.del('cid:' + category_id + ':read_by_uid');
RDB.zadd('topics:recent', (new Date()).getTime(), tid);
//RDB.zadd('topics:active', tid);
RDB.zadd(schema.topics().recent, (new Date()).getTime(), tid);
//RDB.zadd('topics:active', tid);
// in future it may be possible to add topics to several categories, so leaving the door open here.
RDB.sadd('categories:' + category_id + ':tid', tid);
RDB.set('tid:' + tid + ':cid', category_id);
categories.getCategories([category_id], function(data) {
RDB.set('tid:' + tid + ':category_name', data.categories[0].name);
RDB.set('tid:' + tid + ':category_slug', data.categories[0].slug);
});
// in future it may be possible to add topics to several categories, so leaving the door open here.
RDB.sadd('categories:' + category_id + ':tid', tid);
RDB.set(schema.topics(tid).cid, category_id);
categories.getCategories([category_id], function(data) {
RDB.set(schema.topics(tid).category_name, data.categories[0].name);
RDB.set(schema.topics(tid).category_slug, data.categories[0].slug);
});
RDB.incr('cid:' + category_id + ':topiccount');
RDB.incr('cid:' + category_id + ':topiccount');
});
});
};

@ -300,7 +300,8 @@ var config = require('../config.js'),
'gravatarpicture' : gravatar,
'uploadedpicture': '',
'reputation': 0,
'postcount': 0
'postcount': 0,
'lastposttime': 0
});
RDB.set('username:' + username + ':uid', uid);

@ -26,7 +26,7 @@ var express = require('express'),
// Middlewares
app.use(express.favicon()); // 2 args: string path and object options (i.e. expire time etc)
app.use(require('less-middleware')({ src: path.join(__dirname, '../', '/public') }));
app.use(require('less-middleware')({ src: path.join(__dirname, '../', 'public') }));
app.use(express.static(path.join(__dirname, '../', 'public')));
app.use(express.bodyParser()); // Puts POST vars in request.body
app.use(express.cookieParser()); // If you want to parse cookies (res.cookies)

@ -238,7 +238,7 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
});
socket.on('api:posts.edit', function(data) {
postTools.edit(uid, data.pid, data.content);
postTools.edit(uid, data.pid, data.title, data.content);
});
socket.on('api:posts.delete', function(data) {
@ -265,10 +265,9 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
notifications.mark_read(nid, uid);
});
socket.on('api:topics.getRecentReplies', function(tid) {
console.log('test');
topics.getRecentReplies(tid, function(replies) {
socket.emit('api:topics.getRecentReplies', replies);
socket.on('api:categories.getRecentReplies', function(tid) {
categories.getRecentReplies(tid, function(replies) {
socket.emit('api:categories.getRecentReplies', replies);
});
});

Loading…
Cancel
Save