Compare commits

...

95 Commits

Author SHA1 Message Date
Julian Lam
7955a5d53a 0.1.1 2013-11-25 15:08:49 -05:00
psychobunny
c261babf17 minify client scripts only after plugin system is activated 2013-11-25 12:35:54 -05:00
psychobunny
b90eef6d19 use icon-search instead of icon-circle-blank on /users/search 2013-11-25 01:21:26 -05:00
psychobunny
5c597ca218 expand regex for templates, allowing the syntax to be less strict re: whitespace 2013-11-25 01:14:31 -05:00
psychobunny
3dbcf8112d fixes navigation - back button 2013-11-25 00:59:21 -05:00
psychobunny
5357ad61db plugins - filter:scripts.get hook lets a plugin add client-side JS to the header and queue up for minification on production 2013-11-25 00:53:27 -05:00
Baris Soner Usakli
ff50917c29 show no replies in recent and unread 2013-11-24 22:48:58 -05:00
Baris Soner Usakli
48835d8c44 used ELSE in template 2013-11-24 22:29:36 -05:00
Baris Soner Usakli
e9c66bb35a removed console.log 2013-11-24 22:12:36 -05:00
Baris Soner Usakli
23eb7824ac closes #513 2013-11-24 22:08:37 -05:00
Baris Soner Usakli
494b9d23ac Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-11-24 13:58:14 -05:00
Baris Soner Usakli
64ae9ac033 cant delete or restore posts twice, post count goes up or down when posts are deleted or restored in a topic, fixed the post insertion when there is only 1 post in topic 2013-11-24 13:58:06 -05:00
Julian Lam
a72fc69997 added link to RSS for /recent, fixed RSS generation error for /recent 2013-11-24 11:15:40 -05:00
Baris Soner Usakli
a16f93cbd5 loading indicator starts off hidden 2013-11-23 18:05:19 -05:00
Baris Soner Usakli
81e5cf0cf3 fixed posts not appearing immediately 2013-11-23 17:56:03 -05:00
Baris Soner Usakli
01102d5982 added responsive class to images in users recent posts 2013-11-23 17:18:26 -05:00
Baris Soner Usakli
2174aec0e1 closes #542 and other refactors on client side 2013-11-23 17:07:31 -05:00
psychobunny
46bad118de if a thread is unreplied, don't show the OP as the last person to reply 2013-11-23 15:28:00 -05:00
psychobunny
2d7228fa40 added if / else logic to templates 2013-11-23 15:15:33 -05:00
psychobunny
a1839d90fd prevent admin group from being deleted, closes #530 2013-11-23 14:53:06 -05:00
psychobunny
0cc136c3f6 simplifying conditional logic in templates + fixed it so that it takes namespace into account 2013-11-23 14:53:05 -05:00
Julian Lam
cd1e26418d making the loading indicator less obtrusive (and smaller, to boot) 2013-11-22 21:30:34 -05:00
Baris Soner Usakli
dab4f07258 fixed indents 2013-11-22 17:39:53 -05:00
Baris Usakli
501dc56fd3 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-11-22 16:26:33 -05:00
Baris Usakli
253271127d infinite loader will insert posts in correct order 2013-11-22 16:26:19 -05:00
Julian Lam
f2da892b38 removed extra console log 2013-11-22 16:21:26 -05:00
Julian Lam
6dd72f480c Merge pull request #538 from draco2003/add_fav_context
add breadcrumb like context to favorites
2013-11-22 13:15:10 -08:00
Julian Lam
3caf8b4a67 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-11-22 15:48:14 -05:00
Julian Lam
39f2efbef8 closed #540 2013-11-22 15:48:03 -05:00
psychobunny
288341945d missing login menu in visible-sm range 2013-11-22 15:37:58 -05:00
psychobunny
d02bd72764 closes #535 2013-11-22 15:32:15 -05:00
Baris Usakli
7d3adb9275 removed unused stuff from getLatestTopics and recent.tpl 2013-11-22 15:01:00 -05:00
Baris Usakli
156950ac2f cleaned up app.createNewPosts 2013-11-22 14:51:45 -05:00
Baris Usakli
83f18c1915 closes #526 2013-11-22 14:08:02 -05:00
Baris Usakli
332730575f closes #529 2013-11-22 12:26:21 -05:00
Julian Lam
08ef67e824 closed #536 2013-11-22 11:42:42 -05:00
Dan Rowe
7e71fb218c add breadcrumb like context to favorites 2013-11-22 08:57:54 -05:00
Julian Lam
a7216caa3b closed #518 2013-11-21 22:15:04 -05:00
Julian Lam
87309601ce closed #533 2013-11-21 22:09:40 -05:00
Julian Lam
53db9db50f Merge pull request #527 from draco2003/patch-1
small wording change
2013-11-21 17:33:36 -08:00
Dan Rowe
23628668b7 small wording change 2013-11-21 20:29:23 -05:00
Baris Soner Usakli
6ac685b194 possible fix for js error 2013-11-21 20:11:06 -05:00
Baris Soner Usakli
db8c43ca97 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-11-21 18:40:30 -05:00
Baris Soner Usakli
bff0c8fdaf increased active users count to 16 2013-11-21 18:40:16 -05:00
Barış Soner Uşaklı
6f2b809385 Update README.md 2013-11-21 18:34:50 -05:00
Baris Soner Usakli
455479bd54 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-11-21 18:28:51 -05:00
Baris Soner Usakli
03b34a449d turned div into li, moved the a tags into the li elements 2013-11-21 18:28:31 -05:00
Julian Lam
08e51c8942 closed #502 2013-11-21 17:41:27 -05:00
Julian Lam
4aef5bfb72 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-11-21 17:36:00 -05:00
psychobunny
34c74770ce app.js minor cleanup 2013-11-21 17:02:40 -05:00
psychobunny
da8d198676 added some user friendly warnings to chat 2013-11-21 17:00:20 -05:00
Julian Lam
33868804fd fixed #517 2013-11-21 16:55:31 -05:00
psychobunny
22a3794c51 closes #525 2013-11-21 16:47:32 -05:00
psychobunny
1058d54c52 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-11-21 16:07:39 -05:00
psychobunny
90ce539683 fixed language file not parsing in footer 2013-11-21 16:07:32 -05:00
Baris Usakli
99c2fbd947 fixed anon count in browsing text if there are no logged in users 2013-11-21 15:56:28 -05:00
Baris Usakli
866d813218 fixed topic posting bug, if there was only 1 topic in a category the next posted topic in that category wasnt showing up until a full page reload 2013-11-21 15:33:41 -05:00
Baris Usakli
f1df8c2479 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-11-21 15:20:12 -05:00
Baris Usakli
6f1523c279 fixed bottom reply box for small devices 2013-11-21 15:20:03 -05:00
Julian Lam
163cdaf70c Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-11-21 15:05:56 -05:00
Julian Lam
a9ce8393e4 added footer.build and page.load hooks 2013-11-21 15:05:45 -05:00
Baris Usakli
f04e30c4d4 more template changes to topic 2013-11-21 14:55:38 -05:00
Baris Usakli
9d0f8b4543 template changes 2013-11-21 14:33:55 -05:00
Baris Usakli
d631a4b2e5 topic.tpl changes, moved users to the bottom of main post, added reply thread tools buttons under main post, added posts and view count 2013-11-21 13:53:19 -05:00
Julian Lam
2cf55dcf9f added action:page.load hook 2013-11-21 12:28:10 -05:00
Baris Soner Usakli
9fbb139e67 fix post delete state after more posts are loaded 2013-11-20 12:22:59 -05:00
Baris Usakli
11e3b0da7d added spacing between share and edit buttons 2013-11-19 15:03:58 -05:00
Baris Usakli
0b922d3f60 possible fix for #516 2013-11-19 13:04:12 -05:00
Baris Usakli
7e4faa3270 closes #514 2013-11-19 12:38:13 -05:00
Julian Lam
635fba1e45 upping cerlean minver 2013-11-18 20:25:43 -05:00
Baris Usakli
7c950cc350 require cleanup in user.js, removed user.latest not used anymore 2013-11-18 16:22:43 -05:00
Baris Usakli
cc0fe66e3e minor tweak to notif filter :) 2013-11-18 15:56:12 -05:00
Baris Usakli
b2d6ce59cf Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-11-18 15:44:46 -05:00
Baris Usakli
586a181e0a closes #507 2013-11-18 15:44:32 -05:00
Julian Lam
33150943df Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-11-18 15:09:56 -05:00
Julian Lam
28dab60232 resolving notifs issue, I hope 2013-11-18 15:09:34 -05:00
Barış Soner Uşaklı
71ef76b108 Merge pull request #511 from damianb/patch-3
cleanup webserver.js requires
2013-11-18 08:44:23 -08:00
Julian Lam
64008ef5d8 Merge pull request #509 from damianb/patch-2
/bin/bash to /bin/sh
2013-11-18 08:36:37 -08:00
Barış Soner Uşaklı
1859154370 Merge pull request #506 from deniswolf/cleanup_specs
Cleanup specs in test/topic.js
2013-11-18 08:20:51 -08:00
Julian Lam
fa4067e885 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-11-18 08:19:18 -05:00
Julian Lam
c8c355b319 added komodoproject file extensions to gitignore 2013-11-18 08:19:04 -05:00
Damian Bushong
51355a53d9 cleanup webserver.js requires
the .js in a require is implicit and unnecessary;
sorted the requires out so that builtins are first, then npm deps, then locally provided libs.
minor changes to some strings, just using single-quotes to match the style through the rest of the file.
2013-11-18 02:39:08 -06:00
Damian Bushong
1f3f672d3f /bin/bash to /bin/sh
No bashisms present? Don't be explicit about needing bash, then.
2013-11-18 02:27:48 -06:00
Denis Wolf
7c3fa30c13 tests: topic.js: fixed description to mirror code, refactored asserts since mocha's 'done' can process errors in callback 2013-11-18 00:00:22 +02:00
Denis Wolf
cbbb7a7c8e tests: topic.js: extract mock data init in getters 2013-11-17 23:50:19 +02:00
Denis Wolf
6893bd8b04 tests: topic.js: extract mock data init in .post 2013-11-17 23:41:38 +02:00
Denis Wolf
22eabf6620 tests: topic.js: naming flow fix for .post 2013-11-17 23:35:18 +02:00
Baris Soner Usakli
a827888ee3 closes #503 2013-11-17 12:23:19 -05:00
Baris Usakli
54d94f5988 added topic tests 2013-11-15 16:16:50 -05:00
Baris Usakli
7c1b6d6ad2 lots of refactor for error handling 2013-11-15 14:57:50 -05:00
Baris Usakli
8c4f776122 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-11-15 13:39:14 -05:00
Baris Usakli
84fa704b25 refactor abit adding error checking to missing parts 2013-11-15 13:39:09 -05:00
Julian Lam
535379d9d7 added password confirmation to automated setup 2013-11-14 18:45:17 -05:00
Julian Lam
d2927e2be2 Merge pull request #500 from jgable/passwordConfirm
Add admin password confirmation on setup
2013-11-14 11:27:23 -08:00
Jacob Gable
bd04b2f921 Add admin password confirmation on setup
Closes #419

- Introduce a password:confirm question
- Isolate password questions to they can be re-asked
- Verify matching password, re-ask if not
2013-11-14 12:52:00 -06:00
88 changed files with 2328 additions and 1084 deletions

5
.gitignore vendored
View File

@@ -11,4 +11,7 @@ public/css/*.css
*.swp
Vagrantfile
.vagrant
provision.sh
provision.sh
*.komodoproject
feeds/recent.rss

View File

@@ -10,7 +10,7 @@
![NodeBB Main Category Listing](http://i.imgur.com/zffCFoh.png)
![NodeBB Topic Page](http://i.imgur.com/tcHW08M.png)
![NodeBB Topic Page](http://i.imgur.com/JihdcUa.png)
## How can I follow along/contribute?

5
app.js
View File

@@ -64,6 +64,7 @@
nconf.set('url', nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '') + nconf.get('relative_path') + '/');
nconf.set('upload_url', nconf.get('url') + 'uploads/');
nconf.set('base_dir', __dirname);
winston.info('Initializing NodeBB v' + pkg.version + ', on port ' + nconf.get('port') + ', using Redis store at ' + nconf.get('redis:host') + ':' + nconf.get('redis:port') + '.');
winston.info('NodeBB instance bound to: ' + ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? 'Any address (0.0.0.0)' : nconf.get('bind_address')));
@@ -112,7 +113,9 @@
], customTemplates);
templates.ready(webserver.init);
plugins.ready(function() {
templates.ready(webserver.init);
});
Notifications.init();
} else {

4
nodebb
View File

@@ -1,6 +1,6 @@
#!/bin/bash
#!/bin/sh
clear
echo "Launching NodeBB in \"development\" mode."
echo "To run the production build of NodeBB, please use \"forever\"."
echo "More Information: https://github.com/designcreateplay/NodeBB/wiki/How-to-run-NodeBB"
NODE_ENV=development supervisor --extensions 'node|js|tpl' -- app $1
NODE_ENV=development supervisor --extensions 'node|js|tpl' -- app $1

View File

@@ -2,7 +2,7 @@
"name": "nodebb",
"license": "GPLv3 or later",
"description": "NodeBB Forum",
"version": "0.1.0",
"version": "0.1.1",
"homepage": "http://www.nodebb.org",
"repository": {
"type": "git",
@@ -43,7 +43,7 @@
"nodebb-plugin-mentions": "~0.1.14",
"nodebb-plugin-markdown": "~0.1.8",
"nodebb-theme-vanilla": "designcreateplay/nodebb-theme-vanilla",
"nodebb-theme-cerulean": "0.0.6",
"nodebb-theme-cerulean": "0.0.7",
"cron": "~1.0.1"
},
"optionalDependencies": {

View File

@@ -1,5 +1,6 @@
var ajaxify = {};
"use strict";
var ajaxify = {};
(function ($) {
/*global app, templates, utils*/
@@ -23,8 +24,9 @@ var ajaxify = {};
window.onpopstate = function (event) {
// "quiet": If set to true, will not call pushState
if (event !== null && event.state && event.state.url !== undefined)
if (event !== null && event.state && event.state.url !== undefined) {
ajaxify.go(event.state.url, null, null, true);
}
};
var pagination;
@@ -32,10 +34,13 @@ var ajaxify = {};
ajaxify.go = function (url, callback, template, quiet) {
// start: the following should be set like so: ajaxify.onchange(function(){}); where the code actually belongs
$(window).off('scroll');
app.enter_room('global');
app.enterRoom('global');
pagination = pagination || document.getElementById('pagination');
if (pagination) pagination.parentNode.style.display = 'none';
if (pagination) {
pagination.parentNode.style.display = 'none';
}
window.onscroll = null;
// end
@@ -62,13 +67,26 @@ var ajaxify = {};
}
if (templates.is_available(tpl_url) && !templates.force_refresh(tpl_url)) {
if (quiet !== true) {
//if (quiet !== true) {
if (window.history && window.history.pushState) {
window.history.pushState({
"url": url
}, url, RELATIVE_PATH + "/" + url);
url: url
}, url, RELATIVE_PATH + '/' + url);
$.ajax(RELATIVE_PATH + '/plugins/fireHook', {
type: 'PUT',
data: {
_csrf: $('#csrf_token').val(),
hook: 'page.load',
args: {
template: tpl_url,
url: url,
uid: app.uid
}
}
});
}
}
//}
translator.load(tpl_url);
@@ -78,20 +96,27 @@ var ajaxify = {};
templates.load_template(function () {
exec_body_scripts(content);
require(['forum/' + tpl_url], function(script) {
if (script && script.init) script.init();
if (script && script.init) {
script.init();
}
});
if (callback) {
callback();
}
app.process_page();
app.processPage();
jQuery('#content, #footer').stop(true, true).fadeIn(200, function () {
if (window.location.hash)
if (window.location.hash) {
hash = window.location.hash;
if (hash)
app.scrollToPost(hash.substr(1));
}
if (hash) {
require(['forum/topic'], function(topic) {
topic.scrollToPost(hash.substr(1))
});
}
});
utils.refreshTitle(url);
@@ -105,23 +130,27 @@ var ajaxify = {};
};
$('document').ready(function () {
if (!window.history || !window.history.pushState) return; // no ajaxification for old browsers
if (!window.history || !window.history.pushState) {
return; // no ajaxification for old browsers
}
content = content || document.getElementById('content');
// Enhancing all anchors to ajaxify...
$(document.body).on('click', 'a', function (e) {
function hrefEmpty(href) {
return href == 'javascript:;' || href == window.location.href + "#" || href.slice(-1) === "#";
return href === 'javascript:;' || href === window.location.href + "#" || href.slice(-1) === "#";
}
if (hrefEmpty(this.href) || this.target !== '' || this.protocol === 'javascript:')
if (hrefEmpty(this.href) || this.target !== '' || this.protocol === 'javascript:') {
return;
}
if(!window.location.pathname.match(/\/(403|404)$/g))
if(!window.location.pathname.match(/\/(403|404)$/g)) {
app.previousUrl = window.location.href;
}
if (this.getAttribute('data-ajaxify') == 'false') {
if (this.getAttribute('data-ajaxify') === 'false') {
return;
}

View File

@@ -125,7 +125,7 @@ var socket,
setTimeout(app.logout, 1000);
});
app.enter_room('global');
app.enterRoom('global');
}
},
async: false
@@ -186,43 +186,35 @@ var socket,
clearTimeout(alert.attr('timeoutId'));
startTimeout(alert, params.timeout);
} else {
var div = document.createElement('div'),
button = document.createElement('button'),
strong = document.createElement('strong'),
p = document.createElement('p');
var div = $('<div id="' + alert_id + '" class="alert toaster-alert alert-' + params.type +'"></div>'),
button = $('<button class="close">&times;</button>'),
strong = $('<strong>' + title + '</strong>'),
p = $('<p>' + params.message + '</p>');
p.innerHTML = params.message;
strong.innerHTML = title;
div.append(button)
.append(strong)
.append(p);
div.className = "alert toaster-alert " + "alert-" + params.type;
div.setAttribute('id', alert_id);
div.appendChild(button);
div.appendChild(strong);
div.appendChild(p);
button.className = 'close';
button.innerHTML = '&times;';
button.onclick = function (ev) {
div.parentNode.removeChild(div);
}
button.on('click', function () {
div.remove();
});
if (params.location == null)
params.location = 'alert_window';
jQuery('#' + params.location).prepend(jQuery(div).fadeIn('100'));
$('#' + params.location).prepend(div.fadeIn('100'));
if (params.timeout) {
startTimeout(div, params.timeout);
}
if (params.clickfn) {
div.onclick = function () {
div.on('click', function () {
params.clickfn();
jQuery(div).fadeOut(500, function () {
this.remove();
div.fadeOut(500, function () {
$(this).remove();
});
}
});
}
}
}
@@ -251,22 +243,23 @@ var socket,
});
}
app.current_room = null;
app.enter_room = function (room) {
app.currentRoom = null;
app.enterRoom = function (room) {
if (socket) {
if (app.current_room === room)
if (app.currentRoom === room) {
return;
}
socket.emit('event:enter_room', {
'enter': room,
'leave': app.current_room
'leave': app.currentRoom
});
app.current_room = room;
app.currentRoom = room;
}
};
app.populate_online_users = function () {
app.populateOnlineUsers = function () {
var uids = [];
jQuery('.post-row').each(function () {
@@ -276,9 +269,7 @@ var socket,
socket.emit('api:user.get_online_users', uids);
}
app.process_page = function () {
app.populate_online_users();
function highlightNavigationLink() {
var path = window.location.pathname,
parts = path.split('/'),
active = parts[parts.length - 1];
@@ -295,10 +286,27 @@ var socket,
}
});
}
}
app.createUserTooltips = function() {
$('img[title].teaser-pic,img[title].user-img').each(function() {
$(this).tooltip({
placement: 'top',
title: $(this).attr('title')
});
});
}
app.processPage = function () {
app.populateOnlineUsers();
highlightNavigationLink();
$('span.timeago').timeago();
$('.post-content img').addClass('img-responsive');
app.createUserTooltips();
setTimeout(function () {
window.scrollTo(0, 1); // rehide address bar on mobile after page load completes.
}, 100);
@@ -331,6 +339,28 @@ var socket,
}
app.openChat = function (username, touid) {
if (username === app.username) {
app.alert({
type: 'warning',
title: 'Invalid Chat',
message: "You can't chat with yourself!",
timeout: 5000
});
return;
}
if (!app.username) {
app.alert({
type: 'danger',
title: 'Not Logged In',
message: 'Please log in to chat with <strong>' + username + '</strong>',
timeout: 5000
});
return;
}
require(['chat'], function (chat) {
var chatModal;
if (!chat.modalExists(touid)) {
@@ -342,62 +372,7 @@ var socket,
chat.center(chatModal);
});
}
app.createNewPosts = function (data) {
if (data.posts[0].uid !== app.uid) {
data.posts[0].display_moderator_tools = 'none';
}
var html = templates.prepare(templates['topic'].blocks['posts']).parse(data);
translator.translate(html, function(translatedHTML) {
var uniqueid = new Date().getTime(),
tempContainer = jQuery('<div id="' + uniqueid + '"></div>')
.appendTo("#post-container")
.hide()
.append(translatedHTML)
.fadeIn('slow');
for (var x = 0, numPosts = data.posts.length; x < numPosts; x++) {
socket.emit('api:post.privileges', data.posts[x].pid);
}
tempContainer.replaceWith(tempContainer.contents());
infiniteLoaderActive = false;
app.populate_online_users();
app.addCommasToNumbers();
$('span.timeago').timeago();
$('.post-content img').addClass('img-responsive');
});
}
app.infiniteLoaderActive = false;
app.loadMorePosts = function (tid, callback) {
if (app.infiniteLoaderActive)
return;
app.infiniteLoaderActive = true;
if ($('#loading-indicator').attr('done') === '0')
$('#loading-indicator').removeClass('hide');
socket.emit('api:topic.loadMore', {
tid: tid,
after: document.querySelectorAll('#post-container li[data-pid]').length
}, function (data) {
app.infiniteLoaderActive = false;
if (data.posts.length) {
$('#loading-indicator').attr('done', '0');
app.createNewPosts(data);
} else {
$('#loading-indicator').attr('done', '1');
}
$('#loading-indicator').addClass('hide');
if (callback)
callback(data.posts);
});
}
app.scrollToTop = function () {
$('body,html').animate({
scrollTop: 0
@@ -410,43 +385,6 @@ var socket,
});
}
app.scrollToPost = function (pid) {
if (!pid)
return;
var container = $(document.body),
scrollTo = $('#post_anchor_' + pid),
tid = $('#post-container').attr('data-tid');
function animateScroll() {
$('body,html').animate({
scrollTop: scrollTo.offset().top - container.offset().top + container.scrollTop() - $('#header-menu').height()
}, 400);
//$('body,html').scrollTop(scrollTo.offset().top - container.offset().top + container.scrollTop() - $('#header-menu').height());
}
if (!scrollTo.length && tid) {
var intervalID = setInterval(function () {
app.loadMorePosts(tid, function (posts) {
scrollTo = $('#post_anchor_' + pid);
if (tid && scrollTo.length) {
animateScroll();
}
if (!posts.length || scrollTo.length)
clearInterval(intervalID);
});
}, 100);
} else if (tid) {
animateScroll();
}
}
jQuery('document').ready(function () {
$('#search-form').on('submit', function () {
var input = $(this).find('input');

View File

@@ -10,9 +10,10 @@ define(['forum/accountheader'], function(header) {
$(document).ready(function() {
var username = $('.account-username a').html();
app.enter_room('user/' + theirid);
app.enterRoom('user/' + theirid);
app.addCommasToNumbers();
$('.user-recent-posts img').addClass('img-responsive');
var followBtn = $('#follow-btn');
var unfollowBtn = $('#unfollow-btn');

View File

@@ -129,7 +129,7 @@ define(function() {
update_blockclass(ev.target);
});
jQuery('.category_name, .category_description, .blockclass').on('change', function(ev) {
jQuery('.category_name, .category_description, .blockclass .category_bgColor').on('change', function(ev) {
modified(ev.target);
});
@@ -156,6 +156,20 @@ define(function() {
return false;
});
// Colour Picker
$('[data-name="bgColor"], [data-name="color"]').each(function(idx, inputEl) {
var jinputEl = $(this),
parentEl = jinputEl.parents('[data-cid]');
jinputEl.ColorPicker({
color: this.value || '#000',
onChange: function(hsb, hex) {
jinputEl.val('#' + hex);
if (inputEl.getAttribute('data-name') === 'bgColor') parentEl.css('background', '#' + hex);
else if (inputEl.getAttribute('data-name') === 'color') parentEl.css('color', '#' + hex);
modified(inputEl);
}
});
});
});
};

View File

@@ -45,10 +45,12 @@ define(function() {
errorEl.html(errorText).removeClass('hide');
} else {
createModal.modal('hide');
errorEl.addClass('hide');
createNameEl.val('');
ajaxify.go('admin/groups');
createModal.on('hidden.bs.modal', function() {
ajaxify.go('admin/groups');
});
createModal.modal('hide');
}
});
});

View File

@@ -20,7 +20,7 @@ define(function() {
document.getElementById('connections').innerHTML = total;
});
app.enter_room('admin');
app.enterRoom('admin');
socket.emit('api:get_all_rooms');
};

View File

@@ -42,8 +42,13 @@ define(function() {
loadMoreEl.addEventListener('click', function() {
if (this.className.indexOf('disabled') === -1) {
var topics = document.querySelectorAll('.topics li[data-tid]'),
lastTid = parseInt(topics[topics.length - 1].getAttribute('data-tid'));
var topics = document.querySelectorAll('.topics li[data-tid]');
if(!topics.length) {
return;
}
var lastTid = parseInt(topics[topics.length - 1].getAttribute('data-tid'));
this.innerHTML = '<i class="icon-refresh icon-spin"></i> Retrieving topics';
socket.emit('api:admin.topics.getMore', {

View File

@@ -3,7 +3,6 @@ define(function () {
Category.init = function() {
var cid = templates.get('category_id'),
room = 'category_' + cid,
twitterEl = jQuery('#twitter-intent'),
facebookEl = jQuery('#facebook-share'),
googleEl = jQuery('#google-share'),
@@ -12,7 +11,7 @@ define(function () {
google_url = templates.get('google-share-url'),
loadingMoreTopics = false;
app.enter_room(room);
app.enterRoom('category_' + cid);
twitterEl.on('click', function () {
window.open(twitter_url, '_blank', 'width=550,height=420,scrollbars=no,status=no');
@@ -56,7 +55,7 @@ define(function () {
li.setAttribute('data-pid', posts[i].pid);
li.innerHTML = '<a href="/user/' + posts[i].userslug + '"><img title="' + posts[i].username + '" style="width: 48px; height: 48px; /*temporary*/" class="img-rounded" src="' + posts[i].picture + '" class="" /></a>' +
li.innerHTML = '<a href="/user/' + posts[i].userslug + '"><img title="' + posts[i].username + '" style="width: 48px; height: 48px; /*temporary*/" class="img-rounded user-img" src="' + posts[i].picture + '" class="" /></a>' +
'<a href="/topic/' + posts[i].topicSlug + '#' + posts[i].pid + '">' +
'<strong><span>'+ posts[i].username + '</span></strong>' +
'<p>' +
@@ -69,6 +68,7 @@ define(function () {
recent_replies.appendChild(frag);
}
$('#category_recent_replies span.timeago').timeago();
app.createUserTooltips();
});
$(window).off('scroll').on('scroll', function (ev) {
@@ -94,17 +94,20 @@ define(function () {
if (numTopics > 0) {
for (var x = 0; x < numTopics; x++) {
if ($(topics[x]).find('.icon-pushpin').length)
if ($(topics[x]).find('.icon-pushpin').length) {
if(x === numTopics - 1) {
topic.insertAfter(topics[x]);
}
continue;
}
topic.insertBefore(topics[x]);
topic.hide().fadeIn('slow');
break;
}
} else {
container.append(topic);
topic.hide().fadeIn('slow');
}
topic.hide().fadeIn('slow');
socket.emit('api:categories.getRecentReplies', templates.get('category_id'));
addActiveUser(data);

View File

@@ -36,7 +36,9 @@ define(function() {
} else {
$('#login-error-notify').hide();
if(!app.previousUrl) { app.previousUrl = '/'; }
if(!app.previousUrl) {
app.previousUrl = '/';
}
if(app.previousUrl.indexOf('/reset/') != -1)
window.location.replace(RELATIVE_PATH + "/?loggedin");
@@ -63,6 +65,12 @@ define(function() {
});
document.querySelector('#content input').focus();
if(!config.emailSetup)
$('#reset-link').addClass('hide');
else
$('#reset-link').removeClass('hide');
};
return Login;

View File

@@ -8,7 +8,7 @@ define(function() {
var active = '';
Recent.init = function() {
app.enter_room('recent_posts');
app.enterRoom('recent_posts');
ajaxify.register_events([
'event:new_topic',

View File

@@ -1,5 +1,7 @@
define(function() {
var Topic = {};
var Topic = {},
infiniteLoaderActive = false;
Topic.init = function() {
var expose_tools = templates.get('expose_tools'),
@@ -17,16 +19,26 @@ define(function() {
google_url = templates.get('google-share-url');
function fixDeleteStateForPosts() {
var postEls = document.querySelectorAll('#post-container li[data-deleted]');
for (var x = 0, numPosts = postEls.length; x < numPosts; x++) {
if (postEls[x].getAttribute('data-deleted') === '1') {
toggle_post_delete_state(postEls[x].getAttribute('data-pid'));
}
postEls[x].removeAttribute('data-deleted');
}
}
jQuery('document').ready(function() {
app.addCommasToNumbers();
app.enterRoom('topic_' + tid);
var room = 'topic_' + tid,
adminTools = document.getElementById('thread-tools');
app.enter_room(room);
if($('#post-container .sub-posts').length) {
$('.topic-main-buttons').removeClass('hide').parent().removeClass('hide');
}
$('.twitter-share').on('click', function () {
window.open(twitter_url, '_blank', 'width=550,height=420,scrollbars=no,status=no');
@@ -50,11 +62,10 @@ define(function() {
if (expose_tools === '1') {
var moveThreadModal = $('#move_thread_modal');
adminTools.style.visibility = 'inherit';
$('.thread-tools').removeClass('hide');
// Add events to the thread tools
$('#delete_thread').on('click', function(e) {
$('.delete_thread').on('click', function(e) {
if (thread_state.deleted !== '1') {
bootbox.confirm('Are you sure you want to delete this thread?', function(confirm) {
if (confirm) {
@@ -73,7 +84,7 @@ define(function() {
return false;
});
$('#lock_thread').on('click', function(e) {
$('.lock_thread').on('click', function(e) {
if (thread_state.locked !== '1') {
socket.emit('api:topic.lock', {
tid: tid
@@ -86,7 +97,7 @@ define(function() {
return false;
});
$('#pin_thread').on('click', function(e) {
$('.pin_thread').on('click', function(e) {
if (thread_state.pinned !== '1') {
socket.emit('api:topic.pin', {
tid: tid
@@ -99,7 +110,7 @@ define(function() {
return false;
});
$('#move_thread').on('click', function(e) {
$('.move_thread').on('click', function(e) {
moveThreadModal.modal('show');
return false;
});
@@ -109,7 +120,6 @@ define(function() {
var loadingEl = document.getElementById('categories-loading');
if (loadingEl) {
socket.once('api:categories.get', function(data) {
console.log(data);
// Render categories
var categoriesFrag = document.createDocumentFragment(),
categoryEl = document.createElement('li'),
@@ -182,12 +192,8 @@ define(function() {
});
}
// Fix delete state for this thread's posts
var postEls = document.querySelectorAll('#post-container li[data-deleted]');
for (var x = 0, numPosts = postEls.length; x < numPosts; x++) {
if (postEls[x].getAttribute('data-deleted') === '1') toggle_post_delete_state(postEls[x].getAttribute('data-pid'));
postEls[x].removeAttribute('data-deleted');
}
fixDeleteStateForPosts();
// Follow Thread State
var followEl = $('.main-post .follow'),
@@ -246,7 +252,7 @@ define(function() {
var bookmark = localStorage.getItem('topic:' + tid + ':bookmark');
if(bookmark) {
app.scrollToPost(parseInt(bookmark, 10));
Topic.scrollToPost(parseInt(bookmark, 10));
}
$('#post-container').on('click', '.deleted', function(ev) {
@@ -258,13 +264,15 @@ define(function() {
$(window).off('scroll').on('scroll', function() {
var bottom = ($(document).height() - $(window).height()) * 0.9;
if ($(window).scrollTop() > bottom && !app.infiniteLoaderActive && $('#post-container').children().length) {
app.loadMorePosts(tid);
if ($(window).scrollTop() > bottom && !infiniteLoaderActive && $('#post-container').children().length) {
loadMorePosts(tid, function(posts) {
fixDeleteStateForPosts();
});
}
});
}
var reply_fn = function() {
$('.topic').on('click', '.post_reply', function() {
var selectionText = '',
selection = window.getSelection() || document.getSelection();
@@ -278,9 +286,7 @@ define(function() {
cmp.push(tid, null, null, selectionText.length > 0 ? selectionText + '\n\n' : '');
});
}
};
$('#post-container').on('click', '.post_reply', reply_fn);
$('#post_reply').on('click', reply_fn);
});
$('#post-container').on('click', '.quote', function() {
if (thread_state.locked !== '1') {
@@ -307,12 +313,12 @@ define(function() {
if (element.attr('class') == 'icon-star-empty') {
socket.emit('api:posts.favourite', {
pid: pid,
room_id: app.current_room
room_id: app.currentRoom
});
} else {
socket.emit('api:posts.unfavourite', {
pid: pid,
room_id: app.current_room
room_id: app.currentRoom
});
}
});
@@ -341,13 +347,25 @@ define(function() {
confirmDel = confirm((deleteAction ? 'Delete' : 'Restore') + ' this post?');
if (confirmDel) {
deleteAction ?
if(deleteAction) {
socket.emit('api:posts.delete', {
pid: pid
}) :
socket.emit('api:posts.restore', {
pid: pid
pid: pid,
tid: tid
}, function(err) {
if(err) {
return app.alertError('Can\'t delete post!');
}
});
} else {
socket.emit('api:posts.restore', {
pid: pid,
tid: tid
}, function(err) {
if(err) {
return app.alertError('Can\'t restore post!');
}
});
}
}
});
@@ -355,9 +373,6 @@ define(function() {
var username = $(this).parents('li.row').attr('data-username');
var touid = $(this).parents('li.row').attr('data-uid');
if (username === app.username || !app.username)
return;
app.openChat(username, touid);
});
@@ -381,12 +396,15 @@ define(function() {
var userLink = $('<a href="/user/' + userslug + '"></a>').append(userIcon);
userLink.attr('data-uid', uid);
var div = $('<div class="inline-block"></div>');
div.append(userLink);
userLink.tooltip({
placement: 'left',
placement: 'top',
title: username
});
return userLink;
return div;
}
}
@@ -428,7 +446,7 @@ define(function() {
activeEl.find('.anonymous-box').remove();
if(anonymousCount || remainingUsers) {
var anonLink = $('<i class="icon-user anonymous-box"></i>');
var anonLink = $('<div class="anonymous-box inline-block"><i class="icon-user"></i></div>');
activeEl.append(anonLink);
var title = '';
@@ -440,12 +458,12 @@ define(function() {
title = anonymousCount + ' guest(s)';
anonLink.tooltip({
placement: 'left',
placement: 'top',
title: title
});
}
}
app.populate_online_users();
app.populateOnlineUsers();
});
socket.on('event:rep_up', function(data) {
@@ -456,7 +474,7 @@ define(function() {
adjust_rep(-1, data.pid, data.uid);
});
socket.on('event:new_post', app.createNewPosts);
socket.on('event:new_post', createNewPosts);
socket.on('event:topic_deleted', function(data) {
if (data.tid === tid && data.status === 'ok') {
@@ -540,11 +558,15 @@ define(function() {
});
socket.on('event:post_deleted', function(data) {
if (data.pid) toggle_post_delete_state(data.pid, true);
if (data.pid) {
toggle_post_delete_state(data.pid);
}
});
socket.on('event:post_restored', function(data) {
if (data.pid) toggle_post_delete_state(data.pid, true);
if (data.pid) {
toggle_post_delete_state(data.pid);
}
});
socket.on('api:post.privileges', function(privileges) {
@@ -566,19 +588,19 @@ define(function() {
}
function set_locked_state(locked, alert) {
var threadReplyBtn = document.getElementById('post_reply'),
var threadReplyBtn = $('.topic-main-buttons .post_reply'),
postReplyBtns = document.querySelectorAll('#post-container .post_reply'),
quoteBtns = document.querySelectorAll('#post-container .quote'),
editBtns = document.querySelectorAll('#post-container .edit'),
deleteBtns = document.querySelectorAll('#post-container .delete'),
numPosts = document.querySelectorAll('#post_container li[data-pid]').length,
lockThreadEl = document.getElementById('lock_thread'),
lockThreadEl = $('.lock_thread'),
x;
if (locked === true) {
lockThreadEl.innerHTML = '<i class="icon-unlock"></i> Unlock Thread';
threadReplyBtn.disabled = true;
threadReplyBtn.innerHTML = 'Locked <i class="icon-lock"></i>';
lockThreadEl.html('<i class="icon-unlock"></i> Unlock Thread');
threadReplyBtn.attr('disabled', true);
threadReplyBtn.html('Locked <i class="icon-lock"></i>');
for (x = 0; x < numPosts; x++) {
postReplyBtns[x].innerHTML = 'Locked <i class="icon-lock"></i>';
quoteBtns[x].style.display = 'none';
@@ -598,9 +620,9 @@ define(function() {
thread_state.locked = '1';
} else {
lockThreadEl.innerHTML = '<i class="icon-lock"></i> Lock Thread';
threadReplyBtn.disabled = false;
threadReplyBtn.innerHTML = 'Reply';
lockThreadEl.html('<i class="icon-lock"></i> Lock Thread');
threadReplyBtn.attr('disabled', false);
threadReplyBtn.html('Reply');
for (x = 0; x < numPosts; x++) {
postReplyBtns[x].innerHTML = 'Reply <i class="icon-reply"></i>';
quoteBtns[x].style.display = 'inline-block';
@@ -623,13 +645,14 @@ define(function() {
}
function set_delete_state(deleted) {
var deleteThreadEl = document.getElementById('delete_thread'),
deleteTextEl = deleteThreadEl.getElementsByTagName('span')[0],
var deleteThreadEl = $('.delete_thread'),
deleteTextEl = $('.delete_thread span'),
//deleteThreadEl.getElementsByTagName('span')[0],
threadEl = $('#post-container'),
deleteNotice = document.getElementById('thread-deleted') || document.createElement('div');
if (deleted) {
deleteTextEl.innerHTML = '<i class="icon-comment"></i> Restore Thread';
deleteTextEl.html('<i class="icon-comment"></i> Restore Thread');
threadEl.addClass('deleted');
// Spawn a 'deleted' notice at the top of the page
@@ -640,7 +663,7 @@ define(function() {
thread_state.deleted = '1';
} else {
deleteTextEl.innerHTML = '<i class="icon-trash"></i> Delete Thread';
deleteTextEl.html('<i class="icon-trash"></i> Delete Thread');
threadEl.removeClass('deleted');
deleteNotice.parentNode.removeChild(deleteNotice);
@@ -649,10 +672,10 @@ define(function() {
}
function set_pinned_state(pinned, alert) {
var pinEl = document.getElementById('pin_thread');
var pinEl = $('.pin_thread');
if (pinned) {
pinEl.innerHTML = '<i class="icon-pushpin"></i> Unpin Thread';
pinEl.html('<i class="icon-pushpin"></i> Unpin Thread');
if (alert) {
app.alert({
'alert_id': 'thread_pin',
@@ -665,7 +688,7 @@ define(function() {
thread_state.pinned = '1';
} else {
pinEl.innerHTML = '<i class="icon-pushpin"></i> Pin Thread';
pinEl.html('<i class="icon-pushpin"></i> Pin Thread');
if (alert) {
app.alert({
'alert_id': 'thread_pin',
@@ -702,6 +725,7 @@ define(function() {
} else {
postEl.toggleClass('none');
}
updatePostCount();
});
socket.emit('api:post.privileges', pid);
}
@@ -811,5 +835,138 @@ define(function() {
window.onload = updateHeader;
};
Topic.scrollToPost = function(pid) {
if (!pid) {
return;
}
var container = $(document.body),
scrollTo = $('#post_anchor_' + pid),
tid = $('#post-container').attr('data-tid');
function animateScroll() {
$('body,html').animate({
scrollTop: scrollTo.offset().top - container.offset().top + container.scrollTop() - $('#header-menu').height()
}, 400);
}
if (!scrollTo.length && tid) {
var intervalID = setInterval(function () {
loadMorePosts(tid, function (posts) {
scrollTo = $('#post_anchor_' + pid);
if (tid && scrollTo.length) {
animateScroll();
}
if (!posts.length || scrollTo.length)
clearInterval(intervalID);
});
}, 100);
} else if (tid) {
animateScroll();
}
}
function createNewPosts(data, infiniteLoaded) {
if(!data || (data.posts && !data.posts.length))
return;
if (data.posts[0].uid !== app.uid) {
data.posts[0].display_moderator_tools = 'none';
}
function removeAlreadyAddedPosts() {
data.posts = data.posts.filter(function(post) {
return $('#post-container li[data-pid="' + post.pid +'"]').length === 0;
});
}
function findInsertionPoint() {
var after = null,
firstPid = data.posts[0].pid;
$('#post-container li[data-pid]').each(function() {
if(parseInt(firstPid, 10) > parseInt($(this).attr('data-pid'), 10)) {
after = $(this);
if(after.hasClass('main-post')) {
after = after.next();
}
} else {
return false;
}
});
return after;
}
removeAlreadyAddedPosts();
if(!data.posts.length) {
return;
}
var insertAfter = findInsertionPoint();
var html = templates.prepare(templates['topic'].blocks['posts']).parse(data);
translator.translate(html, function(translatedHTML) {
var translated = $(translatedHTML);
if(!infiniteLoaded) {
translated.removeClass('infiniteloaded');
}
translated.insertAfter(insertAfter)
.hide()
.fadeIn('slow');
for (var x = 0, numPosts = data.posts.length; x < numPosts; x++) {
socket.emit('api:post.privileges', data.posts[x].pid);
}
infiniteLoaderActive = false;
app.populateOnlineUsers();
app.addCommasToNumbers();
$('span.timeago').timeago();
$('.post-content img').addClass('img-responsive');
updatePostCount();
});
}
function updatePostCount() {
$('#topic-post-count').html($('#post-container li[data-pid]:not(.deleted)').length);
}
function loadMorePosts(tid, callback) {
var indicatorEl = $('.loading-indicator');
if (infiniteLoaderActive) {
return;
}
infiniteLoaderActive = true;
if (indicatorEl.attr('done') === '0') {
indicatorEl.fadeIn();
}
socket.emit('api:topic.loadMore', {
tid: tid,
after: $('#post-container .post-row.infiniteloaded').length
}, function (data) {
infiniteLoaderActive = false;
if (data.posts.length) {
indicatorEl.attr('done', '0');
createNewPosts(data, true);
} else {
indicatorEl.attr('done', '1');
}
indicatorEl.fadeOut();
if (callback) {
callback(data.posts);
}
});
}
return Topic;
});

View File

@@ -210,13 +210,17 @@ define(['taskbar'], function(taskbar) {
selectionEnd = postContentEl.selectionEnd,
selectionLength = selectionEnd - selectionStart;
function insertIntoInput(element, value) {
var start = postContentEl.selectionStart;
element.value = element.value.slice(0, start) + value + element.value.slice(start, element.value.length);
postContentEl.selectionStart = postContentEl.selectionEnd = start + value.length;
}
switch(iconClass) {
case 'icon-bold':
if (selectionStart === selectionEnd) {
// Nothing selected
postContentEl.value = postContentEl.value + '**bolded text**';
postContentEl.selectionStart = cursorEnd+2;
postContentEl.selectionEnd = postContentEl.value.length - 2;
insertIntoInput(postContentEl, "**bolded text**");
} else {
// Text selected
postContentEl.value = postContentEl.value.slice(0, selectionStart) + '**' + postContentEl.value.slice(selectionStart, selectionEnd) + '**' + postContentEl.value.slice(selectionEnd);
@@ -227,9 +231,7 @@ define(['taskbar'], function(taskbar) {
case 'icon-italic':
if (selectionStart === selectionEnd) {
// Nothing selected
postContentEl.value = postContentEl.value + '*italicised text*';
postContentEl.selectionStart = cursorEnd+1;
postContentEl.selectionEnd = postContentEl.value.length - 1;
insertIntoInput(postContentEl, "*italicised text*");
} else {
// Text selected
postContentEl.value = postContentEl.value.slice(0, selectionStart) + '*' + postContentEl.value.slice(selectionStart, selectionEnd) + '*' + postContentEl.value.slice(selectionEnd);
@@ -239,16 +241,12 @@ define(['taskbar'], function(taskbar) {
break;
case 'icon-list':
// Nothing selected
postContentEl.value = postContentEl.value + "\n\n* list item";
postContentEl.selectionStart = cursorEnd+4;
postContentEl.selectionEnd = postContentEl.value.length;
insertIntoInput(postContentEl, "\n\n* list item");
break;
case 'icon-link':
if (selectionStart === selectionEnd) {
// Nothing selected
postContentEl.value = postContentEl.value + '[link text](link url)';
postContentEl.selectionStart = cursorEnd+12;
postContentEl.selectionEnd = postContentEl.value.length - 1;
insertIntoInput(postContentEl, "[link text](link url)");
} else {
// Text selected
postContentEl.value = postContentEl.value.slice(0, selectionStart) + '[' + postContentEl.value.slice(selectionStart, selectionEnd) + '](link url)' + postContentEl.value.slice(selectionEnd);
@@ -258,6 +256,7 @@ define(['taskbar'], function(taskbar) {
break;
}
});
window.addEventListener('resize', function() {
if (composer.active !== undefined) composer.reposition(composer.active);
});

View File

@@ -235,11 +235,11 @@
}
function makeRegex(block) {
return new RegExp("<!-- BEGIN " + block + " -->[\\s\\S]*<!-- END " + block + " -->", 'g');
return new RegExp("<!--[\\s]*BEGIN " + block + "[\\s]*-->[\\s\\S]*<!--[\\s]*END " + block + "[\\s]*-->", 'g');
}
function makeConditionalRegex(block) {
return new RegExp("<!-- IF " + block + " -->[\\s\\S]*<!-- ENDIF " + block + " -->", 'g');
return new RegExp("<!--[\\s]*IF " + block + "[\\s]*-->[\\s\\S]*<!--[\\s]*ENDIF " + block + "[\\s]*-->", 'g');
}
function getBlock(regex, block, template) {
@@ -249,21 +249,8 @@
if (self.blocks && block !== undefined) self.blocks[block] = data[0];
data = data[0]
.replace("<!-- BEGIN " + block + " -->", "")
.replace("<!-- END " + block + " -->", "");
return data;
}
function getConditionalBlock(regex, block, template) {
data = template.match(regex);
if (data == null) return;
if (self.blocks && block !== undefined) self.blocks[block] = data[0];
data = data[0]
.replace("<!-- IF " + block + " -->", "")
.replace("<!-- ENDIF " + block + " -->", "");
.replace("<!--[\\s]*BEGIN " + block + "[\\s]*-->", "")
.replace("<!--[\\s]*END " + block + "[\\s]*-->", "");
return data;
}
@@ -317,14 +304,29 @@
block = parse(data[d], namespace, block);
template = setBlock(regex, block, template);
} else {
var conditional = makeConditionalRegex(d),
block = getConditionalBlock(conditional, namespace, template);
var conditional = makeConditionalRegex(namespace + d);
if (block && !data[d]) {
template = template.replace(conditional, '');
var conditionalBlock = conditional.exec(template);
if (conditionalBlock !== null) {
conditionalBlock = conditionalBlock[0].split(/<!-- ELSE -->/);
if (conditionalBlock[1]) {
// there is an else statement
if (!data[d]) {
template = template.replace(conditional, conditionalBlock[1]);
} else {
template = template.replace(conditional, conditionalBlock[0]);
}
} else {
// regular if
if (!data[d]) {
template = template.replace(conditional, '');
}
}
}
template = replace(namespace + d, data[d], template);
}
}

View File

@@ -36,7 +36,7 @@
<br/>
<span class="account-bio-label">website</span>
<span><a href="{website}">{website}</a></span>
<span><a href="{website}">{websiteName}</a></span>
<br/>
<span class="account-bio-label">location</span>

File diff suppressed because one or more lines are too long

View File

@@ -7,19 +7,19 @@
<!-- BEGIN groups -->
<li data-gid="{groups.gid}">
<div class="row">
<div class="col-lg-8">
<div class="col-lg-8">
<h2>{groups.name}</h2>
<p>{groups.description}</p>
<div class="btn-group">
<button class="btn btn-default" data-action="members">Members</button>
<!-- IF groups.deletable -->
<button class="btn btn-danger" data-action="delete">Delete Group</button>
<!-- ENDIF groups.deletable -->
</div>
</div>
<div class="col-lg-4">
<ul class="pull-right members">
<!-- BEGIN members -->
<li data-uid="{groups.members.uid}" title="{groups.members.username}"><img src="{groups.members.picture}" /></li>
<!-- END members -->
<!-- BEGIN members --><li data-uid="{groups.members.uid}" title="{groups.members.username}"><img src="{groups.members.picture}" /></li><!-- END members -->
</ul>
</div>
</div>

View File

@@ -12,6 +12,7 @@
<script type="text/javascript" src="{relative_path}/vendor/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/qunit/qunit-git.js"></script>
<link rel="stylesheet" type="text/css" href="http://code.jquery.com/qunit/qunit-git.css">
<link rel="stylesheet" type="text/css" href="{relative_path}/vendor/colorpicker/colorpicker.css">
<script type="text/javascript" src="{relative_path}/socket.io/socket.io.js"></script>
<script type="text/javascript" src="{relative_path}/src/app.js"></script>
<script type="text/javascript" src="{relative_path}/src/templates.js"></script>
@@ -21,6 +22,7 @@
<script src="{relative_path}/vendor/jquery/js/jquery.form.js"></script>
<script src="{relative_path}/vendor/requirejs/require.js"></script>
<script src="{relative_path}/vendor/bootbox/bootbox.min.js"></script>
<script src="{relative_path}/vendor/colorpicker/colorpicker.js"></script>
<script>
require.config({

View File

@@ -1,6 +1,7 @@
<h1>Topics</h1>
<hr />
<ul class="topics">
<!-- BEGIN topics -->
<li data-tid="{topics.tid}" data-locked="{topics.locked}" data-pinned="{topics.pinned}" data-deleted="{topics.deleted}">
@@ -19,6 +20,14 @@
<!-- END topics -->
</ul>
<!-- IF notopics -->
<div class="alert alert-warning" id="category-no-topics">
<strong>There are no topics.</strong>
</div>
<!-- ELSE -->
<div class="text-center">
<button id="topics_loadmore" class="btn btn-primary btn-lg">Load More Topics</button>
</div>
<!-- ENDIF notopics -->

View File

@@ -62,6 +62,9 @@
</span>
<span class="pull-right hidden-xs">
<!-- IF topics.unreplied -->
No one has replied
<!-- ELSE -->
<a href="/user/{topics.teaser_userslug}">
<img class="teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
</a>
@@ -69,7 +72,7 @@
replied
</a>
<span class="timeago" title="{topics.teaser_timestamp}"></span>
<!-- ENDIF topics.unreplied -->
</span>
</small>
</div>
@@ -95,7 +98,7 @@
</div>
<div class="block-content active-users">
<!-- BEGIN active_users -->
<a data-uid="{active_users.uid}" href="/user/{active_users.userslug}"><img title="{active_users.username}" src="{active_users.picture}" class="img-rounded" /></a>
<a data-uid="{active_users.uid}" href="/user/{active_users.userslug}"><img title="{active_users.username}" src="{active_users.picture}" class="img-rounded user-img" /></a>
<!-- END active_users -->
</div>
</div>

View File

@@ -13,7 +13,8 @@
<!-- BEGIN posts -->
<div class="topic-row img-thumbnail clearfix" topic-url="topic/{posts.tid}/#{posts.pid}">
<span><strong>{posts.username}</strong> : </span>
<span>{posts.content}</span>
<span>{posts.category_name} >> {posts.title}</span>
<div>{posts.content}</div>
<div>
<span class="pull-right timeago" title="{posts.relativeTime}"></span>
</div>

View File

@@ -60,6 +60,7 @@
<footer id="footer" class="container footer">
{footerHTML}
<div class="copyright">Copyright &copy; 2013 <a target="_blank" href="http://www.nodebb.org">NodeBB</a> by <a target="_blank" href="https://github.com/psychobunny">psychobunny</a>, <a href="https://github.com/julianlam" target="_blank">julianlam</a>, <a href="https://github.com/barisusakli" target="_blank">barisusakli</a> from <a target="_blank" href="http://www.designcreateplay.com">designcreateplay</a></div>
</footer>

View File

@@ -116,7 +116,13 @@
</ul>
<ul id="logged-out-menu" class="nav navbar-nav navbar-right">
<li>
<li class="visible-lg visible-md visible-sm">
<a href="/register">Register</a>
</li>
<li class="visible-lg visible-md visible-sm">
<a href="/login">Login</a>
</li>
<li class="visible-xs">
<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="loggedout_dropdown"><i class="icon-signin"></i></a>
<ul class="dropdown-menu" aria-labelledby="loggedout_dropdown">
<li>

View File

@@ -8,7 +8,7 @@
<a href="category/{categories.slug}" itemprop="url">
<meta itemprop="name" content="{categories.name}">
<h4><span class="badge {categories.badgeclass}">{categories.topic_count} </span> {categories.name}</h4>
<div class="icon {categories.blockclass}">
<div class="icon" style="background: {categories.bgColor}; color: {categories.color};">
<div id="category-{categories.cid}" class="category-slider-{categories.post_count}">
<div class="category-box"><i class="{categories.icon} icon-4x"></i></div>
<div class="category-box" itemprop="description">{categories.description}</div>

View File

@@ -1,4 +1,3 @@
<h1>User Privilege Thresholds</h1>
<form class="form-inline">
@@ -15,13 +14,13 @@
<label>Manage Content</label> <input type="number" class="input-mini" value="1000" placeholder="1000" data-field="privileges:manage_content" />
</p>
<p>
Users with reach the "Manage Content" threshold are able to edit/delete other users' posts.
Users who reach the "Manage Content" threshold are able to edit/delete other users' posts.
</p>
<p>
<label>Manage Topics</label> <input type="number" class="input-mini" value="2000" placeholder="2000" data-field="privileges:manage_topic" />
</p>
<p>
Users with reach the "Manage Topics" threshold are able to edit, lock, pin, close, and delete topics.
Users who reach the "Manage Topics" threshold are able to edit, lock, pin, close, and delete topics.
</p>
</form>
@@ -43,4 +42,4 @@
document.location.href = '/';
});
})();
</script>
</script>

View File

@@ -40,7 +40,7 @@
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary" id="login" type="submit">[[login:login]]</button> &nbsp; <a href="/reset">[[login:forgot_password]]</a>
<button class="btn btn-primary" id="login" type="submit">[[login:login]]</button> &nbsp; <a id="reset-link" class="hide" href="/reset">[[login:forgot_password]]</a>
</div>
</div>
<input type="hidden" name="_csrf" value="{token}" id="csrf-token" />

View File

@@ -5,7 +5,7 @@
<ul class="notifications-list">
<!-- BEGIN notifications -->
<li data-nid="{notifications.nid}" class="{notifications.readClass}">
<a href="..{notifications.path}">{notifications.text}</a>
<a href="{notifications.path}">{notifications.text}</a>
<p class="timestamp">
<span class="timeago" title="{notifications.datetimeISO}"></span>
</p>

View File

@@ -10,11 +10,11 @@
<div class="well">
<h3>
You are now leaving NodeBB.
You are now leaving {title}.
</h3>
<p>
<a href="{url}" rel="nofollow" class="btn btn-primary btn-lg">Continue to {url}</a>
<a id="return-btn" href="#" class="btn btn-lg btn-warning">Return to NodeBB</a>
<a id="return-btn" href="#" class="btn btn-lg btn-warning">Return to {title}</a>
</p>
</div>
</div>

View File

@@ -1,7 +1,6 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li class="active">{category_name}</li>
<div id="category_active_users"></div>
<li class="active">Recent <a href="./recent.rss"><i class="icon-rss-sign"></i></a></li>
</ol>
<ul class="nav nav-pills">
@@ -21,7 +20,7 @@
</div>
<div class="category row">
<div class="{topic_row_size}">
<div class="col-md-12">
<ul id="topics-container">
<!-- BEGIN topics -->
<li class="category-item {topics.deleted-class}">
@@ -55,6 +54,9 @@
</span>
<span class="pull-right hidden-xs">
<!-- IF topics.unreplied -->
No one has replied
<!-- ELSE -->
<a href="/user/{topics.teaser_userslug}">
<img class="teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
</a>
@@ -62,7 +64,7 @@
replied
</a>
<span class="timeago" title="{topics.teaser_timestamp}"></span>
<!-- ENDIF topics.unreplied -->
</span>
</small>
</div>

View File

@@ -20,13 +20,14 @@
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
<span itemprop="title">{topic_name} <a target="_blank" href="../{topic_id}.rss"><i class="icon-rss-sign"></i></a></span>
</li>
<div class="thread_active_users active-users pull-right hidden-xs"></div>
</ol>
<ul id="post-container" class="container" data-tid="{topic_id}">
<!-- BEGIN main_posts -->
<a id="post_anchor_{main_posts.pid}" name="{main_posts.pid}"></a>
<li class="row post-row main-post" data-pid="{main_posts.pid}" data-uid="{main_posts.uid}" data-username="{main_posts.username}" data-deleted="{main_posts.deleted}" itemscope itemtype="http://schema.org/Article">
<li class="row post-row main-post infiniteloaded" data-pid="{main_posts.pid}" data-uid="{main_posts.uid}" data-username="{main_posts.username}" data-deleted="{main_posts.deleted}" itemscope itemtype="http://schema.org/Article">
<a id="post_anchor_{main_posts.pid}" name="{main_posts.pid}"></a>
<div class="col-md-12">
<div class="post-block">
<meta itemprop="datePublished" content="{main_posts.relativeTime}">
@@ -63,17 +64,17 @@
<button class="btn btn-sm btn-primary btn post_reply" type="button">[[topic:reply]] <i class="icon-reply"></i></button>
</div>
<div class="btn-group pull-right post-tools">
<button class="btn btn-sm btn-default edit {main_posts.display_moderator_tools}" type="button" title="[[topic:edit]]"><i class="icon-pencil"></i></button>
<button class="btn btn-sm btn-default delete {main_posts.display_moderator_tools}" type="button" title="[[topic:delete]]"><i class="icon-trash"></i></button>
</div>
<div class="btn-group pull-right post-tools">
<button class="btn btn-sm btn-default link" type="button" title="[[topic:link]]"><i class="icon-link"></i></button>
<button class="btn btn-sm btn-default facebook-share" type="button" title=""><i class="icon-facebook"></i></button>
<button class="btn btn-sm btn-default twitter-share" type="button" title=""><i class="icon-twitter"></i></button>
<button class="btn btn-sm btn-default google-share" type="button" title=""><i class="icon-google-plus"></i></button>
<div class="pull-right">
<div class="btn-group post-tools">
<button class="btn btn-sm btn-default link" type="button" title="[[topic:link]]"><i class="icon-link"></i></button>
<button class="btn btn-sm btn-default facebook-share" type="button" title=""><i class="icon-facebook"></i></button>
<button class="btn btn-sm btn-default twitter-share" type="button" title=""><i class="icon-twitter"></i></button>
<button class="btn btn-sm btn-default google-share" type="button" title=""><i class="icon-google-plus"></i></button>
</div>
<div class="btn-group post-tools">
<button class="btn btn-sm btn-default edit {main_posts.display_moderator_tools}" type="button" title="[[topic:edit]]"><i class="icon-pencil"></i></button>
<button class="btn btn-sm btn-default delete {main_posts.display_moderator_tools}" type="button" title="[[topic:delete]]"><i class="icon-trash"></i></button>
</div>
</div>
<input id="post_{main_posts.pid}_link" value="" class="pull-right" style="display:none;"></input>
@@ -98,9 +99,38 @@
</li>
<!-- END main_posts -->
<li class="well">
<div class="inline-block">
<small class="topic-stats">
<span>posts</span>
<strong><span id="topic-post-count" class="formatted-number">{postcount}</span></strong> |
<span>views</span>
<strong><span class="formatted-number">{viewcount}</span></strong> |
<span>browsing</span>
</small>
<div class="thread_active_users active-users inline-block"></div>
</div>
<div class="topic-main-buttons pull-right inline-block">
<button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button>
<div class="btn-group thread-tools hide">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="#" class="pin_thread"><i class="icon-pushpin"></i> [[topic:thread_tools.pin]]</a></li>
<li><a href="#" class="lock_thread"><i class="icon-lock"></i> [[topic:thread_tools.lock]]</a></li>
<li class="divider"></li>
<li><a href="#" class="move_thread"><i class="icon-move"></i> [[topic:thread_tools.move]]</a></li>
<li class="divider"></li>
<li><a href="#" class="delete_thread"><span class="text-error"><i class="icon-trash"></i> [[topic:thread_tools.delete]]</span></a></li>
</ul>
</div>
</div>
<div style="clear:both;"></div>
</li>
<!-- BEGIN posts -->
<a id="post_anchor_{posts.pid}" name="{posts.pid}"></a>
<li class="row post-row sub-posts" data-pid="{posts.pid}" data-uid="{posts.uid}" data-username="{posts.username}" data-deleted="{posts.deleted}" itemscope itemtype="http://schema.org/Comment">
<li class="row post-row sub-posts infiniteloaded" data-pid="{posts.pid}" data-uid="{posts.uid}" data-username="{posts.username}" data-deleted="{posts.deleted}" itemscope itemtype="http://schema.org/Comment">
<a id="post_anchor_{posts.pid}" name="{posts.pid}"></a>
<meta itemprop="datePublished" content="{posts.relativeTime}">
<meta itemprop="dateModified" content="{posts.relativeEditTime}">
<div class="col-md-1 profile-image-block hidden-xs hidden-sm">
@@ -163,25 +193,25 @@
<!-- END posts -->
</ul>
<div id="loading-indicator" style="text-align:center;" class="hide" done="0">
<i class="icon-spinner icon-spin icon-large"></i>
</div>
<hr />
<div class="topic-main-buttons">
<button id="post_reply" class="btn btn-primary btn-lg post_reply" type="button">[[topic:reply]]</button>
<div class="btn-group pull-right" id="thread-tools" style="visibility: hidden;">
<button class="btn btn-default btn-lg dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="#" id="pin_thread"><i class="icon-pushpin"></i> [[topic:thread_tools.pin]]</a></li>
<li><a href="#" id="lock_thread"><i class="icon-lock"></i> [[topic:thread_tools.lock]]</a></li>
<li class="divider"></li>
<li><a href="#" id="move_thread"><i class="icon-move"></i> [[topic:thread_tools.move]]</a></li>
<li class="divider"></li>
<li><a href="#" id="delete_thread"><span class="text-error"><i class="icon-trash"></i> [[topic:thread_tools.delete]]</span></a></li>
</ul>
<div class="well col-md-11 col-xs-12 pull-right hide">
<div class="topic-main-buttons pull-right inline-block hide">
<div class="loading-indicator" done="0" style="display:none;">
Loading More Posts <i class="icon-refresh icon-spin"></i>
</div>
<button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button>
<div class="btn-group thread-tools hide">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="#" class="pin_thread"><i class="icon-pushpin"></i> [[topic:thread_tools.pin]]</a></li>
<li><a href="#" class="lock_thread"><i class="icon-lock"></i> [[topic:thread_tools.lock]]</a></li>
<li class="divider"></li>
<li><a href="#" class="move_thread"><i class="icon-move"></i> [[topic:thread_tools.move]]</a></li>
<li class="divider"></li>
<li><a href="#" class="delete_thread"><span class="text-error"><i class="icon-trash"></i> [[topic:thread_tools.delete]]</span></a></li>
</ul>
</div>
</div>
<div style="clear:both;"></div>
</div>
<div class="mobile-author-overlay">

View File

@@ -49,14 +49,17 @@
</span>
<span class="pull-right hidden-xs">
<!-- IF topics.unreplied -->
No one has replied
<!-- ELSE -->
<a href="/user/{topics.teaser_userslug}">
<img class="img-rounded teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
<img class="teaser-pic" src="{topics.teaser_userpicture}" title="{topics.teaser_username}"/>
</a>
<a href="../../topic/{topics.slug}#{topics.teaser_pid}">
replied
</a>
<span class="timeago" title="{topics.teaser_timestamp}"></span>
<!-- ENDIF topics.unreplied -->
</span>
</small>
</div>

View File

@@ -13,7 +13,7 @@
<div class="input-group">
<input class="form-control" id="search-user" type="text" placeholder="[[users:enter_username]]"/>
<span class="input-group-addon">
<span id="user-notfound-notify"><i class="icon icon-circle-blank"></i></span>
<span id="user-notfound-notify"><i class="icon icon-search"></i></span>
</span>
</div>
</div>

View File

@@ -0,0 +1,162 @@
.colorpicker {
width: 356px;
height: 176px;
overflow: hidden;
position: absolute;
background: url(./images/custom_background.png);
font-family: Arial, Helvetica, sans-serif;
display: none;
}
.colorpicker_color {
width: 150px;
height: 150px;
left: 14px;
top: 13px;
position: absolute;
background: #f00;
overflow: hidden;
cursor: crosshair;
}
.colorpicker_color div {
position: absolute;
top: 0;
left: 0;
width: 150px;
height: 150px;
background: url(./images/colorpicker_overlay.png);
}
.colorpicker_color div div {
position: absolute;
top: 0;
left: 0;
width: 11px;
height: 11px;
overflow: hidden;
background: url(./images/colorpicker_select.gif);
margin: -5px 0 0 -5px;
}
.colorpicker_hue {
position: absolute;
top: 13px;
left: 171px;
width: 35px;
height: 150px;
cursor: n-resize;
}
.colorpicker_hue div {
position: absolute;
width: 35px;
height: 9px;
overflow: hidden;
background: url(./images/custom_indic.gif) left top;
margin: -4px 0 0 0;
left: 0px;
}
.colorpicker_new_color {
position: absolute;
width: 60px;
height: 30px;
left: 213px;
top: 13px;
background: #f00;
}
.colorpicker_current_color {
position: absolute;
width: 60px;
height: 30px;
left: 283px;
top: 13px;
background: #f00;
}
.colorpicker input {
background-color: transparent;
border: 1px solid transparent;
position: absolute;
font-size: 10px;
font-family: Arial, Helvetica, sans-serif;
color: #898989;
top: 4px;
right: 11px;
text-align: right;
margin: 0;
padding: 0;
height: 11px;
}
.colorpicker_hex {
position: absolute;
width: 72px;
height: 22px;
background: url(./images/custom_hex.png) top;
left: 212px;
top: 142px;
}
.colorpicker_hex input {
right: 6px;
}
.colorpicker_field {
height: 22px;
width: 62px;
background-position: top;
position: absolute;
}
.colorpicker_field span {
position: absolute;
width: 12px;
height: 22px;
overflow: hidden;
top: 0;
right: 0;
cursor: n-resize;
}
.colorpicker_rgb_r {
background-image: url(./images/custom_rgb_r.png);
top: 52px;
left: 212px;
}
.colorpicker_rgb_g {
background-image: url(./images/custom_rgb_g.png);
top: 82px;
left: 212px;
}
.colorpicker_rgb_b {
background-image: url(./images/custom_rgb_b.png);
top: 112px;
left: 212px;
}
.colorpicker_hsb_h {
background-image: url(./images/custom_hsb_h.png);
top: 52px;
left: 282px;
}
.colorpicker_hsb_s {
background-image: url(./images/custom_hsb_s.png);
top: 82px;
left: 282px;
}
.colorpicker_hsb_b {
background-image: url(./images/custom_hsb_b.png);
top: 112px;
left: 282px;
}
.colorpicker_submit {
display: none;
position: absolute;
width: 22px;
height: 22px;
background: url(./images/custom_submit.png) top;
left: 322px;
top: 142px;
overflow: hidden;
}
.colorpicker_focus {
background-position: center;
}
.colorpicker_hex.colorpicker_focus {
background-position: bottom;
}
.colorpicker_submit.colorpicker_focus {
background-position: bottom;
}
.colorpicker_slider {
background-position: bottom;
}

484
public/vendor/colorpicker/colorpicker.js vendored Normal file
View File

@@ -0,0 +1,484 @@
/**
*
* Color picker
* Author: Stefan Petre www.eyecon.ro
*
* Dual licensed under the MIT and GPL licenses
*
*/
(function ($) {
var ColorPicker = function () {
var
ids = {},
inAction,
charMin = 65,
visible,
tpl = '<div class="colorpicker"><div class="colorpicker_color"><div><div></div></div></div><div class="colorpicker_hue"><div></div></div><div class="colorpicker_new_color"></div><div class="colorpicker_current_color"></div><div class="colorpicker_hex"><input type="text" maxlength="6" size="6" /></div><div class="colorpicker_rgb_r colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_g colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_h colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_s colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_submit"></div></div>',
defaults = {
eventName: 'click',
onShow: function () {},
onBeforeShow: function(){},
onHide: function () {},
onChange: function () {},
onSubmit: function () {},
color: 'ff0000',
livePreview: true,
flat: false
},
fillRGBFields = function (hsb, cal) {
var rgb = HSBToRGB(hsb);
$(cal).data('colorpicker').fields
.eq(1).val(rgb.r).end()
.eq(2).val(rgb.g).end()
.eq(3).val(rgb.b).end();
},
fillHSBFields = function (hsb, cal) {
$(cal).data('colorpicker').fields
.eq(4).val(hsb.h).end()
.eq(5).val(hsb.s).end()
.eq(6).val(hsb.b).end();
},
fillHexFields = function (hsb, cal) {
$(cal).data('colorpicker').fields
.eq(0).val(HSBToHex(hsb)).end();
},
setSelector = function (hsb, cal) {
$(cal).data('colorpicker').selector.css('backgroundColor', '#' + HSBToHex({h: hsb.h, s: 100, b: 100}));
$(cal).data('colorpicker').selectorIndic.css({
left: parseInt(150 * hsb.s/100, 10),
top: parseInt(150 * (100-hsb.b)/100, 10)
});
},
setHue = function (hsb, cal) {
$(cal).data('colorpicker').hue.css('top', parseInt(150 - 150 * hsb.h/360, 10));
},
setCurrentColor = function (hsb, cal) {
$(cal).data('colorpicker').currentColor.css('backgroundColor', '#' + HSBToHex(hsb));
},
setNewColor = function (hsb, cal) {
$(cal).data('colorpicker').newColor.css('backgroundColor', '#' + HSBToHex(hsb));
},
keyDown = function (ev) {
var pressedKey = ev.charCode || ev.keyCode || -1;
if ((pressedKey > charMin && pressedKey <= 90) || pressedKey == 32) {
return false;
}
var cal = $(this).parent().parent();
if (cal.data('colorpicker').livePreview === true) {
change.apply(this);
}
},
change = function (ev) {
var cal = $(this).parent().parent(), col;
if (this.parentNode.className.indexOf('_hex') > 0) {
cal.data('colorpicker').color = col = HexToHSB(fixHex(this.value));
} else if (this.parentNode.className.indexOf('_hsb') > 0) {
cal.data('colorpicker').color = col = fixHSB({
h: parseInt(cal.data('colorpicker').fields.eq(4).val(), 10),
s: parseInt(cal.data('colorpicker').fields.eq(5).val(), 10),
b: parseInt(cal.data('colorpicker').fields.eq(6).val(), 10)
});
} else {
cal.data('colorpicker').color = col = RGBToHSB(fixRGB({
r: parseInt(cal.data('colorpicker').fields.eq(1).val(), 10),
g: parseInt(cal.data('colorpicker').fields.eq(2).val(), 10),
b: parseInt(cal.data('colorpicker').fields.eq(3).val(), 10)
}));
}
if (ev) {
fillRGBFields(col, cal.get(0));
fillHexFields(col, cal.get(0));
fillHSBFields(col, cal.get(0));
}
setSelector(col, cal.get(0));
setHue(col, cal.get(0));
setNewColor(col, cal.get(0));
cal.data('colorpicker').onChange.apply(cal, [col, HSBToHex(col), HSBToRGB(col)]);
},
blur = function (ev) {
var cal = $(this).parent().parent();
cal.data('colorpicker').fields.parent().removeClass('colorpicker_focus');
},
focus = function () {
charMin = this.parentNode.className.indexOf('_hex') > 0 ? 70 : 65;
$(this).parent().parent().data('colorpicker').fields.parent().removeClass('colorpicker_focus');
$(this).parent().addClass('colorpicker_focus');
},
downIncrement = function (ev) {
var field = $(this).parent().find('input').focus();
var current = {
el: $(this).parent().addClass('colorpicker_slider'),
max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255),
y: ev.pageY,
field: field,
val: parseInt(field.val(), 10),
preview: $(this).parent().parent().data('colorpicker').livePreview
};
$(document).bind('mouseup', current, upIncrement);
$(document).bind('mousemove', current, moveIncrement);
},
moveIncrement = function (ev) {
ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val + ev.pageY - ev.data.y, 10))));
if (ev.data.preview) {
change.apply(ev.data.field.get(0), [true]);
}
return false;
},
upIncrement = function (ev) {
change.apply(ev.data.field.get(0), [true]);
ev.data.el.removeClass('colorpicker_slider').find('input').focus();
$(document).unbind('mouseup', upIncrement);
$(document).unbind('mousemove', moveIncrement);
return false;
},
downHue = function (ev) {
var current = {
cal: $(this).parent(),
y: $(this).offset().top
};
current.preview = current.cal.data('colorpicker').livePreview;
$(document).bind('mouseup', current, upHue);
$(document).bind('mousemove', current, moveHue);
},
moveHue = function (ev) {
change.apply(
ev.data.cal.data('colorpicker')
.fields
.eq(4)
.val(parseInt(360*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.y))))/150, 10))
.get(0),
[ev.data.preview]
);
return false;
},
upHue = function (ev) {
fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
$(document).unbind('mouseup', upHue);
$(document).unbind('mousemove', moveHue);
return false;
},
downSelector = function (ev) {
var current = {
cal: $(this).parent(),
pos: $(this).offset()
};
current.preview = current.cal.data('colorpicker').livePreview;
$(document).bind('mouseup', current, upSelector);
$(document).bind('mousemove', current, moveSelector);
},
moveSelector = function (ev) {
change.apply(
ev.data.cal.data('colorpicker')
.fields
.eq(6)
.val(parseInt(100*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.pos.top))))/150, 10))
.end()
.eq(5)
.val(parseInt(100*(Math.max(0,Math.min(150,(ev.pageX - ev.data.pos.left))))/150, 10))
.get(0),
[ev.data.preview]
);
return false;
},
upSelector = function (ev) {
fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
$(document).unbind('mouseup', upSelector);
$(document).unbind('mousemove', moveSelector);
return false;
},
enterSubmit = function (ev) {
$(this).addClass('colorpicker_focus');
},
leaveSubmit = function (ev) {
$(this).removeClass('colorpicker_focus');
},
clickSubmit = function (ev) {
var cal = $(this).parent();
var col = cal.data('colorpicker').color;
cal.data('colorpicker').origColor = col;
setCurrentColor(col, cal.get(0));
cal.data('colorpicker').onSubmit(col, HSBToHex(col), HSBToRGB(col), cal.data('colorpicker').el);
},
show = function (ev) {
var cal = $('#' + $(this).data('colorpickerId'));
cal.data('colorpicker').onBeforeShow.apply(this, [cal.get(0)]);
var pos = $(this).offset();
var viewPort = getViewport();
var top = pos.top + this.offsetHeight;
var left = pos.left;
if (top + 176 > viewPort.t + viewPort.h) {
top -= this.offsetHeight + 176;
}
if (left + 356 > viewPort.l + viewPort.w) {
left -= 356;
}
cal.css({left: left + 'px', top: top + 'px'});
if (cal.data('colorpicker').onShow.apply(this, [cal.get(0)]) != false) {
cal.show();
}
$(document).bind('mousedown', {cal: cal}, hide);
return false;
},
hide = function (ev) {
if (!isChildOf(ev.data.cal.get(0), ev.target, ev.data.cal.get(0))) {
if (ev.data.cal.data('colorpicker').onHide.apply(this, [ev.data.cal.get(0)]) != false) {
ev.data.cal.hide();
}
$(document).unbind('mousedown', hide);
}
},
isChildOf = function(parentEl, el, container) {
if (parentEl == el) {
return true;
}
if (parentEl.contains) {
return parentEl.contains(el);
}
if ( parentEl.compareDocumentPosition ) {
return !!(parentEl.compareDocumentPosition(el) & 16);
}
var prEl = el.parentNode;
while(prEl && prEl != container) {
if (prEl == parentEl)
return true;
prEl = prEl.parentNode;
}
return false;
},
getViewport = function () {
var m = document.compatMode == 'CSS1Compat';
return {
l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft),
t : window.pageYOffset || (m ? document.documentElement.scrollTop : document.body.scrollTop),
w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth),
h : window.innerHeight || (m ? document.documentElement.clientHeight : document.body.clientHeight)
};
},
fixHSB = function (hsb) {
return {
h: Math.min(360, Math.max(0, hsb.h)),
s: Math.min(100, Math.max(0, hsb.s)),
b: Math.min(100, Math.max(0, hsb.b))
};
},
fixRGB = function (rgb) {
return {
r: Math.min(255, Math.max(0, rgb.r)),
g: Math.min(255, Math.max(0, rgb.g)),
b: Math.min(255, Math.max(0, rgb.b))
};
},
fixHex = function (hex) {
var len = 6 - hex.length;
if (len > 0) {
var o = [];
for (var i=0; i<len; i++) {
o.push('0');
}
o.push(hex);
hex = o.join('');
}
return hex;
},
HexToRGB = function (hex) {
var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)};
},
HexToHSB = function (hex) {
return RGBToHSB(HexToRGB(hex));
},
RGBToHSB = function (rgb) {
var hsb = {
h: 0,
s: 0,
b: 0
};
var min = Math.min(rgb.r, rgb.g, rgb.b);
var max = Math.max(rgb.r, rgb.g, rgb.b);
var delta = max - min;
hsb.b = max;
if (max != 0) {
}
hsb.s = max != 0 ? 255 * delta / max : 0;
if (hsb.s != 0) {
if (rgb.r == max) {
hsb.h = (rgb.g - rgb.b) / delta;
} else if (rgb.g == max) {
hsb.h = 2 + (rgb.b - rgb.r) / delta;
} else {
hsb.h = 4 + (rgb.r - rgb.g) / delta;
}
} else {
hsb.h = -1;
}
hsb.h *= 60;
if (hsb.h < 0) {
hsb.h += 360;
}
hsb.s *= 100/255;
hsb.b *= 100/255;
return hsb;
},
HSBToRGB = function (hsb) {
var rgb = {};
var h = Math.round(hsb.h);
var s = Math.round(hsb.s*255/100);
var v = Math.round(hsb.b*255/100);
if(s == 0) {
rgb.r = rgb.g = rgb.b = v;
} else {
var t1 = v;
var t2 = (255-s)*v/255;
var t3 = (t1-t2)*(h%60)/60;
if(h==360) h = 0;
if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3}
else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3}
else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3}
else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3}
else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3}
else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3}
else {rgb.r=0; rgb.g=0; rgb.b=0}
}
return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)};
},
RGBToHex = function (rgb) {
var hex = [
rgb.r.toString(16),
rgb.g.toString(16),
rgb.b.toString(16)
];
$.each(hex, function (nr, val) {
if (val.length == 1) {
hex[nr] = '0' + val;
}
});
return hex.join('');
},
HSBToHex = function (hsb) {
return RGBToHex(HSBToRGB(hsb));
},
restoreOriginal = function () {
var cal = $(this).parent();
var col = cal.data('colorpicker').origColor;
cal.data('colorpicker').color = col;
fillRGBFields(col, cal.get(0));
fillHexFields(col, cal.get(0));
fillHSBFields(col, cal.get(0));
setSelector(col, cal.get(0));
setHue(col, cal.get(0));
setNewColor(col, cal.get(0));
};
return {
init: function (opt) {
opt = $.extend({}, defaults, opt||{});
if (typeof opt.color == 'string') {
opt.color = HexToHSB(opt.color);
} else if (opt.color.r != undefined && opt.color.g != undefined && opt.color.b != undefined) {
opt.color = RGBToHSB(opt.color);
} else if (opt.color.h != undefined && opt.color.s != undefined && opt.color.b != undefined) {
opt.color = fixHSB(opt.color);
} else {
return this;
}
return this.each(function () {
if (!$(this).data('colorpickerId')) {
var options = $.extend({}, opt);
options.origColor = opt.color;
var id = 'collorpicker_' + parseInt(Math.random() * 1000);
$(this).data('colorpickerId', id);
var cal = $(tpl).attr('id', id);
if (options.flat) {
cal.appendTo(this).show();
} else {
cal.appendTo(document.body);
}
options.fields = cal
.find('input')
.bind('keyup', keyDown)
.bind('change', change)
.bind('blur', blur)
.bind('focus', focus);
cal
.find('span').bind('mousedown', downIncrement).end()
.find('>div.colorpicker_current_color').bind('click', restoreOriginal);
options.selector = cal.find('div.colorpicker_color').bind('mousedown', downSelector);
options.selectorIndic = options.selector.find('div div');
options.el = this;
options.hue = cal.find('div.colorpicker_hue div');
cal.find('div.colorpicker_hue').bind('mousedown', downHue);
options.newColor = cal.find('div.colorpicker_new_color');
options.currentColor = cal.find('div.colorpicker_current_color');
cal.data('colorpicker', options);
cal.find('div.colorpicker_submit')
.bind('mouseenter', enterSubmit)
.bind('mouseleave', leaveSubmit)
.bind('click', clickSubmit);
fillRGBFields(options.color, cal.get(0));
fillHSBFields(options.color, cal.get(0));
fillHexFields(options.color, cal.get(0));
setHue(options.color, cal.get(0));
setSelector(options.color, cal.get(0));
setCurrentColor(options.color, cal.get(0));
setNewColor(options.color, cal.get(0));
if (options.flat) {
cal.css({
position: 'relative',
display: 'block'
});
} else {
$(this).bind(options.eventName, show);
}
}
});
},
showPicker: function() {
return this.each( function () {
if ($(this).data('colorpickerId')) {
show.apply(this);
}
});
},
hidePicker: function() {
return this.each( function () {
if ($(this).data('colorpickerId')) {
$('#' + $(this).data('colorpickerId')).hide();
}
});
},
setColor: function(col) {
if (typeof col == 'string') {
col = HexToHSB(col);
} else if (col.r != undefined && col.g != undefined && col.b != undefined) {
col = RGBToHSB(col);
} else if (col.h != undefined && col.s != undefined && col.b != undefined) {
col = fixHSB(col);
} else {
return this;
}
return this.each(function(){
if ($(this).data('colorpickerId')) {
var cal = $('#' + $(this).data('colorpickerId'));
cal.data('colorpicker').color = col;
cal.data('colorpicker').origColor = col;
fillRGBFields(col, cal.get(0));
fillHSBFields(col, cal.get(0));
fillHexFields(col, cal.get(0));
setHue(col, cal.get(0));
setSelector(col, cal.get(0));
setCurrentColor(col, cal.get(0));
setNewColor(col, cal.get(0));
}
});
}
};
}();
$.fn.extend({
ColorPicker: ColorPicker.init,
ColorPickerHide: ColorPicker.hidePicker,
ColorPickerShow: ColorPicker.showPicker,
ColorPickerSetColor: ColorPicker.setColor
});
})(jQuery)

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

View File

@@ -252,26 +252,28 @@ var RDB = require('./redis.js'),
Categories.moveRecentReplies = function(tid, oldCid, cid, callback) {
function movePost(pid, callback) {
posts.getPostField(pid, 'timestamp', function(timestamp) {
posts.getPostField(pid, 'timestamp', function(err, timestamp) {
if(err) {
return callback(err);
}
RDB.zrem('categories:recent_posts:cid:' + oldCid, pid);
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
callback(null);
});
}
topics.getPids(tid, function(err, pids) {
if (!err) {
async.each(pids, movePost, function(err) {
if (!err) {
callback(null, 1);
} else {
winston.err(err);
callback(err, null);
}
});
} else {
winston.err(err);
callback(err, null);
if(err) {
return callback(err, null);
}
async.each(pids, movePost, function(err) {
if(err) {
return callback(err, null);
}
callback(null, 1);
});
});
};
@@ -372,19 +374,6 @@ var RDB = require('./redis.js'),
return callback(err, null);
}
function getPostCategory(pid, callback) {
posts.getPostField(pid, 'tid', function(tid) {
topics.getTopicField(tid, 'cid', function(err, postCid) {
if (err) {
return callback(err, null);
}
return callback(null, postCid);
});
});
}
var index = 0,
active = false;
@@ -393,7 +382,7 @@ var RDB = require('./redis.js'),
return active === false && index < pids.length;
},
function(callback) {
getPostCategory(pids[index], function(err, postCid) {
posts.getCidByPid(pids[index], function(err, postCid) {
if (err) {
return callback(err);
}
@@ -411,7 +400,6 @@ var RDB = require('./redis.js'),
return callback(err, null);
}
callback(null, active);
}
);

View File

@@ -21,7 +21,7 @@ var RDB = require('./redis.js'),
return;
}
posts.getPostFields(pid, ['uid', 'timestamp'], function (postData) {
posts.getPostFields(pid, ['uid', 'timestamp'], function (err, postData) {
Favourites.hasFavourited(pid, uid, function (hasFavourited) {
if (hasFavourited === 0) {
@@ -57,7 +57,7 @@ var RDB = require('./redis.js'),
return;
}
posts.getPostField(pid, 'uid', function (uid_of_poster) {
posts.getPostField(pid, 'uid', function (err, uid_of_poster) {
Favourites.hasFavourited(pid, uid, function (hasFavourited) {
if (hasFavourited === 1) {
RDB.srem('pid:' + pid + ':users_favourited', uid);

View File

@@ -2,12 +2,14 @@
var RDB = require('./redis.js'),
posts = require('./posts.js'),
topics = require('./topics.js'),
categories = require('./categories'),
fs = require('fs'),
rss = require('rss'),
winston = require('winston'),
path = require('path'),
nconf = require('nconf'),
categories = require('./categories');
async = require('async');
Feed.defaults = {
ttl: 60,
@@ -26,43 +28,43 @@
}
Feed.updateTopic = function (tid, callback) {
if (process.env.NODE_ENV === 'development') winston.info('[rss] Updating RSS feeds for topic ' + tid);
topics.getTopicWithPosts(tid, 0, 0, -1, function (err, topicData) {
if (err) return callback(new Error('topic-invalid'));
var feed = new rss({
title: topicData.topic_name,
description: topicData.main_posts[0].content,
feed_url: Feed.defaults.baseUrl + '/topics/' + tid + '.rss',
site_url: nconf.get('url') + 'topic/' + topicData.slug,
image_url: topicData.main_posts[0].picture,
author: topicData.main_posts[0].username,
ttl: Feed.defaults.ttl
}),
title: topicData.topic_name,
description: topicData.main_posts[0].content,
feed_url: Feed.defaults.baseUrl + '/topics/' + tid + '.rss',
site_url: nconf.get('url') + 'topic/' + topicData.slug,
image_url: topicData.main_posts[0].picture,
author: topicData.main_posts[0].username,
ttl: Feed.defaults.ttl
}),
topic_posts = topicData.main_posts.concat(topicData.posts),
title, postData, dateStamp;
dateStamp;
// Add pubDate if topic contains posts
if (topicData.main_posts.length > 0) feed.pubDate = new Date(parseInt(topicData.main_posts[0].timestamp, 10)).toUTCString();
for (var i = 0, ii = topic_posts.length; i < ii; i++) {
if (topic_posts[i].deleted === '0') {
postData = topic_posts[i];
async.each(topic_posts, function(postData, next) {
if (postData.deleted === '0') {
dateStamp = new Date(parseInt(postData.edited === '0' ? postData.timestamp : postData.edited, 10)).toUTCString();
title = 'Reply to ' + topicData.topic_name + ' on ' + dateStamp;
feed.item({
title: title,
title: 'Reply to ' + topicData.topic_name + ' on ' + dateStamp,
description: postData.content,
url: nconf.get('url') + 'topic/' + topicData.slug + '#' + postData.pid,
author: postData.username,
date: dateStamp
});
}
}
Feed.saveFeed('feeds/topics/' + tid + '.rss', feed, function (err) {
next();
}, function() {
if (process.env.NODE_ENV === 'development') {
winston.info('[rss] Re-generated RSS Feed for tid ' + tid + '.');
}
if (callback) callback();
});
});
@@ -70,40 +72,75 @@
};
Feed.updateCategory = function (cid, callback) {
if (process.env.NODE_ENV === 'development') winston.info('[rss] Updating RSS feeds for category ' + cid);
categories.getCategoryById(cid, 0, function (err, categoryData) {
if (err) return callback(new Error('category-invalid'));
var feed = new rss({
title: categoryData.category_name,
description: categoryData.category_description,
feed_url: Feed.defaults.baseUrl + '/categories/' + cid + '.rss',
site_url: nconf.get('url') + 'category/' + categoryData.category_id,
ttl: Feed.defaults.ttl
}),
topics = categoryData.topics,
title, topicData, dateStamp;
title: categoryData.category_name,
description: categoryData.category_description,
feed_url: Feed.defaults.baseUrl + '/categories/' + cid + '.rss',
site_url: nconf.get('url') + 'category/' + categoryData.category_id,
ttl: Feed.defaults.ttl
});
// Add pubDate if category has topics
if (categoryData.topics.length > 0) feed.pubDate = new Date(parseInt(categoryData.topics[0].lastposttime, 10)).toUTCString();
for (var i = 0, ii = topics.length; i < ii; i++) {
topicData = topics[i];
dateStamp = new Date(parseInt(topicData.lastposttime, 10)).toUTCString();
title = topics[i].title;
async.eachSeries(categoryData.topics, function(topicData, next) {
feed.item({
title: title,
title: topicData.title,
url: nconf.get('url') + 'topic/' + topicData.slug,
author: topicData.username,
date: dateStamp
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
});
}
Feed.saveFeed('feeds/categories/' + cid + '.rss', feed, function (err) {
if (callback) callback();
next();
}, function() {
Feed.saveFeed('feeds/categories/' + cid + '.rss', feed, function (err) {
if (process.env.NODE_ENV === 'development') {
winston.info('[rss] Re-generated RSS Feed for cid ' + cid + '.');
}
if (callback) callback();
});
});
});
};
Feed.updateRecent = function(callback) {
console.log('entered');
if (process.env.NODE_ENV === 'development') winston.info('[rss] Updating Recent Posts RSS feed');
topics.getLatestTopics(0, 0, 19, undefined, function (err, recentData) {
var feed = new rss({
title: 'Recently Active Topics',
description: 'A list of topics that have been active within the past 24 hours',
feed_url: Feed.defaults.baseUrl + '/recent.rss',
site_url: nconf.get('url') + 'recent',
ttl: Feed.defaults.ttl
});
// Add pubDate if recent topics list contains topics
if (recentData.topics.length > 0) {
feed.pubDate = new Date(parseInt(recentData.topics[0].lastposttime, 10)).toUTCString();
}
async.eachSeries(recentData.topics, function(topicData, next) {
feed.item({
title: topicData.title,
url: nconf.get('url') + 'topic/' + topicData.slug,
author: topicData.username,
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
});
next();
}, function() {
Feed.saveFeed('feeds/recent.rss', feed, function (err) {
if (process.env.NODE_ENV === 'development') {
winston.info('[rss] Re-generated "recent posts" RSS Feed.');
}
if (callback) callback();
});
});
});
};
}(exports));

View File

@@ -56,6 +56,8 @@
results.base.count = results.users.length;
results.base.members = results.users;
results.base.deletable = (results.base.gid !== '1');
callback(err, results.base);
});
},
@@ -103,7 +105,9 @@
});
},
destroy: function (gid, callback) {
RDB.hset('gid:' + gid, 'deleted', '1', callback);
if (gid !== 1) {
RDB.hset('gid:' + gid, 'deleted', '1', callback);
}
},
join: function (gid, uid, callback) {
RDB.sadd('gid:' + gid + ':members', uid, callback);

View File

@@ -1,12 +1,11 @@
var request = require('request');
var request = require('request'),
winston = require('winston');
(function (imgur) {
"use strict";
var clientID = '';
imgur.upload = function (image, type, callback) {
imgur.upload = function (clientID, image, type, callback) {
var options = {
url: 'https://api.imgur.com/3/upload.json',
headers: {
@@ -15,21 +14,27 @@ var request = require('request');
};
var post = request.post(options, function (err, req, body) {
if(err) {
return callback(err, null);
}
try {
callback(err, JSON.parse(body));
} catch (e) {
callback(err, body);
var response = JSON.parse(body);
if(response.success) {
callback(null, response.data);
} else {
callback(new Error(response.data.error.message), null);
}
} catch(e) {
winston.error('Unable to parse Imgur json response. [' + body +']');
callback(e, null);
}
});
var upload = post.form({
post.form({
type: type,
image: image
});
};
imgur.setClientID = function (id) {
clientID = id;
};
}(exports));

View File

@@ -63,13 +63,14 @@ var async = require('async'),
}
if (setupVal && setupVal instanceof Object) {
if (setupVal['admin:username'] && setupVal['admin:password'] && setupVal['admin:email']) {
if (setupVal['admin:username'] && setupVal['admin:password'] && setupVal['admin:password:confirm'] && setupVal['admin:email']) {
install.values = setupVal;
next();
} else {
winston.error('Required values are missing for automated setup:');
if (!setupVal['admin:username']) winston.error(' admin:username');
if (!setupVal['admin:password']) winston.error(' admin:password');
if (!setupVal['admin:password:confirm']) winston.error(' admin:password:confirm');
if (!setupVal['admin:email']) winston.error(' admin:email');
process.exit();
}
@@ -252,6 +253,11 @@ var async = require('async'),
}
});
}, next);
},
function (next) {
// Upgrading schema
var Upgrade = require('./upgrade');
Upgrade.upgrade(next);
}
], function (err) {
if (err) {
@@ -277,18 +283,32 @@ var async = require('async'),
description: 'Administrator email address',
pattern: /.+@.+/,
required: true
}, {
}],
passwordQuestions = [{
name: 'password',
description: 'Password',
required: true,
hidden: true,
type: 'string'
}, {
name: 'password:confirm',
description: 'Confirm Password',
required: true,
hidden: true,
type: 'string'
}],
success = function(err, results) {
if (!results) {
return callback(new Error('aborted'));
}
// Check if the passwords match
if (results['password:confirm'] !== results.password) {
winston.warn("Passwords did not match, please try again");
// Re-prompt password questions.
return retryPassword(results);
}
nconf.set('bcrypt_rounds', 12);
User.create(results.username, results.password, results.email, function (err, uid) {
if (err) {
@@ -306,14 +326,33 @@ var async = require('async'),
}
});
});
},
retryPassword = function (originalResults) {
// Ask only the password questions
prompt.get(passwordQuestions, function (err, results) {
if (!results) {
return callback(new Error('aborted'));
}
// Update the original data with newly collected password
originalResults.password = results.password;
originalResults['password:confirm'] = results['password:confirm'];
// Send back to success to handle
success(err, originalResults);
});
};
// Add the password questions
questions = questions.concat(passwordQuestions);
if (!install.values) prompt.get(questions, success);
else {
var results = {
username: install.values['admin:username'],
email: install.values['admin:email'],
password: install.values['admin:password']
password: install.values['admin:password'],
'password:confirm': install.values['admin:password:confirm']
};
success(null, results);

View File

@@ -1,5 +1,6 @@
var utils = require('./../public/src/utils.js'),
RDB = require('./redis.js'),
plugins = require('./plugins'),
async = require('async'),
path = require('path'),
fs = require('fs'),
@@ -102,7 +103,9 @@ var utils = require('./../public/src/utils.js'),
var themeData = {
'theme:type': data.type,
'theme:id': data.id,
'theme:staticDir': ''
'theme:staticDir': '',
'theme:templates': '',
'theme:src': ''
};
switch(data.type) {
@@ -205,44 +208,46 @@ var utils = require('./../public/src/utils.js'),
],
minFile: path.join(__dirname, '..', 'public/src/nodebb.min.js'),
get: function (callback) {
var mtime,
jsPaths = this.scripts.map(function (jsPath) {
return path.join(__dirname, '..', '/public', jsPath);
});
plugins.fireHook('filter:scripts.get', this.scripts, function(err, scripts) {
var mtime,
jsPaths = scripts.map(function (jsPath) {
return path.join(__dirname, '..', '/public', jsPath);
});
if (process.env.NODE_ENV !== 'development') {
async.parallel({
mtime: function (next) {
async.map(jsPaths, fs.stat, function (err, stats) {
async.reduce(stats, 0, function (memo, item, callback) {
mtime = +new Date(item.mtime);
callback(null, mtime > memo ? mtime : memo);
}, next);
});
},
minFile: function (next) {
if (!fs.existsSync(Meta.js.minFile)) {
if (process.env.NODE_ENV === 'development') winston.warn('No minified client-side library found');
return next(null, 0);
if (process.env.NODE_ENV !== 'development') {
async.parallel({
mtime: function (next) {
async.map(jsPaths, fs.stat, function (err, stats) {
async.reduce(stats, 0, function (memo, item, callback) {
mtime = +new Date(item.mtime);
callback(null, mtime > memo ? mtime : memo);
}, next);
});
},
minFile: function (next) {
if (!fs.existsSync(Meta.js.minFile)) {
if (process.env.NODE_ENV === 'development') winston.warn('No minified client-side library found');
return next(null, 0);
}
fs.stat(Meta.js.minFile, function (err, stat) {
next(err, +new Date(stat.mtime));
});
}
fs.stat(Meta.js.minFile, function (err, stat) {
next(err, +new Date(stat.mtime));
});
}
}, function (err, results) {
if (results.minFile > results.mtime) {
if (process.env.NODE_ENV === 'development') winston.info('No changes to client-side libraries -- skipping minification');
callback(null, [path.relative(path.join(__dirname, '../public'), Meta.js.minFile)]);
} else {
Meta.js.minify(function () {
}, function (err, results) {
if (results.minFile > results.mtime) {
if (process.env.NODE_ENV === 'development') winston.info('No changes to client-side libraries -- skipping minification');
callback(null, [path.relative(path.join(__dirname, '../public'), Meta.js.minFile)]);
});
}
});
} else {
callback(null, this.scripts);
}
} else {
Meta.js.minify(function () {
callback(null, [path.relative(path.join(__dirname, '../public'), Meta.js.minFile)]);
});
}
});
} else {
callback(null, scripts);
}
});
},
minify: function (callback) {
var uglifyjs = require('uglify-js'),

View File

@@ -284,7 +284,7 @@ var fs = require('fs'),
winston.warn("Plugin: " + file + " is corrupted or invalid. Please check plugin.json for errors.")
return next(err, null);
}
_self.isActive(config.id, function(err, active) {
if (err) next(new Error('no-active-state'));

View File

@@ -34,7 +34,7 @@ var RDB = require('./redis.js'),
}
function getThreadPrivileges(next) {
posts.getPostField(pid, 'tid', function(tid) {
posts.getPostField(pid, 'tid', function(err, tid) {
threadTools.privileges(tid, uid, function(privileges) {
next(null, privileges);
});
@@ -42,7 +42,7 @@ var RDB = require('./redis.js'),
}
function isOwnPost(next) {
posts.getPostField(pid, 'uid', function(author) {
posts.getPostField(pid, 'uid', function(err, author) {
next(null, parseInt(author, 10) === parseInt(uid, 10));
});
}
@@ -87,7 +87,7 @@ var RDB = require('./redis.js'),
async.parallel([
function(next) {
posts.getPostField(pid, 'tid', function(tid) {
posts.getPostField(pid, 'tid', function(err, tid) {
PostTools.isMain(pid, tid, function(isMainPost) {
if (isMainPost) {
topics.setTopicField(tid, 'title', title);
@@ -132,41 +132,47 @@ var RDB = require('./redis.js'),
RDB.decr('totalpostcount');
postSearch.remove(pid);
posts.getPostFields(pid, ['tid', 'uid'], function(postData) {
posts.getPostFields(pid, ['tid', 'uid'], function(err, postData) {
RDB.hincrby('topic:' + postData.tid, 'postcount', -1);
user.decrementUserFieldBy(postData.uid, 'postcount', 1, function(err, postcount) {
RDB.zadd('users:postcount', postcount, postData.uid);
});
io.sockets. in ('topic_' + postData.tid).emit('event:post_deleted', {
pid: pid
});
// Delete the thread if it is the last undeleted post
threadTools.getLatestUndeletedPid(postData.tid, function(err, pid) {
if (err && err.message === 'no-undeleted-pids-found') {
threadTools.delete(postData.tid, -1, function(err) {
if (err) winston.error('Could not delete topic (tid: ' + postData.tid + ')', err.stack);
if (err) {
winston.error('Could not delete topic (tid: ' + postData.tid + ')', err.stack);
}
});
} else {
posts.getPostField(pid, 'timestamp', function(timestamp) {
posts.getPostField(pid, 'timestamp', function(err, timestamp) {
topics.updateTimestamp(postData.tid, timestamp);
});
}
});
Feed.updateTopic(postData.tid);
Feed.updateRecent();
callback();
callback(null);
});
};
PostTools.privileges(pid, uid, function(privileges) {
if (privileges.editable) {
success();
posts.getPostField(pid, 'deleted', function(err, deleted) {
if(deleted === '1') {
return callback(new Error('Post already deleted!'));
}
PostTools.privileges(pid, uid, function(privileges) {
if (privileges.editable) {
success();
}
});
});
}
PostTools.restore = function(uid, pid, callback) {
@@ -174,17 +180,13 @@ var RDB = require('./redis.js'),
posts.setPostField(pid, 'deleted', 0);
RDB.incr('totalpostcount');
posts.getPostFields(pid, ['tid', 'uid', 'content'], function(postData) {
posts.getPostFields(pid, ['tid', 'uid', 'content'], function(err, postData) {
RDB.hincrby('topic:' + postData.tid, 'postcount', 1);
user.incrementUserFieldBy(postData.uid, 'postcount', 1);
io.sockets. in ('topic_' + postData.tid).emit('event:post_restored', {
pid: pid
});
threadTools.getLatestUndeletedPid(postData.tid, function(err, pid) {
posts.getPostField(pid, 'timestamp', function(timestamp) {
posts.getPostField(pid, 'timestamp', function(err, timestamp) {
topics.updateTimestamp(postData.tid, timestamp);
});
});
@@ -197,6 +199,7 @@ var RDB = require('./redis.js'),
});
Feed.updateTopic(postData.tid);
Feed.updateRecent();
postSearch.index(postData.content, pid);
@@ -204,10 +207,16 @@ var RDB = require('./redis.js'),
});
};
PostTools.privileges(pid, uid, function(privileges) {
if (privileges.editable) {
success();
}
posts.getPostField(pid, 'deleted', function(err, deleted) {
if(deleted === '0') {
return callback(new Error('Post already restored'));
}
PostTools.privileges(pid, uid, function(privileges) {
if (privileges.editable) {
success();
}
});
});
}

View File

@@ -2,6 +2,7 @@ var RDB = require('./redis.js'),
utils = require('./../public/src/utils.js'),
user = require('./user.js'),
topics = require('./topics.js'),
categories = require('./categories.js'),
favourites = require('./favourites.js'),
threadTools = require('./threadTools.js'),
postTools = require('./postTools'),
@@ -13,11 +14,183 @@ var RDB = require('./redis.js'),
postSearch = reds.createSearch('nodebbpostsearch'),
nconf = require('nconf'),
meta = require('./meta.js'),
validator = require('validator'),
winston = require('winston');
(function(Posts) {
var customUserInfo = {};
Posts.create = function(uid, tid, content, callback) {
if (uid === null) {
callback(new Error('invalid-user'), null);
return;
}
topics.isLocked(tid, function(err, locked) {
if(err) {
return callback(err, null);
} else if(locked) {
callback(new Error('topic-locked'), null);
}
RDB.incr('global:next_post_id', function(err, pid) {
if(err) {
return callback(err, null);
}
plugins.fireHook('filter:post.save', content, function(err, newContent) {
if(err) {
return callback(err, null);
}
content = newContent;
var timestamp = Date.now(),
postData = {
'pid': pid,
'uid': uid,
'tid': tid,
'content': content,
'timestamp': timestamp,
'reputation': 0,
'editor': '',
'edited': 0,
'deleted': 0,
'fav_button_class': '',
'fav_star_class': 'icon-star-empty',
'show_banned': 'hide',
'relativeTime': new Date(timestamp).toISOString(),
'post_rep': '0',
'edited-class': 'none',
'relativeEditTime': ''
};
RDB.hmset('post:' + pid, postData);
topics.addPostToTopic(tid, pid);
topics.increasePostCount(tid);
topics.updateTimestamp(tid, timestamp);
RDB.incr('totalpostcount');
topics.getTopicFields(tid, ['cid', 'pinned'], function(err, topicData) {
RDB.handle(err);
var cid = topicData.cid;
feed.updateTopic(tid);
feed.updateRecent();
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
if(topicData.pinned === '0')
RDB.zadd('categories:' + cid + ':tid', timestamp, tid);
RDB.scard('cid:' + cid + ':active_users', function(err, amount) {
if (amount > 16) {
RDB.spop('cid:' + cid + ':active_users');
}
categories.addActiveUser(cid, uid);
});
});
user.onNewPostMade(uid, tid, pid, timestamp);
plugins.fireHook('filter:post.get', postData, function(err, newPostData) {
if(err) {
return callback(err, null);
}
postData = newPostData;
postTools.parse(postData.content, function(err, content) {
if(err) {
return callback(err, null);
}
postData.content = content;
plugins.fireHook('action:post.save', postData);
postSearch.index(content, pid);
callback(null, postData);
});
});
});
});
});
};
Posts.reply = function(tid, uid, content, callback) {
if(content) {
content = content.trim();
}
if (!content || content.length < meta.config.minimumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
Posts.create(uid, tid, content, function(err, postData) {
if(err) {
return callback(err, null);
} else if(!postData) {
callback(new Error('reply-error'), null);
}
async.parallel([
function(next) {
topics.markUnRead(tid, function(err) {
if(err) {
return next(err);
}
topics.markAsRead(tid, uid);
next();
});
},
function(next) {
Posts.getCidByPid(postData.pid, function(err, cid) {
if(err) {
return next(err);
}
RDB.del('cid:' + cid + ':read_by_uid');
next();
});
},
function(next) {
threadTools.notifyFollowers(tid, uid);
next();
},
function(next) {
Posts.addUserInfoToPost(postData, function(err) {
if(err) {
return next(err);
}
var socketData = {
posts: [postData]
};
io.sockets.in('topic_' + tid).emit('event:new_post', socketData);
io.sockets.in('recent_posts').emit('event:new_post', socketData);
io.sockets.in('user/' + uid).emit('event:new_post', socketData);
next();
});
}
], function(err, results) {
if(err) {
return callback(err, null);
}
callback(null, 'Reply successful');
});
});
}
Posts.getPostsByTid = function(tid, start, end, callback) {
RDB.lrange('tid:' + tid + ':posts', start, end, function(err, pids) {
RDB.handle(err);
@@ -86,7 +259,7 @@ var RDB = require('./redis.js'),
function getPostSummary(pid, callback) {
async.waterfall([
function(next) {
Posts.getPostFields(pid, ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted'], function(postData) {
Posts.getPostFields(pid, ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted'], function(err, postData) {
if (postData.deleted === '1') return callback(null);
else {
postData.relativeTime = new Date(parseInt(postData.timestamp || 0, 10)).toISOString();
@@ -100,12 +273,15 @@ var RDB = require('./redis.js'),
});
},
function(postData, next) {
topics.getTopicFields(postData.tid, ['slug', 'deleted'], function(err, topicData) {
topics.getTopicFields(postData.tid, ['title', 'cid', 'slug', 'deleted'], function(err, topicData) {
if (err) return callback(err);
else if (topicData.deleted === '1') return callback(null);
postData.topicSlug = topicData.slug;
next(null, postData);
categories.getCategoryField(topicData.cid, 'name', function(err, categoryData) {
postData.category_name = categoryData;
postData.title = validator.sanitize(topicData.title).escape();
postData.topicSlug = topicData.slug;
next(null, postData);
})
});
},
function(postData, next) {
@@ -131,51 +307,48 @@ var RDB = require('./redis.js'),
});
};
// TODO: this function is never called except from some debug route. clean up?
Posts.getPostData = function(pid, callback) {
RDB.hgetall('post:' + pid, function(err, data) {
if (err === null) {
plugins.fireHook('filter:post.get', data, function(err, newData) {
if (!err) callback(newData);
else callback(data);
});
} else {
winston.error(err);
if(err) {
return callback(err, null);
}
plugins.fireHook('filter:post.get', data, function(err, newData) {
if(err) {
return callback(err, null);
}
callback(null, newData);
});
});
}
Posts.getPostFields = function(pid, fields, callback) {
RDB.hmgetObject('post:' + pid, fields, function(err, data) {
if (err === null) {
// TODO: I think the plugins system needs an optional 'parameters' paramter so I don't have to do this:
data = data || {};
data.pid = pid;
data.fields = fields;
plugins.fireHook('filter:post.getFields', data, function(err, data) {
callback(data);
});
} else {
console.log(err);
if(err) {
return callback(err, null);
}
// TODO: I think the plugins system needs an optional 'parameters' paramter so I don't have to do this:
data = data || {};
data.pid = pid;
data.fields = fields;
plugins.fireHook('filter:post.getFields', data, function(err, data) {
if(err) {
return callback(err, null);
}
callback(null, data);
});
});
}
Posts.getPostField = function(pid, field, callback) {
RDB.hget('post:' + pid, field, function(err, data) {
if (err === null) {
// TODO: I think the plugins system needs an optional 'parameters' paramter so I don't have to do this:
data = data || {};
data.pid = pid;
data.field = field;
plugins.fireHook('filter:post.getField', data, function(err, data) {
callback(data);
});
} else {
console.log(err);
Posts.getPostFields(pid, [field], function(err, data) {
if(err) {
return callback(err, null);
}
callback(null, data[field]);
});
}
@@ -192,8 +365,8 @@ var RDB = require('./redis.js'),
var posts = [],
multi = RDB.multi();
for(var x=0,numPids=pids.length;x<numPids;x++) {
multi.hgetall("post:"+pids[x]);
for(var x=0, numPids=pids.length; x<numPids; x++) {
multi.hgetall("post:" + pids[x]);
}
multi.exec(function (err, replies) {
@@ -204,12 +377,11 @@ var RDB = require('./redis.js'),
postData['edited-class'] = postData.editor !== '' ? '' : 'none';
try {
postData.relativeTime = new Date(parseInt(postData.timestamp,10)).toISOString();
postData['relativeEditTime'] = postData.edited !== '0' ? (new Date(parseInt(postData.edited,10)).toISOString()) : '';
postData.relativeEditTime = postData.edited !== '0' ? (new Date(parseInt(postData.edited,10)).toISOString()) : '';
} catch(e) {
winston.err('invalid time value');
}
if (postData.uploadedImages) {
try {
postData.uploadedImages = JSON.parse(postData.uploadedImages);
@@ -238,17 +410,23 @@ var RDB = require('./redis.js'),
})
}
Posts.get_cid_by_pid = function(pid, callback) {
Posts.getPostField(pid, 'tid', function(tid) {
if (tid) {
topics.getTopicField(tid, 'cid', function(err, cid) {
if (cid) {
callback(cid);
} else {
callback(false);
}
});
Posts.getCidByPid = function(pid, callback) {
Posts.getPostField(pid, 'tid', function(err, tid) {
if(err) {
return callback(err, null);
}
topics.getTopicField(tid, 'cid', function(err, cid) {
if(err) {
return callback(err, null);
}
if (cid) {
callback(null, cid);
} else {
callback(new Error('invalid-category-id'), null);
}
});
});
}
@@ -271,157 +449,19 @@ var RDB = require('./redis.js'),
});
}
Posts.reply = function(tid, uid, content, callback) {
if(content) {
content = content.trim();
}
if (!content || content.length < meta.config.minimumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
Posts.create(uid, tid, content, function(postData) {
if (postData) {
topics.markUnRead(tid);
Posts.get_cid_by_pid(postData.pid, function(cid) {
RDB.del('cid:' + cid + ':read_by_uid', function(err, data) {
topics.markAsRead(tid, uid);
});
});
threadTools.notifyFollowers(tid, uid);
Posts.addUserInfoToPost(postData, function() {
var socketData = {
posts: [postData]
};
io.sockets.in('topic_' + tid).emit('event:new_post', socketData);
io.sockets.in('recent_posts').emit('event:new_post', socketData);
io.sockets.in('user/' + uid).emit('event:new_post', socketData);
});
callback(null, 'Reply successful');
} else {
callback(new Error('reply-error'), null);
}
});
}
Posts.create = function(uid, tid, content, callback) {
if (uid === null) {
callback(null);
return;
}
topics.isLocked(tid, function(locked) {
if (!locked || locked === '0') {
RDB.incr('global:next_post_id', function(err, pid) {
RDB.handle(err);
plugins.fireHook('filter:post.save', content, function(err, newContent) {
if (!err) content = newContent;
var timestamp = Date.now(),
postData = {
'pid': pid,
'uid': uid,
'tid': tid,
'content': content,
'timestamp': timestamp,
'reputation': 0,
'editor': '',
'edited': 0,
'deleted': 0,
'fav_button_class': '',
'fav_star_class': 'icon-star-empty',
'show_banned': 'hide',
'relativeTime': new Date(timestamp).toISOString(),
'post_rep': '0',
'edited-class': 'none',
'relativeEditTime': ''
};
RDB.hmset('post:' + pid, postData);
topics.addPostToTopic(tid, pid);
topics.increasePostCount(tid);
topics.updateTimestamp(tid, timestamp);
RDB.incr('totalpostcount');
topics.getTopicFields(tid, ['cid', 'pinned'], function(err, topicData) {
RDB.handle(err);
var cid = topicData.cid;
feed.updateTopic(tid);
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
if(topicData.pinned === '0')
RDB.zadd('categories:' + cid + ':tid', timestamp, tid);
RDB.scard('cid:' + cid + ':active_users', function(err, amount) {
if (amount > 10) {
RDB.spop('cid:' + cid + ':active_users');
}
categories.addActiveUser(cid, uid);
});
});
user.onNewPostMade(uid, tid, pid, timestamp);
async.parallel({
content: function(next) {
plugins.fireHook('filter:post.get', postData, function(err, newPostData) {
if (!err) postData = newPostData;
postTools.parse(postData.content, function(err, content) {
next(null, content);
});
});
}
}, function(err, results) {
postData.content = results.content;
callback(postData);
});
plugins.fireHook('action:post.save', postData);
postSearch.index(content, pid);
});
});
} else {
callback(null);
}
});
}
Posts.uploadPostImage = function(image, callback) {
var imgur = require('./imgur');
imgur.setClientID(meta.config.imgurClientID);
if(!image)
return callback('invalid image', null);
imgur.upload(image.data, 'base64', function(err, data) {
require('./imgur').upload(meta.config.imgurClientID, image.data, 'base64', function(err, data) {
if(err) {
callback('Can\'t upload image!', null);
callback(err.message, null);
} else {
if(data.success) {
var img= {url:data.data.link, name:image.name};
callback(null, img);
} else {
winston.error('Can\'t upload image, did you set imgurClientID?');
callback("upload error", null);
}
callback(null, {
url: data.link,
name: image.name
});
}
});
}
@@ -462,7 +502,7 @@ var RDB = require('./redis.js'),
function reIndex(pid, callback) {
Posts.getPostField(pid, 'content', function(content) {
Posts.getPostField(pid, 'content', function(err, content) {
postSearch.remove(pid, function() {
if (content && content.length) {

View File

@@ -58,18 +58,18 @@
*/
RedisDB.hmgetObject = function(key, fields, callback) {
RedisDB.hmget(key, fields, function(err, data) {
if (err === null) {
var returnData = {};
for (var i = 0, ii = fields.length; i < ii; ++i) {
returnData[fields[i]] = data[i];
}
callback(null, returnData);
} else {
console.log(err);
callback(err, null);
if(err) {
return callback(err, null);
}
var returnData = {};
for (var i = 0, ii = fields.length; i < ii; ++i) {
returnData[fields[i]] = data[i];
}
callback(null, returnData);
});
};

View File

@@ -111,7 +111,7 @@ var user = require('./../user.js'),
}
var filename = 'site-logo' + extension;
var uploadPath = path.join(process.cwd(), nconf.get('upload_path'), filename);
var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), filename);
winston.info('Attempting upload to: ' + uploadPath);
@@ -246,7 +246,8 @@ var user = require('./../user.js'),
app.get('/topics', function (req, res) {
topics.getAllTopics(10, null, function (topics) {
res.json({
topics: topics
topics: topics,
notopics: topics.length === 0
});
});
});

View File

@@ -31,6 +31,7 @@ var user = require('./../user.js'),
config.maximumUsernameLength = meta.config.maximumUsernameLength;
config.minimumPasswordLength = meta.config.minimumPasswordLength;
config.useOutgoingLinksPage = meta.config.useOutgoingLinksPage;
config.emailSetup = !!meta.config['email:from'];
res.json(200, config);
});
@@ -137,8 +138,12 @@ var user = require('./../user.js'),
app.get('/recent/:term?', function (req, res) {
var uid = (req.user) ? req.user.uid : 0;
topics.getLatestTopics(uid, 0, 19, req.params.term, function (data) {
res.json(data);
topics.getLatestTopics(uid, 0, 19, req.params.term, function (err, data) {
if (!err) {
res.json(data);
} else {
res.send(500);
}
});
});
@@ -189,7 +194,8 @@ var user = require('./../user.js'),
if (url) {
res.json({
url: url
url: url,
title: meta.config.title
});
} else {
res.status(404);

View File

@@ -1,50 +1,75 @@
var user = require('./../user'),
categories = require('./../categories'),
topics = require('./../topics'),
posts = require('./../posts');
var DebugRoute = function(app) {
app.namespace('/debug', function() {
app.get('/cid/:cid', function (req, res) {
categories.getCategoryData(req.params.cid, function (err, data) {
if (data) {
res.send(data);
} else {
res.send(404, "Category doesn't exist!");
}
});
});
app.get('/tid/:tid', function (req, res) {
topics.getTopicData(req.params.tid, function (data) {
if (data) {
res.send(data);
} else {
res.send(404, "Topic doesn't exist!");
}
});
});
app.namespace('/debug', function() {
app.get('/pid/:pid', function (req, res) {
posts.getPostData(req.params.pid, function (data) {
if (data) {
res.send(data);
} else {
res.send(404, "Post doesn't exist!");
}
});
});
app.get('/uid/:uid', function (req, res) {
app.get('/prune', function(req, res) {
var Notifications = require('../notifications');
if (!req.params.uid)
return res.redirect('/404');
Notifications.prune(new Date(), function() {
console.log('done');
});
res.send();
});
app.get('/uuidtest', function(req, res) {
var Utils = require('../../public/src/utils.js');
res.send(Utils.generateUUID());
user.getUserData(req.params.uid, function (err, data) {
if (data) {
res.send(data);
} else {
res.json(404, {
error: "User doesn't exist!"
});
}
});
});
};
app.get('/cid/:cid', function (req, res) {
categories.getCategoryData(req.params.cid, function (err, data) {
if (data) {
res.send(data);
} else {
res.send(404, "Category doesn't exist!");
}
});
});
app.get('/tid/:tid', function (req, res) {
topics.getTopicData(req.params.tid, function (err, data) {
if (data) {
res.send(data);
} else {
res.send(404, "Topic doesn't exist!");
}
});
});
app.get('/pid/:pid', function (req, res) {
posts.getPostData(req.params.pid, function (err, data) {
if (data) {
res.send(data);
} else {
res.send(404, "Post doesn't exist!");
}
});
});
app.get('/prune', function(req, res) {
var Notifications = require('../notifications');
Notifications.prune(new Date(), function() {
console.log('done');
});
res.send();
});
app.get('/uuidtest', function(req, res) {
var Utils = require('../../public/src/utils.js');
res.send(Utils.generateUUID());
});
});
};
module.exports = DebugRoute;

View File

@@ -1,9 +1,29 @@
"use strict";
var nconf = require('nconf'),
path = require('path'),
fs = require('fs'),
validator = require('validator'),
Plugins = require('../plugins'),
PluginRoutes = function(app) {
app.get('/plugins/fireHook', function(req, res) {
// GET = filter
Plugins.fireHook('filter:' + req.query.hook, req.query.args, function(err, returnData) {
if (typeof returnData === 'object') {
res.json(200, returnData);
} else {
res.send(200, validator.sanitize(returnData).escape());
}
});
});
app.put('/plugins/fireHook', function(req, res) {
// PUT = action
Plugins.fireHook('action:' + req.body.hook, req.body.args);
res.send(200);
});
// Static Assets
app.get('/plugins/:id/*', function(req, res) {
var relPath = req.url.replace('/plugins/' + req.params.id, '');
@@ -15,7 +35,7 @@ var nconf = require('nconf'),
} else {
res.redirect('/404');
}
})
});
} else {
res.redirect('/404');
}

View File

@@ -14,22 +14,6 @@ var user = require('./../user.js'),
(function (User) {
User.createRoutes = function (app) {
app.get('/uid/:uid', function (req, res) {
if (!req.params.uid)
return res.redirect('/404');
user.getUserData(req.params.uid, function (err, data) {
if (data) {
res.send(data);
} else {
res.json(404, {
error: "User doesn't exist!"
});
}
});
});
app.namespace('/users', function () {
app.get('', function (req, res) {
app.build_header({
@@ -175,7 +159,7 @@ var user = require('./../user.js'),
return;
}
var absolutePath = path.join(process.cwd(), nconf.get('upload_path'), path.basename(oldpicture));
var absolutePath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), path.basename(oldpicture));
fs.unlink(absolutePath, function (err) {
if (err) {
@@ -197,7 +181,7 @@ var user = require('./../user.js'),
}
var filename = uid + '-profileimg' + extension;
var uploadPath = path.join(process.cwd(), nconf.get('upload_path'), filename);
var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), filename);
winston.info('Attempting upload to: ' + uploadPath);
@@ -559,6 +543,8 @@ var user = require('./../user.js'),
else
data.emailClass = "hide";
data.websiteName = data.website.replace('http://', '').replace('https://', '');
data.show_banned = data.banned === '1' ? '' : 'hide';
data.uid = uid;

View File

@@ -303,7 +303,7 @@ var RDB = require('./redis.js'),
pids.reverse();
async.detectSeries(pids, function(pid, next) {
posts.getPostField(pid, 'deleted', function(deleted) {
posts.getPostField(pid, 'deleted', function(err, deleted) {
if (deleted === '0') next(true);
else next(false);
});

View File

@@ -18,30 +18,132 @@ var RDB = require('./redis.js'),
(function(Topics) {
Topics.getTopicData = function(tid, callback) {
RDB.hgetall('topic:' + tid, function(err, data) {
if (err === null) {
if(data) {
data.title = validator.sanitize(data.title).escape();
if(data.timestamp) {
data.relativeTime = new Date(parseInt(data.timestamp, 10)).toISOString();
}
Topics.post = function(uid, title, content, category_id, callback) {
if (!category_id)
throw new Error('Attempted to post without a category_id');
if (content)
content = content.trim();
if (title)
title = title.trim();
if (!uid) {
callback(new Error('not-logged-in'), null);
return;
} else if (!title || title.length < meta.config.minimumTitleLength) {
callback(new Error('title-too-short'), null);
return;
} else if (!content || content.length < meta.config.miminumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
user.getUserField(uid, 'lastposttime', function(err, lastposttime) {
if (err) lastposttime = 0;
if (Date.now() - lastposttime < meta.config.postDelay * 1000) {
callback(new Error('too-many-posts'), null);
return;
}
RDB.incr('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);
}
callback(data);
} else {
console.log(err);
var slug = tid + '/' + utils.slugify(title);
var timestamp = Date.now();
RDB.hmset('topic:' + tid, {
'tid': tid,
'uid': uid,
'cid': category_id,
'title': title,
'slug': slug,
'timestamp': timestamp,
'lastposttime': 0,
'postcount': 0,
'viewcount': 0,
'locked': 0,
'deleted': 0,
'pinned': 0
});
topicSearch.index(title, tid);
user.addTopicIdToUser(uid, tid);
// let everyone know that there is an unread topic in this category
RDB.del('cid:' + category_id + ':read_by_uid', function(err, data) {
Topics.markAsRead(tid, uid);
});
// in future it may be possible to add topics to several categories, so leaving the door open here.
RDB.zadd('categories:' + category_id + ':tid', timestamp, tid);
RDB.hincrby('category:' + category_id, 'topic_count', 1);
RDB.incr('totaltopiccount');
feed.updateCategory(category_id);
posts.create(uid, tid, content, function(err, postData) {
if(err) {
return callback(err, null);
} else if(!postData) {
return callback(new Error('invalid-post'), null);
}
// Auto-subscribe the post creator to the newly created topic
threadTools.toggleFollow(tid, uid);
Topics.getTopicForCategoryView(tid, uid, function(topicData) {
callback(null, {
topicData: topicData,
postData: postData
});
});
});
});
});
};
Topics.getTopicData = function(tid, callback) {
RDB.hgetall('topic:' + tid, function(err, data) {
if(err) {
return callback(err, null);
}
if(data) {
data.title = validator.sanitize(data.title).escape();
if(data.timestamp) {
data.relativeTime = new Date(parseInt(data.timestamp, 10)).toISOString();
}
}
callback(null, data);
});
}
Topics.getTopicDataWithUser = function(tid, callback) {
Topics.getTopicData(tid, function(topic) {
Topics.getTopicData(tid, function(err, topic) {
if(err) {
return callback(err, null);
}
user.getUserFields(topic.uid, ['username', 'userslug', 'picture'] , function(err, userData) {
if(err) {
return callback(err, null);
}
topic.username = userData.username;
topic.userslug = userData.userslug
topic.picture = userData.picture;
callback(topic);
callback(null, topic);
});
});
}
@@ -119,26 +221,24 @@ var RDB = require('./redis.js'),
var args = ['topics:recent', '+inf', timestamp - since, 'LIMIT', start, end - start + 1];
RDB.zrevrangebyscore(args, function(err, tids) {
if (err) {
return callback(err);
}
var latestTopics = {
'category_name': 'Recent',
'show_sidebar': 'hidden',
'show_topic_button': 'hidden',
'no_topics_message': 'hidden',
'topic_row_size': 'col-md-12',
'category_id': false,
'topics': []
};
if (!tids || !tids.length) {
latestTopics.no_topics_message = 'show';
callback(latestTopics);
callback(err, latestTopics);
return;
}
Topics.getTopicsByTids(tids, current_user, function(topicData) {
latestTopics.topics = topicData;
callback(latestTopics);
callback(err, latestTopics);
});
});
}
@@ -331,7 +431,7 @@ var RDB = require('./redis.js'),
}
function loadTopic(tid, callback) {
Topics.getTopicData(tid, function(topicData) {
Topics.getTopicData(tid, function(err, topicData) {
if (!topicData) {
return callback(null);
}
@@ -342,6 +442,7 @@ var RDB = require('./redis.js'),
topicData['lock-icon'] = topicData.locked === '1' ? 'icon-lock' : 'none';
topicData['deleted-class'] = topicData.deleted === '1' ? 'deleted' : '';
topicData.unreplied = topicData.postcount === '1';
topicData.username = topicInfo.username;
topicData.userslug = topicInfo.userslug;
topicData.picture = topicInfo.picture;
@@ -381,9 +482,7 @@ var RDB = require('./redis.js'),
Topics.increaseViewCount(tid);
function getTopicData(next) {
Topics.getTopicData(tid, function(topicData) {
next(null, topicData);
});
Topics.getTopicData(tid, next);
}
function getTopicPosts(next) {
@@ -426,6 +525,7 @@ var RDB = require('./redis.js'),
'slug': topicData.slug,
'postcount': topicData.postcount,
'viewcount': topicData.viewcount,
'unreplied': topicData.postcount > 1,
'topic_id': tid,
'expose_tools': privileges.editable ? 1 : 0,
'posts': topicPosts,
@@ -442,9 +542,7 @@ var RDB = require('./redis.js'),
Topics.getTopicForCategoryView = function(tid, uid, callback) {
function getTopicData(next) {
Topics.getTopicDataWithUser(tid, function(topic) {
next(null, topic);
});
Topics.getTopicDataWithUser(tid, next);
}
function getReadStatus(next) {
@@ -473,6 +571,9 @@ var RDB = require('./redis.js'),
hasRead = results[1],
teaser = results[2];
topicData['pin-icon'] = topicData.pinned === '1' ? 'icon-pushpin' : 'none';
topicData['lock-icon'] = topicData.locked === '1' ? 'icon-lock' : 'none';
topicData.badgeclass = hasRead ? '' : 'badge-important';
topicData.teaser_text = teaser.text || '';
topicData.teaser_username = teaser.username || '';
@@ -517,7 +618,7 @@ var RDB = require('./redis.js'),
});
async.each(tids, function(tid, next) {
Topics.getTopicDataWithUser(tid, function(topicData) {
Topics.getTopicDataWithUser(tid, function(err, topicData) {
topics.push(topicData);
next();
});
@@ -546,15 +647,15 @@ var RDB = require('./redis.js'),
}
Topics.getTitleByPid = function(pid, callback) {
posts.getPostField(pid, 'tid', function(tid) {
posts.getPostField(pid, 'tid', function(err, tid) {
Topics.getTopicField(tid, 'title', function(err, title) {
callback(title);
});
});
}
Topics.markUnRead = function(tid) {
RDB.del('tid:' + tid + ':read_by_uid');
Topics.markUnRead = function(tid, callback) {
RDB.del('tid:' + tid + ':read_by_uid', callback);
}
Topics.markAsRead = function(tid, uid) {
@@ -620,36 +721,44 @@ var RDB = require('./redis.js'),
Topics.getTeaser = function(tid, callback) {
threadTools.getLatestUndeletedPid(tid, function(err, pid) {
if (!err) {
posts.getPostFields(pid, ['pid', 'content', 'uid', 'timestamp'], function(postData) {
if (err) {
return callback(err, null);
}
user.getUserFields(postData.uid, ['username', 'userslug', 'picture'], function(err, userData) {
if (err)
return callback(err, null);
posts.getPostFields(pid, ['pid', 'content', 'uid', 'timestamp'], function(err, postData) {
if (err) {
return callback(err, null);
} else if(!postData) {
return callback(new Error('no-teaser-found'));
}
var stripped = postData.content,
timestamp = postData.timestamp,
returnObj = {
"pid": postData.pid,
"username": userData.username,
"userslug": userData.userslug,
"picture": userData.picture,
"timestamp": timestamp
};
user.getUserFields(postData.uid, ['username', 'userslug', 'picture'], function(err, userData) {
if (err) {
return callback(err, null);
}
if (postData.content) {
stripped = postData.content.replace(/>.+\n\n/, '');
postTools.parse(stripped, function(err, stripped) {
returnObj.text = utils.strip_tags(stripped);
callback(null, returnObj);
});
} else {
returnObj.text = '';
var stripped = postData.content,
timestamp = postData.timestamp,
returnObj = {
"pid": postData.pid,
"username": userData.username,
"userslug": userData.userslug,
"picture": userData.picture,
"timestamp": timestamp
};
if (postData.content) {
stripped = postData.content.replace(/>.+\n\n/, '');
postTools.parse(stripped, function(err, stripped) {
returnObj.text = utils.strip_tags(stripped);
callback(null, returnObj);
}
});
});
} else {
returnObj.text = '';
callback(null, returnObj);
}
});
} else callback(new Error('no-teaser-found'));
});
});
}
@@ -663,101 +772,6 @@ var RDB = require('./redis.js'),
});
}
Topics.post = function(uid, title, content, category_id, callback) {
if (!category_id)
throw new Error('Attempted to post without a category_id');
if (content)
content = content.trim();
if (title)
title = title.trim();
if (uid === 0) {
callback(new Error('not-logged-in'), null);
return;
} else if (!title || title.length < meta.config.minimumTitleLength) {
callback(new Error('title-too-short'), null);
return;
} else if (!content || content.length < meta.config.miminumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
user.getUserField(uid, 'lastposttime', function(err, lastposttime) {
if (err) lastposttime = 0;
if (Date.now() - lastposttime < meta.config.postDelay * 1000) {
callback(new Error('too-many-posts'), null);
return;
}
RDB.incr('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);
}
var slug = tid + '/' + utils.slugify(title);
var timestamp = Date.now();
RDB.hmset('topic:' + tid, {
'tid': tid,
'uid': uid,
'cid': category_id,
'title': title,
'slug': slug,
'timestamp': timestamp,
'lastposttime': 0,
'postcount': 0,
'viewcount': 0,
'locked': 0,
'deleted': 0,
'pinned': 0
});
topicSearch.index(title, tid);
user.addTopicIdToUser(uid, tid);
// let everyone know that there is an unread topic in this category
RDB.del('cid:' + category_id + ':read_by_uid', function(err, data) {
Topics.markAsRead(tid, uid);
});
// in future it may be possible to add topics to several categories, so leaving the door open here.
RDB.zadd('categories:' + category_id + ':tid', timestamp, tid);
RDB.hincrby('category:' + category_id, 'topic_count', 1);
RDB.incr('totaltopiccount');
feed.updateCategory(category_id);
posts.create(uid, tid, content, function(postData) {
if (postData) {
// Auto-subscribe the post creator to the newly created topic
threadTools.toggleFollow(tid, uid);
// Notify any users looking at the category that a new topic has arrived
Topics.getTopicForCategoryView(tid, uid, function(topicData) {
io.sockets.in('category_' + category_id).emit('event:new_topic', topicData);
io.sockets.in('recent_posts').emit('event:new_topic', topicData);
io.sockets.in('user/' + uid).emit('event:new_post', {
posts: postData
});
});
callback(null, postData);
}
});
});
});
};
Topics.getTopicField = function(tid, field, callback) {
RDB.hget('topic:' + tid, field, callback);
}
@@ -780,7 +794,10 @@ var RDB = require('./redis.js'),
Topics.isLocked = function(tid, callback) {
Topics.getTopicField(tid, 'locked', function(err, locked) {
callback(locked);
if(err) {
return callback(err, null);
}
callback(null, locked === "1");
});
}
@@ -802,7 +819,7 @@ var RDB = require('./redis.js'),
Topics.getPids(tid, function(err, pids) {
function getUid(pid, next) {
posts.getPostField(pid, 'uid', function(uid) {
posts.getPostField(pid, 'uid', function(err, uid) {
if (err)
return next(err);
uids[uid] = 1;

View File

@@ -9,7 +9,7 @@ var RDB = require('./redis.js'),
schemaDate, thisSchemaDate;
Upgrade.check = function(callback) {
var latestSchema = new Date(2013, 10, 11).getTime();
var latestSchema = new Date(2013, 10, 22).getTime();
RDB.get('schemaDate', function(err, value) {
if (parseInt(value, 10) >= latestSchema) {
@@ -20,7 +20,7 @@ Upgrade.check = function(callback) {
});
};
Upgrade.upgrade = function() {
Upgrade.upgrade = function(callback) {
winston.info('Beginning Redis database schema update');
async.series([
@@ -128,6 +128,52 @@ Upgrade.upgrade = function() {
winston.info('[2013/11/11] Update to postDelay skipped.');
next();
}
},
function(next) {
thisSchemaDate = new Date(2013, 10, 22).getTime();
if (schemaDate < thisSchemaDate) {
RDB.keys('category:*', function(err, categories) {
async.each(categories, function(categoryStr, next) {
var hex;
RDB.hgetall(categoryStr, function(err, categoryObj) {
switch(categoryObj.blockclass) {
case 'category-purple':
hex = '#ab1290';
break;
case 'category-darkblue':
hex = '#004c66';
break;
case 'category-blue':
hex = '#0059b2';
break;
case 'category-darkgreen':
hex = '#004000';
break;
case 'category-orange':
hex = '#ff7a4d';
break;
default:
hex = '#0059b2';
break;
}
RDB.hset(categoryStr, 'bgColor', hex, next);
RDB.hdel(categoryStr, 'blockclass');
});
}, function() {
winston.info('[2013/11/22] Updated Category colours.');
next();
});
});
} else {
winston.info('[2013/11/22] Update to Category colours skipped.');
next();
}
}
// Add new schema updates here
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 12!!!
@@ -136,13 +182,19 @@ Upgrade.upgrade = function() {
RDB.set('schemaDate', thisSchemaDate, function(err) {
if (!err) {
winston.info('[upgrade] Redis schema update complete!');
process.exit();
if (callback) {
callback(err);
} else {
process.exit();
}
} else {
winston.error('[upgrade] Could not update NodeBB schema date!');
process.exit();
}
});
} else {
winston.error('[upgrade] Errors were encountered while updating the NodeBB schema: ' + err.message);
process.exit();
}
});
};

View File

@@ -1,16 +1,20 @@
var utils = require('./../public/src/utils.js'),
RDB = require('./redis.js'),
emailjs = require('emailjs'),
meta = require('./meta.js'),
emailjsServer = emailjs.server.connect(meta.config['email:smtp:host'] || '127.0.0.1'),
bcrypt = require('bcrypt'),
Groups = require('./groups'),
notifications = require('./notifications.js'),
topics = require('./topics.js'),
var bcrypt = require('bcrypt'),
async = require('async'),
emailjs = require('emailjs'),
nconf = require('nconf'),
winston = require('winston'),
userSearch = require('reds').createSearch('nodebbusersearch'),
winston = require('winston');
check = require('validator').check,
sanitize = require('validator').sanitize,
utils = require('./../public/src/utils'),
RDB = require('./redis'),
meta = require('./meta'),
emailjsServer = emailjs.server.connect(meta.config['email:smtp:host'] || '127.0.0.1'),
Groups = require('./groups'),
notifications = require('./notifications'),
topics = require('./topics');
(function(User) {
'use strict';
@@ -114,13 +118,6 @@ var utils = require('./../public/src/utils.js'),
userSearch.index(username, uid);
if (typeof io !== 'undefined') {
io.sockets.emit('user.latest', {
userslug: userslug,
username: username
});
}
if (password !== undefined) {
User.hashPassword(password, function(err, hash) {
User.setUserField(uid, 'password', hash);
@@ -250,6 +247,9 @@ var utils = require('./../public/src/utils.js'),
function updateField(field, next) {
if (data[field] !== undefined && typeof data[field] === 'string') {
data[field] = data[field].trim();
data[field] = sanitize(data[field]).escape();
if (field === 'email') {
var gravatarpicture = User.createGravatarURLFromEmail(data[field]);
User.setUserField(uid, 'gravatarpicture', gravatarpicture);
@@ -271,6 +271,10 @@ var utils = require('./../public/src/utils.js'),
return;
} else if (field === 'signature') {
data[field] = utils.strip_tags(data[field]);
} else if (field === 'website') {
if(data[field].substr(0, 7) !== 'http://' && data[field].substr(0, 8) !== 'https://') {
data[field] = 'http://' + data[field];
}
}
User.setUserField(uid, field, data[field]);
@@ -642,21 +646,6 @@ var utils = require('./../public/src/utils.js'),
});
};
User.latest = function(socket) {
RDB.zrevrange('users:joindate', 0, 0, function(err, uid) {
RDB.handle(err);
User.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
if (!err && userData) {
socket.emit('user.latest', {
userslug: userData.userslug,
username: userData.username
});
}
});
});
};
User.getUidByUsername = function(username, callback) {
RDB.hget('username:uid', username, callback);
};
@@ -1009,7 +998,9 @@ var utils = require('./../public/src/utils.js'),
next(null, notif_data);
});
}, function(err, notifs) {
notifs = notifs.sort(function(a, b) {
notifs = notifs.filter(function(notif) {
return notif !== null;
}).sort(function(a, b) {
return parseInt(b.datetime, 10) - parseInt(a.datetime, 10);
}).map(function(notif) {
notif.datetimeISO = new Date(parseInt(notif.datetime, 10)).toISOString();

View File

@@ -1,31 +1,33 @@
var express = require('express'),
var path = require('path'),
fs = require('fs'),
express = require('express'),
express_namespace = require('express-namespace'),
WebServer = express(),
server = require('http').createServer(WebServer),
RedisStore = require('connect-redis')(express),
path = require('path'),
RDB = require('./redis'),
utils = require('../public/src/utils.js'),
pkg = require('../package.json'),
fs = require('fs'),
user = require('./user.js'),
categories = require('./categories.js'),
posts = require('./posts.js'),
topics = require('./topics.js'),
notifications = require('./notifications.js'),
admin = require('./routes/admin.js'),
userRoute = require('./routes/user.js'),
apiRoute = require('./routes/api.js'),
auth = require('./routes/authentication.js'),
meta = require('./meta.js'),
feed = require('./feed'),
plugins = require('./plugins'),
nconf = require('nconf'),
winston = require('winston'),
validator = require('validator'),
async = require('async'),
logger = require('./logger.js');
pkg = require('../package.json'),
utils = require('../public/src/utils'),
RDB = require('./redis'),
user = require('./user'),
categories = require('./categories'),
posts = require('./posts'),
topics = require('./topics'),
notifications = require('./notifications'),
admin = require('./routes/admin'),
userRoute = require('./routes/user'),
apiRoute = require('./routes/api'),
auth = require('./routes/authentication'),
meta = require('./meta'),
feed = require('./feed'),
plugins = require('./plugins'),
logger = require('./logger');
(function (app) {
"use strict";
@@ -33,17 +35,21 @@ var express = require('express'),
var templates = null,
clientScripts;
// Minify client-side libraries
meta.js.get(function (err, scripts) {
clientScripts = scripts.map(function (script) {
script = {
script: script
};
plugins.ready(function() {
// Minify client-side libraries
meta.js.get(function (err, scripts) {
clientScripts = scripts.map(function (script) {
script = {
script: script
};
return script;
return script;
});
});
});
server.app = app;
/**
@@ -144,7 +150,7 @@ var express = require('express'),
res.locals.csrf_token = req.session._csrf;
// Disable framing
res.setHeader("X-Frame-Options", "DENY");
res.setHeader('X-Frame-Options', 'DENY');
next();
});
@@ -306,9 +312,17 @@ var express = require('express'),
templates = global.templates;
// translate all static templates served by webserver here. ex. footer, logout
translator.translate(templates.footer.toString(), function(parsedTemplate) {
templates.footer = parsedTemplate;
plugins.fireHook('filter:footer.build', '', function(err, appendHTML) {
var footer = templates.footer.parse({
footerHTML: appendHTML
});
translator.translate(footer, function(parsedTemplate) {
templates.footer = parsedTemplate;
});
});
translator.translate(templates.logout.toString(), function(parsedTemplate) {
templates.logout = parsedTemplate;
});
@@ -639,13 +653,39 @@ var express = require('express'),
"Sitemap: " + nconf.get('url') + "sitemap.xml");
});
app.get('/recent.rss', function(req, res) {
var rssPath = path.join(__dirname, '../', 'feeds/recent.rss'),
loadFeed = function () {
fs.readFile(rssPath, function (err, data) {
if (err) {
res.type('text').send(404, "Unable to locate an rss feed at this location.");
} else {
res.type('xml').set('Content-Length', data.length).send(data);
}
});
};
if (!fs.existsSync(rssPath)) {
feed.updateRecent(function (err) {
if (err) {
res.redirect('/404');
} else {
loadFeed();
}
});
} else {
loadFeed();
}
});
app.get('/recent/:term?', function (req, res) {
// TODO consolidate with /recent route as well -> that can be combined into this area. See "Basic Routes" near top.
app.build_header({
req: req,
res: res
}, function (err, header) {
res.send(header + app.create_route("recent/" + req.params.term, null, "recent") + templates.footer);
res.send(header + app.create_route('recent/' + req.params.term, null, 'recent') + templates.footer);
});
});
@@ -676,7 +716,7 @@ var express = require('express'),
req: req,
res: res
}, function (err, header) {
res.send(header + app.create_route("search/" + req.params.term, null, "search") + templates.footer);
res.send(header + app.create_route('search/' + req.params.term, null, 'search') + templates.footer);
});
});
@@ -736,4 +776,4 @@ var express = require('express'),
}(WebServer));
global.server = server;
global.server = server;

View File

@@ -1,36 +1,37 @@
var cookie = require('cookie'),
var cookie = require('cookie'),
express = require('express'),
user = require('./user.js'),
Groups = require('./groups'),
posts = require('./posts.js'),
favourites = require('./favourites.js'),
utils = require('../public/src/utils.js'),
util = require('util'),
topics = require('./topics.js'),
categories = require('./categories.js'),
notifications = require('./notifications.js'),
threadTools = require('./threadTools.js'),
postTools = require('./postTools.js'),
meta = require('./meta.js'),
async = require('async'),
fs = require('fs'),
nconf = require('nconf'),
winston = require('winston'),
RedisStoreLib = require('connect-redis')(express),
RDB = require('./redis'),
util = require('util'),
logger = require('./logger.js'),
fs = require('fs'),
RedisStore = new RedisStoreLib({
client: RDB,
ttl: 60 * 60 * 24 * 14
}),
nconf = require('nconf'),
user = require('./user'),
Groups = require('./groups'),
posts = require('./posts'),
favourites = require('./favourites'),
utils = require('../public/src/utils'),
topics = require('./topics'),
categories = require('./categories'),
notifications = require('./notifications'),
threadTools = require('./threadTools'),
postTools = require('./postTools'),
meta = require('./meta'),
logger = require('./logger'),
socketCookieParser = express.cookieParser(nconf.get('secret')),
admin = {
'categories': require('./admin/categories.js'),
'user': require('./admin/user.js')
'categories': require('./admin/categories'),
'user': require('./admin/user')
},
plugins = require('./plugins'),
winston = require('winston');
plugins = require('./plugins');
var users = {},
@@ -43,8 +44,9 @@ module.exports.logoutUser = function(uid) {
userSockets[uid][i].emit('event:disconnect');
userSockets[uid][i].disconnect();
if(!userSockets[uid])
if(!userSockets[uid]) {
return;
}
}
}
}
@@ -169,11 +171,11 @@ module.exports.init = function(io) {
var anonymousCount = getAnonymousCount(roomName);
if (uids.length === 0) {
io.sockets. in (roomName).emit('api:get_users_in_room', { users: [], anonymousCount:0 });
io.sockets. in (roomName).emit('api:get_users_in_room', { users: [], anonymousCount: anonymousCount });
} else {
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], function(err, users) {
if(!err)
io.sockets. in (roomName).emit('api:get_users_in_room', { users: users, anonymousCount:anonymousCount });
io.sockets. in (roomName).emit('api:get_users_in_room', { users: users, anonymousCount: anonymousCount });
});
}
}
@@ -247,10 +249,6 @@ module.exports.init = function(io) {
posts.getTopicPostStats();
});
socket.on('user.latest', function(data) {
user.latest(socket, data);
});
socket.on('user.email.exists', function(data) {
user.email.exists(socket, data.email);
});
@@ -368,11 +366,24 @@ module.exports.init = function(io) {
posts.emitContentTooShortAlert(socket);
} else if (err.message === 'too-many-posts') {
posts.emitTooManyPostsAlert(socket);
} else {
socket.emit('event:alert', {
title: 'Error',
message: err.message,
type: 'warning',
timeout: 7500
});
}
return;
}
if (result) {
io.sockets.in('category_' + data.category_id).emit('event:new_topic', result.topicData);
io.sockets.in('recent_posts').emit('event:new_topic', result.topicData);
io.sockets.in('user/' + uid).emit('event:new_post', {
posts: result.postData
});
posts.getTopicPostStats();
socket.emit('event:alert', {
@@ -533,7 +544,7 @@ module.exports.init = function(io) {
});
socket.on('api:posts.getRawPost', function(data) {
posts.getPostField(data.pid, 'content', function(raw) {
posts.getPostField(data.pid, 'content', function(err, raw) {
socket.emit('api:posts.getRawPost', {
post: raw
});
@@ -560,15 +571,33 @@ module.exports.init = function(io) {
postTools.edit(uid, data.pid, data.title, data.content, data.images);
});
socket.on('api:posts.delete', function(data) {
postTools.delete(uid, data.pid, function() {
socket.on('api:posts.delete', function(data, callback) {
postTools.delete(uid, data.pid, function(err) {
if(err) {
return callback(err);
}
posts.getTopicPostStats();
io.sockets.in('topic_' + data.tid).emit('event:post_deleted', {
pid: data.pid
});
callback(null);
});
});
socket.on('api:posts.restore', function(data) {
postTools.restore(uid, data.pid, function() {
socket.on('api:posts.restore', function(data, callback) {
postTools.restore(uid, data.pid, function(err) {
if(err) {
return callback(err);
}
posts.getTopicPostStats();
io.sockets.in('topic_' + data.tid).emit('event:post_restored', {
pid: data.pid
});
callback(null);
});
});
@@ -688,7 +717,7 @@ module.exports.init = function(io) {
socket.on('api:composer.push', function(data) {
if (uid > 0 || meta.config.allowGuestPosting === '1') {
if (parseInt(data.tid) > 0) {
topics.getTopicData(data.tid, function(topicData) {
topics.getTopicData(data.tid, function(err, topicData) {
if (data.body)
topicData.body = data.body;
@@ -714,9 +743,7 @@ module.exports.init = function(io) {
async.parallel([
function(next) {
posts.getPostFields(data.pid, ['content'], function(raw) {
next(null, raw);
});
posts.getPostFields(data.pid, ['content'], next);
},
function(next) {
topics.getTitleByPid(data.pid, function(title) {
@@ -739,7 +766,7 @@ module.exports.init = function(io) {
});
socket.on('api:composer.editCheck', function(pid) {
posts.getPostField(pid, 'tid', function(tid) {
posts.getPostField(pid, 'tid', function(err, tid) {
postTools.isMain(pid, tid, function(isMain) {
socket.emit('api:composer.editCheck', {
titleEditable: isMain
@@ -800,8 +827,12 @@ module.exports.init = function(io) {
var start = data.after,
end = start + 9;
topics.getLatestTopics(uid, start, end, data.term, function(latestTopics) {
callback(latestTopics);
topics.getLatestTopics(uid, start, end, data.term, function(err, latestTopics) {
if (!err) {
callback(latestTopics);
} else {
winston.error('[socket api:topics.loadMoreRecentTopics] ' + err.message);
}
});
});

85
tests/topics.js Normal file
View File

@@ -0,0 +1,85 @@
var winston = require('winston');
process.on('uncaughtException', function (err) {
winston.error('Encountered error while running test suite: ' + err.message);
});
var assert = require('assert'),
RDB = require('../mocks/redismock');
// Reds is not technically used in this test suite, but its invocation is required to stop the included
// libraries from trying to connect to the default Redis host/port
var reds = require('reds');
reds.createClient = function () {
return reds.client || (reds.client = RDB);
};
var Topics = require('../src/topics');
describe('Topic\'s', function() {
var topic;
beforeEach(function(){
topic = {
userId: 1,
categoryId: 1,
title: 'Test Topic Title',
content: 'The content of test topic'
};
});
describe('.post', function() {
it('should create a new topic with proper parameters', function(done) {
Topics.post(topic.userId, topic.title, topic.content, topic.categoryId, function(err, result) {
assert.equal(err, null, 'was created with error');
assert.ok(result);
done();
});
});
it('should fail to create new topic with wrong parameters', function(done) {
topic.userId = null;
Topics.post(topic.userId, topic.title, topic.content, topic.categoryId, function(err, result) {
assert.equal(err.message, 'not-logged-in');
done();
});
});
});
describe('Get methods', function() {
var newTopic;
var newPost;
beforeEach(function(done){
Topics.post(topic.userId, topic.title, topic.content, topic.categoryId, function(err, result) {
newTopic = result.topicData;
newPost = result.postData;
done();
});
});
describe('.getTopicData', function() {
it('should not receive errors', function(done) {
Topics.getTopicData(newTopic.tid, done);
});
});
describe('.getTopicDataWithUser', function() {
it('should not receive errors', function(done) {
Topics.getTopicDataWithUser(newTopic.tid, done);
});
});
});
after(function() {
RDB.send_command('flushdb', [], function(error){
if(error){
winston.error(error);
throw new Error(error);
}
});
});
});